Files
tutti/apps/desktop/scripts/upsert-release-changelog.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

132 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
import { readFile, writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
const schemaVersion = "tutti.desktop.changelog.v1";
function createEmptyChangelog() {
return {
schemaVersion,
generatedAt: new Date().toISOString(),
entries: []
};
}
function parseExistingChangelog(value) {
if (!value.trim()) {
return createEmptyChangelog();
}
let parsed;
try {
parsed = JSON.parse(value);
} catch (error) {
throw new Error(
`Existing changelog is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
);
}
if (
!parsed ||
typeof parsed !== "object" ||
parsed.schemaVersion !== schemaVersion ||
!Array.isArray(parsed.entries)
) {
throw new Error(`Existing changelog must use schema ${schemaVersion}`);
}
return {
schemaVersion,
generatedAt:
typeof parsed.generatedAt === "string"
? parsed.generatedAt
: new Date().toISOString(),
entries: parsed.entries
};
}
function toChangelogEntry(summary) {
return {
tag: summary.tag,
version: summary.version,
channel: summary.channel,
prerelease: Boolean(summary.prerelease),
releasedAt: summary.generatedAt,
gitSha: summary.targetCommit,
compare: summary.compare,
zh: summary.zh,
en: summary.en
};
}
function compareEntries(left, right) {
return String(right.releasedAt ?? "").localeCompare(
String(left.releasedAt ?? "")
);
}
function upsertChangelogEntry(existing, summary) {
const changelog =
typeof existing === "string" ? parseExistingChangelog(existing) : existing;
const entry = toChangelogEntry(summary);
const entries = [
entry,
...changelog.entries.filter(
(candidate) =>
candidate.tag !== entry.tag && candidate.version !== entry.version
)
].sort(compareEntries);
return {
schemaVersion,
generatedAt: new Date().toISOString(),
entries
};
}
async function readOptionalFile(path) {
try {
return await readFile(path, "utf8");
} catch {
return "";
}
}
async function main() {
const [existingChangelogPath, summaryPath, outputChangelogPath] =
process.argv.slice(2);
if (!existingChangelogPath || !summaryPath || !outputChangelogPath) {
throw new Error(
"Usage: node apps/desktop/scripts/upsert-release-changelog.mjs <existing-changelog-json-path> <summary-json-path> <output-changelog-json-path>"
);
}
const existing = await readOptionalFile(existingChangelogPath);
const summary = JSON.parse(await readFile(summaryPath, "utf8"));
const changelog = upsertChangelogEntry(existing, summary);
await writeFile(
outputChangelogPath,
`${JSON.stringify(changelog, null, 2)}\n`,
"utf8"
);
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
void main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
}
export {
createEmptyChangelog,
parseExistingChangelog,
schemaVersion,
toChangelogEntry,
upsertChangelogEntry
};