Files
class/scripts/update-people-from-issue.mjs
2026-05-03 07:28:13 +08:00

165 lines
4.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFileSync, writeFileSync } from "node:fs";
import { relative, resolve } from "node:path";
const peoplePath = resolve(process.env.PEOPLE_PATH ?? "src/data/people.ts");
const issueBody = process.env.ISSUE_BODY ?? "";
const fields = [
"slug",
"initial",
"name",
"photo",
"location",
"school",
"direction",
"keywords",
"text",
"currentStatus",
"highlight",
"toPastSelf",
"messageToClass",
"favoriteMemory",
"contact"
];
const requiredFields = [
"slug",
"initial",
"name",
"location",
"school",
"direction",
"keywords",
"text",
"currentStatus",
"highlight",
"toPastSelf",
"messageToClass",
"favoriteMemory"
];
const sectionAliases = new Map(fields.map((field) => [field.toLowerCase(), field]));
function normalizeValue(value) {
const trimmed = value.trim();
return trimmed === "_No response_" ? "" : trimmed;
}
function parseIssueForm(body) {
const result = {};
const sectionPattern = /^###\s+(.+?)\s*$/gm;
const matches = [...body.matchAll(sectionPattern)];
for (let index = 0; index < matches.length; index += 1) {
const rawLabel = matches[index][1].trim().toLowerCase();
const field = sectionAliases.get(rawLabel);
if (!field) continue;
const start = matches[index].index + matches[index][0].length;
const end = index + 1 < matches.length ? matches[index + 1].index : body.length;
result[field] = normalizeValue(body.slice(start, end));
}
return result;
}
function assertValidInput(input) {
for (const field of requiredFields) {
if (!input[field]) {
throw new Error(`Issue form is missing required field: ${field}`);
}
}
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(input.slug)) {
throw new Error("slug must use lowercase letters, numbers, and single hyphens only.");
}
}
function readPeopleModule(filePath) {
const source = readFileSync(filePath, "utf8");
const executable = source
.replace("export const peopleIntro =", "const peopleIntro =")
.replace("export const people =", "const people =");
return Function(`${executable}; return { peopleIntro, people };`)();
}
function splitKeywords(value) {
return value
.split(/[、,]/)
.map((keyword) => keyword.trim())
.filter(Boolean);
}
function buildPerson(input, existing = {}) {
const person = { ...existing };
for (const field of fields) {
if (field === "keywords") continue;
if (input[field] || !(field in person)) {
person[field] = input[field] ?? "";
}
}
if (input.keywords) {
person.keywords = splitKeywords(input.keywords);
} else if (!Array.isArray(person.keywords)) {
person.keywords = [];
}
return person;
}
function quote(value) {
return JSON.stringify(value);
}
function formatPerson(person) {
return ` {
slug: ${quote(person.slug)},
initial: ${quote(person.initial)},
name: ${quote(person.name)},
photo: ${quote(person.photo ?? "")},
location: ${quote(person.location)},
school: ${quote(person.school)},
direction: ${quote(person.direction)},
keywords: [${person.keywords.map(quote).join(", ")}],
text: ${quote(person.text)},
currentStatus: ${quote(person.currentStatus)},
highlight: ${quote(person.highlight)},
toPastSelf: ${quote(person.toPastSelf)},
messageToClass: ${quote(person.messageToClass)},
favoriteMemory: ${quote(person.favoriteMemory)},
contact: ${quote(person.contact ?? "")}
}`;
}
function formatPeopleModule(peopleIntro, people) {
return `export const peopleIntro = {
title: ${quote(peopleIntro.title)},
text: ${quote(peopleIntro.text)}
};
export const people = [
${people.map(formatPerson).join(",\n")}
];
`;
}
const input = parseIssueForm(issueBody);
assertValidInput(input);
const { peopleIntro, people } = readPeopleModule(peoplePath);
const existingIndex = people.findIndex((person) => person.slug === input.slug);
if (existingIndex >= 0) {
people[existingIndex] = buildPerson(input, people[existingIndex]);
console.log(`Updated existing person: ${input.slug}`);
} else {
people.push(buildPerson(input));
console.log(`Added new person: ${input.slug}`);
}
writeFileSync(peoplePath, formatPeopleModule(peopleIntro, people), "utf8");
console.log(`Wrote ${relative(process.cwd(), peoplePath)}`);