Skip to content

Commit f2aa705

Browse files
committed
fix(ci): walk every route entry the workspace app composes, not just pages and layouts
The tool-registry guard collected `page.tsx` and `layout.tsx`, and Next composes three more entries by convention: `error.tsx`, `loading.tsx`, `not-found.tsx`. Twenty-six exist under `app/workspace` and none was walked. `error.tsx` is always a Client Component — Next requires it — so a registry edge there reaches the browser bundle exactly as one from a page does. Coverage goes from 34 entry graphs to 60. Nothing new is reported: the hole was unexploited, and closing it costs nothing. The root deliberately stays at `app/workspace`. Widening it to `app` reports `(interfaces)/resume/[workflowId]/[executionId]/page.tsx`, a Server Component (`runtime = 'nodejs'`, `force-dynamic`) whose `PauseResumeManager` import resolves server-side and never reaches a client bundle. The guard cannot distinguish server from client entries, so it stays where its premise holds.
1 parent 297e970 commit f2aa705

1 file changed

Lines changed: 19 additions & 5 deletions

File tree

scripts/check-tool-registry-boundary.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,33 @@ const APP = join(ROOT, 'apps/sim')
4646
const FORBIDDEN = join(APP, 'tools/registry.ts')
4747

4848
/**
49-
* Root the guard walks: every `page.tsx` and `layout.tsx` under the workspace app.
49+
* Root the guard walks: every route entry Next.js composes under the workspace app.
5050
*
5151
* Discovered rather than listed. A hardcoded list goes stale silently — the
5252
* first version of this guard named `app/workspace/layout.tsx` as "the shared
5353
* shell", but that file only wraps `SocketProvider`; the real shell is
5454
* `app/workspace/[workspaceId]/layout.tsx`, which was never checked.
5555
*
56-
* Layouts must be enumerated separately because Next.js composes them by
57-
* convention — a page does not `import` its layout, so walking pages alone never
58-
* reaches layout modules even though every route pays for them.
56+
* Every filename here is composed by convention rather than imported, so each
57+
* must be enumerated: a page does not `import` its layout, its error boundary,
58+
* or its loading state, yet the route pays for all of them. `error.tsx` in
59+
* particular is always a Client Component — Next requires it — so a registry
60+
* edge there lands in the browser bundle as surely as one from a page.
61+
*
62+
* The root stays at `app/workspace`. Widening it to `app` reports
63+
* `(interfaces)/resume/[workflowId]/[executionId]/page.tsx`, which is a Server
64+
* Component (`runtime = 'nodejs'`, `force-dynamic`) whose `PauseResumeManager`
65+
* import resolves server-side and never reaches a client bundle. This guard
66+
* cannot tell the two apart, so it stays where the premise holds.
5967
*/
6068
const ENTRY_ROOT = 'app/workspace'
61-
const ENTRY_FILENAMES = new Set(['page.tsx', 'layout.tsx'])
69+
const ENTRY_FILENAMES = new Set([
70+
'page.tsx',
71+
'layout.tsx',
72+
'error.tsx',
73+
'loading.tsx',
74+
'not-found.tsx',
75+
])
6276

6377
function collectEntries(dir: string, found: string[] = []): string[] {
6478
for (const entry of readdirSync(dir, { withFileTypes: true })) {

0 commit comments

Comments
 (0)