#!/usr/bin/env node import { readFile, writeFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; import { buildLimitedGithubReleaseBody } from "./lib/githubReleaseBody.mjs"; const SECTION_START = ""; const SECTION_END = ""; const REVIEW_START = ""; const REVIEW_END = ""; const SUGGESTION_START = ""; const SUGGESTION_END = ""; function escapeRegex(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function sectionPattern(start, end) { return new RegExp( `\\n*${escapeRegex(start)}[\\s\\S]*?${escapeRegex(end)}\\n*`, "g" ); } function removeSection(body, start, end) { return body.replace(sectionPattern(start, end), "\n").trimEnd(); } function extractSection(body, start, end) { const match = body.match(sectionPattern(start, end)); return match?.[0]?.trim() ?? ""; } function renderLocaleSummary(title, localeSummary) { const lines = [`### ${title}`, "", localeSummary.headline, ""]; for (const section of localeSummary.sections ?? []) { lines.push(`#### ${section.title}`); for (const item of section.items ?? []) { lines.push(`- ${item}`); } lines.push(""); } return lines; } function renderReviewSection(summary) { return [ REVIEW_START, "## Release Notes", "", ...renderLocaleSummary("中文", summary.zh), ...renderLocaleSummary("English", summary.en), REVIEW_END ].join("\n"); } function renderSuggestionSection(summary) { return [ SUGGESTION_START, "
", "Current generated suggestions (review only)", "", ...renderLocaleSummary("中文建议", summary.zh), ...renderLocaleSummary("English suggestions", summary.en), "
", SUGGESTION_END ].join("\n"); } function removeManagedSection(body) { return removeSection(body, SECTION_START, SECTION_END); } function renderReleaseSummarySection(summary) { return renderReviewSection(summary); } function buildUpdatedReleaseBody({ existingBody, summary }) { const existingReview = extractSection(existingBody, REVIEW_START, REVIEW_END); let cleanedBody = removeManagedSection(existingBody); cleanedBody = removeSection(cleanedBody, REVIEW_START, REVIEW_END); cleanedBody = removeSection(cleanedBody, SUGGESTION_START, SUGGESTION_END); return buildLimitedGithubReleaseBody({ existingBody: cleanedBody, leadingSections: [ existingReview || renderReviewSection(summary), renderSuggestionSection(summary) ] }); } function buildPublishedReleaseBody(body) { return removeSection(body, SUGGESTION_START, SUGGESTION_END); } function parseLocaleSection(sectionBody, title, nextTitle = null) { const start = sectionBody.indexOf(`### ${title}`); if (start < 0) throw new Error(`Missing review locale: ${title}`); const contentStart = start + `### ${title}`.length; const next = nextTitle ? sectionBody.indexOf(`### ${nextTitle}`, contentStart) : sectionBody.length; const lines = sectionBody .slice(contentStart, next < 0 ? sectionBody.length : next) .split("\n") .map((line) => line.trim()); const headline = lines.find((line) => line && !line.startsWith("#")); if (!headline) throw new Error(`${title} review headline is required`); const sections = []; let current = null; for (const line of lines) { if (line.startsWith("#### ")) { current = { title: line.slice(5).trim(), items: [] }; sections.push(current); } else if ( current && line && !line.startsWith("#") && !line.startsWith("