@warlock.js/web 5.5.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.
- package/CHANGELOG.md +27 -5
- package/esm/build/contribution.d.mts +2 -2
- package/esm/build/contribution.mjs.map +1 -1
- package/esm/build/discover-pages.mjs +127 -205
- package/esm/build/discover-pages.mjs.map +1 -1
- package/esm/build/generate-pages-barrel.mjs +5 -3
- package/esm/build/generate-pages-barrel.mjs.map +1 -1
- package/esm/build/public-files.mjs +29 -1
- package/esm/build/public-files.mjs.map +1 -1
- package/esm/build/read-metadata-keys.mjs +225 -0
- package/esm/build/read-metadata-keys.mjs.map +1 -0
- package/esm/build/read-route-exports.mjs.map +1 -1
- package/esm/client/build-hydrated-tree.mjs +1 -1
- package/esm/client/build-hydrated-tree.mjs.map +1 -1
- package/esm/client/navigation/document-metadata.mjs +108 -0
- package/esm/client/navigation/document-metadata.mjs.map +1 -0
- package/esm/client/navigation/navigation-root.mjs +23 -103
- package/esm/client/navigation/navigation-root.mjs.map +1 -1
- package/esm/client/navigation/scroll-to-fragment.mjs.map +1 -1
- package/esm/client/navigation/use-query-string.d.mts +35 -0
- package/esm/client/navigation/use-query-string.mjs +68 -0
- package/esm/client/navigation/use-query-string.mjs.map +1 -0
- package/esm/client/runtime/index.d.mts +1 -1
- package/esm/client/runtime/index.mjs +1 -1
- package/esm/client/runtime/manifest.mjs.map +1 -1
- package/esm/client/runtime/matcher.mjs.map +1 -1
- package/esm/client/runtime/types.d.mts +1 -1
- package/esm/components/link.mjs.map +1 -1
- package/esm/core/src/router/normalize-route-path.mjs +33 -0
- package/esm/core/src/router/normalize-route-path.mjs.map +1 -0
- package/esm/{hydration → entry}/index.mjs +1 -1
- package/esm/entry/index.mjs.map +1 -0
- package/esm/hydration-payload.mjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +3 -2
- package/esm/loaders.d.mts +10 -3
- package/esm/localization.mjs.map +1 -1
- package/esm/metadata.d.mts +4 -4
- package/esm/metadata.mjs.map +1 -1
- package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs +33 -0
- package/esm/node_modules/.pnpm/@mongez_concat-route@1.2.0/node_modules/@mongez/concat-route/esm/index.mjs.map +1 -0
- package/esm/{runtime/register-modules.d.mts → register-modules.d.mts} +1 -1
- package/esm/{runtime/register-modules.mjs → register-modules.mjs} +1 -1
- package/esm/register-modules.mjs.map +1 -0
- package/esm/route.d.mts +35 -2
- package/esm/routing/duplicate-route-path.mjs +22 -0
- package/esm/routing/duplicate-route-path.mjs.map +1 -0
- package/esm/routing/layout-level.mjs +50 -0
- package/esm/routing/layout-level.mjs.map +1 -0
- package/esm/routing/layout-policy.mjs.map +1 -1
- package/esm/routing/query-string.d.mts +23 -1
- package/esm/routing/query-string.mjs +23 -1
- package/esm/routing/query-string.mjs.map +1 -1
- package/esm/routing/route-identity.mjs +27 -2
- package/esm/routing/route-identity.mjs.map +1 -1
- package/esm/routing/route-table.mjs.map +1 -1
- package/esm/routing/router-events.d.mts +5 -4
- package/esm/routing/router-events.mjs.map +1 -1
- package/esm/server/create-page-route-handler.d.mts +2 -2
- package/esm/server/create-page-route-handler.mjs +6 -6
- package/esm/server/create-page-route-handler.mjs.map +1 -1
- package/esm/server/{dev-server.mjs → dev-error-transport.mjs} +2 -2
- package/esm/server/dev-error-transport.mjs.map +1 -0
- package/esm/server/execute-page-request.mjs +87 -23
- package/esm/server/execute-page-request.mjs.map +1 -1
- package/esm/server/execute-page-request.types.d.mts +10 -1
- package/esm/server/fold-layout-loaders.mjs +36 -0
- package/esm/server/fold-layout-loaders.mjs.map +1 -0
- package/esm/server/hydration-client-url.mjs +2 -2
- package/esm/server/hydration-client-url.mjs.map +1 -1
- package/esm/server/index.d.mts +2 -2
- package/esm/server/index.mjs +2 -2
- package/esm/server/install-page-routes-from-manifest.d.mts +1 -1
- package/esm/server/install-page-routes-from-manifest.mjs +43 -40
- package/esm/server/install-page-routes-from-manifest.mjs.map +1 -1
- package/esm/server/install-page-routes.d.mts +22 -2
- package/esm/server/install-page-routes.mjs +108 -42
- package/esm/server/install-page-routes.mjs.map +1 -1
- package/esm/server/install-production-page-routes.mjs.map +1 -1
- package/esm/server/match-page-route.mjs.map +1 -1
- package/esm/server/page-file-change.mjs.map +1 -1
- package/esm/server/page-route-reload.mjs +4 -3
- package/esm/server/page-route-reload.mjs.map +1 -1
- package/esm/server/register-production-public-files.mjs +25 -1
- package/esm/server/register-production-public-files.mjs.map +1 -1
- package/esm/server/render-page.d.mts +11 -48
- package/esm/server/render-page.mjs +13 -55
- package/esm/server/render-page.mjs.map +1 -1
- package/esm/server/resolve-page-metadata.mjs.map +1 -1
- package/esm/server/resolve-route-validation-input.mjs +11 -0
- package/esm/server/resolve-route-validation-input.mjs.map +1 -0
- package/esm/server/route-validation-error.mjs +32 -0
- package/esm/server/route-validation-error.mjs.map +1 -0
- package/esm/server/settle-page-response.mjs +5 -3
- package/esm/server/settle-page-response.mjs.map +1 -1
- package/esm/server/unregistered-pages.mjs +2 -1
- package/esm/server/unregistered-pages.mjs.map +1 -1
- package/esm/server/web-connector-factory.mjs +1 -1
- package/esm/server/web-connector-factory.mjs.map +1 -1
- package/esm/server/web-connector.mjs +24 -321
- package/esm/server/web-connector.mjs.map +1 -1
- package/esm/shared/module-key.mjs +15 -0
- package/esm/shared/module-key.mjs.map +1 -0
- package/esm/shared/to-posix.mjs +13 -0
- package/esm/shared/to-posix.mjs.map +1 -0
- package/esm/shared.mjs +24 -0
- package/esm/shared.mjs.map +1 -1
- package/esm/vite/build-client.mjs.map +1 -1
- package/esm/vite/dev-server-config.mjs +240 -0
- package/esm/vite/dev-server-config.mjs.map +1 -0
- package/esm/vite/gate-a-resolve.d.mts +5 -4
- package/esm/vite/gate-a-resolve.mjs +31 -70
- package/esm/vite/gate-a-resolve.mjs.map +1 -1
- package/esm/vite/gate-b-secrets.mjs.map +1 -1
- package/esm/vite/gate-c-verify.d.mts +1 -1
- package/esm/vite/gate-c-verify.mjs +1 -1
- package/esm/vite/gate-c-verify.mjs.map +1 -1
- package/esm/vite/hydration-entries.mjs +5 -7
- package/esm/vite/hydration-entries.mjs.map +1 -1
- package/esm/vite/index.d.mts +1 -1
- package/esm/vite/index.mjs +4 -110
- package/esm/vite/index.mjs.map +1 -1
- package/esm/vite/page-registry-plugin.mjs +9 -26
- package/esm/vite/page-registry-plugin.mjs.map +1 -1
- package/esm/vite/projection.mjs.map +1 -1
- package/esm/vite/react-refresh-preamble.mjs +90 -0
- package/esm/vite/react-refresh-preamble.mjs.map +1 -0
- package/esm/vite/ssr-client-view.mjs +135 -0
- package/esm/vite/ssr-client-view.mjs.map +1 -0
- package/llms-full.txt +558 -482
- package/llms.txt +2 -2
- package/package.json +6 -6
- package/skills/create-a-page/SKILL.md +348 -295
- package/skills/navigate-on-the-client/SKILL.md +212 -189
- package/esm/hydration/index.mjs.map +0 -1
- package/esm/runtime/register-modules.mjs.map +0 -1
- package/esm/server/dev-server.mjs.map +0 -1
- /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
|
|
18
|
-
* `$module/web/`"
|
|
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
|
|
21
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
-
*
|
|
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`, `
|
|
289
|
-
*
|
|
290
|
-
*
|
|
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 —
|
|
376
|
-
*
|
|
377
|
-
* Gate C. Two conditions
|
|
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 !
|
|
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 =
|
|
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 =
|
|
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 = `${
|
|
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
|
|
848
|
+
export { createEnvironmentClassifier, gateAResolve, isAppSourcePath, isRecognizedUniversalSurface, isServerFile };
|
|
888
849
|
//# sourceMappingURL=gate-a-resolve.mjs.map
|