@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
package/src/cli/dev.ts ADDED
@@ -0,0 +1,276 @@
1
+ import { dim } from '../utils/ansi';
2
+ import { moduleGenerationStats } from '../dev/module-generations';
3
+ import { markBoot, printBootTrace } from './boot-trace';
4
+ import { browserHost, ensurePortFree, openBrowser, printServerReady } from './serve-ui';
5
+
6
+ export const devProcessTitle = 'pnext-dev';
7
+ export const devRestartEnv = 'PNEXT_DEV_RESTART';
8
+
9
+ const defaultMaxDevRssMb = 2048;
10
+ const defaultMaxEsbuildRssMb = 1024;
11
+ // A leaf edit supersedes a couple of generations, so this caps stale mass at roughly 300 MB -
12
+ // about one recycle per 75 leaf edits, and one per ~30 on wide-shared-module edits that rename
13
+ // most of a route graph.
14
+ const defaultMaxStaleModules = 150;
15
+ /** How long a recycle waits for in-flight responses before replacing the image. */
16
+ const drainTimeoutMs = 5_000;
17
+
18
+ export function nameDevProcess() {
19
+ process.title = devProcessTitle;
20
+ }
21
+
22
+ export interface DevOptions {
23
+ root?: string;
24
+ port?: number;
25
+ hostname?: string;
26
+ }
27
+
28
+ /**
29
+ * The dev server, running directly in the process bin/pnext exec'd - there is no supervisor.
30
+ * Resilience comes from self-replacement instead: a memory watchdog trip re-execs this process in
31
+ * place, which frees the module graph without paying a second process spawn on every boot.
32
+ */
33
+ export async function dev(options: DevOptions = {}) {
34
+ nameDevProcess();
35
+ // Loaded lazily so config resolution and the server graph start in parallel.
36
+ const [{ loadConfig }, { startDevServer }] = await Promise.all([
37
+ import('../config'),
38
+ import('../dev/server'),
39
+ ]);
40
+ markBoot('dev:imports');
41
+ const config = await loadConfig(options.root, { dev: true });
42
+ markBoot('dev:config');
43
+ const port = options.port ?? 3000;
44
+ const hostname = options.hostname ?? 'localhost';
45
+ // A memory-triggered restart can briefly race the previous listener's
46
+ // shutdown, so give the port a moment to free before failing.
47
+ await ensurePortFree(port, hostname, { attempts: 10 });
48
+ markBoot('dev:port');
49
+ const start = Date.now();
50
+ // Bisect switch, same role `PNEXT_DEV_RESTART_CACHE=0` plays for the restart
51
+ // caches: turns the boot warm-up off so a measurement can price it.
52
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
53
+ const warm = process.env.PNEXT_DEV_WARM !== '0';
54
+ const server = await startDevServer({ config, port, hostname, warm });
55
+ markBoot('dev:server');
56
+ const elapsedMs = Date.now() - start;
57
+ const url = `http://${browserHost(hostname)}:${server.port ?? port}`;
58
+ printServerReady({ mode: 'dev', hostname, port: server.port ?? port, elapsedMs });
59
+ printBootTrace();
60
+ registerShutdown(server);
61
+ watchServerMemory(server);
62
+ watchStaleModules(server);
63
+ watchEsbuildMemory();
64
+ // Hold the browser until the entry route has compiled, so it opens onto an
65
+ // already-built page instead of a multi-second cold compile.
66
+ if (server.warmup) console.log(dim('Warming up and opening the browser...'));
67
+ await server.warmup;
68
+ // A memory-triggered restart should refresh silently, not open another tab.
69
+ if (process.env[devRestartEnv] !== '1') openBrowser(url);
70
+ return server;
71
+ }
72
+
73
+ // Every dev reload imports changed modules under fresh URLs, and the ES module
74
+ // registry never evicts, so a long session pins every stale graph in memory.
75
+ // Once RSS crosses the limit, trade a seconds-long restart (vendor bundles are
76
+ // preserved on disk) for getting the memory back.
77
+ function watchServerMemory(server: DevServerHandle) {
78
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
79
+ const limitMb = Number(process.env.PNEXT_DEV_MAX_RSS_MB || defaultMaxDevRssMb);
80
+ if (!Number.isFinite(limitMb) || limitMb <= 0) return;
81
+ const limitBytes = limitMb * 1024 * 1024;
82
+ const timer = setInterval(() => {
83
+ const rss = process.memoryUsage.rss();
84
+ if (rss < limitBytes) return;
85
+ clearInterval(timer);
86
+ const usedMb = Math.round(rss / (1024 * 1024));
87
+ console.log(
88
+ dim(
89
+ `Dev server reached ${usedMb}MB of memory (limit ${limitMb}MB, set PNEXT_DEV_MAX_RSS_MB to change); restarting to release stale modules...`,
90
+ ),
91
+ );
92
+ void restartDevProcess(server);
93
+ }, 15_000);
94
+ timer.unref();
95
+ }
96
+
97
+ // The esbuild Go service self-drains its heap when idle, so two consecutive readings above the
98
+ // limit mean it is genuinely parked high. Stopping it is lossless - pnext only issues one-shot
99
+ // builds, never incremental contexts. A stop that races an in-flight build fails that one build;
100
+ // dev asset caches drop failed builds, so the next request retries.
101
+ function watchEsbuildMemory() {
102
+ if (process.platform === 'win32') return;
103
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
104
+ const limitMb = Number(process.env.PNEXT_DEV_MAX_ESBUILD_RSS_MB || defaultMaxEsbuildRssMb);
105
+ if (!Number.isFinite(limitMb) || limitMb <= 0) return;
106
+ let highTicks = 0;
107
+ const timer = setInterval(() => {
108
+ const rssMb = esbuildServiceRssMb();
109
+ highTicks = rssMb > limitMb ? highTicks + 1 : 0;
110
+ if (highTicks < 2) return;
111
+ highTicks = 0;
112
+ console.log(
113
+ dim(
114
+ `esbuild reached ${Math.round(rssMb)}MB of memory (limit ${limitMb}MB, set PNEXT_DEV_MAX_ESBUILD_RSS_MB to change); restarting it to release the heap...`,
115
+ ),
116
+ );
117
+ void (async () => {
118
+ try {
119
+ const esbuild = await import('esbuild');
120
+ await esbuild.stop();
121
+ } catch {
122
+ // ignore: the service may already be gone
123
+ }
124
+ // stop() can strand in-flight build promises unsettled; fail their
125
+ // waiters so requests retry against the respawned service.
126
+ const { failInFlightClientBuilds } = await import('../dev/server');
127
+ failInFlightClientBuilds('esbuild service restarted by the memory watchdog');
128
+ })();
129
+ }, 15_000);
130
+ timer.unref();
131
+ }
132
+
133
+ /**
134
+ * Recycle on stale registry mass rather than on RSS. Every save renames the modules it changes and
135
+ * Bun's registry never unlinks the old ones, so a session accumulates superseded generations at a
136
+ * fixed rate that nothing in-process can free. RSS is a lagging proxy: it trips only after the
137
+ * growth has already cost save latency, and it also trips on a warm route set that is entirely
138
+ * live. The replacement re-warms from the content-keyed disk cache, so the cost is a restart, not
139
+ * a cold build.
140
+ */
141
+ function watchStaleModules(server: DevServerHandle) {
142
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
143
+ const limit = Number(process.env.PNEXT_DEV_MAX_STALE_MODULES || defaultMaxStaleModules);
144
+ if (!Number.isFinite(limit) || limit <= 0) return;
145
+ const timer = setInterval(() => {
146
+ const { stale } = moduleGenerationStats();
147
+ if (stale < limit) return;
148
+ clearInterval(timer);
149
+ console.log(
150
+ dim(
151
+ `Dev server is holding ${stale} superseded module generations (limit ${limit}, set PNEXT_DEV_MAX_STALE_MODULES to change); restarting to release them...`,
152
+ ),
153
+ );
154
+ void restartDevProcess(server);
155
+ }, 15_000);
156
+ timer.unref();
157
+ }
158
+
159
+ // Our esbuild service children: [pid, RSS in MB] per process.
160
+ function esbuildServices(): [number, number][] {
161
+ const found: [number, number][] = [];
162
+ try {
163
+ const stdout = Bun.spawnSync(['ps', '-ax', '-o', 'pid=,ppid=,rss=,ucomm=']).stdout.toString();
164
+ for (const line of stdout.split('\n')) {
165
+ const [pid, ppid, rss, ...name] = line.trim().split(/\s+/);
166
+ if (Number(ppid) === process.pid && name.join(' ').includes('esbuild')) {
167
+ found.push([Number(pid), Number(rss) / 1024]);
168
+ }
169
+ }
170
+ } catch {
171
+ // ignore: no ps available means no monitoring, not a broken dev server
172
+ }
173
+ return found;
174
+ }
175
+
176
+ // RSS of our esbuild service, in MB (0 when not running).
177
+ function esbuildServiceRssMb() {
178
+ return esbuildServices().reduce((total, [, rssMb]) => total + rssMb, 0);
179
+ }
180
+
181
+ interface DevServerHandle {
182
+ stop(closeActiveConnections?: boolean): unknown;
183
+ /** Resolves once nothing is being served (or the timeout elapses). */
184
+ drain?(timeoutMs: number): Promise<boolean>;
185
+ }
186
+
187
+ /**
188
+ * Replace this process with a fresh dev server. A real execve keeps the pid, the stdio and the
189
+ * shell's job control intact, so the restart is invisible from the terminal - and because the image
190
+ * is replaced, every byte of the stale module graph goes away. Bun exposes no exec, so libc's is
191
+ * called directly; where that is unavailable (Windows) the replacement is spawned instead, which
192
+ * frees the same memory but detaches the server from the shell's foreground job.
193
+ */
194
+ async function restartDevProcess(server: DevServerHandle): Promise<void> {
195
+ try {
196
+ // Stop accepting, then let what is already being served finish: an exec
197
+ // mid-response would leave the browser with a truncated page and no error.
198
+ server.stop(false);
199
+ await server.drain?.(drainTimeoutMs);
200
+ server.stop(true);
201
+ } catch {
202
+ // ignore: we are being replaced regardless
203
+ }
204
+ // The esbuild service keeps its pipes across an exec, so it would outlive the
205
+ // image that owned it — with exactly the heap this restart is reclaiming.
206
+ for (const [pid] of esbuildServices()) {
207
+ try {
208
+ process.kill(pid, 'SIGTERM');
209
+ } catch {
210
+ // ignore: already gone
211
+ }
212
+ }
213
+ const argv = [process.execPath, '--conditions=react-server', ...process.argv.slice(1)];
214
+ const env = { ...process.env, [devRestartEnv]: '1' };
215
+ try {
216
+ await execSelf(argv, env);
217
+ } catch {
218
+ // ignore: fall through to the spawn path
219
+ }
220
+ // Only reached when exec is unavailable or failed.
221
+ Bun.spawn(argv, { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', env });
222
+ process.exit(0);
223
+ }
224
+
225
+ // execve(2) via libc. argv/envp are NUL-terminated C string arrays, and the
226
+ // pointer array must stay reachable until the call returns (it never does on
227
+ // success).
228
+ async function execSelf(argv: string[], env: Record<string, string | undefined>): Promise<void> {
229
+ if (process.platform === 'win32') return;
230
+ const { dlopen, FFIType, ptr, suffix } = await import('bun:ffi');
231
+ const libc = process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : `libc.${suffix}.6`;
232
+ const { symbols } = dlopen(libc, {
233
+ execve: {
234
+ args: [FFIType.cstring, FFIType.ptr, FFIType.ptr],
235
+ returns: FFIType.i32,
236
+ },
237
+ });
238
+ const encoder = new TextEncoder();
239
+ const cString = (value: string) => encoder.encode(`${value}\0`);
240
+ const cArray = (values: string[]) => {
241
+ const cells = values.map(cString);
242
+ const array = new BigInt64Array(cells.length + 1);
243
+ cells.forEach((cell, index) => {
244
+ array[index] = BigInt(ptr(cell));
245
+ });
246
+ return { array, cells };
247
+ };
248
+ const args = cArray(argv);
249
+ const envp = cArray(
250
+ Object.entries(env)
251
+ .filter(([, value]) => value !== undefined)
252
+ .map(([key, value]) => `${key}=${value}`),
253
+ );
254
+ symbols.execve(cString(argv[0]!), ptr(args.array), ptr(envp.array));
255
+ // execve only returns on failure; keep the buffers alive until then.
256
+ void [args.cells, envp.cells];
257
+ }
258
+
259
+ // Stop the server and run process `exit` handlers on a terminating signal,
260
+ // so killing the dev server never leaves orphaned subprocesses behind.
261
+ function registerShutdown(server: DevServerHandle) {
262
+ let closing = false;
263
+ const shutdown = () => {
264
+ if (closing) return;
265
+ closing = true;
266
+ try {
267
+ server.stop(true);
268
+ } catch {
269
+ // ignore: process is exiting regardless
270
+ }
271
+ process.exit(0);
272
+ };
273
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {
274
+ process.once(signal, shutdown);
275
+ }
276
+ }
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { markBoot } from './boot-trace';
5
+ import { commandBinaryName, nameEsbuildProcess, namedBunBinary } from './named-bin';
6
+
7
+ const [, , command, ...args] = process.argv;
8
+ markBoot('cli:entry');
9
+
10
+ // A no-op once we already run under the command's name; otherwise it creates
11
+ // the hardlink so bin/pnext can exec it directly from the next run on.
12
+ namedBunBinary(commandBinaryName(command));
13
+
14
+ // Before any command lazily imports esbuild, which captures the binary path on
15
+ // module load.
16
+ nameEsbuildProcess();
17
+ markBoot('cli:naming');
18
+
19
+ try {
20
+ if (command === 'dev') {
21
+ const { dev } = await import('./dev');
22
+ await dev({
23
+ root: positionalRoot(args),
24
+ port: optionNumber(args, '--port'),
25
+ hostname: optionString(args, '--hostname'),
26
+ });
27
+ } else if (command === 'build') {
28
+ const { buildProject } = await import('./build');
29
+ await buildProject(positionalRoot(args), {
30
+ adapter: adapterOption(args),
31
+ verbose: args.includes('--verbose'),
32
+ debugBuildPaths: optionString(args, '--debug-build-paths'),
33
+ buildMode: buildModeOption(args),
34
+ debugPrerender: args.includes('--debug-prerender'),
35
+ });
36
+ // Nothing here keeps the event loop alive on success, but a project's
37
+ // next.config can (e.g. a stray setInterval) and Next's own `next build`
38
+ // force-exits regardless of such handles. Do the same so `build` always
39
+ // terminates once buildProject resolves.
40
+ process.exit(0);
41
+ } else if (command === 'start') {
42
+ const { start } = await import('./start');
43
+ await start({
44
+ root: positionalRoot(args),
45
+ port: optionNumber(args, '--port'),
46
+ hostname: optionString(args, '--hostname'),
47
+ });
48
+ } else if (command === 'analyze') {
49
+ const { analyzeProject } = await import('./analyze');
50
+ const { route, root } = analyzePositionals(args);
51
+ const result = await analyzeProject(root, {
52
+ compression: analyzeCompression(args),
53
+ route,
54
+ });
55
+ if (args.includes('--json')) {
56
+ console.log(JSON.stringify(result, null, 2));
57
+ } else {
58
+ const { printAnalyzeResult } = await import('./analyze-print');
59
+ printAnalyzeResult(result, { route, files: args.includes('--files') });
60
+ }
61
+ } else if (command === 'typegen') {
62
+ const { typegenProject } = await import('../typegen');
63
+ const result = await typegenProject(positionalRoot(args));
64
+ console.log(
65
+ `Generated ${result.routes} route types, ${result.aliases} aliases, and ${result.checks} checks at ${result.file}`,
66
+ );
67
+ // Like `next typegen`, exit even when the loaded next.config leaves open
68
+ // handles (timers/connections) alive — a one-shot command must not hang.
69
+ process.exit(0);
70
+ } else {
71
+ printHelp();
72
+ process.exit(command ? 1 : 0);
73
+ }
74
+ } catch (error) {
75
+ const formatted = formatCliError(error);
76
+ console.error(formatted.message);
77
+ if (formatted.trace) console.log(`\n${formatted.trace}`);
78
+ process.exit(1);
79
+ }
80
+
81
+ function formatCliError(error: unknown) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ const traceStart = message.indexOf('\npnext client import trace for route ');
84
+ if (traceStart === -1) return { message };
85
+ return {
86
+ message: message.slice(0, traceStart),
87
+ trace: message.slice(traceStart + 1),
88
+ };
89
+ }
90
+
91
+ function positionalRoot(args: string[]) {
92
+ return positionals(args)[0];
93
+ }
94
+
95
+ // The analyze command takes optional [route] and [directory] positionals. A
96
+ // leading-slash argument is the route filter unless it points at a directory
97
+ // that looks like a project root — system directories like /home or /run
98
+ // collide with real route names.
99
+ function analyzePositionals(args: string[]) {
100
+ let route: string | undefined;
101
+ let root: string | undefined;
102
+ for (const arg of positionals(args)) {
103
+ if (!route && arg.startsWith('/') && !isProjectDirectory(arg)) route = arg;
104
+ else root ??= arg;
105
+ }
106
+ return { route, root };
107
+ }
108
+
109
+ function isProjectDirectory(candidate: string) {
110
+ if (!isDirectory(candidate)) return false;
111
+ return ['package.json', '.pnext', 'app', path.join('src', 'app')].some(entry =>
112
+ existsSync(path.join(candidate, entry)),
113
+ );
114
+ }
115
+
116
+ function positionals(args: string[]) {
117
+ const out: string[] = [];
118
+ for (let index = 0; index < args.length; index += 1) {
119
+ const arg = args[index];
120
+ if (!arg) continue;
121
+ if (
122
+ arg === '--port' ||
123
+ arg === '--adapter' ||
124
+ arg === '--hostname' ||
125
+ arg === '--debug-build-paths' ||
126
+ arg === '--experimental-build-mode'
127
+ ) {
128
+ index += 1;
129
+ continue;
130
+ }
131
+ if (!arg.startsWith('--')) out.push(arg);
132
+ }
133
+ return out;
134
+ }
135
+
136
+ function isDirectory(candidate: string) {
137
+ try {
138
+ return statSync(candidate).isDirectory();
139
+ } catch {
140
+ return false;
141
+ }
142
+ }
143
+
144
+ function optionNumber(args: string[], name: string) {
145
+ const index = args.indexOf(name);
146
+ if (index === -1) return undefined;
147
+ const value = args[index + 1];
148
+ return value ? Number(value) : undefined;
149
+ }
150
+
151
+ function optionString(args: string[], name: string) {
152
+ // Accept both `--name value` and `--name=value`.
153
+ const inline = args.find(arg => arg.startsWith(`${name}=`));
154
+ if (inline !== undefined) return inline.slice(name.length + 1);
155
+ const index = args.indexOf(name);
156
+ if (index === -1) return undefined;
157
+ return args[index + 1];
158
+ }
159
+
160
+ /**
161
+ * `next build --experimental-build-mode compile|generate` parity. Both the
162
+ * space (`--experimental-build-mode generate`) and equals
163
+ * (`--experimental-build-mode=generate`) forms are accepted; `generate` runs
164
+ * the standard full build (idempotent over a prior `compile` run).
165
+ */
166
+ function buildModeOption(args: string[]): 'compile' | 'generate' | undefined {
167
+ const value = optionString(args, '--experimental-build-mode');
168
+ if (value === 'compile' || value === 'generate') return value;
169
+ if (value !== undefined) {
170
+ console.error(
171
+ `error: option '--experimental-build-mode <mode>' argument '${value}' is invalid. Expected 'compile' or 'generate'.`,
172
+ );
173
+ process.exit(1);
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ function adapterOption(args: string[]) {
179
+ const index = args.indexOf('--adapter');
180
+ if (index === -1) return undefined;
181
+ const adapter = args[index + 1];
182
+ if (adapter === 'vercel') return adapter;
183
+ throw new Error(`Unsupported adapter: ${adapter ?? ''}`);
184
+ }
185
+
186
+ function analyzeCompression(args: string[]) {
187
+ return args.includes('--brotli') ? 'brotli' : 'gzip';
188
+ }
189
+
190
+ function printHelp() {
191
+ console.log(`pnext dev [directory] [--port 3000] [--hostname 127.0.0.1]
192
+ pnext build [directory] [--adapter vercel] [--verbose]
193
+ pnext start [directory] [--port 3000] [--hostname 127.0.0.1]
194
+ pnext analyze [route] [directory] [--brotli] [--files] [--json]
195
+ pnext typegen [directory]`);
196
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ chmodSync,
3
+ constants as fsConstants,
4
+ copyFileSync,
5
+ existsSync,
6
+ linkSync,
7
+ mkdirSync,
8
+ readdirSync,
9
+ renameSync,
10
+ rmSync,
11
+ } from 'node:fs';
12
+ import { createRequire } from 'node:module';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+
16
+ export const namedBinaryName = 'pnext';
17
+
18
+ // Keep in sync with the command case in bin/pnext, which resolves the same name.
19
+ const cliCommands = new Set(['dev', 'build', 'start', 'analyze', 'typegen']);
20
+
21
+ /** Process name for a CLI command: `pnext dev` runs as `pnext-dev`, etc. */
22
+ export function commandBinaryName(command: string | undefined) {
23
+ if (command && cliCommands.has(command)) return `${namedBinaryName}-${command}`;
24
+ return namedBinaryName;
25
+ }
26
+
27
+ /**
28
+ * Path to a bun executable whose file is literally named `name`.
29
+ *
30
+ * Activity Monitor and `ps -o ucomm` show the kernel's process name, set from the basename of the
31
+ * executable file at exec time - `argv0`, `exec -a` and `process.title` only change the argv that
32
+ * plain `ps` prints. So the running bun binary is hardlinked (or cloned/copied) into the user cache
33
+ * once per role and every process spawns through its role's link. Hardlinks share the inode, so the
34
+ * extra names are free.
35
+ *
36
+ * Falls back to the plain bun binary when the cache is unavailable: naming is cosmetic and must never
37
+ * break spawning.
38
+ */
39
+ export function namedBunBinary(name: string = namedBinaryName): string {
40
+ if (process.platform === 'win32') return process.execPath;
41
+ if (path.basename(process.execPath) === name) return process.execPath;
42
+ return namedExecutable(process.execPath, `bun-${Bun.version}`, name);
43
+ }
44
+
45
+ /**
46
+ * Label esbuild's service process. The esbuild JS API spawns its own Go binary, so pnext never execs
47
+ * it - but esbuild honors `ESBUILD_BINARY_PATH`, so pointing that at a hardlink of the platform
48
+ * binary renames the service. The lib captures the variable at module load, so this must run before
49
+ * anything imports esbuild.
50
+ */
51
+ export function nameEsbuildProcess() {
52
+ if (process.platform === 'win32') return;
53
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
54
+ if (process.env.ESBUILD_BINARY_PATH) return; // respect an explicit override
55
+ try {
56
+ const require = createRequire(import.meta.url);
57
+ const { version } = require('esbuild/package.json') as { version: string };
58
+ const binPath = require.resolve(`@esbuild/${process.platform}-${process.arch}/bin/esbuild`);
59
+ const named = namedExecutable(binPath, `esbuild-${version}`, 'pnext-esbuild');
60
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
61
+ if (named !== binPath) process.env.ESBUILD_BINARY_PATH = named;
62
+ } catch {
63
+ // unknown platform package or unavailable cache: esbuild resolves its own
64
+ // binary and just keeps its name
65
+ }
66
+ }
67
+
68
+ function namedExecutable(sourcePath: string, versionDir: string, name: string): string {
69
+ try {
70
+ // One directory per tool version, so upgrades switch paths instead of
71
+ // serving a stale binary.
72
+ const binRoot = path.join(cacheRoot(), 'bin');
73
+ const dir = path.join(binRoot, versionDir);
74
+ const named = path.join(dir, name);
75
+ if (!existsSync(named)) {
76
+ mkdirSync(dir, { recursive: true });
77
+ // Land under a temp name and rename, so a concurrent pnext process can
78
+ // never exec a half-written binary.
79
+ const staging = path.join(dir, `.${name}-${process.pid}`);
80
+ rmSync(staging, { force: true });
81
+ try {
82
+ linkSync(sourcePath, staging);
83
+ } catch {
84
+ // Cross-volume or hardlink-restricted: clone when the filesystem
85
+ // supports it (free on APFS), else fall back to a full copy.
86
+ copyFileSync(sourcePath, staging, fsConstants.COPYFILE_FICLONE);
87
+ chmodSync(staging, 0o755);
88
+ }
89
+ renameSync(staging, named);
90
+ removeStaleVersions(binRoot, path.basename(dir));
91
+ }
92
+ return named;
93
+ } catch {
94
+ return sourcePath;
95
+ }
96
+ }
97
+
98
+ // Best-effort sweep of binaries linked for previous versions of the same
99
+ // tool: keeping `bun-1.3.6` removes other `bun-*` dirs but leaves
100
+ // `esbuild-*` alone.
101
+ function removeStaleVersions(binRoot: string, keep: string) {
102
+ const toolPrefix = keep.slice(0, keep.indexOf('-') + 1);
103
+ try {
104
+ for (const entry of readdirSync(binRoot)) {
105
+ if (entry === keep || !entry.startsWith(toolPrefix)) continue;
106
+ rmSync(path.join(binRoot, entry), { recursive: true, force: true });
107
+ }
108
+ } catch {
109
+ // ignore: stale versions only waste a hardlink's worth of space
110
+ }
111
+ }
112
+
113
+ /** pnext's user cache dir — survives the build wiping the output directory. */
114
+ export function cacheRoot() {
115
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
116
+ const xdg = process.env.XDG_CACHE_HOME;
117
+ if (xdg) return path.join(xdg, 'pnext');
118
+ return path.join(os.homedir(), '.cache', 'pnext');
119
+ }