Skip to content

Commit fdc6132

Browse files
committed
ci: teach R8 to resolve pnpm script names, drop affected-selector's inline copy
R8's zero-dep-job entry scan matched literal script paths in a run: block, so a bare `pnpm <script>` invocation found zero entries and R8 failed closed — the reason affected-selector kept an inline node command instead of calling pnpm check:affected:test (#1462). zeroDepJobs now also resolves a pnpm script name against package.json and scans the resolved command for entry paths, so affected-selector can call the named script like every other job. Also replaced the other workflows' inlined copies of named package.json scripts (test:replay:*, perf, perf:android, maestro:conformance:differential, check:mcp-metadata, size) with their pnpm names, keeping each job's CI-specific trailing flags — found via a repo-wide sweep for any run: block whose text duplicates a scripts entry. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01L67kDSTAJwaoLCwANEJFRM
1 parent ffd37a2 commit fdc6132

11 files changed

Lines changed: 108 additions & 29 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -129,23 +129,12 @@ jobs:
129129
install-deps: false
130130

131131
# The selector is fail-open and advisory (GitHub CI stays authoritative),
132-
# so the gate only guards the derivation model. Invoked directly with no
133-
# deps, mirroring the layering guard.
134-
#
135-
# Kept as an inline `node` invocation rather than `pnpm check:affected:test`
136-
# deliberately (#1462): R8's zero-dep-job closure check (scripts/layering/
137-
# zero-dep-jobs.ts) discovers each zero-dep job's entry scripts by matching
138-
# literal `scripts/**/*.test.ts` paths inside the `run:` block. A bare pnpm
139-
# script name has no such path, so the scan would find zero entries and
140-
# check.ts fails closed on that (`no entry script was found in its run
141-
# steps`) rather than silently exempting the job — see the model.test.ts
142-
# case "a job with no recognizable entry script is reported rather than
143-
# exempted". pnpm itself is provisioned regardless of this choice; R8
144-
# polices `pnpm install`, not the invocation style, so this is not a
145-
# `pnpm install` for the job — the entry-detection regex is the actual
146-
# constraint here.
132+
# so the gate only guards the derivation model. Invoked with no deps,
133+
# mirroring the layering guard. R8 (scripts/layering/zero-dep-jobs.ts)
134+
# resolves this bare pnpm script name back to package.json's own command
135+
# to find its entry scripts, so this is not a `pnpm install` for the job.
147136
- name: Check affected-selector model
148-
run: node --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/run.test.ts
137+
run: pnpm check:affected:test
149138

150139
maestro-conformance:
151140
name: Maestro Conformance Oracle

‎.github/workflows/conformance-differential.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ jobs:
125125
# burning its budget) — outcome parity alone cannot see that.
126126
- name: Run differential
127127
run: |
128-
node --experimental-strip-types scripts/maestro-conformance/differential/run.ts \
128+
pnpm maestro:conformance:differential \
129129
--platform ios \
130130
--out-dir "${{ github.workspace }}/.tmp/conformance-differential" \
131131
--trace-root "${{ github.workspace }}/.agent-device" \

‎.github/workflows/linux.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
- name: Run Linux replay smoke test
105105
run: |
106106
pnpm clean:daemon
107-
node --experimental-strip-types src/bin.ts test test/integration/replays/linux \
107+
pnpm test:replay:linux \
108108
--retries 2 \
109109
--report-junit test/artifacts/replays-linux.junit.xml
110110

‎.github/workflows/macos.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
run: pnpm build:macos-helper
5151

5252
- name: Run macOS integration test
53-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
53+
run: pnpm test:replay:macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
5454

5555
- name: Upload macOS artifacts
5656
if: always()

