@warlock.js/web 5.4.0 → 5.6.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 (139) hide show
  1. package/CHANGELOG.md +33 -5
  2. package/esm/build/contribution.d.mts +2 -2
  3. package/esm/build/contribution.mjs.map +1 -1
  4. package/esm/build/discover-pages.mjs +127 -205
  5. package/esm/build/discover-pages.mjs.map +1 -1
  6. package/esm/build/generate-pages-barrel.mjs +5 -3
  7. package/esm/build/generate-pages-barrel.mjs.map +1 -1
  8. package/esm/build/public-files.mjs +29 -1
  9. package/esm/build/public-files.mjs.map +1 -1
  10. package/esm/build/read-metadata-keys.mjs +225 -0
  11. package/esm/build/read-metadata-keys.mjs.map +1 -0
  12. package/esm/build/read-route-exports.mjs.map +1 -1
  13. package/esm/client/build-hydrated-tree.mjs +1 -1
  14. package/esm/client/build-hydrated-tree.mjs.map +1 -1
  15. package/esm/client/navigation/document-metadata.mjs +108 -0
  16. package/esm/client/navigation/document-metadata.mjs.map +1 -0
  17. package/esm/client/navigation/navigation-root.mjs +23 -103
  18. package/esm/client/navigation/navigation-root.mjs.map +1 -1
  19. package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -1
  20. package/esm/client/navigation/use-query-string.d.mts +35 -0
  21. package/esm/client/navigation/use-query-string.mjs +68 -0
  22. package/esm/client/navigation/use-query-string.mjs.map +1 -0
  23. package/esm/client/runtime/index.d.mts +1 -1
  24. package/esm/client/runtime/index.mjs +1 -1
  25. package/esm/client/runtime/manifest.mjs.map +1 -1
  26. package/esm/client/runtime/matcher.mjs.map +1 -1
  27. package/esm/client/runtime/types.d.mts +1 -1
  28. package/esm/components/link.mjs.map +1 -1
  29. package/esm/core/src/router/normalize-route-path.mjs +33 -0
  30. package/esm/core/src/router/normalize-route-path.mjs.map +1 -0
  31. package/esm/{hydration → entry}/index.mjs +1 -1
  32. package/esm/entry/index.mjs.map +1 -0
  33. package/esm/hydration-payload.mjs.map +1 -1
  34. package/esm/index.d.mts +3 -2
  35. package/esm/index.mjs +3 -2
  36. package/esm/loaders.d.mts +10 -3
  37. package/esm/localization.mjs.map +1 -1
  38. package/esm/metadata.d.mts +4 -4
  39. package/esm/metadata.mjs.map +1 -1
  40. package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs +33 -0
  41. package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs.map +1 -0
  42. package/esm/{runtime/register-modules.d.mts → register-modules.d.mts} +1 -1
  43. package/esm/{runtime/register-modules.mjs → register-modules.mjs} +1 -1
  44. package/esm/register-modules.mjs.map +1 -0
  45. package/esm/route.d.mts +35 -2
  46. package/esm/routing/duplicate-route-path.mjs +22 -0
  47. package/esm/routing/duplicate-route-path.mjs.map +1 -0
  48. package/esm/routing/layout-level.mjs +50 -0
  49. package/esm/routing/layout-level.mjs.map +1 -0
  50. package/esm/routing/layout-policy.mjs.map +1 -1
  51. package/esm/routing/query-string.d.mts +23 -1
  52. package/esm/routing/query-string.mjs +23 -1
  53. package/esm/routing/query-string.mjs.map +1 -1
  54. package/esm/routing/route-identity.mjs +27 -2
  55. package/esm/routing/route-identity.mjs.map +1 -1
  56. package/esm/routing/route-table.mjs.map +1 -1
  57. package/esm/routing/router-events.d.mts +5 -4
  58. package/esm/routing/router-events.mjs.map +1 -1
  59. package/esm/server/create-page-route-handler.d.mts +2 -2
  60. package/esm/server/create-page-route-handler.mjs +6 -6
  61. package/esm/server/create-page-route-handler.mjs.map +1 -1
  62. package/esm/server/{dev-server.mjs → dev-error-transport.mjs} +2 -2
  63. package/esm/server/dev-error-transport.mjs.map +1 -0
  64. package/esm/server/execute-page-request.mjs +87 -23
  65. package/esm/server/execute-page-request.mjs.map +1 -1
  66. package/esm/server/execute-page-request.types.d.mts +10 -1
  67. package/esm/server/fold-layout-loaders.mjs +36 -0
  68. package/esm/server/fold-layout-loaders.mjs.map +1 -0
  69. package/esm/server/hydration-client-url.mjs +2 -2
  70. package/esm/server/hydration-client-url.mjs.map +1 -1
  71. package/esm/server/index.d.mts +2 -2
  72. package/esm/server/index.mjs +2 -2
  73. package/esm/server/install-page-routes-from-manifest.d.mts +1 -1
  74. package/esm/server/install-page-routes-from-manifest.mjs +43 -40
  75. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
  76. package/esm/server/install-page-routes.d.mts +22 -2
  77. package/esm/server/install-page-routes.mjs +108 -42
  78. package/esm/server/install-page-routes.mjs.map +1 -1
  79. package/esm/server/install-production-page-routes.mjs.map +1 -1
  80. package/esm/server/match-page-route.mjs.map +1 -1
  81. package/esm/server/page-file-change.mjs.map +1 -1
  82. package/esm/server/page-route-reload.mjs +4 -3
  83. package/esm/server/page-route-reload.mjs.map +1 -1
  84. package/esm/server/register-production-public-files.mjs +25 -1
  85. package/esm/server/register-production-public-files.mjs.map +1 -1
  86. package/esm/server/render-page.d.mts +11 -48
  87. package/esm/server/render-page.mjs +13 -55
  88. package/esm/server/render-page.mjs.map +1 -1
  89. package/esm/server/resolve-page-metadata.mjs.map +1 -1
  90. package/esm/server/resolve-route-validation-input.mjs +11 -0
  91. package/esm/server/resolve-route-validation-input.mjs.map +1 -0
  92. package/esm/server/route-validation-error.mjs +32 -0
  93. package/esm/server/route-validation-error.mjs.map +1 -0
  94. package/esm/server/settle-page-response.mjs +5 -3
  95. package/esm/server/settle-page-response.mjs.map +1 -1
  96. package/esm/server/unregistered-pages.mjs +2 -1
  97. package/esm/server/unregistered-pages.mjs.map +1 -1
  98. package/esm/server/web-connector-factory.mjs +1 -1
  99. package/esm/server/web-connector-factory.mjs.map +1 -1
  100. package/esm/server/web-connector.mjs +24 -321
  101. package/esm/server/web-connector.mjs.map +1 -1
  102. package/esm/shared/module-key.mjs +15 -0
  103. package/esm/shared/module-key.mjs.map +1 -0
  104. package/esm/shared/to-posix.mjs +13 -0
  105. package/esm/shared/to-posix.mjs.map +1 -0
  106. package/esm/shared.mjs +24 -0
  107. package/esm/shared.mjs.map +1 -1
  108. package/esm/vite/build-client.mjs.map +1 -1
  109. package/esm/vite/dev-server-config.mjs +240 -0
  110. package/esm/vite/dev-server-config.mjs.map +1 -0
  111. package/esm/vite/gate-a-resolve.d.mts +5 -4
  112. package/esm/vite/gate-a-resolve.mjs +31 -70
  113. package/esm/vite/gate-a-resolve.mjs.map +1 -1
  114. package/esm/vite/gate-b-secrets.mjs.map +1 -1
  115. package/esm/vite/gate-c-verify.d.mts +1 -1
  116. package/esm/vite/gate-c-verify.mjs +1 -1
  117. package/esm/vite/gate-c-verify.mjs.map +1 -1
  118. package/esm/vite/hydration-entries.mjs +5 -7
  119. package/esm/vite/hydration-entries.mjs.map +1 -1
  120. package/esm/vite/index.d.mts +1 -1
  121. package/esm/vite/index.mjs +4 -110
  122. package/esm/vite/index.mjs.map +1 -1
  123. package/esm/vite/page-registry-plugin.mjs +9 -26
  124. package/esm/vite/page-registry-plugin.mjs.map +1 -1
  125. package/esm/vite/projection.mjs.map +1 -1
  126. package/esm/vite/react-refresh-preamble.mjs +90 -0
  127. package/esm/vite/react-refresh-preamble.mjs.map +1 -0
  128. package/esm/vite/ssr-client-view.mjs +135 -0
  129. package/esm/vite/ssr-client-view.mjs.map +1 -0
  130. package/llms-full.txt +564 -485
  131. package/llms.txt +2 -2
  132. package/package.json +6 -6
  133. package/skills/add-web-to-an-app/SKILL.md +3 -3
  134. package/skills/create-a-page/SKILL.md +348 -295
  135. package/skills/navigate-on-the-client/SKILL.md +212 -189
  136. package/esm/hydration/index.mjs.map +0 -1
  137. package/esm/runtime/register-modules.mjs.map +0 -1
  138. package/esm/server/dev-server.mjs.map +0 -1
  139. /package/esm/{hydration → entry}/index.d.mts +0 -0
