@warlock.js/web 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/esm/build/contribution.d.mts +55 -0
  5. package/esm/build/contribution.mjs +132 -0
  6. package/esm/build/contribution.mjs.map +1 -0
  7. package/esm/build/discover-pages.mjs +294 -0
  8. package/esm/build/discover-pages.mjs.map +1 -0
  9. package/esm/build/generate-client-registry.mjs +123 -0
  10. package/esm/build/generate-client-registry.mjs.map +1 -0
  11. package/esm/build/generate-pages-barrel.mjs +330 -0
  12. package/esm/build/generate-pages-barrel.mjs.map +1 -0
  13. package/esm/build/read-route-exports.mjs +212 -0
  14. package/esm/build/read-route-exports.mjs.map +1 -0
  15. package/esm/client/build-hydrated-tree.mjs +113 -0
  16. package/esm/client/build-hydrated-tree.mjs.map +1 -0
  17. package/esm/client/hydrate-page.mjs +73 -0
  18. package/esm/client/hydrate-page.mjs.map +1 -0
  19. package/esm/client/navigation/current-route.d.mts +91 -0
  20. package/esm/client/navigation/current-route.mjs +66 -0
  21. package/esm/client/navigation/current-route.mjs.map +1 -0
  22. package/esm/client/navigation/fetch-page-data.mjs +97 -0
  23. package/esm/client/navigation/fetch-page-data.mjs.map +1 -0
  24. package/esm/client/navigation/navigation-root.mjs +196 -0
  25. package/esm/client/navigation/navigation-root.mjs.map +1 -0
  26. package/esm/client/navigation/prefetch.mjs +110 -0
  27. package/esm/client/navigation/prefetch.mjs.map +1 -0
  28. package/esm/client/navigation/refresh.d.mts +40 -0
  29. package/esm/client/navigation/refresh.mjs +146 -0
  30. package/esm/client/navigation/refresh.mjs.map +1 -0
  31. package/esm/client/navigation/verbs.d.mts +55 -0
  32. package/esm/client/navigation/verbs.mjs +66 -0
  33. package/esm/client/navigation/verbs.mjs.map +1 -0
  34. package/esm/client/runtime/index.d.mts +4 -0
  35. package/esm/client/runtime/index.mjs +4 -0
  36. package/esm/client/runtime/manifest.d.mts +8 -0
  37. package/esm/client/runtime/manifest.mjs +77 -0
  38. package/esm/client/runtime/manifest.mjs.map +1 -0
  39. package/esm/client/runtime/matcher.d.mts +7 -0
  40. package/esm/client/runtime/matcher.mjs +169 -0
  41. package/esm/client/runtime/matcher.mjs.map +1 -0
  42. package/esm/client/runtime/types.d.mts +22 -0
  43. package/esm/components/default-app.mjs +32 -0
  44. package/esm/components/default-app.mjs.map +1 -0
  45. package/esm/components/document-context.d.mts +1 -0
  46. package/esm/components/document-context.mjs +24 -0
  47. package/esm/components/document-context.mjs.map +1 -0
  48. package/esm/components/head.d.mts +14 -0
  49. package/esm/components/head.mjs +63 -0
  50. package/esm/components/head.mjs.map +1 -0
  51. package/esm/components/link.d.mts +157 -0
  52. package/esm/components/link.mjs +194 -0
  53. package/esm/components/link.mjs.map +1 -0
  54. package/esm/components/scripts.d.mts +14 -0
  55. package/esm/components/scripts.mjs +21 -0
  56. package/esm/components/scripts.mjs.map +1 -0
  57. package/esm/connector/index.d.mts +6 -0
  58. package/esm/connector/index.mjs +7 -0
  59. package/esm/context.d.mts +107 -0
  60. package/esm/hydration/index.d.mts +1 -0
  61. package/esm/hydration/index.mjs +33 -0
  62. package/esm/hydration/index.mjs.map +1 -0
  63. package/esm/hydration-payload.d.mts +1 -0
  64. package/esm/hydration-payload.mjs +72 -0
  65. package/esm/hydration-payload.mjs.map +1 -0
  66. package/esm/index.d.mts +35 -0
  67. package/esm/index.mjs +12 -0
  68. package/esm/loaders.d.mts +47 -0
  69. package/esm/metadata.d.mts +54 -0
  70. package/esm/props.d.mts +42 -0
  71. package/esm/route.d.mts +30 -0
  72. package/esm/routing/compose-route-path.mjs +33 -0
  73. package/esm/routing/compose-route-path.mjs.map +1 -0
  74. package/esm/routing/data-request.mjs +52 -0
  75. package/esm/routing/data-request.mjs.map +1 -0
  76. package/esm/routing/layout-policy.mjs +78 -0
  77. package/esm/routing/layout-policy.mjs.map +1 -0
  78. package/esm/routing/navigator.mjs +21 -0
  79. package/esm/routing/navigator.mjs.map +1 -0
  80. package/esm/routing/query-string.d.mts +250 -0
  81. package/esm/routing/query-string.mjs +340 -0
  82. package/esm/routing/query-string.mjs.map +1 -0
  83. package/esm/routing/route-identity.mjs +68 -0
  84. package/esm/routing/route-identity.mjs.map +1 -0
  85. package/esm/routing/route-table.d.mts +34 -0
  86. package/esm/routing/route-table.mjs +182 -0
  87. package/esm/routing/route-table.mjs.map +1 -0
  88. package/esm/routing/router-events.d.mts +159 -0
  89. package/esm/routing/router-events.mjs +83 -0
  90. package/esm/routing/router-events.mjs.map +1 -0
  91. package/esm/server/buffered-response.mjs +115 -0
  92. package/esm/server/buffered-response.mjs.map +1 -0
  93. package/esm/server/build-hydration-payload.mjs +27 -0
  94. package/esm/server/build-hydration-payload.mjs.map +1 -0
  95. package/esm/server/client-asset-url-prefix.d.mts +22 -0
  96. package/esm/server/client-asset-url-prefix.mjs +23 -0
  97. package/esm/server/client-asset-url-prefix.mjs.map +1 -0
  98. package/esm/server/create-page-module-loader.mjs +49 -0
  99. package/esm/server/create-page-module-loader.mjs.map +1 -0
  100. package/esm/server/create-page-route-handler.mjs +107 -0
  101. package/esm/server/create-page-route-handler.mjs.map +1 -0
  102. package/esm/server/dev-server.mjs +167 -0
  103. package/esm/server/dev-server.mjs.map +1 -0
  104. package/esm/server/execute-page-request.d.mts +1 -0
  105. package/esm/server/execute-page-request.mjs +166 -0
  106. package/esm/server/execute-page-request.mjs.map +1 -0
  107. package/esm/server/execute-page-request.types.d.mts +1 -0
  108. package/esm/server/hydration-client-url.d.mts +37 -0
  109. package/esm/server/hydration-client-url.mjs +101 -0
  110. package/esm/server/hydration-client-url.mjs.map +1 -0
  111. package/esm/server/index.mjs +13 -0
  112. package/esm/server/install-page-routes-from-manifest.mjs +133 -0
  113. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -0
  114. package/esm/server/install-page-routes.d.mts +1 -0
  115. package/esm/server/install-page-routes.mjs +6 -0
  116. package/esm/server/install-production-page-routes.mjs +55 -0
  117. package/esm/server/install-production-page-routes.mjs.map +1 -0
  118. package/esm/server/match-page-route.mjs +48 -0
  119. package/esm/server/match-page-route.mjs.map +1 -0
  120. package/esm/server/page-context.d.mts +1 -0
  121. package/esm/server/page-context.mjs +33 -0
  122. package/esm/server/page-context.mjs.map +1 -0
  123. package/esm/server/page-manifest.d.mts +102 -0
  124. package/esm/server/page-manifest.mjs +31 -0
  125. package/esm/server/page-manifest.mjs.map +1 -0
  126. package/esm/server/render-page.mjs +214 -0
  127. package/esm/server/render-page.mjs.map +1 -0
  128. package/esm/server/resolve-page-metadata.mjs +33 -0
  129. package/esm/server/resolve-page-metadata.mjs.map +1 -0
  130. package/esm/server/resolve-validation-data.mjs +27 -0
  131. package/esm/server/resolve-validation-data.mjs.map +1 -0
  132. package/esm/server/settle-page-response.d.mts +1 -0
  133. package/esm/server/settle-page-response.mjs +90 -0
  134. package/esm/server/settle-page-response.mjs.map +1 -0
  135. package/esm/server/stylesheet-urls.mjs +65 -0
  136. package/esm/server/stylesheet-urls.mjs.map +1 -0
  137. package/esm/server/web-connector-factory.d.mts +25 -0
  138. package/esm/server/web-connector-factory.mjs +121 -0
  139. package/esm/server/web-connector-factory.mjs.map +1 -0
  140. package/esm/server/web-connector.d.mts +34 -0
  141. package/esm/server/web-connector.mjs +516 -0
  142. package/esm/server/web-connector.mjs.map +1 -0
  143. package/esm/shared.d.mts +22 -0
  144. package/esm/shared.mjs +223 -0
  145. package/esm/shared.mjs.map +1 -0
  146. package/esm/validation.d.mts +31 -0
  147. package/esm/vite/app-convention-aliases.mjs +43 -0
  148. package/esm/vite/app-convention-aliases.mjs.map +1 -0
  149. package/esm/vite/build-client.d.mts +38 -0
  150. package/esm/vite/build-client.mjs +72 -0
  151. package/esm/vite/build-client.mjs.map +1 -0
  152. package/esm/vite/gate-a-resolve.d.mts +57 -0
  153. package/esm/vite/gate-a-resolve.mjs +834 -0
  154. package/esm/vite/gate-a-resolve.mjs.map +1 -0
  155. package/esm/vite/gate-b-secrets.d.mts +57 -0
  156. package/esm/vite/gate-b-secrets.mjs +299 -0
  157. package/esm/vite/gate-b-secrets.mjs.map +1 -0
  158. package/esm/vite/gate-c-verify.d.mts +118 -0
  159. package/esm/vite/gate-c-verify.mjs +263 -0
  160. package/esm/vite/gate-c-verify.mjs.map +1 -0
  161. package/esm/vite/hydration-entries.d.mts +17 -0
  162. package/esm/vite/hydration-entries.mjs +45 -0
  163. package/esm/vite/hydration-entries.mjs.map +1 -0
  164. package/esm/vite/index.d.mts +126 -0
  165. package/esm/vite/index.mjs +146 -0
  166. package/esm/vite/index.mjs.map +1 -0
  167. package/esm/vite/page-registry-plugin.d.mts +43 -0
  168. package/esm/vite/page-registry-plugin.mjs +138 -0
  169. package/esm/vite/page-registry-plugin.mjs.map +1 -0
  170. package/esm/vite/projection.d.mts +31 -0
  171. package/esm/vite/projection.mjs +384 -0
  172. package/esm/vite/projection.mjs.map +1 -0
  173. package/llms-full.txt +1041 -0
  174. package/llms.txt +21 -0
  175. package/package.json +77 -0
  176. package/skills/add-web-to-an-app/SKILL.md +103 -0
  177. package/skills/create-a-page/SKILL.md +141 -0
  178. package/skills/load-page-data/SKILL.md +183 -0
  179. package/skills/navigate-on-the-client/SKILL.md +182 -0
  180. package/skills/serve-styles/SKILL.md +145 -0
  181. package/skills/use-layouts/SKILL.md +121 -0
  182. package/skills/write-the-root/SKILL.md +131 -0
