fix: current-tree package checks support unreleased versions (#104186)

* fix(ci): package unreleased QA builds safely

* style(ci): format QA changelog intent
This commit is contained in:
Peter Steinberger
2026-07-10 22:31:42 -07:00
committed by GitHub
parent 81a201df26
commit 6e9e64bd30
18 changed files with 168 additions and 28 deletions
@@ -119,6 +119,11 @@ on:
required: false
default: false
type: boolean
allow_unreleased_changelog:
description: Allow current-tree QA packaging to use Unreleased notes when no exact version section exists
required: false
default: false
type: boolean
workflow_call:
inputs:
advisory:
@@ -131,6 +136,11 @@ on:
required: false
default: false
type: boolean
allow_unreleased_changelog:
description: Allow current-tree QA packaging to use Unreleased notes when no exact version section exists
required: false
default: false
type: boolean
ref:
description: Ref, tag, or SHA to validate
required: true
@@ -378,6 +388,7 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
NODE_VERSION: "24.15.0"
OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog }}
jobs:
validate_selected_ref:
@@ -1906,13 +1917,21 @@ jobs:
- name: Pack OpenClaw package for Docker E2E
if: steps.plan.outputs.needs_package == '1' && inputs.package_artifact_name == '' && inputs.package_artifact_run_id == ''
env:
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog }}
shell: bash
run: |
set -euo pipefail
mkdir -p .artifacts/docker-e2e-package
node scripts/package-openclaw-for-docker.mjs \
--output-dir .artifacts/docker-e2e-package \
package_args=(
--output-dir .artifacts/docker-e2e-package
--output-name openclaw-current.tgz
)
# Only current-tree QA callers opt in; release and package-candidate callers stay strict.
if [[ "$ALLOW_UNRELEASED_CHANGELOG" == "true" ]]; then
package_args+=(--allow-unreleased-changelog)
fi
node scripts/package-openclaw-for-docker.mjs "${package_args[@]}"
- name: Validate OpenClaw Docker E2E package
id: package
@@ -32,6 +32,7 @@ jobs:
include_release_path_suites: true
include_openwebui: true
include_live_suites: true
allow_unreleased_changelog: true
shared_image_artifact_namespace: scheduled-live
shared_image_policy: no-push-artifact
secrets:
+1
View File
@@ -549,6 +549,7 @@ jobs:
include_openwebui: false
docker_lanes: ${{ needs.preflight.outputs.plugin_prerelease_docker_lanes }}
targeted_docker_lane_group_size: 4
allow_unreleased_changelog: true
include_live_suites: false
live_models_only: false
shared_image_artifact_namespace: plugin-prerelease
+11 -2
View File
@@ -98,6 +98,7 @@ const TRUSTED_WORKFLOW_INPUTS = new Map([
["published_upgrade_survivor_baseline", "publishedUpgradeSurvivorBaseline"],
["published_upgrade_survivor_baselines", "publishedUpgradeSurvivorBaselines"],
["published_upgrade_survivor_scenarios", "publishedUpgradeSurvivorScenarios"],
["allow_unreleased_changelog", "allowUnreleasedChangelog"],
]);
const REUSE_INPUT_KEYS = [
@@ -106,6 +107,7 @@ const REUSE_INPUT_KEYS = [
"publishedUpgradeSurvivorBaseline",
"publishedUpgradeSurvivorBaselines",
"publishedUpgradeSurvivorScenarios",
"allowUnreleasedChangelog",
];
const WORKFLOW_INPUT_RE = /(?:^|\s)-f\s+([a-z0-9_]+)=('([^']*)'|[^\s]+)/gu;
@@ -122,8 +124,12 @@ function trustedReuseInputsFromCommand(command) {
if (!target || !value) {
continue;
}
const normalized =
target === "bareImage" || target === "functionalImage" ? maybeGhcrImage(value) : value;
let normalized = value;
if (target === "bareImage" || target === "functionalImage") {
normalized = maybeGhcrImage(value);
} else if (target === "allowUnreleasedChangelog" && value !== "true") {
normalized = "";
}
if (normalized) {
inputs[target] = normalized;
}
@@ -235,6 +241,9 @@ function ghWorkflowCommand(lanes, ref, workflow, reuseInputs = {}) {
if (reuseInputs.bareImage || reuseInputs.functionalImage) {
fields.push("-f", "shared_image_policy=existing-only");
}
if (reuseInputs.allowUnreleasedChangelog === "true") {
fields.push("-f", "allow_unreleased_changelog=true");
}
if (reuseInputs.publishedUpgradeSurvivorBaseline) {
fields.push(
"-f",
+1
View File
@@ -107,6 +107,7 @@ resolve_package_tgz() {
echo "==> Pack OpenClaw tarball"
PACKAGE_TGZ="$(
node scripts/package-openclaw-for-docker.mjs \
--allow-unreleased-changelog \
--skip-build \
--output-dir "$PACK_DIR" \
--output-name openclaw-current.tgz
@@ -178,6 +178,7 @@ export async function packOpenClaw(input: {
"node",
[
"scripts/package-openclaw-for-docker.mjs",
"--allow-unreleased-changelog",
"--skip-build",
"--source-dir",
repoRoot,
+1
View File
@@ -315,6 +315,7 @@ docker_e2e_prepare_package_tgz() {
local pack_status=0
package_tgz="$(
node "$ROOT_DIR/scripts/package-openclaw-for-docker.mjs" \
--allow-unreleased-changelog \
--output-dir "$pack_dir" \
--output-name openclaw-current.tgz
)" || pack_status="$?"
+33 -22
View File
@@ -276,6 +276,7 @@ function maybeGhcrImage(value) {
export function githubWorkflowRerunCommand(laneNames, ref, env = process.env) {
const workflowRef = githubWorkflowRef(env);
const releasePath = env.OPENCLAW_DOCKER_ALL_PROFILE === RELEASE_PATH_PROFILE;
const allowUnreleasedChangelog = env.OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG === "true";
const bareImage = maybeGhcrImage(env.OPENCLAW_DOCKER_E2E_BARE_IMAGE);
const functionalImage = maybeGhcrImage(env.OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE);
const fields = [
@@ -297,6 +298,9 @@ export function githubWorkflowRerunCommand(laneNames, ref, env = process.env) {
"-f",
"live_models_only=false",
];
if (allowUnreleasedChangelog) {
fields.push("-f", "allow_unreleased_changelog=true");
}
if (env.OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC) {
fields.push(
"-f",
@@ -417,38 +421,38 @@ async function writeTimingStore(timingStore, results) {
console.log(`==> Docker lane timings: ${timingStore.file}`);
}
export async function writeRunSummary(logDir, summary) {
export async function writeRunSummary(logDir, summary, env = process.env) {
const file = path.join(logDir, "summary.json");
const payload = {
...summary,
packageArtifactName: process.env.OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME || undefined,
packageArtifactName: env.OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME || undefined,
finishedAt: new Date().toISOString(),
github: {
ref: process.env.GITHUB_REF_NAME || undefined,
repository: process.env.GITHUB_REPOSITORY || undefined,
runId: process.env.GITHUB_RUN_ID || undefined,
ref: env.GITHUB_REF_NAME || undefined,
repository: env.GITHUB_REPOSITORY || undefined,
runId: env.GITHUB_RUN_ID || undefined,
runUrl:
process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
env.GITHUB_SERVER_URL && env.GITHUB_REPOSITORY && env.GITHUB_RUN_ID
? `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`
: undefined,
selectedSha: process.env.OPENCLAW_DOCKER_E2E_SELECTED_SHA || undefined,
sha: process.env.GITHUB_SHA || undefined,
workflow: process.env.GITHUB_WORKFLOW || undefined,
selectedSha: env.OPENCLAW_DOCKER_E2E_SELECTED_SHA || undefined,
sha: env.GITHUB_SHA || undefined,
workflow: env.GITHUB_WORKFLOW || undefined,
},
version: 1,
};
await fs.promises.writeFile(file, `${JSON.stringify(payload, null, 2)}\n`);
await writeFailureIndex(logDir, payload);
await writeFailureIndex(logDir, payload, env);
console.log(`==> Docker run summary: ${file}`);
}
async function writeFailureIndex(logDir, summary) {
async function writeFailureIndex(logDir, summary, env) {
const ref =
summary.github?.selectedSha ||
process.env.OPENCLAW_DOCKER_E2E_SELECTED_SHA ||
env.OPENCLAW_DOCKER_E2E_SELECTED_SHA ||
summary.github?.sha ||
summary.github?.ref ||
process.env.GITHUB_SHA ||
env.GITHUB_SHA ||
"HEAD";
const failures = Array.isArray(summary.failures)
? summary.failures
@@ -456,7 +460,9 @@ async function writeFailureIndex(logDir, summary) {
const workflowRerunFailures = failures.filter((failure) => failure.targetable !== false);
const lanes = failures.map((failure) => ({
ghWorkflowCommand:
failure.targetable === false ? undefined : githubWorkflowRerunCommand([failure.name], ref),
failure.targetable === false
? undefined
: githubWorkflowRerunCommand([failure.name], ref, env),
image: failure.image,
imageKind: failure.imageKind,
lane: failure.name,
@@ -474,18 +480,19 @@ async function writeFailureIndex(logDir, summary) {
? githubWorkflowRerunCommand(
workflowRerunFailures.map((failure) => failure.name),
ref,
env,
)
: undefined,
generatedAt: new Date().toISOString(),
lanes,
note: "Targeted GitHub reruns repack the exact selected ref and reuse only GHCR-backed shared images when the generated command includes docker_e2e_*_image inputs.",
images: summary.images,
packageArtifactName: process.env.OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME || undefined,
packageArtifactName: env.OPENCLAW_DOCKER_E2E_PACKAGE_ARTIFACT_NAME || undefined,
ref,
runUrl: summary.github?.runUrl,
status: summary.status,
version: 1,
workflow: process.env.OPENCLAW_DOCKER_E2E_WORKFLOW || DEFAULT_GITHUB_WORKFLOW,
workflow: env.OPENCLAW_DOCKER_E2E_WORKFLOW || DEFAULT_GITHUB_WORKFLOW,
};
await fs.promises.writeFile(
path.join(logDir, "failures.json"),
@@ -981,10 +988,13 @@ async function prepareOpenClawPackage(baseEnv, logDir) {
const packageTgz = path.join(packDir, "openclaw-current.tgz");
await runForeground(
"Prepare OpenClaw package once",
`node scripts/package-openclaw-for-docker.mjs --output-dir ${shellQuote(packDir)} --output-name openclaw-current.tgz`,
`node scripts/package-openclaw-for-docker.mjs --allow-unreleased-changelog --output-dir ${shellQuote(packDir)} --output-name openclaw-current.tgz`,
baseEnv,
);
await fs.promises.access(packageTgz);
// Preserve current-tree package intent in generated GitHub reruns; otherwise a local QA
// failure would retry through the strict release path and fail before reaching its lane.
baseEnv.OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG = "true";
baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ = packageTgz;
baseEnv.OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD = "0";
baseEnv.OPENCLAW_NPM_ONBOARD_HOST_BUILD = "0";
@@ -1492,6 +1502,7 @@ async function main() {
});
baseEnv.OPENCLAW_DOCKER_E2E_IMAGE =
process.env.OPENCLAW_DOCKER_E2E_IMAGE || baseEnv.OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE;
const writeSummary = (summary) => writeRunSummary(logDir, summary, baseEnv);
appendExtension(baseEnv, "matrix");
appendExtension(baseEnv, "acpx");
appendExtension(baseEnv, "codex");
@@ -1653,7 +1664,7 @@ async function main() {
const allResults = [...mainResult.results];
await writeTimingStore(timingStore, mainResult.results);
if (failFast && failures.length > 0) {
await writeRunSummary(logDir, {
await writeSummary({
chunk: releaseChunk || undefined,
failures,
image: baseEnv.OPENCLAW_DOCKER_E2E_IMAGE,
@@ -1692,7 +1703,7 @@ async function main() {
console.log("==> Provider-sensitive Docker tail lanes: none");
}
if (failures.length > 0) {
await writeRunSummary(logDir, {
await writeSummary({
chunk: releaseChunk || undefined,
failures,
image: baseEnv.OPENCLAW_DOCKER_E2E_IMAGE,
@@ -1721,7 +1732,7 @@ async function main() {
}
await writeTimingStore(timingStore, allResults);
if (failures.length > 0) {
await writeRunSummary(logDir, {
await writeSummary({
chunk: releaseChunk || undefined,
failures,
image: baseEnv.OPENCLAW_DOCKER_E2E_IMAGE,
@@ -1739,7 +1750,7 @@ async function main() {
await printFailureSummary(failures, tailLines);
process.exit(1);
}
await writeRunSummary(logDir, {
await writeSummary({
chunk: releaseChunk || undefined,
failures,
image: baseEnv.OPENCLAW_DOCKER_E2E_IMAGE,
+1
View File
@@ -324,6 +324,7 @@ prepare_update_tarball() {
fi
UPDATE_EXPECT_VERSION="$(read_candidate_version)"
package_args=(
--allow-unreleased-changelog
--source-dir "$ROOT_DIR"
--output-dir "$UPDATE_DIR"
--pack-json "$pack_json_file"
+26
View File
@@ -183,6 +183,7 @@ describe("scripts/test-docker-all scheduler", () => {
const registryCommand = githubWorkflowRerunCommand(["install-e2e"], "b".repeat(40), {
OPENCLAW_DOCKER_E2E_BARE_IMAGE: "ghcr.io/openclaw/openclaw-docker-e2e-bare:test",
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE: "ghcr.io/openclaw/openclaw-docker-e2e-functional:test",
OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG: "true",
OPENCLAW_DOCKER_E2E_WORKFLOW_REF: "main",
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: "openclaw@2026.5.3",
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: "openclaw@2026.5.3 openclaw@2026.5.2",
@@ -196,9 +197,34 @@ describe("scripts/test-docker-all scheduler", () => {
"docker_e2e_functional_image='ghcr.io/openclaw/openclaw-docker-e2e-functional:test'",
);
expect(registryCommand).toContain("shared_image_policy=existing-only");
expect(registryCommand).toContain("allow_unreleased_changelog=true");
expectDeclaredDispatchInputs(registryCommand);
});
it("preserves ephemeral package intent in generated failure reruns", async () => {
const logDir = createTempDir("openclaw-docker-all-rerun-intent-");
try {
await writeRunSummary(
logDir,
{
failures: [{ name: "install-e2e", status: 1 }],
lanes: [],
status: "failed",
},
{
...process.env,
GITHUB_SHA: "c".repeat(40),
OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG: "true",
},
);
const failureIndex = JSON.parse(readFileSync(path.join(logDir, "failures.json"), "utf8"));
expect(failureIndex.combinedGhWorkflowCommand).toContain("allow_unreleased_changelog=true");
} finally {
rmSync(logDir, { force: true, recursive: true });
}
});
it("rejects loose numeric resource limit env vars before scheduling lanes", () => {
const logDir = mkdtempSync(`${tmpdir()}/openclaw-docker-all-`);
try {
+2
View File
@@ -3881,6 +3881,7 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}"
it("mounts root helper modules imported by bare Docker E2E scripts", () => {
const helper = readFileSync(DOCKER_E2E_PACKAGE_HELPER_PATH, "utf8");
expect(helper).toContain("--allow-unreleased-changelog");
expect(helper).toContain(
'-v "$ROOT_DIR/scripts/windows-cmd-helpers.mjs:/app/scripts/windows-cmd-helpers.mjs:ro"',
);
@@ -3891,6 +3892,7 @@ heartbeat_elapsed="\${BASH_REMATCH[1]}"
it("preserves pnpm lookup paths for scheduled Docker child lanes", () => {
const scheduler = readFileSync(DOCKER_ALL_SCHEDULER_PATH, "utf8");
expect(scheduler).toContain("--allow-unreleased-changelog");
expect(scheduler).toContain("env.PNPM_HOME");
expect(scheduler).toContain("env.npm_execpath ? path.dirname(env.npm_execpath)");
expect(scheduler).toContain("path.dirname(process.execPath)");
+4 -2
View File
@@ -439,7 +439,7 @@ describe("Docker E2E helper CLIs", () => {
lanes: [
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'full-release-validation-temp-deleted' -f package_artifact_run_id='12345' -f package_artifact_name='docker-e2e-package' -f docker_e2e_bare_image='ghcr.io/openclaw/openclaw-bare:test' -f published_upgrade_survivor_baselines='openclaw@2026.5.3' -f published_upgrade_survivor_scenarios='plugin-dependency-cleanup' -f unsafe_input='do-not-copy'",
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'full-release-validation-temp-deleted' -f package_artifact_run_id='12345' -f package_artifact_name='docker-e2e-package' -f docker_e2e_bare_image='ghcr.io/openclaw/openclaw-bare:test' -f published_upgrade_survivor_baselines='openclaw@2026.5.3' -f published_upgrade_survivor_scenarios='plugin-dependency-cleanup' -f allow_unreleased_changelog=true -f unsafe_input='do-not-copy'",
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
@@ -469,6 +469,7 @@ describe("Docker E2E helper CLIs", () => {
expect(combinedCommand).toContain(
"published_upgrade_survivor_scenarios='plugin-dependency-cleanup'",
);
expect(combinedCommand).toContain("allow_unreleased_changelog=true");
expect(combinedCommand).not.toContain("unsafe_input");
expect(result.stdout).not.toContain("package_artifact_run_id=");
expect(result.stdout).not.toContain("package_artifact_name=");
@@ -503,7 +504,7 @@ describe("Docker E2E helper CLIs", () => {
lanes: [
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'release/2026.6' -f published_upgrade_survivor_baselines='openclaw@2026.5.3'",
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'release/2026.6' -f published_upgrade_survivor_baselines='openclaw@2026.5.3' -f allow_unreleased_changelog=1",
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
@@ -542,6 +543,7 @@ describe("Docker E2E helper CLIs", () => {
expect(result.stdout).not.toContain(
"docker_lanes='published-upgrade-survivor-openclaw-2026-5-3 published-upgrade-survivor-openclaw-2026-5-2'",
);
expect(result.stdout).not.toContain("allow_unreleased_changelog");
} finally {
rmSync(root, { force: true, recursive: true });
}
+38
View File
@@ -48,6 +48,10 @@ describe("package-changelog", () => {
"2026.5.28",
"Unreleased",
]);
expect(resolvePackageChangelogVersions("2026.5.29", { allowUnreleased: true })).toEqual([
"2026.5.29",
"Unreleased",
]);
});
it("extracts only the package version stable release section", () => {
@@ -143,6 +147,22 @@ Docs: https://docs.openclaw.ai
`);
});
it("does not fall back when exact non-publish notes fail safety checks", () => {
const source = changelog`
# Changelog
## Unreleased
- Pending development package notes with enough release detail.
## 2026.5.29
- Tiny.
## 2026.5.28
- Older stable release notes with enough detail.
`;
expect(() =>
extractCurrentPackageChangelog(source, "2026.5.29", { allowUnreleased: true }),
).toThrow("Packaged changelog section for 2026.5.29 is only 7 body bytes");
});
it("fails closed when the packaged changelog is unexpectedly large", () => {
const source = changelog`
# Changelog
@@ -188,6 +208,24 @@ Docs: https://docs.openclaw.ai
}
});
it("recovers an interrupted ephemeral QA package with the default restore path", async () => {
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-package-changelog-"));
const unreleasedChangelog = cumulativeChangelog.replace(
"- Pending note.",
"- Pending release note with enough detail.",
);
try {
writeFileSync(path.join(root, "package.json"), '{"version":"2026.5.29"}\n', "utf8");
writeFileSync(path.join(root, "CHANGELOG.md"), unreleasedChangelog, "utf8");
await expect(preparePackageChangelog(root, { allowUnreleased: true })).resolves.toBe(true);
await expect(restorePackageChangelog(root)).resolves.toBe(true);
expect(readFileSync(path.join(root, "CHANGELOG.md"), "utf8")).toBe(unreleasedChangelog);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("refuses to restore stale backups over current changelog edits", async () => {
const root = mkdtempSync(path.join(os.tmpdir(), "openclaw-package-changelog-"));
const backupPath = path.join(
@@ -402,6 +402,7 @@ describe("Parallels smoke model selection", () => {
expect(packageArtifact).toContain("Wait for Parallels package lock");
expect(packageArtifact).toContain("export async function packageVersionFromTgz");
expect(packageArtifact).toContain("export async function packOpenClaw");
expect(packageArtifact).toContain('"--allow-unreleased-changelog"');
expect(packageArtifact).toContain("function resolveNpmPackTarballFilename");
expect(packageArtifact).toContain("filename !== path.basename(filename)");
expect(packageArtifact).toContain("filename !== path.win32.basename(filename)");
@@ -498,6 +498,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mjs", () => {
include_release_path_suites: false,
include_repo_e2e: false,
live_models_only: false,
allow_unreleased_changelog: true,
ref: "${{ needs.preflight.outputs.checkout_revision }}",
shared_image_artifact_namespace: "plugin-prerelease",
shared_image_policy: "no-push-artifact",
@@ -345,6 +345,7 @@ describe("release validation no-push transport", () => {
shared_image_artifact_namespace: "release-live",
shared_image_policy: "no-push-artifact",
});
expect(live.with).not.toHaveProperty("allow_unreleased_changelog");
expect(docker.with).toMatchObject({
package_artifact_digest: "${{ needs.prepare_release_package.outputs.artifact_digest }}",
package_artifact_id: "${{ needs.prepare_release_package.outputs.artifact_id }}",
@@ -359,6 +360,7 @@ describe("release validation no-push transport", () => {
shared_image_artifact_namespace: "release-docker",
shared_image_policy: "no-push-artifact",
});
expect(docker.with).not.toHaveProperty("allow_unreleased_changelog");
expect(acceptance.with).toMatchObject({
artifact_digest: "${{ needs.prepare_release_package.outputs.artifact_digest }}",
artifact_id: "${{ needs.prepare_release_package.outputs.artifact_id }}",
@@ -399,6 +401,8 @@ describe("release validation no-push transport", () => {
package_source_sha: "${{ needs.resolve_package.outputs.package_source_sha }}",
package_version: "${{ needs.resolve_package.outputs.package_version }}",
});
expect(standardAcceptance.with).not.toHaveProperty("allow_unreleased_changelog");
expect(registryAcceptance.with).not.toHaveProperty("allow_unreleased_changelog");
expect(standardAcceptance.if).toContain("shared_image_policy == 'no-push-artifact'");
expectReadOnlyPackagePermission(standardAcceptance);
expect(registryAcceptance.if).toContain("shared_image_policy == 'existing-only'");
@@ -832,6 +836,7 @@ describe("release validation no-push transport", () => {
expect(permissionAt(scheduled.permissions, "packages", "none")).toBe("read");
expectReadOnlyPackagePermission(scheduledValidation);
expect(scheduledValidation.with).toMatchObject({
allow_unreleased_changelog: true,
shared_image_artifact_namespace: "scheduled-live",
shared_image_policy: "no-push-artifact",
});
@@ -132,6 +132,24 @@ describe("scripts/plan-release-workflow-matrix.mjs", () => {
required: false,
type: "boolean",
});
expect(definition.on.workflow_dispatch.inputs.allow_unreleased_changelog).toEqual(
definition.on.workflow_call.inputs.allow_unreleased_changelog,
);
expect(definition.on.workflow_call.inputs.allow_unreleased_changelog).toMatchObject({
default: false,
required: false,
type: "boolean",
});
expect(definition.env.OPENCLAW_DOCKER_E2E_ALLOW_UNRELEASED_CHANGELOG).toBe(
"${{ inputs.allow_unreleased_changelog }}",
);
const packageStep = definition.jobs.prepare_docker_e2e_image.steps.find(
(step) => step.name === "Pack OpenClaw package for Docker E2E",
);
expect(packageStep.env.ALLOW_UNRELEASED_CHANGELOG).toBe(
"${{ inputs.allow_unreleased_changelog }}",
);
expect(packageStep.run).toContain("package_args+=(--allow-unreleased-changelog)");
});
it.each(PROFILE_EXPECTATIONS)(
@@ -872,6 +872,7 @@ printf 'status=%s\\n' "$status"
const script = readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain('node "$HARNESS_ROOT/scripts/package-openclaw-for-docker.mjs"');
expect(script).toContain("--allow-unreleased-changelog");
expect(script).toContain('--source-dir "$ROOT_DIR"');
expect(script).toContain('--pack-json "$pack_json_file"');
expect(script).toContain("--skip-build");
@@ -1171,6 +1172,7 @@ describe("bun global install smoke", () => {
const packageHelper = readFileSync(DOCKER_E2E_PACKAGE_HELPER_PATH, "utf8");
expect(script).toContain("node scripts/package-openclaw-for-docker.mjs");
expect(script).toContain("--allow-unreleased-changelog");
expect(script).toContain("--skip-build");
expect(script).toContain("--output-name openclaw-current.tgz");
expect(script).not.toContain("npm pack --ignore-scripts --json --pack-destination");