mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(release): keep source-ref packaging self-contained (#105360)
* fix(release): make ref packaging harness self-contained * docs(agents): preserve cwd after PR merge * test(release): track package harness temp dirs * style(test): format package harness imports
This commit is contained in:
@@ -199,6 +199,7 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- CI polling: exact SHA, relevant checks only, minimal fields. Skip routine noise (`Auto response`, `Labeler`, docs agents, performance/stale). Logs only after failure/completion or concrete need.
|
||||
- Trusted-workflow release-branch CI: pass `target_ref` + `release_candidate_ref`; never `release_gate` (requires workflow head == target).
|
||||
- Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init <PR>`, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init <PR>`, validate them with `scripts/pr review-validate-artifacts <PR>`, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run <PR>` and `scripts/pr merge-run <PR>`. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`.
|
||||
- After `scripts/pr merge-run` removes its worktree, `cd` to a persistent repo before follow-up commands.
|
||||
- `scripts/pr` review JSON: land-ready recommendation `READY FOR /prepare-pr`, `issueValidation.status=valid`; never `APPROVE`.
|
||||
|
||||
## Code
|
||||
|
||||
@@ -6,7 +6,6 @@ import { spawn } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as tar from "tar";
|
||||
import { DOCKER_SELECTED_PLUGIN_BUILD_IDS_ENV } from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import { preparePackageChangelog, restorePackageChangelog } from "./package-changelog.mjs";
|
||||
|
||||
@@ -515,7 +514,14 @@ export async function prepareBundledAiRuntimePackage(
|
||||
const extractAiRuntime =
|
||||
options.extractAiRuntime ??
|
||||
((tarballPath, destination) =>
|
||||
Promise.resolve(tar.x({ cwd: destination, file: tarballPath, strip: 1 })));
|
||||
// Source-ref validation runs this trusted harness outside the candidate's dependency tree.
|
||||
// Keep extraction on the system tar contract so only the candidate checkout needs install.
|
||||
run("tar", ["-xzf", tarballPath, "-C", destination, "--strip-components=1"], destination, {
|
||||
timeoutMs: resolveTimeoutMs(
|
||||
"OPENCLAW_DOCKER_PACKAGE_PACK_TIMEOUT_MS",
|
||||
DEFAULT_PACKAGE_PACK_TIMEOUT_MS,
|
||||
),
|
||||
}));
|
||||
const originalPackageJson = await fs.readFile(packageJsonPath, "utf8");
|
||||
let packageJson;
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { DOCKER_SELECTED_PLUGIN_BUILD_IDS_ENV } from "../../../../scripts/lib/bundled-plugin-build-entries.mjs";
|
||||
import {
|
||||
buildPackageArtifacts,
|
||||
@@ -14,8 +14,10 @@ import {
|
||||
prepareBundledAiRuntimePackage,
|
||||
runCommandForTest,
|
||||
} from "../../../../scripts/package-openclaw-for-docker.mjs";
|
||||
import { useAutoCleanupTempDirTracker } from "../../../helpers/temp-dir.js";
|
||||
|
||||
const skipBundledAiRuntime = async (): Promise<() => Promise<void>> => async () => {};
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
function isProcessAlive(pid: number): boolean {
|
||||
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
||||
@@ -134,6 +136,45 @@ describe("package-openclaw-for-docker", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("loads from a trusted harness checkout without installed dependencies", async () => {
|
||||
const tempRoot = tempDirs.make("openclaw-package-harness-");
|
||||
const copiedFiles = [
|
||||
"scripts/package-openclaw-for-docker.mjs",
|
||||
"scripts/package-changelog.mjs",
|
||||
"scripts/lib/bundled-plugin-build-entries.mjs",
|
||||
"scripts/lib/bundled-plugin-paths.mjs",
|
||||
"scripts/lib/optional-bundled-clusters.mjs",
|
||||
];
|
||||
try {
|
||||
for (const relativePath of copiedFiles) {
|
||||
const target = path.join(tempRoot, relativePath);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.copyFileSync(relativePath, target);
|
||||
}
|
||||
const result = await new Promise<{ status: number | null; stderr: string }>(
|
||||
(resolve, reject) => {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[path.join(tempRoot, "scripts/package-openclaw-for-docker.mjs"), "--invalid"],
|
||||
{ cwd: tempRoot, stdio: ["ignore", "ignore", "pipe"] },
|
||||
);
|
||||
let stderr = "";
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += String(chunk);
|
||||
});
|
||||
child.on("error", reject);
|
||||
child.on("close", (status) => resolve({ status, stderr }));
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain("unknown argument: --invalid");
|
||||
expect(result.stderr).not.toContain("ERR_MODULE_NOT_FOUND");
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects pnpm pack with npm metadata output", () => {
|
||||
expect(parseArgs(["--pnpm-pack"]).pnpmPack).toBe(true);
|
||||
expect(() => parseArgs(["--pnpm-pack", "--pack-json", "pack.json"])).toThrow(
|
||||
|
||||
@@ -778,6 +778,17 @@ describe("package acceptance workflow", () => {
|
||||
expect(workflow).toContain("package_integrity=${PACKAGE_INTEGRITY_RESULT}");
|
||||
});
|
||||
|
||||
it("keeps ref packaging independent of workflow-checkout dependencies", () => {
|
||||
const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
|
||||
const resolveJob = workflow.slice(
|
||||
workflow.indexOf(" resolve_package:"),
|
||||
workflow.indexOf(" package_integrity:"),
|
||||
);
|
||||
|
||||
expect(resolveJob).toContain("scripts/resolve-openclaw-package-candidate.mjs");
|
||||
expect(resolveJob).not.toContain("pnpm install");
|
||||
});
|
||||
|
||||
it("offers bounded product profiles and can run Telegram against the resolved artifact", () => {
|
||||
const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
|
||||
const npmTelegramWorkflow = readFileSync(NPM_TELEGRAM_WORKFLOW, "utf8");
|
||||
|
||||
Reference in New Issue
Block a user