Files
adolf/test/scripts/setup-pnpm-store-cache-ensure-node.test.ts
alvis bedb527145
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Vendor OpenClaw source as Adolf fork baseline
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
2026-07-05 09:36:54 +00:00

289 lines
9.6 KiB
TypeScript

// Setup Pnpm Store Cache Ensure Node tests cover setup pnpm store cache ensure node script behavior.
import { spawnSync } from "node:child_process";
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { beforeAll, describe, expect, it } from "vitest";
const ensureNodeScript = resolve(".github/actions/setup-pnpm-store-cache/ensure-node.sh");
let missingToolcacheCase: {
status: number | null;
stderr: string;
};
function writeFakeNode(binDir: string, version: string) {
mkdirSync(binDir, { recursive: true });
const nodePath = join(binDir, "node");
writeFileSync(
nodePath,
`#!/usr/bin/env bash
if [[ "$1" == "-p" ]]; then
echo "${version}"
exit 0
fi
if [[ "$1" == "-v" ]]; then
echo "v${version}"
exit 0
fi
exit 0
`,
);
chmodSync(nodePath, 0o755);
return nodePath;
}
function runEnsureNode(root: string, requested: string, extraEnv: NodeJS.ProcessEnv = {}) {
const githubPath = join(root, "github-path");
const pathOverride = extraEnv.PATH;
const result = spawnSync(
"bash",
[
"-c",
[
"set -e",
...(pathOverride ? [`export PATH=${JSON.stringify(pathOverride)}`] : []),
`source "${ensureNodeScript}"`,
`openclaw_ensure_node "${requested}"`,
"command -v node",
"node -p 'process.versions.node'",
].join("; "),
],
{
encoding: "utf8",
env: {
...process.env,
GITHUB_PATH: githubPath,
...extraEnv,
},
},
);
return result;
}
function runVersionMatch(actual: string, requested: string) {
return spawnSync(
"bash",
[
"-c",
[
`source "${ensureNodeScript}"`,
`openclaw_node_version_matches "${actual}" "${requested}"`,
].join("; "),
],
{ encoding: "utf8", env: process.env },
);
}
describe("setup-pnpm-store-cache ensure-node", () => {
beforeAll(() => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const result = spawnSync(
"bash",
[
"-c",
[
"set -euo pipefail",
`source "${ensureNodeScript}"`,
`openclaw_find_toolcache_node "99.99.99"`,
].join("; "),
],
{
encoding: "utf8",
env: {
PATH: process.env.PATH ?? "",
RUNNER_TOOL_CACHE: join(root, "missing-toolcache"),
AGENT_TOOLSDIRECTORY: join(root, "missing-agent-tools"),
ACTIONS_RUNNER_TOOL_CACHE: join(root, "missing-actions-cache"),
OPENCLAW_CONTAINER_TOOL_CACHE: join(root, "missing-container-cache"),
},
},
);
missingToolcacheCase = {
status: result.status,
stderr: result.stderr,
};
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("uses a matching active node", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
const activeNode = writeFakeNode(activeBin, "24.15.0");
const result = runEnsureNode(root, "24.15.0", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
RUNNER_TOOL_CACHE: join(root, "missing-toolcache"),
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Using active Node 24.15.0 at ${activeNode}`);
expect(result.stdout.trim().endsWith("24.15.0")).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("repairs PATH from the toolcache when setup-node leaves an old node active", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "20.20.0");
const toolcacheBin = join(root, "toolcache", "node", "24.15.0", "x64", "bin");
const toolcacheNode = writeFakeNode(toolcacheBin, "24.15.0");
const result = runEnsureNode(root, "24.15.0", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
RUNNER_TOOL_CACHE: join(root, "toolcache"),
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Using Node 24.15.0 from ${toolcacheNode}`);
expect(result.stdout).toContain(`${toolcacheNode}\n24.15.0`);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("normalizes Windows toolcache paths for Git Bash before prepending PATH", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "22.22.3");
const toolcacheBin = join(root, "toolcache", "node", "24.15.0", "x64");
const toolcacheNode = writeFakeNode(toolcacheBin, "24.15.0");
const helperBin = join(root, "helpers");
mkdirSync(helperBin, { recursive: true });
const cygpath = join(helperBin, "cygpath");
writeFileSync(
cygpath,
`#!/usr/bin/env bash
if [[ "$1" == "-u" ]]; then
echo "${toolcacheBin}"
exit 0
fi
if [[ "$1" == "-w" ]]; then
echo "C:\\\\hostedtoolcache\\\\windows\\\\node\\\\24.15.0\\\\x64"
exit 0
fi
exit 1
`,
);
chmodSync(cygpath, 0o755);
const githubPath = join(root, "github-path");
const result = spawnSync(
"bash",
[
"-c",
[
"set -e",
`export PATH=${JSON.stringify(`${helperBin}:${activeBin}:${process.env.PATH ?? ""}`)}`,
`export GITHUB_PATH=${JSON.stringify(githubPath)}`,
`source "${ensureNodeScript}"`,
`openclaw_prepend_node_bin "C:\\\\hostedtoolcache\\\\windows/node/24.15.0/x64"`,
"command -v node",
"node -p 'process.versions.node'",
`cat "${githubPath}"`,
].join("; "),
],
{ encoding: "utf8", env: process.env },
);
expect(result.status).toBe(0);
expect(result.stdout).toContain(`${toolcacheNode}\n24.15.0`);
expect(result.stdout).toContain("C:\\hostedtoolcache\\windows\\node\\24.15.0\\x64");
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("repairs PATH from the container-mounted GitHub Actions toolcache", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "20.20.0");
const toolcacheBin = join(root, "__t", "node", "24.99.99", "x64", "bin");
const toolcacheNode = writeFakeNode(toolcacheBin, "24.99.99");
const result = runEnsureNode(root, "24.99.99", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
OPENCLAW_CONTAINER_TOOL_CACHE: join(root, "__t"),
RUNNER_TOOL_CACHE: join(root, "hostedtoolcache"),
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Using Node 24.99.99 from ${toolcacheNode}`);
expect(result.stdout).toContain(`${toolcacheNode}\n24.99.99`);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("accepts major wildcard requests when selecting a toolcache node", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "20.20.0");
const toolcacheBin = join(root, "toolcache", "node", "24.15.0", "x64", "bin");
writeFakeNode(toolcacheBin, "24.15.0");
const result = runEnsureNode(root, "24.x", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
RUNNER_TOOL_CACHE: join(root, "toolcache"),
});
expect(result.status).toBe(0);
expect(result.stdout.trim().endsWith("24.15.0")).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("keeps the Node 22 wildcard at the supported minimum", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "22.18.0");
const toolcacheBin = join(root, "toolcache", "node", "22.22.3", "x64", "bin");
const toolcacheNode = writeFakeNode(toolcacheBin, "22.22.3");
const result = runEnsureNode(root, "22.x", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
RUNNER_TOOL_CACHE: join(root, "toolcache"),
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Using Node 22.22.3 from ${toolcacheNode}`);
expect(result.stdout.trim().endsWith("22.22.3")).toBe(true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("rejects Node 22 wildcard matches below the supported minimum", () => {
expect(runVersionMatch("22.18.0", "22.x").status).toBe(1);
expect(runVersionMatch("22.19.0", "22.x").status).toBe(0);
});
it("fails clearly when no matching node is available", () => {
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
try {
const activeBin = join(root, "active", "bin");
writeFakeNode(activeBin, "20.20.0");
const result = runEnsureNode(root, "99.99.99", {
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
RUNNER_TOOL_CACHE: join(root, "toolcache"),
});
expect(result.status).toBe(1);
expect(result.stdout).toContain("::error::Expected Node '99.99.99'");
expect(result.stdout).toContain("active node is '20.20.0'");
} finally {
rmSync(root, { recursive: true, force: true });
}
});
it("handles missing toolcache roots under nounset", () => {
expect(missingToolcacheCase.status).toBe(1);
expect(missingToolcacheCase.stderr).not.toContain("unbound variable");
});
});