fix(ci): allow unreleased notes in QA packages

This commit is contained in:
Vincent Koc
2026-07-10 21:29:57 -07:00
parent bcb10e2a28
commit 1403b29436
6 changed files with 97 additions and 13 deletions

View File

@@ -965,6 +965,7 @@ jobs:
node scripts/build-all.mjs qaRuntime
pnpm ui:build
node scripts/package-openclaw-for-docker.mjs \
--allow-unreleased-changelog \
--skip-build \
--output-dir .artifacts/qa-e2e/smoke-ci-package \
--output-name openclaw-current.tgz

View File

@@ -22,7 +22,7 @@ const PRERELEASE_VERSION_PATTERN =
/**
* Resolves acceptable changelog headings for a package version.
*/
export function resolvePackageChangelogVersions(packageVersion) {
export function resolvePackageChangelogVersions(packageVersion, options = {}) {
const match = RELEASE_VERSION_PATTERN.exec(packageVersion);
if (!match) {
throw new Error(
@@ -32,7 +32,7 @@ export function resolvePackageChangelogVersions(packageVersion) {
if (PRERELEASE_VERSION_PATTERN.test(packageVersion)) {
return [packageVersion, match[1], UNRELEASED_HEADING];
}
return [packageVersion];
return options.allowUnreleased ? [packageVersion, UNRELEASED_HEADING] : [packageVersion];
}
function splitLines(content) {
@@ -61,8 +61,8 @@ function extractPreamble(lines, firstHeadingIndex) {
/**
* Extracts the current release changelog section for package publishing.
*/
export function extractCurrentPackageChangelog(content, packageVersion) {
const targetVersions = resolvePackageChangelogVersions(packageVersion);
export function extractCurrentPackageChangelog(content, packageVersion, options = {}) {
const targetVersions = resolvePackageChangelogVersions(packageVersion, options);
const lines = splitLines(content);
const headings = findLevelTwoHeadings(lines);
const heading = targetVersions
@@ -125,11 +125,17 @@ export async function restorePackageChangelog(cwd = process.cwd()) {
try {
expectedPackaged = extractCurrentPackageChangelog(backup, packageVersion);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`Refusing to restore stale packaged changelog backup from ${BACKUP_PATH}: ${message}`,
{ cause: error },
);
try {
expectedPackaged = extractCurrentPackageChangelog(backup, packageVersion, {
allowUnreleased: true,
});
} catch {
const message = error instanceof Error ? error.message : String(error);
throw new Error(
`Refusing to restore stale packaged changelog backup from ${BACKUP_PATH}: ${message}`,
{ cause: error },
);
}
}
if (current !== expectedPackaged) {
throw new Error(
@@ -145,13 +151,13 @@ export async function restorePackageChangelog(cwd = process.cwd()) {
/**
* Writes packaged changelog content while preserving a restorable backup.
*/
export async function preparePackageChangelog(cwd = process.cwd()) {
export async function preparePackageChangelog(cwd = process.cwd(), options = {}) {
await restorePackageChangelog(cwd);
const changelogPath = path.join(cwd, CHANGELOG_PATH);
const backupPath = path.join(cwd, BACKUP_PATH);
const original = await readFile(changelogPath, "utf8");
const packageVersion = await readPackageVersion(cwd);
const packaged = extractCurrentPackageChangelog(original, packageVersion);
const packaged = extractCurrentPackageChangelog(original, packageVersion, options);
if (packaged === original) {
return false;
}

View File

@@ -137,6 +137,7 @@ function resolvePackedOpenClawFileName(value) {
export function parseArgs(argv) {
const options = {
allowUnreleasedChangelog: false,
outputDir: "",
outputName: "",
packJson: "",
@@ -154,7 +155,9 @@ export function parseArgs(argv) {
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--output-dir") {
if (arg === "--allow-unreleased-changelog") {
setOnce(arg, "allowUnreleasedChangelog", true);
} else if (arg === "--output-dir") {
setOnce("--output-dir", "outputDir", readOptionValue(argv, index, arg));
index += 1;
} else if (arg?.startsWith("--output-dir=")) {
@@ -653,7 +656,12 @@ export async function prepareBundledAiRuntimePackage(
export async function packOpenClawPackageForDocker(sourceDir, outputDir, options = {}) {
const runCaptureImpl = options.runCaptureImpl ?? runCapture;
const prepareChangelog = options.prepareChangelog ?? preparePackageChangelog;
const prepareChangelog =
options.prepareChangelog ??
((cwd) =>
preparePackageChangelog(cwd, {
allowUnreleased: options.allowUnreleasedChangelog,
}));
const restoreChangelog = options.restoreChangelog ?? restorePackageChangelog;
const prepareBundledAiRuntime = options.prepareBundledAiRuntime ?? prepareBundledAiRuntimePackage;
const packTool = options.pnpmPack ? "pnpm" : "npm";
@@ -740,6 +748,7 @@ async function main() {
);
const tarball = await packOpenClawPackageForDocker(sourceDir, outputDir, {
allowUnreleasedChangelog: options.allowUnreleasedChangelog,
outputName: options.outputName,
packJsonPath: options.packJson,
pnpmPack: options.pnpmPack,

View File

@@ -91,9 +91,11 @@ describe("package-openclaw-for-docker", () => {
".artifacts/docker/pack.json",
"--source-dir",
"/repo",
"--allow-unreleased-changelog",
"--skip-build",
]),
).toEqual({
allowUnreleasedChangelog: true,
outputDir: ".artifacts/docker",
outputName: "openclaw-current.tgz",
packJson: ".artifacts/docker/pack.json",
@@ -118,6 +120,10 @@ describe("package-openclaw-for-docker", () => {
["--output-dir", ["--output-dir", "one", "--output-dir=two"]],
["--output-name", ["--output-name", "one.tgz", "--output-name=two.tgz"]],
["--pack-json", ["--pack-json", "one.json", "--pack-json=two.json"]],
[
"--allow-unreleased-changelog",
["--allow-unreleased-changelog", "--allow-unreleased-changelog"],
],
["--pnpm-pack", ["--pnpm-pack", "--pnpm-pack"]],
["--source-dir", ["--source-dir", "/repo-a", "--source-dir=/repo-b"]],
["--skip-build", ["--skip-build", "--skip-build"]],
@@ -423,6 +429,48 @@ describe("package-openclaw-for-docker", () => {
]);
});
it("packages Unreleased notes for explicitly non-publish stable artifacts", async () => {
const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-unreleased-package-"));
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-unreleased-output-"));
const sourceChangelog = [
"# Changelog",
"",
"## Unreleased",
"### Fixes",
"- Pending release notes with enough detail.",
"",
"## 2026.5.28",
"- Previous release notes with enough detail.",
"",
].join("\n");
fs.writeFileSync(
path.join(sourceDir, "package.json"),
'{"name":"openclaw","version":"2026.5.29"}\n',
);
fs.writeFileSync(path.join(sourceDir, "CHANGELOG.md"), sourceChangelog);
try {
const tarball = await packOpenClawPackageForDocker(sourceDir, outputDir, {
allowUnreleasedChangelog: true,
prepareBundledAiRuntime: skipBundledAiRuntime,
runCaptureImpl: async () => {
const packagedChangelog = fs.readFileSync(path.join(sourceDir, "CHANGELOG.md"), "utf8");
expect(packagedChangelog).toContain("## Unreleased");
expect(packagedChangelog).not.toContain("## 2026.5.28");
const packedPath = path.join(outputDir, "openclaw-2026.5.29.tgz");
fs.writeFileSync(packedPath, "package");
return "openclaw-2026.5.29.tgz\n";
},
});
expect(tarball).toBe(path.join(outputDir, "openclaw-2026.5.29.tgz"));
expect(fs.readFileSync(path.join(sourceDir, "CHANGELOG.md"), "utf8")).toBe(sourceChangelog);
} finally {
fs.rmSync(sourceDir, { recursive: true, force: true });
fs.rmSync(outputDir, { recursive: true, force: true });
}
});
it("uses pnpm pack when requested", async () => {
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-pnpm-pack-"));
const calls: string[] = [];

View File

@@ -2166,6 +2166,7 @@ describe("ci workflow guards", () => {
expect(smokeBuildStep.run).toContain("pnpm ui:build");
expect(smokeBuildStep.env.OPENCLAW_BUILD_PRIVATE_QA).toBe("1");
expect(smokeBuildStep.run).toContain("--skip-build");
expect(smokeBuildStep.run).toContain("--allow-unreleased-changelog");
expect(workflow.jobs["qa-smoke-ci-artifacts"]).toBeUndefined();
expect(workflow.jobs["qa-smoke-ci"]).toBeUndefined();
expect(smokeProfileJob.needs).toEqual(["preflight"]);

View File

@@ -124,6 +124,25 @@ Docs: https://docs.openclaw.ai
);
});
it("allows Unreleased notes for explicitly non-publish stable artifacts", () => {
const unreleasedChangelog = cumulativeChangelog.replace(
"- Pending note.",
"- Pending release note with enough detail.",
);
expect(
extractCurrentPackageChangelog(unreleasedChangelog, "2026.5.29", {
allowUnreleased: true,
}),
).toBe(changelog`
# Changelog
Docs: https://docs.openclaw.ai
## Unreleased
### Fixes
- Pending release note with enough detail.
`);
});
it("fails closed when the packaged changelog is unexpectedly large", () => {
const source = changelog`
# Changelog