@@ -0,0 +1,384 @@
1
+ import path from "node:path";
2
+ import { parse } from "@babel/parser";
3
+ import MagicString from "magic-string";
4
+
5
+ //#region ../web/src/vite/projection.ts
6
+ /**
7
+ * Projection — the compile-time AST transform that strips a page module's
8
+ * five server exports before the CLIENT graph forms.
9
+ *
10
+ * Removes `export const route/middleware/validation/loader/metadata = ...`
11
+ * (const-arrow form) and `export async function loader(...) {...}`
12
+ * (function-declaration form — a page declares these as separate named
13
+ * exports, not one fused object, so both forms are real), plus any import
14
+ * OR top-level declaration that becomes
15
+ * unreferenced ONLY as a result of that removal. The default export (the
16
+ * page component) and every other non-server-named export survive
17
+ * unconditionally, regardless of what they reference — classification is by
18
+ * FILE, not by what an export does with data (`c604f0bc` §9).
19
+ *
20
+ * ATTRIBUTION APPLIES TO LOCAL DECLARATIONS, NOT JUST IMPORTS. A page or
21
+ * layout hoists things to module scope for two ordinary reasons, and the
22
+ * reference graph tells them apart without guessing:
23
+ *
24
+ * `const publishCart: Middleware = ...` + `export const middleware =
25
+ * [publishCart]` (`v5/app/.../products/web/layout.tsx:30,43`) — the only
26
+ * reader is a server export being removed, so the binding goes with it.
27
+ *
28
+ * `const COMMON_TIMEZONES = [...]` read by the default export
29
+ * (`v5/app/.../account/settings.page.tsx:118,129`) — a surviving reader, so
30
+ * it survives.
31
+ *
32
+ * That is the SAME rule already applied to import bindings, extended to the
33
+ * other binding kind. It is deliberately not a widening of
34
+ * `SERVER_EXPORT_NAMES`, and not "accept what I don't recognise": a
35
+ * declaration is kept when something the client keeps reads it, dropped when
36
+ * nothing does AND dropping it cannot delete a side effect, and REFUSED
37
+ * otherwise — see `isDefinitionShapedInit`.
38
+ *
39
+ * This is NOT Gate A (`resolveId` path refusal), Gate B (inline secret
40
+ * reads) or Gate C (emitted-output verification) — those are separate,
41
+ * later slices. Projection runs first; the gates enforce after.
42
+ */
43
+ /**
44
+ * Exported so Gate C (`gate-c-verify.ts`) can re-derive "does the emitted
45
+ * bundle contain a server export as a top-level binding" from this exact set
46
+ * rather than hand-typing a second copy that could drift from projection's
47
+ * own list.
48
+ */
49
+ const SERVER_EXPORT_NAMES = new Set([
50
+ "route",
51
+ "middleware",
52
+ "validation",
53
+ "loader",
54
+ "metadata"
55
+ ]);
56
+ /**
57
+ * Recognized client-safe assets that always survive projection untouched,
58
+ * whether imported bare (`import "./x.css"`) or with specifiers
59
+ * (`import styles from "./x.module.css"`) — `c604f0bc` §3 names CSS
60
+ * explicitly; the rest of this list is the same "never guess, but a known
61
+ * asset extension is not ambiguous" reasoning extended to the other static
62
+ * asset kinds Vite treats as URL/asset imports, not executable code.
63
+ */
64
+ const ASSET_EXTENSION_RE = /\.(css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|otf)(\?.*)?$/i;
65
+ /**
66
+ * Top-level statement types that need no ambiguity check and are never
67
+ * touched by removal: import declarations are handled by their own
68
+ * survives/orphaned logic below, and every export (other than the 5 server
69
+ * names) plus type-only declarations survive unconditionally per
70
+ * `c604f0bc` §9 ("classify FILES, not the data they touch").
71
+ */
72
+ const ALWAYS_SAFE_STATEMENT_TYPES = new Set([
73
+ "ExportNamedDeclaration",
74
+ "ExportDefaultDeclaration",
75
+ "ExportAllDeclaration",
76
+ "TSInterfaceDeclaration",
77
+ "TSTypeAliasDeclaration",
78
+ "EmptyStatement"
79
+ ]);
80
+ /**
81
+ * Thrown when projection encounters an attribution-ambiguous top-level
82
+ * statement (`c604f0bc` §3: "the compiler must not guess"). Carries the
83
+ * file/statement/fix fields the plugin's `transform` hook formats into the
84
+ * build-failure message — never silently kept or silently dropped.
85
+ */
86
+ var ProjectionAmbiguityError = class extends Error {
87
+ file;
88
+ statement;
89
+ line;
90
+ explanation;
91
+ fix;
92
+ constructor(file, statement, line, explanation, fix) {
93
+ super([
94
+ `Projection refused to guess: an ambiguous top-level statement in the client build.`,
95
+ ``,
96
+ `File: ${file}:${line}`,
97
+ `Statement: ${statement}`,
98
+ `Cause: ${explanation}`,
99
+ `Fix: ${fix}`
100
+ ].join("\n"));
101
+ this.file = file;
102
+ this.statement = statement;
103
+ this.line = line;
104
+ this.explanation = explanation;
105
+ this.fix = fix;
106
+ this.name = "ProjectionAmbiguityError";
107
+ }
108
+ };
109
+ function hasSurvivingReader(local, survivingNames) {
110
+ for (const name of local.names) if (survivingNames.has(name)) return true;
111
+ return false;
112
+ }
113
+ function isKnownSafeAsset(source) {
114
+ return ASSET_EXTENSION_RE.test(source);
115
+ }
116
+ /**
117
+ * Matches `export const <name> = ...` only when the declaration has exactly
118
+ * one declarator — every server export in every fixture and v5/app page is
119
+ * written one-const-per-export (`product-details.page.tsx:15-18,42-69,71-74`);
120
+ * a multi-declarator `export const a = 1, b = 2` is left to the ambiguity
121
+ * path below rather than guessing which half is server-only.
122
+ */
123
+ function isServerExportDeclaration(stmt) {
124
+ if (stmt.type !== "ExportNamedDeclaration" || !stmt.declaration) return false;
125
+ const decl = stmt.declaration;
126
+ if (decl.type === "VariableDeclaration" && decl.declarations.length === 1) {
127
+ const id = decl.declarations[0].id;
128
+ return id?.type === "Identifier" && SERVER_EXPORT_NAMES.has(id.name);
129
+ }
130
+ if (decl.type === "FunctionDeclaration") return !!decl.id && SERVER_EXPORT_NAMES.has(decl.id.name);
131
+ return false;
132
+ }
133
+ /**
134
+ * Generic duck-typed AST walk (no `@babel/traverse` dependency — this
135
+ * package only needs `@babel/parser` + `@babel/types`-shaped nodes).
136
+ * Collects every `Identifier`/`JSXIdentifier` name reachable from `node`,
137
+ * used to decide whether an import binding still has a reader once the 5
138
+ * server exports are gone. Over-collecting (e.g. counting an object
139
+ * property key as a "use") only ever biases toward KEEPING an import, never
140
+ * toward dropping one that is still needed — the safe direction for a
141
+ * heuristic that must not guess in the removal direction.
142
+ */
143
+ function collectIdentifierNames(node, names) {
144
+ if (!node || typeof node !== "object") return;
145
+ if (Array.isArray(node)) {
146
+ for (const item of node) collectIdentifierNames(item, names);
147
+ return;
148
+ }
149
+ const record = node;
150
+ if (typeof record.type !== "string") return;
151
+ if (record.type === "Identifier" || record.type === "JSXIdentifier") names.add(record.name);
152
+ for (const key of Object.keys(record)) {
153
+ if (key === "type" || key === "start" || key === "end" || key === "loc" || key === "range") continue;
154
+ if (key === "leadingComments" || key === "trailingComments" || key === "innerComments" || key === "extra") continue;
155
+ collectIdentifierNames(record[key], names);
156
+ }
157
+ }
158
+ /**
159
+ * Top-level statements that BIND a name, and are therefore attributable by the
160
+ * reference graph rather than by guessing. Everything outside this set and
161
+ * `ALWAYS_SAFE_STATEMENT_TYPES` declares nothing — a bare `console.log("boot")`
162
+ * has no binding to trace a reader from, which is why it stays a hard refusal.
163
+ */
164
+ const DECLARATION_STATEMENT_TYPES = new Set([
165
+ "VariableDeclaration",
166
+ "FunctionDeclaration",
167
+ "ClassDeclaration"
168
+ ]);
169
+ function collectPatternNames(node, names) {
170
+ if (!node || typeof node !== "object") return;
171
+ switch (node.type) {
172
+ case "Identifier":
173
+ names.add(node.name);
174
+ return;
175
+ case "ObjectPattern":
176
+ for (const property of node.properties) collectPatternNames(property.type === "RestElement" ? property.argument : property.value, names);
177
+ return;
178
+ case "ArrayPattern":
179
+ for (const element of node.elements) collectPatternNames(element, names);
180
+ return;
181
+ case "AssignmentPattern":
182
+ collectPatternNames(node.left, names);
183
+ return;
184
+ case "RestElement":
185
+ collectPatternNames(node.argument, names);
186
+ return;
187
+ }
188
+ }
189
+ /** The names a top-level declaration introduces into module scope. */
190
+ function declaredNames(stmt) {
191
+ const names = /* @__PURE__ */ new Set();
192
+ if (stmt.type === "VariableDeclaration") for (const declarator of stmt.declarations) collectPatternNames(declarator.id, names);
193
+ else if (stmt.id?.type === "Identifier") names.add(stmt.id.name);
194
+ return names;
195
+ }
196
+ /**
197
+ * Whether EVALUATING this initializer can run anything.
198
+ *
199
+ * This is the whole safety argument for removing an unreferenced declaration.
200
+ * A function definition or a literal only creates a value, so dropping it can
201
+ * only drop a binding nothing reads. A call, a `new`, an `await`, a member
202
+ * access (a getter) — those can do work, and "was that work for the server or
203
+ * for the client?" is precisely the question projection must not answer by
204
+ * guessing (`c604f0bc` §3). `const _ = installPolyfill()` with no reader at all
205
+ * is the shape this predicate exists to refuse rather than silently delete.
206
+ *
207
+ * Conservative by construction: an unrecognized node type is NOT
208
+ * definition-shaped, so a new syntax form arrives as a refusal with a message,
209
+ * never as a silent removal.
210
+ */
211
+ function isDefinitionShapedInit(node) {
212
+ if (!node) return true;
213
+ switch (node.type) {
214
+ case "ArrowFunctionExpression":
215
+ case "FunctionExpression":
216
+ case "StringLiteral":
217
+ case "NumericLiteral":
218
+ case "BooleanLiteral":
219
+ case "NullLiteral":
220
+ case "BigIntLiteral":
221
+ case "RegExpLiteral":
222
+ case "Identifier": return true;
223
+ case "TemplateLiteral": return node.expressions.every((expression) => isDefinitionShapedInit(expression));
224
+ case "UnaryExpression": return node.operator !== "delete" && isDefinitionShapedInit(node.argument);
225
+ case "ArrayExpression": return node.elements.every((element) => element === null || element.type !== "SpreadElement" && isDefinitionShapedInit(element));
226
+ case "ObjectExpression": return node.properties.every((property) => property.type === "ObjectProperty" && !property.computed && isDefinitionShapedInit(property.value));
227
+ case "TSAsExpression":
228
+ case "TSSatisfiesExpression":
229
+ case "TSNonNullExpression":
230
+ case "TSTypeAssertion":
231
+ case "TSInstantiationExpression":
232
+ case "ParenthesizedExpression": return isDefinitionShapedInit(node.expression);
233
+ default: return false;
234
+ }
235
+ }
236
+ /**
237
+ * Statement-level form of the above. A `class` is excluded on purpose: static
238
+ * blocks, decorators and computed member keys all run at class-definition time,
239
+ * so a class is only ever kept or refused, never silently removed.
240
+ */
241
+ function isDefinitionShapedStatement(stmt) {
242
+ if (stmt.type === "FunctionDeclaration") return true;
243
+ if (stmt.type !== "VariableDeclaration") return false;
244
+ return stmt.declarations.every((declarator) => isDefinitionShapedInit(declarator.init));
245
+ }
246
+ function removeStatement(s, code, node) {
247
+ let end = node.end;
248
+ if (code[end] === "\r" && code[end + 1] === "\n") end += 2;
249
+ else if (code[end] === "\n") end += 1;
250
+ s.remove(node.start, end);
251
+ }
252
+ function statementSnippet(code, node) {
253
+ return code.slice(node.start, node.end).split("\n")[0].trim();
254
+ }
255
+ /**
256
+ * The transform itself: parse, remove the 5 server exports and every import
257
+ * orphaned only by that removal, fail closed on anything attribution-
258
+ * ambiguous. `filePath` is only used for error messages (`c604f0bc` §7 —
259
+ * fence errors must name the file).
260
+ */
261
+ function projectModule(code, filePath) {
262
+ const ast = parse(code, {
263
+ sourceType: "module",
264
+ plugins: ["typescript", "jsx"]
265
+ });
266
+ const s = new MagicString(code);
267
+ const body = ast.program.body;
268
+ const removedServerExports = [];
269
+ const importDeclarations = [];
270
+ const localDeclarations = [];
271
+ for (const stmt of body) {
272
+ if (stmt.type === "ImportDeclaration") {
273
+ importDeclarations.push(stmt);
274
+ continue;
275
+ }
276
+ if (isServerExportDeclaration(stmt)) {
277
+ removedServerExports.push(stmt);
278
+ continue;
279
+ }
280
+ if (ALWAYS_SAFE_STATEMENT_TYPES.has(stmt.type)) continue;
281
+ if (DECLARATION_STATEMENT_TYPES.has(stmt.type)) {
282
+ localDeclarations.push({
283
+ stmt,
284
+ names: declaredNames(stmt),
285
+ definitionShaped: isDefinitionShapedStatement(stmt),
286
+ removed: false
287
+ });
288
+ continue;
289
+ }
290
+ throw new ProjectionAmbiguityError(filePath, statementSnippet(code, stmt), stmt.loc.start.line, `top-level executable code that declares nothing — outside the 5 known server exports (route, middleware, validation, loader, metadata), and binding no name, so projection has no reader to attribute it by and can't tell whether it belongs to the server or the client`, `mark it with an explicit .server/.client file, or move it inside one of the 5 declared server exports (if server-only) or the default export/a component (if client-safe)`);
291
+ }
292
+ const removedLocals = /* @__PURE__ */ new Set();
293
+ /**
294
+ * Every name READ by something that survives projection. Imports are excluded
295
+ * so an import specifier never counts as a use of itself, and a local
296
+ * declaration does not count as a use of ITSELF either — otherwise a
297
+ * self-recursive server-only helper would pin its own binding alive forever.
298
+ *
299
+ * Over-collecting (an object property key, a shadowing parameter) only ever
300
+ * biases toward KEEPING, never toward dropping something still needed — the
301
+ * safe direction for a heuristic that must not guess in the removal
302
+ * direction.
303
+ */
304
+ function collectSurvivingNames() {
305
+ const names = /* @__PURE__ */ new Set();
306
+ for (const stmt of body) {
307
+ if (stmt.type === "ImportDeclaration") continue;
308
+ if (removedServerExports.includes(stmt) || removedLocals.has(stmt)) continue;
309
+ const own = /* @__PURE__ */ new Set();
310
+ collectIdentifierNames(stmt, own);
311
+ if (DECLARATION_STATEMENT_TYPES.has(stmt.type)) for (const name of declaredNames(stmt)) own.delete(name);
312
+ for (const name of own) names.add(name);
313
+ }
314
+ return names;
315
+ }
316
+ let survivingNames = collectSurvivingNames();
317
+ for (let changed = true; changed;) {
318
+ changed = false;
319
+ for (const local of localDeclarations) {
320
+ if (local.removed || !local.definitionShaped) continue;
321
+ if (hasSurvivingReader(local, survivingNames)) continue;
322
+ local.removed = true;
323
+ removedLocals.add(local.stmt);
324
+ changed = true;
325
+ }
326
+ if (changed) survivingNames = collectSurvivingNames();
327
+ }
328
+ for (const local of localDeclarations) {
329
+ if (local.removed || hasSurvivingReader(local, survivingNames)) continue;
330
+ throw new ProjectionAmbiguityError(filePath, statementSnippet(code, local.stmt), local.stmt.loc.start.line, `a top-level declaration read only by the server exports being removed, but whose initializer executes code rather than just defining a value — projection can't tell whether that work is server-only or a side effect the client depends on`, `move the initializer inside the server export that reads it, or split it into an explicit .server/.client file`);
331
+ }
332
+ for (const decl of importDeclarations) {
333
+ const source = decl.source.value;
334
+ if (isKnownSafeAsset(source)) continue;
335
+ if (decl.specifiers.length === 0) throw new ProjectionAmbiguityError(filePath, statementSnippet(code, decl), decl.loc.start.line, `a bare side-effect import with no recognized client-safe asset extension — projection can't tell if it belongs only to the server exports being removed or must ship to the client`, `mark it with an explicit .server/.client file, or move it inside one of the 5 declared server exports if it's server-only`);
336
+ if (!decl.specifiers.some((spec) => survivingNames.has(spec.local.name))) removeStatement(s, code, decl);
337
+ }
338
+ for (const stmt of removedServerExports) removeStatement(s, code, stmt);
339
+ for (const stmt of removedLocals) removeStatement(s, code, stmt);
340
+ return {
341
+ code: s.toString(),
342
+ map: s.generateMap({
343
+ hires: true,
344
+ source: filePath
345
+ })
346
+ };
347
+ }
348
+ function isProjectableFile(id) {
349
+ const base = path.basename(id.split("?")[0]);
350
+ if (/\.page\.tsx?$/.test(base)) return true;
351
+ if (base === "layout.tsx" || base === "layout.ts") return true;
352
+ if (/\.layout\.tsx?$/.test(base)) return true;
353
+ if (base === "root.tsx") return true;
354
+ return false;
355
+ }
356
+ /**
357
+ * The client-build Vite plugin. Scoped to `*.page.tsx`/`layout.tsx`/`root.tsx`
358
+ * and skipped entirely for the SSR build (`options.ssr`) — the server still
359
+ * needs `route`/`middleware`/`validation`/`loader`/`metadata` intact.
360
+ */
361
+ function projection() {
362
+ return {
363
+ name: "warlock:projection",
364
+ enforce: "pre",
365
+ transform(code, id, options) {
366
+ if (options?.ssr) return null;
367
+ if (!isProjectableFile(id)) return null;
368
+ try {
369
+ const { code: transformed, map } = projectModule(code, id);
370
+ return {
371
+ code: transformed,
372
+ map
373
+ };
374
+ } catch (error) {
375
+ if (error instanceof ProjectionAmbiguityError) this.error(error.message);
376
+ throw error;
377
+ }
378
+ }
379
+ };
380
+ }
381
+
382
+ //#endregion
383
+ export { ProjectionAmbiguityError, SERVER_EXPORT_NAMES, projection };
384
+ //# sourceMappingURL=projection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.mjs","names":[],"sources":["../../../../../../../web/src/vite/projection.ts"],"sourcesContent":["/**\n * Projection — the compile-time AST transform that strips a page module's\n * five server exports before the CLIENT graph forms.\n *\n * Removes `export const route/middleware/validation/loader/metadata = ...`\n * (const-arrow form) and `export async function loader(...) {...}`\n * (function-declaration form — a page declares these as separate named\n * exports, not one fused object, so both forms are real), plus any import\n * OR top-level declaration that becomes\n * unreferenced ONLY as a result of that removal. The default export (the\n * page component) and every other non-server-named export survive\n * unconditionally, regardless of what they reference — classification is by\n * FILE, not by what an export does with data (`c604f0bc` §9).\n *\n * ATTRIBUTION APPLIES TO LOCAL DECLARATIONS, NOT JUST IMPORTS. A page or\n * layout hoists things to module scope for two ordinary reasons, and the\n * reference graph tells them apart without guessing:\n *\n * `const publishCart: Middleware = ...` + `export const middleware =\n * [publishCart]` (`v5/app/.../products/web/layout.tsx:30,43`) — the only\n * reader is a server export being removed, so the binding goes with it.\n *\n * `const COMMON_TIMEZONES = [...]` read by the default export\n * (`v5/app/.../account/settings.page.tsx:118,129`) — a surviving reader, so\n * it survives.\n *\n * That is the SAME rule already applied to import bindings, extended to the\n * other binding kind. It is deliberately not a widening of\n * `SERVER_EXPORT_NAMES`, and not \"accept what I don't recognise\": a\n * declaration is kept when something the client keeps reads it, dropped when\n * nothing does AND dropping it cannot delete a side effect, and REFUSED\n * otherwise — see `isDefinitionShapedInit`.\n *\n * This is NOT Gate A (`resolveId` path refusal), Gate B (inline secret\n * reads) or Gate C (emitted-output verification) — those are separate,\n * later slices. Projection runs first; the gates enforce after.\n */\nimport { parse } from \"@babel/parser\";\nimport MagicString from \"magic-string\";\nimport path from \"node:path\";\nimport type { Plugin } from \"vite\";\n\n/**\n * Exported so Gate C (`gate-c-verify.ts`) can re-derive \"does the emitted\n * bundle contain a server export as a top-level binding\" from this exact set\n * rather than hand-typing a second copy that could drift from projection's\n * own list.\n */\nexport const SERVER_EXPORT_NAMES = new Set([\"route\", \"middleware\", \"validation\", \"loader\", \"metadata\"]);\n\n/**\n * Recognized client-safe assets that always survive projection untouched,\n * whether imported bare (`import \"./x.css\"`) or with specifiers\n * (`import styles from \"./x.module.css\"`) — `c604f0bc` §3 names CSS\n * explicitly; the rest of this list is the same \"never guess, but a known\n * asset extension is not ambiguous\" reasoning extended to the other static\n * asset kinds Vite treats as URL/asset imports, not executable code.\n */\nconst ASSET_EXTENSION_RE =\n /\\.(css|scss|sass|less|styl|stylus|svg|png|jpe?g|gif|webp|avif|ico|woff2?|ttf|eot|otf)(\\?.*)?$/i;\n\n/**\n * Top-level statement types that need no ambiguity check and are never\n * touched by removal: import declarations are handled by their own\n * survives/orphaned logic below, and every export (other than the 5 server\n * names) plus type-only declarations survive unconditionally per\n * `c604f0bc` §9 (\"classify FILES, not the data they touch\").\n */\nconst ALWAYS_SAFE_STATEMENT_TYPES = new Set([\n \"ExportNamedDeclaration\",\n \"ExportDefaultDeclaration\",\n \"ExportAllDeclaration\",\n \"TSInterfaceDeclaration\",\n \"TSTypeAliasDeclaration\",\n \"EmptyStatement\",\n]);\n\n/**\n * Thrown when projection encounters an attribution-ambiguous top-level\n * statement (`c604f0bc` §3: \"the compiler must not guess\"). Carries the\n * file/statement/fix fields the plugin's `transform` hook formats into the\n * build-failure message — never silently kept or silently dropped.\n */\nexport class ProjectionAmbiguityError extends Error {\n constructor(\n public readonly file: string,\n public readonly statement: string,\n public readonly line: number,\n public readonly explanation: string,\n public readonly fix: string,\n ) {\n super(\n [\n `Projection refused to guess: an ambiguous top-level statement in the client build.`,\n ``,\n `File: ${file}:${line}`,\n `Statement: ${statement}`,\n `Cause: ${explanation}`,\n `Fix: ${fix}`,\n ].join(\"\\n\"),\n );\n this.name = \"ProjectionAmbiguityError\";\n }\n}\n\nexport interface ProjectionResult {\n code: string;\n map: ReturnType<MagicString[\"generateMap\"]>;\n}\n\n/** A top-level `const`/`function`/`class` awaiting attribution by reference. */\ninterface LocalDeclaration {\n stmt: any;\n /** The module-scope names it binds. */\n names: Set<string>;\n /** Whether removing it could delete a side effect — see `isDefinitionShapedInit`. */\n definitionShaped: boolean;\n removed: boolean;\n}\n\nfunction hasSurvivingReader(local: LocalDeclaration, survivingNames: Set<string>): boolean {\n for (const name of local.names) {\n if (survivingNames.has(name)) return true;\n }\n return false;\n}\n\nfunction isKnownSafeAsset(source: string): boolean {\n return ASSET_EXTENSION_RE.test(source);\n}\n\n/**\n * Matches `export const <name> = ...` only when the declaration has exactly\n * one declarator — every server export in every fixture and v5/app page is\n * written one-const-per-export (`product-details.page.tsx:15-18,42-69,71-74`);\n * a multi-declarator `export const a = 1, b = 2` is left to the ambiguity\n * path below rather than guessing which half is server-only.\n */\nfunction isServerExportDeclaration(stmt: any): boolean {\n if (stmt.type !== \"ExportNamedDeclaration\" || !stmt.declaration) return false;\n const decl = stmt.declaration;\n if (decl.type === \"VariableDeclaration\" && decl.declarations.length === 1) {\n const id = decl.declarations[0].id;\n return id?.type === \"Identifier\" && SERVER_EXPORT_NAMES.has(id.name);\n }\n if (decl.type === \"FunctionDeclaration\") {\n return !!decl.id && SERVER_EXPORT_NAMES.has(decl.id.name);\n }\n return false;\n}\n\n/**\n * Generic duck-typed AST walk (no `@babel/traverse` dependency — this\n * package only needs `@babel/parser` + `@babel/types`-shaped nodes).\n * Collects every `Identifier`/`JSXIdentifier` name reachable from `node`,\n * used to decide whether an import binding still has a reader once the 5\n * server exports are gone. Over-collecting (e.g. counting an object\n * property key as a \"use\") only ever biases toward KEEPING an import, never\n * toward dropping one that is still needed — the safe direction for a\n * heuristic that must not guess in the removal direction.\n */\nfunction collectIdentifierNames(node: unknown, names: Set<string>): void {\n if (!node || typeof node !== \"object\") return;\n if (Array.isArray(node)) {\n for (const item of node) collectIdentifierNames(item, names);\n return;\n }\n const record = node as Record<string, unknown>;\n if (typeof record.type !== \"string\") return;\n if (record.type === \"Identifier\" || record.type === \"JSXIdentifier\") {\n names.add((record as any).name);\n }\n for (const key of Object.keys(record)) {\n if (key === \"type\" || key === \"start\" || key === \"end\" || key === \"loc\" || key === \"range\") continue;\n if (key === \"leadingComments\" || key === \"trailingComments\" || key === \"innerComments\" || key === \"extra\") {\n continue;\n }\n collectIdentifierNames(record[key], names);\n }\n}\n\n/**\n * Top-level statements that BIND a name, and are therefore attributable by the\n * reference graph rather than by guessing. Everything outside this set and\n * `ALWAYS_SAFE_STATEMENT_TYPES` declares nothing — a bare `console.log(\"boot\")`\n * has no binding to trace a reader from, which is why it stays a hard refusal.\n */\nconst DECLARATION_STATEMENT_TYPES = new Set([\n \"VariableDeclaration\",\n \"FunctionDeclaration\",\n \"ClassDeclaration\",\n]);\n\nfunction collectPatternNames(node: any, names: Set<string>): void {\n if (!node || typeof node !== \"object\") return;\n switch (node.type) {\n case \"Identifier\":\n names.add(node.name);\n return;\n case \"ObjectPattern\":\n for (const property of node.properties) {\n collectPatternNames(property.type === \"RestElement\" ? property.argument : property.value, names);\n }\n return;\n case \"ArrayPattern\":\n for (const element of node.elements) collectPatternNames(element, names);\n return;\n case \"AssignmentPattern\":\n collectPatternNames(node.left, names);\n return;\n case \"RestElement\":\n collectPatternNames(node.argument, names);\n return;\n }\n}\n\n/** The names a top-level declaration introduces into module scope. */\nfunction declaredNames(stmt: any): Set<string> {\n const names = new Set<string>();\n if (stmt.type === \"VariableDeclaration\") {\n for (const declarator of stmt.declarations) collectPatternNames(declarator.id, names);\n } else if (stmt.id?.type === \"Identifier\") {\n names.add(stmt.id.name);\n }\n return names;\n}\n\n/**\n * Whether EVALUATING this initializer can run anything.\n *\n * This is the whole safety argument for removing an unreferenced declaration.\n * A function definition or a literal only creates a value, so dropping it can\n * only drop a binding nothing reads. A call, a `new`, an `await`, a member\n * access (a getter) — those can do work, and \"was that work for the server or\n * for the client?\" is precisely the question projection must not answer by\n * guessing (`c604f0bc` §3). `const _ = installPolyfill()` with no reader at all\n * is the shape this predicate exists to refuse rather than silently delete.\n *\n * Conservative by construction: an unrecognized node type is NOT\n * definition-shaped, so a new syntax form arrives as a refusal with a message,\n * never as a silent removal.\n */\nfunction isDefinitionShapedInit(node: any): boolean {\n // `let x;` — a bare binding with nothing to evaluate.\n if (!node) return true;\n\n switch (node.type) {\n case \"ArrowFunctionExpression\":\n case \"FunctionExpression\":\n case \"StringLiteral\":\n case \"NumericLiteral\":\n case \"BooleanLiteral\":\n case \"NullLiteral\":\n case \"BigIntLiteral\":\n case \"RegExpLiteral\":\n // A bare identifier read is a binding lookup, not a computation.\n case \"Identifier\":\n return true;\n case \"TemplateLiteral\":\n return node.expressions.every((expression: any) => isDefinitionShapedInit(expression));\n case \"UnaryExpression\":\n return node.operator !== \"delete\" && isDefinitionShapedInit(node.argument);\n case \"ArrayExpression\":\n return node.elements.every(\n (element: any) =>\n element === null || (element.type !== \"SpreadElement\" && isDefinitionShapedInit(element)),\n );\n case \"ObjectExpression\":\n // Spread and computed keys both evaluate arbitrary expressions; a getter\n // or setter defines a body that runs on ACCESS, which the surviving half\n // could still trigger — none of them are definitions.\n return node.properties.every(\n (property: any) =>\n property.type === \"ObjectProperty\" &&\n !property.computed &&\n isDefinitionShapedInit(property.value),\n );\n // TS-only wrappers erase at compile time; look through them.\n case \"TSAsExpression\":\n case \"TSSatisfiesExpression\":\n case \"TSNonNullExpression\":\n case \"TSTypeAssertion\":\n case \"TSInstantiationExpression\":\n case \"ParenthesizedExpression\":\n return isDefinitionShapedInit(node.expression);\n default:\n return false;\n }\n}\n\n/**\n * Statement-level form of the above. A `class` is excluded on purpose: static\n * blocks, decorators and computed member keys all run at class-definition time,\n * so a class is only ever kept or refused, never silently removed.\n */\nfunction isDefinitionShapedStatement(stmt: any): boolean {\n if (stmt.type === \"FunctionDeclaration\") return true;\n if (stmt.type !== \"VariableDeclaration\") return false;\n return stmt.declarations.every((declarator: any) => isDefinitionShapedInit(declarator.init));\n}\n\nfunction removeStatement(s: MagicString, code: string, node: any): void {\n let end = node.end as number;\n // Swallow one trailing newline so a removed statement doesn't leave a\n // blank line behind — cosmetic only, the output's correctness never\n // depends on it.\n if (code[end] === \"\\r\" && code[end + 1] === \"\\n\") end += 2;\n else if (code[end] === \"\\n\") end += 1;\n s.remove(node.start as number, end);\n}\n\nfunction statementSnippet(code: string, node: any): string {\n return code.slice(node.start as number, node.end as number).split(\"\\n\")[0].trim();\n}\n\n/**\n * The transform itself: parse, remove the 5 server exports and every import\n * orphaned only by that removal, fail closed on anything attribution-\n * ambiguous. `filePath` is only used for error messages (`c604f0bc` §7 —\n * fence errors must name the file).\n */\nexport function projectModule(code: string, filePath: string): ProjectionResult {\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"jsx\"],\n });\n\n const s = new MagicString(code);\n const body = ast.program.body as any[];\n\n const removedServerExports: any[] = [];\n const importDeclarations: any[] = [];\n const localDeclarations: LocalDeclaration[] = [];\n\n for (const stmt of body) {\n if (stmt.type === \"ImportDeclaration\") {\n importDeclarations.push(stmt);\n continue;\n }\n if (isServerExportDeclaration(stmt)) {\n removedServerExports.push(stmt);\n continue;\n }\n if (ALWAYS_SAFE_STATEMENT_TYPES.has(stmt.type)) continue;\n if (DECLARATION_STATEMENT_TYPES.has(stmt.type)) {\n // Attributable by the reference graph — decided below, once it is known\n // which statements survive. NOT accepted here.\n localDeclarations.push({\n stmt,\n names: declaredNames(stmt),\n definitionShaped: isDefinitionShapedStatement(stmt),\n removed: false,\n });\n continue;\n }\n\n // Attribution-IMPOSSIBLE: not an import, not one of the 5 known server\n // exports, not another export, not a type-only declaration, and it binds\n // no name for a reader to point at. Fail closed rather than guess which\n // side of the fence it belongs on (`c604f0bc` §3).\n throw new ProjectionAmbiguityError(\n filePath,\n statementSnippet(code, stmt),\n stmt.loc.start.line,\n `top-level executable code that declares nothing — outside the 5 known server exports (route, middleware, validation, loader, metadata), and binding no name, so projection has no reader to attribute it by and can't tell whether it belongs to the server or the client`,\n `mark it with an explicit .server/.client file, or move it inside one of the 5 declared server exports (if server-only) or the default export/a component (if client-safe)`,\n );\n }\n\n const removedLocals = new Set<any>();\n\n /**\n * Every name READ by something that survives projection. Imports are excluded\n * so an import specifier never counts as a use of itself, and a local\n * declaration does not count as a use of ITSELF either — otherwise a\n * self-recursive server-only helper would pin its own binding alive forever.\n *\n * Over-collecting (an object property key, a shadowing parameter) only ever\n * biases toward KEEPING, never toward dropping something still needed — the\n * safe direction for a heuristic that must not guess in the removal\n * direction.\n */\n function collectSurvivingNames(): Set<string> {\n const names = new Set<string>();\n for (const stmt of body) {\n if (stmt.type === \"ImportDeclaration\") continue;\n if (removedServerExports.includes(stmt) || removedLocals.has(stmt)) continue;\n const own = new Set<string>();\n collectIdentifierNames(stmt, own);\n if (DECLARATION_STATEMENT_TYPES.has(stmt.type)) {\n for (const name of declaredNames(stmt)) own.delete(name);\n }\n for (const name of own) names.add(name);\n }\n return names;\n }\n\n // Fixpoint, not one pass: a server-only helper can be reached only through\n // ANOTHER server-only helper, and dropping the first orphans the second.\n let survivingNames = collectSurvivingNames();\n for (let changed = true; changed; ) {\n changed = false;\n for (const local of localDeclarations) {\n if (local.removed || !local.definitionShaped) continue;\n if (hasSurvivingReader(local, survivingNames)) continue;\n local.removed = true;\n removedLocals.add(local.stmt);\n changed = true;\n }\n if (changed) survivingNames = collectSurvivingNames();\n }\n\n for (const local of localDeclarations) {\n if (local.removed || hasSurvivingReader(local, survivingNames)) continue;\n\n // Nothing the client keeps reads it, so it belongs to the server exports\n // being removed — but its initializer can RUN, and a side effect is not\n // attributable by the reference graph. Keeping it ships server work to the\n // browser; dropping it deletes a side effect the client may depend on.\n // Refuse rather than pick one (`c604f0bc` §3).\n throw new ProjectionAmbiguityError(\n filePath,\n statementSnippet(code, local.stmt),\n local.stmt.loc.start.line,\n `a top-level declaration read only by the server exports being removed, but whose initializer executes code rather than just defining a value — projection can't tell whether that work is server-only or a side effect the client depends on`,\n `move the initializer inside the server export that reads it, or split it into an explicit .server/.client file`,\n );\n }\n\n for (const decl of importDeclarations) {\n const source = decl.source.value as string;\n if (isKnownSafeAsset(source)) continue; // always survives, no orphan check\n\n if (decl.specifiers.length === 0) {\n // A bare side-effect import that isn't a recognized asset extension is\n // just as attribution-ambiguous as an executable statement — could be\n // a server-only side effect or something the client genuinely needs.\n throw new ProjectionAmbiguityError(\n filePath,\n statementSnippet(code, decl),\n decl.loc.start.line,\n `a bare side-effect import with no recognized client-safe asset extension — projection can't tell if it belongs only to the server exports being removed or must ship to the client`,\n `mark it with an explicit .server/.client file, or move it inside one of the 5 declared server exports if it's server-only`,\n );\n }\n\n const isUsed = decl.specifiers.some((spec: any) => survivingNames.has(spec.local.name));\n if (!isUsed) removeStatement(s, code, decl);\n }\n\n for (const stmt of removedServerExports) {\n removeStatement(s, code, stmt);\n }\n\n for (const stmt of removedLocals) {\n removeStatement(s, code, stmt);\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: true, source: filePath }),\n };\n}\n\nfunction isProjectableFile(id: string): boolean {\n const base = path.basename(id.split(\"?\")[0]);\n if (/\\.page\\.tsx?$/.test(base)) return true;\n if (base === \"layout.tsx\" || base === \"layout.ts\") return true;\n // NAMED layouts — `dashboard.layout.tsx` and friends — are subjects too.\n //\n // Only the exact name `layout.tsx` is POSITIONAL (discovered by its folder).\n // A named layout is addressed by import instead, which is a documented part of\n // the contract: `v5/app/src/web/layouts/dashboard.layout.tsx` says so in its\n // own header, and modules opt in with two re-export lines.\n //\n // Projection did not recognise them, and the consequence was not cosmetic.\n // `dashboard.layout.tsx` calls `navService.forUser()` INSIDE its `loader` —\n // exactly where server work belongs. But because the file was not a subject,\n // the loader was never stripped, so its `navService` import survived into the\n // client graph and dragged auth, the user model and three Node builtins with\n // it. The app was right and the subject test was wrong.\n //\n // Matching `*.layout.tsx` rather than a list of known layout names is\n // deliberate: an enumerated list is the shape that has produced every other\n // boundary defect here (canon `eb0c5ee8`).\n if (/\\.layout\\.tsx?$/.test(base)) return true;\n if (base === \"root.tsx\") return true;\n return false;\n}\n\n/**\n * The client-build Vite plugin. Scoped to `*.page.tsx`/`layout.tsx`/`root.tsx`\n * and skipped entirely for the SSR build (`options.ssr`) — the server still\n * needs `route`/`middleware`/`validation`/`loader`/`metadata` intact.\n */\nexport function projection(): Plugin {\n return {\n name: \"warlock:projection\",\n enforce: \"pre\",\n transform(code, id, options) {\n if (options?.ssr) return null;\n if (!isProjectableFile(id)) return null;\n\n try {\n const { code: transformed, map } = projectModule(code, id);\n return { code: transformed, map };\n } catch (error) {\n if (error instanceof ProjectionAmbiguityError) {\n this.error(error.message);\n }\n throw error;\n }\n },\n };\n}\n"],"mappings":"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"}