vinext 0.0.47 → 0.0.48

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 (263) hide show
  1. package/README.md +1 -1
  2. package/dist/build/layout-classification.js +3 -1
  3. package/dist/build/layout-classification.js.map +1 -1
  4. package/dist/build/prerender.js +10 -10
  5. package/dist/build/prerender.js.map +1 -1
  6. package/dist/build/report.d.ts +8 -4
  7. package/dist/build/report.js +17 -7
  8. package/dist/build/report.js.map +1 -1
  9. package/dist/build/run-prerender.d.ts +5 -0
  10. package/dist/build/run-prerender.js +4 -1
  11. package/dist/build/run-prerender.js.map +1 -1
  12. package/dist/build/server-manifest.js +2 -7
  13. package/dist/build/server-manifest.js.map +1 -1
  14. package/dist/build/standalone.js +3 -5
  15. package/dist/build/standalone.js.map +1 -1
  16. package/dist/check.js +45 -29
  17. package/dist/check.js.map +1 -1
  18. package/dist/cli-args.d.ts +3 -1
  19. package/dist/cli-args.js +18 -1
  20. package/dist/cli-args.js.map +1 -1
  21. package/dist/cli.js +9 -1
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config/config-matchers.js +46 -37
  24. package/dist/config/config-matchers.js.map +1 -1
  25. package/dist/deploy.d.ts +18 -2
  26. package/dist/deploy.js +47 -4
  27. package/dist/deploy.js.map +1 -1
  28. package/dist/entries/app-rsc-entry.js +11 -9
  29. package/dist/entries/app-rsc-entry.js.map +1 -1
  30. package/dist/entries/app-rsc-manifest.js +4 -1
  31. package/dist/entries/app-rsc-manifest.js.map +1 -1
  32. package/dist/entries/pages-client-entry.js +3 -2
  33. package/dist/entries/pages-client-entry.js.map +1 -1
  34. package/dist/entries/pages-server-entry.js +14 -59
  35. package/dist/entries/pages-server-entry.js.map +1 -1
  36. package/dist/entries/runtime-entry-module.d.ts +12 -3
  37. package/dist/entries/runtime-entry-module.js +15 -4
  38. package/dist/entries/runtime-entry-module.js.map +1 -1
  39. package/dist/index.js +12 -7
  40. package/dist/index.js.map +1 -1
  41. package/dist/plugins/og-assets.js +15 -16
  42. package/dist/plugins/og-assets.js.map +1 -1
  43. package/dist/plugins/rsc-client-shim-excludes.d.ts +2 -1
  44. package/dist/plugins/rsc-client-shim-excludes.js +10 -1
  45. package/dist/plugins/rsc-client-shim-excludes.js.map +1 -1
  46. package/dist/routing/app-route-graph.d.ts +90 -4
  47. package/dist/routing/app-route-graph.js +210 -7
  48. package/dist/routing/app-route-graph.js.map +1 -1
  49. package/dist/routing/app-router.d.ts +15 -3
  50. package/dist/routing/app-router.js +20 -23
  51. package/dist/routing/app-router.js.map +1 -1
  52. package/dist/routing/file-matcher.d.ts +3 -1
  53. package/dist/routing/file-matcher.js +6 -1
  54. package/dist/routing/file-matcher.js.map +1 -1
  55. package/dist/routing/pages-router.js +10 -19
  56. package/dist/routing/pages-router.js.map +1 -1
  57. package/dist/routing/route-matching.d.ts +28 -0
  58. package/dist/routing/route-matching.js +44 -0
  59. package/dist/routing/route-matching.js.map +1 -0
  60. package/dist/routing/route-pattern.js +4 -1
  61. package/dist/routing/route-pattern.js.map +1 -1
  62. package/dist/routing/route-trie.d.ts +8 -0
  63. package/dist/routing/route-trie.js +12 -1
  64. package/dist/routing/route-trie.js.map +1 -1
  65. package/dist/routing/route-validation.js +3 -4
  66. package/dist/routing/route-validation.js.map +1 -1
  67. package/dist/routing/utils.d.ts +8 -1
  68. package/dist/routing/utils.js +25 -2
  69. package/dist/routing/utils.js.map +1 -1
  70. package/dist/server/app-browser-entry.js +66 -49
  71. package/dist/server/app-browser-entry.js.map +1 -1
  72. package/dist/server/app-browser-navigation-controller.d.ts +7 -5
  73. package/dist/server/app-browser-navigation-controller.js +43 -35
  74. package/dist/server/app-browser-navigation-controller.js.map +1 -1
  75. package/dist/server/app-browser-state.d.ts +33 -15
  76. package/dist/server/app-browser-state.js +52 -59
  77. package/dist/server/app-browser-state.js.map +1 -1
  78. package/dist/server/app-browser-visible-commit.d.ts +68 -0
  79. package/dist/server/app-browser-visible-commit.js +182 -0
  80. package/dist/server/app-browser-visible-commit.js.map +1 -0
  81. package/dist/server/app-client-reference-preloader.d.ts +15 -0
  82. package/dist/server/app-client-reference-preloader.js +46 -0
  83. package/dist/server/app-client-reference-preloader.js.map +1 -0
  84. package/dist/server/app-elements-wire.d.ts +130 -0
  85. package/dist/server/app-elements-wire.js +205 -0
  86. package/dist/server/app-elements-wire.js.map +1 -0
  87. package/dist/server/app-elements.d.ts +2 -84
  88. package/dist/server/app-elements.js +3 -102
  89. package/dist/server/app-elements.js.map +1 -1
  90. package/dist/server/app-fallback-renderer.d.ts +1 -1
  91. package/dist/server/app-middleware.d.ts +2 -1
  92. package/dist/server/app-middleware.js +34 -11
  93. package/dist/server/app-middleware.js.map +1 -1
  94. package/dist/server/app-page-boundary-render.d.ts +1 -1
  95. package/dist/server/app-page-boundary-render.js +8 -5
  96. package/dist/server/app-page-boundary-render.js.map +1 -1
  97. package/dist/server/app-page-boundary.js +2 -1
  98. package/dist/server/app-page-boundary.js.map +1 -1
  99. package/dist/server/app-page-cache.d.ts +1 -0
  100. package/dist/server/app-page-cache.js +8 -13
  101. package/dist/server/app-page-cache.js.map +1 -1
  102. package/dist/server/app-page-dispatch.d.ts +2 -1
  103. package/dist/server/app-page-dispatch.js +18 -10
  104. package/dist/server/app-page-dispatch.js.map +1 -1
  105. package/dist/server/app-page-element-builder.d.ts +1 -1
  106. package/dist/server/app-page-element-builder.js +8 -5
  107. package/dist/server/app-page-element-builder.js.map +1 -1
  108. package/dist/server/app-page-execution.d.ts +23 -5
  109. package/dist/server/app-page-execution.js +39 -24
  110. package/dist/server/app-page-execution.js.map +1 -1
  111. package/dist/server/app-page-head.js +2 -1
  112. package/dist/server/app-page-head.js.map +1 -1
  113. package/dist/server/app-page-method.js +2 -5
  114. package/dist/server/app-page-method.js.map +1 -1
  115. package/dist/server/app-page-probe.d.ts +1 -1
  116. package/dist/server/app-page-probe.js +5 -1
  117. package/dist/server/app-page-probe.js.map +1 -1
  118. package/dist/server/app-page-render.d.ts +1 -1
  119. package/dist/server/app-page-render.js +38 -3
  120. package/dist/server/app-page-render.js.map +1 -1
  121. package/dist/server/app-page-request.d.ts +0 -1
  122. package/dist/server/app-page-request.js +7 -10
  123. package/dist/server/app-page-request.js.map +1 -1
  124. package/dist/server/app-page-response.js +3 -2
  125. package/dist/server/app-page-response.js.map +1 -1
  126. package/dist/server/app-page-route-wiring.d.ts +5 -2
  127. package/dist/server/app-page-route-wiring.js +15 -12
  128. package/dist/server/app-page-route-wiring.js.map +1 -1
  129. package/dist/server/app-page-stream.d.ts +7 -0
  130. package/dist/server/app-page-stream.js +9 -2
  131. package/dist/server/app-page-stream.js.map +1 -1
  132. package/dist/server/app-prerender-endpoints.js +3 -2
  133. package/dist/server/app-prerender-endpoints.js.map +1 -1
  134. package/dist/server/app-route-handler-cache.js +2 -1
  135. package/dist/server/app-route-handler-cache.js.map +1 -1
  136. package/dist/server/app-route-handler-dispatch.js +6 -5
  137. package/dist/server/app-route-handler-dispatch.js.map +1 -1
  138. package/dist/server/app-route-handler-policy.js +13 -13
  139. package/dist/server/app-route-handler-policy.js.map +1 -1
  140. package/dist/server/app-route-handler-response.js +2 -1
  141. package/dist/server/app-route-handler-response.js.map +1 -1
  142. package/dist/server/app-route-handler-runtime.d.ts +9 -1
  143. package/dist/server/app-route-handler-runtime.js +11 -1
  144. package/dist/server/app-route-handler-runtime.js.map +1 -1
  145. package/dist/server/app-router-entry.js +9 -4
  146. package/dist/server/app-router-entry.js.map +1 -1
  147. package/dist/server/app-rsc-cache-busting.d.ts +34 -0
  148. package/dist/server/app-rsc-cache-busting.js +137 -0
  149. package/dist/server/app-rsc-cache-busting.js.map +1 -0
  150. package/dist/server/app-rsc-handler.js +22 -11
  151. package/dist/server/app-rsc-handler.js.map +1 -1
  152. package/dist/server/app-rsc-request-normalization.d.ts +4 -2
  153. package/dist/server/app-rsc-request-normalization.js +10 -6
  154. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  155. package/dist/server/app-rsc-response-finalizer.js +1 -1
  156. package/dist/server/app-rsc-route-matching.js +8 -4
  157. package/dist/server/app-rsc-route-matching.js.map +1 -1
  158. package/dist/server/app-segment-config.js +4 -0
  159. package/dist/server/app-segment-config.js.map +1 -1
  160. package/dist/server/app-server-action-execution.js +43 -51
  161. package/dist/server/app-server-action-execution.js.map +1 -1
  162. package/dist/server/app-ssr-entry.js +21 -20
  163. package/dist/server/app-ssr-entry.js.map +1 -1
  164. package/dist/server/artifact-compatibility.d.ts +44 -0
  165. package/dist/server/artifact-compatibility.js +82 -0
  166. package/dist/server/artifact-compatibility.js.map +1 -0
  167. package/dist/server/cache-proof.d.ts +200 -0
  168. package/dist/server/cache-proof.js +342 -0
  169. package/dist/server/cache-proof.js.map +1 -0
  170. package/dist/server/dev-origin-check.js +8 -4
  171. package/dist/server/dev-origin-check.js.map +1 -1
  172. package/dist/server/dev-server.js +1 -6
  173. package/dist/server/dev-server.js.map +1 -1
  174. package/dist/server/http-error-responses.d.ts +67 -0
  175. package/dist/server/http-error-responses.js +77 -0
  176. package/dist/server/http-error-responses.js.map +1 -0
  177. package/dist/server/image-optimization.js +2 -1
  178. package/dist/server/image-optimization.js.map +1 -1
  179. package/dist/server/metadata-route-response.js +6 -5
  180. package/dist/server/metadata-route-response.js.map +1 -1
  181. package/dist/server/metadata-routes.d.ts +1 -0
  182. package/dist/server/metadata-routes.js +6 -0
  183. package/dist/server/metadata-routes.js.map +1 -1
  184. package/dist/server/middleware-matcher.js +2 -2
  185. package/dist/server/middleware-matcher.js.map +1 -1
  186. package/dist/server/middleware-response-headers.js +21 -0
  187. package/dist/server/middleware-response-headers.js.map +1 -1
  188. package/dist/server/middleware-runtime.js +3 -3
  189. package/dist/server/middleware-runtime.js.map +1 -1
  190. package/dist/server/navigation-trace.d.ts +33 -0
  191. package/dist/server/navigation-trace.js +35 -0
  192. package/dist/server/navigation-trace.js.map +1 -0
  193. package/dist/server/next-error-digest.d.ts +44 -0
  194. package/dist/server/next-error-digest.js +40 -0
  195. package/dist/server/next-error-digest.js.map +1 -0
  196. package/dist/server/pages-api-route.js +2 -1
  197. package/dist/server/pages-api-route.js.map +1 -1
  198. package/dist/server/pages-node-compat.js +4 -16
  199. package/dist/server/pages-node-compat.js.map +1 -1
  200. package/dist/server/pages-page-response.d.ts +2 -8
  201. package/dist/server/pages-page-response.js +44 -14
  202. package/dist/server/pages-page-response.js.map +1 -1
  203. package/dist/server/prod-server.d.ts +6 -0
  204. package/dist/server/prod-server.js +28 -21
  205. package/dist/server/prod-server.js.map +1 -1
  206. package/dist/server/request-pipeline.d.ts +42 -1
  207. package/dist/server/request-pipeline.js +97 -17
  208. package/dist/server/request-pipeline.js.map +1 -1
  209. package/dist/shims/cache-runtime.d.ts +2 -2
  210. package/dist/shims/cache-runtime.js +3 -6
  211. package/dist/shims/cache-runtime.js.map +1 -1
  212. package/dist/shims/cache.js +3 -5
  213. package/dist/shims/cache.js.map +1 -1
  214. package/dist/shims/fetch-cache.js +2 -3
  215. package/dist/shims/fetch-cache.js.map +1 -1
  216. package/dist/shims/head-state.js +2 -3
  217. package/dist/shims/head-state.js.map +1 -1
  218. package/dist/shims/headers.js +4 -44
  219. package/dist/shims/headers.js.map +1 -1
  220. package/dist/shims/i18n-state.js +2 -3
  221. package/dist/shims/i18n-state.js.map +1 -1
  222. package/dist/shims/internal/als-registry.d.ts +15 -0
  223. package/dist/shims/internal/als-registry.js +55 -0
  224. package/dist/shims/internal/als-registry.js.map +1 -0
  225. package/dist/shims/internal/cookie-serialize.d.ts +46 -0
  226. package/dist/shims/internal/cookie-serialize.js +51 -0
  227. package/dist/shims/internal/cookie-serialize.js.map +1 -0
  228. package/dist/shims/link.js +31 -26
  229. package/dist/shims/link.js.map +1 -1
  230. package/dist/shims/metadata.d.ts +26 -1
  231. package/dist/shims/metadata.js +94 -4
  232. package/dist/shims/metadata.js.map +1 -1
  233. package/dist/shims/navigation-state.js +2 -3
  234. package/dist/shims/navigation-state.js.map +1 -1
  235. package/dist/shims/navigation.d.ts +2 -7
  236. package/dist/shims/navigation.js +44 -36
  237. package/dist/shims/navigation.js.map +1 -1
  238. package/dist/shims/request-context.js +2 -4
  239. package/dist/shims/request-context.js.map +1 -1
  240. package/dist/shims/router-state.js +2 -3
  241. package/dist/shims/router-state.js.map +1 -1
  242. package/dist/shims/router.js +2 -2
  243. package/dist/shims/router.js.map +1 -1
  244. package/dist/shims/server.js +5 -30
  245. package/dist/shims/server.js.map +1 -1
  246. package/dist/shims/slot.d.ts +1 -1
  247. package/dist/shims/slot.js +5 -4
  248. package/dist/shims/slot.js.map +1 -1
  249. package/dist/shims/thenable-params.d.ts +5 -2
  250. package/dist/shims/thenable-params.js +26 -6
  251. package/dist/shims/thenable-params.js.map +1 -1
  252. package/dist/shims/unified-request-context.js +2 -14
  253. package/dist/shims/unified-request-context.js.map +1 -1
  254. package/dist/utils/base-path.d.ts +7 -1
  255. package/dist/utils/base-path.js +12 -1
  256. package/dist/utils/base-path.js.map +1 -1
  257. package/dist/utils/safe-json-file.d.ts +18 -0
  258. package/dist/utils/safe-json-file.js +25 -0
  259. package/dist/utils/safe-json-file.js.map +1 -0
  260. package/dist/utils/text-stream.d.ts +29 -0
  261. package/dist/utils/text-stream.js +66 -0
  262. package/dist/utils/text-stream.js.map +1 -0
  263. package/package.json +5 -5
