@wular/pnext 0.0.4 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -16
- package/bin/pnext +6 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +87 -22
- package/reference/config.md +40 -93
- package/reference/css.md +42 -33
- package/reference/dev.md +26 -33
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +31 -46
- package/reference/navigation.md +65 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +78 -75
- package/reference/routing.md +97 -107
- package/reference/typegen.md +27 -29
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +121 -114
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +52 -53
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +72 -64
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +94 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +802 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +32 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +2022 -1995
- package/src/{api → client}/router/types.ts +98 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +86 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2087 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +87 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +954 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +428 -406
- package/src/styles.d.ts +9 -0
- package/src/types.ts +324 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/reference/overview.md +0 -35
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
package/src/cli/create.ts
CHANGED
|
@@ -1,43 +1,45 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { mkdir, readdir } from 'node:fs/promises'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { bold, cyan, dim, green } from '../utils/ansi'
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { mkdir, readdir } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { bold, cyan, dim, green } from '../utils/ansi'
|
|
5
|
+
import { pnextVersionRange } from '../utils/fs'
|
|
6
|
+
import { formatDuration } from '../utils/verbose'
|
|
5
7
|
|
|
6
8
|
export interface CreateAppOptions {
|
|
7
|
-
install: boolean
|
|
9
|
+
install: boolean
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export async function createApp(dir: string | undefined, options: CreateAppOptions) {
|
|
11
13
|
if (!dir) {
|
|
12
|
-
console.log('Usage: pnext create <directory> [--no-install]')
|
|
13
|
-
throw new Error('pnext create requires a directory argument')
|
|
14
|
+
console.log('Usage: pnext create <directory> [--no-install]')
|
|
15
|
+
throw new Error('pnext create requires a directory argument')
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
const target = path.resolve(dir)
|
|
18
|
+
const target = path.resolve(dir)
|
|
17
19
|
if (existsSync(target)) {
|
|
18
20
|
if ((await readdir(target)).length > 0) {
|
|
19
|
-
throw new Error(`Directory already exists and is not empty: ${target}`)
|
|
21
|
+
throw new Error(`Directory already exists and is not empty: ${target}`)
|
|
20
22
|
}
|
|
21
23
|
} else {
|
|
22
|
-
await mkdir(target, { recursive: true })
|
|
24
|
+
await mkdir(target, { recursive: true })
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const name = sanitizePackageName(path.basename(target))
|
|
26
|
-
console.log(`${cyan('▲')} ${bold('pnext create')} ${dim(`— scaffolding ${name}`)}\n`)
|
|
27
|
+
const name = sanitizePackageName(path.basename(target))
|
|
28
|
+
console.log(`${cyan('▲')} ${bold('pnext create')} ${dim(`— scaffolding ${name}`)}\n`)
|
|
27
29
|
|
|
28
|
-
const files = scaffoldFiles(name)
|
|
30
|
+
const files = scaffoldFiles(name)
|
|
29
31
|
await Promise.all(
|
|
30
32
|
Object.entries(files).map(([file, source]) => Bun.write(path.join(target, file), source)),
|
|
31
|
-
)
|
|
32
|
-
for (const file of Object.keys(files)) console.log(` ${green('+')} ${dim(file)}`)
|
|
33
|
+
)
|
|
34
|
+
for (const file of Object.keys(files)) console.log(` ${green('+')} ${dim(file)}`)
|
|
33
35
|
|
|
34
|
-
let installed = false
|
|
36
|
+
let installed = false
|
|
35
37
|
if (options.install) {
|
|
36
|
-
console.log('')
|
|
37
|
-
installed = await install(target, dir)
|
|
38
|
+
console.log('')
|
|
39
|
+
installed = await install(target, dir)
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
printNextSteps(dir, installed)
|
|
42
|
+
printNextSteps(dir, installed)
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
// npm package name rules: lowercase, url-safe, no leading dot/underscore.
|
|
@@ -46,71 +48,68 @@ function sanitizePackageName(raw: string) {
|
|
|
46
48
|
.trim()
|
|
47
49
|
.toLowerCase()
|
|
48
50
|
.replace(/[^a-z0-9._-]+/g, '-')
|
|
49
|
-
.replace(/^[-._]+|[-._]+$/g, '')
|
|
50
|
-
return name || 'pnext-app'
|
|
51
|
+
.replace(/^[-._]+|[-._]+$/g, '')
|
|
52
|
+
return name || 'pnext-app'
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
async function install(target: string, dir: string) {
|
|
54
|
-
const stop = spinner('Installing dependencies...')
|
|
55
|
-
const start = Bun.nanoseconds()
|
|
56
|
-
const proc = Bun.spawn(['bun', 'install'], { cwd: target, stdout: 'pipe', stderr: 'pipe' })
|
|
56
|
+
const stop = spinner('Installing dependencies...')
|
|
57
|
+
const start = Bun.nanoseconds()
|
|
58
|
+
const proc = Bun.spawn(['bun', 'install'], { cwd: target, stdout: 'pipe', stderr: 'pipe' })
|
|
57
59
|
// Drain both pipes: an unread pipe can fill and stall the child.
|
|
58
60
|
const [stderr, , code] = await Promise.all([
|
|
59
61
|
new Response(proc.stderr).text(),
|
|
60
62
|
new Response(proc.stdout).text(),
|
|
61
63
|
proc.exited,
|
|
62
|
-
])
|
|
63
|
-
stop()
|
|
64
|
+
])
|
|
65
|
+
stop()
|
|
64
66
|
|
|
65
67
|
if (code === 0) {
|
|
66
|
-
const durationMs = (Bun.nanoseconds() - start) / 1e6
|
|
67
|
-
console.log(
|
|
68
|
-
|
|
68
|
+
const durationMs = (Bun.nanoseconds() - start) / 1e6
|
|
69
|
+
console.log(
|
|
70
|
+
`${green('✓')} ${bold('Installed dependencies')} ${dim(`in ${formatDuration(durationMs)}`)}`,
|
|
71
|
+
)
|
|
72
|
+
return true
|
|
69
73
|
}
|
|
70
|
-
console.log(`${dim('Install failed. Run it manually:')}`)
|
|
71
|
-
console.log(` ${cyan('cd')} ${dir}`)
|
|
72
|
-
console.log(` ${cyan('bun install')}`)
|
|
73
|
-
if (stderr.trim()) console.log(dim(stderr.trim()))
|
|
74
|
-
return false
|
|
74
|
+
console.log(`${dim('Install failed. Run it manually:')}`)
|
|
75
|
+
console.log(` ${cyan('cd')} ${dir}`)
|
|
76
|
+
console.log(` ${cyan('bun install')}`)
|
|
77
|
+
if (stderr.trim()) console.log(dim(stderr.trim()))
|
|
78
|
+
return false
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
/** Braille spinner, TTY-only; a plain line prints once and stays put otherwise. */
|
|
78
82
|
function spinner(label: string) {
|
|
79
83
|
if (!process.stdout.isTTY) {
|
|
80
|
-
console.log(label)
|
|
81
|
-
return () => undefined
|
|
84
|
+
console.log(label)
|
|
85
|
+
return () => undefined
|
|
82
86
|
}
|
|
83
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
84
|
-
let frame = 0
|
|
85
|
-
process.stdout.write(`${cyan(frames[0]!)} ${label}`)
|
|
87
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
88
|
+
let frame = 0
|
|
89
|
+
process.stdout.write(`${cyan(frames[0]!)} ${label}`)
|
|
86
90
|
const timer = setInterval(() => {
|
|
87
|
-
frame = (frame + 1) % frames.length
|
|
88
|
-
process.stdout.write(`\r${cyan(frames[frame]!)} ${label}`)
|
|
89
|
-
}, 80)
|
|
91
|
+
frame = (frame + 1) % frames.length
|
|
92
|
+
process.stdout.write(`\r${cyan(frames[frame]!)} ${label}`)
|
|
93
|
+
}, 80)
|
|
90
94
|
return () => {
|
|
91
|
-
clearInterval(timer)
|
|
92
|
-
process.stdout.write('\r\x1b[K')
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function formatDuration(durationMs: number) {
|
|
97
|
-
const totalSeconds = Math.max(0, durationMs) / 1000;
|
|
98
|
-
return `${totalSeconds.toFixed(totalSeconds < 10 ? 2 : 1)}s`;
|
|
95
|
+
clearInterval(timer)
|
|
96
|
+
process.stdout.write('\r\x1b[K')
|
|
97
|
+
}
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
function printNextSteps(dir: string, installed: boolean) {
|
|
102
|
-
const commands = installed ? [`cd ${dir}`, 'bun dev'] : [`cd ${dir}`, 'bun install', 'bun dev']
|
|
103
|
-
const title = 'Next steps'
|
|
104
|
-
const width = Math.max(title.length, ...commands.map(c => c.length)) + 2
|
|
101
|
+
const commands = installed ? [`cd ${dir}`, 'bun dev'] : [`cd ${dir}`, 'bun install', 'bun dev']
|
|
102
|
+
const title = 'Next steps'
|
|
103
|
+
const width = Math.max(title.length, ...commands.map(c => c.length)) + 2
|
|
105
104
|
const row = (text: string, colorFn: (s: string) => string = s => s) =>
|
|
106
|
-
`${dim('│')} ${colorFn(text.padEnd(width))} ${dim('│')}
|
|
105
|
+
`${dim('│')} ${colorFn(text.padEnd(width))} ${dim('│')}`
|
|
107
106
|
|
|
108
|
-
console.log('')
|
|
109
|
-
console.log(dim(`┌${'─'.repeat(width + 2)}┐`))
|
|
110
|
-
console.log(row(title, bold))
|
|
111
|
-
console.log(row(''))
|
|
112
|
-
for (const cmd of commands) console.log(row(cmd, cyan))
|
|
113
|
-
console.log(dim(`└${'─'.repeat(width + 2)}┘`))
|
|
107
|
+
console.log('')
|
|
108
|
+
console.log(dim(`┌${'─'.repeat(width + 2)}┐`))
|
|
109
|
+
console.log(row(title, bold))
|
|
110
|
+
console.log(row(''))
|
|
111
|
+
for (const cmd of commands) console.log(row(cmd, cyan))
|
|
112
|
+
console.log(dim(`└${'─'.repeat(width + 2)}┘`))
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
function scaffoldFiles(name: string): Record<string, string> {
|
|
@@ -119,9 +118,18 @@ function scaffoldFiles(name: string): Record<string, string> {
|
|
|
119
118
|
{
|
|
120
119
|
name,
|
|
121
120
|
private: true,
|
|
122
|
-
scripts: {
|
|
121
|
+
scripts: {
|
|
122
|
+
dev: 'pnext dev',
|
|
123
|
+
build: 'pnext build',
|
|
124
|
+
start: 'pnext start',
|
|
125
|
+
analyze: 'pnext analyze',
|
|
126
|
+
},
|
|
123
127
|
dependencies: { preact: '^10' },
|
|
124
|
-
devDependencies: {
|
|
128
|
+
devDependencies: {
|
|
129
|
+
'@wular/pnext': pnextVersionRange(),
|
|
130
|
+
typescript: '^5',
|
|
131
|
+
'@types/bun': '^1',
|
|
132
|
+
},
|
|
125
133
|
},
|
|
126
134
|
null,
|
|
127
135
|
2,
|
|
@@ -141,6 +149,6 @@ function scaffoldFiles(name: string): Record<string, string> {
|
|
|
141
149
|
'app/counter.tsx': `'use client';\n\nimport { useState } from 'preact/hooks';\n\nexport default function Counter() {\n const [count, setCount] = useState(0);\n return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;\n}\n`,
|
|
142
150
|
'app/globals.css': `body {\n margin: 0;\n font-family: system-ui, sans-serif;\n}\n`,
|
|
143
151
|
'.gitignore': `node_modules\n.pnext\n`,
|
|
144
|
-
'README.md': `# ${name}\n\nA pnext app.\n\n\`\`\`\nbun install\nbun dev\n\`\`\`\n\nDocs: node_modules/@wular/pnext/reference/
|
|
145
|
-
}
|
|
152
|
+
'README.md': `# ${name}\n\nA pnext app.\n\n\`\`\`\nbun install\nbun dev\n\`\`\`\n\nDocs: node_modules/@wular/pnext/reference/getting-started.md\n`,
|
|
153
|
+
}
|
|
146
154
|
}
|
package/src/cli/dev.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { dim } from '../utils/ansi'
|
|
2
|
-
import { moduleGenerationStats } from '../
|
|
3
|
-
import { markBoot, printBootTrace } from './boot
|
|
4
|
-
import { browserHost, ensurePortFree, openBrowser, printServerReady } from './serve
|
|
1
|
+
import { dim } from '../utils/ansi'
|
|
2
|
+
import { moduleGenerationStats } from '../runtime/module-generations'
|
|
3
|
+
import { markBoot, printBootTrace } from './boot/trace'
|
|
4
|
+
import { browserHost, ensurePortFree, openBrowser, printServerReady } from './serve/ui'
|
|
5
5
|
|
|
6
|
-
export const devProcessTitle = 'pnext-dev'
|
|
7
|
-
export const devRestartEnv = 'PNEXT_DEV_RESTART'
|
|
6
|
+
export const devProcessTitle = 'pnext-dev'
|
|
7
|
+
export const devRestartEnv = 'PNEXT_DEV_RESTART'
|
|
8
8
|
|
|
9
|
-
const defaultMaxDevRssMb = 2048
|
|
10
|
-
const defaultMaxEsbuildRssMb = 1024
|
|
9
|
+
const defaultMaxDevRssMb = 2048
|
|
10
|
+
const defaultMaxEsbuildRssMb = 1024
|
|
11
11
|
// A leaf edit supersedes a couple of generations, so this caps stale mass at roughly 300 MB -
|
|
12
12
|
// about one recycle per 75 leaf edits, and one per ~30 on wide-shared-module edits that rename
|
|
13
13
|
// most of a route graph.
|
|
14
|
-
const defaultMaxStaleModules = 150
|
|
14
|
+
const defaultMaxStaleModules = 150
|
|
15
15
|
/** How long a recycle waits for in-flight responses before replacing the image. */
|
|
16
|
-
const drainTimeoutMs = 5_000
|
|
16
|
+
const drainTimeoutMs = 5_000
|
|
17
17
|
|
|
18
18
|
export function nameDevProcess() {
|
|
19
|
-
process.title = devProcessTitle
|
|
19
|
+
process.title = devProcessTitle
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface DevOptions {
|
|
23
|
-
root?: string
|
|
24
|
-
port?: number
|
|
25
|
-
hostname?: string
|
|
23
|
+
root?: string
|
|
24
|
+
port?: number
|
|
25
|
+
hostname?: string
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -31,49 +31,49 @@ export interface DevOptions {
|
|
|
31
31
|
* place, which frees the module graph without paying a second process spawn on every boot.
|
|
32
32
|
*/
|
|
33
33
|
export async function dev(options: DevOptions = {}) {
|
|
34
|
-
nameDevProcess()
|
|
34
|
+
nameDevProcess()
|
|
35
35
|
// Loaded lazily so config resolution and the server graph start in parallel.
|
|
36
36
|
const [{ loadConfig }, { startDevServer }] = await Promise.all([
|
|
37
37
|
import('../config'),
|
|
38
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'
|
|
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
45
|
// A memory-triggered restart can briefly race the previous listener's
|
|
46
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()
|
|
47
|
+
await ensurePortFree(port, hostname, { attempts: 10 })
|
|
48
|
+
markBoot('dev:port')
|
|
49
|
+
const start = Date.now()
|
|
50
50
|
// Bisect switch, same role `PNEXT_DEV_RESTART_CACHE=0` plays for the restart
|
|
51
51
|
// caches: turns the boot warm-up off so a measurement can price it.
|
|
52
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()
|
|
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
60
|
// Boot compiles nothing (config loads natively), so the first compile would pay esbuild's service
|
|
61
61
|
// spawn. Warm it here, after the banner - readiness must not wait on it. PNEXT_DEV_ESBUILD_WARM=0 opts out.
|
|
62
62
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
63
63
|
if (process.env.PNEXT_DEV_ESBUILD_WARM !== '0') {
|
|
64
|
-
void import('../utils/esbuild').then(module => module.warmEsbuildService())
|
|
64
|
+
void import('../utils/esbuild').then(module => module.warmEsbuildService())
|
|
65
65
|
}
|
|
66
|
-
registerShutdown(server)
|
|
67
|
-
watchServerMemory(server)
|
|
68
|
-
watchStaleModules(server)
|
|
69
|
-
watchEsbuildMemory()
|
|
66
|
+
registerShutdown(server)
|
|
67
|
+
watchServerMemory(server)
|
|
68
|
+
watchStaleModules(server)
|
|
69
|
+
watchEsbuildMemory()
|
|
70
70
|
// Hold the browser until the entry route has compiled, so it opens onto an
|
|
71
71
|
// already-built page instead of a multi-second cold compile.
|
|
72
|
-
if (server.warmup) console.log(dim('Warming up and opening the browser...'))
|
|
73
|
-
await server.warmup
|
|
72
|
+
if (server.warmup) console.log(dim('Warming up and opening the browser...'))
|
|
73
|
+
await server.warmup
|
|
74
74
|
// A memory-triggered restart should refresh silently, not open another tab.
|
|
75
|
-
if (process.env[devRestartEnv] !== '1') openBrowser(url)
|
|
76
|
-
return server
|
|
75
|
+
if (process.env[devRestartEnv] !== '1') openBrowser(url)
|
|
76
|
+
return server
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Every dev reload imports changed modules under fresh URLs, and the ES module
|
|
@@ -82,22 +82,22 @@ export async function dev(options: DevOptions = {}) {
|
|
|
82
82
|
// preserved on disk) for getting the memory back.
|
|
83
83
|
function watchServerMemory(server: DevServerHandle) {
|
|
84
84
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
85
|
-
const limitMb = Number(process.env.PNEXT_DEV_MAX_RSS_MB || defaultMaxDevRssMb)
|
|
86
|
-
if (!Number.isFinite(limitMb) || limitMb <= 0) return
|
|
87
|
-
const limitBytes = limitMb * 1024 * 1024
|
|
85
|
+
const limitMb = Number(process.env.PNEXT_DEV_MAX_RSS_MB || defaultMaxDevRssMb)
|
|
86
|
+
if (!Number.isFinite(limitMb) || limitMb <= 0) return
|
|
87
|
+
const limitBytes = limitMb * 1024 * 1024
|
|
88
88
|
const timer = setInterval(() => {
|
|
89
|
-
const rss = process.memoryUsage.rss()
|
|
90
|
-
if (rss < limitBytes) return
|
|
91
|
-
clearInterval(timer)
|
|
92
|
-
const usedMb = Math.round(rss / (1024 * 1024))
|
|
89
|
+
const rss = process.memoryUsage.rss()
|
|
90
|
+
if (rss < limitBytes) return
|
|
91
|
+
clearInterval(timer)
|
|
92
|
+
const usedMb = Math.round(rss / (1024 * 1024))
|
|
93
93
|
console.log(
|
|
94
94
|
dim(
|
|
95
95
|
`Dev server reached ${usedMb}MB of memory (limit ${limitMb}MB, set PNEXT_DEV_MAX_RSS_MB to change); restarting to release stale modules...`,
|
|
96
96
|
),
|
|
97
|
-
)
|
|
98
|
-
void restartDevProcess(server)
|
|
99
|
-
}, 15_000)
|
|
100
|
-
timer.unref()
|
|
97
|
+
)
|
|
98
|
+
void restartDevProcess(server)
|
|
99
|
+
}, 15_000)
|
|
100
|
+
timer.unref()
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// The esbuild Go service self-drains its heap when idle, so two consecutive readings above the
|
|
@@ -105,35 +105,35 @@ function watchServerMemory(server: DevServerHandle) {
|
|
|
105
105
|
// builds, never incremental contexts. A stop that races an in-flight build fails that one build;
|
|
106
106
|
// dev asset caches drop failed builds, so the next request retries.
|
|
107
107
|
function watchEsbuildMemory() {
|
|
108
|
-
if (process.platform === 'win32') return
|
|
108
|
+
if (process.platform === 'win32') return
|
|
109
109
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
110
|
-
const limitMb = Number(process.env.PNEXT_DEV_MAX_ESBUILD_RSS_MB || defaultMaxEsbuildRssMb)
|
|
111
|
-
if (!Number.isFinite(limitMb) || limitMb <= 0) return
|
|
112
|
-
let highTicks = 0
|
|
110
|
+
const limitMb = Number(process.env.PNEXT_DEV_MAX_ESBUILD_RSS_MB || defaultMaxEsbuildRssMb)
|
|
111
|
+
if (!Number.isFinite(limitMb) || limitMb <= 0) return
|
|
112
|
+
let highTicks = 0
|
|
113
113
|
const timer = setInterval(() => {
|
|
114
|
-
const rssMb = esbuildServiceRssMb()
|
|
115
|
-
highTicks = rssMb > limitMb ? highTicks + 1 : 0
|
|
116
|
-
if (highTicks < 2) return
|
|
117
|
-
highTicks = 0
|
|
114
|
+
const rssMb = esbuildServiceRssMb()
|
|
115
|
+
highTicks = rssMb > limitMb ? highTicks + 1 : 0
|
|
116
|
+
if (highTicks < 2) return
|
|
117
|
+
highTicks = 0
|
|
118
118
|
console.log(
|
|
119
119
|
dim(
|
|
120
120
|
`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...`,
|
|
121
121
|
),
|
|
122
|
-
)
|
|
122
|
+
)
|
|
123
123
|
void (async () => {
|
|
124
124
|
try {
|
|
125
|
-
const esbuild = await import('esbuild')
|
|
126
|
-
await esbuild.stop()
|
|
125
|
+
const esbuild = await import('esbuild')
|
|
126
|
+
await esbuild.stop()
|
|
127
127
|
} catch {
|
|
128
128
|
// ignore: the service may already be gone
|
|
129
129
|
}
|
|
130
130
|
// stop() can strand in-flight build promises unsettled; fail their
|
|
131
131
|
// waiters so requests retry against the respawned service.
|
|
132
|
-
const { failInFlightClientBuilds } = await import('../dev/server')
|
|
133
|
-
failInFlightClientBuilds('esbuild service restarted by the memory watchdog')
|
|
134
|
-
})()
|
|
135
|
-
}, 15_000)
|
|
136
|
-
timer.unref()
|
|
132
|
+
const { failInFlightClientBuilds } = await import('../dev/server')
|
|
133
|
+
failInFlightClientBuilds('esbuild service restarted by the memory watchdog')
|
|
134
|
+
})()
|
|
135
|
+
}, 15_000)
|
|
136
|
+
timer.unref()
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -146,48 +146,48 @@ function watchEsbuildMemory() {
|
|
|
146
146
|
*/
|
|
147
147
|
function watchStaleModules(server: DevServerHandle) {
|
|
148
148
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
149
|
-
const limit = Number(process.env.PNEXT_DEV_MAX_STALE_MODULES || defaultMaxStaleModules)
|
|
150
|
-
if (!Number.isFinite(limit) || limit <= 0) return
|
|
149
|
+
const limit = Number(process.env.PNEXT_DEV_MAX_STALE_MODULES || defaultMaxStaleModules)
|
|
150
|
+
if (!Number.isFinite(limit) || limit <= 0) return
|
|
151
151
|
const timer = setInterval(() => {
|
|
152
|
-
const { stale } = moduleGenerationStats()
|
|
153
|
-
if (stale < limit) return
|
|
154
|
-
clearInterval(timer)
|
|
152
|
+
const { stale } = moduleGenerationStats()
|
|
153
|
+
if (stale < limit) return
|
|
154
|
+
clearInterval(timer)
|
|
155
155
|
console.log(
|
|
156
156
|
dim(
|
|
157
157
|
`Dev server is holding ${stale} superseded module generations (limit ${limit}, set PNEXT_DEV_MAX_STALE_MODULES to change); restarting to release them...`,
|
|
158
158
|
),
|
|
159
|
-
)
|
|
160
|
-
void restartDevProcess(server)
|
|
161
|
-
}, 15_000)
|
|
162
|
-
timer.unref()
|
|
159
|
+
)
|
|
160
|
+
void restartDevProcess(server)
|
|
161
|
+
}, 15_000)
|
|
162
|
+
timer.unref()
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// Our esbuild service children: [pid, RSS in MB] per process.
|
|
166
166
|
function esbuildServices(): [number, number][] {
|
|
167
|
-
const found: [number, number][] = []
|
|
167
|
+
const found: [number, number][] = []
|
|
168
168
|
try {
|
|
169
|
-
const stdout = Bun.spawnSync(['ps', '-ax', '-o', 'pid=,ppid=,rss=,ucomm=']).stdout.toString()
|
|
169
|
+
const stdout = Bun.spawnSync(['ps', '-ax', '-o', 'pid=,ppid=,rss=,ucomm=']).stdout.toString()
|
|
170
170
|
for (const line of stdout.split('\n')) {
|
|
171
|
-
const [pid, ppid, rss, ...name] = line.trim().split(/\s+/)
|
|
171
|
+
const [pid, ppid, rss, ...name] = line.trim().split(/\s+/)
|
|
172
172
|
if (Number(ppid) === process.pid && name.join(' ').includes('esbuild')) {
|
|
173
|
-
found.push([Number(pid), Number(rss) / 1024])
|
|
173
|
+
found.push([Number(pid), Number(rss) / 1024])
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
} catch {
|
|
177
177
|
// ignore: no ps available means no monitoring, not a broken dev server
|
|
178
178
|
}
|
|
179
|
-
return found
|
|
179
|
+
return found
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// RSS of our esbuild service, in MB (0 when not running).
|
|
183
183
|
function esbuildServiceRssMb() {
|
|
184
|
-
return esbuildServices().reduce((total, [, rssMb]) => total + rssMb, 0)
|
|
184
|
+
return esbuildServices().reduce((total, [, rssMb]) => total + rssMb, 0)
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
interface DevServerHandle {
|
|
188
|
-
stop(closeActiveConnections?: boolean): unknown
|
|
188
|
+
stop(closeActiveConnections?: boolean): unknown
|
|
189
189
|
/** Resolves once nothing is being served (or the timeout elapses). */
|
|
190
|
-
drain?(timeoutMs: number): Promise<boolean
|
|
190
|
+
drain?(timeoutMs: number): Promise<boolean>
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
@@ -201,9 +201,9 @@ async function restartDevProcess(server: DevServerHandle): Promise<void> {
|
|
|
201
201
|
try {
|
|
202
202
|
// Stop accepting, then let what is already being served finish: an exec
|
|
203
203
|
// mid-response would leave the browser with a truncated page and no error.
|
|
204
|
-
server.stop(false)
|
|
205
|
-
await server.drain?.(drainTimeoutMs)
|
|
206
|
-
server.stop(true)
|
|
204
|
+
server.stop(false)
|
|
205
|
+
await server.drain?.(drainTimeoutMs)
|
|
206
|
+
server.stop(true)
|
|
207
207
|
} catch {
|
|
208
208
|
// ignore: we are being replaced regardless
|
|
209
209
|
}
|
|
@@ -211,72 +211,72 @@ async function restartDevProcess(server: DevServerHandle): Promise<void> {
|
|
|
211
211
|
// image that owned it — with exactly the heap this restart is reclaiming.
|
|
212
212
|
for (const [pid] of esbuildServices()) {
|
|
213
213
|
try {
|
|
214
|
-
process.kill(pid, 'SIGTERM')
|
|
214
|
+
process.kill(pid, 'SIGTERM')
|
|
215
215
|
} catch {
|
|
216
216
|
// ignore: already gone
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
const argv = [process.execPath, '--conditions=react-server', ...process.argv.slice(1)]
|
|
220
|
-
const env = { ...process.env, [devRestartEnv]: '1' }
|
|
219
|
+
const argv = [process.execPath, '--conditions=react-server', ...process.argv.slice(1)]
|
|
220
|
+
const env = { ...process.env, [devRestartEnv]: '1' }
|
|
221
221
|
try {
|
|
222
|
-
await execSelf(argv, env)
|
|
222
|
+
await execSelf(argv, env)
|
|
223
223
|
} catch {
|
|
224
224
|
// ignore: fall through to the spawn path
|
|
225
225
|
}
|
|
226
226
|
// Only reached when exec is unavailable or failed.
|
|
227
|
-
Bun.spawn(argv, { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', env })
|
|
228
|
-
process.exit(0)
|
|
227
|
+
Bun.spawn(argv, { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', env })
|
|
228
|
+
process.exit(0)
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
// execve(2) via libc. argv/envp are NUL-terminated C string arrays, and the
|
|
232
232
|
// pointer array must stay reachable until the call returns (it never does on
|
|
233
233
|
// success).
|
|
234
234
|
async function execSelf(argv: string[], env: Record<string, string | undefined>): Promise<void> {
|
|
235
|
-
if (process.platform === 'win32') return
|
|
236
|
-
const { dlopen, FFIType, ptr, suffix } = await import('bun:ffi')
|
|
237
|
-
const libc = process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : `libc.${suffix}.6
|
|
235
|
+
if (process.platform === 'win32') return
|
|
236
|
+
const { dlopen, FFIType, ptr, suffix } = await import('bun:ffi')
|
|
237
|
+
const libc = process.platform === 'darwin' ? '/usr/lib/libSystem.B.dylib' : `libc.${suffix}.6`
|
|
238
238
|
const { symbols } = dlopen(libc, {
|
|
239
239
|
execve: {
|
|
240
240
|
args: [FFIType.cstring, FFIType.ptr, FFIType.ptr],
|
|
241
241
|
returns: FFIType.i32,
|
|
242
242
|
},
|
|
243
|
-
})
|
|
244
|
-
const encoder = new TextEncoder()
|
|
245
|
-
const cString = (value: string) => encoder.encode(`${value}\0`)
|
|
243
|
+
})
|
|
244
|
+
const encoder = new TextEncoder()
|
|
245
|
+
const cString = (value: string) => encoder.encode(`${value}\0`)
|
|
246
246
|
const cArray = (values: string[]) => {
|
|
247
|
-
const cells = values.map(cString)
|
|
248
|
-
const array = new BigInt64Array(cells.length + 1)
|
|
247
|
+
const cells = values.map(cString)
|
|
248
|
+
const array = new BigInt64Array(cells.length + 1)
|
|
249
249
|
cells.forEach((cell, index) => {
|
|
250
|
-
array[index] = BigInt(ptr(cell))
|
|
251
|
-
})
|
|
252
|
-
return { array, cells }
|
|
253
|
-
}
|
|
254
|
-
const args = cArray(argv)
|
|
250
|
+
array[index] = BigInt(ptr(cell))
|
|
251
|
+
})
|
|
252
|
+
return { array, cells }
|
|
253
|
+
}
|
|
254
|
+
const args = cArray(argv)
|
|
255
255
|
const envp = cArray(
|
|
256
256
|
Object.entries(env)
|
|
257
257
|
.filter(([, value]) => value !== undefined)
|
|
258
258
|
.map(([key, value]) => `${key}=${value}`),
|
|
259
|
-
)
|
|
260
|
-
symbols.execve(cString(argv[0]!), ptr(args.array), ptr(envp.array))
|
|
259
|
+
)
|
|
260
|
+
symbols.execve(cString(argv[0]!), ptr(args.array), ptr(envp.array))
|
|
261
261
|
// execve only returns on failure; keep the buffers alive until then.
|
|
262
|
-
void [args.cells, envp.cells]
|
|
262
|
+
void [args.cells, envp.cells]
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
// Stop the server and run process `exit` handlers on a terminating signal,
|
|
266
266
|
// so killing the dev server never leaves orphaned subprocesses behind.
|
|
267
267
|
function registerShutdown(server: DevServerHandle) {
|
|
268
|
-
let closing = false
|
|
268
|
+
let closing = false
|
|
269
269
|
const shutdown = () => {
|
|
270
|
-
if (closing) return
|
|
271
|
-
closing = true
|
|
270
|
+
if (closing) return
|
|
271
|
+
closing = true
|
|
272
272
|
try {
|
|
273
|
-
server.stop(true)
|
|
273
|
+
server.stop(true)
|
|
274
274
|
} catch {
|
|
275
275
|
// ignore: process is exiting regardless
|
|
276
276
|
}
|
|
277
|
-
process.exit(0)
|
|
278
|
-
}
|
|
277
|
+
process.exit(0)
|
|
278
|
+
}
|
|
279
279
|
for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {
|
|
280
|
-
process.once(signal, shutdown)
|
|
280
|
+
process.once(signal, shutdown)
|
|
281
281
|
}
|
|
282
282
|
}
|