@@ -0,0 +1,240 @@
1
+ import { warlockClientBoundary } from "./index.mjs";
2
+ import { appConventionAliases } from "./app-convention-aliases.mjs";
3
+ import { resolveReactFastRefreshPlugins } from "./react-refresh-preamble.mjs";
4
+ import fs from "node:fs";
5
+
6
+ //#region ../web/src/vite/dev-server-config.ts
7
+ /**
8
+ * Third-party packages core reaches through `await import(...)` and that must
9
+ * therefore never enter Vite's SSR transform graph.
10
+ *
11
+ * Left un-externalized, Vite's SSR module runner tries to resolve them anyway
12
+ * and jams: the failure is NOT a missing-module error but a `transport invoke
13
+ * timed out` on whatever unrelated module happened to be in flight. Derived in
14
+ * one pass from `peerDependenciesMeta.optional` across every workspace package
15
+ * reachable from `core/src/index.ts`, and carried over verbatim from
16
+ * `dev-error-transport.ts`'s own list. Only THIRD-PARTY peers belong here — every
17
+ * `@warlock.js/*` sibling must stay in Vite's graph.
18
+ *
19
+ * This list is core's peer list, not web's; publishing it from core instead
20
+ * of duplicating it here is still outstanding.
21
+ */
22
+ const CORE_OPTIONAL_PEERS = [
23
+ "nodemailer",
24
+ "@aws-sdk/client-sesv2",
25
+ "@react-email/render",
26
+ "redis",
27
+ "pg",
28
+ "mongodb",
29
+ "@sentry/node",
30
+ "sharp",
31
+ "socket.io",
32
+ "@aws-sdk/client-s3",
33
+ "@aws-sdk/lib-storage",
34
+ "@aws-sdk/s3-request-presigner",
35
+ "amqplib",
36
+ "langfuse",
37
+ "openai",
38
+ "pdf-parse"
39
+ ];
40
+ /**
41
+ * `web`'s OWN optional peers, kept separate from {@link CORE_OPTIONAL_PEERS}
42
+ * because that list is core's and is documented as such.
43
+ *
44
+ * Same rule, same reason: anything this package reaches through
45
+ * `await import(...)` must be external to every bundler and SSR pipeline, or
46
+ * some pipeline will try to resolve it in an install that legitimately does not
47
+ * have it. `vite` itself is the precedent — `createViteServer` has always
48
+ * loaded it this way — and `@vitejs/plugin-react` is loaded from the same
49
+ * function, for the same dev-only reason, so it belongs in the same set.
50
+ */
51
+ const WEB_OPTIONAL_PEERS = ["vite", "@vitejs/plugin-react"];
52
+ /**
53
+ * A directory, plus the path the filesystem really stores it at when the two
54
+ * differ. Both forms belong in `server.fs.allow` — see the `fs` block in
55
+ * {@link createWebConnectorViteConfig} for why one of them is never enough.
56
+ *
57
+ * A missing directory is not this function's problem to report: the roots it is
58
+ * handed are already proven (`resolveWebPackageRoot`) or are the app's own cwd,
59
+ * and an allow-list entry that points nowhere simply matches nothing.
60
+ */
61
+ function withRealPath(directory) {
62
+ try {
63
+ const realPath = fs.realpathSync(directory);
64
+ return realPath === directory ? [directory] : [directory, realPath];
65
+ } catch {
66
+ return [directory];
67
+ }
68
+ }
69
+ /**
70
+ * `WebConnector`'s Vite dev-server config — Vite in middleware mode,
71
+ * `appType: "custom"` — Warlock owns the response shape and Vite never fronts
72
+ * the server.
73
+ *
74
+ * `server.hmr.server` is handed the RAW node server (`fastify.server`), so the
75
+ * HMR websocket shares the one port the app already listens on. No `hmr.port`
76
+ * and no `clientPort`: Vite's HMR path stays the default `"/"`, socket.io
77
+ * stays on `"/socket.io"` (`core/src/connectors/socket-connector.ts:90`), and
78
+ * the two `upgrade` listeners coexist because each is a selective filter that
79
+ * leaves a non-matching socket alone — verified empirically in both attachment
80
+ * orders.
81
+ */
82
+ async function createWebConnectorViteConfig(options) {
83
+ return {
84
+ root: options.appRoot,
85
+ appType: "custom",
86
+ plugins: [
87
+ ...options.leadingPlugins,
88
+ ...warlockClientBoundary({
89
+ appRoot: options.appRoot,
90
+ beforePageHotUpdate: ({ file }) => options.handlePageHotUpdate(file)
91
+ }),
92
+ ...await resolveReactFastRefreshPlugins({ webRoot: options.webRoot }),
93
+ ...options.plugins ?? []
94
+ ],
95
+ server: {
96
+ middlewareMode: true,
97
+ hmr: { server: options.hmrServer },
98
+ fs: { allow: [...new Set([
99
+ ...withRealPath(options.workspaceRoot),
100
+ ...withRealPath(options.appRoot),
101
+ ...withRealPath(options.webRoot)
102
+ ])] }
103
+ },
104
+ esbuild: {
105
+ target: "es2022",
106
+ jsx: "automatic"
107
+ },
108
+ /**
109
+ * REACT MUST BE PRE-BUNDLED, and naming it here is the only thing that
110
+ * makes that happen for the framework's own client graph.
111
+ *
112
+ * `react-dom/client` is CommonJS. A browser cannot import a named export
113
+ * from it, so Vite's dep optimizer normally rewrites it into an ESM shim
114
+ * under `/node_modules/.vite/deps/`. Whether that rewrite happens is
115
+ * decided in `tryNodeResolve`, and one of the conditions that SKIPS it is
116
+ * `importer && isInNodeModules(importer)`
117
+ * (`vite/dist/node/chunks/config.js:32822`). Vite's reasoning is sound in
118
+ * general — a dependency's own internal imports are the optimizer's job,
119
+ * not the resolver's — but it is exactly wrong here:
120
+ *
121
+ * `<Scripts />` points the browser at the hydration entry, and in an
122
+ * INSTALLED app that entry is
123
+ * `<app>/node_modules/@warlock.js/web/esm/entry/index.mjs`.
124
+ * Every module it reaches is therefore inside `node_modules`, so every
125
+ * bare import it makes takes the skip branch and is served as the raw
126
+ * file with a `?v=<browserHash>` cache key bolted on.
127
+ *
128
+ * For `esm/**.mjs` that is harmless — they are already ESM. For
129
+ * `react-dom/client` it is fatal, and it is the whole defect: the browser
130
+ * receives `"use strict"; function checkDCE()…` and refuses the module
131
+ * with
132
+ *
133
+ * SyntaxError: The requested module '/node_modules/react-dom/client.js?v=…'
134
+ * does not provide an export named 'hydrateRoot'
135
+ *
136
+ * Nothing in the client runtime then runs at all: no hydration, so no
137
+ * `useState`, no Fast Refresh, and `<Link>` degrades to a full document
138
+ * load because the navigation listener was never attached. Measured on a
139
+ * published 5.0.2 install (fresh app, no symlinks): all four symptoms
140
+ * before this block, none after.
141
+ *
142
+ * `include` is the fix rather than `entries` because it does not depend
143
+ * on the scanner reaching the entry: an included id is pre-bundled at
144
+ * server start and `tryOptimizedResolve` matches it by NAME
145
+ * (`config.js:32633`) before `tryNodeResolve` — and that lookup has no
146
+ * importer condition, so a node_modules importer resolves to the shim
147
+ * like anyone else. Pointing `optimizeDeps.entries` at the hydration
148
+ * entry would not work: the scanner classifies a resolution inside
149
+ * `node_modules` as a dependency to externalize rather than a source to
150
+ * walk.
151
+ *
152
+ * All four names are listed even though `react` and `react/jsx-runtime`
153
+ * usually get discovered anyway — they are discovered only because the
154
+ * APP's own pages import them, which is a fact about the app and not
155
+ * something the framework may rely on. `react-dom/client` is imported by
156
+ * `client/hydrate-page.tsx` and by nothing a normal app writes, which is
157
+ * why it was the one that broke.
158
+ *
159
+ * This is INVISIBLE from the monorepo checkout: there the hydration entry
160
+ * resolves to `web/src/entry/index.ts`, a path with no `node_modules`
161
+ * segment, so the skip branch never fires and React optimizes normally.
162
+ * Canon: nothing measured inside the checkout is evidence about a
163
+ * published install.
164
+ */
165
+ optimizeDeps: {
166
+ include: [
167
+ "react",
168
+ "react-dom",
169
+ "react-dom/client",
170
+ "react/jsx-runtime"
171
+ ],
172
+ /**
173
+ * The SECOND instance problem, and the reason this is `exclude` and not
174
+ * another `include`.
175
+ *
176
+ * An app page's `import { Link } from "@warlock.js/web"` has an importer
177
+ * OUTSIDE `node_modules`, so it takes the opposite branch above and the
178
+ * whole package is pre-bundled into
179
+ * `/node_modules/.vite/deps/@warlock__js_web.js`. The hydration entry
180
+ * cannot reach that bundle — it is loaded by absolute path through
181
+ * `/@fs/` and its imports are relative, so they resolve to the raw
182
+ * `esm/*.mjs` files. The browser then holds TWO copies of every client
183
+ * module in this package: the app renders `<Link>` from the bundled one
184
+ * while the hydration entry publishes routes and mounts the document
185
+ * context on the raw one. Module-level state does not cross that line.
186
+ *
187
+ * `routing/route-table` survives it by accident — it keeps its table in
188
+ * a `Symbol.for` slot on `globalThis`, which is realm-wide — but
189
+ * `components/document-context` is a React context OBJECT, and two
190
+ * `createContext()` calls are two different contexts no provider can
191
+ * bridge.
192
+ *
193
+ * Excluding the package makes the app's import resolve to
194
+ * `esm/index.mjs` with the same `?v=` key the hydration entry's imports
195
+ * carry, so there is one instance again. It costs the pre-bundle (a
196
+ * handful of extra dev requests for files that are already valid ESM)
197
+ * and nothing else — the grep across every client-reachable module in
198
+ * this package finds exactly three bare imports, all of them React, all
199
+ * of them covered by `include` above.
200
+ */
201
+ exclude: ["@warlock.js/web"]
202
+ },
203
+ ssr: {
204
+ external: [
205
+ ...CORE_OPTIONAL_PEERS,
206
+ ...WEB_OPTIONAL_PEERS,
207
+ ...options.ssrExternal ?? []
208
+ ],
209
+ /**
210
+ * ONE `@warlock.js/web`, for the same reason `resolve.dedupe` below
211
+ * insists on one React — and it is invisible from inside this repo.
212
+ *
213
+ * Vite externalises `node_modules` in SSR by default, so an installed
214
+ * app gets TWO instances: the app's own `root.tsx` imports
215
+ * `@warlock.js/web` and Vite hands that off to Node, while the pipeline
216
+ * is loaded deliberately through `vite.ssrLoadModule(...)` and stays
217
+ * inside Vite's graph. `renderPage` then sets the document context on
218
+ * Vite's copy of `components/document-context`, and the app's `<Head/>`
219
+ * reads Node's copy, which has nothing in it:
220
+ *
221
+ * <Head/> was rendered outside the page pipeline's document context
222
+ *
223
+ * Measured on a published 5.0.1 install: `GET /` 500 without this line,
224
+ * 200 with it (404 control still 404). In THIS checkout the package
225
+ * resolves to source under Vite's root, never through `node_modules`,
226
+ * so both paths land on one instance and the bug cannot reproduce.
227
+ * Canon `6b7ab838`.
228
+ */
229
+ noExternal: ["@warlock.js/web"]
230
+ },
231
+ resolve: {
232
+ dedupe: ["react", "react-dom"],
233
+ alias: [...options.resolveAlias ?? [], ...appConventionAliases(options.appSrcRoot)]
234
+ }
235
+ };
236
+ }
237
+
238
+ //#endregion
239
+ export { createWebConnectorViteConfig };
240
+ //# sourceMappingURL=dev-server-config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server-config.mjs","names":[],"sources":["../../../../../../../web/src/vite/dev-server-config.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Alias, HttpServer, InlineConfig, PluginOption } from \"vite\";\nimport { appConventionAliases } from \"./app-convention-aliases\";\nimport { warlockClientBoundary } from \"./index\";\nimport { resolveReactFastRefreshPlugins } from \"./react-refresh-preamble\";\n\n/**\n * Third-party packages core reaches through `await import(...)` and that must\n * therefore never enter Vite's SSR transform graph.\n *\n * Left un-externalized, Vite's SSR module runner tries to resolve them anyway\n * and jams: the failure is NOT a missing-module error but a `transport invoke\n * timed out` on whatever unrelated module happened to be in flight. Derived in\n * one pass from `peerDependenciesMeta.optional` across every workspace package\n * reachable from `core/src/index.ts`, and carried over verbatim from\n * `dev-error-transport.ts`'s own list. Only THIRD-PARTY peers belong here — every\n * `@warlock.js/*` sibling must stay in Vite's graph.\n *\n * This list is core's peer list, not web's; publishing it from core instead\n * of duplicating it here is still outstanding.\n */\nconst CORE_OPTIONAL_PEERS = [\n // mail\n \"nodemailer\",\n \"@aws-sdk/client-sesv2\",\n \"@react-email/render\",\n // cache\n \"redis\",\n \"pg\",\n // cascade\n \"mongodb\",\n // logger\n \"@sentry/node\",\n // core\n \"sharp\",\n \"socket.io\",\n \"@aws-sdk/client-s3\",\n \"@aws-sdk/lib-storage\",\n \"@aws-sdk/s3-request-presigner\",\n // herald\n \"amqplib\",\n // ai\n \"langfuse\",\n \"openai\",\n \"pdf-parse\",\n] as const;\n\n/**\n * `web`'s OWN optional peers, kept separate from {@link CORE_OPTIONAL_PEERS}\n * because that list is core's and is documented as such.\n *\n * Same rule, same reason: anything this package reaches through\n * `await import(...)` must be external to every bundler and SSR pipeline, or\n * some pipeline will try to resolve it in an install that legitimately does not\n * have it. `vite` itself is the precedent — `createViteServer` has always\n * loaded it this way — and `@vitejs/plugin-react` is loaded from the same\n * function, for the same dev-only reason, so it belongs in the same set.\n */\nconst WEB_OPTIONAL_PEERS = [\"vite\", \"@vitejs/plugin-react\"] as const;\n\n/**\n * A directory, plus the path the filesystem really stores it at when the two\n * differ. Both forms belong in `server.fs.allow` — see the `fs` block in\n * {@link createWebConnectorViteConfig} for why one of them is never enough.\n *\n * A missing directory is not this function's problem to report: the roots it is\n * handed are already proven (`resolveWebPackageRoot`) or are the app's own cwd,\n * and an allow-list entry that points nowhere simply matches nothing.\n */\nfunction withRealPath(directory: string): string[] {\n try {\n const realPath = fs.realpathSync(directory);\n\n return realPath === directory ? [directory] : [directory, realPath];\n } catch {\n return [directory];\n }\n}\n\nexport type WebConnectorViteConfigOptions = {\n appRoot: string;\n appSrcRoot: string;\n webRoot: string;\n workspaceRoot: string;\n /** The raw node server Vite's HMR websocket shares with the rest of the app. */\n hmrServer: HttpServer;\n handlePageHotUpdate: (file: string) => Promise<boolean>;\n /** Plugins that must run ahead of the client-boundary gates, e.g. `devErrorTransportPlugin`. */\n leadingPlugins: PluginOption[];\n resolveAlias?: Alias[];\n ssrExternal?: string[];\n plugins?: PluginOption[];\n};\n\n/**\n * `WebConnector`'s Vite dev-server config — Vite in middleware mode,\n * `appType: \"custom\"` — Warlock owns the response shape and Vite never fronts\n * the server.\n *\n * `server.hmr.server` is handed the RAW node server (`fastify.server`), so the\n * HMR websocket shares the one port the app already listens on. No `hmr.port`\n * and no `clientPort`: Vite's HMR path stays the default `\"/\"`, socket.io\n * stays on `\"/socket.io\"` (`core/src/connectors/socket-connector.ts:90`), and\n * the two `upgrade` listeners coexist because each is a selective filter that\n * leaves a non-matching socket alone — verified empirically in both attachment\n * orders.\n */\nexport async function createWebConnectorViteConfig(\n options: WebConnectorViteConfigOptions,\n): Promise<InlineConfig> {\n return {\n root: options.appRoot,\n appType: \"custom\",\n plugins: [\n // FIRST, and dev-only by construction: this method is reachable only\n // from `boot()`'s Vite branch, past the `isProductionRuntime()` guard.\n // The same predicate is handed in rather than re-derived, and the\n // factory throws if it is ever true — see the plugin's own header for\n // why the layer has to be registered from a plugin and not from\n // `vite.middlewares.use(...)` after this call returns.\n ...options.leadingPlugins,\n ...warlockClientBoundary({\n appRoot: options.appRoot,\n beforePageHotUpdate: ({ file }) => options.handlePageHotUpdate(file),\n }),\n // AFTER the boundary, and the order matters among `enforce: \"pre\"`\n // plugins (Vite keeps array order within an enforce bucket).\n // `warlock:projection` strips a page's server exports — `loader` and\n // friends — before React's babel pass sees the module, so Fast Refresh\n // never registers a refresh boundary for an export that is not supposed\n // to reach the browser at all.\n //\n // BEFORE `options.plugins` so an application can still override.\n ...(await resolveReactFastRefreshPlugins({ webRoot: options.webRoot })),\n ...(options.plugins ?? []),\n ],\n server: {\n middlewareMode: true,\n hmr: { server: options.hmrServer },\n fs: {\n // `<Scripts />` points the browser at the hydration client entry under\n // `<webRoot>`: the published `esm/entry/index.mjs` when installed,\n // or `src/entry/index.ts` in this checkout. A dependency normally\n // lives under the app root's `node_modules`; when `@warlock.js/web` is\n // LINKED — a monorepo checkout, `npm link`, or a `file:` dependency —\n // its real path can sit outside every directory Vite allows by default\n // and the request comes back `403 Restricted`.\n //\n // That failure is silent in the worst way: SSR has already produced\n // the markup by the time the browser asks for the script, so the page\n // renders perfectly, nothing is logged, and the only symptom is that\n // no button ever works. Naming web's own root makes a linked install\n // behave like an installed one.\n //\n // Every root is listed twice, as given and as `realpathSync` reports\n // it, because Vite resolves a requested file to its REAL path before\n // testing it against this list. Allowing the symlink alone therefore\n // matches nothing — the 403 page prints the link that was allowed\n // directly above the real path it rejected.\n allow: [\n ...new Set([\n ...withRealPath(options.workspaceRoot),\n ...withRealPath(options.appRoot),\n ...withRealPath(options.webRoot),\n ]),\n ],\n },\n },\n // Without an explicit target, esbuild assumes native (TC39) decorator\n // support and leaves `@RegisterModel()`-style syntax untouched — but Vite's\n // SSR module runner evaluates transformed code via `new AsyncFunction(...)`,\n // which node has no native decorator support for. `es2022` downlevels them\n // into helper calls. `jsx` is named explicitly rather than left to tsconfig\n // discovery, because Vite matches a file against a tsconfig's `include` and\n // `web/tsconfig.json`'s is narrow enough that most of `web/src` matched no\n // config at all and fell back to the CLASSIC transform — emitting\n // `React.createElement` into modules that import no `React` binding.\n esbuild: { target: \"es2022\", jsx: \"automatic\" },\n /**\n * REACT MUST BE PRE-BUNDLED, and naming it here is the only thing that\n * makes that happen for the framework's own client graph.\n *\n * `react-dom/client` is CommonJS. A browser cannot import a named export\n * from it, so Vite's dep optimizer normally rewrites it into an ESM shim\n * under `/node_modules/.vite/deps/`. Whether that rewrite happens is\n * decided in `tryNodeResolve`, and one of the conditions that SKIPS it is\n * `importer && isInNodeModules(importer)`\n * (`vite/dist/node/chunks/config.js:32822`). Vite's reasoning is sound in\n * general — a dependency's own internal imports are the optimizer's job,\n * not the resolver's — but it is exactly wrong here:\n *\n * `<Scripts />` points the browser at the hydration entry, and in an\n * INSTALLED app that entry is\n * `<app>/node_modules/@warlock.js/web/esm/entry/index.mjs`.\n * Every module it reaches is therefore inside `node_modules`, so every\n * bare import it makes takes the skip branch and is served as the raw\n * file with a `?v=<browserHash>` cache key bolted on.\n *\n * For `esm/**.mjs` that is harmless — they are already ESM. For\n * `react-dom/client` it is fatal, and it is the whole defect: the browser\n * receives `\"use strict\"; function checkDCE()…` and refuses the module\n * with\n *\n * SyntaxError: The requested module '/node_modules/react-dom/client.js?v=…'\n * does not provide an export named 'hydrateRoot'\n *\n * Nothing in the client runtime then runs at all: no hydration, so no\n * `useState`, no Fast Refresh, and `<Link>` degrades to a full document\n * load because the navigation listener was never attached. Measured on a\n * published 5.0.2 install (fresh app, no symlinks): all four symptoms\n * before this block, none after.\n *\n * `include` is the fix rather than `entries` because it does not depend\n * on the scanner reaching the entry: an included id is pre-bundled at\n * server start and `tryOptimizedResolve` matches it by NAME\n * (`config.js:32633`) before `tryNodeResolve` — and that lookup has no\n * importer condition, so a node_modules importer resolves to the shim\n * like anyone else. Pointing `optimizeDeps.entries` at the hydration\n * entry would not work: the scanner classifies a resolution inside\n * `node_modules` as a dependency to externalize rather than a source to\n * walk.\n *\n * All four names are listed even though `react` and `react/jsx-runtime`\n * usually get discovered anyway — they are discovered only because the\n * APP's own pages import them, which is a fact about the app and not\n * something the framework may rely on. `react-dom/client` is imported by\n * `client/hydrate-page.tsx` and by nothing a normal app writes, which is\n * why it was the one that broke.\n *\n * This is INVISIBLE from the monorepo checkout: there the hydration entry\n * resolves to `web/src/entry/index.ts`, a path with no `node_modules`\n * segment, so the skip branch never fires and React optimizes normally.\n * Canon: nothing measured inside the checkout is evidence about a\n * published install.\n */\n optimizeDeps: {\n include: [\"react\", \"react-dom\", \"react-dom/client\", \"react/jsx-runtime\"],\n /**\n * The SECOND instance problem, and the reason this is `exclude` and not\n * another `include`.\n *\n * An app page's `import { Link } from \"@warlock.js/web\"` has an importer\n * OUTSIDE `node_modules`, so it takes the opposite branch above and the\n * whole package is pre-bundled into\n * `/node_modules/.vite/deps/@warlock__js_web.js`. The hydration entry\n * cannot reach that bundle — it is loaded by absolute path through\n * `/@fs/` and its imports are relative, so they resolve to the raw\n * `esm/*.mjs` files. The browser then holds TWO copies of every client\n * module in this package: the app renders `<Link>` from the bundled one\n * while the hydration entry publishes routes and mounts the document\n * context on the raw one. Module-level state does not cross that line.\n *\n * `routing/route-table` survives it by accident — it keeps its table in\n * a `Symbol.for` slot on `globalThis`, which is realm-wide — but\n * `components/document-context` is a React context OBJECT, and two\n * `createContext()` calls are two different contexts no provider can\n * bridge.\n *\n * Excluding the package makes the app's import resolve to\n * `esm/index.mjs` with the same `?v=` key the hydration entry's imports\n * carry, so there is one instance again. It costs the pre-bundle (a\n * handful of extra dev requests for files that are already valid ESM)\n * and nothing else — the grep across every client-reachable module in\n * this package finds exactly three bare imports, all of them React, all\n * of them covered by `include` above.\n */\n exclude: [\"@warlock.js/web\"],\n },\n ssr: {\n external: [...CORE_OPTIONAL_PEERS, ...WEB_OPTIONAL_PEERS, ...(options.ssrExternal ?? [])],\n /**\n * ONE `@warlock.js/web`, for the same reason `resolve.dedupe` below\n * insists on one React — and it is invisible from inside this repo.\n *\n * Vite externalises `node_modules` in SSR by default, so an installed\n * app gets TWO instances: the app's own `root.tsx` imports\n * `@warlock.js/web` and Vite hands that off to Node, while the pipeline\n * is loaded deliberately through `vite.ssrLoadModule(...)` and stays\n * inside Vite's graph. `renderPage` then sets the document context on\n * Vite's copy of `components/document-context`, and the app's `<Head/>`\n * reads Node's copy, which has nothing in it:\n *\n * <Head/> was rendered outside the page pipeline's document context\n *\n * Measured on a published 5.0.1 install: `GET /` 500 without this line,\n * 200 with it (404 control still 404). In THIS checkout the package\n * resolves to source under Vite's root, never through `node_modules`,\n * so both paths land on one instance and the bug cannot reproduce.\n * Canon `6b7ab838`.\n */\n noExternal: [\"@warlock.js/web\"],\n },\n resolve: {\n // ONE React, resolved from the application. A linked `@warlock.js/web`\n // resolves `react` out of its own tree while the app's pages resolve it\n // out of theirs; two React instances share no hook dispatcher, and SSR\n // dies on the first `useState` with \"Cannot read properties of null\".\n // `dedupe` forces these package names to resolve from Vite's `root` —\n // the app — whoever imported them, in the SSR environment as much as in\n // the client one.\n //\n // Two names cover every entry point. Vite matches a deep import against\n // the package it belongs to before consulting this list, so\n // `react-dom/client`, `react/jsx-runtime` and `react/jsx-dev-runtime`\n // are already deduped by `react-dom` and `react`; listing them\n // separately would only add entries that can never match.\n //\n // Do NOT express this as a `resolve.alias` entry instead. Pointing a\n // bare React specifier at a directory drags React's CommonJS entry into\n // Vite's SSR module graph, and the dev server then dies at startup with\n // \"module is not defined\" before it renders anything at all.\n dedupe: [\"react\", \"react-dom\"],\n alias: [\n ...(options.resolveAlias ?? []),\n // The app-tree convention `v5/app/tsconfig.json`'s own `paths` declare.\n // Vite does not read tsconfig paths on its own and no\n // `vite-tsconfig-paths` plugin is installed in this workspace.\n //\n // ONE definition, shared with the production build contribution\n // (`web/src/build/contribution.ts`). These were two separate literals\n // until 2026-08-24, and the production half simply did not have them —\n // dev resolved `web/*` while the production client build died on the\n // first page. Do not inline them back here.\n ...appConventionAliases(options.appSrcRoot),\n ],\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,MAAM,sBAAsB;CAE1B;CACA;CACA;CAEA;CACA;CAEA;CAEA;CAEA;CACA;CACA;CACA;CACA;CAEA;CAEA;CACA;CACA;AACF;;;;;;;;;;;;AAaA,MAAM,qBAAqB,CAAC,QAAQ,sBAAsB;;;;;;;;;;AAW1D,SAAS,aAAa,WAA6B;CACjD,IAAI;EACF,MAAM,WAAW,GAAG,aAAa,SAAS;EAE1C,OAAO,aAAa,YAAY,CAAC,SAAS,IAAI,CAAC,WAAW,QAAQ;CACpE,QAAQ;EACN,OAAO,CAAC,SAAS;CACnB;AACF;;;;;;;;;;;;;;AA8BA,eAAsB,6BACpB,SACuB;CACvB,OAAO;EACL,MAAM,QAAQ;EACd,SAAS;EACT,SAAS;GAOP,GAAG,QAAQ;GACX,GAAG,sBAAsB;IACvB,SAAS,QAAQ;IACjB,sBAAsB,EAAE,WAAW,QAAQ,oBAAoB,IAAI;GACrE,CAAC;GASD,GAAI,MAAM,+BAA+B,EAAE,SAAS,QAAQ,QAAQ,CAAC;GACrE,GAAI,QAAQ,WAAW,CAAC;EAC1B;EACA,QAAQ;GACN,gBAAgB;GAChB,KAAK,EAAE,QAAQ,QAAQ,UAAU;GACjC,IAAI,EAoBF,OAAO,CACL,GAAG,IAAI,IAAI;IACT,GAAG,aAAa,QAAQ,aAAa;IACrC,GAAG,aAAa,QAAQ,OAAO;IAC/B,GAAG,aAAa,QAAQ,OAAO;GACjC,CAAC,CACH,EACF;EACF;EAUA,SAAS;GAAE,QAAQ;GAAU,KAAK;EAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0D9C,cAAc;GACZ,SAAS;IAAC;IAAS;IAAa;IAAoB;GAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BvE,SAAS,CAAC,iBAAiB;EAC7B;EACA,KAAK;GACH,UAAU;IAAC,GAAG;IAAqB,GAAG;IAAoB,GAAI,QAAQ,eAAe,CAAC;GAAE;;;;;;;;;;;;;;;;;;;;;GAqBxF,YAAY,CAAC,iBAAiB;EAChC;EACA,SAAS;GAmBP,QAAQ,CAAC,SAAS,WAAW;GAC7B,OAAO,CACL,GAAI,QAAQ,gBAAgB,CAAC,GAU7B,GAAG,qBAAqB,QAAQ,UAAU,CAC5C;EACF;CACF;AACF"}
@@ -14,11 +14,12 @@ interface EnvironmentClassifierOptions {
14
14
  serverPackages?: Iterable<string>;
15
15
  /**
16
16
  * The app's project root. Defines what counts as APP SOURCE (see
17
- * `isAppSourcePath`), which two rules depend on: rule 4 ("outside
18
- * `$module/web/`") and rule 3's plain-`server/`-folder half. A dependency's
17
+ * `isAppSourcePath`), which rule 3's plain-`server/`-folder half depends on
18
+ * (rule 4, "outside `$module/web/`", drew on the same distinction before it
19
+ * was removed — see `ruleViolation`'s "RULE 4 IS GONE" note). A dependency's
19
20
  * own internal file layout — reached once resolution has left the app root,
20
- * or through a `node_modules/` segment — is exempt from both, though rules
21
- * 1, 2 and the `.server` half of rule 3 still apply to it. Defaults to
21
+ * or through a `node_modules/` segment — is exempt from it, though rules 1,
22
+ * 2 and the `.server` half of rule 3 still apply to it. Defaults to
22
23
  * `process.cwd()`, matching Vite's own default `root`.
23
24
  */
24
25
  appRoot?: string;
@@ -1,3 +1,5 @@
1
+ import { toPosix } from "../shared/to-posix.mjs";
2
+ import { moduleKey } from "../shared/module-key.mjs";
1
3
  import { builtinModules } from "node:module";
2
4
  import { existsSync, readFileSync, statSync } from "node:fs";
3
5
  import path from "node:path";
@@ -14,6 +16,10 @@ import { fileURLToPath } from "node:url";
14
16
  * `externalizedBuiltins` for the one route that reaches a chunk
15
17
  * without passing through `resolveId` at all. This rule is judged for
16
18
  * EVERY importer, in or out of `GOVERNED_SCOPES` — see its note below.
19
+ * 1b. The `server-only` npm package — a module importing it has declared
20
+ * itself server-side and cannot reach the browser. Judged by NAME, right
21
+ * after rule 1 and ahead of rule 2's scope check; see
22
+ * `BOUNDARY_DECLARATION_PACKAGES`.
17
23
  * 2. `@warlock.js/*` / `@mongez/*` packages whose `package.json` carries
18
24
  * `"warlock": { "environment": "server" }`. An ABSENT marker is NOT a
19
25
  * refusal — see the burden note on `createEnvironmentClassifier`. Judged
@@ -24,17 +30,16 @@ import { fileURLToPath } from "node:url";
24
30
  * 3. Server-only modules declared by FILE NAME: `*.server.ts` files,
25
31
  * anything under a `.server/` directory, and — for the app's OWN source
26
32
  * only — anything under a plain `server/` directory segment.
27
- * 4. Local modules outside a `$module/web/` (or `src/web/`) folder that are
28
- * not a recognized universal surface (`*.page.tsx`, `layout.tsx`). The app
29
- * root (`src/web/root.tsx`) is admitted by the `web/` FOLDER rule above,
30
- * not by its name see the note in `isRecognizedUniversalSurface`. A
31
- * specifier that omits its file extension is judged on the file it will
32
- * actually load, not on the extensionless string see
33
- * `completeLocalModulePath`.
34
- * 5. The `server-only` npm package a module importing it has declared
35
- * itself server-side and cannot reach the browser. Judged by NAME, right
36
- * after rule 1 and ahead of rule 2's scope check; see
37
- * `BOUNDARY_DECLARATION_PACKAGES`.
33
+ *
34
+ * A former rule 4 — refusing a local module outside a `$module/web/` folder
35
+ * that was not a recognized universal surface is GONE, deliberately. Owner
36
+ * ruling 2026-08-24 (canon `10f6041c`, see `ruleViolation`'s "RULE 4 IS GONE"
37
+ * note): it was a location PROXY for danger and refused genuinely universal
38
+ * files for their address alone. The client boundary is now decided by the
39
+ * import GRAPH — rules 1, 1b, 2 and 3 above — not by where a file sits. A
40
+ * specifier that omits its file extension is judged on the file it will
41
+ * actually load, not on the extensionless string see
42
+ * `completeLocalModulePath`.
38
43
  *
39
44
  * Gate B (inline secrets), Gate C (output verification) and the SSR mirror
40
45
  * rule for a future client-only rendering primitive is NOT this gate. Do not
@@ -202,9 +207,6 @@ function isNodeBuiltin(source) {
202
207
  if (source.startsWith("node:")) return true;
203
208
  return NODE_BUILTINS.has(source);
204
209
  }
205
- function normalize(filePath) {
206
- return filePath.replace(/\\/g, "/");
207
- }
208
210
  /**
209
211
  * A plain `server/` path segment, tested against a path ALREADY made relative
210
212
  * to the app root — never against the absolute path. An app checked out at
@@ -213,55 +215,23 @@ function normalize(filePath) {
213
215
  const APP_SERVER_SEGMENT = /(^|\/)server(\/|$)/;
214
216
  function isServerFile(resolvedPath, appRoot) {
215
217
  const bare = resolvedPath.split("?")[0];
216
- const normalized = normalize(bare);
218
+ const normalized = toPosix(bare);
217
219
  if (/\.server(\.[jt]sx?)?$/.test(normalized)) return true;
218
220
  if (/(^|\/)\.server(\/|$)/.test(normalized)) return true;
219
221
  if (isAppSourcePath(bare, appRoot)) {
220
- if (APP_SERVER_SEGMENT.test(normalize(path.relative(appRoot, bare)))) return true;
222
+ if (APP_SERVER_SEGMENT.test(toPosix(path.relative(appRoot, bare)))) return true;
221
223
  }
222
224
  return false;
223
225
  }
224
226
  function isRecognizedUniversalSurface(resolvedPath) {
225
- const normalized = normalize(resolvedPath);
227
+ const normalized = toPosix(resolvedPath);
226
228
  const base = path.basename(normalized);
227
229
  if (/\.page\.tsx?$/.test(base)) return true;
228
230
  if (base === "layout.tsx" || base === "layout.ts") return true;
229
231
  return false;
230
232
  }
231
233
  /**
232
- * A `web/` path segment, tested like `APP_SERVER_SEGMENT` above and for the
233
- * same reason — against a path ALREADY made relative to the app root.
234
- *
235
- * Never against the absolute path, and this repo is its own proof: Gate A's
236
- * fixtures live at `@warlock.js/web/__tests__/vite/fixtures/gate-a/...`, so
237
- * EVERY absolute path under them contains a `/web/` segment belonging to the
238
- * package directory rather than to any app module. Matched absolutely, rule 4
239
- * would admit the whole fixture tree and cases 9 and 25-28 would all go green
240
- * for the wrong reason.
241
- */
242
- const MODULE_WEB_SEGMENT = /(^|\/)web(\/|$)/;
243
- /**
244
- * Whether a judged path is anywhere INSIDE a module's `web/` folder — at any
245
- * depth, not merely directly inside one.
246
- *
247
- * The depth matters and used to be wrong: this asked for `/web/<one-segment>`,
248
- * so `src/web/middleware/base.middleware.ts` — two segments in — did not count
249
- * as being within `src/web/` at all. Nothing had noticed because the only way
250
- * to reach such a file is a relative import, and rule 4 was not judging those
251
- * (see `completeLocalModulePath`): a nested universal file escaped the rule
252
- * rather than being admitted by it, and the two are indistinguishable until the
253
- * escape is closed. Closing it without this would have turned the v5 app's own
254
- * `src/web/root.tsx` — which imports exactly that middleware — into a refusal.
255
- *
256
- * Rule 4's premise is a FOLDER ("outside a `$module/web/` folder"), and a
257
- * subfolder of `web/` is inside it. `web/components/`, `web/layouts/` and
258
- * `web/utils/` are ordinary app layout, not a way around the fence.
259
- */
260
- function isWithinModuleWebFolder(resolvedPath, appRoot) {
261
- return MODULE_WEB_SEGMENT.test(normalize(path.relative(appRoot, resolvedPath)));
262
- }
263
- /**
264
- * Rule 4's subject set: local files that CARRY CODE. Deliberately the same
234
+ * Local files that CARRY CODE. Deliberately the same
265
235
  * shape as `PARSEABLE_MODULE_EXTENSIONS` below — one notion of "this is a
266
236
  * JS/TS module" for the whole file — which also picks up `.mts`/`.cts`, absent
267
237
  * from the earlier hand-spelled `/\.(tsx?|jsx?|mjs|cjs)$/`. `.mts` is a real
@@ -285,9 +255,9 @@ const LOCAL_MODULE_EXTENSIONS = /\.([cm]?[jt]sx?)$/;
285
255
  * escape hatch of exactly the shape this completion exists to close — the
286
256
  * judged path would gain a suffix and still be waved through. Pinned by case 32
287
257
  * in the spec. The order only decides which of two same-named files gets
288
- * judged, and every rule downstream (`isServerFile`, `isWithinModuleWebFolder`,
289
- * `isRecognizedUniversalSurface`) answers identically for `x.ts` and `x.js`, so
290
- * matching Vite is for least surprise rather than for correctness.
258
+ * judged, and every rule downstream (`isServerFile`, `isRecognizedUniversalSurface`)
259
+ * answers identically for `x.ts` and `x.js`, so matching Vite is for least
260
+ * surprise rather than for correctness.
291
261
  */
292
262
  const IMPLICIT_MODULE_EXTENSIONS = [
293
263
  ".mjs",
@@ -372,10 +342,9 @@ function completeBareLocalModulePath(judgedPath) {
372
342
  }
373
343
  /**
374
344
  * Whether a judged path is the APP'S OWN SOURCE, as opposed to a dependency's
375
- * internals. This is Gate A's single definition of that boundary — rules 3
376
- * and 4 both call it, and `packageNameForFilePath` draws the same line for
377
- * Gate C. Two conditions, both already required by rule 4 before this was
378
- * extracted:
345
+ * internals. This is Gate A's single definition of that boundary — rule 3
346
+ * calls it, and `packageNameForFilePath` draws the same line for
347
+ * Gate C. Two conditions:
379
348
  *
380
349
  * - inside `appRoot` (a monorepo sibling or any other path resolution has
381
350
  * left the app root for is that package's own business), and
@@ -391,7 +360,7 @@ function completeBareLocalModulePath(judgedPath) {
391
360
  function isAppSourcePath(resolvedPath, appRoot) {
392
361
  if (!path.isAbsolute(resolvedPath)) return false;
393
362
  if (!isInsideAppRoot(resolvedPath, appRoot)) return false;
394
- return !normalize(resolvedPath).includes("/node_modules/");
363
+ return !toPosix(resolvedPath).includes("/node_modules/");
395
364
  }
396
365
  function isInsideAppRoot(resolvedPath, appRoot) {
397
366
  const relative = path.relative(appRoot, resolvedPath);
@@ -408,14 +377,6 @@ const GOVERNED_SCOPE_MENTION = /@warlock\.js\/|@mongez\//;
408
377
  * `undefined` for any id containing `\0` — see its note).
409
378
  */
410
379
  const TYPE_ONLY_ERASED_ID = "\0warlock:type-only-erased";
411
- /**
412
- * Rollup module ids carry query suffixes (`?v=`, `?used`, ...) that differ
413
- * between the `transform` id and a later `importer`. Both sides key through
414
- * here so they agree.
415
- */
416
- function moduleKey(id) {
417
- return normalize(id.split("?")[0]);
418
- }
419
380
  function parserPluginsFor(id) {
420
381
  return /\.[cm]?ts$/.test(moduleKey(id)) ? ["typescript"] : ["typescript", "jsx"];
421
382
  }
@@ -567,7 +528,7 @@ function ruleViolation(source, resolvedPath, importer, environmentOf, appRoot, i
567
528
  * than the one at the top of the tree.
568
529
  */
569
530
  function displayName(id) {
570
- const normalized = normalize(id);
531
+ const normalized = toPosix(id);
571
532
  const segments = normalized.split("node_modules/");
572
533
  if (segments.length < 2) return path.basename(normalized);
573
534
  return segments[segments.length - 1];
@@ -646,12 +607,12 @@ function createEnvironmentClassifier(options = {}) {
646
607
  }
647
608
  function packageNameForFilePath(absPath) {
648
609
  if (absPath.includes("\0")) return void 0;
649
- const normalized = normalize(path.resolve(absPath));
610
+ const normalized = toPosix(path.resolve(absPath));
650
611
  const nodeModulesMatch = packageNameFromNodeModulesPath(normalized);
651
612
  if (nodeModulesMatch) return nodeModulesMatch;
652
613
  if (isInsideAppRoot(normalized, appRoot)) return void 0;
653
614
  if (workspaceIndex) for (const [name, pkgJsonPath] of workspaceIndex.packageJsonByName) {
654
- const root = `${normalize(path.dirname(pkgJsonPath))}/`;
615
+ const root = `${toPosix(path.dirname(pkgJsonPath))}/`;
655
616
  if (normalized.startsWith(root)) return name;
656
617
  }
657
618
  }
@@ -884,5 +845,5 @@ function gateAResolve(options = {}) {
884
845
  }
885
846
 
886
847
  //#endregion
887
- export { createEnvironmentClassifier, gateAResolve, isAppSourcePath, isRecognizedUniversalSurface, isServerFile, isWithinModuleWebFolder };
848
+ export { createEnvironmentClassifier, gateAResolve, isAppSourcePath, isRecognizedUniversalSurface, isServerFile };
888
849
  //# sourceMappingURL=gate-a-resolve.mjs.map