@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
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
//
|
|
8
8
|
// The graph the hash walks is read from a persisted index keyed by (mtime, size, content hash), so a
|
|
9
9
|
// restart re-derives it with one stat per source instead of re-scanning and re-resolving every import.
|
|
10
|
-
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
11
|
-
import { cachedExistsSync } from '../utils/fs-cache'
|
|
12
|
-
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
13
|
-
import { createHash, randomUUID } from 'node:crypto'
|
|
14
|
-
import path from 'node:path'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import type { ResolvedConfig } from '../config'
|
|
17
|
-
import { writeFileAtomic } from '../utils/fs'
|
|
10
|
+
import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
11
|
+
import { cachedExistsSync } from '../utils/fs-cache'
|
|
12
|
+
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
13
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
import type { ResolvedConfig } from '../config'
|
|
17
|
+
import { writeFileAtomic } from '../utils/fs'
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Kill switch for the request-head trims: the naming walk's synchronous source
|
|
@@ -22,7 +22,7 @@ import { writeFileAtomic } from '../utils/fs';
|
|
|
22
22
|
*/
|
|
23
23
|
export function devHeadTrimEnabled(): boolean {
|
|
24
24
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
25
|
-
return process.env.PNEXT_HEAD_TRIM !== '0'
|
|
25
|
+
return process.env.PNEXT_HEAD_TRIM !== '0'
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// While a recursive fs watcher is reporting every save, the memoized graph is
|
|
@@ -31,27 +31,27 @@ export function devHeadTrimEnabled(): boolean {
|
|
|
31
31
|
// fails. Per cache root, so other processes' caches (tests, one-off renders)
|
|
32
32
|
// keep the stat-based freshness they rely on.
|
|
33
33
|
// Bisect seam: PNEXT_DEV_WATCH_FRESHNESS=0 restores the per-request re-stat.
|
|
34
|
-
const watcherFreshRoots = new Set<string>()
|
|
34
|
+
const watcherFreshRoots = new Set<string>()
|
|
35
35
|
|
|
36
36
|
export function setDevWatcherFreshness(outPath: string, trusted: boolean) {
|
|
37
|
-
if (trusted) watcherFreshRoots.add(cacheRoot(outPath))
|
|
38
|
-
else watcherFreshRoots.delete(cacheRoot(outPath))
|
|
37
|
+
if (trusted) watcherFreshRoots.add(cacheRoot(outPath))
|
|
38
|
+
else watcherFreshRoots.delete(cacheRoot(outPath))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function watcherFreshnessTrusted(root: string) {
|
|
42
42
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
43
|
-
return watcherFreshRoots.has(root) && process.env.PNEXT_DEV_WATCH_FRESHNESS !== '0'
|
|
43
|
+
return watcherFreshRoots.has(root) && process.env.PNEXT_DEV_WATCH_FRESHNESS !== '0'
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Files whose bytes are not source: hashed from stat, never read or scanned. */
|
|
47
|
-
const BINARY_SOURCE = /\.(?:png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|otf|eot|mp[34]|webm|pdf)$/i
|
|
47
|
+
const BINARY_SOURCE = /\.(?:png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|otf|eot|mp[34]|webm|pdf)$/i
|
|
48
48
|
|
|
49
|
-
const MISSING = 'missing'
|
|
49
|
+
const MISSING = 'missing'
|
|
50
50
|
|
|
51
51
|
// The framework's own package root. Compat runtime modules (next/form and
|
|
52
52
|
// friends) are compiled like app sources but live outside the workspace, so
|
|
53
53
|
// they need their own relocation anchor.
|
|
54
|
-
const frameworkRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
54
|
+
const frameworkRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* A source's identity for hashing and for the persisted index - relative to the workspace (or the
|
|
@@ -64,123 +64,129 @@ const frameworkRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
|
|
|
64
64
|
// Two `path.relative` calls per source per lookup, and every graph hash asks for its whole closure.
|
|
65
65
|
// The answer is a pure function of the pair and the set of sources is bounded by the app, so it is
|
|
66
66
|
// simply remembered.
|
|
67
|
-
const portablePaths = new Map<string, string>()
|
|
67
|
+
const portablePaths = new Map<string, string>()
|
|
68
68
|
// Same bisect seam as the naming walk it serves (PNEXT_DEV_GRAPH_FAST=0).
|
|
69
69
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
70
|
-
const memoPortablePaths = process.env.PNEXT_DEV_GRAPH_FAST !== '0'
|
|
70
|
+
const memoPortablePaths = process.env.PNEXT_DEV_GRAPH_FAST !== '0'
|
|
71
71
|
|
|
72
72
|
function portableSourcePath(file: string, workspaceRoot: string) {
|
|
73
|
-
if (!memoPortablePaths) return computePortableSourcePath(file, workspaceRoot)
|
|
74
|
-
const memoKey = `${workspaceRoot}\0${file}
|
|
75
|
-
const memoized = portablePaths.get(memoKey)
|
|
76
|
-
if (memoized !== undefined) return memoized
|
|
77
|
-
const computed = computePortableSourcePath(file, workspaceRoot)
|
|
78
|
-
portablePaths.set(memoKey, computed)
|
|
79
|
-
return computed
|
|
73
|
+
if (!memoPortablePaths) return computePortableSourcePath(file, workspaceRoot)
|
|
74
|
+
const memoKey = `${workspaceRoot}\0${file}`
|
|
75
|
+
const memoized = portablePaths.get(memoKey)
|
|
76
|
+
if (memoized !== undefined) return memoized
|
|
77
|
+
const computed = computePortableSourcePath(file, workspaceRoot)
|
|
78
|
+
portablePaths.set(memoKey, computed)
|
|
79
|
+
return computed
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function computePortableSourcePath(file: string, workspaceRoot: string) {
|
|
83
|
-
const inWorkspace = path.relative(workspaceRoot, file)
|
|
83
|
+
const inWorkspace = path.relative(workspaceRoot, file)
|
|
84
84
|
if (inWorkspace && !inWorkspace.startsWith('..') && !path.isAbsolute(inWorkspace)) {
|
|
85
|
-
return `w:${toPosix(inWorkspace)}
|
|
85
|
+
return `w:${toPosix(inWorkspace)}`
|
|
86
86
|
}
|
|
87
|
-
const inFramework = path.relative(frameworkRoot, file)
|
|
87
|
+
const inFramework = path.relative(frameworkRoot, file)
|
|
88
88
|
if (inFramework && !inFramework.startsWith('..') && !path.isAbsolute(inFramework)) {
|
|
89
|
-
return `f:${toPosix(inFramework)}
|
|
89
|
+
return `f:${toPosix(inFramework)}`
|
|
90
90
|
}
|
|
91
|
-
return `a:${toPosix(file)}
|
|
91
|
+
return `a:${toPosix(file)}`
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** Inverse of `portableSourcePath` for the roots this process is running under. */
|
|
95
95
|
function absoluteSourcePath(key: string, workspaceRoot: string) {
|
|
96
|
-
const value = key.slice(2).split('/').join(path.sep)
|
|
97
|
-
if (key.startsWith('w:')) return path.resolve(workspaceRoot, value)
|
|
98
|
-
if (key.startsWith('f:')) return path.resolve(frameworkRoot, value)
|
|
99
|
-
return value
|
|
96
|
+
const value = key.slice(2).split('/').join(path.sep)
|
|
97
|
+
if (key.startsWith('w:')) return path.resolve(workspaceRoot, value)
|
|
98
|
+
if (key.startsWith('f:')) return path.resolve(frameworkRoot, value)
|
|
99
|
+
return value
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
const toPosix = (file: string) => (path.sep === '/' ? file : file.split(path.sep).join('/'))
|
|
102
|
+
const toPosix = (file: string) => (path.sep === '/' ? file : file.split(path.sep).join('/'))
|
|
103
103
|
|
|
104
104
|
/** Stable identity for a source outside the dev cache — see `portableSourcePath`. */
|
|
105
105
|
export function devSourceIdentity(file: string, workspaceRoot: string) {
|
|
106
|
-
return portableSourcePath(file, workspaceRoot)
|
|
106
|
+
return portableSourcePath(file, workspaceRoot)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
interface SourceRecord {
|
|
110
|
-
mtimeMs: number
|
|
111
|
-
size: number
|
|
110
|
+
mtimeMs: number
|
|
111
|
+
size: number
|
|
112
112
|
/** Content hash (stat hash for binaries, `missing` when the file is gone). */
|
|
113
|
-
srcHash: string
|
|
113
|
+
srcHash: string
|
|
114
114
|
/** Resolved local import targets — the same edges the compile walk follows. */
|
|
115
|
-
imports: string[]
|
|
115
|
+
imports: string[]
|
|
116
116
|
/** Bare package specifiers, so a route's vendor demand is known before it compiles. */
|
|
117
|
-
packages: string[]
|
|
117
|
+
packages: string[]
|
|
118
118
|
/** Whether the source opens with `'use client'` — the layer boundary, recorded by the scan. */
|
|
119
|
-
client: boolean
|
|
119
|
+
client: boolean
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export interface DevModuleCacheOptions {
|
|
123
123
|
/** Local import targets, package specifiers and layer of `source`, as the compile walk sees them. */
|
|
124
|
-
edges: (
|
|
124
|
+
edges: (
|
|
125
|
+
file: string,
|
|
126
|
+
source: string,
|
|
127
|
+
) => { imports: string[]; packages: string[]; client: boolean }
|
|
125
128
|
/** Everything outside the sources that changes output: aliases, defines, compat. */
|
|
126
|
-
compileKey: string
|
|
129
|
+
compileKey: string
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
/** `[file, mtimeMs, size, srcHash]` — one indexed source, as callers revalidate it. */
|
|
130
|
-
export type DevGraphSource = [string, number, number, string]
|
|
133
|
+
export type DevGraphSource = [string, number, number, string]
|
|
131
134
|
|
|
132
135
|
/** `srcHash` for a source outside the index — `missing` when it cannot be read. */
|
|
133
|
-
export const DEV_SOURCE_MISSING = MISSING
|
|
136
|
+
export const DEV_SOURCE_MISSING = MISSING
|
|
134
137
|
|
|
135
138
|
/** Files whose `srcHash` comes from stat rather than content (see BINARY_SOURCE). */
|
|
136
139
|
export function isBinaryDevSource(file: string): boolean {
|
|
137
|
-
return BINARY_SOURCE.test(file)
|
|
140
|
+
return BINARY_SOURCE.test(file)
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/** The `srcHash` rule for a text source, for caches revalidating indexed records. */
|
|
141
144
|
export function devTextSourceHash(source: string): string {
|
|
142
|
-
return Bun.hash(source).toString(36)
|
|
145
|
+
return Bun.hash(source).toString(36)
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
export interface DevModuleCache {
|
|
146
149
|
/** Hash naming `file`'s artifact: its own source plus its whole source graph. */
|
|
147
|
-
graphHash(file: string): Promise<string
|
|
150
|
+
graphHash(file: string): Promise<string>
|
|
148
151
|
/** Everything the last `graphHash` of these files walked, as indexed records. */
|
|
149
|
-
graphSources(files: string[]): Promise<DevGraphSource[]
|
|
152
|
+
graphSources(files: string[]): Promise<DevGraphSource[]>
|
|
150
153
|
/** Everything outside the sources that names an artifact — compat, aliases, pnext itself. */
|
|
151
|
-
readonly graphKey: string
|
|
154
|
+
readonly graphKey: string
|
|
152
155
|
/** Forget the given sources and every module that reaches them (its dependents). */
|
|
153
|
-
invalidate(files: Iterable<string>): void
|
|
156
|
+
invalidate(files: Iterable<string>): void
|
|
154
157
|
/** Whether this source is already part of the compiled graph. */
|
|
155
|
-
knows(file: string): boolean
|
|
158
|
+
knows(file: string): boolean
|
|
156
159
|
/**
|
|
157
160
|
* Whether this source opens with `'use client'`, from the record the walk already scanned - the
|
|
158
161
|
* layer-aware seed used to re-read and re-scan every source of the route to learn the same thing, on the
|
|
159
162
|
* request the response is blocked on.
|
|
160
163
|
*/
|
|
161
|
-
isClientSource(file: string): Promise<boolean
|
|
164
|
+
isClientSource(file: string): Promise<boolean>
|
|
162
165
|
/**
|
|
163
166
|
* Every package specifier reachable from `file` - the route's vendor demand. With `prune`, a SEPARATE
|
|
164
167
|
* non-memoized walk that skips pruned files and their subtrees (layer-aware seeding); `closure()` keeps
|
|
165
168
|
* its superset for invalidation.
|
|
166
169
|
*/
|
|
167
|
-
packageDemand(
|
|
170
|
+
packageDemand(
|
|
171
|
+
file: string,
|
|
172
|
+
prune?: (file: string) => boolean | Promise<boolean>,
|
|
173
|
+
): Promise<string[]>
|
|
168
174
|
/**
|
|
169
175
|
* Freeze names for the duration of one compile pass: each source is re-checked once when the pass first
|
|
170
176
|
* touches it, never again while it runs. A hash that moved mid-pass would let a module bake an import
|
|
171
177
|
* href its target is then never written to - a dangling import Bun caches for the life of the process.
|
|
172
178
|
*/
|
|
173
|
-
hold(): () => void
|
|
179
|
+
hold(): () => void
|
|
174
180
|
/** Directory the compiled artifacts live in. */
|
|
175
|
-
readonly root: string
|
|
181
|
+
readonly root: string
|
|
176
182
|
}
|
|
177
183
|
|
|
178
|
-
const caches = new Map<string, DevModuleCache>()
|
|
179
|
-
const persistFlushes = new Set<() => void>()
|
|
184
|
+
const caches = new Map<string, DevModuleCache>()
|
|
185
|
+
const persistFlushes = new Set<() => void>()
|
|
180
186
|
|
|
181
187
|
/** Write every cache's pending graph.json now. A finished build must leave no unref'd timer renaming temp files under .pnext. */
|
|
182
188
|
export function flushDevModuleCaches() {
|
|
183
|
-
for (const flushNow of persistFlushes) flushNow()
|
|
189
|
+
for (const flushNow of persistFlushes) flushNow()
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
/** The cache for `config.outPath`, created once per process. */
|
|
@@ -188,16 +194,16 @@ export function devModuleCache(
|
|
|
188
194
|
config: ResolvedConfig,
|
|
189
195
|
options: DevModuleCacheOptions,
|
|
190
196
|
): DevModuleCache {
|
|
191
|
-
const root = cacheRoot(config.outPath)
|
|
192
|
-
const existing = caches.get(root)
|
|
193
|
-
if (existing) return existing
|
|
194
|
-
const created = createDevModuleCache(root, config.workspaceRoot, options)
|
|
195
|
-
caches.set(root, created)
|
|
196
|
-
return created
|
|
197
|
+
const root = cacheRoot(config.outPath)
|
|
198
|
+
const existing = caches.get(root)
|
|
199
|
+
if (existing) return existing
|
|
200
|
+
const created = createDevModuleCache(root, config.workspaceRoot, options)
|
|
201
|
+
caches.set(root, created)
|
|
202
|
+
return created
|
|
197
203
|
}
|
|
198
204
|
|
|
199
205
|
export function cacheRoot(outPath: string) {
|
|
200
|
-
return path.join(outPath, 'cache', 'server')
|
|
206
|
+
return path.join(outPath, 'cache', 'server')
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
function createDevModuleCache(
|
|
@@ -205,19 +211,19 @@ function createDevModuleCache(
|
|
|
205
211
|
workspaceRoot: string,
|
|
206
212
|
options: DevModuleCacheOptions,
|
|
207
213
|
): DevModuleCache {
|
|
208
|
-
const indexFile = path.join(root, 'graph.json')
|
|
214
|
+
const indexFile = path.join(root, 'graph.json')
|
|
209
215
|
// Compiler-generated sources — pnext's own output tree and the materialized
|
|
210
216
|
// pages-compat app — are rewritten from scratch on every boot and can land at
|
|
211
217
|
// a fresh path each time; they are hashed by content alone (below).
|
|
212
|
-
const outRoot = `${path.dirname(path.dirname(root))}${path.sep}
|
|
218
|
+
const outRoot = `${path.dirname(path.dirname(root))}${path.sep}`
|
|
213
219
|
const generated = (file: string) =>
|
|
214
|
-
file.startsWith(outRoot) || file.includes(`${path.sep}pnext-pages-compat${path.sep}`)
|
|
220
|
+
file.startsWith(outRoot) || file.includes(`${path.sep}pnext-pages-compat${path.sep}`)
|
|
215
221
|
const key = createHash('sha256')
|
|
216
222
|
.update(`${options.compileKey}\0${frameworkFingerprint()}`)
|
|
217
223
|
.digest('hex')
|
|
218
|
-
.slice(0, 16)
|
|
219
|
-
establishCacheTrust(root)
|
|
220
|
-
const records = indexRecords(readIndexFile(indexFile), key, workspaceRoot)
|
|
224
|
+
.slice(0, 16)
|
|
225
|
+
establishCacheTrust(root)
|
|
226
|
+
const records = indexRecords(readIndexFile(indexFile), key, workspaceRoot)
|
|
221
227
|
// PNEXT_DEV_CACHE_DEBUG=1 names every source whose hash differs from the one
|
|
222
228
|
// the last boot persisted — i.e. exactly why an artifact was renamed and
|
|
223
229
|
// recompiled instead of reused.
|
|
@@ -225,8 +231,8 @@ function createDevModuleCache(
|
|
|
225
231
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
226
232
|
process.env.PNEXT_DEV_CACHE_DEBUG && records.size > 0
|
|
227
233
|
? new Map([...records].map(([file, record]) => [file, record.srcHash] as const))
|
|
228
|
-
: undefined
|
|
229
|
-
const pending = new Map<string, Promise<SourceRecord>>()
|
|
234
|
+
: undefined
|
|
235
|
+
const pending = new Map<string, Promise<SourceRecord>>()
|
|
230
236
|
// Records already re-checked in the current pass - what `hold()` promises ("re-checked once when
|
|
231
237
|
// the pass first touches it, never again while it runs") and what `pending` alone does not
|
|
232
238
|
// deliver: it drops each entry the moment the stat settles, so naming a route re-stat'ed its whole
|
|
@@ -234,34 +240,34 @@ function createDevModuleCache(
|
|
|
234
240
|
// Bisect seam: `PNEXT_DEV_GRAPH_FAST=0` restores the previous cost model - re-stat every touch,
|
|
235
241
|
// one module per turn, no path memo.
|
|
236
242
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
237
|
-
const graphFast = process.env.PNEXT_DEV_GRAPH_FAST !== '0'
|
|
238
|
-
const settledThisPass = new Map<string, Promise<SourceRecord>>()
|
|
239
|
-
const refreshedThisPass = new Set<string>()
|
|
240
|
-
let passes = 0
|
|
241
|
-
const closures = new Map<string, Set<string>>()
|
|
242
|
-
const hashes = new Map<string, Promise<string>>()
|
|
243
|
-
let dirty = false
|
|
244
|
-
let flush: Timer | undefined
|
|
245
|
-
let retries = 0
|
|
243
|
+
const graphFast = process.env.PNEXT_DEV_GRAPH_FAST !== '0'
|
|
244
|
+
const settledThisPass = new Map<string, Promise<SourceRecord>>()
|
|
245
|
+
const refreshedThisPass = new Set<string>()
|
|
246
|
+
let passes = 0
|
|
247
|
+
const closures = new Map<string, Set<string>>()
|
|
248
|
+
const hashes = new Map<string, Promise<string>>()
|
|
249
|
+
let dirty = false
|
|
250
|
+
let flush: Timer | undefined
|
|
251
|
+
let retries = 0
|
|
246
252
|
|
|
247
253
|
function persist() {
|
|
248
|
-
if (flush) return
|
|
254
|
+
if (flush) return
|
|
249
255
|
flush = setTimeout(async () => {
|
|
250
|
-
flush = undefined
|
|
251
|
-
if (!dirty) return
|
|
252
|
-
const contents = serializeIndex(key, records, workspaceRoot)
|
|
256
|
+
flush = undefined
|
|
257
|
+
if (!dirty) return
|
|
258
|
+
const contents = serializeIndex(key, records, workspaceRoot)
|
|
253
259
|
try {
|
|
254
|
-
await mkdir(root, { recursive: true })
|
|
255
|
-
await writeFileAtomic(indexFile, contents)
|
|
256
|
-
dirty = false
|
|
260
|
+
await mkdir(root, { recursive: true })
|
|
261
|
+
await writeFileAtomic(indexFile, contents)
|
|
262
|
+
dirty = false
|
|
257
263
|
} catch {
|
|
258
264
|
// A write that lost a race with a cache wipe must not look like a
|
|
259
265
|
// persisted index: keep `dirty` and try again a couple of times.
|
|
260
|
-
if (++retries < 3) persist()
|
|
266
|
+
if (++retries < 3) persist()
|
|
261
267
|
}
|
|
262
|
-
}, 50)
|
|
268
|
+
}, 50)
|
|
263
269
|
// The index is a pure optimization; never hold the process open for it.
|
|
264
|
-
flush.unref?.()
|
|
270
|
+
flush.unref?.()
|
|
265
271
|
}
|
|
266
272
|
|
|
267
273
|
// A short-lived process (a one-off compile, a build) can exit before the
|
|
@@ -270,35 +276,36 @@ function createDevModuleCache(
|
|
|
270
276
|
// whoever copies or walks .pnext right after it returns (see flushDevModuleCaches).
|
|
271
277
|
function flushPersistNow() {
|
|
272
278
|
if (flush) {
|
|
273
|
-
clearTimeout(flush)
|
|
274
|
-
flush = undefined
|
|
279
|
+
clearTimeout(flush)
|
|
280
|
+
flush = undefined
|
|
275
281
|
}
|
|
276
|
-
if (!dirty) return
|
|
277
|
-
dirty = false
|
|
282
|
+
if (!dirty) return
|
|
283
|
+
dirty = false
|
|
278
284
|
try {
|
|
279
|
-
writeFileSync(indexFile, serializeIndex(key, records, workspaceRoot))
|
|
285
|
+
writeFileSync(indexFile, serializeIndex(key, records, workspaceRoot))
|
|
280
286
|
} catch {
|
|
281
287
|
// Best effort: a missing index only costs the next boot a rescan.
|
|
282
288
|
}
|
|
283
289
|
}
|
|
284
|
-
persistFlushes.add(flushPersistNow)
|
|
285
|
-
process.on('exit', flushPersistNow)
|
|
290
|
+
persistFlushes.add(flushPersistNow)
|
|
291
|
+
process.on('exit', flushPersistNow)
|
|
286
292
|
|
|
287
293
|
async function sourceRecord(file: string): Promise<SourceRecord> {
|
|
288
|
-
const inflight =
|
|
289
|
-
|
|
290
|
-
|
|
294
|
+
const inflight =
|
|
295
|
+
pending.get(file) ?? (graphFast && passes > 0 ? settledThisPass.get(file) : undefined)
|
|
296
|
+
if (inflight) return inflight
|
|
297
|
+
const previous = records.get(file)
|
|
291
298
|
const next = readSourceRecord(file, previous, options.edges).then(record => {
|
|
292
|
-
pending.delete(file)
|
|
293
|
-
if (record === previous) return record
|
|
294
|
-
records.set(file, record)
|
|
295
|
-
dirty = true
|
|
296
|
-
persist()
|
|
297
|
-
return record
|
|
298
|
-
})
|
|
299
|
-
pending.set(file, next)
|
|
300
|
-
if (graphFast && passes > 0) settledThisPass.set(file, next)
|
|
301
|
-
return next
|
|
299
|
+
pending.delete(file)
|
|
300
|
+
if (record === previous) return record
|
|
301
|
+
records.set(file, record)
|
|
302
|
+
dirty = true
|
|
303
|
+
persist()
|
|
304
|
+
return record
|
|
305
|
+
})
|
|
306
|
+
pending.set(file, next)
|
|
307
|
+
if (graphFast && passes > 0) settledThisPass.set(file, next)
|
|
308
|
+
return next
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
/**
|
|
@@ -307,208 +314,211 @@ function createDevModuleCache(
|
|
|
307
314
|
* everywhere), and the disk is the only authority on what a module contains.
|
|
308
315
|
*/
|
|
309
316
|
async function refresh(file: string) {
|
|
310
|
-
if (watcherFreshnessTrusted(root)) return
|
|
311
|
-
const reachable = closures.get(file)
|
|
312
|
-
if (!reachable) return
|
|
313
|
-
if (passes > 0 && refreshedThisPass.has(file)) return
|
|
314
|
-
refreshedThisPass.add(file)
|
|
315
|
-
const changed: string[] = []
|
|
317
|
+
if (watcherFreshnessTrusted(root)) return
|
|
318
|
+
const reachable = closures.get(file)
|
|
319
|
+
if (!reachable) return
|
|
320
|
+
if (passes > 0 && refreshedThisPass.has(file)) return
|
|
321
|
+
refreshedThisPass.add(file)
|
|
322
|
+
const changed: string[] = []
|
|
316
323
|
await Promise.all(
|
|
317
324
|
[...reachable].map(async source => {
|
|
318
|
-
const previous = records.get(source)
|
|
319
|
-
const record = await sourceRecord(source)
|
|
320
|
-
if (previous?.srcHash !== record.srcHash) changed.push(source)
|
|
325
|
+
const previous = records.get(source)
|
|
326
|
+
const record = await sourceRecord(source)
|
|
327
|
+
if (previous?.srcHash !== record.srcHash) changed.push(source)
|
|
321
328
|
}),
|
|
322
|
-
)
|
|
323
|
-
if (changed.length > 0) forget(changed)
|
|
329
|
+
)
|
|
330
|
+
if (changed.length > 0) forget(changed)
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
/** Drop the memoized graph of every module that reaches one of `changed`. */
|
|
327
334
|
function forget(changed: string[]) {
|
|
328
335
|
for (const [source, reachable] of closures) {
|
|
329
|
-
if (!changed.some(target => reachable.has(target))) continue
|
|
330
|
-
closures.delete(source)
|
|
331
|
-
hashes.delete(source)
|
|
336
|
+
if (!changed.some(target => reachable.has(target))) continue
|
|
337
|
+
closures.delete(source)
|
|
338
|
+
hashes.delete(source)
|
|
332
339
|
}
|
|
333
340
|
}
|
|
334
341
|
|
|
335
342
|
/** The pre-batching walk and hash gather, kept behind PNEXT_DEV_GRAPH_FAST=0. */
|
|
336
343
|
async function serialFrontier(frontier: string[], file: string) {
|
|
337
|
-
const reached: { expand: boolean; targets: Iterable<string> }[] = []
|
|
344
|
+
const reached: { expand: boolean; targets: Iterable<string> }[] = []
|
|
338
345
|
for (const current of frontier) {
|
|
339
|
-
const known = current === file ? undefined : closures.get(current)
|
|
346
|
+
const known = current === file ? undefined : closures.get(current)
|
|
340
347
|
reached.push(
|
|
341
348
|
known
|
|
342
349
|
? { expand: false, targets: known as Iterable<string> }
|
|
343
350
|
: { expand: true, targets: (await sourceRecord(current)).imports },
|
|
344
|
-
)
|
|
351
|
+
)
|
|
345
352
|
}
|
|
346
|
-
return reached
|
|
353
|
+
return reached
|
|
347
354
|
}
|
|
348
355
|
|
|
349
356
|
async function serialRecords(reachable: string[]) {
|
|
350
|
-
const records: SourceRecord[] = []
|
|
351
|
-
for (const source of reachable) records.push(await sourceRecord(source))
|
|
352
|
-
return records
|
|
357
|
+
const records: SourceRecord[] = []
|
|
358
|
+
for (const source of reachable) records.push(await sourceRecord(source))
|
|
359
|
+
return records
|
|
353
360
|
}
|
|
354
361
|
|
|
355
362
|
/** Every source reachable from `file`, itself included. Cycle-safe by construction. */
|
|
356
363
|
async function closure(file: string): Promise<Set<string>> {
|
|
357
|
-
const cached = closures.get(file)
|
|
358
|
-
if (cached) return cached
|
|
359
|
-
const seen = new Set<string>([file])
|
|
364
|
+
const cached = closures.get(file)
|
|
365
|
+
if (cached) return cached
|
|
366
|
+
const seen = new Set<string>([file])
|
|
360
367
|
// A whole frontier per turn, not one module per turn: the walk is pure I/O latency (one stat per
|
|
361
368
|
// module), so taking it in series turns a route graph into that many sequential round trips, on
|
|
362
369
|
// the request the response is blocked on.
|
|
363
|
-
let frontier = [file]
|
|
370
|
+
let frontier = [file]
|
|
364
371
|
while (frontier.length > 0) {
|
|
365
372
|
const reached = graphFast
|
|
366
373
|
? await Promise.all(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
frontier.map(async current => {
|
|
375
|
+
// A complete reachable set stays complete however it was reached, so a
|
|
376
|
+
// memoized child is merged instead of re-expanded.
|
|
377
|
+
const known = current === file ? undefined : closures.get(current)
|
|
378
|
+
return known
|
|
379
|
+
? { expand: false, targets: known as Iterable<string> }
|
|
380
|
+
: { expand: true, targets: (await sourceRecord(current)).imports }
|
|
381
|
+
}),
|
|
375
382
|
)
|
|
376
|
-
: await serialFrontier(frontier, file)
|
|
377
|
-
const next: string[] = []
|
|
383
|
+
: await serialFrontier(frontier, file)
|
|
384
|
+
const next: string[] = []
|
|
378
385
|
for (const { expand, targets } of reached) {
|
|
379
386
|
for (const target of targets) {
|
|
380
|
-
if (seen.has(target)) continue
|
|
381
|
-
seen.add(target)
|
|
382
|
-
if (expand) next.push(target)
|
|
387
|
+
if (seen.has(target)) continue
|
|
388
|
+
seen.add(target)
|
|
389
|
+
if (expand) next.push(target)
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
|
-
frontier = next
|
|
392
|
+
frontier = next
|
|
386
393
|
}
|
|
387
|
-
closures.set(file, seen)
|
|
388
|
-
return seen
|
|
394
|
+
closures.set(file, seen)
|
|
395
|
+
return seen
|
|
389
396
|
}
|
|
390
397
|
|
|
391
398
|
async function computeGraphHash(file: string) {
|
|
392
|
-
const reachable = [...(await closure(file))].sort()
|
|
399
|
+
const reachable = [...(await closure(file))].sort()
|
|
393
400
|
// Same reason as the walk above: gather every record first, then fold them
|
|
394
401
|
// in sorted order, instead of one awaited stat between each hash update.
|
|
395
402
|
const records: SourceRecord[] = graphFast
|
|
396
403
|
? await Promise.all(reachable.map(source => sourceRecord(source)))
|
|
397
|
-
: await serialRecords(reachable)
|
|
398
|
-
const hash = createHash('sha256').update(key).update('\0')
|
|
399
|
-
const moved: string[] = []
|
|
404
|
+
: await serialRecords(reachable)
|
|
405
|
+
const hash = createHash('sha256').update(key).update('\0')
|
|
406
|
+
const moved: string[] = []
|
|
400
407
|
for (const [index, source] of reachable.entries()) {
|
|
401
|
-
const record = records[index]
|
|
408
|
+
const record = records[index]!
|
|
402
409
|
// A generated module (materialized pages wrapper, bundled config) can
|
|
403
410
|
// live at a path that changes between boots while its content does not;
|
|
404
411
|
// naming it by content alone keeps its dependents' names stable.
|
|
405
|
-
hash.update(generated(source) ? '' : portableSourcePath(source, workspaceRoot))
|
|
406
|
-
hash.update('\0')
|
|
407
|
-
hash.update(record.srcHash)
|
|
408
|
-
hash.update('\0')
|
|
409
|
-
if (booted && booted.get(source) !== record.srcHash) moved.push(source)
|
|
412
|
+
hash.update(generated(source) ? '' : portableSourcePath(source, workspaceRoot))
|
|
413
|
+
hash.update('\0')
|
|
414
|
+
hash.update(record.srcHash)
|
|
415
|
+
hash.update('\0')
|
|
416
|
+
if (booted && booted.get(source) !== record.srcHash) moved.push(source)
|
|
410
417
|
}
|
|
411
418
|
if (booted && moved.length > 0) {
|
|
412
419
|
console.log(
|
|
413
420
|
`dev-cache ${path.basename(file)} renamed: ${moved.length} source(s) differ from the persisted graph\n ${moved
|
|
414
421
|
.slice(0, 10)
|
|
415
422
|
.join('\n ')}`,
|
|
416
|
-
)
|
|
423
|
+
)
|
|
417
424
|
}
|
|
418
|
-
return hash.digest('hex').slice(0, 16)
|
|
425
|
+
return hash.digest('hex').slice(0, 16)
|
|
419
426
|
}
|
|
420
427
|
|
|
421
428
|
return {
|
|
422
429
|
root,
|
|
423
430
|
graphKey: key,
|
|
424
431
|
async graphSources(files) {
|
|
425
|
-
const reachable = new Set<string>()
|
|
426
|
-
for (const file of files)
|
|
432
|
+
const reachable = new Set<string>()
|
|
433
|
+
for (const file of files)
|
|
434
|
+
for (const source of await closure(path.resolve(file))) reachable.add(source)
|
|
427
435
|
return Promise.all(
|
|
428
436
|
[...reachable].sort().map(async (source): Promise<DevGraphSource> => {
|
|
429
|
-
const record = await sourceRecord(source)
|
|
430
|
-
return [source, record.mtimeMs, record.size, record.srcHash]
|
|
437
|
+
const record = await sourceRecord(source)
|
|
438
|
+
return [source, record.mtimeMs, record.size, record.srcHash]
|
|
431
439
|
}),
|
|
432
|
-
)
|
|
440
|
+
)
|
|
433
441
|
},
|
|
434
442
|
async graphHash(file) {
|
|
435
|
-
const resolved = path.resolve(file)
|
|
436
|
-
await refresh(resolved)
|
|
437
|
-
const existing = hashes.get(resolved)
|
|
438
|
-
if (existing) return existing
|
|
443
|
+
const resolved = path.resolve(file)
|
|
444
|
+
await refresh(resolved)
|
|
445
|
+
const existing = hashes.get(resolved)
|
|
446
|
+
if (existing) return existing
|
|
439
447
|
const next = computeGraphHash(resolved).catch(error => {
|
|
440
|
-
hashes.delete(resolved)
|
|
441
|
-
throw error
|
|
442
|
-
})
|
|
443
|
-
hashes.set(resolved, next)
|
|
444
|
-
return next
|
|
448
|
+
hashes.delete(resolved)
|
|
449
|
+
throw error
|
|
450
|
+
})
|
|
451
|
+
hashes.set(resolved, next)
|
|
452
|
+
return next
|
|
445
453
|
},
|
|
446
454
|
knows(file) {
|
|
447
|
-
return records.has(path.resolve(file))
|
|
455
|
+
return records.has(path.resolve(file))
|
|
448
456
|
},
|
|
449
457
|
async isClientSource(file) {
|
|
450
|
-
return (await sourceRecord(path.resolve(file))).client
|
|
458
|
+
return (await sourceRecord(path.resolve(file))).client
|
|
451
459
|
},
|
|
452
460
|
async packageDemand(file, prune) {
|
|
453
|
-
const resolved = path.resolve(file)
|
|
454
|
-
const demand = new Set<string>()
|
|
461
|
+
const resolved = path.resolve(file)
|
|
462
|
+
const demand = new Set<string>()
|
|
455
463
|
if (!prune) {
|
|
456
464
|
for (const source of await closure(resolved)) {
|
|
457
|
-
for (const name of (await sourceRecord(source)).packages) demand.add(name)
|
|
465
|
+
for (const name of (await sourceRecord(source)).packages) demand.add(name)
|
|
458
466
|
}
|
|
459
|
-
return [...demand]
|
|
467
|
+
return [...demand]
|
|
460
468
|
}
|
|
461
|
-
const seen = new Set<string>([resolved])
|
|
462
|
-
let frontier = [resolved]
|
|
469
|
+
const seen = new Set<string>([resolved])
|
|
470
|
+
let frontier = [resolved]
|
|
463
471
|
while (frontier.length > 0) {
|
|
464
472
|
const kept = (
|
|
465
|
-
await Promise.all(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
await Promise.all(
|
|
474
|
+
frontier.map(async source => ((await prune(source)) ? undefined : source)),
|
|
475
|
+
)
|
|
476
|
+
).filter((source): source is string => source !== undefined)
|
|
477
|
+
const reached = await Promise.all(kept.map(source => sourceRecord(source)))
|
|
478
|
+
const next: string[] = []
|
|
469
479
|
for (const record of reached) {
|
|
470
|
-
for (const name of record.packages) demand.add(name)
|
|
480
|
+
for (const name of record.packages) demand.add(name)
|
|
471
481
|
for (const target of record.imports) {
|
|
472
|
-
if (seen.has(target)) continue
|
|
473
|
-
seen.add(target)
|
|
474
|
-
next.push(target)
|
|
482
|
+
if (seen.has(target)) continue
|
|
483
|
+
seen.add(target)
|
|
484
|
+
next.push(target)
|
|
475
485
|
}
|
|
476
486
|
}
|
|
477
|
-
frontier = next
|
|
487
|
+
frontier = next
|
|
478
488
|
}
|
|
479
|
-
return [...demand]
|
|
489
|
+
return [...demand]
|
|
480
490
|
},
|
|
481
491
|
hold() {
|
|
482
492
|
if (passes++ === 0) {
|
|
483
|
-
refreshedThisPass.clear()
|
|
484
|
-
settledThisPass.clear()
|
|
493
|
+
refreshedThisPass.clear()
|
|
494
|
+
settledThisPass.clear()
|
|
485
495
|
}
|
|
486
|
-
let released = false
|
|
496
|
+
let released = false
|
|
487
497
|
return () => {
|
|
488
|
-
if (released) return
|
|
489
|
-
released = true
|
|
498
|
+
if (released) return
|
|
499
|
+
released = true
|
|
490
500
|
// Outside a pass the disk is the only authority again, so nothing may
|
|
491
501
|
// survive the last release.
|
|
492
|
-
if (--passes === 0) settledThisPass.clear()
|
|
493
|
-
}
|
|
502
|
+
if (--passes === 0) settledThisPass.clear()
|
|
503
|
+
}
|
|
494
504
|
},
|
|
495
505
|
invalidate(files) {
|
|
496
|
-
const changed = new Set([...files].map(file => path.resolve(file)))
|
|
497
|
-
if (changed.size === 0) return
|
|
506
|
+
const changed = new Set([...files].map(file => path.resolve(file)))
|
|
507
|
+
if (changed.size === 0) return
|
|
498
508
|
for (const file of changed) {
|
|
499
|
-
records.delete(file)
|
|
500
|
-
pending.delete(file)
|
|
509
|
+
records.delete(file)
|
|
510
|
+
pending.delete(file)
|
|
501
511
|
// A save lands mid-pass often enough to matter: the pass must not go on
|
|
502
512
|
// reusing the record the watcher just invalidated.
|
|
503
|
-
settledThisPass.delete(file)
|
|
504
|
-
dirty = true
|
|
513
|
+
settledThisPass.delete(file)
|
|
514
|
+
dirty = true
|
|
505
515
|
}
|
|
506
516
|
// Dependents are exactly the modules whose reachable set holds a changed
|
|
507
517
|
// file: drop their memoized graph, keep every unrelated module's.
|
|
508
|
-
forget([...changed])
|
|
509
|
-
persist()
|
|
518
|
+
forget([...changed])
|
|
519
|
+
persist()
|
|
510
520
|
},
|
|
511
|
-
}
|
|
521
|
+
}
|
|
512
522
|
}
|
|
513
523
|
|
|
514
524
|
const missingRecord = (): SourceRecord => ({
|
|
@@ -518,7 +528,7 @@ const missingRecord = (): SourceRecord => ({
|
|
|
518
528
|
imports: [],
|
|
519
529
|
packages: [],
|
|
520
530
|
client: false,
|
|
521
|
-
})
|
|
531
|
+
})
|
|
522
532
|
|
|
523
533
|
// Sync reads skip the fs thread-pool queue the vendor preloads own - the walk is cheap standing
|
|
524
534
|
// alone but slow once queued behind them.
|
|
@@ -527,15 +537,21 @@ async function readSourceRecord(
|
|
|
527
537
|
previous: SourceRecord | undefined,
|
|
528
538
|
edges: DevModuleCacheOptions['edges'],
|
|
529
539
|
): Promise<SourceRecord> {
|
|
530
|
-
const sync = devHeadTrimEnabled()
|
|
540
|
+
const sync = devHeadTrimEnabled()
|
|
531
541
|
const stats = sync
|
|
532
|
-
? (() => {
|
|
533
|
-
|
|
534
|
-
|
|
542
|
+
? (() => {
|
|
543
|
+
try {
|
|
544
|
+
return statSync(file)
|
|
545
|
+
} catch {
|
|
546
|
+
return undefined
|
|
547
|
+
}
|
|
548
|
+
})()
|
|
549
|
+
: await stat(file).catch(() => undefined)
|
|
550
|
+
if (!stats) return missingRecord()
|
|
535
551
|
// Fast path: an untouched file keeps the indexed hash and edge list, so a
|
|
536
552
|
// restart pays one stat per source instead of a read + scan + resolve.
|
|
537
553
|
if (previous?.mtimeMs === stats.mtimeMs && previous.size === stats.size) {
|
|
538
|
-
return previous
|
|
554
|
+
return previous
|
|
539
555
|
}
|
|
540
556
|
if (isBinaryDevSource(file)) {
|
|
541
557
|
return {
|
|
@@ -545,16 +561,22 @@ async function readSourceRecord(
|
|
|
545
561
|
imports: [],
|
|
546
562
|
packages: [],
|
|
547
563
|
client: false,
|
|
548
|
-
}
|
|
564
|
+
}
|
|
549
565
|
}
|
|
550
566
|
const source = sync
|
|
551
|
-
? (() => {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
567
|
+
? (() => {
|
|
568
|
+
try {
|
|
569
|
+
return readFileSync(file, 'utf8')
|
|
570
|
+
} catch {
|
|
571
|
+
return undefined
|
|
572
|
+
}
|
|
573
|
+
})()
|
|
574
|
+
: await readFile(file, 'utf8').catch(() => undefined)
|
|
575
|
+
if (source === undefined) return missingRecord()
|
|
576
|
+
const srcHash = devTextSourceHash(source)
|
|
555
577
|
// Content decides: a touched-but-unchanged file keeps its edges (and so its
|
|
556
578
|
// dependents keep their names) even though its mtime moved.
|
|
557
|
-
const scanned = previous?.srcHash === srcHash ? previous : edges(file, source)
|
|
579
|
+
const scanned = previous?.srcHash === srcHash ? previous : edges(file, source)
|
|
558
580
|
return {
|
|
559
581
|
mtimeMs: stats.mtimeMs,
|
|
560
582
|
size: stats.size,
|
|
@@ -562,7 +584,7 @@ async function readSourceRecord(
|
|
|
562
584
|
imports: scanned.imports,
|
|
563
585
|
packages: scanned.packages,
|
|
564
586
|
client: scanned.client,
|
|
565
|
-
}
|
|
587
|
+
}
|
|
566
588
|
}
|
|
567
589
|
|
|
568
590
|
// --------------------------------------------------------------------------
|
|
@@ -575,13 +597,13 @@ async function readSourceRecord(
|
|
|
575
597
|
// edges, the folder carries a uuid marker: while it still reads back as the value this process adopted at
|
|
576
598
|
// boot, the folder was never wiped and every artifact still in it still has its edges. Once it does not -
|
|
577
599
|
// or once there is no marker at all - nothing on disk is trusted until this process rewrites it.
|
|
578
|
-
const markers = new Map<string, string>()
|
|
579
|
-
const wiped = new Set<string>()
|
|
580
|
-
const written = new Set<string>()
|
|
581
|
-
const markerChecks = new Map<string, number>()
|
|
600
|
+
const markers = new Map<string, string>()
|
|
601
|
+
const wiped = new Set<string>()
|
|
602
|
+
const written = new Set<string>()
|
|
603
|
+
const markerChecks = new Map<string, number>()
|
|
582
604
|
|
|
583
605
|
function markerFile(root: string) {
|
|
584
|
-
return path.join(root, '.cache-id')
|
|
606
|
+
return path.join(root, '.cache-id')
|
|
585
607
|
}
|
|
586
608
|
|
|
587
609
|
/**
|
|
@@ -590,28 +612,28 @@ function markerFile(root: string) {
|
|
|
590
612
|
* there still has its edges; a folder without one starts untrusted.
|
|
591
613
|
*/
|
|
592
614
|
function establishCacheTrust(root: string) {
|
|
593
|
-
const current = readMarker(root)
|
|
594
|
-
const value = current ?? randomUUID()
|
|
615
|
+
const current = readMarker(root)
|
|
616
|
+
const value = current ?? randomUUID()
|
|
595
617
|
if (!current) {
|
|
596
|
-
writeMarker(root, value)
|
|
597
|
-
wiped.add(root)
|
|
618
|
+
writeMarker(root, value)
|
|
619
|
+
wiped.add(root)
|
|
598
620
|
}
|
|
599
|
-
markers.set(root, value)
|
|
600
|
-
return value
|
|
621
|
+
markers.set(root, value)
|
|
622
|
+
return value
|
|
601
623
|
}
|
|
602
624
|
|
|
603
625
|
function readMarker(root: string) {
|
|
604
626
|
try {
|
|
605
|
-
return readFileSync(markerFile(root), 'utf8').trim() || undefined
|
|
627
|
+
return readFileSync(markerFile(root), 'utf8').trim() || undefined
|
|
606
628
|
} catch {
|
|
607
|
-
return undefined
|
|
629
|
+
return undefined
|
|
608
630
|
}
|
|
609
631
|
}
|
|
610
632
|
|
|
611
633
|
function writeMarker(root: string, value: string) {
|
|
612
634
|
try {
|
|
613
|
-
mkdirSync(root, { recursive: true })
|
|
614
|
-
writeFileSync(markerFile(root), value)
|
|
635
|
+
mkdirSync(root, { recursive: true })
|
|
636
|
+
writeFileSync(markerFile(root), value)
|
|
615
637
|
} catch {
|
|
616
638
|
// Non-fatal: an unwritable marker only costs a recompile.
|
|
617
639
|
}
|
|
@@ -622,25 +644,25 @@ function writeMarker(root: string, value: string) {
|
|
|
622
644
|
* window it leaves is a wipe racing an in-flight compile, which recompiles into place anyway.
|
|
623
645
|
*/
|
|
624
646
|
function cacheMarkerIntact(root: string) {
|
|
625
|
-
const recorded = markers.get(root)
|
|
626
|
-
if (recorded === undefined || wiped.has(root)) return false
|
|
627
|
-
const now = performance.now()
|
|
628
|
-
if (now - (markerChecks.get(root) ?? -Infinity) < 100) return true
|
|
629
|
-
markerChecks.set(root, now)
|
|
630
|
-
const current = readMarker(root)
|
|
631
|
-
if (current === recorded) return true
|
|
647
|
+
const recorded = markers.get(root)
|
|
648
|
+
if (recorded === undefined || wiped.has(root)) return false
|
|
649
|
+
const now = performance.now()
|
|
650
|
+
if (now - (markerChecks.get(root) ?? -Infinity) < 100) return true
|
|
651
|
+
markerChecks.set(root, now)
|
|
652
|
+
const current = readMarker(root)
|
|
653
|
+
if (current === recorded) return true
|
|
632
654
|
// Wiped under us: re-establish the marker so the rebuilt cache is reusable
|
|
633
655
|
// next boot, but distrust everything still on disk for the rest of this run.
|
|
634
|
-
if (!current) writeMarker(root, recorded)
|
|
635
|
-
else markers.set(root, current)
|
|
636
|
-
wiped.add(root)
|
|
637
|
-
return false
|
|
656
|
+
if (!current) writeMarker(root, recorded)
|
|
657
|
+
else markers.set(root, current)
|
|
658
|
+
wiped.add(root)
|
|
659
|
+
return false
|
|
638
660
|
}
|
|
639
661
|
|
|
640
662
|
/** Record an artifact this process just wrote — trusted even after a wipe. */
|
|
641
663
|
export function noteDevArtifactWritten(file: string) {
|
|
642
|
-
const root = artifactCacheRoot(file)
|
|
643
|
-
if (root && wiped.has(root)) written.add(file)
|
|
664
|
+
const root = artifactCacheRoot(file)
|
|
665
|
+
if (root && wiped.has(root)) written.add(file)
|
|
644
666
|
}
|
|
645
667
|
|
|
646
668
|
/**
|
|
@@ -649,18 +671,18 @@ export function noteDevArtifactWritten(file: string) {
|
|
|
649
671
|
* module, per boot.
|
|
650
672
|
*/
|
|
651
673
|
export function devArtifactUsable(file: string) {
|
|
652
|
-
if (!cachedExistsSync(file)) return false
|
|
653
|
-
const root = artifactCacheRoot(file)
|
|
674
|
+
if (!cachedExistsSync(file)) return false
|
|
675
|
+
const root = artifactCacheRoot(file)
|
|
654
676
|
// No dev module cache for this folder — a prod build writes compiled output
|
|
655
677
|
// here too, and it owns the directory it just emptied.
|
|
656
|
-
if (!root || !markers.has(root)) return true
|
|
657
|
-
return cacheMarkerIntact(root) || written.has(file)
|
|
678
|
+
if (!root || !markers.has(root)) return true
|
|
679
|
+
return cacheMarkerIntact(root) || written.has(file)
|
|
658
680
|
}
|
|
659
681
|
|
|
660
682
|
function artifactCacheRoot(file: string) {
|
|
661
|
-
const marker = `${path.sep}cache${path.sep}server${path.sep}
|
|
662
|
-
const index = file.lastIndexOf(marker)
|
|
663
|
-
return index === -1 ? undefined : file.slice(0, index + marker.length - 1)
|
|
683
|
+
const marker = `${path.sep}cache${path.sep}server${path.sep}`
|
|
684
|
+
const index = file.lastIndexOf(marker)
|
|
685
|
+
return index === -1 ? undefined : file.slice(0, index + marker.length - 1)
|
|
664
686
|
}
|
|
665
687
|
|
|
666
688
|
// --------------------------------------------------------------------------
|
|
@@ -668,19 +690,19 @@ function artifactCacheRoot(file: string) {
|
|
|
668
690
|
// --------------------------------------------------------------------------
|
|
669
691
|
|
|
670
692
|
interface IndexFile {
|
|
671
|
-
version: number
|
|
672
|
-
key: string
|
|
673
|
-
files: Record<string, [number, number, string, string[], string[], 0 | 1]
|
|
693
|
+
version: number
|
|
694
|
+
key: string
|
|
695
|
+
files: Record<string, [number, number, string, string[], string[], 0 | 1]>
|
|
674
696
|
}
|
|
675
697
|
|
|
676
698
|
/** Bumped whenever a record's shape changes; an older index is simply ignored. */
|
|
677
|
-
const INDEX_VERSION = 4
|
|
699
|
+
const INDEX_VERSION = 4
|
|
678
700
|
|
|
679
701
|
function readIndexFile(file: string): IndexFile | undefined {
|
|
680
702
|
try {
|
|
681
|
-
return JSON.parse(readFileSync(file, 'utf8')) as IndexFile
|
|
703
|
+
return JSON.parse(readFileSync(file, 'utf8')) as IndexFile
|
|
682
704
|
} catch {
|
|
683
|
-
return undefined
|
|
705
|
+
return undefined
|
|
684
706
|
}
|
|
685
707
|
}
|
|
686
708
|
|
|
@@ -689,8 +711,8 @@ function readIndexFile(file: string): IndexFile | undefined {
|
|
|
689
711
|
// Paths are stored portably (see `portableSourcePath`) so an index written on
|
|
690
712
|
// the build machine still describes the same sources under the deployment root.
|
|
691
713
|
function indexRecords(parsed: IndexFile | undefined, key: string, workspaceRoot: string) {
|
|
692
|
-
const records = new Map<string, SourceRecord>()
|
|
693
|
-
if (parsed?.key !== key || parsed.version !== INDEX_VERSION) return records
|
|
714
|
+
const records = new Map<string, SourceRecord>()
|
|
715
|
+
if (parsed?.key !== key || parsed.version !== INDEX_VERSION) return records
|
|
694
716
|
for (const [source, entry] of Object.entries(parsed.files ?? {})) {
|
|
695
717
|
records.set(absoluteSourcePath(source, workspaceRoot), {
|
|
696
718
|
mtimeMs: entry[0],
|
|
@@ -699,13 +721,13 @@ function indexRecords(parsed: IndexFile | undefined, key: string, workspaceRoot:
|
|
|
699
721
|
imports: entry[3].map(file => absoluteSourcePath(file, workspaceRoot)),
|
|
700
722
|
packages: entry[4] ?? [],
|
|
701
723
|
client: entry[5] === 1,
|
|
702
|
-
})
|
|
724
|
+
})
|
|
703
725
|
}
|
|
704
|
-
return records
|
|
726
|
+
return records
|
|
705
727
|
}
|
|
706
728
|
|
|
707
729
|
function serializeIndex(key: string, records: Map<string, SourceRecord>, workspaceRoot: string) {
|
|
708
|
-
const files: IndexFile['files'] = {}
|
|
730
|
+
const files: IndexFile['files'] = {}
|
|
709
731
|
for (const [source, record] of records) {
|
|
710
732
|
files[portableSourcePath(source, workspaceRoot)] = [
|
|
711
733
|
record.mtimeMs,
|
|
@@ -714,9 +736,9 @@ function serializeIndex(key: string, records: Map<string, SourceRecord>, workspa
|
|
|
714
736
|
record.imports.map(file => portableSourcePath(file, workspaceRoot)),
|
|
715
737
|
record.packages,
|
|
716
738
|
record.client ? 1 : 0,
|
|
717
|
-
]
|
|
739
|
+
]
|
|
718
740
|
}
|
|
719
|
-
return JSON.stringify({ version: INDEX_VERSION, key, files } satisfies IndexFile)
|
|
741
|
+
return JSON.stringify({ version: INDEX_VERSION, key, files } satisfies IndexFile)
|
|
720
742
|
}
|
|
721
743
|
|
|
722
744
|
// pnext's own compiler sources shape every artifact, so editing the framework must invalidate the cache the
|
|
@@ -724,31 +746,31 @@ function serializeIndex(key: string, records: Map<string, SourceRecord>, workspa
|
|
|
724
746
|
// whenever a file was merely touched (a reinstall, a checkout, a staged copy, a no-op save), so this reads
|
|
725
747
|
// the files that actually emit compiled output. A few dozen small files, once per process, off the boot
|
|
726
748
|
// path - the first compile pays it.
|
|
727
|
-
let fingerprint: string | undefined
|
|
749
|
+
let fingerprint: string | undefined
|
|
728
750
|
|
|
729
751
|
function frameworkFingerprint() {
|
|
730
|
-
if (fingerprint !== undefined) return fingerprint
|
|
731
|
-
const hash = createHash('sha256')
|
|
752
|
+
if (fingerprint !== undefined) return fingerprint
|
|
753
|
+
const hash = createHash('sha256')
|
|
732
754
|
for (const dir of ['.', '..', '../runtime', '../compat/bundler', '../resolve']) {
|
|
733
|
-
const root = new URL(`${dir}/`, import.meta.url).pathname
|
|
734
|
-
let entries: string[]
|
|
755
|
+
const root = new URL(`${dir}/`, import.meta.url).pathname
|
|
756
|
+
let entries: string[]
|
|
735
757
|
try {
|
|
736
|
-
entries = readdirSync(root).sort()
|
|
758
|
+
entries = readdirSync(root).sort()
|
|
737
759
|
} catch {
|
|
738
|
-
continue
|
|
760
|
+
continue
|
|
739
761
|
}
|
|
740
762
|
for (const entry of entries) {
|
|
741
763
|
// Declarations emit nothing, and a deployed build ships without them
|
|
742
764
|
// (the vercel adapter drops `.d.ts`) — hashing one would key the cache to
|
|
743
765
|
// a file that is provably absent where the artifacts are replayed.
|
|
744
|
-
if (!entry.endsWith('.ts') || /\.d\.[cm]?ts$/.test(entry)) continue
|
|
766
|
+
if (!entry.endsWith('.ts') || /\.d\.[cm]?ts$/.test(entry)) continue
|
|
745
767
|
try {
|
|
746
|
-
hash.update(`${entry}:${Bun.hash(readFileSync(path.join(root, entry))).toString(36)}\0`)
|
|
768
|
+
hash.update(`${entry}:${Bun.hash(readFileSync(path.join(root, entry))).toString(36)}\0`)
|
|
747
769
|
} catch {
|
|
748
770
|
// ignore unreadable entries
|
|
749
771
|
}
|
|
750
772
|
}
|
|
751
773
|
}
|
|
752
|
-
fingerprint = hash.digest('hex').slice(0, 16)
|
|
753
|
-
return fingerprint
|
|
774
|
+
fingerprint = hash.digest('hex').slice(0, 16)
|
|
775
|
+
return fingerprint
|
|
754
776
|
}
|