mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix: restore release validation on main (#102516)
* test(qqbot): assert approval fence exactly * fix(release): wait for ClawHub publish logging * fix(ci): repair Bun global install smoke * test(slack): fix app identity fixture
This commit is contained in:
parent
32de666163
commit
42a8679c4d
6 changed files with 278 additions and 106 deletions
|
|
@ -32,7 +32,7 @@ describe("buildExecApprovalText", () => {
|
|||
commandPreview: `${safePrefix}🎉 trailing text`,
|
||||
},
|
||||
});
|
||||
const codeFence = text.split("```\n")[1]?.split("\n```")[0] ?? "";
|
||||
expect(codeFence).toBe(safePrefix);
|
||||
const expectedCommandBlock = ["```", safePrefix, "```"].join("\n");
|
||||
expect(text).toContain(expectedCommandBlock);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ describe("monitorSlackProvider tool results", () => {
|
|||
(client.auth as { test: ReturnType<typeof vi.fn> }).test.mockResolvedValue({
|
||||
user_id: "bot-user",
|
||||
team_id: "T1",
|
||||
api_app_id: "A1",
|
||||
app_id: "A1",
|
||||
});
|
||||
|
||||
await runSlackMessageOnce(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ DIST_IMAGE="${OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE:-}"
|
|||
PACKAGE_TGZ="${OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ:-}"
|
||||
COMMAND_TIMEOUT_MS="$(read_positive_int_env OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_MS 180000)"
|
||||
DOCKER_COMMAND_TIMEOUT="${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_BUN_GLOBAL_SMOKE_DOCKER_COMMAND_TIMEOUT:-600s}}"
|
||||
AI_PACKAGE_TGZ=""
|
||||
SMOKE_DIR=""
|
||||
PACK_DIR=""
|
||||
|
||||
|
|
@ -36,6 +37,39 @@ cleanup() {
|
|||
fi
|
||||
}
|
||||
|
||||
prepare_ai_candidate() {
|
||||
local ai_manifest
|
||||
local ai_package_dir
|
||||
local ai_tarballs
|
||||
local root_manifest
|
||||
|
||||
if [ -z "$PACK_DIR" ]; then
|
||||
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-pack.XXXXXX")"
|
||||
fi
|
||||
echo "==> Extract bundled candidate @openclaw/ai package"
|
||||
ai_package_dir="$PACK_DIR/ai-candidate"
|
||||
mkdir -p "$ai_package_dir"
|
||||
tar -xzf "$PACKAGE_TGZ" \
|
||||
-C "$ai_package_dir" \
|
||||
--strip-components=4 \
|
||||
package/node_modules/@openclaw/ai
|
||||
root_manifest="$PACK_DIR/openclaw-package.json"
|
||||
ai_manifest="$ai_package_dir/package.json"
|
||||
tar -xOf "$PACKAGE_TGZ" package/package.json >"$root_manifest"
|
||||
node scripts/e2e/lib/bun-global-install/assertions.mjs \
|
||||
assert-release-versions \
|
||||
"$root_manifest" \
|
||||
"$ai_manifest" \
|
||||
>/dev/null
|
||||
npm pack --ignore-scripts --silent --pack-destination "$PACK_DIR" "$ai_package_dir" >/dev/null
|
||||
ai_tarballs=("$PACK_DIR"/openclaw-ai-*.tgz)
|
||||
if [ "${#ai_tarballs[@]}" -ne 1 ] || [ ! -f "${ai_tarballs[0]}" ]; then
|
||||
echo "expected one packed @openclaw/ai candidate in $PACK_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
AI_PACKAGE_TGZ="${ai_tarballs[0]}"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
run_with_timeout() {
|
||||
|
|
@ -44,52 +78,50 @@ run_with_timeout() {
|
|||
node scripts/e2e/lib/bun-global-install/assertions.mjs run-with-timeout "$timeout_ms" "$@"
|
||||
}
|
||||
|
||||
resolve_pack_tarball_path() {
|
||||
local pack_json_file="$1"
|
||||
local pack_dir="$2"
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const raw = fs.readFileSync(process.argv[1], "utf8") || "[]";
|
||||
const parsed = JSON.parse(raw);
|
||||
const last = Array.isArray(parsed) ? parsed.at(-1) : null;
|
||||
const filename = typeof last?.filename === "string" ? last.filename.trim() : "";
|
||||
if (
|
||||
!filename.endsWith(".tgz") ||
|
||||
filename.includes("\0") ||
|
||||
filename !== path.basename(filename) ||
|
||||
filename !== path.win32.basename(filename)
|
||||
) {
|
||||
console.error(`ERROR: npm pack reported unsafe tarball filename ${JSON.stringify(filename)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(path.resolve(process.argv[2], filename));
|
||||
' "$pack_json_file" "$pack_dir"
|
||||
}
|
||||
|
||||
restore_dist_from_image() {
|
||||
local image="$1"
|
||||
local ai_backup_dir=""
|
||||
local ai_dist_installed=0
|
||||
local backup_dir=""
|
||||
local container_id=""
|
||||
local swapped=0
|
||||
local dist_installed=0
|
||||
local restore_complete=0
|
||||
local temp_dir=""
|
||||
|
||||
cleanup_restore_dist() {
|
||||
if [ -n "$container_id" ]; then
|
||||
docker_e2e_docker_cmd rm -f "$container_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$swapped" != "1" ] && [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
|
||||
rm -rf "$ROOT_DIR/dist" >/dev/null 2>&1 || true
|
||||
if [ ! -e "$ROOT_DIR/dist" ] && mv "$backup_dir" "$ROOT_DIR/dist" >/dev/null 2>&1; then
|
||||
backup_dir=""
|
||||
# Both build trees come from one image. A partial swap must restore both or
|
||||
# the following package step could mix artifacts from different builds.
|
||||
if [ "$restore_complete" != "1" ]; then
|
||||
if [ "$dist_installed" = "1" ]; then
|
||||
rm -rf "$ROOT_DIR/dist" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
|
||||
if [ ! -e "$ROOT_DIR/dist" ] && mv "$backup_dir" "$ROOT_DIR/dist" >/dev/null 2>&1; then
|
||||
backup_dir=""
|
||||
fi
|
||||
fi
|
||||
if [ "$ai_dist_installed" = "1" ]; then
|
||||
rm -rf "$ROOT_DIR/packages/ai/dist" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$ai_backup_dir" ] && [ -d "$ai_backup_dir" ]; then
|
||||
if [ ! -e "$ROOT_DIR/packages/ai/dist" ] && \
|
||||
mv "$ai_backup_dir" "$ROOT_DIR/packages/ai/dist" >/dev/null 2>&1; then
|
||||
ai_backup_dir=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$temp_dir" ]; then
|
||||
rm -rf "$temp_dir"
|
||||
fi
|
||||
if [ "$swapped" = "1" ] && [ -n "$backup_dir" ]; then
|
||||
if [ "$restore_complete" = "1" ] && [ -n "$backup_dir" ]; then
|
||||
rm -rf "$backup_dir"
|
||||
fi
|
||||
if [ "$restore_complete" = "1" ] && [ -n "$ai_backup_dir" ]; then
|
||||
rm -rf "$ai_backup_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> Reuse dist/ from Docker image: $image"
|
||||
|
|
@ -105,6 +137,12 @@ restore_dist_from_image() {
|
|||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! docker_e2e_docker_cmd cp \
|
||||
"${container_id}:/app/node_modules/@openclaw/ai/dist" \
|
||||
"$temp_dir/ai-dist"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if [ -e "$ROOT_DIR/dist" ]; then
|
||||
if ! backup_dir="$(mktemp -d "$ROOT_DIR/.dist-backup.XXXXXX")"; then
|
||||
cleanup_restore_dist
|
||||
|
|
@ -123,7 +161,27 @@ restore_dist_from_image() {
|
|||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
swapped=1
|
||||
dist_installed=1
|
||||
if [ -e "$ROOT_DIR/packages/ai/dist" ]; then
|
||||
if ! ai_backup_dir="$(mktemp -d "$ROOT_DIR/packages/ai/.dist-backup.XXXXXX")"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! rmdir "$ai_backup_dir"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! mv "$ROOT_DIR/packages/ai/dist" "$ai_backup_dir"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if ! mv "$temp_dir/ai-dist" "$ROOT_DIR/packages/ai/dist"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
ai_dist_installed=1
|
||||
restore_complete=1
|
||||
cleanup_restore_dist
|
||||
}
|
||||
|
||||
|
|
@ -151,16 +209,15 @@ resolve_package_tgz() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Write package inventory"
|
||||
node --import tsx scripts/write-package-dist-inventory.ts
|
||||
|
||||
local pack_json_file
|
||||
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-pack.XXXXXX")"
|
||||
pack_json_file="$PACK_DIR/pack.json"
|
||||
|
||||
echo "==> Pack OpenClaw tarball"
|
||||
npm pack --ignore-scripts --json --pack-destination "$PACK_DIR" >"$pack_json_file"
|
||||
PACKAGE_TGZ="$(resolve_pack_tarball_path "$pack_json_file" "$PACK_DIR")"
|
||||
PACKAGE_TGZ="$(
|
||||
node scripts/package-openclaw-for-docker.mjs \
|
||||
--skip-build \
|
||||
--output-dir "$PACK_DIR" \
|
||||
--output-name openclaw-current.tgz
|
||||
)"
|
||||
if [ -z "$PACKAGE_TGZ" ] || [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
exit 1
|
||||
|
|
@ -176,6 +233,7 @@ main() {
|
|||
fi
|
||||
|
||||
resolve_package_tgz
|
||||
prepare_ai_candidate
|
||||
|
||||
local bun_path
|
||||
local openclaw_bin
|
||||
|
|
@ -188,8 +246,21 @@ main() {
|
|||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_DISABLE_UPDATE_CHECK=1
|
||||
export NO_COLOR=1
|
||||
mkdir -p "$HOME" "$BUN_INSTALL/bin" "$XDG_CACHE_HOME"
|
||||
mkdir -p "$HOME" "$BUN_INSTALL/bin" "$BUN_INSTALL/install/global" "$XDG_CACHE_HOME"
|
||||
export PATH="$BUN_INSTALL/bin:$(dirname "$(command -v node)"):$PATH"
|
||||
# Release publishes @openclaw/ai first. Bun 1.3.14 ignores bundled deps in
|
||||
# local tarballs, so resolve that one package from the exact candidate bytes.
|
||||
node --input-type=module - \
|
||||
"$BUN_INSTALL/install/global/package.json" \
|
||||
"$AI_PACKAGE_TGZ" <<'NODE'
|
||||
import fs from "node:fs";
|
||||
|
||||
const [, , packageJsonPath, aiPackageTarball] = process.argv;
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
`${JSON.stringify({ private: true, overrides: { "@openclaw/ai": `file:${aiPackageTarball}` } })}\n`,
|
||||
);
|
||||
NODE
|
||||
|
||||
echo "==> Bun version"
|
||||
"$bun_path" --version
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
// Assertions for Bun global install E2E validation.
|
||||
import { spawn } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
|
||||
const DEFAULT_TIMEOUT_KILL_GRACE_MS = 30_000;
|
||||
const PARENT_TERMINATION_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
||||
|
||||
const usage = () => {
|
||||
console.error("Usage: assertions.mjs <run-with-timeout|assert-image-providers> [...]");
|
||||
console.error(
|
||||
"Usage: assertions.mjs <run-with-timeout|assert-image-providers|assert-release-versions> [...]",
|
||||
);
|
||||
process.exit(2);
|
||||
};
|
||||
|
||||
|
|
@ -204,4 +207,28 @@ if (mode === "assert-image-providers") {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
if (mode === "assert-release-versions") {
|
||||
const [rootManifestPath, aiManifestPath] = args;
|
||||
if (!rootManifestPath || !aiManifestPath) {
|
||||
usage();
|
||||
}
|
||||
const rootManifest = JSON.parse(fs.readFileSync(rootManifestPath, "utf8"));
|
||||
const aiManifest = JSON.parse(fs.readFileSync(aiManifestPath, "utf8"));
|
||||
const rootVersion = rootManifest.version;
|
||||
const aiVersion = aiManifest.version;
|
||||
const rootAiVersion = rootManifest.dependencies?.["@openclaw/ai"];
|
||||
if (
|
||||
typeof rootVersion !== "string" ||
|
||||
typeof aiVersion !== "string" ||
|
||||
rootVersion !== aiVersion ||
|
||||
rootAiVersion !== aiVersion
|
||||
) {
|
||||
throw new Error(
|
||||
`candidate version mismatch: openclaw=${String(rootVersion)}, dependency=${String(rootAiVersion)}, @openclaw/ai=${String(aiVersion)}`,
|
||||
);
|
||||
}
|
||||
process.stdout.write(aiVersion);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
usage();
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ fi
|
|||
|
||||
publish_log="${pack_dir}/publish.log"
|
||||
for attempt in $(seq 1 "${OPENCLAW_CLAWHUB_PUBLISH_ATTEMPTS:-8}"); do
|
||||
if CLAWHUB_WORKDIR="${clawhub_workdir}" "${publish_cmd[@]}" > >(tee "${publish_log}") 2>&1; then
|
||||
if CLAWHUB_WORKDIR="${clawhub_workdir}" "${publish_cmd[@]}" 2>&1 | tee "${publish_log}"; then
|
||||
exit 0
|
||||
fi
|
||||
if ! grep -Eqi "rate limit|too many requests|\\b429\\b" "${publish_log}"; then
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
// Test Install Sh Docker tests cover test install sh docker script behavior.
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path, { join } from "node:path";
|
||||
import { runInNewContext } from "node:vm";
|
||||
|
|
@ -216,40 +224,6 @@ read_pack_tarball_filename "$pack_json_file"`,
|
|||
);
|
||||
}
|
||||
|
||||
function extractResolvePackTarballPath(): string {
|
||||
const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8");
|
||||
const match = script.match(/(resolve_pack_tarball_path\(\) \{[\s\S]*?\n\})\n\nrestore_dist/u);
|
||||
if (!match) {
|
||||
throw new Error("resolve_pack_tarball_path helper was not found");
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function runResolvePackTarballPath(filename: string) {
|
||||
return spawnSync(
|
||||
"bash",
|
||||
[
|
||||
"--noprofile",
|
||||
"--norc",
|
||||
"-c",
|
||||
`${extractResolvePackTarballPath()}
|
||||
pack_dir="$(mktemp -d)"
|
||||
pack_json_file="$pack_dir/pack.json"
|
||||
trap 'rm -rf "$pack_dir"' EXIT
|
||||
printf '%s' "$PACK_JSON" >"$pack_json_file"
|
||||
resolve_pack_tarball_path "$pack_json_file" "$pack_dir"`,
|
||||
],
|
||||
{
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
HOME: "/tmp",
|
||||
PACK_JSON: JSON.stringify([{ filename }]),
|
||||
PATH: process.env.PATH ?? "",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe("test-install-sh-docker", () => {
|
||||
it("defaults ARM hosts to native arm64 while keeping x64 CI on amd64", () => {
|
||||
expect(runDefaultSmokePlatform({ CI: "true" }, "aarch64")).toBe("linux/arm64");
|
||||
|
|
@ -902,7 +876,10 @@ describe("bun global install smoke", () => {
|
|||
const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8");
|
||||
const assertions = readFileSync(BUN_GLOBAL_ASSERTIONS_PATH, "utf8");
|
||||
|
||||
expect(script).toContain("npm pack --ignore-scripts --json --pack-destination");
|
||||
expect(script).toContain("node scripts/package-openclaw-for-docker.mjs");
|
||||
expect(script).toContain("--skip-build");
|
||||
expect(script).toContain("--output-name openclaw-current.tgz");
|
||||
expect(script).not.toContain("npm pack --ignore-scripts --json --pack-destination");
|
||||
expect(script).toContain('"$bun_path" install -g "$PACKAGE_TGZ" --no-progress');
|
||||
expect(script).toContain("infer image providers --json");
|
||||
expect(script).toContain("assert-image-providers");
|
||||
|
|
@ -919,6 +896,9 @@ describe("bun global install smoke", () => {
|
|||
expect(script).toContain(
|
||||
'docker_e2e_docker_cmd cp "${container_id}:/app/dist" "$temp_dir/dist"',
|
||||
);
|
||||
expect(script).toContain('"${container_id}:/app/node_modules/@openclaw/ai/dist"');
|
||||
expect(script).toContain('"$temp_dir/ai-dist"');
|
||||
expect(script).toContain('mv "$temp_dir/ai-dist" "$ROOT_DIR/packages/ai/dist"');
|
||||
expect(script).toContain("cleanup_restore_dist() {");
|
||||
expect(script).toContain('mv "$ROOT_DIR/dist" "$backup_dir"');
|
||||
expect(script).toContain('mv "$temp_dir/dist" "$ROOT_DIR/dist"');
|
||||
|
|
@ -947,40 +927,134 @@ describe("bun global install smoke", () => {
|
|||
expect(result.stderr).not.toContain("Bun is required");
|
||||
});
|
||||
|
||||
it("keeps npm pack tarball paths inside the Bun smoke pack directory", () => {
|
||||
it("uses the canonical package builder for bundled workspace dependencies", () => {
|
||||
const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8");
|
||||
|
||||
expect(script).toContain("resolve_pack_tarball_path()");
|
||||
expect(script).toContain(
|
||||
'PACKAGE_TGZ="$(resolve_pack_tarball_path "$pack_json_file" "$PACK_DIR")"',
|
||||
expect(script).toContain('PACK_DIR="$(mktemp -d');
|
||||
expect(script).toContain("node scripts/package-openclaw-for-docker.mjs");
|
||||
expect(script).toContain('--output-dir "$PACK_DIR"');
|
||||
expect(script).toContain("--output-name openclaw-current.tgz");
|
||||
});
|
||||
|
||||
it("resolves the matching candidate AI package without changing the public registry", () => {
|
||||
const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8");
|
||||
|
||||
expect(script).toContain("assert-release-versions");
|
||||
expect(script).toContain('"$BUN_INSTALL/install/global/package.json"');
|
||||
expect(script).toContain("package/node_modules/@openclaw/ai");
|
||||
expect(script).toContain("--strip-components=4");
|
||||
expect(script).toContain('npm pack --ignore-scripts --silent --pack-destination "$PACK_DIR"');
|
||||
expect(script).toContain('overrides: { "@openclaw/ai": `file:${aiPackageTarball}` }');
|
||||
expect(script).not.toContain("--registry");
|
||||
expect(script).not.toContain("@openclaw:registry");
|
||||
});
|
||||
|
||||
it("requires root and AI candidate versions to match", () => {
|
||||
const tempDir = tempDirs.make("openclaw-bun-candidate-versions-");
|
||||
const rootManifestPath = join(tempDir, "openclaw.json");
|
||||
const aiManifestPath = join(tempDir, "ai.json");
|
||||
writeFileSync(
|
||||
rootManifestPath,
|
||||
JSON.stringify({
|
||||
name: "openclaw",
|
||||
version: "2026.6.17",
|
||||
dependencies: { "@openclaw/ai": "2026.6.17" },
|
||||
}),
|
||||
);
|
||||
writeFileSync(aiManifestPath, JSON.stringify({ name: "@openclaw/ai", version: "2026.6.17" }));
|
||||
|
||||
const matching = spawnSync(
|
||||
process.execPath,
|
||||
[BUN_GLOBAL_ASSERTIONS_PATH, "assert-release-versions", rootManifestPath, aiManifestPath],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
expect(matching).toMatchObject({ status: 0, stdout: "2026.6.17" });
|
||||
|
||||
writeFileSync(aiManifestPath, JSON.stringify({ name: "@openclaw/ai", version: "2026.6.18" }));
|
||||
const mismatched = spawnSync(
|
||||
process.execPath,
|
||||
[BUN_GLOBAL_ASSERTIONS_PATH, "assert-release-versions", rootManifestPath, aiManifestPath],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
expect(mismatched.status).not.toBe(0);
|
||||
expect(mismatched.stderr).toContain(
|
||||
"candidate version mismatch: openclaw=2026.6.17, dependency=2026.6.17, @openclaw/ai=2026.6.18",
|
||||
);
|
||||
expect(script).toContain("filename !== path.basename(filename)");
|
||||
expect(script).toContain("filename !== path.win32.basename(filename)");
|
||||
expect(script).toContain("npm pack reported unsafe tarball filename");
|
||||
});
|
||||
|
||||
it("rejects path-like npm pack tarball filenames in Bun smoke metadata", () => {
|
||||
const safeResult = runResolvePackTarballPath("openclaw-2026.6.17.tgz");
|
||||
it.runIf(process.platform !== "win32")(
|
||||
"uses bundled AI bytes when a prebuilt tarball is provided",
|
||||
() => {
|
||||
const tempDir = tempDirs.make("openclaw-bun-prebuilt-");
|
||||
const packageDir = join(tempDir, "fixture", "package");
|
||||
const aiDir = join(packageDir, "node_modules", "@openclaw", "ai");
|
||||
const packageTgz = join(tempDir, "openclaw-prebuilt.tgz");
|
||||
const bunPath = join(tempDir, "bun");
|
||||
mkdirSync(aiDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({
|
||||
name: "openclaw",
|
||||
version: "2026.6.17",
|
||||
dependencies: { "@openclaw/ai": "2026.6.17" },
|
||||
bundleDependencies: ["@openclaw/ai"],
|
||||
}),
|
||||
);
|
||||
writeFileSync(
|
||||
join(aiDir, "package.json"),
|
||||
JSON.stringify({ name: "@openclaw/ai", version: "2026.6.17" }),
|
||||
);
|
||||
const packed = spawnSync(
|
||||
"tar",
|
||||
["-czf", packageTgz, "-C", join(tempDir, "fixture"), "package"],
|
||||
{
|
||||
encoding: "utf8",
|
||||
},
|
||||
);
|
||||
expect(packed.status, packed.stderr).toBe(0);
|
||||
writeFileSync(
|
||||
bunPath,
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [ "\${1:-}" = "--version" ]; then
|
||||
echo "1.3.14"
|
||||
exit 0
|
||||
fi
|
||||
override="$(node -e 'const p=require(process.argv[1]);process.stdout.write(p.overrides["@openclaw/ai"])' "$BUN_INSTALL/install/global/package.json")"
|
||||
case "\${override#file:}" in
|
||||
*.tgz) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
test -f "\${override#file:}"
|
||||
mkdir -p "$BUN_INSTALL/bin"
|
||||
cat >"$BUN_INSTALL/bin/openclaw" <<'OPENCLAW'
|
||||
#!/usr/bin/env bash
|
||||
if [ "\${1:-}" = "--version" ]; then
|
||||
echo "OpenClaw 2026.6.17"
|
||||
else
|
||||
printf '[{"id":"google"},{"id":"openai"},{"id":"xai"}]\n'
|
||||
fi
|
||||
OPENCLAW
|
||||
chmod +x "$BUN_INSTALL/bin/openclaw"
|
||||
`,
|
||||
);
|
||||
chmodSync(bunPath, 0o755);
|
||||
|
||||
expect(safeResult.status).toBe(0);
|
||||
expect(safeResult.stdout).toMatch(/\/openclaw-2026\.6\.17\.tgz$/u);
|
||||
const result = spawnSync("bash", [BUN_GLOBAL_SMOKE_PATH], {
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BIN: bunPath,
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD: "0",
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ: packageTgz,
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_MS: "10000",
|
||||
},
|
||||
});
|
||||
|
||||
const unsafeFilenames = [
|
||||
"../openclaw.tgz",
|
||||
"nested/openclaw.tgz",
|
||||
"nested\\openclaw.tgz",
|
||||
"/tmp/openclaw.tgz",
|
||||
"C:\\temp\\openclaw.tgz",
|
||||
"openclaw.tar.gz",
|
||||
];
|
||||
|
||||
for (const filename of unsafeFilenames) {
|
||||
const result = runResolvePackTarballPath(filename);
|
||||
|
||||
expect(result.status, filename).not.toBe(0);
|
||||
expect(result.stderr, filename).toContain("npm pack reported unsafe tarball filename");
|
||||
}
|
||||
});
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout).toContain("bun-global-install-smoke: image providers OK (3 providers)");
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(process.platform !== "win32" && existsSync("/usr/bin/time"))(
|
||||
"preserves Bun global timeout kill grace after the leader exits",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue