mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
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:
committed by
GitHub
parent
81a201df26
commit
6e9e64bd30
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user