‎.github/workflows/perf-nightly.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
7070
- name: Run iOS command perf benchmark
7171
run: |
72-
node --experimental-strip-types scripts/perf/run.ts \
72+
pnpm perf \
7373
--platform ios \
7474
--device "iPhone 17 Pro" \
7575
--n "$PERF_ROUNDS" --warmup 1 \
@@ -116,7 +116,7 @@ jobs:
116116
adb -s emulator-5554 shell settings put global window_animation_scale 0 || true;
117117
adb -s emulator-5554 shell settings put global transition_animation_scale 0 || true;
118118
adb -s emulator-5554 shell settings put global animator_duration_scale 0 || true;
119-
node --experimental-strip-types scripts/perf/run.ts --platform android --serial emulator-5554 --n "$PERF_ROUNDS" --warmup 1 --out-dir "$GITHUB_WORKSPACE/perf-results"
119+
pnpm perf:android --serial emulator-5554 --n "$PERF_ROUNDS" --warmup 1 --out-dir "$GITHUB_WORKSPACE/perf-results"
120120
121121
- name: Upload Android perf report
122122
if: always()

‎.github/workflows/publish-mcp-registry.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
shell: bash
5656

5757
- name: Check MCP metadata sync
58-
run: node scripts/sync-mcp-metadata.mjs --check
58+
run: pnpm check:mcp-metadata
5959

6060
- name: Install mcp-publisher
6161
run: |

‎.github/workflows/replays-nightly.yml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ jobs:
111111
profile: pixel_7
112112
target: google_apis_playstore
113113
emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -no-metrics
114-
script: node --experimental-strip-types src/bin.ts test test/integration/replays/android --retries 2 --report-junit test/artifacts/replays-android.junit.xml
114+
script: pnpm test:replay:android --retries 2 --report-junit test/artifacts/replays-android.junit.xml
115115

116116
- name: Upload Android artifacts
117117
if: always()
@@ -160,13 +160,13 @@ jobs:
160160
pnpm clean:daemon
161161
162162
- name: Run iOS simulator replay suite
163-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/ios/simulator --retries 2 --artifacts-dir test/artifacts/replays-ios-simulator --report-junit test/artifacts/replays-ios-simulator.junit.xml
163+
run: pnpm test:replay:ios --retries 2 --artifacts-dir test/artifacts/replays-ios-simulator --report-junit test/artifacts/replays-ios-simulator.junit.xml
164164

165165
- name: Run iOS physical device replay suite
166166
if: env.IOS_UDID != ''
167167
env:
168168
IOS_UDID: ${{ vars.IOS_UDID }}
169-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/ios/device --udid "$IOS_UDID" --retries 2 --artifacts-dir test/artifacts/replays-ios-device --report-junit test/artifacts/replays-ios-device.junit.xml
169+
run: pnpm test:replay:ios-device --udid "$IOS_UDID" --retries 2 --artifacts-dir test/artifacts/replays-ios-device --report-junit test/artifacts/replays-ios-device.junit.xml
170170

171171
- name: Upload iOS artifacts
172172
if: always()
@@ -204,7 +204,7 @@ jobs:
204204
run: pnpm build:macos-helper
205205

206206
- name: Run macOS replay suite
207-
run: node --experimental-strip-types src/bin.ts test test/integration/replays/macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
207+
run: pnpm test:replay:macos --retries 2 --report-junit test/artifacts/replays-macos.junit.xml
208208

209209
- name: Upload macOS artifacts
210210
if: always()

‎.github/workflows/size.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
git checkout --detach "${{ github.event.pull_request.head.sha }}"
7272
pnpm install --frozen-lockfile
7373
pnpm build
74-
node scripts/size-report.mjs \
74+
pnpm size \
7575
--compare /tmp/agent-device-size-base.json \
7676
--startup-runs 7 \
7777
--json .tmp/size-report.json \
@@ -84,4 +84,4 @@ jobs:
8484
env:
8585
GITHUB_TOKEN: ${{ github.token }}
8686
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
87-
run: node scripts/size-report.mjs --post-comment .tmp/size-report.md
87+
run: pnpm size --post-comment .tmp/size-report.md

‎scripts/layering/check.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,13 @@ function checkZeroDepJobs(): Violation[] {
452452
.split('\n')
453453
.filter(Boolean),
454454
);
455+
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
456+
scripts?: Record<string, string>;
457+
};
458+
const packageScripts = new Map(Object.entries(packageJson.scripts ?? {}));
455459

