ci: bootstrap Node before pnpm setup (#12808)
## Thinking Path The trusted workflows currently invoke `pnpm/action-setup@v6` before installing the repository Node version. On hosts whose ambient Node is older than 22.13, the action downloads standalone `@pnpm/exe`, which has repeatedly taken several minutes. Supplying Node 24 first lets the same pinned pnpm action use its normal Node-backed path. ## What Changed - install Node 24 before every trusted `pnpm/action-setup` invocation - preserve the existing pnpm-store cache setup, pinned actions, telemetry suppression, conditions, and secret boundaries - enforce ordering, modern Node, condition parity, and cache counts in the workflow security contract ## Verification - workflow security: 7/7 - Prettier - actionlint (excluding one pre-existing SC2129 in an untouched Daytona shell block) - `git diff --check` ## Risks Low. Product code, providers, paid-runner selection, credentials, and pnpm version are unchanged. Jobs that restore pnpm cache run `setup-node` a second time after pnpm becomes available; the first setup is deliberately cache-free. ## Model Used Codex (GPT-5)
This commit is contained in:
parent
18ea965442
commit
a0a78ee609
|
|
@ -280,6 +280,11 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -373,6 +378,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -464,6 +474,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -546,6 +561,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -615,6 +635,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -656,6 +681,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -738,6 +768,11 @@ jobs:
|
|||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js for pnpm bootstrap
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -158,6 +158,10 @@ jobs:
|
|||
ref: ${{ needs.authorize.outputs.target_sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -166,10 +170,6 @@ jobs:
|
|||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Resolve target lockfile without lifecycle scripts
|
||||
id: lock
|
||||
run: |
|
||||
|
|
@ -236,6 +236,10 @@ jobs:
|
|||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -490,6 +494,10 @@ jobs:
|
|||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -615,6 +623,11 @@ jobs:
|
|||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
|
||||
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
|
|
@ -769,6 +782,10 @@ jobs:
|
|||
cp "$lock" pnpm-lock.yaml
|
||||
test "$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" = "$EXPECTED_LOCK_SHA256"
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -777,10 +794,6 @@ jobs:
|
|||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
# This job receives provider credentials only in the final paid-test
|
||||
# step. Keep target-selected dependency lifecycle code from running in
|
||||
# the protected environment during setup.
|
||||
|
|
@ -931,6 +944,10 @@ jobs:
|
|||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -1041,6 +1058,10 @@ jobs:
|
|||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
|
||||
env:
|
||||
NPM_CONFIG_AUDIT: "false"
|
||||
|
|
@ -1049,10 +1070,6 @@ jobs:
|
|||
with:
|
||||
version: 9.15.4
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download access-controlled normalized campaign
|
||||
|
|
|
|||
|
|
@ -45,6 +45,58 @@ describe("public repository paid workflow security", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("installs a modern Node runtime before every trusted pnpm bootstrap", async () => {
|
||||
const workflows = [
|
||||
{
|
||||
name: "runner-full-stack-e2e.yml",
|
||||
expectedCachedSetupNodeSteps: 4,
|
||||
},
|
||||
{
|
||||
name: "pr-trusted.yml",
|
||||
expectedCachedSetupNodeSteps: 7,
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, expectedCachedSetupNodeSteps } of workflows) {
|
||||
const workflow = await readFile(
|
||||
path.join(repositoryRoot, ".github/workflows", name),
|
||||
"utf8",
|
||||
);
|
||||
const steps = workflow.split(/\n(?= {6}- )/u);
|
||||
const pnpmSetupStepIndexes = steps.flatMap((step, index) =>
|
||||
step.includes("uses: pnpm/action-setup@") ? [index] : [],
|
||||
);
|
||||
|
||||
expect(pnpmSetupStepIndexes, name).toHaveLength(7);
|
||||
for (const pnpmSetupStepIndex of pnpmSetupStepIndexes) {
|
||||
const pnpmSetupStep = steps[pnpmSetupStepIndex]!;
|
||||
const nodeBootstrapStep = steps[pnpmSetupStepIndex - 1]!;
|
||||
expect(nodeBootstrapStep, name).toContain("uses: actions/setup-node@");
|
||||
expect(nodeBootstrapStep, name).not.toContain("cache: pnpm");
|
||||
|
||||
const nodeVersionMatch = nodeBootstrapStep.match(
|
||||
/^\s*node-version:\s*["']?(\d+)(?:\.(\d+))?/mu,
|
||||
);
|
||||
expect(nodeVersionMatch, name).not.toBeNull();
|
||||
const nodeMajor = Number(nodeVersionMatch![1]);
|
||||
const nodeMinor = Number(nodeVersionMatch![2] ?? 0);
|
||||
expect(
|
||||
nodeMajor > 22 || (nodeMajor === 22 && nodeMinor >= 13),
|
||||
`${name} must install Node >=22.13 before pnpm/action-setup`,
|
||||
).toBe(true);
|
||||
|
||||
const conditionPattern = /^ {6}- if:\s*(.+)$/mu;
|
||||
expect(nodeBootstrapStep.match(conditionPattern)?.[1] ?? null).toBe(
|
||||
pnpmSetupStep.match(conditionPattern)?.[1] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
expect(workflow.match(/^\s+cache: pnpm$/gmu), name).toHaveLength(
|
||||
expectedCachedSetupNodeSteps,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("gates every provider-secret job with stable actor IDs", async () => {
|
||||
const workflows = await Promise.all(
|
||||
["runner-full-stack-e2e.yml", "runner-live-evals.yml", "e2e.yml"].map(
|
||||
|
|
|
|||
Loading…
Reference in New Issue