fix(package): reuse bundled artifact for install smoke

This commit is contained in:
Vincent Koc
2026-07-06 22:42:57 +02:00
committed by Vincent Koc
parent 9d86728795
commit 74bb2ad7a5
4 changed files with 153 additions and 21 deletions
+72 -12
View File
@@ -133,6 +133,7 @@ export function parseArgs(argv) {
const options = {
outputDir: "",
outputName: "",
packJson: "",
skipBuild: false,
sourceDir: ROOT_DIR,
};
@@ -164,6 +165,15 @@ export function parseArgs(argv) {
"outputName",
readEqualsOptionValue(arg.slice("--output-name=".length), "--output-name"),
);
} else if (arg === "--pack-json") {
setOnce("--pack-json", "packJson", readOptionValue(argv, index, arg));
index += 1;
} else if (arg?.startsWith("--pack-json=")) {
setOnce(
"--pack-json",
"packJson",
readEqualsOptionValue(arg.slice("--pack-json=".length), "--pack-json"),
);
} else if (arg === "--skip-build") {
setOnce(arg, "skipBuild", true);
} else if (arg === "--source-dir") {
@@ -373,6 +383,20 @@ async function runCapture(command, args, cwd, options = {}) {
async function newestOpenClawTarball(outputDir, packOutput) {
let fromOutput = "";
try {
const parsed = JSON.parse(packOutput);
if (Array.isArray(parsed)) {
for (const entry of parsed) {
if (typeof entry?.filename !== "string") {
continue;
}
const filename = resolvePackedOpenClawFileName(entry.filename);
if (filename) {
fromOutput = filename;
}
}
}
} catch {}
for (const line of packOutput.split(/\r?\n/u)) {
const filename = resolvePackedOpenClawFileName(line);
if (filename) {
@@ -400,6 +424,30 @@ async function newestOpenClawTarball(outputDir, packOutput) {
return path.join(outputDir, packed);
}
async function writePackJson(packOutput, tarball, packJsonPath, sourceDir) {
if (!packJsonPath) {
return;
}
let parsed;
try {
parsed = JSON.parse(packOutput);
} catch (error) {
throw new Error("npm pack --json output was not valid JSON", { cause: error });
}
if (!Array.isArray(parsed)) {
throw new Error("npm pack --json output must be an array");
}
const filename = path.basename(tarball);
for (const entry of parsed) {
if (entry && typeof entry === "object" && typeof entry.filename === "string") {
entry.filename = filename;
}
}
const target = path.resolve(sourceDir, packJsonPath);
await fs.mkdir(path.dirname(target), { recursive: true });
await fs.writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`);
}
async function cleanPackedOpenClawTarballs(outputDir) {
let entries;
try {
@@ -597,9 +645,17 @@ export async function packOpenClawPackageForDocker(sourceDir, outputDir, options
try {
await cleanPackedOpenClawTarballs(outputDir);
cleanupBundledAiRuntime = await prepareBundledAiRuntime(sourceDir, outputDir, runCaptureImpl);
const packArgs = [
"pack",
...(options.packJsonPath ? ["--json"] : []),
"--silent",
"--ignore-scripts",
"--pack-destination",
outputDir,
];
packOutput = await runCaptureImpl(
"npm",
["pack", "--silent", "--ignore-scripts", "--pack-destination", outputDir],
packArgs,
sourceDir,
{
deferForwardedSignalExit: true,
@@ -616,7 +672,17 @@ export async function packOpenClawPackageForDocker(sourceDir, outputDir, options
await restoreChangelog(sourceDir);
}
}
return await newestOpenClawTarball(outputDir, packOutput);
let tarball = await newestOpenClawTarball(outputDir, packOutput);
if (options.outputName) {
const target = path.join(outputDir, options.outputName);
if (target !== tarball) {
await fs.rm(target, { force: true });
await fs.rename(tarball, target);
tarball = target;
}
}
await writePackJson(packOutput, tarball, options.packJsonPath, sourceDir);
return tarball;
}
async function main() {
@@ -651,16 +717,10 @@ async function main() {
},
);
let tarball = await packOpenClawPackageForDocker(sourceDir, outputDir);
if (options.outputName) {
const target = path.join(outputDir, options.outputName);
if (target !== tarball) {
await fs.rm(target, { force: true });
await fs.rename(tarball, target);
tarball = target;
}
}
const tarball = await packOpenClawPackageForDocker(sourceDir, outputDir, {
outputName: options.outputName,
packJsonPath: options.packJson,
});
console.error("==> Checking OpenClaw package tarball");
const checkStartedAt = Date.now();
+14 -5
View File
@@ -299,6 +299,8 @@ prepare_update_tarball() {
local baseline_pack_json
local pack_json_file
local baseline_pack_json_file
local package_args
local package_tgz
local packed_update_version
pack_json_file="${UPDATE_DIR}/pack.json"
baseline_pack_json_file="${UPDATE_DIR}/baseline-pack.json"
@@ -316,13 +318,20 @@ prepare_update_tarball() {
UPDATE_EXPECT_VERSION="$(
node -p 'JSON.parse(require("node:fs").readFileSync("package.json", "utf8")).version'
)"
node --import tsx scripts/write-package-dist-inventory.ts
node scripts/check-package-dist-imports.mjs "$ROOT_DIR"
quiet_npm pack --ignore-scripts --json --pack-destination "$UPDATE_DIR" >"$pack_json_file"
package_args=(
--output-dir "$UPDATE_DIR"
--pack-json "$pack_json_file"
--skip-build
)
package_tgz="$(node scripts/package-openclaw-for-docker.mjs "${package_args[@]}")"
UPDATE_TGZ_FILE="$(basename "$package_tgz")"
fi
UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"
if [[ -z "$UPDATE_PACKAGE_SPEC" ]]; then
node scripts/check-openclaw-package-tarball.mjs "${UPDATE_DIR}/${UPDATE_TGZ_FILE}"
node scripts/check-openclaw-package-tarball.mjs \
--require-bundled-workspace-deps \
"${UPDATE_DIR}/${UPDATE_TGZ_FILE}"
else
UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"
fi
print_pack_audit "update" "$pack_json_file"
assert_pack_unpacked_size_budget "update" "$pack_json_file"
@@ -86,6 +86,8 @@ describe("package-openclaw-for-docker", () => {
"--output-dir",
".artifacts/docker",
"--output-name=openclaw-current.tgz",
"--pack-json",
".artifacts/docker/pack.json",
"--source-dir",
"/repo",
"--skip-build",
@@ -93,6 +95,7 @@ describe("package-openclaw-for-docker", () => {
).toEqual({
outputDir: ".artifacts/docker",
outputName: "openclaw-current.tgz",
packJson: ".artifacts/docker/pack.json",
skipBuild: true,
sourceDir: "/repo",
});
@@ -112,6 +115,7 @@ describe("package-openclaw-for-docker", () => {
const duplicateCases = [
["--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"]],
["--source-dir", ["--source-dir", "/repo-a", "--source-dir=/repo-b"]],
["--skip-build", ["--skip-build", "--skip-build"]],
] satisfies Array<[string, string[]]>;
@@ -383,6 +387,61 @@ describe("package-openclaw-for-docker", () => {
]);
});
it("writes npm pack metadata for renamed package artifacts", async () => {
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-pack-json-"));
const packJsonPath = path.join(outputDir, "pack.json");
try {
const tarball = await packOpenClawPackageForDocker("/repo", outputDir, {
outputName: "openclaw-current.tgz",
packJsonPath,
prepareBundledAiRuntime: skipBundledAiRuntime,
prepareChangelog: async () => {},
restoreChangelog: async () => {},
runCaptureImpl: async (
command: string,
args: string[],
_cwd: string,
options: { deferForwardedSignalExit?: boolean },
) => {
expect(command).toBe("npm");
expect(args).toEqual([
"pack",
"--json",
"--silent",
"--ignore-scripts",
"--pack-destination",
outputDir,
]);
expect(options.deferForwardedSignalExit).toBe(true);
fs.writeFileSync(path.join(outputDir, "openclaw-2026.5.28.tgz"), "package");
return JSON.stringify([
{
entryCount: 1,
filename: "openclaw-2026.5.28.tgz",
size: 7,
unpackedSize: 7,
version: "2026.5.28",
},
]);
},
});
expect(tarball).toBe(path.join(outputDir, "openclaw-current.tgz"));
expect(JSON.parse(fs.readFileSync(packJsonPath, "utf8"))).toEqual([
{
entryCount: 1,
filename: "openclaw-current.tgz",
size: 7,
unpackedSize: 7,
version: "2026.5.28",
},
]);
} finally {
fs.rmSync(outputDir, { force: true, recursive: true });
}
});
it("rejects path-like npm pack stdout before resolving Docker package tarballs", async () => {
for (const filename of [
"../openclaw-2026.6.17.tgz",
+8 -4
View File
@@ -612,6 +612,7 @@ describe("test-install-sh-docker", () => {
const script = readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain("read_pack_tarball_filename()");
expect(script).toContain('UPDATE_TGZ_FILE="$(basename "$package_tgz")"');
expect(script).toContain('UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"');
expect(script).toContain(
'BASELINE_TGZ_FILE="$(read_pack_tarball_filename "$baseline_pack_json_file")"',
@@ -644,13 +645,16 @@ describe("test-install-sh-docker", () => {
}
});
it("writes the package dist inventory before packing ignore-scripts tarballs", () => {
it("uses the package artifact helper for local update tarballs", () => {
const script = readFileSync(SCRIPT_PATH, "utf8");
expect(script).toContain("node --import tsx scripts/write-package-dist-inventory.ts");
expect(script).toContain('node scripts/check-package-dist-imports.mjs "$ROOT_DIR"');
expect(script).toContain("quiet_npm pack --ignore-scripts");
expect(script).toContain("node scripts/package-openclaw-for-docker.mjs");
expect(script).toContain('--pack-json "$pack_json_file"');
expect(script).toContain("--skip-build");
expect(script).not.toContain("node --import tsx scripts/write-package-dist-inventory.ts");
expect(script).not.toContain("quiet_npm pack --ignore-scripts --json");
expect(script).toContain("node scripts/check-openclaw-package-tarball.mjs");
expect(script).toContain("--require-bundled-workspace-deps");
});
it("runs candidate tarballs through the installer script instead of direct npm", () => {