Skip to content

Commit 74bb2ad

Browse files
committed
fix(package): reuse bundled artifact for install smoke
1 parent 9d86728 commit 74bb2ad

4 files changed

Lines changed: 153 additions & 21 deletions

File tree

scripts/package-openclaw-for-docker.mjs

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export function parseArgs(argv) {
133133
const options = {
134134
outputDir: "",
135135
outputName: "",
136+
packJson: "",
136137
skipBuild: false,
137138
sourceDir: ROOT_DIR,
138139
};
@@ -164,6 +165,15 @@ export function parseArgs(argv) {
164165
"outputName",
165166
readEqualsOptionValue(arg.slice("--output-name=".length), "--output-name"),
166167
);
168+
} else if (arg === "--pack-json") {
169+
setOnce("--pack-json", "packJson", readOptionValue(argv, index, arg));
170+
index += 1;
171+
} else if (arg?.startsWith("--pack-json=")) {
172+
setOnce(
173+
"--pack-json",
174+
"packJson",
175+
readEqualsOptionValue(arg.slice("--pack-json=".length), "--pack-json"),
176+
);
167177
} else if (arg === "--skip-build") {
168178
setOnce(arg, "skipBuild", true);
169179
} else if (arg === "--source-dir") {
@@ -373,6 +383,20 @@ async function runCapture(command, args, cwd, options = {}) {
373383

374384
async function newestOpenClawTarball(outputDir, packOutput) {
375385
let fromOutput = "";
386+
try {
387+
const parsed = JSON.parse(packOutput);
388+
if (Array.isArray(parsed)) {
389+
for (const entry of parsed) {
390+
if (typeof entry?.filename !== "string") {
391+
continue;
392+
}
393+
const filename = resolvePackedOpenClawFileName(entry.filename);
394+
if (filename) {
395+
fromOutput = filename;
396+
}
397+
}
398+
}
399+
} catch {}
376400
for (const line of packOutput.split(/\r?\n/u)) {
377401
const filename = resolvePackedOpenClawFileName(line);
378402
if (filename) {
@@ -400,6 +424,30 @@ async function newestOpenClawTarball(outputDir, packOutput) {
400424
return path.join(outputDir, packed);
401425
}
402426

427+
async function writePackJson(packOutput, tarball, packJsonPath, sourceDir) {
428+
if (!packJsonPath) {
429+
return;
430+
}
431+
let parsed;
432+
try {
433+
parsed = JSON.parse(packOutput);
434+
} catch (error) {
435+
throw new Error("npm pack --json output was not valid JSON", { cause: error });
436+
}
437+
if (!Array.isArray(parsed)) {
438+
throw new Error("npm pack --json output must be an array");
439+
}
440+
const filename = path.basename(tarball);
441+
for (const entry of parsed) {
442+
if (entry && typeof entry === "object" && typeof entry.filename === "string") {
443+
entry.filename = filename;
444+
}
445+
}
446+
const target = path.resolve(sourceDir, packJsonPath);
447+
await fs.mkdir(path.dirname(target), { recursive: true });
448+
await fs.writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`);
449+
}
450+
403451
async function cleanPackedOpenClawTarballs(outputDir) {
404452
let entries;
405453
try {
@@ -597,9 +645,17 @@ export async function packOpenClawPackageForDocker(sourceDir, outputDir, options
597645
try {
598646
await cleanPackedOpenClawTarballs(outputDir);
599647
cleanupBundledAiRuntime = await prepareBundledAiRuntime(sourceDir, outputDir, runCaptureImpl);
648+
const packArgs = [
649+
"pack",
650+
...(options.packJsonPath ? ["--json"] : []),
651+
"--silent",
652+
"--ignore-scripts",
653+
"--pack-destination",
654+
outputDir,
655+
];
600656
packOutput = await runCaptureImpl(
601657
"npm",
602-
["pack", "--silent", "--ignore-scripts", "--pack-destination", outputDir],
658+
packArgs,
603659
sourceDir,
604660
{
605661
deferForwardedSignalExit: true,
@@ -616,7 +672,17 @@ export async function packOpenClawPackageForDocker(sourceDir, outputDir, options
616672
await restoreChangelog(sourceDir);
617673
}
618674
}
619-
return await newestOpenClawTarball(outputDir, packOutput);
675+
let tarball = await newestOpenClawTarball(outputDir, packOutput);
676+
if (options.outputName) {
677+
const target = path.join(outputDir, options.outputName);
678+
if (target !== tarball) {
679+
await fs.rm(target, { force: true });
680+
await fs.rename(tarball, target);
681+
tarball = target;
682+
}
683+
}
684+
await writePackJson(packOutput, tarball, options.packJsonPath, sourceDir);
685+
return tarball;
620686
}
621687

622688
async function main() {
@@ -651,16 +717,10 @@ async function main() {
651717
},
652718
);
653719

654-
let tarball = await packOpenClawPackageForDocker(sourceDir, outputDir);
655-
656-
if (options.outputName) {
657-
const target = path.join(outputDir, options.outputName);
658-
if (target !== tarball) {
659-
await fs.rm(target, { force: true });
660-
await fs.rename(tarball, target);
661-
tarball = target;
662-
}
663-
}
720+
const tarball = await packOpenClawPackageForDocker(sourceDir, outputDir, {
721+
outputName: options.outputName,
722+
packJsonPath: options.packJson,
723+
});
664724

665725
console.error("==> Checking OpenClaw package tarball");
666726
const checkStartedAt = Date.now();

scripts/test-install-sh-docker.sh

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ prepare_update_tarball() {
299299
local baseline_pack_json
300300
local pack_json_file
301301
local baseline_pack_json_file
302+
local package_args
303+
local package_tgz
302304
local packed_update_version
303305
pack_json_file="${UPDATE_DIR}/pack.json"
304306
baseline_pack_json_file="${UPDATE_DIR}/baseline-pack.json"
@@ -316,13 +318,20 @@ prepare_update_tarball() {
316318
UPDATE_EXPECT_VERSION="$(
317319
node -p 'JSON.parse(require("node:fs").readFileSync("package.json", "utf8")).version'
318320
)"
319-
node --import tsx scripts/write-package-dist-inventory.ts
320-
node scripts/check-package-dist-imports.mjs "$ROOT_DIR"
321-
quiet_npm pack --ignore-scripts --json --pack-destination "$UPDATE_DIR" >"$pack_json_file"
321+
package_args=(
322+
--output-dir "$UPDATE_DIR"
323+
--pack-json "$pack_json_file"
324+
--skip-build
325+
)
326+
package_tgz="$(node scripts/package-openclaw-for-docker.mjs "${package_args[@]}")"
327+
UPDATE_TGZ_FILE="$(basename "$package_tgz")"
322328
fi
323-
UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"
324329
if [[ -z "$UPDATE_PACKAGE_SPEC" ]]; then
325-
node scripts/check-openclaw-package-tarball.mjs "${UPDATE_DIR}/${UPDATE_TGZ_FILE}"
330+
node scripts/check-openclaw-package-tarball.mjs \
331+
--require-bundled-workspace-deps \
332+
"${UPDATE_DIR}/${UPDATE_TGZ_FILE}"
333+
else
334+
UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"
326335
fi
327336
print_pack_audit "update" "$pack_json_file"
328337
assert_pack_unpacked_size_budget "update" "$pack_json_file"

test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,16 @@ describe("package-openclaw-for-docker", () => {
8686
"--output-dir",
8787
".artifacts/docker",
8888
"--output-name=openclaw-current.tgz",
89+
"--pack-json",
90+
".artifacts/docker/pack.json",
8991
"--source-dir",
9092
"/repo",
9193
"--skip-build",
9294
]),
9395
).toEqual({
9496
outputDir: ".artifacts/docker",
9597
outputName: "openclaw-current.tgz",
98+
packJson: ".artifacts/docker/pack.json",
9699
skipBuild: true,
97100
sourceDir: "/repo",
98101
});
@@ -112,6 +115,7 @@ describe("package-openclaw-for-docker", () => {
112115
const duplicateCases = [
113116
["--output-dir", ["--output-dir", "one", "--output-dir=two"]],
114117
["--output-name", ["--output-name", "one.tgz", "--output-name=two.tgz"]],
118+
["--pack-json", ["--pack-json", "one.json", "--pack-json=two.json"]],
115119
["--source-dir", ["--source-dir", "/repo-a", "--source-dir=/repo-b"]],
116120
["--skip-build", ["--skip-build", "--skip-build"]],
117121
] satisfies Array<[string, string[]]>;
@@ -383,6 +387,61 @@ describe("package-openclaw-for-docker", () => {
383387
]);
384388
});
385389

390+
it("writes npm pack metadata for renamed package artifacts", async () => {
391+
const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docker-pack-json-"));
392+
const packJsonPath = path.join(outputDir, "pack.json");
393+
394+
try {
395+
const tarball = await packOpenClawPackageForDocker("/repo", outputDir, {
396+
outputName: "openclaw-current.tgz",
397+
packJsonPath,
398+
prepareBundledAiRuntime: skipBundledAiRuntime,
399+
prepareChangelog: async () => {},
400+
restoreChangelog: async () => {},
401+
runCaptureImpl: async (
402+
command: string,
403+
args: string[],
404+
_cwd: string,
405+
options: { deferForwardedSignalExit?: boolean },
406+
) => {
407+
expect(command).toBe("npm");
408+
expect(args).toEqual([
409+
"pack",
410+
"--json",
411+
"--silent",
412+
"--ignore-scripts",
413+
"--pack-destination",
414+
outputDir,
415+
]);
416+
expect(options.deferForwardedSignalExit).toBe(true);
417+
fs.writeFileSync(path.join(outputDir, "openclaw-2026.5.28.tgz"), "package");
418+
return JSON.stringify([
419+
{
420+
entryCount: 1,
421+
filename: "openclaw-2026.5.28.tgz",
422+
size: 7,
423+
unpackedSize: 7,
424+
version: "2026.5.28",
425+
},
426+
]);
427+
},
428+
});
429+
430+
expect(tarball).toBe(path.join(outputDir, "openclaw-current.tgz"));
431+
expect(JSON.parse(fs.readFileSync(packJsonPath, "utf8"))).toEqual([
432+
{
433+
entryCount: 1,
434+
filename: "openclaw-current.tgz",
435+
size: 7,
436+
unpackedSize: 7,
437+
version: "2026.5.28",
438+
},
439+
]);
440+
} finally {
441+
fs.rmSync(outputDir, { force: true, recursive: true });
442+
}
443+
});
444+
386445
it("rejects path-like npm pack stdout before resolving Docker package tarballs", async () => {
387446
for (const filename of [
388447
"../openclaw-2026.6.17.tgz",

test/scripts/test-install-sh-docker.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ describe("test-install-sh-docker", () => {
612612
const script = readFileSync(SCRIPT_PATH, "utf8");
613613

614614
expect(script).toContain("read_pack_tarball_filename()");
615+
expect(script).toContain('UPDATE_TGZ_FILE="$(basename "$package_tgz")"');
615616
expect(script).toContain('UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")"');
616617
expect(script).toContain(
617618
'BASELINE_TGZ_FILE="$(read_pack_tarball_filename "$baseline_pack_json_file")"',
@@ -644,13 +645,16 @@ describe("test-install-sh-docker", () => {
644645
}
645646
});
646647

647-
it("writes the package dist inventory before packing ignore-scripts tarballs", () => {
648+
it("uses the package artifact helper for local update tarballs", () => {
648649
const script = readFileSync(SCRIPT_PATH, "utf8");
649650

650-
expect(script).toContain("node --import tsx scripts/write-package-dist-inventory.ts");
651-
expect(script).toContain('node scripts/check-package-dist-imports.mjs "$ROOT_DIR"');
652-
expect(script).toContain("quiet_npm pack --ignore-scripts");
651+
expect(script).toContain("node scripts/package-openclaw-for-docker.mjs");
652+
expect(script).toContain('--pack-json "$pack_json_file"');
653+
expect(script).toContain("--skip-build");
654+
expect(script).not.toContain("node --import tsx scripts/write-package-dist-inventory.ts");
655+
expect(script).not.toContain("quiet_npm pack --ignore-scripts --json");
653656
expect(script).toContain("node scripts/check-openclaw-package-tarball.mjs");
657+
expect(script).toContain("--require-bundled-workspace-deps");
654658
});
655659

656660
it("runs candidate tarballs through the installer script instead of direct npm", () => {

0 commit comments

Comments
 (0)