@@ -30,17 +30,22 @@ function createOgInlineFetchAssetsPlugin() {
30
30
  const moduleDir = path.dirname(id);
31
31
  let newCode = code;
32
32
  let didReplace = false;
33
+ const readAsBase64 = async (absPath) => {
34
+ const cached = useCache ? cache.get(absPath) : void 0;
35
+ if (cached !== void 0) return cached;
36
+ try {
37
+ const b64 = (await fs.promises.readFile(absPath)).toString("base64");
38
+ if (useCache) cache.set(absPath, b64);
39
+ return b64;
40
+ } catch {
41
+ return null;
42
+ }
43
+ };
33
44
  if (code.includes("fetch(")) for (const match of code.matchAll(/fetch\(\s*new URL\(\s*(["'])(\.\/[^"']+)\1\s*,\s*import\.meta\.url\s*\)\s*\)(?:\.then\(\s*(?:function\s*\([^)]*\)|\([^)]*\)\s*=>)\s*\{?\s*return\s+[^.]+\.arrayBuffer\(\)\s*\}?\s*\)|\.then\(\s*\([^)]*\)\s*=>\s*[^.]+\.arrayBuffer\(\)\s*\))/g)) {
34
45
  const fullMatch = match[0];
35
46
  const relPath = match[2];
36
- const absPath = path.resolve(moduleDir, relPath);
37
- let fileBase64 = useCache ? cache.get(absPath) : void 0;
38
- if (fileBase64 === void 0) try {
39
- fileBase64 = (await fs.promises.readFile(absPath)).toString("base64");
40
- if (useCache) cache.set(absPath, fileBase64);
41
- } catch {
42
- continue;
43
- }
47
+ const fileBase64 = await readAsBase64(path.resolve(moduleDir, relPath));
48
+ if (fileBase64 === null) continue;
44
49
  const inlined = [
45
50
  `(function(){`,
46
51
  `var b=${JSON.stringify(fileBase64)};`,
@@ -56,14 +61,8 @@ function createOgInlineFetchAssetsPlugin() {
56
61
  if (code.includes("readFileSync(")) for (const match of newCode.matchAll(/[a-zA-Z_$][a-zA-Z0-9_$]*\.readFileSync\(\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\.)?fileURLToPath\(\s*new URL\(\s*(["'])(\.\/[^"']+)\1\s*,\s*import\.meta\.url\s*\)\s*\)\s*\)/g)) {
57
62
  const fullMatch = match[0];
58
63
  const relPath = match[2];
59
- const absPath = path.resolve(moduleDir, relPath);
60
- let fileBase64 = useCache ? cache.get(absPath) : void 0;
61
- if (fileBase64 === void 0) try {
62
- fileBase64 = (await fs.promises.readFile(absPath)).toString("base64");
63
- if (useCache) cache.set(absPath, fileBase64);
64
- } catch {
65
- continue;
66
- }
64
+ const fileBase64 = await readAsBase64(path.resolve(moduleDir, relPath));
65
+ if (fileBase64 === null) continue;
67
66
  const inlined = `Buffer.from(${JSON.stringify(fileBase64)},"base64")`;
68
67
  newCode = newCode.replaceAll(fullMatch, inlined);
69
68
  didReplace = true;
@@ -1 +1 @@
1
- {"version":3,"file":"og-assets.js","names":[],"sources":["../../src/plugins/og-assets.ts"],"sourcesContent":["/**\n * vinext OG image asset plugins\n *\n * Exports two Vite plugins:\n *\n * `createOgInlineFetchAssetsPlugin` — vinext:og-inline-fetch-assets\n * Some bundled libraries (notably @vercel/og) load assets at module init\n * time with the pattern:\n *\n * fetch(new URL(\"./some-font.ttf\", import.meta.url)).then(res => res.arrayBuffer())\n *\n * This works in browser and standard Node.js because import.meta.url is a\n * real file:// URL. In Cloudflare Workers (both wrangler dev and production),\n * however, import.meta.url is the string \"worker\" — not a URL — so\n * new URL(...) throws \"TypeError: Invalid URL string\" and the Worker fails to\n * start.\n *\n * Fix: at Vite transform time, find every such pattern, resolve the referenced\n * file relative to the module's actual path on disk (available as `id`), read\n * it, and replace the entire fetch(new URL(...)) expression with an inline\n * base64 IIFE that resolves synchronously. This eliminates the runtime fetch\n * entirely and works in all environments (workerd, Node.js, browser).\n *\n * Note: WASM files imported via `import ... from \"./foo.wasm?module\"` are\n * handled by the bundler/Vite directly and do not need this treatment. Only\n * assets that are runtime-fetched (not statically imported) need inlining.\n *\n * `ogAssetsPlugin` — vinext:og-assets\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * og-inline-fetch-assets; this plugin is a safety net to ensure resvg.wasm is\n * present for the Node.js disk-read fallback.\n */\n\nimport type { Plugin } from \"vite\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\n\n// ── Plugin factories ──────────────────────────────────────────────────────────\n\n/**\n * Create the `vinext:og-inline-fetch-assets` Vite plugin.\n *\n * Inlines binary assets that are runtime-fetched via\n * `fetch(new URL(\"./asset\", import.meta.url))` or read via\n * `readFileSync(fileURLToPath(new URL(\"./asset\", import.meta.url)))`.\n * Both patterns are rewritten to inline base64 literals so the code works\n * correctly inside Cloudflare Workers where `import.meta.url` is not a\n * valid file URL.\n */\nexport function createOgInlineFetchAssetsPlugin(): Plugin {\n // Build-only cache to avoid repeated file reads during a single production\n // build. Dev mode skips the cache so asset edits are picked up without\n // restarting the Vite server.\n const cache = new Map<string, string>(); // absPath -> base64\n let isBuild = false;\n\n return {\n name: \"vinext:og-inline-fetch-assets\",\n enforce: \"pre\",\n\n configResolved(config) {\n isBuild = config.command === \"build\";\n },\n\n buildStart() {\n if (isBuild) {\n cache.clear();\n }\n },\n\n async transform(code, id) {\n // Quick bail-out: only process modules that use new URL(..., import.meta.url)\n if (!code.includes(\"import.meta.url\")) {\n return null;\n }\n\n const useCache = isBuild;\n const moduleDir = path.dirname(id);\n let newCode = code;\n let didReplace = false;\n\n // Pattern 1 — edge build: fetch(new URL(\"./file\", import.meta.url)).then((res) => res.arrayBuffer())\n // Replace with an inline IIFE that decodes the asset as base64 and returns Promise<ArrayBuffer>.\n if (code.includes(\"fetch(\")) {\n const fetchPattern =\n /fetch\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)(?:\\.then\\(\\s*(?:function\\s*\\([^)]*\\)|\\([^)]*\\)\\s*=>)\\s*\\{?\\s*return\\s+[^.]+\\.arrayBuffer\\(\\)\\s*\\}?\\s*\\)|\\.then\\(\\s*\\([^)]*\\)\\s*=>\\s*[^.]+\\.arrayBuffer\\(\\)\\s*\\))/g;\n\n for (const match of code.matchAll(fetchPattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n let fileBase64 = useCache ? cache.get(absPath) : undefined;\n if (fileBase64 === undefined) {\n try {\n const buf = await fs.promises.readFile(absPath);\n fileBase64 = buf.toString(\"base64\");\n if (useCache) {\n cache.set(absPath, fileBase64);\n }\n } catch {\n // File not found on disk — skip (may be a runtime-only asset)\n continue;\n }\n }\n\n // Replace fetch(...).then(...) with an inline IIFE that returns Promise<ArrayBuffer>.\n const inlined = [\n `(function(){`,\n `var b=${JSON.stringify(fileBase64)};`,\n `var r=atob(b);`,\n `var a=new Uint8Array(r.length);`,\n `for(var i=0;i<r.length;i++)a[i]=r.charCodeAt(i);`,\n `return Promise.resolve(a.buffer);`,\n `})()`,\n ].join(\"\");\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n // Pattern 2 — node build: readFileSync(fileURLToPath(new URL(\"./file\", import.meta.url)))\n // Replace with Buffer.from(\"<base64>\", \"base64\"), which returns a Buffer (compatible with\n // both font data passed to satori and WASM bytes passed to initWasm).\n if (code.includes(\"readFileSync(\")) {\n const readFilePattern =\n /[a-zA-Z_$][a-zA-Z0-9_$]*\\.readFileSync\\(\\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\\.)?fileURLToPath\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)\\s*\\)/g;\n\n for (const match of newCode.matchAll(readFilePattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n let fileBase64 = useCache ? cache.get(absPath) : undefined;\n if (fileBase64 === undefined) {\n try {\n const buf = await fs.promises.readFile(absPath);\n fileBase64 = buf.toString(\"base64\");\n if (useCache) {\n cache.set(absPath, fileBase64);\n }\n } catch {\n // File not found on disk — skip\n continue;\n }\n }\n\n // Replace readFileSync(...) with Buffer.from(\"<base64>\", \"base64\").\n // Buffer is always available in Node.js and in the vinext SSR/RSC environments.\n const inlined = `Buffer.from(${JSON.stringify(fileBase64)},\"base64\")`;\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n if (!didReplace) return null;\n return { code: newCode, map: null };\n },\n } satisfies Plugin;\n}\n\n/**\n * The `vinext:og-assets` Vite plugin.\n *\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * `vinext:og-inline-fetch-assets`; this plugin is a safety net to ensure\n * resvg.wasm exists in the output directory for the Node.js disk-read fallback.\n */\nexport const ogAssetsPlugin: Plugin = {\n name: \"vinext:og-assets\",\n apply: \"build\",\n enforce: \"post\",\n writeBundle: {\n sequential: true,\n order: \"post\",\n async handler(options) {\n const envName = this.environment?.name;\n if (envName !== \"rsc\") return;\n\n const outDir = options.dir;\n if (!outDir) return;\n\n // Check if the bundle references @vercel/og assets\n const indexPath = path.join(outDir, \"index.js\");\n if (!fs.existsSync(indexPath)) return;\n\n const content = fs.readFileSync(indexPath, \"utf-8\");\n // The font is inlined as base64 by vinext:og-inline-fetch-assets, so only\n // the WASM needs to be present as a file alongside the bundle.\n const ogAssets = [\"resvg.wasm\"];\n\n // Only copy if the bundle actually references these files\n const referencedAssets = ogAssets.filter((asset) => content.includes(asset));\n if (referencedAssets.length === 0) return;\n\n // Find @vercel/og in node_modules\n try {\n const require = createRequire(import.meta.url);\n const ogPkgPath = require.resolve(\"@vercel/og/package.json\");\n const ogDistDir = path.join(path.dirname(ogPkgPath), \"dist\");\n\n for (const asset of referencedAssets) {\n const src = path.join(ogDistDir, asset);\n const dest = path.join(outDir, asset);\n if (fs.existsSync(src) && !fs.existsSync(dest)) {\n fs.copyFileSync(src, dest);\n }\n }\n } catch {\n // @vercel/og not installed — nothing to copy\n }\n },\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAmDA,SAAgB,kCAA0C;CAIxD,MAAM,wBAAQ,IAAI,KAAqB;CACvC,IAAI,UAAU;AAEd,QAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAQ;AACrB,aAAU,OAAO,YAAY;;EAG/B,aAAa;AACX,OAAI,QACF,OAAM,OAAO;;EAIjB,MAAM,UAAU,MAAM,IAAI;AAExB,OAAI,CAAC,KAAK,SAAS,kBAAkB,CACnC,QAAO;GAGT,MAAM,WAAW;GACjB,MAAM,YAAY,KAAK,QAAQ,GAAG;GAClC,IAAI,UAAU;GACd,IAAI,aAAa;AAIjB,OAAI,KAAK,SAAS,SAAS,CAIzB,MAAK,MAAM,SAAS,KAAK,SAFvB,iPAE6C,EAAE;IAC/C,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IACtB,MAAM,UAAU,KAAK,QAAQ,WAAW,QAAQ;IAEhD,IAAI,aAAa,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AACjD,QAAI,eAAe,KAAA,EACjB,KAAI;AAEF,mBADY,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC9B,SAAS,SAAS;AACnC,SAAI,SACF,OAAM,IAAI,SAAS,WAAW;YAE1B;AAEN;;IAKJ,MAAM,UAAU;KACd;KACA,SAAS,KAAK,UAAU,WAAW,CAAC;KACpC;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;AAEV,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAOjB,OAAI,KAAK,SAAS,gBAAgB,CAIhC,MAAK,MAAM,SAAS,QAAQ,SAF1B,uKAEmD,EAAE;IACrD,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IACtB,MAAM,UAAU,KAAK,QAAQ,WAAW,QAAQ;IAEhD,IAAI,aAAa,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AACjD,QAAI,eAAe,KAAA,EACjB,KAAI;AAEF,mBADY,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC9B,SAAS,SAAS;AACnC,SAAI,SACF,OAAM,IAAI,SAAS,WAAW;YAE1B;AAEN;;IAMJ,MAAM,UAAU,eAAe,KAAK,UAAU,WAAW,CAAC;AAE1D,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAIjB,OAAI,CAAC,WAAY,QAAO;AACxB,UAAO;IAAE,MAAM;IAAS,KAAK;IAAM;;EAEtC;;;;;;;;;;AAWH,MAAa,iBAAyB;CACpC,MAAM;CACN,OAAO;CACP,SAAS;CACT,aAAa;EACX,YAAY;EACZ,OAAO;EACP,MAAM,QAAQ,SAAS;AAErB,OADgB,KAAK,aAAa,SAClB,MAAO;GAEvB,MAAM,SAAS,QAAQ;AACvB,OAAI,CAAC,OAAQ;GAGb,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW;AAC/C,OAAI,CAAC,GAAG,WAAW,UAAU,CAAE;GAE/B,MAAM,UAAU,GAAG,aAAa,WAAW,QAAQ;GAMnD,MAAM,mBAHW,CAAC,aAAa,CAGG,QAAQ,UAAU,QAAQ,SAAS,MAAM,CAAC;AAC5E,OAAI,iBAAiB,WAAW,EAAG;AAGnC,OAAI;IAEF,MAAM,YADU,cAAc,OAAO,KAAK,IAAI,CACpB,QAAQ,0BAA0B;IAC5D,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,UAAU,EAAE,OAAO;AAE5D,SAAK,MAAM,SAAS,kBAAkB;KACpC,MAAM,MAAM,KAAK,KAAK,WAAW,MAAM;KACvC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrC,SAAI,GAAG,WAAW,IAAI,IAAI,CAAC,GAAG,WAAW,KAAK,CAC5C,IAAG,aAAa,KAAK,KAAK;;WAGxB;;EAIX;CACF"}
1
+ {"version":3,"file":"og-assets.js","names":[],"sources":["../../src/plugins/og-assets.ts"],"sourcesContent":["/**\n * vinext OG image asset plugins\n *\n * Exports two Vite plugins:\n *\n * `createOgInlineFetchAssetsPlugin` — vinext:og-inline-fetch-assets\n * Some bundled libraries (notably @vercel/og) load assets at module init\n * time with the pattern:\n *\n * fetch(new URL(\"./some-font.ttf\", import.meta.url)).then(res => res.arrayBuffer())\n *\n * This works in browser and standard Node.js because import.meta.url is a\n * real file:// URL. In Cloudflare Workers (both wrangler dev and production),\n * however, import.meta.url is the string \"worker\" — not a URL — so\n * new URL(...) throws \"TypeError: Invalid URL string\" and the Worker fails to\n * start.\n *\n * Fix: at Vite transform time, find every such pattern, resolve the referenced\n * file relative to the module's actual path on disk (available as `id`), read\n * it, and replace the entire fetch(new URL(...)) expression with an inline\n * base64 IIFE that resolves synchronously. This eliminates the runtime fetch\n * entirely and works in all environments (workerd, Node.js, browser).\n *\n * Note: WASM files imported via `import ... from \"./foo.wasm?module\"` are\n * handled by the bundler/Vite directly and do not need this treatment. Only\n * assets that are runtime-fetched (not statically imported) need inlining.\n *\n * `ogAssetsPlugin` — vinext:og-assets\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * og-inline-fetch-assets; this plugin is a safety net to ensure resvg.wasm is\n * present for the Node.js disk-read fallback.\n */\n\nimport type { Plugin } from \"vite\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\n\n// ── Plugin factories ──────────────────────────────────────────────────────────\n\n/**\n * Create the `vinext:og-inline-fetch-assets` Vite plugin.\n *\n * Inlines binary assets that are runtime-fetched via\n * `fetch(new URL(\"./asset\", import.meta.url))` or read via\n * `readFileSync(fileURLToPath(new URL(\"./asset\", import.meta.url)))`.\n * Both patterns are rewritten to inline base64 literals so the code works\n * correctly inside Cloudflare Workers where `import.meta.url` is not a\n * valid file URL.\n */\nexport function createOgInlineFetchAssetsPlugin(): Plugin {\n // Build-only cache to avoid repeated file reads during a single production\n // build. Dev mode skips the cache so asset edits are picked up without\n // restarting the Vite server.\n const cache = new Map<string, string>(); // absPath -> base64\n let isBuild = false;\n\n return {\n name: \"vinext:og-inline-fetch-assets\",\n enforce: \"pre\",\n\n configResolved(config) {\n isBuild = config.command === \"build\";\n },\n\n buildStart() {\n if (isBuild) {\n cache.clear();\n }\n },\n\n async transform(code, id) {\n // Quick bail-out: only process modules that use new URL(..., import.meta.url)\n if (!code.includes(\"import.meta.url\")) {\n return null;\n }\n\n const useCache = isBuild;\n const moduleDir = path.dirname(id);\n let newCode = code;\n let didReplace = false;\n\n // Read a file from disk and return its base64 encoding, using the build\n // cache when enabled. Returns null on any read error so callers can skip\n // the match (e.g. file not present on disk for the active environment).\n const readAsBase64 = async (absPath: string): Promise<string | null> => {\n const cached = useCache ? cache.get(absPath) : undefined;\n if (cached !== undefined) return cached;\n try {\n const buf = await fs.promises.readFile(absPath);\n const b64 = buf.toString(\"base64\");\n if (useCache) cache.set(absPath, b64);\n return b64;\n } catch {\n return null;\n }\n };\n\n // Pattern 1 — edge build: fetch(new URL(\"./file\", import.meta.url)).then((res) => res.arrayBuffer())\n // Replace with an inline IIFE that decodes the asset as base64 and returns Promise<ArrayBuffer>.\n if (code.includes(\"fetch(\")) {\n const fetchPattern =\n /fetch\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)(?:\\.then\\(\\s*(?:function\\s*\\([^)]*\\)|\\([^)]*\\)\\s*=>)\\s*\\{?\\s*return\\s+[^.]+\\.arrayBuffer\\(\\)\\s*\\}?\\s*\\)|\\.then\\(\\s*\\([^)]*\\)\\s*=>\\s*[^.]+\\.arrayBuffer\\(\\)\\s*\\))/g;\n\n for (const match of code.matchAll(fetchPattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n const fileBase64 = await readAsBase64(absPath);\n if (fileBase64 === null) continue; // may be a runtime-only asset\n\n // Replace fetch(...).then(...) with an inline IIFE that returns Promise<ArrayBuffer>.\n const inlined = [\n `(function(){`,\n `var b=${JSON.stringify(fileBase64)};`,\n `var r=atob(b);`,\n `var a=new Uint8Array(r.length);`,\n `for(var i=0;i<r.length;i++)a[i]=r.charCodeAt(i);`,\n `return Promise.resolve(a.buffer);`,\n `})()`,\n ].join(\"\");\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n // Pattern 2 — node build: readFileSync(fileURLToPath(new URL(\"./file\", import.meta.url)))\n // Replace with Buffer.from(\"<base64>\", \"base64\"), which returns a Buffer (compatible with\n // both font data passed to satori and WASM bytes passed to initWasm).\n if (code.includes(\"readFileSync(\")) {\n const readFilePattern =\n /[a-zA-Z_$][a-zA-Z0-9_$]*\\.readFileSync\\(\\s*(?:[a-zA-Z_$][a-zA-Z0-9_$]*\\.)?fileURLToPath\\(\\s*new URL\\(\\s*([\"'])(\\.\\/[^\"']+)\\1\\s*,\\s*import\\.meta\\.url\\s*\\)\\s*\\)\\s*\\)/g;\n\n for (const match of newCode.matchAll(readFilePattern)) {\n const fullMatch = match[0];\n const relPath = match[2]; // e.g. \"./noto-sans-v27-latin-regular.ttf\"\n const absPath = path.resolve(moduleDir, relPath);\n\n const fileBase64 = await readAsBase64(absPath);\n if (fileBase64 === null) continue;\n\n // Replace readFileSync(...) with Buffer.from(\"<base64>\", \"base64\").\n // Buffer is always available in Node.js and in the vinext SSR/RSC environments.\n const inlined = `Buffer.from(${JSON.stringify(fileBase64)},\"base64\")`;\n\n newCode = newCode.replaceAll(fullMatch, inlined);\n didReplace = true;\n }\n }\n\n if (!didReplace) return null;\n return { code: newCode, map: null };\n },\n } satisfies Plugin;\n}\n\n/**\n * The `vinext:og-assets` Vite plugin.\n *\n * Copies @vercel/og binary assets (e.g. resvg.wasm) to the RSC output\n * directory for production builds. The edge build inlines fonts as base64 via\n * `vinext:og-inline-fetch-assets`; this plugin is a safety net to ensure\n * resvg.wasm exists in the output directory for the Node.js disk-read fallback.\n */\nexport const ogAssetsPlugin: Plugin = {\n name: \"vinext:og-assets\",\n apply: \"build\",\n enforce: \"post\",\n writeBundle: {\n sequential: true,\n order: \"post\",\n async handler(options) {\n const envName = this.environment?.name;\n if (envName !== \"rsc\") return;\n\n const outDir = options.dir;\n if (!outDir) return;\n\n // Check if the bundle references @vercel/og assets\n const indexPath = path.join(outDir, \"index.js\");\n if (!fs.existsSync(indexPath)) return;\n\n const content = fs.readFileSync(indexPath, \"utf-8\");\n // The font is inlined as base64 by vinext:og-inline-fetch-assets, so only\n // the WASM needs to be present as a file alongside the bundle.\n const ogAssets = [\"resvg.wasm\"];\n\n // Only copy if the bundle actually references these files\n const referencedAssets = ogAssets.filter((asset) => content.includes(asset));\n if (referencedAssets.length === 0) return;\n\n // Find @vercel/og in node_modules\n try {\n const require = createRequire(import.meta.url);\n const ogPkgPath = require.resolve(\"@vercel/og/package.json\");\n const ogDistDir = path.join(path.dirname(ogPkgPath), \"dist\");\n\n for (const asset of referencedAssets) {\n const src = path.join(ogDistDir, asset);\n const dest = path.join(outDir, asset);\n if (fs.existsSync(src) && !fs.existsSync(dest)) {\n fs.copyFileSync(src, dest);\n }\n }\n } catch {\n // @vercel/og not installed — nothing to copy\n }\n },\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAmDA,SAAgB,kCAA0C;CAIxD,MAAM,wBAAQ,IAAI,KAAqB;CACvC,IAAI,UAAU;AAEd,QAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAQ;AACrB,aAAU,OAAO,YAAY;;EAG/B,aAAa;AACX,OAAI,QACF,OAAM,OAAO;;EAIjB,MAAM,UAAU,MAAM,IAAI;AAExB,OAAI,CAAC,KAAK,SAAS,kBAAkB,CACnC,QAAO;GAGT,MAAM,WAAW;GACjB,MAAM,YAAY,KAAK,QAAQ,GAAG;GAClC,IAAI,UAAU;GACd,IAAI,aAAa;GAKjB,MAAM,eAAe,OAAO,YAA4C;IACtE,MAAM,SAAS,WAAW,MAAM,IAAI,QAAQ,GAAG,KAAA;AAC/C,QAAI,WAAW,KAAA,EAAW,QAAO;AACjC,QAAI;KAEF,MAAM,OADM,MAAM,GAAG,SAAS,SAAS,QAAQ,EAC/B,SAAS,SAAS;AAClC,SAAI,SAAU,OAAM,IAAI,SAAS,IAAI;AACrC,YAAO;YACD;AACN,YAAO;;;AAMX,OAAI,KAAK,SAAS,SAAS,CAIzB,MAAK,MAAM,SAAS,KAAK,SAFvB,iPAE6C,EAAE;IAC/C,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IAGtB,MAAM,aAAa,MAAM,aAFT,KAAK,QAAQ,WAAW,QAAQ,CAEF;AAC9C,QAAI,eAAe,KAAM;IAGzB,MAAM,UAAU;KACd;KACA,SAAS,KAAK,UAAU,WAAW,CAAC;KACpC;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,GAAG;AAEV,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAOjB,OAAI,KAAK,SAAS,gBAAgB,CAIhC,MAAK,MAAM,SAAS,QAAQ,SAF1B,uKAEmD,EAAE;IACrD,MAAM,YAAY,MAAM;IACxB,MAAM,UAAU,MAAM;IAGtB,MAAM,aAAa,MAAM,aAFT,KAAK,QAAQ,WAAW,QAAQ,CAEF;AAC9C,QAAI,eAAe,KAAM;IAIzB,MAAM,UAAU,eAAe,KAAK,UAAU,WAAW,CAAC;AAE1D,cAAU,QAAQ,WAAW,WAAW,QAAQ;AAChD,iBAAa;;AAIjB,OAAI,CAAC,WAAY,QAAO;AACxB,UAAO;IAAE,MAAM;IAAS,KAAK;IAAM;;EAEtC;;;;;;;;;;AAWH,MAAa,iBAAyB;CACpC,MAAM;CACN,OAAO;CACP,SAAS;CACT,aAAa;EACX,YAAY;EACZ,OAAO;EACP,MAAM,QAAQ,SAAS;AAErB,OADgB,KAAK,aAAa,SAClB,MAAO;GAEvB,MAAM,SAAS,QAAQ;AACvB,OAAI,CAAC,OAAQ;GAGb,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW;AAC/C,OAAI,CAAC,GAAG,WAAW,UAAU,CAAE;GAE/B,MAAM,UAAU,GAAG,aAAa,WAAW,QAAQ;GAMnD,MAAM,mBAHW,CAAC,aAAa,CAGG,QAAQ,UAAU,QAAQ,SAAS,MAAM,CAAC;AAC5E,OAAI,iBAAiB,WAAW,EAAG;AAGnC,OAAI;IAEF,MAAM,YADU,cAAc,OAAO,KAAK,IAAI,CACpB,QAAQ,0BAA0B;IAC5D,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,UAAU,EAAE,OAAO;AAE5D,SAAK,MAAM,SAAS,kBAAkB;KACpC,MAAM,MAAM,KAAK,KAAK,WAAW,MAAM;KACvC,MAAM,OAAO,KAAK,KAAK,QAAQ,MAAM;AACrC,SAAI,GAAG,WAAW,IAAI,IAAI,CAAC,GAAG,WAAW,KAAK,CAC5C,IAAG,aAAa,KAAK,KAAK;;WAGxB;;EAIX;CACF"}
@@ -1,6 +1,7 @@
1
1
  //#region src/plugins/rsc-client-shim-excludes.d.ts
2
2
  declare const VINEXT_OPTIMIZE_DEPS_EXCLUDE: readonly string[];
3
+ declare const SSR_EXTERNAL_REACT_ENTRIES: readonly string[];
3
4
  declare function mergeOptimizeDepsExclude(...excludeGroups: readonly (readonly string[])[]): string[];
4
5
  //#endregion
5
- export { VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
6
+ export { SSR_EXTERNAL_REACT_ENTRIES, VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
6
7
  //# sourceMappingURL=rsc-client-shim-excludes.d.ts.map
@@ -14,6 +14,15 @@ const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([
14
14
  "private-next-instrumentation-client",
15
15
  ...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE
16
16
  ]);
17
+ const SSR_EXTERNAL_REACT_ENTRIES = Object.freeze([
18
+ "react",
19
+ "react-dom",
20
+ "react-dom/server.edge",
21
+ "react-dom/static.edge",
22
+ "react/jsx-runtime",
23
+ "react/jsx-dev-runtime",
24
+ "react-server-dom-webpack/client.edge"
25
+ ]);
17
26
  function mergeOptimizeDepsExclude(...excludeGroups) {
18
27
  const seen = /* @__PURE__ */ new Set();
19
28
  for (const group of excludeGroups) for (const entry of group) {
@@ -23,6 +32,6 @@ function mergeOptimizeDepsExclude(...excludeGroups) {
23
32
  return [...seen];
24
33
  }
25
34
  //#endregion
26
- export { VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
35
+ export { SSR_EXTERNAL_REACT_ENTRIES, VINEXT_OPTIMIZE_DEPS_EXCLUDE, mergeOptimizeDepsExclude };
27
36
 
28
37
  //# sourceMappingURL=rsc-client-shim-excludes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-client-shim-excludes.js","names":[],"sources":["../../src/plugins/rsc-client-shim-excludes.ts"],"sourcesContent":["const RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n // @vitejs/plugin-rsc tracks package client references by the original\n // bare source. If Vite pre-bundles these known client shims, the generated\n // client-package proxy can lose the matching export metadata in dev.\n \"vinext/shims/error-boundary\",\n \"vinext/shims/form\",\n \"vinext/shims/layout-segment-context\",\n \"vinext/shims/link\",\n \"vinext/shims/script\",\n \"vinext/shims/slot\",\n \"vinext/shims/offline\",\n]);\n\nexport const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n \"vinext\",\n \"@vercel/og\",\n // Aliased to the user's instrumentation-client source file (or an empty\n // shim). Not a real npm dep, so pre-bundling it would break HMR and cause\n // a \"new dependencies optimized\" reload on the first request.\n \"private-next-instrumentation-client\",\n ...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE,\n]);\n\nexport function mergeOptimizeDepsExclude(\n ...excludeGroups: readonly (readonly string[])[]\n): string[] {\n const seen = new Set<string>();\n\n for (const group of excludeGroups) {\n for (const entry of group) {\n if (seen.has(entry)) continue;\n seen.add(entry);\n }\n }\n\n return [...seen];\n}\n"],"mappings":";AAAA,MAAM,wCAAwC,OAAO,OAAO;CAI1D;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,+BAA+B,OAAO,OAAO;CACxD;CACA;CAIA;CACA,GAAG;CACJ,CAAC;AAEF,SAAgB,yBACd,GAAG,eACO;CACV,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,cAClB,MAAK,MAAM,SAAS,OAAO;AACzB,MAAI,KAAK,IAAI,MAAM,CAAE;AACrB,OAAK,IAAI,MAAM;;AAInB,QAAO,CAAC,GAAG,KAAK"}
1
+ {"version":3,"file":"rsc-client-shim-excludes.js","names":[],"sources":["../../src/plugins/rsc-client-shim-excludes.ts"],"sourcesContent":["const RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n // @vitejs/plugin-rsc tracks package client references by the original\n // bare source. If Vite pre-bundles these known client shims, the generated\n // client-package proxy can lose the matching export metadata in dev.\n \"vinext/shims/error-boundary\",\n \"vinext/shims/form\",\n \"vinext/shims/layout-segment-context\",\n \"vinext/shims/link\",\n \"vinext/shims/script\",\n \"vinext/shims/slot\",\n \"vinext/shims/offline\",\n]);\n\nexport const VINEXT_OPTIMIZE_DEPS_EXCLUDE = Object.freeze([\n \"vinext\",\n \"@vercel/og\",\n // Aliased to the user's instrumentation-client source file (or an empty\n // shim). Not a real npm dep, so pre-bundling it would break HMR and cause\n // a \"new dependencies optimized\" reload on the first request.\n \"private-next-instrumentation-client\",\n ...RSC_CLIENT_SHIM_OPTIMIZE_DEPS_EXCLUDE,\n]);\n\n// React entries that @vitejs/plugin-rsc adds to environments.ssr.optimizeDeps.include\n// via crawlFrameworkPkgs. When the user sets ssr.external: true, the SSR env loads\n// everything via Node's resolver (including React from /node_modules/react). If Vite\n// also pre-bundles React into deps_ssr/, two distinct React module records coexist:\n// react-dom-server.edge sets the dispatcher on its bundled React, but externalized\n// callers (vinext's runtime, and 'use client' modules going through the SSR transform)\n// see a different React → React.H is null → useContext / useSyncExternalStore crash.\n// Adding these to optimizeDeps.exclude keeps deps_ssr/ React-free so the runtime and\n// the renderer share a single Node-loaded React copy.\nexport const SSR_EXTERNAL_REACT_ENTRIES = Object.freeze([\n \"react\",\n \"react-dom\",\n \"react-dom/server.edge\",\n \"react-dom/static.edge\",\n \"react/jsx-runtime\",\n \"react/jsx-dev-runtime\",\n \"react-server-dom-webpack/client.edge\",\n]);\n\nexport function mergeOptimizeDepsExclude(\n ...excludeGroups: readonly (readonly string[])[]\n): string[] {\n const seen = new Set<string>();\n\n for (const group of excludeGroups) {\n for (const entry of group) {\n if (seen.has(entry)) continue;\n seen.add(entry);\n }\n }\n\n return [...seen];\n}\n"],"mappings":";AAAA,MAAM,wCAAwC,OAAO,OAAO;CAI1D;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,+BAA+B,OAAO,OAAO;CACxD;CACA;CAIA;CACA,GAAG;CACJ,CAAC;AAWF,MAAa,6BAA6B,OAAO,OAAO;CACtD;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,yBACd,GAAG,eACO;CACV,MAAM,uBAAO,IAAI,KAAa;AAE9B,MAAK,MAAM,SAAS,cAClB,MAAK,MAAM,SAAS,OAAO;AACzB,MAAI,KAAK,IAAI,MAAM,CAAE;AACrB,OAAK,IAAI,MAAM;;AAInB,QAAO,CAAC,GAAG,KAAK"}
@@ -9,7 +9,8 @@ type InterceptingRoute = {
9
9
  params: string[];
10
10
  };
11
11
  type ParallelSlot = {
12
- /** Stable slot identity (name + owning directory), used for route serialization keys. */key: string; /** Slot name (e.g. "team" from @team) */
12
+ /** Graph-owned semantic slot identity. Required on AppRouteGraphParallelSlot. */id?: string; /** Stable slot identity (name + owning directory), used for route serialization keys. */
13
+ key: string; /** Slot name (e.g. "team" from @team) */
13
14
  name: string; /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */
14
15
  ownerDir: string; /** Absolute path to the slot's page component */
15
16
  pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
@@ -47,7 +48,8 @@ type ParallelSlot = {
47
48
  slotParamNames?: string[];
48
49
  };
49
50
  type AppRoute = {
50
- /** URL pattern, e.g. "/" or "/about" or "/blog/:slug" */pattern: string; /** Absolute file path to the page component */
51
+ /** Graph-owned semantic identities. Required on AppRouteGraphRoute. */ids?: AppRouteSemanticIds; /** URL pattern, e.g. "/" or "/about" or "/blog/:slug" */
52
+ pattern: string; /** Absolute file path to the page component */
51
53
  pagePath: string | null; /** Absolute file path to the route handler (route.ts) */
52
54
  routePath: string | null; /** Ordered list of layout files from root to leaf */
53
55
  layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
@@ -100,10 +102,94 @@ type AppRoute = {
100
102
  rootParamNames?: string[]; /** Pre-split pattern segments (computed once at scan time, reused per request) */
101
103
  patternParts: string[];
102
104
  };
105
+ type AppRouteSemanticIds = {
106
+ route: string;
107
+ page: string | null;
108
+ routeHandler: string | null;
109
+ rootBoundary: RootBoundaryId | null;
110
+ layouts: readonly string[];
111
+ templates: readonly string[];
112
+ /**
113
+ * Bridge map for the current route metadata shape: keyed by `slot.key`
114
+ * (`name@relative/path` infrastructure id), value is the graph-owned semantic slot id.
115
+ */
116
+ slots: Readonly<Record<string, string>>;
117
+ };
118
+ type AppRouteGraphParallelSlot = ParallelSlot & {
119
+ id: string;
120
+ };
121
+ type AppRouteGraphRoute = Omit<AppRoute, "ids" | "parallelSlots" | "rootParamNames"> & {
122
+ ids: AppRouteSemanticIds;
123
+ parallelSlots: AppRouteGraphParallelSlot[];
124
+ rootParamNames: string[];
125
+ };
126
+ type Flavor<T, Brand extends string> = T & {
127
+ readonly __flavor?: Brand;
128
+ };
129
+ type GraphVersion = Flavor<string, "GraphVersion">;
130
+ type RootBoundaryId = Flavor<string, "RootBoundaryId">;
131
+ type RouteManifestRoute = {
132
+ id: string;
133
+ pattern: string;
134
+ patternParts: readonly string[];
135
+ isDynamic: boolean;
136
+ paramNames: readonly string[];
137
+ rootParamNames: readonly string[];
138
+ rootBoundaryId: RootBoundaryId | null;
139
+ pageId: string | null;
140
+ routeHandlerId: string | null;
141
+ layoutIds: readonly string[];
142
+ templateIds: readonly string[];
143
+ slotIds: readonly string[];
144
+ };
145
+ type RouteManifestPage = {
146
+ id: string;
147
+ routeId: string;
148
+ pattern: string;
149
+ };
150
+ type RouteManifestRouteHandler = {
151
+ id: string;
152
+ routeId: string;
153
+ pattern: string;
154
+ };
155
+ type RouteManifestLayout = {
156
+ id: string;
157
+ treePath: string;
158
+ rootBoundaryId: RootBoundaryId | null;
159
+ };
160
+ type RouteManifestTemplate = {
161
+ id: string;
162
+ treePath: string;
163
+ rootBoundaryId: RootBoundaryId | null;
164
+ };
165
+ type RouteManifestSlot = {
166
+ id: string;
167
+ key: string;
168
+ name: string;
169
+ };
170
+ type RouteManifestRootBoundary = {
171
+ id: RootBoundaryId;
172
+ layoutId: string;
173
+ treePath: string;
174
+ };
175
+ type StaticSegmentGraph = {
176
+ routes: ReadonlyMap<string, RouteManifestRoute>;
177
+ pages: ReadonlyMap<string, RouteManifestPage>;
178
+ routeHandlers: ReadonlyMap<string, RouteManifestRouteHandler>;
179
+ layouts: ReadonlyMap<string, RouteManifestLayout>;
180
+ templates: ReadonlyMap<string, RouteManifestTemplate>;
181
+ slots: ReadonlyMap<string, RouteManifestSlot>;
182
+ rootBoundaries: ReadonlyMap<RootBoundaryId, RouteManifestRootBoundary>;
183
+ };
184
+ type RouteManifest = {
185
+ graphVersion: GraphVersion;
186
+ segmentGraph: StaticSegmentGraph;
187
+ };
103
188
  declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
104
- routes: AppRoute[];
189
+ routes: AppRouteGraphRoute[];
190
+ routeManifest: RouteManifest;
105
191
  }>;
106
192
  declare function computeRootParamNames(routeSegments: readonly string[], layoutTreePositions: readonly number[]): string[];
107
193
  //#endregion
108
- export { AppRoute, InterceptingRoute, ParallelSlot, buildAppRouteGraph, computeRootParamNames };
194
+ export { AppRoute, AppRouteGraphParallelSlot, AppRouteGraphRoute, AppRouteSemanticIds, GraphVersion, InterceptingRoute, ParallelSlot, RootBoundaryId, RouteManifest, RouteManifestLayout, RouteManifestPage, RouteManifestRootBoundary, RouteManifestRoute, RouteManifestRouteHandler, RouteManifestSlot, RouteManifestTemplate, StaticSegmentGraph, buildAppRouteGraph, computeRootParamNames };
109
195
  //# sourceMappingURL=app-route-graph.d.ts.map
@@ -3,6 +3,7 @@ import { scanWithExtensions } from "./file-matcher.js";
3
3
  import { validateRoutePatterns } from "./route-validation.js";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
+ import { createHash } from "node:crypto";
6
7
  //#region src/routing/app-route-graph.ts
7
8
  /**
8
9
  * App Router route graph construction.
@@ -10,6 +11,139 @@ import path from "node:path";
10
11
  * Scans app/ directories and materializes route metadata before the request-time
11
12
  * matcher consumes it. Keep request matching and cache ownership in app-router.ts.
12
13
  */
14
+ function createAppRouteGraphRouteId(pattern) {
15
+ return `route:${pattern}`;
16
+ }
17
+ function createAppRouteGraphPageId(pattern) {
18
+ return `page:${pattern}`;
19
+ }
20
+ function createAppRouteGraphRouteHandlerId(pattern) {
21
+ return `route-handler:${pattern}`;
22
+ }
23
+ function createAppRouteGraphLayoutId(treePath) {
24
+ return `layout:${treePath}`;
25
+ }
26
+ function createAppRouteGraphTemplateId(treePath) {
27
+ return `template:${treePath}`;
28
+ }
29
+ function createAppRouteGraphSlotId(slotName, ownerTreePath) {
30
+ return `slot:${slotName}:${ownerTreePath}`;
31
+ }
32
+ function createAppRouteGraphRootBoundaryId(treePath) {
33
+ return `root-boundary:${treePath}`;
34
+ }
35
+ function compareStableStrings(left, right) {
36
+ if (left < right) return -1;
37
+ if (left > right) return 1;
38
+ return 0;
39
+ }
40
+ function sortedMapValues(map) {
41
+ return Array.from(map.entries()).sort(([left], [right]) => compareStableStrings(left, right)).map(([, value]) => value);
42
+ }
43
+ function createRouteManifest(routes) {
44
+ const segmentGraph = createStaticSegmentGraph(routes);
45
+ return {
46
+ graphVersion: createRouteManifestGraphVersion(segmentGraph),
47
+ segmentGraph
48
+ };
49
+ }
50
+ function createStaticSegmentGraph(routes) {
51
+ const routeEntries = /* @__PURE__ */ new Map();
52
+ const pages = /* @__PURE__ */ new Map();
53
+ const routeHandlers = /* @__PURE__ */ new Map();
54
+ const layouts = /* @__PURE__ */ new Map();
55
+ const templates = /* @__PURE__ */ new Map();
56
+ const slots = /* @__PURE__ */ new Map();
57
+ const rootBoundaries = /* @__PURE__ */ new Map();
58
+ for (const route of routes) {
59
+ routeEntries.set(route.ids.route, {
60
+ id: route.ids.route,
61
+ pattern: route.pattern,
62
+ patternParts: [...route.patternParts],
63
+ isDynamic: route.isDynamic,
64
+ paramNames: [...route.params],
65
+ rootParamNames: [...route.rootParamNames],
66
+ rootBoundaryId: route.ids.rootBoundary,
67
+ pageId: route.ids.page,
68
+ routeHandlerId: route.ids.routeHandler,
69
+ layoutIds: [...route.ids.layouts],
70
+ templateIds: [...route.ids.templates],
71
+ slotIds: route.parallelSlots.map((slot) => slot.id).sort(compareStableStrings)
72
+ });
73
+ if (route.ids.page) pages.set(route.ids.page, {
74
+ id: route.ids.page,
75
+ routeId: route.ids.route,
76
+ pattern: route.pattern
77
+ });
78
+ if (route.ids.routeHandler) routeHandlers.set(route.ids.routeHandler, {
79
+ id: route.ids.routeHandler,
80
+ routeId: route.ids.route,
81
+ pattern: route.pattern
82
+ });
83
+ for (const [index, layoutId] of route.ids.layouts.entries()) {
84
+ const treePosition = route.layoutTreePositions[index];
85
+ assertRouteManifestTreePosition("layout", route, layoutId, treePosition);
86
+ const treePath = createAppRouteGraphTreePath(route.routeSegments, treePosition);
87
+ const existingLayout = layouts.get(layoutId);
88
+ if (existingLayout) assertRouteManifestRootBoundary("layout", route, layoutId, existingLayout.rootBoundaryId);
89
+ layouts.set(layoutId, {
90
+ id: layoutId,
91
+ treePath,
92
+ rootBoundaryId: route.ids.rootBoundary
93
+ });
94
+ if (index === 0 && route.ids.rootBoundary) rootBoundaries.set(route.ids.rootBoundary, {
95
+ id: route.ids.rootBoundary,
96
+ layoutId,
97
+ treePath
98
+ });
99
+ }
100
+ for (const [index, templateId] of route.ids.templates.entries()) {
101
+ const treePosition = route.templateTreePositions?.[index];
102
+ assertRouteManifestTreePosition("template", route, templateId, treePosition);
103
+ const existingTemplate = templates.get(templateId);
104
+ if (existingTemplate) assertRouteManifestRootBoundary("template", route, templateId, existingTemplate.rootBoundaryId);
105
+ templates.set(templateId, {
106
+ id: templateId,
107
+ treePath: createAppRouteGraphTreePath(route.routeSegments, treePosition),
108
+ rootBoundaryId: route.ids.rootBoundary
109
+ });
110
+ }
111
+ for (const slot of route.parallelSlots) slots.set(slot.id, {
112
+ id: slot.id,
113
+ key: slot.key,
114
+ name: slot.name
115
+ });
116
+ }
117
+ return {
118
+ routes: routeEntries,
119
+ pages,
120
+ routeHandlers,
121
+ layouts,
122
+ templates,
123
+ slots,
124
+ rootBoundaries
125
+ };
126
+ }
127
+ function assertRouteManifestTreePosition(kind, route, id, treePosition) {
128
+ if (treePosition !== void 0) return;
129
+ throw new Error(`[vinext] App route graph invariant violated: missing ${kind} tree position for ${id} on ${route.pattern}`);
130
+ }
131
+ function assertRouteManifestRootBoundary(kind, route, id, existingRootBoundaryId) {
132
+ if (existingRootBoundaryId === route.ids.rootBoundary) return;
133
+ throw new Error(`[vinext] App route graph invariant violated: ${kind} ${id} is shared across root boundaries (${existingRootBoundaryId ?? "none"} and ${route.ids.rootBoundary ?? "none"}) on ${route.pattern}`);
134
+ }
135
+ function createRouteManifestGraphVersion(segmentGraph) {
136
+ const stableShape = {
137
+ routes: sortedMapValues(segmentGraph.routes),
138
+ pages: sortedMapValues(segmentGraph.pages),
139
+ routeHandlers: sortedMapValues(segmentGraph.routeHandlers),
140
+ layouts: sortedMapValues(segmentGraph.layouts),
141
+ templates: sortedMapValues(segmentGraph.templates),
142
+ slots: sortedMapValues(segmentGraph.slots),
143
+ rootBoundaries: sortedMapValues(segmentGraph.rootBoundaries)
144
+ };
145
+ return `graph:${createHash("sha256").update(JSON.stringify(stableShape)).digest("hex")}`;
146
+ }
13
147
  async function buildAppRouteGraph(appDir, matcher) {
14
148
  const routes = [];
15
149
  const excludeDir = (name) => name.startsWith("@") || name.startsWith("_");
@@ -38,7 +172,10 @@ async function buildAppRouteGraph(appDir, matcher) {
38
172
  validateRoutePatterns(routes.map((route) => route.pattern));
39
173
  validateRoutePatterns([...new Set(routes.flatMap((route) => route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern))))]);
40
174
  routes.sort(compareRoutes);
41
- return { routes };
175
+ return {
176
+ routes,
177
+ routeManifest: createRouteManifest(routes)
178
+ };
42
179
  }
43
180
  function hasParallelSlotDirectory(dir) {
44
181
  try {
@@ -99,8 +236,9 @@ function discoverSlotSubRoutes(routes, matcher) {
99
236
  };
100
237
  for (const parentRoute of routes) {
101
238
  if (parentRoute.parallelSlots.length === 0) continue;
102
- if (!parentRoute.pagePath) continue;
103
- const parentPageDir = path.dirname(parentRoute.pagePath);
239
+ const isLayoutOnlyUiRoute = !parentRoute.pagePath && !parentRoute.routePath && parentRoute.layouts.length > 0;
240
+ if (!parentRoute.pagePath && !isLayoutOnlyUiRoute) continue;
241
+ const parentPageDir = parentRoute.pagePath ? path.dirname(parentRoute.pagePath) : path.dirname(parentRoute.layouts[parentRoute.layouts.length - 1]);
104
242
  const subPathMap = /* @__PURE__ */ new Map();
105
243
  for (const slot of parentRoute.parallelSlots) {
106
244
  if (path.dirname(slot.ownerDir) !== parentPageDir) continue;
@@ -131,7 +269,7 @@ function discoverSlotSubRoutes(routes, matcher) {
131
269
  }
132
270
  if (subPathMap.size === 0) continue;
133
271
  const childrenDefault = findFile(parentPageDir, "default", matcher);
134
- if (!childrenDefault) continue;
272
+ if (parentRoute.pagePath && !childrenDefault) continue;
135
273
  for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
136
274
  const { urlSegments: urlParts, params: subParams, isDynamic: subIsDynamic } = convertedSubRoute;
137
275
  const subUrlPath = urlParts.join("/");
@@ -142,6 +280,8 @@ function discoverSlotSubRoutes(routes, matcher) {
142
280
  applySlotSubPages(existingRoute, slotPages, rawSegments);
143
281
  continue;
144
282
  }
283
+ const syntheticParts = [...parentRoute.patternParts, ...urlParts];
284
+ if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
145
285
  const subSlots = parentRoute.parallelSlots.map((slot) => {
146
286
  const subPage = slotPages.get(slot.key);
147
287
  return {
@@ -151,6 +291,15 @@ function discoverSlotSubRoutes(routes, matcher) {
151
291
  };
152
292
  });
153
293
  const newRoute = {
294
+ ids: createAppRouteSemanticIds({
295
+ pattern,
296
+ pagePath: childrenDefault,
297
+ routePath: null,
298
+ routeSegments: [...parentRoute.routeSegments, ...rawSegments],
299
+ layoutTreePositions: parentRoute.layoutTreePositions,
300
+ templateTreePositions: parentRoute.templateTreePositions,
301
+ slots: subSlots
302
+ }),
154
303
  pattern,
155
304
  pagePath: childrenDefault,
156
305
  routePath: null,
@@ -245,6 +394,15 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
245
394
  const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
246
395
  const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
247
396
  return {
397
+ ids: createAppRouteSemanticIds({
398
+ pattern: pattern === "/" ? "/" : pattern,
399
+ pagePath,
400
+ routePath,
401
+ routeSegments: segments,
402
+ layoutTreePositions,
403
+ templateTreePositions,
404
+ slots: parallelSlots
405
+ }),
248
406
  pattern: pattern === "/" ? "/" : pattern,
249
407
  pagePath,
250
408
  routePath,
@@ -285,6 +443,29 @@ function computeRootParamNames(routeSegments, layoutTreePositions) {
285
443
  }
286
444
  return names;
287
445
  }
446
+ function resolveRootBoundaryId(routeSegments, layoutTreePositions) {
447
+ const rootLayoutPosition = layoutTreePositions[0];
448
+ if (rootLayoutPosition === void 0) return null;
449
+ return createAppRouteGraphRootBoundaryId(createAppRouteGraphTreePath(routeSegments, rootLayoutPosition));
450
+ }
451
+ function createAppRouteSemanticIds(input) {
452
+ const slots = {};
453
+ for (const slot of input.slots) slots[slot.key] = slot.id;
454
+ return {
455
+ route: createAppRouteGraphRouteId(input.pattern),
456
+ page: input.pagePath ? createAppRouteGraphPageId(input.pattern) : null,
457
+ routeHandler: input.routePath ? createAppRouteGraphRouteHandlerId(input.pattern) : null,
458
+ rootBoundary: resolveRootBoundaryId(input.routeSegments, input.layoutTreePositions),
459
+ layouts: input.layoutTreePositions.map((treePosition) => createAppRouteGraphLayoutId(createAppRouteGraphTreePath(input.routeSegments, treePosition))),
460
+ templates: (input.templateTreePositions ?? []).map((treePosition) => createAppRouteGraphTemplateId(createAppRouteGraphTreePath(input.routeSegments, treePosition))),
461
+ slots
462
+ };
463
+ }
464
+ function createAppRouteGraphTreePath(routeSegments, treePosition) {
465
+ const treePathSegments = routeSegments.slice(0, treePosition);
466
+ if (treePathSegments.length === 0) return "/";
467
+ return `/${treePathSegments.join("/")}`;
468
+ }
288
469
  /**
289
470
  * Compute the tree position (directory depth from app root) for each layout.
290
471
  * Root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.
@@ -545,6 +726,22 @@ function scoreSlotPattern(urlSegments) {
545
726
  return score;
546
727
  }
547
728
  /**
729
+ * Map a pattern segment to the tree-node type used by Next.js' route
730
+ * validator. Two segments are structurally equivalent iff they share the
731
+ * same tree-node type.
732
+ */
733
+ function segmentTreeNodeType(seg) {
734
+ if (!seg.startsWith(":")) return `literal:${seg}`;
735
+ if (seg.endsWith("*")) return "optionalCatchAll";
736
+ if (seg.endsWith("+")) return "catchAll";
737
+ return "dynamic";
738
+ }
739
+ function patternsStructurallyEquivalent(a, b) {
740
+ if (a.length !== b.length) return false;
741
+ for (let i = 0; i < a.length; i++) if (segmentTreeNodeType(a[i]) !== segmentTreeNodeType(b[i])) return false;
742
+ return true;
743
+ }
744
+ /**
548
745
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
549
746
  * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
550
747
  */
@@ -560,7 +757,10 @@ function discoverParallelSlots(dir, appDir, matcher) {
560
757
  const defaultPath = findFile(slotDir, "default", matcher);
561
758
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
562
759
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
760
+ const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
761
+ const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
563
762
  slots.push({
763
+ id: createAppRouteGraphSlotId(slotName, ownerTreePath),
564
764
  key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
565
765
  name: slotName,
566
766
  ownerDir: slotDir,
@@ -774,24 +974,27 @@ function convertSegmentsToRouteParts(segments) {
774
974
  for (let i = 0; i < segments.length; i++) {
775
975
  const segment = segments[i];
776
976
  if (isInvisibleSegment(segment)) continue;
777
- const catchAllMatch = segment.match(/^\[\.\.\.([\w-]+)\]$/);
977
+ const catchAllMatch = segment.match(/^\[\.\.\.([^\]]+)\]$/);
778
978
  if (catchAllMatch) {
779
979
  if (hasRemainingVisibleSegments(segments, i + 1)) return null;
980
+ if (catchAllMatch[1].endsWith("+") || catchAllMatch[1].endsWith("*")) return null;
780
981
  isDynamic = true;
781
982
  params.push(catchAllMatch[1]);
782
983
  urlSegments.push(`:${catchAllMatch[1]}+`);
783
984
  continue;
784
985
  }
785
- const optionalCatchAllMatch = segment.match(/^\[\[\.\.\.([\w-]+)\]\]$/);
986
+ const optionalCatchAllMatch = segment.match(/^\[\[\.\.\.([^\]]+)\]\]$/);
786
987
  if (optionalCatchAllMatch) {
787
988
  if (hasRemainingVisibleSegments(segments, i + 1)) return null;
989
+ if (optionalCatchAllMatch[1].endsWith("+") || optionalCatchAllMatch[1].endsWith("*")) return null;
788
990
  isDynamic = true;
789
991
  params.push(optionalCatchAllMatch[1]);
790
992
  urlSegments.push(`:${optionalCatchAllMatch[1]}*`);
791
993
  continue;
792
994
  }
793
- const dynamicMatch = segment.match(/^\[([\w-]+)\]$/);
995
+ const dynamicMatch = segment.match(/^\[([^\]]+)\]$/);
794
996
  if (dynamicMatch) {
997
+ if (dynamicMatch[1].endsWith("+") || dynamicMatch[1].endsWith("*")) return null;
795
998
  isDynamic = true;
796
999
  params.push(dynamicMatch[1]);
797
1000
  urlSegments.push(`:${dynamicMatch[1]}`);