mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-19 02:11:35 +00:00
fix(ci): allow unreleased notes in QA packages
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user