mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
fix(release): require exact per-manager version identity in Docker package acceptance (#138085)
* fix(release): require exact per-manager version identity in Docker package acceptance Replaces substring CLI checks with parsed exact semver equality and reads each manager's own installed manifest (including Bun via the smoke proof JSON) instead of copying npm's version. Adds a focused predicate regression test. Closes #127415 * fix(release): bind package identity to artifact * test(release): distinguish pnpm identity fixture * test(release): cover Bun identity rejection * test(docker): provision native build tools for musl package installs --------- Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org> Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com>
This commit is contained in:
parent
8643229754
commit
e79e97b359
7 changed files with 396 additions and 73 deletions
|
|
@ -3,7 +3,7 @@
|
|||
extract_openclaw_semver() {
|
||||
local raw="${1:-}"
|
||||
raw="${raw//$'\r'/}"
|
||||
if [[ "$raw" =~ v?([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?(\+[0-9A-Za-z.-]+)?) ]]; then
|
||||
if [[ "$raw" =~ v?([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z.-]+)?) ]]; then
|
||||
printf '%s' "${BASH_REMATCH[1]}"
|
||||
fi
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ CMD ["bash"]
|
|||
|
||||
FROM ${OPENCLAW_NODE_ALPINE_IMAGE} AS musl
|
||||
|
||||
RUN apk add --no-cache bash
|
||||
# Native dependencies without musl prebuilds must compile during real npm installs.
|
||||
RUN apk add --no-cache bash g++ make python3
|
||||
COPY --from=e2e-runner /opt/openclaw-e2e /opt/openclaw-e2e
|
||||
COPY scripts/docker/verify-fs-safe-native.mjs /tmp/verify-fs-safe-native.mjs
|
||||
|
||||
|
|
|
|||
|
|
@ -449,15 +449,19 @@ NODE
|
|||
"$bun_path" \
|
||||
"$openclaw_bin" \
|
||||
"$openclaw_version" \
|
||||
"$runtime_label" <<'NODE'
|
||||
"$runtime_label" \
|
||||
"$package_root" <<'NODE'
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const [, , proofPath, bunPath, openclawPath, openclawVersion, runtime] = process.argv;
|
||||
const [, , proofPath, bunPath, openclawPath, openclawVersion, runtime, installedPackageRoot] = process.argv;
|
||||
const installedPackageVersion = JSON.parse(
|
||||
fs.readFileSync(path.join(installedPackageRoot, "package.json"), "utf8"),
|
||||
).version;
|
||||
fs.mkdirSync(path.dirname(proofPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
proofPath,
|
||||
`${JSON.stringify({ bunPath, openclawPath, openclawVersion, runtime }, null, 2)}\n`,
|
||||
`${JSON.stringify({ bunPath, openclawPath, openclawVersion, runtime, installedPackageRoot, installedPackageVersion }, null, 2)}\n`,
|
||||
);
|
||||
NODE
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -184,59 +184,13 @@ for container_name in "$NPM_PROOF_CONTAINER" "$PNPM_PROOF_CONTAINER" "$BUN_PROOF
|
|||
wait_for_proof "$container_name"
|
||||
done
|
||||
|
||||
NPM_PACKAGE_ROOT="/usr/local/lib/node_modules/openclaw"
|
||||
NPM_INSTALLED_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')"
|
||||
PNPM_PACKAGE_ROOT="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-package-root | tr -d '\r\n')"
|
||||
PNPM_PACKAGE_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" node -p "require('$PNPM_PACKAGE_ROOT/package.json').version")"
|
||||
PNPM_INSTALLED_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')"
|
||||
BUN_OPENCLAW_PATH="$(
|
||||
docker exec "$BUN_PROOF_CONTAINER" \
|
||||
node -p 'JSON.parse(require("node:fs").readFileSync("/tmp/openclaw-bun-proof.json", "utf8")).openclawPath'
|
||||
)"
|
||||
BUN_INSTALLED_VERSION="$(
|
||||
docker exec "$BUN_PROOF_CONTAINER" \
|
||||
node -p 'JSON.parse(require("node:fs").readFileSync("/tmp/openclaw-bun-proof.json", "utf8")).openclawVersion'
|
||||
)"
|
||||
PACKAGE_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" node -p "require('$NPM_PACKAGE_ROOT/package.json').version")"
|
||||
test "$PNPM_PACKAGE_VERSION" = "$PACKAGE_VERSION"
|
||||
for installed_version in "$NPM_INSTALLED_VERSION" "$PNPM_INSTALLED_VERSION" "$BUN_INSTALLED_VERSION"; do
|
||||
if [[ "$installed_version" != *"$PACKAGE_VERSION"* ]]; then
|
||||
echo "installed CLI output $installed_version does not contain package version $PACKAGE_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# The legacy contract intentionally skips native verification; evidence must not
|
||||
# present that omission as an executed native proof.
|
||||
MUSL_FS_SAFE_NATIVE_OUTCOME="passed"
|
||||
if [[ "${OPENCLAW_FS_SAFE_NATIVE_CONTRACT:-required}" == "not-applicable" ]]; then
|
||||
MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable"
|
||||
fi
|
||||
|
||||
node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts" \
|
||||
--scenario docker-package-install \
|
||||
--output "$IDENTITY_PATH" \
|
||||
--image "$IMAGE_NAME" \
|
||||
--package "$PACKAGE_TGZ" \
|
||||
--container "npm=$NPM_PROOF_CONTAINER" \
|
||||
--container "pnpm=$PNPM_PROOF_CONTAINER" \
|
||||
--container "bun=$BUN_PROOF_CONTAINER" \
|
||||
--container "musl=$MUSL_PROOF_CONTAINER" \
|
||||
--detail "npm:installedPackageRoot=$NPM_PACKAGE_ROOT" \
|
||||
--detail "npm:installedPackageVersion=$PACKAGE_VERSION" \
|
||||
--detail "npm:openclawVersion=$NPM_INSTALLED_VERSION" \
|
||||
--detail "npm:openclawPath=/usr/local/bin/openclaw" \
|
||||
--detail "npm:helpCommand=passed" \
|
||||
--detail "npm:nonRootExecution=passed" \
|
||||
--detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME" \
|
||||
--detail "pnpm:installedPackageRoot=$PNPM_PACKAGE_ROOT" \
|
||||
--detail "pnpm:installedPackageVersion=$PNPM_PACKAGE_VERSION" \
|
||||
--detail "pnpm:openclawVersion=$PNPM_INSTALLED_VERSION" \
|
||||
--detail "pnpm:openclawPath=/tmp/pnpm-home/bin/openclaw" \
|
||||
--detail "pnpm:helpCommand=passed" \
|
||||
--detail "bun:installedPackageVersion=$PACKAGE_VERSION" \
|
||||
--detail "bun:openclawVersion=$BUN_INSTALLED_VERSION" \
|
||||
--detail "bun:openclawPath=$BUN_OPENCLAW_PATH" \
|
||||
--detail "bun:helpCommand=passed"
|
||||
bash "$ROOT_DIR/scripts/e2e/lib/docker-package-identity.sh" \
|
||||
"$PACKAGE_TGZ" \
|
||||
"$IDENTITY_PATH" \
|
||||
"$IMAGE_NAME" \
|
||||
"$NPM_PROOF_CONTAINER" \
|
||||
"$PNPM_PROOF_CONTAINER" \
|
||||
"$BUN_PROOF_CONTAINER" \
|
||||
"$MUSL_PROOF_CONTAINER"
|
||||
|
||||
echo "npm, pnpm, and Bun package artifact proofs passed."
|
||||
|
|
|
|||
121
scripts/e2e/lib/docker-package-identity.sh
Normal file
121
scripts/e2e/lib/docker-package-identity.sh
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/docker/install-sh-common/version-parse.sh"
|
||||
|
||||
PACKAGE_TGZ="${1:?missing package tarball}"
|
||||
IDENTITY_PATH="${2:?missing identity output path}"
|
||||
IMAGE_NAME="${3:?missing image name}"
|
||||
NPM_PROOF_CONTAINER="${4:?missing npm proof container}"
|
||||
PNPM_PROOF_CONTAINER="${5:?missing pnpm proof container}"
|
||||
BUN_PROOF_CONTAINER="${6:?missing Bun proof container}"
|
||||
MUSL_PROOF_CONTAINER="${7:?missing musl proof container}"
|
||||
|
||||
read_manifest_version() {
|
||||
local container_name="$1"
|
||||
local manifest_path="$2"
|
||||
docker exec "$container_name" node -e '
|
||||
const fs = require("node:fs");
|
||||
const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
||||
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
||||
throw new Error(`missing version in ${process.argv[1]}`);
|
||||
}
|
||||
process.stdout.write(manifest.version);
|
||||
' "$manifest_path"
|
||||
}
|
||||
|
||||
read_bun_proof() {
|
||||
local field="$1"
|
||||
docker exec "$BUN_PROOF_CONTAINER" node -e '
|
||||
const fs = require("node:fs");
|
||||
const proof = JSON.parse(fs.readFileSync("/tmp/openclaw-bun-proof.json", "utf8"));
|
||||
const value = proof[process.argv[1]];
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
throw new Error(`missing Bun proof field ${process.argv[1]}`);
|
||||
}
|
||||
process.stdout.write(value);
|
||||
' "$field"
|
||||
}
|
||||
|
||||
EXPECTED_PACKAGE_VERSION="$(
|
||||
tar -xOf "$PACKAGE_TGZ" package/package.json | node -e '
|
||||
const fs = require("node:fs");
|
||||
const manifest = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
||||
throw new Error("package artifact manifest is missing version");
|
||||
}
|
||||
process.stdout.write(manifest.version);
|
||||
'
|
||||
)"
|
||||
|
||||
NPM_PACKAGE_ROOT="/usr/local/lib/node_modules/openclaw"
|
||||
PNPM_PACKAGE_ROOT="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-package-root | tr -d '\r\n')"
|
||||
BUN_PACKAGE_ROOT="$(read_bun_proof installedPackageRoot)"
|
||||
|
||||
NPM_PACKAGE_VERSION="$(read_manifest_version "$NPM_PROOF_CONTAINER" "$NPM_PACKAGE_ROOT/package.json")"
|
||||
PNPM_PACKAGE_VERSION="$(read_manifest_version "$PNPM_PROOF_CONTAINER" "$PNPM_PACKAGE_ROOT/package.json")"
|
||||
BUN_PACKAGE_VERSION="$(read_bun_proof installedPackageVersion)"
|
||||
|
||||
NPM_INSTALLED_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')"
|
||||
PNPM_INSTALLED_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')"
|
||||
BUN_INSTALLED_VERSION="$(read_bun_proof openclawVersion)"
|
||||
BUN_OPENCLAW_PATH="$(read_bun_proof openclawPath)"
|
||||
|
||||
MANAGERS=(npm pnpm bun)
|
||||
MANIFEST_VERSIONS=("$NPM_PACKAGE_VERSION" "$PNPM_PACKAGE_VERSION" "$BUN_PACKAGE_VERSION")
|
||||
CLI_OUTPUTS=("$NPM_INSTALLED_VERSION" "$PNPM_INSTALLED_VERSION" "$BUN_INSTALLED_VERSION")
|
||||
PARSED_VERSIONS=()
|
||||
|
||||
for index in "${!MANAGERS[@]}"; do
|
||||
manager="${MANAGERS[$index]}"
|
||||
manifest_version="${MANIFEST_VERSIONS[$index]}"
|
||||
cli_output="${CLI_OUTPUTS[$index]}"
|
||||
if [[ "$manifest_version" != "$EXPECTED_PACKAGE_VERSION" ]]; then
|
||||
echo "[$manager] installed manifest version '$manifest_version' != artifact '$EXPECTED_PACKAGE_VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
parsed_version="$(extract_openclaw_semver "$cli_output")"
|
||||
if [[ "$parsed_version" != "$EXPECTED_PACKAGE_VERSION" ]]; then
|
||||
echo "[$manager] CLI output parses to '${parsed_version:-<unparseable>}' (raw: '$cli_output'), expected artifact '$EXPECTED_PACKAGE_VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
PARSED_VERSIONS+=("$parsed_version")
|
||||
done
|
||||
|
||||
# The legacy contract intentionally skips native verification; evidence must not
|
||||
# present that omission as an executed native proof.
|
||||
MUSL_FS_SAFE_NATIVE_OUTCOME="passed"
|
||||
if [[ "${OPENCLAW_FS_SAFE_NATIVE_CONTRACT:-required}" == "not-applicable" ]]; then
|
||||
MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable"
|
||||
fi
|
||||
|
||||
node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts" \
|
||||
--scenario docker-package-install \
|
||||
--output "$IDENTITY_PATH" \
|
||||
--image "$IMAGE_NAME" \
|
||||
--package "$PACKAGE_TGZ" \
|
||||
--container "npm=$NPM_PROOF_CONTAINER" \
|
||||
--container "pnpm=$PNPM_PROOF_CONTAINER" \
|
||||
--container "bun=$BUN_PROOF_CONTAINER" \
|
||||
--container "musl=$MUSL_PROOF_CONTAINER" \
|
||||
--detail "npm:installedPackageRoot=$NPM_PACKAGE_ROOT" \
|
||||
--detail "npm:installedPackageVersion=$NPM_PACKAGE_VERSION" \
|
||||
--detail "npm:openclawVersion=$NPM_INSTALLED_VERSION" \
|
||||
--detail "npm:parsedOpenclawVersion=${PARSED_VERSIONS[0]}" \
|
||||
--detail "npm:openclawPath=/usr/local/bin/openclaw" \
|
||||
--detail "npm:helpCommand=passed" \
|
||||
--detail "npm:nonRootExecution=passed" \
|
||||
--detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME" \
|
||||
--detail "pnpm:installedPackageRoot=$PNPM_PACKAGE_ROOT" \
|
||||
--detail "pnpm:installedPackageVersion=$PNPM_PACKAGE_VERSION" \
|
||||
--detail "pnpm:openclawVersion=$PNPM_INSTALLED_VERSION" \
|
||||
--detail "pnpm:parsedOpenclawVersion=${PARSED_VERSIONS[1]}" \
|
||||
--detail "pnpm:openclawPath=/tmp/pnpm-home/bin/openclaw" \
|
||||
--detail "pnpm:helpCommand=passed" \
|
||||
--detail "bun:installedPackageRoot=$BUN_PACKAGE_ROOT" \
|
||||
--detail "bun:installedPackageVersion=$BUN_PACKAGE_VERSION" \
|
||||
--detail "bun:openclawVersion=$BUN_INSTALLED_VERSION" \
|
||||
--detail "bun:parsedOpenclawVersion=${PARSED_VERSIONS[2]}" \
|
||||
--detail "bun:openclawPath=$BUN_OPENCLAW_PATH" \
|
||||
--detail "bun:helpCommand=passed"
|
||||
|
|
@ -6920,11 +6920,6 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh"
|
|||
expect(packageRunner.match(/verify-fs-safe-native\.mjs[^\n]+--mode require/gu)).toHaveLength(3);
|
||||
expect(packageRunner).toContain("bash scripts/e2e/bun-global-install-smoke.sh");
|
||||
expect(packageRunner.match(/-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT/g)).toHaveLength(4);
|
||||
expectTextToIncludeAll(packageRunner, [
|
||||
'MUSL_FS_SAFE_NATIVE_OUTCOME="passed"',
|
||||
'MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable"',
|
||||
'--detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME"',
|
||||
]);
|
||||
expect(updateRunner).toContain('mv "$platform_package" "$platform_package.omitted"');
|
||||
expect(updateRunner).toContain("--mode fallback");
|
||||
expect(updateRunner).toContain("-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT");
|
||||
|
|
@ -7966,11 +7961,10 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta
|
|||
'DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d',
|
||||
);
|
||||
expect(packageRunner).not.toMatch(/(^|\n)docker run -d/u);
|
||||
for (const runner of [composeRunner, packageRunner]) {
|
||||
expect(runner).toContain(
|
||||
'node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts"',
|
||||
);
|
||||
}
|
||||
expect(composeRunner).toContain(
|
||||
'node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts"',
|
||||
);
|
||||
expect(packageRunner).toContain('bash "$ROOT_DIR/scripts/e2e/lib/docker-package-identity.sh"');
|
||||
});
|
||||
|
||||
it("copies the complete bun harness closure into the package-install lane", () => {
|
||||
|
|
@ -8035,7 +8029,6 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta
|
|||
'corepack prepare "$1" --activate',
|
||||
"pnpm list --global --json",
|
||||
'test -f "$package_root/package.json"',
|
||||
'test "$PNPM_PACKAGE_VERSION" = "$PACKAGE_VERSION"',
|
||||
"pnpm add --global openclaw@file:/tmp/openclaw-current.tgz",
|
||||
'pnpm approve-builds --global "$artifact_build"',
|
||||
"bun@1.4.0",
|
||||
|
|
@ -8045,9 +8038,9 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta
|
|||
'PACKAGE_HARNESS_DIR="$(mktemp -d',
|
||||
"chmod -R a+rX",
|
||||
'-v "$PACKAGE_HARNESS_DIR:/repo:ro"',
|
||||
'--container "npm=$NPM_PROOF_CONTAINER"',
|
||||
'--container "pnpm=$PNPM_PROOF_CONTAINER"',
|
||||
'--container "bun=$BUN_PROOF_CONTAINER"',
|
||||
'"$PACKAGE_TGZ" \\',
|
||||
'"$IDENTITY_PATH" \\',
|
||||
'"$MUSL_PROOF_CONTAINER"',
|
||||
]);
|
||||
expect(packageRunner).not.toContain('-v "$ROOT_DIR:/repo:ro"');
|
||||
expectTextToIncludeAll(installerRunner, [
|
||||
|
|
|
|||
250
test/scripts/docker-package-identity.test.ts
Normal file
250
test/scripts/docker-package-identity.test.ts
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
import { spawnSync } from "node:child_process";
|
||||
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { delimiter, join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
const ROOT_DIR = process.cwd();
|
||||
const RUNNER_PATH = join(ROOT_DIR, "scripts/e2e/docker-package-install.sh");
|
||||
|
||||
type PackageIdentityOptions = {
|
||||
artifactVersion: string;
|
||||
nativeContract?: "required" | "not-applicable";
|
||||
bunCli?: string;
|
||||
bunManifest?: string;
|
||||
npmCli?: string;
|
||||
npmManifest?: string;
|
||||
pnpmCli?: string;
|
||||
pnpmManifest?: string;
|
||||
};
|
||||
|
||||
function runPackageIdentity(options: PackageIdentityOptions) {
|
||||
const root = tempDirs.make("openclaw-docker-package-identity-");
|
||||
const binDir = join(root, "bin");
|
||||
const packageDir = join(root, "package");
|
||||
const packageTgz = join(root, "candidate.tgz");
|
||||
const identityPath = join(root, "identity.json");
|
||||
mkdirSync(binDir);
|
||||
mkdirSync(packageDir);
|
||||
writeFileSync(
|
||||
join(packageDir, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", version: options.artifactVersion }),
|
||||
);
|
||||
const pack = spawnSync("tar", ["-czf", packageTgz, "-C", root, "package"], {
|
||||
encoding: "utf8",
|
||||
});
|
||||
expect(pack.status, pack.stderr).toBe(0);
|
||||
|
||||
const dockerPath = join(binDir, "docker");
|
||||
writeFileSync(
|
||||
dockerPath,
|
||||
`#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
command="\${1:-}"
|
||||
shift || true
|
||||
case "$command" in
|
||||
image|run|rm|logs)
|
||||
exit 0
|
||||
;;
|
||||
exec)
|
||||
container="\${1:?missing container}"
|
||||
shift
|
||||
command_line="$*"
|
||||
if [[ "$command_line" == "test -f /tmp/openclaw-proof-ready" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$command_line" == "cat /tmp/openclaw-package-root" ]]; then
|
||||
printf "/fake/pnpm/openclaw"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$command_line" == "cat /tmp/openclaw-version" ]]; then
|
||||
if [[ "$container" == *-npm-proof-* ]]; then
|
||||
printf "%s" "$FAKE_NPM_CLI"
|
||||
elif [[ "$container" == *-pnpm-proof-* ]]; then
|
||||
printf "%s" "$FAKE_PNPM_CLI"
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$command_line" == *"/tmp/openclaw-bun-proof.json"* ]]; then
|
||||
case "$command_line" in
|
||||
*installedPackageRoot*) printf "/fake/bun/openclaw" ;;
|
||||
*installedPackageVersion*) printf "%s" "$FAKE_BUN_MANIFEST" ;;
|
||||
*openclawVersion*) printf "%s" "$FAKE_BUN_CLI" ;;
|
||||
*openclawPath*) printf "/fake/bun/bin/openclaw" ;;
|
||||
*) exit 2 ;;
|
||||
esac
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$command_line" == *"package.json"* ]]; then
|
||||
if [[ "$container" == *-npm-proof-* ]]; then
|
||||
printf "%s" "$FAKE_NPM_MANIFEST"
|
||||
elif [[ "$container" == *-pnpm-proof-* ]]; then
|
||||
printf "%s" "$FAKE_PNPM_MANIFEST"
|
||||
else
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
exit 2
|
||||
;;
|
||||
inspect)
|
||||
reference="\${!#}"
|
||||
printf '[{"Id":"sha256:fake","Image":"sha256:image","Name":"/%s","RepoDigests":[],"State":{"Status":"running"}}]\\n' "$reference"
|
||||
;;
|
||||
*)
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
`,
|
||||
);
|
||||
chmodSync(dockerPath, 0o755);
|
||||
|
||||
const result = spawnSync("/bin/bash", [RUNNER_PATH], {
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
FAKE_BUN_CLI: options.bunCli ?? `OpenClaw ${options.artifactVersion}`,
|
||||
FAKE_BUN_MANIFEST: options.bunManifest ?? options.artifactVersion,
|
||||
FAKE_NPM_CLI: options.npmCli ?? `OpenClaw ${options.artifactVersion}`,
|
||||
FAKE_NPM_MANIFEST: options.npmManifest ?? options.artifactVersion,
|
||||
FAKE_PNPM_CLI: options.pnpmCli ?? `OpenClaw ${options.artifactVersion}`,
|
||||
FAKE_PNPM_MANIFEST: options.pnpmManifest ?? options.artifactVersion,
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: packageTgz,
|
||||
OPENCLAW_FS_SAFE_NATIVE_CONTRACT: options.nativeContract ?? "required",
|
||||
OPENCLAW_DOCKER_ARTIFACT_IDENTITY_PATH: identityPath,
|
||||
OPENCLAW_DOCKER_E2E_DISABLE_RESOURCE_LIMITS: "1",
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1",
|
||||
PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`,
|
||||
},
|
||||
});
|
||||
return {
|
||||
identity: result.status === 0 ? JSON.parse(readFileSync(identityPath, "utf8")) : undefined,
|
||||
result,
|
||||
};
|
||||
}
|
||||
|
||||
describe.skipIf(process.platform === "win32")("Docker package identity report", () => {
|
||||
it("rejects installed manifests that do not match the package artifact", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
bunCli: "OpenClaw 11.2.30",
|
||||
bunManifest: "11.2.30",
|
||||
npmCli: "OpenClaw 11.2.30",
|
||||
npmManifest: "11.2.30",
|
||||
pnpmCli: "OpenClaw 11.2.30",
|
||||
pnpmManifest: "11.2.30",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"[npm] installed manifest version '11.2.30' != artifact '1.2.3'",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a stale CLI version that only contains the artifact version as a substring", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
npmCli: "OpenClaw 11.2.30 (wrong)",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("[npm] CLI output parses to '11.2.30'");
|
||||
});
|
||||
|
||||
it("rejects a pnpm manifest version that differs from the artifact", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
pnpmManifest: "11.2.30",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"[pnpm] installed manifest version '11.2.30' != artifact '1.2.3'",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a pnpm CLI version that differs from the artifact", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
pnpmCli: "OpenClaw 11.2.30 (wrong)",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("[pnpm] CLI output parses to '11.2.30'");
|
||||
});
|
||||
|
||||
it("rejects a Bun manifest version that differs from the artifact", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
bunManifest: "11.2.30",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"[bun] installed manifest version '11.2.30' != artifact '1.2.3'",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a Bun CLI version that differs from the artifact", () => {
|
||||
const { result } = runPackageIdentity({
|
||||
artifactVersion: "1.2.3",
|
||||
bunCli: "OpenClaw 11.2.30 (wrong)",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("[bun] CLI output parses to '11.2.30'");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ version: "2026.6.21-beta.1+build.7", nativeContract: "required" },
|
||||
{ version: "2026.6.21-beta.1+build.7", nativeContract: "not-applicable" },
|
||||
{ version: "1.2.3-beta-rc.1+build.7", nativeContract: "required" },
|
||||
] as const)(
|
||||
"emits complete manager-owned identity for $version with the $nativeContract native contract",
|
||||
({ version, nativeContract }) => {
|
||||
const { identity, result } = runPackageIdentity({ artifactVersion: version, nativeContract });
|
||||
|
||||
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
|
||||
expect(identity).toEqual(
|
||||
expect.objectContaining({
|
||||
package: expect.objectContaining({ version }),
|
||||
containers: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
role: "musl",
|
||||
details: expect.objectContaining({
|
||||
fsSafeNative: nativeContract === "required" ? "passed" : "not-applicable",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role: "npm",
|
||||
details: expect.objectContaining({
|
||||
installedPackageRoot: "/usr/local/lib/node_modules/openclaw",
|
||||
installedPackageVersion: version,
|
||||
parsedOpenclawVersion: version,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role: "pnpm",
|
||||
details: expect.objectContaining({
|
||||
installedPackageRoot: "/fake/pnpm/openclaw",
|
||||
installedPackageVersion: version,
|
||||
parsedOpenclawVersion: version,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
role: "bun",
|
||||
details: expect.objectContaining({
|
||||
installedPackageRoot: "/fake/bun/openclaw",
|
||||
installedPackageVersion: version,
|
||||
parsedOpenclawVersion: version,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue