mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
ci: honor the pinned Node version on hosted runners
Resolve hosted 24.x requests through the workflow's existing same-major Node pin, preserving explicit compatibility inputs and self-hosted selection. This keeps runner moves from silently changing the tested runtime patch. No version value changes. Linux owning-config and setup guards, changed checks, boundary lint, and P2 review passed. The repository pin remains 24.21.0.
This commit is contained in:
parent
11c32c4313
commit
84bf30fecf
4 changed files with 54 additions and 9 deletions
2
.github/actions/setup-node-env/action.yml
vendored
2
.github/actions/setup-node-env/action.yml
vendored
|
|
@ -149,7 +149,7 @@ runs:
|
||||||
id: setup-node
|
id: setup-node
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
REQUESTED_NODE_VERSION: ${{ inputs.node-version }}
|
REQUESTED_NODE_VERSION: ${{ runner.environment == 'github-hosted' && inputs.node-version == '24.x' && startsWith(env.NODE_VERSION, '24.') && env.NODE_VERSION || inputs.node-version }}
|
||||||
OPENCLAW_NODE_TOOLCHAIN_ROOT: ${{ inputs.cache-mode != 'off' && runner.os != 'Windows' && format('{0}/openclaw-node-toolchain/node', runner.temp) || '' }}
|
OPENCLAW_NODE_TOOLCHAIN_ROOT: ${{ inputs.cache-mode != 'off' && runner.os != 'Windows' && format('{0}/openclaw-node-toolchain/node', runner.temp) || '' }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ read_when:
|
||||||
|
|
||||||
## Runners
|
## Runners
|
||||||
|
|
||||||
|
Hosted Node 24 setup honors the workflow's existing `NODE_VERSION` pin when the
|
||||||
|
setup input is `24.x`. This prevents runner-image refreshes from silently choosing
|
||||||
|
a different patch version. Explicit compatibility versions retain their own
|
||||||
|
selection, and self-hosted setup retains its existing range and toolchain-cache
|
||||||
|
policy. A restored cache key is not runtime proof: record the selected binary and
|
||||||
|
`node -v` when comparing runner backends.
|
||||||
|
|
||||||
Runner choice follows contributor trust, not whether a pull request came from a fork. Every `runs-on` expression admits Blacksmith only when `github.event.pull_request.author_association` is `OWNER`, `MEMBER`, `COLLABORATOR`, or `CONTRIBUTOR`, so a fork pull request from someone who has already landed a commit is routed exactly like a maintainer pull request. `FIRST_TIME_CONTRIBUTOR`, `FIRST_TIMER`, `NONE`, and `MANNEQUIN` stay on GitHub-hosted runners, which are free for public repositories, so an unreviewed author cannot spend Blacksmith capacity. Maintainers report `CONTRIBUTOR` here because org membership is concealed; keep `CONTRIBUTOR` in that list or maintainer pull requests lose Blacksmith. Pushes and manual dispatches are unaffected. Cache trust is a separate, stricter boundary: exact dependency restores require a pull request from `openclaw/openclaw`, and ordinary CI never publishes the shared archives. The separate trusted warmer owns publication.
|
Runner choice follows contributor trust, not whether a pull request came from a fork. Every `runs-on` expression admits Blacksmith only when `github.event.pull_request.author_association` is `OWNER`, `MEMBER`, `COLLABORATOR`, or `CONTRIBUTOR`, so a fork pull request from someone who has already landed a commit is routed exactly like a maintainer pull request. `FIRST_TIME_CONTRIBUTOR`, `FIRST_TIMER`, `NONE`, and `MANNEQUIN` stay on GitHub-hosted runners, which are free for public repositories, so an unreviewed author cannot spend Blacksmith capacity. Maintainers report `CONTRIBUTOR` here because org membership is concealed; keep `CONTRIBUTOR` in that list or maintainer pull requests lose Blacksmith. Pushes and manual dispatches are unaffected. Cache trust is a separate, stricter boundary: exact dependency restores require a pull request from `openclaw/openclaw`, and ordinary CI never publishes the shared archives. The separate trusted warmer owns publication.
|
||||||
|
|
||||||
| Runner | Jobs |
|
| Runner | Jobs |
|
||||||
|
|
|
||||||
|
|
@ -4883,6 +4883,40 @@ setImmediate(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses the workflow Node 24 pin for hosted default requests", () => {
|
||||||
|
const action = parse(readFileSync(".github/actions/setup-node-env/action.yml", "utf8"));
|
||||||
|
const setup: WorkflowStep = expectDefined(
|
||||||
|
action.runs.steps.find((step: WorkflowStep) => step.id === "setup-node"),
|
||||||
|
"Node setup",
|
||||||
|
);
|
||||||
|
const expression = String(
|
||||||
|
expectDefined(setup.env?.REQUESTED_NODE_VERSION, "requested Node version"),
|
||||||
|
)
|
||||||
|
.replace(/^\$\{\{\s*|\s*\}\}$/gu, "")
|
||||||
|
.replaceAll("inputs.node-version", 'inputs["node-version"]');
|
||||||
|
for (const [environment, requested, pin, expected] of [
|
||||||
|
["github-hosted", "24.x", "24.21.0", "24.21.0"],
|
||||||
|
["github-hosted", "24.x", undefined, "24.x"],
|
||||||
|
["github-hosted", "24.x", "", "24.x"],
|
||||||
|
["github-hosted", "24.x", "26.1.0", "24.x"],
|
||||||
|
["github-hosted", "24.16.0", "24.21.0", "24.16.0"],
|
||||||
|
["github-hosted", "26.x", "24.21.0", "26.x"],
|
||||||
|
["self-hosted", "24.x", "24.21.0", "24.x"],
|
||||||
|
["", "24.x", "24.21.0", "24.x"],
|
||||||
|
] as const) {
|
||||||
|
expect(
|
||||||
|
runInNewContext(expression, {
|
||||||
|
runner: { environment },
|
||||||
|
inputs: { "node-version": requested },
|
||||||
|
env: pin === undefined ? {} : { NODE_VERSION: pin },
|
||||||
|
startsWith: (value: unknown, prefix: string) =>
|
||||||
|
typeof value === "string" && value.startsWith(prefix),
|
||||||
|
}),
|
||||||
|
`${environment}/${requested}/${pin ?? "unset"}`,
|
||||||
|
).toBe(expected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("owns one exact immutable semantic dependency cache", () => {
|
it("owns one exact immutable semantic dependency cache", () => {
|
||||||
const actionSource = readFileSync(".github/actions/setup-node-env/action.yml", "utf8");
|
const actionSource = readFileSync(".github/actions/setup-node-env/action.yml", "utf8");
|
||||||
const ciSource = readFileSync(".github/workflows/ci.yml", "utf8");
|
const ciSource = readFileSync(".github/workflows/ci.yml", "utf8");
|
||||||
|
|
|
||||||
|
|
@ -177,25 +177,29 @@ describe("setup-pnpm-store-cache ensure-node", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("repairs PATH from the toolcache when setup-node leaves an old node active", () => {
|
it.each([
|
||||||
|
{ active: "20.20.0", requested: "24.16.0" },
|
||||||
|
{ active: "24.22.0", requested: "24.21.0" },
|
||||||
|
])("repairs PATH from active Node $active to requested $requested", ({ active, requested }) => {
|
||||||
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
|
const root = mkdtempSync(join(tmpdir(), "openclaw-ensure-node-"));
|
||||||
try {
|
try {
|
||||||
const activeBin = join(root, "active", "bin");
|
const activeBin = join(root, "active", "bin");
|
||||||
writeFakeNode(activeBin, "20.20.0");
|
writeFakeNode(activeBin, active);
|
||||||
const toolcacheBin = join(root, "toolcache", "node", "24.16.0", "x64", "bin");
|
const toolcacheBin = join(root, "toolcache", "node", requested, "x64", "bin");
|
||||||
const toolcacheNode = writeFakeNode(toolcacheBin, "24.16.0");
|
const toolcacheNode = writeFakeNode(toolcacheBin, requested);
|
||||||
writeFakeNode(
|
writeFakeNode(
|
||||||
join(root, "toolcache", "node", "26.1.0", "x64", "lib", "node_modules", "bundled", "bin"),
|
join(root, "toolcache", "node", "26.1.0", "x64", "lib", "node_modules", "bundled", "bin"),
|
||||||
"24.16.0",
|
requested,
|
||||||
);
|
);
|
||||||
const result = runEnsureNode(root, "24.16.0", {
|
const result = runEnsureNode(root, requested, {
|
||||||
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
|
PATH: `${activeBin}:${process.env.PATH ?? ""}`,
|
||||||
|
OPENCLAW_NODE_TOOLCHAIN_ROOT: join(root, "missing-owned-toolchain"),
|
||||||
RUNNER_TOOL_CACHE: join(root, "toolcache"),
|
RUNNER_TOOL_CACHE: join(root, "toolcache"),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.status).toBe(0);
|
expect(result.status).toBe(0);
|
||||||
expect(result.stdout).toContain(`Using Node 24.16.0 from ${toolcacheNode}`);
|
expect(result.stdout).toContain(`Using Node ${requested} from ${toolcacheNode}`);
|
||||||
expect(result.stdout).toContain(`${toolcacheNode}\n24.16.0`);
|
expect(result.stdout).toContain(`${toolcacheNode}\n${requested}`);
|
||||||
} finally {
|
} finally {
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue