mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(release): validate changelog before atomic write
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
renameSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
@@ -2004,6 +2012,19 @@ function releaseChecks(changelog, version, releaseTags) {
|
||||
return checks;
|
||||
}
|
||||
|
||||
function writeFileAtomic(filePath, contents) {
|
||||
const directory = path.dirname(filePath);
|
||||
mkdirSync(directory, { recursive: true });
|
||||
const tempDirectory = mkdtempSync(path.join(directory, `.${path.basename(filePath)}.tmp-`));
|
||||
const tempPath = path.join(tempDirectory, path.basename(filePath));
|
||||
try {
|
||||
writeFileSync(tempPath, contents);
|
||||
renameSync(tempPath, filePath);
|
||||
} finally {
|
||||
rmSync(tempDirectory, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
@@ -2173,32 +2194,32 @@ function main() {
|
||||
ledger,
|
||||
relationships.directCommits,
|
||||
);
|
||||
|
||||
if (options.manifestPath) {
|
||||
writeFileSync(options.manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
writeFileAtomic(options.manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
}
|
||||
|
||||
let candidateChangelog = changelog;
|
||||
let candidateSection = section;
|
||||
if (options.writeLedger) {
|
||||
changelog = replaceLedger(
|
||||
candidateChangelog = replaceLedger(
|
||||
changelog,
|
||||
section,
|
||||
ledger.ledger,
|
||||
ledger.pullRequests,
|
||||
relationships.directCommits,
|
||||
);
|
||||
writeFileSync("CHANGELOG.md", changelog);
|
||||
section = sectionFor(changelog, options.version);
|
||||
candidateSection = sectionFor(candidateChangelog, options.version);
|
||||
}
|
||||
|
||||
const errors = ledgerChecks(
|
||||
section,
|
||||
candidateSection,
|
||||
ledger.pullRequests,
|
||||
nodes,
|
||||
relationships.directCommits,
|
||||
source.shippedBaselines,
|
||||
);
|
||||
const github = options.checkGithub
|
||||
? releaseChecks(changelog, options.version, options.releaseTags)
|
||||
? releaseChecks(candidateChangelog, options.version, options.releaseTags)
|
||||
: [];
|
||||
for (const check of github) {
|
||||
if (!check.matches) {
|
||||
@@ -2207,6 +2228,11 @@ function main() {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (errors.length === 0) {
|
||||
if (options.writeLedger) {
|
||||
writeFileAtomic("CHANGELOG.md", candidateChangelog);
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
base: options.base,
|
||||
|
||||
@@ -470,6 +470,57 @@ describe("release-note verification", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("leaves CHANGELOG.md untouched when the rendered ledger fails validation", () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "openclaw-release-notes-"));
|
||||
try {
|
||||
git(cwd, ["init", "-q"]);
|
||||
const changelog = [
|
||||
"# Changelog",
|
||||
"",
|
||||
"## 2026.7.1",
|
||||
"",
|
||||
"### Highlights",
|
||||
"",
|
||||
"- Only one highlight.",
|
||||
"",
|
||||
"### Changes",
|
||||
"",
|
||||
"### Fixes",
|
||||
"",
|
||||
].join("\n");
|
||||
writeFileSync(join(cwd, "CHANGELOG.md"), changelog);
|
||||
git(cwd, ["add", "CHANGELOG.md"]);
|
||||
git(cwd, ["commit", "-qm", "initial"]);
|
||||
const manifestPath = join(cwd, "release-manifest.json");
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
verifier,
|
||||
"--base",
|
||||
"HEAD",
|
||||
"--target",
|
||||
"HEAD",
|
||||
"--main-ref",
|
||||
"HEAD",
|
||||
"--manifest",
|
||||
manifestPath,
|
||||
"--version",
|
||||
"2026.7.1",
|
||||
"--write-ledger",
|
||||
],
|
||||
{ cwd, encoding: "utf8" },
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stdout).toContain("1 errors");
|
||||
expect(JSON.parse(readFileSync(manifestPath, "utf8")).version).toBe("2026.7.1");
|
||||
expect(readFileSync(join(cwd, "CHANGELOG.md"), "utf8")).toBe(changelog);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects a release base that is not an ancestor of the target", () => {
|
||||
const cwd = mkdtempSync(join(tmpdir(), "openclaw-release-notes-"));
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user