456460
const violations: Violation[] = [];
457-
for (const job of zeroDepJobs(workflows, fileExists)) {
461+
for (const job of zeroDepJobs(workflows, fileExists, packageScripts)) {
458462
// Fail closed: a zero-dep job whose commands the entry scan cannot recognize would
459463
// otherwise be silently exempt from the rule it is the whole reason for.
460464
if (job.entries.length === 0) {

‎scripts/layering/model.test.ts‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,66 @@ test('a job with no recognizable entry script is reported rather than exempted',
332332
assert.deepEqual(jobs, [{ workflow: 'w.yml', job: 'zero-dep', entries: [] }]);
333333
});
334334

335+
test('a bare pnpm script name resolves through package.json to its entry scripts', () => {
336+
// The workflow names no path at all — only the pnpm script name package.json maps to the
337+
// real command. A zero-dep job may call its script this way (#1462) without R8 losing the
338+
// entries it needs to check: the resolution reads the same paths out of the mapped command.
339+
const workflow = `
340+
name: CI
341+
jobs:
342+
zero-dep:
343+
steps:
344+
- uses: ./.github/actions/setup-node-pnpm
345+
with:
346+
install-deps: false
347+
- run: pnpm check:affected:test
348+
`;
349+
const present = new Set([
350+
'scripts/check-affected/model.test.ts',
351+
'scripts/check-affected/run.test.ts',
352+
]);
353+
const packageScripts = new Map([
354+
[
355+
'check:affected:test',
356+
'node --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/run.test.ts',
357+
],
358+
]);
359+
const jobs = zeroDepJobs(
360+
new Map([['w.yml', workflow]]),
361+
(file) => present.has(file),
362+
packageScripts,
363+
);
364+
assert.deepEqual(jobs, [
365+
{
366+
workflow: 'w.yml',
367+
job: 'zero-dep',
368+
entries: ['scripts/check-affected/model.test.ts', 'scripts/check-affected/run.test.ts'],
369+
},
370+
]);
371+
});
372+
373+
test('a pnpm word that names no real package.json script resolves to nothing', () => {
374+
// `pnpm install` (or any other non-script pnpm subcommand) must not be treated as a script
375+
// name just because it follows `pnpm` — it is absent from packageScripts, same as a shell
376+
// word that merely looks like a path is absent from the tree.
377+
const workflow = `
378+
name: CI
379+
jobs:
380+
zero-dep:
381+
steps:
382+
- uses: ./.github/actions/setup-node-pnpm
383+
with:
384+
install-deps: false
385+
- run: pnpm install --frozen-lockfile
386+
`;
387+
const jobs = zeroDepJobs(
388+
new Map([['w.yml', workflow]]),
389+
() => true,
390+
new Map([['check:affected:test', 'node scripts/check-affected/run.test.ts']]),
391+
);
392+
assert.deepEqual(jobs, [{ workflow: 'w.yml', job: 'zero-dep', entries: [] }]);
393+
});
394+
335395
test('a package import anywhere in a zero-dep closure is rejected, builtins are not', () => {
336396
const tree = new Map([
337397
[
@@ -394,10 +454,13 @@ test("the repo's own zero-dep jobs resolve without node_modules", () => {
394454
: null;
395455
};
396456
const exists = (file: string): boolean => read(file) !== null;
457+
const packageJson = JSON.parse(read('package.json')!) as { scripts?: Record<string, string> };
458+
const packageScripts = new Map(Object.entries(packageJson.scripts ?? {}));
397459

398460
const jobs = zeroDepJobs(
399461
new Map([['.github/workflows/ci.yml', read('.github/workflows/ci.yml')!]]),
400462
exists,
463+
packageScripts,
401464
);
402465
assert.ok(jobs.length > 0, 'expected ci.yml to still declare at least one zero-dep job');
403466
for (const job of jobs) {

0 commit comments

Comments
 (0)