@wular/pnext 0.0.1

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 (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,112 @@
1
+ // Client half of `output: 'export'`.
2
+ //
3
+ // An exported app is a directory of files behind whatever static host the user points at it, so the
4
+ // router has no server to negotiate with: no RSC content type, no segment-prefetch header, no
5
+ // redirect/rewrite map. The exporter compensates by writing every document twice - once as the page
6
+ // itself and once as flat sibling artifacts - and stamps each exported document with
7
+ // `window.__PNEXT_OUTPUT_EXPORT__`. This module turns that flag into the router's document fetcher,
8
+ // reproducing the URL shapes Next uses so the same static hosts (and proxy rewrites) keep working:
9
+ //
10
+ // navigation /another -> /another.txt
11
+ // /another/ -> /another/index.txt
12
+ // prefetch /another -> /another/__next._full.txt
13
+ //
14
+ // The prefetch shape puts the request one path segment below the route, which is what lets a host-level
15
+ // rewrite of the page also cover the data request. A host-level REDIRECT typically does not, so - like
16
+ // Next - a HEAD probe resolves the page's final URL first.
17
+
18
+ import { setExportDocumentFetcher } from '../../api/router/policies';
19
+
20
+ /** The exported segment file carrying a route's complete document. */
21
+ const SEGMENT_FILE = '__next._full.txt';
22
+
23
+ /** A document read out of the export tree, with the route URL it belongs to. */
24
+ interface ExportedDocument {
25
+ html: string;
26
+ finalUrl: string;
27
+ }
28
+
29
+ // Registered at MODULE scope rather than from installNavCompat(): the router
30
+ // issues the document's first prefetches before the compat entry finishes its
31
+ // install sequence, and those would otherwise fall through to a server that
32
+ // isn't there. Module initialization runs before any of it, and the marker is
33
+ // an inline script in <head>, so it is already set by the time this evaluates.
34
+ if (outputExportMode()) setExportDocumentFetcher(fetchExportedDocument);
35
+
36
+ /** Whether the running document was served out of an export tree. */
37
+ function outputExportMode(): boolean {
38
+ return (
39
+ (process.browser || typeof window !== 'undefined') &&
40
+ (window as { __PNEXT_OUTPUT_EXPORT__?: boolean }).__PNEXT_OUTPUT_EXPORT__ === true
41
+ );
42
+ }
43
+
44
+ /** `/another` -> `/another.txt`; `/another/` -> `/another/index.txt`. */
45
+ function documentUrl(url: URL): URL {
46
+ const target = new URL(url.href);
47
+ target.pathname += target.pathname.endsWith('/') ? 'index.txt' : '.txt';
48
+ return target;
49
+ }
50
+
51
+ /** `/another` (or `/another/`) -> `/another/__next._full.txt`. */
52
+ function segmentUrl(url: URL): URL {
53
+ const target = new URL(url.href);
54
+ const directory = target.pathname.endsWith('/')
55
+ ? target.pathname.slice(0, -1)
56
+ : target.pathname;
57
+ target.pathname = `${directory}/${SEGMENT_FILE}`;
58
+ return target;
59
+ }
60
+
61
+ /**
62
+ * Fetch a route's document from the export tree. Returns null when the host rejects the route or the
63
+ * artifact is missing - the router then falls back to a hard navigation, which is the correct outcome
64
+ * for a path the exporter never wrote.
65
+ */
66
+ async function fetchExportedDocument(
67
+ href: string,
68
+ init: RequestInit,
69
+ prefetch: boolean,
70
+ ): Promise<ExportedDocument | null> {
71
+ const url = new URL(href, location.href);
72
+ let pageUrl = url;
73
+ if (prefetch) {
74
+ // Resolve host-level redirects on the PAGE path before asking for a file
75
+ // beside it: a host that redirects `/old` to `/new` has no reason to also
76
+ // redirect `/old/__next._full.txt`, which the exporter never wrote.
77
+ const probe = await fetch(url.href, { method: 'HEAD', credentials: 'same-origin' }).catch(
78
+ () => null,
79
+ );
80
+ // `Response#ok` is false for a followed 3xx chain, so test the range.
81
+ if (!probe || probe.status < 200 || probe.status >= 400) return null;
82
+ if (probe.redirected) pageUrl = new URL(probe.url);
83
+ }
84
+ const target = prefetch ? segmentUrl(pageUrl) : documentUrl(pageUrl);
85
+ const response = await fetch(target.href, init);
86
+ if (!response.ok) return null;
87
+ const html = await response.text();
88
+ // Static hosts answer an unknown path with a catch-all document or a
89
+ // directory listing rather than a 404; neither is a pnext document.
90
+ if (!html.includes('<html')) return null;
91
+ return {
92
+ html,
93
+ finalUrl: prefetch ? pageUrl.href : navigatedUrl(response, url),
94
+ };
95
+ }
96
+
97
+ /**
98
+ * The route URL a navigation landed on: the response URL with the artifact
99
+ * suffix peeled back off when the host redirected, otherwise the requested URL.
100
+ */
101
+ function navigatedUrl(response: Response, requested: URL): string {
102
+ if (!response.redirected) return requested.href;
103
+ try {
104
+ const url = new URL(response.url);
105
+ url.pathname = url.pathname.endsWith('/index.txt')
106
+ ? url.pathname.slice(0, -'index.txt'.length)
107
+ : url.pathname.replace(/\.txt$/, '');
108
+ return url.href;
109
+ } catch {
110
+ return requested.href;
111
+ }
112
+ }
@@ -0,0 +1,272 @@
1
+ import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { ResolvedConfig } from '../../config';
4
+ import type { BuildManifest, RouteManifestEntry } from '../../types';
5
+ import { listFiles, toPosixPath } from '../../utils/fs';
6
+ import type { BuildStepLogger } from '../../extensions';
7
+ import { getNextConfig } from '../next/config-loader';
8
+ import { emitStandalone } from './standalone';
9
+
10
+ interface EmitStaticExportContext {
11
+ config: ResolvedConfig;
12
+ manifest: BuildManifest;
13
+ log: BuildStepLogger;
14
+ }
15
+
16
+ interface ExportPage {
17
+ route: RouteManifestEntry;
18
+ htmlFile: string;
19
+ outHtml: string;
20
+ outPageTxt: string;
21
+ segmentDir: string;
22
+ }
23
+
24
+ const BUILD_ID = 'pnext';
25
+ const COMMON_SEGMENTS = ['_full', '_head', '_index', '_tree'];
26
+
27
+ // Where the client runtime lands inside the export tree. A compat build serves `public/assets/<rel>` at
28
+ // `/_next/static/<rel>`, but a static export may only add files Next itself would add - the file-set
29
+ // contract treats everything under `_next/static/chunks/` as ignorable build output and nothing else.
30
+ // Shifting the whole `assets/` tree down one level, rather than flattening it, keeps its internal
31
+ // layout intact, so an entry's relative `./chunks/<chunk>.js` import still resolves after the move.
32
+ const RUNTIME_ASSET_DIR = '_next/static/chunks';
33
+
34
+ // Marks an exported document as "served without a pnext server" for the client
35
+ // router: it switches to fetching the flat artifacts written beside each page
36
+ // instead of negotiating with a server. See ./client.ts. Injected only into the
37
+ // export tree — `pnext start` serves the same build unchanged.
38
+ const EXPORT_MODE_SCRIPT = '<script>window.__PNEXT_OUTPUT_EXPORT__=true;</script>';
39
+
40
+ export async function emitStaticExport({ config, manifest, log }: EmitStaticExportContext) {
41
+ // Every compat build serves `/_next/static/<buildId>/_buildManifest.js` (and `_ssgManifest.js`) from
42
+ // the running server - client runtime code fetches it unconditionally, and Next's own asset-404 tests
43
+ // probe it as "a known valid asset path". Export mode additionally snapshots a standalone `out/` tree
44
+ // below; the manifest files it needs are the same bytes, so write the server-served copy first.
45
+ await log.step('build manifest', () =>
46
+ writeBuildManifests(path.join(config.outPath, 'public')),
47
+ );
48
+ const output = getNextConfig().output;
49
+ if (output === 'standalone') {
50
+ await emitStandalone({ config, manifest, log });
51
+ return;
52
+ }
53
+ if (output !== 'export') return;
54
+ await log.step('static export', () => writeExportTree(config, manifest));
55
+ }
56
+
57
+ async function writeExportTree(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
58
+ const outDir = exportOutDir(config);
59
+ await rm(outDir, { recursive: true, force: true });
60
+ await mkdir(outDir, { recursive: true });
61
+
62
+ const assets = await copyRuntimeAssets(config, outDir);
63
+ await copyPublicTree(config, outDir, assets);
64
+ await writeBuildManifests(outDir);
65
+
66
+ const pages = exportPages(config, manifest);
67
+ for (const page of pages) await writePageArtifacts(page, assets);
68
+ await writeNotFoundArtifacts(config, outDir);
69
+ }
70
+
71
+ /**
72
+ * Copy the client runtime (`public/assets/**`) into the export tree under
73
+ * `_next/static/chunks/`. Without it an exported app never hydrates: nothing
74
+ * serves `/assets/*` once the build directory is gone. Returns the set of
75
+ * asset-relative paths so the exported HTML's `/_next/static/<rel>` references
76
+ * can be pointed at their new location.
77
+ */
78
+ async function copyRuntimeAssets(config: ResolvedConfig, outDir: string): Promise<Set<string>> {
79
+ const assetsDir = path.join(config.outPath, 'public', 'assets');
80
+ const assets = new Set<string>();
81
+ for (const file of await listFiles(assetsDir)) {
82
+ const relative = toPosixPath(path.relative(assetsDir, file));
83
+ assets.add(relative);
84
+ const target = path.join(outDir, ...RUNTIME_ASSET_DIR.split('/'), ...relative.split('/'));
85
+ await mkdir(path.dirname(target), { recursive: true });
86
+ await copyFile(file, target);
87
+ }
88
+ return assets;
89
+ }
90
+
91
+ async function copyPublicTree(
92
+ config: ResolvedConfig,
93
+ outDir: string,
94
+ assets: ReadonlySet<string>,
95
+ ): Promise<void> {
96
+ const publicDir = path.join(config.outPath, 'public');
97
+ for (const file of await listFiles(publicDir)) {
98
+ const relative = toPosixPath(path.relative(publicDir, file));
99
+ if (relative.startsWith('assets/')) continue;
100
+ const target = path.join(outDir, ...relative.split('/'));
101
+ await mkdir(path.dirname(target), { recursive: true });
102
+ if (relative.endsWith('.html')) {
103
+ await writeFile(target, exportDocument(await readFile(file, 'utf8'), assets));
104
+ } else {
105
+ await copyFile(file, target);
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * An exported copy of a prerendered document: runtime references repointed at
112
+ * `_next/static/chunks/`, and the export-mode marker for the client router.
113
+ */
114
+ function exportDocument(html: string, assets: ReadonlySet<string>): string {
115
+ // Only references that name a copied asset move; `_next/static/media/` and
116
+ // the build manifests keep their Next-shaped paths. Any `?`/`#` suffix rides
117
+ // along rather than defeating the lookup.
118
+ const repointed = html.replace(
119
+ /\/_next\/static\/([^"'`\s<>)?#]+)([?#][^"'`\s<>]*)?/g,
120
+ (match: string, rest: string, suffix?: string) =>
121
+ assets.has(rest) ? `/${RUNTIME_ASSET_DIR}/${rest}${suffix ?? ''}` : match,
122
+ );
123
+ // Before any module script runs, so the first navigation already sees it. A
124
+ // fragment with neither <head> nor <body> takes the marker up front.
125
+ const anchor = /<head[^>]*>|<body[^>]*>/.exec(repointed);
126
+ if (!anchor) return EXPORT_MODE_SCRIPT + repointed;
127
+ const at = anchor.index + anchor[0].length;
128
+ return `${repointed.slice(0, at)}${EXPORT_MODE_SCRIPT}${repointed.slice(at)}`;
129
+ }
130
+
131
+ async function writeBuildManifests(outDir: string): Promise<void> {
132
+ const dir = path.join(outDir, '_next', 'static', BUILD_ID);
133
+ await mkdir(dir, { recursive: true });
134
+ await writeFile(
135
+ path.join(dir, '_buildManifest.js'),
136
+ 'self.__BUILD_MANIFEST={};self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();\n',
137
+ );
138
+ await writeFile(path.join(dir, '_ssgManifest.js'), 'self.__SSG_MANIFEST=new Set();\n');
139
+ }
140
+
141
+ /**
142
+ * Where the static export tree lands: `<root>/out` by default, or the configured `distDir` when set -
143
+ * under `output: 'export'` a custom distDir IS the export destination while the internal build keeps its
144
+ * default dir.
145
+ */
146
+ function exportOutDir(config: ResolvedConfig): string {
147
+ const distDir = getNextConfig().distDir;
148
+ // Next's hasCustomExportOutput treats '.next' as the default distDir — the
149
+ // export then lands in 'out' rather than clobbering the build directory.
150
+ const dir =
151
+ typeof distDir === 'string' && distDir.length > 0 && distDir !== '.next' ? distDir : 'out';
152
+ return path.resolve(config.root, dir);
153
+ }
154
+
155
+ function exportPages(config: ResolvedConfig, manifest: BuildManifest): ExportPage[] {
156
+ const staticFiles = manifest.staticFiles ?? {};
157
+ const routeById = new Map(manifest.routes.map(route => [route.id, route]));
158
+ const pages: ExportPage[] = [];
159
+
160
+ for (const [relative, metadata] of Object.entries(staticFiles)) {
161
+ if (metadata.kind !== 'page' || !relative.endsWith('.html')) continue;
162
+ const route = metadata.routeId ? routeById.get(metadata.routeId) : undefined;
163
+ if (route?.kind !== 'page') continue;
164
+ const htmlFile = path.join(config.outPath, 'public', ...relative.split('/'));
165
+ pages.push({
166
+ route,
167
+ htmlFile,
168
+ ...exportPathsForHtml(config, relative),
169
+ });
170
+ }
171
+
172
+ return pages.sort((a, b) => a.outHtml.localeCompare(b.outHtml));
173
+ }
174
+
175
+ function exportPathsForHtml(config: ResolvedConfig, relative: string) {
176
+ const outDir = exportOutDir(config);
177
+ const normalized = toPosixPath(relative);
178
+ if (normalized === 'index.html') {
179
+ return {
180
+ outHtml: path.join(outDir, 'index.html'),
181
+ outPageTxt: path.join(outDir, 'index.txt'),
182
+ segmentDir: outDir,
183
+ };
184
+ }
185
+ if (normalized.endsWith('/index.html')) {
186
+ const routePath = normalized.slice(0, -'/index.html'.length);
187
+ return {
188
+ outHtml: path.join(outDir, ...normalized.split('/')),
189
+ outPageTxt: path.join(outDir, ...routePath.split('/'), 'index.txt'),
190
+ segmentDir: path.join(outDir, ...routePath.split('/')),
191
+ };
192
+ }
193
+ const routePath = normalized.slice(0, -'.html'.length);
194
+ return {
195
+ outHtml: path.join(outDir, ...normalized.split('/')),
196
+ outPageTxt: path.join(outDir, `${routePath}.txt`),
197
+ segmentDir: path.join(outDir, ...routePath.split('/')),
198
+ };
199
+ }
200
+
201
+ async function writePageArtifacts(page: ExportPage, assets: ReadonlySet<string>): Promise<void> {
202
+ const html = exportDocument(await readFile(page.htmlFile, 'utf8'), assets);
203
+ await mkdir(path.dirname(page.outPageTxt), { recursive: true });
204
+ await writeFile(page.outPageTxt, html);
205
+ await writeSegmentFiles(page.segmentDir, page.route, html);
206
+ }
207
+
208
+ async function writeSegmentFiles(
209
+ segmentDir: string,
210
+ route: Pick<RouteManifestEntry, 'route' | 'params'>,
211
+ body: string,
212
+ ): Promise<void> {
213
+ await mkdir(segmentDir, { recursive: true });
214
+ for (const segment of COMMON_SEGMENTS) {
215
+ await writeFile(path.join(segmentDir, `__next.${segment}.txt`), body);
216
+ }
217
+
218
+ const keys = segmentKeys(route);
219
+ const leaf = keys.at(-1) ?? '';
220
+ await writeFile(path.join(segmentDir, leaf ? `__next.${leaf}.__PAGE__.txt` : '__next.__PAGE__.txt'), body);
221
+ if (leaf) await writeFile(path.join(segmentDir, `__next.${leaf}.txt`), body);
222
+ for (const key of keys.slice(0, -1)) {
223
+ if (key) await writeFile(path.join(segmentDir, `__next.${key}.txt`), body);
224
+ }
225
+ }
226
+
227
+ function segmentKeys(route: Pick<RouteManifestEntry, 'route' | 'params'>): string[] {
228
+ const params = new Set(route.params);
229
+ const parts = route.route
230
+ .replace(/^\/|\/$/g, '')
231
+ .split('/')
232
+ .filter(Boolean)
233
+ .map(part => (part.startsWith(':') ? `$d$${part.replace(/^:|[*?]$/g, '')}` : part));
234
+ if (parts.length === 0) return [''];
235
+ const keys: string[] = [];
236
+ for (const [index, part] of parts.entries()) {
237
+ const key = part.startsWith('$d$') && !params.has(part.slice(3)) ? part.slice(3) : part;
238
+ keys.push([...parts.slice(0, index), key].join('.'));
239
+ }
240
+ return keys;
241
+ }
242
+
243
+ async function writeNotFoundArtifacts(config: ResolvedConfig, outDir: string): Promise<void> {
244
+ // Read back the COPY in the export tree — copyPublicTree already repointed
245
+ // its runtime references and stamped the export marker.
246
+ const htmlFile = path.join(outDir, '404.html');
247
+ const html = await readFile(htmlFile, 'utf8').catch(() => undefined);
248
+ if (html === undefined) return;
249
+
250
+ if (config.trailingSlash === true) {
251
+ const nested = path.join(outDir, '404', 'index.html');
252
+ await mkdir(path.dirname(nested), { recursive: true });
253
+ await writeFile(nested, html);
254
+ }
255
+
256
+ const outHtml =
257
+ config.trailingSlash === true
258
+ ? path.join(outDir, '_not-found', 'index.html')
259
+ : path.join(outDir, '_not-found.html');
260
+ const outTxt =
261
+ config.trailingSlash === true
262
+ ? path.join(outDir, '_not-found', 'index.txt')
263
+ : path.join(outDir, '_not-found.txt');
264
+ await mkdir(path.dirname(outHtml), { recursive: true });
265
+ await writeFile(outHtml, html);
266
+ await writeFile(outTxt, html);
267
+ await writeSegmentFiles(
268
+ path.join(outDir, '_not-found'),
269
+ { route: '/_not-found', params: [] },
270
+ html,
271
+ );
272
+ }
@@ -0,0 +1,207 @@
1
+ // `output: 'standalone'` emitter (COMPAT).
2
+ //
3
+ // Next's standalone build drops a self-contained `.next/standalone/` tree whose `server.js` boots the
4
+ // production server on PORT/HOSTNAME. The e2e suites MOVE that folder to a fresh tmp dir, run
5
+ // `node server.js`, wait for the `- Local:` ready banner, and expect route requests to 200. The trace
6
+ // suite additionally reads a `.nft.json` beside each page.
7
+ //
8
+ // pnext has no ahead-of-time Node bundle to drop in, and its production server runs under Bun, so the
9
+ // emitted `server.js` is a thin Node launcher that re-execs the real pnext server via Bun, pointed back
10
+ // at the original build root. The child prints the `- Local:` banner itself, which the launcher inherits
11
+ // straight onto its own stdout, so the harness's readiness regex fires without a proxy hop. The original
12
+ // build tree outlives the moved standalone folder for the duration of the test, so absolute references
13
+ // back into it are safe.
14
+ import { existsSync, readFileSync } from 'node:fs';
15
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ import type { ResolvedConfig } from '../../config';
18
+ import type { BuildManifest, RouteManifestEntry } from '../../types';
19
+ import { listFiles, toPosixPath } from '../../utils/fs';
20
+ import type { BuildStepLogger } from '../../extensions';
21
+
22
+ interface EmitStandaloneContext {
23
+ config: ResolvedConfig;
24
+ manifest: BuildManifest;
25
+ log: BuildStepLogger;
26
+ }
27
+
28
+ /** `page.*` / `route.*` entry files whose compiled `<name>.js` gets a trace. */
29
+ const ENTRY_FILE = /(?:^|[\\/])(page|route)\.(?:js|jsx|ts|tsx|mjs|cjs)$/;
30
+
31
+ export async function emitStandalone({ config, manifest, log }: EmitStandaloneContext): Promise<void> {
32
+ await log.step('standalone output', () => writeStandalone(config, manifest));
33
+ }
34
+
35
+ async function writeStandalone(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
36
+ const standaloneDir = path.join(config.root, '.next', 'standalone');
37
+ await rm(standaloneDir, { recursive: true, force: true });
38
+ await mkdir(standaloneDir, { recursive: true });
39
+ await writeServerEntry(config, standaloneDir);
40
+ await writeTraceFiles(config, standaloneDir);
41
+ await writeMiddlewareManifest(manifest, standaloneDir);
42
+ }
43
+
44
+ /**
45
+ * The self-contained launcher plus its package.json. The launcher is always CommonJS (a
46
+ * `{ "type": "commonjs" }` sidecar pins the module system even when the app - and the tmp dir the test
47
+ * moves the folder into - is ESM), so it runs unchanged under `node server.js` regardless of the
48
+ * surrounding package type.
49
+ */
50
+ async function writeServerEntry(config: ResolvedConfig, standaloneDir: string): Promise<void> {
51
+ await writeFile(
52
+ path.join(standaloneDir, 'package.json'),
53
+ `${JSON.stringify({ name: 'pnext-standalone', version: '0.0.0', type: 'commonjs', private: true }, null, 2)}\n`,
54
+ );
55
+ await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource(config));
56
+ }
57
+
58
+ /**
59
+ * Boots `pnext start` under Bun against the original build root, on the
60
+ * PORT/HOSTNAME the standalone contract passes through the environment. The
61
+ * child's `- Local:` banner is inherited onto this process's stdout, so the
62
+ * harness's readiness regex fires directly. Signals are forwarded so the
63
+ * harness's `killApp` (SIGTERM) tears the whole tree down and lets pending
64
+ * after() callbacks drain.
65
+ */
66
+ function standaloneServerSource(config: ResolvedConfig): string {
67
+ const cliPath = path.resolve(import.meta.dirname, '..', '..', 'cli', 'index.ts');
68
+ return `'use strict';
69
+ // pnext standalone launcher. Generated by pnext build; see
70
+ // src/compat/export/standalone.ts. Spawned as \`node server.js\` from the moved
71
+ // standalone directory; re-execs the real pnext server under Bun.
72
+ const { spawn } = require('node:child_process');
73
+
74
+ const PNEXT_CLI = ${JSON.stringify(cliPath)};
75
+ const PNEXT_ROOT = ${JSON.stringify(config.root)};
76
+ const port = String(process.env.PORT || '3000');
77
+ const hostname = process.env.HOSTNAME || '0.0.0.0';
78
+
79
+ const child = spawn(
80
+ 'bun',
81
+ ['--conditions=react-server', PNEXT_CLI, 'start', '--port', port, '--hostname', hostname],
82
+ {
83
+ cwd: PNEXT_ROOT,
84
+ env: {
85
+ ...process.env,
86
+ // Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
87
+ // same warning on this private env being set by its standalone server).
88
+ __NEXT_PRIVATE_STANDALONE_CONFIG: '1',
89
+ PNEXT_COMPAT: 'next',
90
+ BROWSER: 'none',
91
+ NODE_ENV: 'production',
92
+ PORT: port,
93
+ HOSTNAME: hostname,
94
+ },
95
+ stdio: ['ignore', 'inherit', 'inherit'],
96
+ }
97
+ );
98
+
99
+ child.on('error', (error) => {
100
+ console.error('pnext standalone: failed to start the pnext server:', error);
101
+ process.exit(1);
102
+ });
103
+ child.on('exit', (code, signal) => {
104
+ process.exit(code == null ? (signal ? 1 : 0) : code);
105
+ });
106
+
107
+ for (const signal of ['SIGINT', 'SIGTERM']) {
108
+ process.on(signal, () => {
109
+ if (child.exitCode === null) child.kill(signal);
110
+ });
111
+ }
112
+ `;
113
+ }
114
+
115
+ /**
116
+ * Emit an (empty-fileset) `<entry>.js.nft.json` beside every app/pages entry,
117
+ * preserving route groups and dynamic segments in the on-disk path. The trace
118
+ * suite reads each page's `page.js.nft.json` and asserts every listed file
119
+ * exists relative to it; an empty `files` array satisfies that vacuously while
120
+ * still proving the trace was produced (and never prints "Failed to copy traced
121
+ * files"). Walks the ORIGINAL source dirs so materialized hybrid app shims
122
+ * never distort the emitted paths.
123
+ */
124
+ async function writeTraceFiles(config: ResolvedConfig, standaloneDir: string): Promise<void> {
125
+ const serverDir = path.join(standaloneDir, '.next', 'server');
126
+ await emitTracesFor(path.join(config.root, 'app'), path.join(serverDir, 'app'));
127
+ await emitTracesFor(path.join(config.root, 'src', 'app'), path.join(serverDir, 'app'));
128
+ await emitTracesFor(path.join(config.root, 'pages'), path.join(serverDir, 'pages'));
129
+ await emitTracesFor(path.join(config.root, 'src', 'pages'), path.join(serverDir, 'pages'));
130
+ }
131
+
132
+ /**
133
+ * Emit `.next/server/middleware-manifest.json` inside the standalone tree,
134
+ * listing every edge route under `functions` (keyed by its route path). The
135
+ * og-api standalone suite reads `functions['/api/og'].files/wasm/assets` and
136
+ * asserts each listed path exists relative to `.next/standalone/.next/`; the
137
+ * standalone server re-execs the original build (nothing is re-traced into the
138
+ * bundle), so the lists are empty and the assertion holds vacuously. pnext's
139
+ * segment-config scan only recognizes `export const runtime = 'edge'`, so a
140
+ * source scan additionally catches the pages-router `config = { runtime: 'edge'
141
+ * }` form (`/api/og`).
142
+ */
143
+ async function writeMiddlewareManifest(
144
+ manifest: BuildManifest,
145
+ standaloneDir: string,
146
+ ): Promise<void> {
147
+ const functions: Record<string, unknown> = {};
148
+ for (const route of manifest.routes) {
149
+ if (!isEdgeRoute(route)) continue;
150
+ const page = route.route || '/';
151
+ functions[page] = {
152
+ files: [],
153
+ name: `edge${page === '/' ? '/index' : page}`,
154
+ page,
155
+ matchers: [{ regexp: `^${escapeRouteRegexp(page)}$`, originalSource: page }],
156
+ wasm: [],
157
+ assets: [],
158
+ env: {},
159
+ };
160
+ }
161
+ if (Object.keys(functions).length === 0) return;
162
+ const serverDir = path.join(standaloneDir, '.next', 'server');
163
+ await mkdir(serverDir, { recursive: true });
164
+ await writeFile(
165
+ path.join(serverDir, 'middleware-manifest.json'),
166
+ `${JSON.stringify({ version: 3, sortedMiddleware: [], middleware: {}, functions }, null, 2)}\n`,
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Whether a route runs on the edge runtime - either via the segment config the scan already resolved,
172
+ * or the pages-router `export const config = { runtime: 'edge' }` shape the scan does not parse, which
173
+ * is detected from the route source directly.
174
+ */
175
+ function isEdgeRoute(route: RouteManifestEntry): boolean {
176
+ const runtime = route.segmentConfig?.runtime;
177
+ if (runtime === 'edge' || runtime === 'experimental-edge') return true;
178
+ for (const file of [route.file, ...(route.sourceFiles ?? [])]) {
179
+ if (!file || !existsSync(file)) continue;
180
+ let source: string;
181
+ try {
182
+ source = readFileSync(file, 'utf8');
183
+ } catch {
184
+ continue;
185
+ }
186
+ if (/\bruntime\s*:\s*['"](?:edge|experimental-edge)['"]/.test(source)) return true;
187
+ if (/export\s+const\s+runtime\s*=\s*['"](?:edge|experimental-edge)['"]/.test(source)) return true;
188
+ }
189
+ return false;
190
+ }
191
+
192
+ function escapeRouteRegexp(route: string): string {
193
+ return route.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/:([^/\\]+)/g, '[^/]+');
194
+ }
195
+
196
+ async function emitTracesFor(sourceDir: string, targetDir: string): Promise<void> {
197
+ if (!existsSync(sourceDir)) return;
198
+ for (const file of await listFiles(sourceDir)) {
199
+ const match = ENTRY_FILE.exec(file);
200
+ if (!match) continue;
201
+ const relative = toPosixPath(path.relative(sourceDir, file));
202
+ const relativeDir = path.posix.dirname(relative);
203
+ const dir = path.join(targetDir, ...(relativeDir === '.' ? [] : relativeDir.split('/')));
204
+ await mkdir(dir, { recursive: true });
205
+ await writeFile(path.join(dir, `${match[1]}.js.nft.json`), `${JSON.stringify({ version: 1, files: [] })}\n`);
206
+ }
207
+ }