@wular/pnext 0.0.3 → 0.0.6
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 +11 -11
- package/bin/pnext +1 -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 +1 -1
- package/reference/config.md +2 -2
- package/reference/css.md +13 -9
- package/reference/dev.md +1 -1
- package/reference/metadata.md +7 -7
- package/reference/navigation.md +35 -28
- package/reference/performance.md +70 -70
- package/reference/rendering.md +17 -17
- package/reference/routing.md +17 -17
- package/reference/typegen.md +12 -8
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +113 -109
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +49 -52
- 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 +71 -63
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +88 -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 +794 -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 +17 -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 +1988 -1961
- package/src/{api → client}/router/types.ts +93 -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 -1887
- 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 +69 -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 +2076 -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} +152 -143
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +442 -186
- 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 +69 -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 +833 -848
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +952 -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 +425 -404
- package/src/styles.d.ts +9 -0
- package/src/types.ts +320 -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/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
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { copyFile, link, mkdir, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs'
|
|
3
|
-
import { builtinModules } from 'node:module'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { pathToFileHref } from '../../config'
|
|
7
|
-
import type { ResolvedConfig } from '../../config'
|
|
8
|
-
import { globalCssSources } from '../../css'
|
|
9
|
-
import { getImportAliasExtensions } from '../../extensions'
|
|
10
|
-
import { importSpecifiers } from '../../resolve/scan-facts'
|
|
11
|
-
import { compiledSpecifiersManifestSuffix } from '../../
|
|
12
|
-
import { resolveImport, workspacePackageRoots } from '../../resolve/imports'
|
|
13
|
-
import { findProxyFile, proxyRoutePatterns, type ProxyModule } from '../../proxy'
|
|
14
|
-
import { cacheRoot } from '../named-bin'
|
|
15
|
-
import { startWarmChild, type WarmChild } from './vercel-warm'
|
|
16
|
-
import { listFiles, toPosixPath, writeText } from '../../utils/fs'
|
|
17
|
-
import { createVerboseLogger, type VerboseLogger } from '../../utils/verbose'
|
|
18
|
-
import type { BuildManifest, StaticFileMetadata } from '../../types'
|
|
1
|
+
import { copyFile, link, mkdir, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs'
|
|
3
|
+
import { builtinModules } from 'node:module'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { pathToFileHref } from '../../config'
|
|
7
|
+
import type { ResolvedConfig } from '../../config'
|
|
8
|
+
import { globalCssSources } from '../../css/build'
|
|
9
|
+
import { getImportAliasExtensions } from '../../extensions'
|
|
10
|
+
import { importSpecifiers } from '../../resolve/scan-facts'
|
|
11
|
+
import { compiledSpecifiersManifestSuffix } from '../../runtime/modules'
|
|
12
|
+
import { resolveImport, workspacePackageRoots } from '../../resolve/imports'
|
|
13
|
+
import { findProxyFile, proxyRoutePatterns, type ProxyModule } from '../../routing/proxy'
|
|
14
|
+
import { cacheRoot } from '../boot/named-bin'
|
|
15
|
+
import { startWarmChild, type WarmChild } from './vercel-warm'
|
|
16
|
+
import { listFiles, toPosixPath, writeText } from '../../utils/fs'
|
|
17
|
+
import { createVerboseLogger, type VerboseLogger } from '../../utils/verbose'
|
|
18
|
+
import type { BuildManifest, StaticFileMetadata } from '../../types'
|
|
19
19
|
|
|
20
20
|
// Vercel unpacks a function's `.func` directory here at runtime. Baked
|
|
21
21
|
// absolute paths in the build output are rewritten to this root.
|
|
22
|
-
const RUNTIME_ROOT = '/var/task'
|
|
22
|
+
const RUNTIME_ROOT = '/var/task'
|
|
23
23
|
|
|
24
24
|
// The single catch-all server function: the same `pnext start` pipeline,
|
|
25
25
|
// running on Vercel's Bun runtime (`"bunVersion": "1.x"` in vercel.json).
|
|
26
|
-
const SERVER_FUNCTION = '_pnext'
|
|
26
|
+
const SERVER_FUNCTION = '_pnext'
|
|
27
27
|
|
|
28
28
|
// The platform the function actually runs on, declared once: `architecture`
|
|
29
29
|
// goes into .vc-config.json and the same target selects the native packages
|
|
@@ -34,7 +34,7 @@ const FUNCTION_PLATFORM = {
|
|
|
34
34
|
os: 'linux',
|
|
35
35
|
cpu: 'x64',
|
|
36
36
|
libc: 'glibc',
|
|
37
|
-
} as const
|
|
37
|
+
} as const
|
|
38
38
|
|
|
39
39
|
// Directories that only ever hold build-tool output, pruned at *any* depth. Depth matters: pruning
|
|
40
40
|
// only a package's top level ships every nested one. node_modules and .git are here for the same
|
|
@@ -48,7 +48,7 @@ const prunedDirNames = [
|
|
|
48
48
|
'.cache',
|
|
49
49
|
'.vercel',
|
|
50
50
|
'.tmp',
|
|
51
|
-
]
|
|
51
|
+
]
|
|
52
52
|
|
|
53
53
|
// Files nothing reads at request time. Sourcemaps only decorate stack traces,
|
|
54
54
|
// and Bun strips types rather than resolving declarations — neither can change
|
|
@@ -63,27 +63,27 @@ const droppedFileSuffixes = [
|
|
|
63
63
|
// The compile-time specifier sidecars (see traceNodeModulesClosure below):
|
|
64
64
|
// build-time metadata the trace consumes, never read at request time.
|
|
65
65
|
compiledSpecifiersManifestSuffix,
|
|
66
|
-
]
|
|
66
|
+
]
|
|
67
67
|
|
|
68
68
|
/** What the function tree ships, resolved once from config. */
|
|
69
69
|
interface PackRules {
|
|
70
|
-
prunedDirs: Set<string
|
|
71
|
-
droppedSuffixes: string[]
|
|
70
|
+
prunedDirs: Set<string>
|
|
71
|
+
droppedSuffixes: string[]
|
|
72
72
|
/** Absolute sources exempt from the prune list — the app's own build output. */
|
|
73
|
-
keepPaths: Set<string
|
|
73
|
+
keepPaths: Set<string>
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Test seam: the packing suite builds one app both ways and diffs the trees,
|
|
77
77
|
// so it needs the unpruned closure on demand. Never off for a real deployment.
|
|
78
|
-
let packPruningEnabled = true
|
|
78
|
+
let packPruningEnabled = true
|
|
79
79
|
|
|
80
80
|
/** @internal Test-only. Returns a restore function. */
|
|
81
81
|
export function setPackPruningEnabled(enabled: boolean) {
|
|
82
|
-
const previous = packPruningEnabled
|
|
83
|
-
packPruningEnabled = enabled
|
|
82
|
+
const previous = packPruningEnabled
|
|
83
|
+
packPruningEnabled = enabled
|
|
84
84
|
return () => {
|
|
85
|
-
packPruningEnabled = previous
|
|
86
|
-
}
|
|
85
|
+
packPruningEnabled = previous
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function packRules(config: ResolvedConfig): PackRules {
|
|
@@ -94,11 +94,11 @@ function packRules(config: ResolvedConfig): PackRules {
|
|
|
94
94
|
prunedDirs: new Set(['node_modules', '.git']),
|
|
95
95
|
droppedSuffixes: [],
|
|
96
96
|
keepPaths: new Set([path.resolve(config.outPath)]),
|
|
97
|
-
}
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
|
-
const keep = new Set(config.adapter?.keep ?? [])
|
|
100
|
-
const extra = config.adapter?.exclude ?? []
|
|
101
|
-
const isSuffix = (entry: string) => entry.startsWith('.') && !prunedDirNames.includes(entry)
|
|
99
|
+
const keep = new Set(config.adapter?.keep ?? [])
|
|
100
|
+
const extra = config.adapter?.exclude ?? []
|
|
101
|
+
const isSuffix = (entry: string) => entry.startsWith('.') && !prunedDirNames.includes(entry)
|
|
102
102
|
return {
|
|
103
103
|
prunedDirs: new Set(
|
|
104
104
|
[...prunedDirNames, ...extra.filter(entry => !isSuffix(entry))].filter(
|
|
@@ -111,24 +111,24 @@ function packRules(config: ResolvedConfig): PackRules {
|
|
|
111
111
|
// The app's outDir shares its name with the prune list; it holds the
|
|
112
112
|
// compiled module cache the function serves from and must survive.
|
|
113
113
|
keepPaths: new Set([path.resolve(config.outPath)]),
|
|
114
|
-
}
|
|
114
|
+
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function shipsDir(pack: PackRules, name: string, source: string) {
|
|
118
|
-
return !pack.prunedDirs.has(name) || pack.keepPaths.has(path.resolve(source))
|
|
118
|
+
return !pack.prunedDirs.has(name) || pack.keepPaths.has(path.resolve(source))
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function shipsFile(pack: PackRules, name: string) {
|
|
122
|
-
return !pack.droppedSuffixes.some(suffix => name.endsWith(suffix))
|
|
122
|
+
return !pack.droppedSuffixes.some(suffix => name.endsWith(suffix))
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
interface VercelConfig {
|
|
126
|
-
version: 3
|
|
126
|
+
version: 3
|
|
127
127
|
routes?: (
|
|
128
128
|
| { src: string; dest?: string; headers?: Record<string, string>; continue?: boolean }
|
|
129
129
|
| { handle: 'filesystem' }
|
|
130
|
-
)[]
|
|
131
|
-
overrides?: Record<string, { path?: string; contentType?: string }
|
|
130
|
+
)[]
|
|
131
|
+
overrides?: Record<string, { path?: string; contentType?: string }>
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
export async function writeVercelOutput(
|
|
@@ -136,13 +136,13 @@ export async function writeVercelOutput(
|
|
|
136
136
|
manifest: BuildManifest,
|
|
137
137
|
options: { verbose?: boolean; warm?: WarmChild } = {},
|
|
138
138
|
) {
|
|
139
|
-
const log = createVerboseLogger(options.verbose ?? false, 'vercel')
|
|
140
|
-
const outputPath = path.join(config.root, '.vercel', 'output')
|
|
141
|
-
const staticFiles = manifest.staticFiles ?? {}
|
|
139
|
+
const log = createVerboseLogger(options.verbose ?? false, 'vercel')
|
|
140
|
+
const outputPath = path.join(config.root, '.vercel', 'output')
|
|
141
|
+
const staticFiles = manifest.staticFiles ?? {}
|
|
142
142
|
// Warming runs in its own process (see ./vercel-warm) and normally started
|
|
143
143
|
// with the build; a caller that skipped that only loses the overlap.
|
|
144
|
-
const warm = options.warm ?? startWarmChild(config)
|
|
145
|
-
await log.step('prepare output directory', () => emptyDir(outputPath))
|
|
144
|
+
const warm = options.warm ?? startWarmChild(config)
|
|
145
|
+
await log.step('prepare output directory', () => emptyDir(outputPath))
|
|
146
146
|
|
|
147
147
|
await log.step('copy static files', () =>
|
|
148
148
|
copyStaticFiles(
|
|
@@ -150,28 +150,28 @@ export async function writeVercelOutput(
|
|
|
150
150
|
path.join(outputPath, 'static'),
|
|
151
151
|
relative => !staticFiles[relative] || canServeStaticOnVercel(staticFiles[relative]),
|
|
152
152
|
),
|
|
153
|
-
)
|
|
154
|
-
const overrides = await staticOverrides(path.join(config.outPath, 'public'), staticFiles)
|
|
153
|
+
)
|
|
154
|
+
const overrides = await staticOverrides(path.join(config.outPath, 'public'), staticFiles)
|
|
155
155
|
|
|
156
156
|
// The compiled set is already final once the child reaches its handler
|
|
157
157
|
// phase, so `onCompiled` resolves this ahead of full completion and
|
|
158
158
|
// writeServerFunction starts tracing while the child's handler imports
|
|
159
159
|
// still run. `finish` itself always fires `onCompiled` before it resolves,
|
|
160
160
|
// so this promise never hangs on it.
|
|
161
|
-
let resolveWarmedModules: (modules: string[]) => void
|
|
161
|
+
let resolveWarmedModules: (modules: string[]) => void
|
|
162
162
|
const warmedModulesEarly = new Promise<string[]>(resolve => {
|
|
163
|
-
resolveWarmedModules = resolve
|
|
164
|
-
})
|
|
165
|
-
const warmSettled = warm.finish(log, modules => resolveWarmedModules(modules))
|
|
163
|
+
resolveWarmedModules = resolve
|
|
164
|
+
})
|
|
165
|
+
const warmSettled = warm.finish(log, modules => resolveWarmedModules(modules))
|
|
166
166
|
// Nothing awaits it until writeServerFunction's copy step; park the
|
|
167
167
|
// rejection so it never surfaces as unhandled in between (it never actually
|
|
168
168
|
// rejects — warmWithRestarts catches everything itself — but the gap
|
|
169
169
|
// between here and that await is otherwise unguarded).
|
|
170
|
-
warmSettled.catch(() => undefined)
|
|
171
|
-
const warmedModules = await log.step('warm module cache (compiled)', () => warmedModulesEarly)
|
|
170
|
+
warmSettled.catch(() => undefined)
|
|
171
|
+
const warmedModules = await log.step('warm module cache (compiled)', () => warmedModulesEarly)
|
|
172
172
|
|
|
173
|
-
const functionPath = path.join(outputPath, 'functions', `${SERVER_FUNCTION}.func`)
|
|
174
|
-
await writeServerFunction(config, manifest, functionPath, warmedModules, warmSettled, log)
|
|
173
|
+
const functionPath = path.join(outputPath, 'functions', `${SERVER_FUNCTION}.func`)
|
|
174
|
+
await writeServerFunction(config, manifest, functionPath, warmedModules, warmSettled, log)
|
|
175
175
|
|
|
176
176
|
const routes: NonNullable<VercelConfig['routes']> = [
|
|
177
177
|
// Chunk and font filenames are content-hashed; serve them immutable.
|
|
@@ -185,17 +185,17 @@ export async function writeVercelOutput(
|
|
|
185
185
|
...(await proxyRoutes(config)),
|
|
186
186
|
{ handle: 'filesystem' },
|
|
187
187
|
{ src: '^/.*$', dest: `/${SERVER_FUNCTION}` },
|
|
188
|
-
]
|
|
188
|
+
]
|
|
189
189
|
|
|
190
190
|
const vercelConfig: VercelConfig = {
|
|
191
191
|
version: 3,
|
|
192
192
|
routes,
|
|
193
193
|
...(Object.keys(overrides).length > 0 ? { overrides } : {}),
|
|
194
|
-
}
|
|
194
|
+
}
|
|
195
195
|
await writeText(
|
|
196
196
|
path.join(outputPath, 'config.json'),
|
|
197
197
|
`${JSON.stringify(vercelConfig, null, 2)}\n`,
|
|
198
|
-
)
|
|
198
|
+
)
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Unlinking the previous run's output (tens of thousands of hardlinks) must not block the build:
|
|
@@ -203,32 +203,32 @@ export async function writeVercelOutput(
|
|
|
203
203
|
// unlinking. Nothing downstream depends on the bytes being gone, and anything an interrupted run
|
|
204
204
|
// leaves behind is swept on the next build.
|
|
205
205
|
async function emptyDir(dir: string) {
|
|
206
|
-
const parent = path.dirname(dir)
|
|
207
|
-
await mkdir(parent, { recursive: true })
|
|
208
|
-
const staleMarker = `${path.basename(dir)}.stale
|
|
206
|
+
const parent = path.dirname(dir)
|
|
207
|
+
await mkdir(parent, { recursive: true })
|
|
208
|
+
const staleMarker = `${path.basename(dir)}.stale-`
|
|
209
209
|
if (existsSync(dir)) {
|
|
210
210
|
await rename(
|
|
211
211
|
dir,
|
|
212
212
|
path.join(parent, `${staleMarker}${process.pid.toString(36)}-${Date.now().toString(36)}`),
|
|
213
|
-
)
|
|
213
|
+
)
|
|
214
214
|
}
|
|
215
215
|
const stale = readdirSync(parent)
|
|
216
216
|
.filter(entry => entry.startsWith(staleMarker))
|
|
217
|
-
.map(entry => path.join(parent, entry))
|
|
218
|
-
await mkdir(dir, { recursive: true })
|
|
217
|
+
.map(entry => path.join(parent, entry))
|
|
218
|
+
await mkdir(dir, { recursive: true })
|
|
219
219
|
if (stale.length > 0) {
|
|
220
|
-
Bun.spawn(['rm', '-rf', ...stale], { stdout: 'ignore', stderr: 'ignore' })
|
|
220
|
+
Bun.spawn(['rm', '-rf', ...stale], { stdout: 'ignore', stderr: 'ignore' })
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
async function proxyRoutes(config: ResolvedConfig) {
|
|
225
|
-
const proxyFile = findProxyFile(config)
|
|
226
|
-
if (!proxyFile) return []
|
|
227
|
-
const proxyModule = (await import(pathToFileHref(proxyFile))) as ProxyModule
|
|
225
|
+
const proxyFile = findProxyFile(config)
|
|
226
|
+
if (!proxyFile) return []
|
|
227
|
+
const proxyModule = (await import(pathToFileHref(proxyFile))) as ProxyModule
|
|
228
228
|
return proxyRoutePatterns(proxyModule.config).map(src => ({
|
|
229
229
|
src,
|
|
230
230
|
dest: `/${SERVER_FUNCTION}`,
|
|
231
|
-
}))
|
|
231
|
+
}))
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
async function writeServerFunction(
|
|
@@ -243,12 +243,12 @@ async function writeServerFunction(
|
|
|
243
243
|
warmSettled: Promise<unknown>,
|
|
244
244
|
log: VerboseLogger,
|
|
245
245
|
) {
|
|
246
|
-
const workspaceRoot = config.workspaceRoot
|
|
247
|
-
await mkdir(functionPath, { recursive: true })
|
|
246
|
+
const workspaceRoot = config.workspaceRoot
|
|
247
|
+
await mkdir(functionPath, { recursive: true })
|
|
248
248
|
|
|
249
|
-
const pack = packRules(config)
|
|
250
|
-
const framework = await resolveFrameworkPackage(workspaceRoot)
|
|
251
|
-
const { root: pnextRoot, inWorkspace: pnextInWorkspace, targetRel: pnextTargetRel } = framework
|
|
249
|
+
const pack = packRules(config)
|
|
250
|
+
const framework = await resolveFrameworkPackage(workspaceRoot)
|
|
251
|
+
const { root: pnextRoot, inWorkspace: pnextInWorkspace, targetRel: pnextTargetRel } = framework
|
|
252
252
|
const closure = await traceNodeModulesClosure(
|
|
253
253
|
config,
|
|
254
254
|
manifest,
|
|
@@ -257,54 +257,54 @@ async function writeServerFunction(
|
|
|
257
257
|
warmedModules,
|
|
258
258
|
pack,
|
|
259
259
|
log,
|
|
260
|
-
)
|
|
260
|
+
)
|
|
261
261
|
|
|
262
262
|
const replicaPathFor = (file: string) => {
|
|
263
263
|
if (isInsideDir(pnextRoot, file)) {
|
|
264
|
-
return path.join(functionPath, pnextTargetRel, path.relative(pnextRoot, file))
|
|
264
|
+
return path.join(functionPath, pnextTargetRel, path.relative(pnextRoot, file))
|
|
265
265
|
}
|
|
266
266
|
if (isInsideDir(workspaceRoot, file)) {
|
|
267
|
-
return path.join(functionPath, path.relative(workspaceRoot, file))
|
|
267
|
+
return path.join(functionPath, path.relative(workspaceRoot, file))
|
|
268
268
|
}
|
|
269
|
-
return undefined
|
|
270
|
-
}
|
|
269
|
+
return undefined
|
|
270
|
+
}
|
|
271
271
|
|
|
272
272
|
// Replicate the workspace layout the build ran in: app source (the renderer reads convention files
|
|
273
273
|
// and global css imports from it at request time), the build output, and the workspace packages the
|
|
274
274
|
// server can actually reach. Unrelated workspace trees never ship.
|
|
275
|
-
const appRel = path.relative(workspaceRoot, config.root)
|
|
276
|
-
const packageRoots = workspacePackageRoots(workspaceRoot).map(root => path.resolve(root))
|
|
277
|
-
const neededRoots = new Set<string>()
|
|
275
|
+
const appRel = path.relative(workspaceRoot, config.root)
|
|
276
|
+
const packageRoots = workspacePackageRoots(workspaceRoot).map(root => path.resolve(root))
|
|
277
|
+
const neededRoots = new Set<string>()
|
|
278
278
|
for (const file of closure.tracedFiles) {
|
|
279
|
-
const root = packageRoots.find(candidate => isInsideDir(candidate, file))
|
|
280
|
-
if (root) neededRoots.add(root)
|
|
279
|
+
const root = packageRoots.find(candidate => isInsideDir(candidate, file))
|
|
280
|
+
if (root) neededRoots.add(root)
|
|
281
281
|
}
|
|
282
282
|
for (const relative of closure.workspacePackages) {
|
|
283
|
-
neededRoots.add(path.resolve(workspaceRoot, relative))
|
|
283
|
+
neededRoots.add(path.resolve(workspaceRoot, relative))
|
|
284
284
|
}
|
|
285
285
|
// The renderer re-resolves the root layout's global css imports from source
|
|
286
286
|
// at request time; the packages holding those css files must be present.
|
|
287
287
|
for (const file of globalCssSources(config)) {
|
|
288
|
-
const root = packageRoots.find(candidate => isInsideDir(candidate, file))
|
|
289
|
-
if (root) neededRoots.add(root)
|
|
288
|
+
const root = packageRoots.find(candidate => isInsideDir(candidate, file))
|
|
289
|
+
if (root) neededRoots.add(root)
|
|
290
290
|
}
|
|
291
291
|
// Tracing sees what the *server* loaded, which is not everything the shipped source can still ask
|
|
292
292
|
// for: the app's own sources travel with the function and a compat compile at request time resolves
|
|
293
293
|
// their imports for real. A workspace package one of them names but no traced module reached would
|
|
294
294
|
// not be here, so close over the declared workspace dependencies too.
|
|
295
|
-
addWorkspaceDependencies(neededRoots, packageRoots, [path.resolve(config.root), ...neededRoots])
|
|
296
|
-
neededRoots.delete(path.resolve(config.root))
|
|
297
|
-
neededRoots.delete(pnextRoot)
|
|
295
|
+
addWorkspaceDependencies(neededRoots, packageRoots, [path.resolve(config.root), ...neededRoots])
|
|
296
|
+
neededRoots.delete(path.resolve(config.root))
|
|
297
|
+
neededRoots.delete(pnextRoot)
|
|
298
298
|
|
|
299
299
|
// The trace above never needed the handler phase, but the copy below ships
|
|
300
300
|
// config.outPath verbatim — handler imports can still be writing vendor
|
|
301
301
|
// bundles into it. Most of this wait is already gone by the time the trace
|
|
302
302
|
// and the roots above finish; whatever remains is genuine handler-phase work.
|
|
303
|
-
await log.step('await handler warm-up', () => warmSettled)
|
|
303
|
+
await log.step('await handler warm-up', () => warmSettled)
|
|
304
304
|
|
|
305
305
|
// Every tree lands in its own subdirectory and the copies are io-bound, so
|
|
306
306
|
// the whole replica goes out as one concurrent step instead of tree by tree.
|
|
307
|
-
const roots = neededRoots.size + 2
|
|
307
|
+
const roots = neededRoots.size + 2
|
|
308
308
|
await log.step(`copy function tree (${closure.packageCount} packages, ${roots} roots)`, () =>
|
|
309
309
|
Promise.all([
|
|
310
310
|
closure.copy(),
|
|
@@ -330,7 +330,7 @@ async function writeServerFunction(
|
|
|
330
330
|
.filter(file => existsSync(file))
|
|
331
331
|
.map(file => copyFile(file, path.join(functionPath, path.basename(file)))),
|
|
332
332
|
]),
|
|
333
|
-
)
|
|
333
|
+
)
|
|
334
334
|
|
|
335
335
|
// Baked absolute paths (compiled cache imports, manifest file paths) point
|
|
336
336
|
// at the build machine's workspace; rewrite them to the runtime root.
|
|
@@ -341,7 +341,7 @@ async function writeServerFunction(
|
|
|
341
341
|
: [[pnextRoot, toPosixPath(path.join(RUNTIME_ROOT, pnextTargetRel))] as const]),
|
|
342
342
|
[workspaceRoot, RUNTIME_ROOT] as const,
|
|
343
343
|
]),
|
|
344
|
-
)
|
|
344
|
+
)
|
|
345
345
|
|
|
346
346
|
// Bun's runtime onResolve plugins never see bare specifiers, so the compat aliases the dev pipeline
|
|
347
347
|
// applies during compilation cannot be applied at runtime for raw-loaded sources (proxy, handlers).
|
|
@@ -349,9 +349,9 @@ async function writeServerFunction(
|
|
|
349
349
|
// every shipped tsconfig.
|
|
350
350
|
await log.step('inject compat tsconfig paths', () =>
|
|
351
351
|
injectCompatPaths(config, functionPath, replicaPathFor),
|
|
352
|
-
)
|
|
352
|
+
)
|
|
353
353
|
|
|
354
|
-
const startEntry = toPosixPath(path.join(pnextTargetRel, 'src/cli/start.ts'))
|
|
354
|
+
const startEntry = toPosixPath(path.join(pnextTargetRel, 'src/cli/start.ts'))
|
|
355
355
|
await writeText(
|
|
356
356
|
path.join(functionPath, 'index.mjs'),
|
|
357
357
|
`import path from 'node:path';
|
|
@@ -368,12 +368,12 @@ async function handler(request) {
|
|
|
368
368
|
// Callable for the Node-style launcher, \`fetch\` for the Bun runtime.
|
|
369
369
|
export default Object.assign(handler, { fetch: handler });
|
|
370
370
|
`,
|
|
371
|
-
)
|
|
371
|
+
)
|
|
372
372
|
|
|
373
373
|
const maxDuration = manifest.routes.reduce<number | undefined>(
|
|
374
374
|
(max, route) => (route.maxDuration ? Math.max(max ?? 0, route.maxDuration) : max),
|
|
375
375
|
undefined,
|
|
376
|
-
)
|
|
376
|
+
)
|
|
377
377
|
await writeText(
|
|
378
378
|
path.join(functionPath, '.vc-config.json'),
|
|
379
379
|
`${JSON.stringify(
|
|
@@ -393,7 +393,7 @@ export default Object.assign(handler, { fetch: handler });
|
|
|
393
393
|
null,
|
|
394
394
|
2,
|
|
395
395
|
)}\n`,
|
|
396
|
-
)
|
|
396
|
+
)
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
/**
|
|
@@ -402,47 +402,47 @@ export default Object.assign(handler, { fetch: handler });
|
|
|
402
402
|
* the usual spelling, but a pinned version resolved through the workspace counts the same.
|
|
403
403
|
*/
|
|
404
404
|
function addWorkspaceDependencies(roots: Set<string>, packageRoots: string[], from: string[]) {
|
|
405
|
-
const byName = new Map<string, string>()
|
|
405
|
+
const byName = new Map<string, string>()
|
|
406
406
|
for (const root of packageRoots) {
|
|
407
|
-
const name = packageManifest(root)?.name
|
|
408
|
-
if (name && !byName.has(name)) byName.set(name, root)
|
|
407
|
+
const name = packageManifest(root)?.name
|
|
408
|
+
if (name && !byName.has(name)) byName.set(name, root)
|
|
409
409
|
}
|
|
410
|
-
const queue = [...from]
|
|
411
|
-
const seen = new Set(queue)
|
|
410
|
+
const queue = [...from]
|
|
411
|
+
const seen = new Set(queue)
|
|
412
412
|
while (queue.length > 0) {
|
|
413
|
-
const manifest = packageManifest(queue.pop()!)
|
|
414
|
-
if (!manifest) continue
|
|
413
|
+
const manifest = packageManifest(queue.pop()!)
|
|
414
|
+
if (!manifest) continue
|
|
415
415
|
for (const field of ['dependencies', 'optionalDependencies'] as const) {
|
|
416
416
|
for (const name of Object.keys(manifest[field] ?? {})) {
|
|
417
|
-
const root = byName.get(name)
|
|
418
|
-
if (!root || seen.has(root)) continue
|
|
419
|
-
seen.add(root)
|
|
420
|
-
roots.add(root)
|
|
421
|
-
queue.push(root)
|
|
417
|
+
const root = byName.get(name)
|
|
418
|
+
if (!root || seen.has(root)) continue
|
|
419
|
+
seen.add(root)
|
|
420
|
+
roots.add(root)
|
|
421
|
+
queue.push(root)
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
interface PackageManifest {
|
|
428
|
-
name?: string
|
|
429
|
-
dependencies?: Record<string, string
|
|
430
|
-
optionalDependencies?: Record<string, string
|
|
428
|
+
name?: string
|
|
429
|
+
dependencies?: Record<string, string>
|
|
430
|
+
optionalDependencies?: Record<string, string>
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
const manifests = new Map<string, PackageManifest | undefined>()
|
|
433
|
+
const manifests = new Map<string, PackageManifest | undefined>()
|
|
434
434
|
|
|
435
435
|
function packageManifest(root: string): PackageManifest | undefined {
|
|
436
|
-
const cached = manifests.get(root)
|
|
437
|
-
if (cached !== undefined || manifests.has(root)) return cached
|
|
438
|
-
let parsed: PackageManifest | undefined
|
|
436
|
+
const cached = manifests.get(root)
|
|
437
|
+
if (cached !== undefined || manifests.has(root)) return cached
|
|
438
|
+
let parsed: PackageManifest | undefined
|
|
439
439
|
try {
|
|
440
|
-
parsed = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')) as PackageManifest
|
|
440
|
+
parsed = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')) as PackageManifest
|
|
441
441
|
} catch {
|
|
442
|
-
parsed = undefined
|
|
442
|
+
parsed = undefined // not a package, or unreadable
|
|
443
443
|
}
|
|
444
|
-
manifests.set(root, parsed)
|
|
445
|
-
return parsed
|
|
444
|
+
manifests.set(root, parsed)
|
|
445
|
+
return parsed
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
// Clones a tree with hardlinks instead of byte copies - the output is packaging-only, so sharing
|
|
@@ -456,54 +456,54 @@ async function copyTree(
|
|
|
456
456
|
excludeNames: string[] = [],
|
|
457
457
|
visited = new Set<string>([realDir(from) ?? path.resolve(from)]),
|
|
458
458
|
) {
|
|
459
|
-
const exclude = new Set(excludeNames)
|
|
460
|
-
const clone = directoryCloneEnabled ? await directoryCloner() : null
|
|
459
|
+
const exclude = new Set(excludeNames)
|
|
460
|
+
const clone = directoryCloneEnabled ? await directoryCloner() : null
|
|
461
461
|
// Nothing to skip at this level: the whole tree clones in one syscall.
|
|
462
462
|
if (exclude.size === 0 && !existsSync(to)) {
|
|
463
|
-
await mkdir(path.dirname(to), { recursive: true })
|
|
464
|
-
if (clone?.(from, to)) return normalizeClone(to, pack, visited)
|
|
463
|
+
await mkdir(path.dirname(to), { recursive: true })
|
|
464
|
+
if (clone?.(from, to)) return normalizeClone(to, pack, visited)
|
|
465
465
|
}
|
|
466
466
|
// The build output is still live while the walk runs — typegen rewrites
|
|
467
467
|
// `.pnext/types` under it — so a directory can vanish between the parent's
|
|
468
468
|
// readdir and this one. It was never part of the shipped closure; skip it.
|
|
469
|
-
const entries = readDirEntries(from)
|
|
470
|
-
if (!entries) return
|
|
471
|
-
await mkdir(to, { recursive: true })
|
|
469
|
+
const entries = readDirEntries(from)
|
|
470
|
+
if (!entries) return
|
|
471
|
+
await mkdir(to, { recursive: true })
|
|
472
472
|
await Promise.all(
|
|
473
473
|
entries.map(async entry => {
|
|
474
|
-
const source = path.join(from, entry.name)
|
|
475
|
-
const target = path.join(to, entry.name)
|
|
474
|
+
const source = path.join(from, entry.name)
|
|
475
|
+
const target = path.join(to, entry.name)
|
|
476
476
|
// Matched by name before type: a symlinked `node_modules` must be pruned
|
|
477
477
|
// as the directory it points at, not followed.
|
|
478
|
-
if (exclude.has(entry.name) || !shipsDir(pack, entry.name, source)) return
|
|
478
|
+
if (exclude.has(entry.name) || !shipsDir(pack, entry.name, source)) return
|
|
479
479
|
if (entry.isSymbolicLink()) {
|
|
480
|
-
const link = resolveLinkTarget(source)
|
|
481
|
-
if (!link) return
|
|
480
|
+
const link = resolveLinkTarget(source)
|
|
481
|
+
if (!link) return
|
|
482
482
|
if (link.isDirectory) {
|
|
483
|
-
if (visited.has(link.path)) return
|
|
484
|
-
visited.add(link.path)
|
|
485
|
-
return copyTree(link.path, target, pack, [], visited)
|
|
483
|
+
if (visited.has(link.path)) return
|
|
484
|
+
visited.add(link.path)
|
|
485
|
+
return copyTree(link.path, target, pack, [], visited)
|
|
486
486
|
}
|
|
487
|
-
return shipsFile(pack, entry.name) ? linkOrCopyFile(link.path, target) : undefined
|
|
487
|
+
return shipsFile(pack, entry.name) ? linkOrCopyFile(link.path, target) : undefined
|
|
488
488
|
}
|
|
489
489
|
// One syscall for the whole subtree beats one per file — the excluded
|
|
490
490
|
// names are stripped from the copy afterwards.
|
|
491
491
|
if (entry.isDirectory() && clone?.(source, target)) {
|
|
492
|
-
return normalizeClone(target, pack, visited)
|
|
492
|
+
return normalizeClone(target, pack, visited)
|
|
493
493
|
}
|
|
494
|
-
if (entry.isDirectory()) return copyTree(source, target, pack, [], visited)
|
|
495
|
-
if (entry.isFile() && shipsFile(pack, entry.name)) return linkOrCopyFile(source, target)
|
|
494
|
+
if (entry.isDirectory()) return copyTree(source, target, pack, [], visited)
|
|
495
|
+
if (entry.isFile() && shipsFile(pack, entry.name)) return linkOrCopyFile(source, target)
|
|
496
496
|
}),
|
|
497
|
-
)
|
|
497
|
+
)
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
/** Directory entries, or undefined if the directory vanished under the walk. */
|
|
501
501
|
function readDirEntries(dir: string) {
|
|
502
502
|
try {
|
|
503
|
-
return readdirSync(dir, { withFileTypes: true })
|
|
503
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
504
504
|
} catch (error) {
|
|
505
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined
|
|
506
|
-
throw error
|
|
505
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined
|
|
506
|
+
throw error
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
|
|
@@ -513,34 +513,34 @@ function readDirEntries(dir: string) {
|
|
|
513
513
|
* never retried - no cloner just means the hardlink walk below does the work.
|
|
514
514
|
*/
|
|
515
515
|
// Test seam: the equivalence suite builds the same app both ways and diffs the trees.
|
|
516
|
-
let directoryCloneEnabled = true
|
|
516
|
+
let directoryCloneEnabled = true
|
|
517
517
|
|
|
518
518
|
/** @internal Test-only. Returns a restore function. */
|
|
519
519
|
export function setDirectoryCloneEnabled(enabled: boolean) {
|
|
520
|
-
const previous = directoryCloneEnabled
|
|
521
|
-
directoryCloneEnabled = enabled
|
|
520
|
+
const previous = directoryCloneEnabled
|
|
521
|
+
directoryCloneEnabled = enabled
|
|
522
522
|
return () => {
|
|
523
|
-
directoryCloneEnabled = previous
|
|
524
|
-
}
|
|
523
|
+
directoryCloneEnabled = previous
|
|
524
|
+
}
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
let cloner: ((from: string, to: string) => boolean) | null | undefined
|
|
527
|
+
let cloner: ((from: string, to: string) => boolean) | null | undefined
|
|
528
528
|
async function directoryCloner() {
|
|
529
|
-
if (cloner !== undefined) return cloner
|
|
530
|
-
cloner = null
|
|
529
|
+
if (cloner !== undefined) return cloner
|
|
530
|
+
cloner = null
|
|
531
531
|
if (process.platform === 'darwin') {
|
|
532
532
|
try {
|
|
533
|
-
const { dlopen } = await import('bun:ffi')
|
|
533
|
+
const { dlopen } = await import('bun:ffi')
|
|
534
534
|
const { symbols } = dlopen('libSystem.B.dylib', {
|
|
535
535
|
clonefile: { args: ['cstring', 'cstring', 'i32'], returns: 'i32' },
|
|
536
|
-
})
|
|
536
|
+
})
|
|
537
537
|
cloner = (from, to) =>
|
|
538
|
-
symbols.clonefile(Buffer.from(`${from}\0`), Buffer.from(`${to}\0`), 0) === 0
|
|
538
|
+
symbols.clonefile(Buffer.from(`${from}\0`), Buffer.from(`${to}\0`), 0) === 0
|
|
539
539
|
} catch {
|
|
540
540
|
// no clonefile here; the per-file walk stays correct, just slower
|
|
541
541
|
}
|
|
542
542
|
}
|
|
543
|
-
return cloner
|
|
543
|
+
return cloner
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
/**
|
|
@@ -551,49 +551,49 @@ async function directoryCloner() {
|
|
|
551
551
|
* to the per-file copies this replaces.
|
|
552
552
|
*/
|
|
553
553
|
async function normalizeClone(dir: string, pack: PackRules, visited: Set<string>) {
|
|
554
|
-
const stack = [dir]
|
|
555
|
-
const pending: Promise<unknown>[] = []
|
|
554
|
+
const stack = [dir]
|
|
555
|
+
const pending: Promise<unknown>[] = []
|
|
556
556
|
while (stack.length > 0) {
|
|
557
|
-
const current = stack.pop()
|
|
557
|
+
const current = stack.pop()!
|
|
558
558
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
559
|
-
const full = path.join(current, entry.name)
|
|
559
|
+
const full = path.join(current, entry.name)
|
|
560
560
|
// Matched by name before type, as the walk does: the pages-compat shim
|
|
561
561
|
// links `node_modules` at the app root, and following it would pull the
|
|
562
562
|
// whole store into the function. The clone is already inside the
|
|
563
563
|
// function, so the walk's keep-path exemption cannot apply here.
|
|
564
564
|
if (pack.prunedDirs.has(entry.name)) {
|
|
565
|
-
rmSync(full, { recursive: true, force: true })
|
|
565
|
+
rmSync(full, { recursive: true, force: true })
|
|
566
566
|
} else if (entry.isSymbolicLink()) {
|
|
567
|
-
const link = resolveLinkTarget(full)
|
|
568
|
-
rmSync(full, { force: true })
|
|
569
|
-
if (!link) continue
|
|
567
|
+
const link = resolveLinkTarget(full)
|
|
568
|
+
rmSync(full, { force: true })
|
|
569
|
+
if (!link) continue
|
|
570
570
|
if (link.isDirectory) {
|
|
571
|
-
if (visited.has(link.path)) continue
|
|
572
|
-
visited.add(link.path)
|
|
573
|
-
pending.push(copyTree(link.path, full, pack, [], visited))
|
|
571
|
+
if (visited.has(link.path)) continue
|
|
572
|
+
visited.add(link.path)
|
|
573
|
+
pending.push(copyTree(link.path, full, pack, [], visited))
|
|
574
574
|
} else if (shipsFile(pack, entry.name)) {
|
|
575
|
-
pending.push(linkOrCopyFile(link.path, full))
|
|
575
|
+
pending.push(linkOrCopyFile(link.path, full))
|
|
576
576
|
}
|
|
577
577
|
} else if (entry.isDirectory()) {
|
|
578
|
-
stack.push(full)
|
|
578
|
+
stack.push(full)
|
|
579
579
|
} else if (!shipsFile(pack, entry.name)) {
|
|
580
|
-
rmSync(full, { force: true })
|
|
580
|
+
rmSync(full, { force: true })
|
|
581
581
|
}
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
|
-
await Promise.all(pending)
|
|
584
|
+
await Promise.all(pending)
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
interface FrameworkPackage {
|
|
588
|
-
root: string
|
|
589
|
-
packageName: string
|
|
588
|
+
root: string
|
|
589
|
+
packageName: string
|
|
590
590
|
/** Declared runtime dependencies — the only packages it can load eagerly. */
|
|
591
|
-
dependencies: string[]
|
|
591
|
+
dependencies: string[]
|
|
592
592
|
/** Lazily loaded feature deps (og, mdx, sass, image optimization). */
|
|
593
|
-
optionalDependencies: string[]
|
|
594
|
-
inWorkspace: boolean
|
|
593
|
+
optionalDependencies: string[]
|
|
594
|
+
inWorkspace: boolean
|
|
595
595
|
/** Where the framework ships inside the function, relative to its root. */
|
|
596
|
-
targetRel: string
|
|
596
|
+
targetRel: string
|
|
597
597
|
}
|
|
598
598
|
|
|
599
599
|
// The framework itself ships with the function. Inside a workspace it keeps its
|
|
@@ -601,10 +601,10 @@ interface FrameworkPackage {
|
|
|
601
601
|
// outside the workspace (e.g. installed from a registry) it lands in the
|
|
602
602
|
// function's node_modules under its package name.
|
|
603
603
|
async function resolveFrameworkPackage(workspaceRoot: string): Promise<FrameworkPackage> {
|
|
604
|
-
const root = path.resolve(import.meta.dirname, '../../..')
|
|
605
|
-
const packageJson = await readPackageJson(root)
|
|
606
|
-
const packageName = packageJson?.name ?? 'pnext'
|
|
607
|
-
const inWorkspace = isInsideDir(workspaceRoot, root) && !root.includes('node_modules')
|
|
604
|
+
const root = path.resolve(import.meta.dirname, '../../..')
|
|
605
|
+
const packageJson = await readPackageJson(root)
|
|
606
|
+
const packageName = packageJson?.name ?? 'pnext'
|
|
607
|
+
const inWorkspace = isInsideDir(workspaceRoot, root) && !root.includes('node_modules')
|
|
608
608
|
return {
|
|
609
609
|
root,
|
|
610
610
|
packageName,
|
|
@@ -616,7 +616,7 @@ async function resolveFrameworkPackage(workspaceRoot: string): Promise<Framework
|
|
|
616
616
|
targetRel: inWorkspace
|
|
617
617
|
? path.relative(workspaceRoot, root)
|
|
618
618
|
: path.join('node_modules', packageName),
|
|
619
|
-
}
|
|
619
|
+
}
|
|
620
620
|
}
|
|
621
621
|
|
|
622
622
|
// Build-time directories a package that publishes its whole tree still keeps.
|
|
@@ -630,7 +630,7 @@ const packageCopyExcludes = [
|
|
|
630
630
|
'test',
|
|
631
631
|
'tests',
|
|
632
632
|
'__tests__',
|
|
633
|
-
]
|
|
633
|
+
]
|
|
634
634
|
|
|
635
635
|
/**
|
|
636
636
|
* Copies a workspace package the way npm would publish it: a `files` list is the package's own
|
|
@@ -638,69 +638,69 @@ const packageCopyExcludes = [
|
|
|
638
638
|
* back to the exclude list - matching npm's glob semantics is not worth it for a packaging copy.
|
|
639
639
|
*/
|
|
640
640
|
async function copyPackageTree(from: string, to: string, pack: PackRules) {
|
|
641
|
-
const packageJson = await readPackageJson(from)
|
|
642
|
-
const files = packageJson?.files
|
|
641
|
+
const packageJson = await readPackageJson(from)
|
|
642
|
+
const files = packageJson?.files
|
|
643
643
|
if (!files?.length || files.some(entry => /[*?[\]{}!]/.test(entry))) {
|
|
644
|
-
return copyTree(from, to, pack, packageCopyExcludes)
|
|
644
|
+
return copyTree(from, to, pack, packageCopyExcludes)
|
|
645
645
|
}
|
|
646
|
-
await mkdir(to, { recursive: true })
|
|
646
|
+
await mkdir(to, { recursive: true })
|
|
647
647
|
// npm always publishes these two regardless of `files`; the runtime resolves
|
|
648
648
|
// through both (tsconfig for Bun's `paths`/jsx settings). Listed entries need
|
|
649
649
|
// no exclude list — `files` already said what ships — which also lets each
|
|
650
650
|
// directory clone whole.
|
|
651
651
|
await Promise.all(
|
|
652
652
|
[...files, 'package.json', 'tsconfig.json'].map(async name => {
|
|
653
|
-
const source = path.join(from, name)
|
|
654
|
-
const stats = statSync(source, { throwIfNoEntry: false })
|
|
655
|
-
if (!stats) return
|
|
656
|
-
if (stats.isDirectory()) return copyTree(source, path.join(to, name), pack)
|
|
657
|
-
if (!shipsFile(pack, name)) return
|
|
658
|
-
return linkOrCopyFile(source, path.join(to, name))
|
|
653
|
+
const source = path.join(from, name)
|
|
654
|
+
const stats = statSync(source, { throwIfNoEntry: false })
|
|
655
|
+
if (!stats) return
|
|
656
|
+
if (stats.isDirectory()) return copyTree(source, path.join(to, name), pack)
|
|
657
|
+
if (!shipsFile(pack, name)) return
|
|
658
|
+
return linkOrCopyFile(source, path.join(to, name))
|
|
659
659
|
}),
|
|
660
|
-
)
|
|
660
|
+
)
|
|
661
661
|
}
|
|
662
662
|
|
|
663
663
|
async function readPackageJson(dir: string) {
|
|
664
|
-
const file = path.join(dir, 'package.json')
|
|
665
|
-
if (!existsSync(file)) return undefined
|
|
664
|
+
const file = path.join(dir, 'package.json')
|
|
665
|
+
if (!existsSync(file)) return undefined
|
|
666
666
|
return JSON.parse(await readFile(file, 'utf8')) as {
|
|
667
|
-
name?: string
|
|
668
|
-
version?: string
|
|
669
|
-
files?: string[]
|
|
670
|
-
os?: string[]
|
|
671
|
-
cpu?: string[]
|
|
672
|
-
libc?: string[]
|
|
673
|
-
dependencies?: Record<string, string
|
|
674
|
-
optionalDependencies?: Record<string, string
|
|
675
|
-
}
|
|
667
|
+
name?: string
|
|
668
|
+
version?: string
|
|
669
|
+
files?: string[]
|
|
670
|
+
os?: string[]
|
|
671
|
+
cpu?: string[]
|
|
672
|
+
libc?: string[]
|
|
673
|
+
dependencies?: Record<string, string>
|
|
674
|
+
optionalDependencies?: Record<string, string>
|
|
675
|
+
}
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
// The closure hardlinks ~13k files: ~2.9s of a ~3.1s build. Test seam so suites
|
|
679
679
|
// asserting only config shape can skip it. Never off for a real deployment.
|
|
680
|
-
let dependencyClosureEnabled = true
|
|
680
|
+
let dependencyClosureEnabled = true
|
|
681
681
|
|
|
682
682
|
/** @internal Test-only. Returns a restore function. */
|
|
683
683
|
export function setDependencyClosureEnabled(enabled: boolean) {
|
|
684
|
-
const previous = dependencyClosureEnabled
|
|
685
|
-
dependencyClosureEnabled = enabled
|
|
684
|
+
const previous = dependencyClosureEnabled
|
|
685
|
+
dependencyClosureEnabled = enabled
|
|
686
686
|
return () => {
|
|
687
|
-
dependencyClosureEnabled = previous
|
|
688
|
-
}
|
|
687
|
+
dependencyClosureEnabled = previous
|
|
688
|
+
}
|
|
689
689
|
}
|
|
690
690
|
|
|
691
691
|
function skipDependencyClosure() {
|
|
692
|
-
return !dependencyClosureEnabled
|
|
692
|
+
return !dependencyClosureEnabled
|
|
693
693
|
}
|
|
694
694
|
|
|
695
695
|
async function linkOrCopyFile(from: string, to: string) {
|
|
696
696
|
try {
|
|
697
|
-
await link(from, to)
|
|
697
|
+
await link(from, to)
|
|
698
698
|
} catch {
|
|
699
699
|
// Same race as the walk above: a source that vanished mid-copy was never
|
|
700
700
|
// part of the closure, and nothing else here is worth failing the build for.
|
|
701
701
|
await copyFile(from, to).catch(error => {
|
|
702
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
703
|
-
})
|
|
702
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
703
|
+
})
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
|
|
@@ -709,26 +709,26 @@ async function linkOrCopyFile(from: string, to: string) {
|
|
|
709
709
|
// dep, interrupted install), so an unreadable target is skipped rather than
|
|
710
710
|
// failing the build.
|
|
711
711
|
function resolveLinkTarget(link: string) {
|
|
712
|
-
const resolved = realDir(link) ?? realFile(link)
|
|
713
|
-
if (!resolved) return undefined
|
|
712
|
+
const resolved = realDir(link) ?? realFile(link)
|
|
713
|
+
if (!resolved) return undefined
|
|
714
714
|
try {
|
|
715
|
-
return { path: resolved, isDirectory: statSync(resolved).isDirectory() }
|
|
715
|
+
return { path: resolved, isDirectory: statSync(resolved).isDirectory() }
|
|
716
716
|
} catch {
|
|
717
|
-
return undefined
|
|
717
|
+
return undefined
|
|
718
718
|
}
|
|
719
719
|
}
|
|
720
720
|
|
|
721
721
|
function realFile(file: string) {
|
|
722
722
|
try {
|
|
723
|
-
return realpathSync(file)
|
|
723
|
+
return realpathSync(file)
|
|
724
724
|
} catch {
|
|
725
|
-
return undefined
|
|
725
|
+
return undefined
|
|
726
726
|
}
|
|
727
727
|
}
|
|
728
728
|
|
|
729
729
|
function isInsideDir(root: string, file: string) {
|
|
730
|
-
const relative = path.relative(root, file)
|
|
731
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
730
|
+
const relative = path.relative(root, file)
|
|
731
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
732
732
|
}
|
|
733
733
|
|
|
734
734
|
async function injectCompatPaths(
|
|
@@ -739,101 +739,100 @@ async function injectCompatPaths(
|
|
|
739
739
|
const aliasTargets = Object.entries(getImportAliasExtensions().aliases(config, 'server'))
|
|
740
740
|
.filter(([, target]) => path.isAbsolute(target) && !target.includes('node_modules'))
|
|
741
741
|
.map(([specifier, target]) => [specifier, replicaPathFor(target)] as const)
|
|
742
|
-
.filter((entry): entry is readonly [string, string] => Boolean(entry[1]))
|
|
743
|
-
if (aliasTargets.length === 0) return
|
|
742
|
+
.filter((entry): entry is readonly [string, string] => Boolean(entry[1]))
|
|
743
|
+
if (aliasTargets.length === 0) return
|
|
744
744
|
|
|
745
745
|
for (const file of walkTsconfigFiles(functionPath)) {
|
|
746
|
-
let parsed: { compilerOptions?: { baseUrl?: string; paths?: Record<string, string[]> } }
|
|
746
|
+
let parsed: { compilerOptions?: { baseUrl?: string; paths?: Record<string, string[]> } }
|
|
747
747
|
try {
|
|
748
|
-
const source = stripJsonComments(await readFile(file, 'utf8')).replace(/,\s*([}\]])/g, '$1')
|
|
749
|
-
parsed = JSON.parse(source) as typeof parsed
|
|
748
|
+
const source = stripJsonComments(await readFile(file, 'utf8')).replace(/,\s*([}\]])/g, '$1')
|
|
749
|
+
parsed = JSON.parse(source) as typeof parsed
|
|
750
750
|
} catch (error) {
|
|
751
|
-
console.warn(`vercel adapter: could not update ${file}:`, error)
|
|
752
|
-
continue
|
|
751
|
+
console.warn(`vercel adapter: could not update ${file}:`, error)
|
|
752
|
+
continue
|
|
753
753
|
}
|
|
754
|
-
const dir = path.dirname(file)
|
|
755
|
-
const compilerOptions = (parsed.compilerOptions ??= {})
|
|
756
|
-
const paths = (compilerOptions.paths ??= {})
|
|
754
|
+
const dir = path.dirname(file)
|
|
755
|
+
const compilerOptions = (parsed.compilerOptions ??= {})
|
|
756
|
+
const paths = (compilerOptions.paths ??= {})
|
|
757
757
|
for (const [specifier, target] of aliasTargets) {
|
|
758
|
-
if (paths[specifier]) continue
|
|
759
|
-
const relative = toPosixPath(path.relative(dir, target))
|
|
760
|
-
paths[specifier] = [relative.startsWith('.') ? relative : `./${relative}`]
|
|
758
|
+
if (paths[specifier]) continue
|
|
759
|
+
const relative = toPosixPath(path.relative(dir, target))
|
|
760
|
+
paths[specifier] = [relative.startsWith('.') ? relative : `./${relative}`]
|
|
761
761
|
}
|
|
762
|
-
compilerOptions.baseUrl ??= '.'
|
|
762
|
+
compilerOptions.baseUrl ??= '.'
|
|
763
763
|
// The replica shares inodes with the original tsconfig; never write
|
|
764
764
|
// through the hardlink.
|
|
765
|
-
await rm(file)
|
|
766
|
-
await writeFile(file, `${JSON.stringify(parsed, null, 2)}\n`)
|
|
765
|
+
await rm(file)
|
|
766
|
+
await writeFile(file, `${JSON.stringify(parsed, null, 2)}\n`)
|
|
767
767
|
}
|
|
768
768
|
}
|
|
769
769
|
|
|
770
770
|
// Minimal JSONC support for tsconfig files: strips // and /* */ comments
|
|
771
771
|
// outside strings (a "$schema": "https://..." value must survive).
|
|
772
772
|
function stripJsonComments(source: string) {
|
|
773
|
-
let result = ''
|
|
774
|
-
let inString = false
|
|
773
|
+
let result = ''
|
|
774
|
+
let inString = false
|
|
775
775
|
for (let index = 0; index < source.length; index++) {
|
|
776
|
-
const char = source[index]
|
|
776
|
+
const char = source[index]!
|
|
777
777
|
if (inString) {
|
|
778
|
-
result += char
|
|
778
|
+
result += char
|
|
779
779
|
if (char === '\\') {
|
|
780
|
-
result += source[++index] ?? ''
|
|
780
|
+
result += source[++index] ?? ''
|
|
781
781
|
} else if (char === '"') {
|
|
782
|
-
inString = false
|
|
782
|
+
inString = false
|
|
783
783
|
}
|
|
784
|
-
continue
|
|
784
|
+
continue
|
|
785
785
|
}
|
|
786
786
|
if (char === '"') {
|
|
787
|
-
inString = true
|
|
788
|
-
result += char
|
|
789
|
-
continue
|
|
787
|
+
inString = true
|
|
788
|
+
result += char
|
|
789
|
+
continue
|
|
790
790
|
}
|
|
791
791
|
if (char === '/' && source[index + 1] === '/') {
|
|
792
|
-
while (index < source.length && source[index] !== '\n') index
|
|
793
|
-
result += '\n'
|
|
794
|
-
continue
|
|
792
|
+
while (index < source.length && source[index] !== '\n') index++
|
|
793
|
+
result += '\n'
|
|
794
|
+
continue
|
|
795
795
|
}
|
|
796
796
|
if (char === '/' && source[index + 1] === '*') {
|
|
797
|
-
index += 2
|
|
798
|
-
while (index < source.length && !(source[index] === '*' && source[index + 1] === '/'))
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
continue;
|
|
797
|
+
index += 2
|
|
798
|
+
while (index < source.length && !(source[index] === '*' && source[index + 1] === '/')) index++
|
|
799
|
+
index++
|
|
800
|
+
continue
|
|
802
801
|
}
|
|
803
|
-
result += char
|
|
802
|
+
result += char
|
|
804
803
|
}
|
|
805
|
-
return result
|
|
804
|
+
return result
|
|
806
805
|
}
|
|
807
806
|
|
|
808
|
-
const bakedPathFileFilter = /\.(?:m?js|cjs|jsx|tsx?|json|html|css)
|
|
807
|
+
const bakedPathFileFilter = /\.(?:m?js|cjs|jsx|tsx?|json|html|css)$/
|
|
809
808
|
|
|
810
809
|
// The build output is thousands of small files and only a few hundred carry a
|
|
811
810
|
// baked path, so the pass is io-latency-bound: run it in bounded-concurrency
|
|
812
811
|
// batches instead of one file at a time.
|
|
813
|
-
const BAKED_PATH_CONCURRENCY = 64
|
|
812
|
+
const BAKED_PATH_CONCURRENCY = 64
|
|
814
813
|
|
|
815
814
|
async function rewriteBakedPaths(
|
|
816
815
|
dir: string,
|
|
817
816
|
replacements: readonly (readonly [string, string])[],
|
|
818
817
|
) {
|
|
819
|
-
if (!existsSync(dir)) return
|
|
820
|
-
const files = (await listFiles(dir)).filter(file => bakedPathFileFilter.test(file))
|
|
818
|
+
if (!existsSync(dir)) return
|
|
819
|
+
const files = (await listFiles(dir)).filter(file => bakedPathFileFilter.test(file))
|
|
821
820
|
for (let i = 0; i < files.length; i += BAKED_PATH_CONCURRENCY) {
|
|
822
821
|
await Promise.all(
|
|
823
822
|
files.slice(i, i + BAKED_PATH_CONCURRENCY).map(async file => {
|
|
824
|
-
const source = await readFile(file, 'utf8')
|
|
825
|
-
let rewritten = source
|
|
823
|
+
const source = await readFile(file, 'utf8')
|
|
824
|
+
let rewritten = source
|
|
826
825
|
for (const [from, to] of replacements) {
|
|
827
|
-
if (rewritten.includes(from)) rewritten = rewritten.replaceAll(from, to)
|
|
826
|
+
if (rewritten.includes(from)) rewritten = rewritten.replaceAll(from, to)
|
|
828
827
|
}
|
|
829
|
-
if (rewritten === source) return
|
|
828
|
+
if (rewritten === source) return
|
|
830
829
|
// The replica is hardlinked to the real build output; unlink before
|
|
831
830
|
// writing so the rewrite never reaches the original through a shared
|
|
832
831
|
// inode.
|
|
833
|
-
await rm(file)
|
|
834
|
-
await writeFile(file, rewritten)
|
|
832
|
+
await rm(file)
|
|
833
|
+
await writeFile(file, rewritten)
|
|
835
834
|
}),
|
|
836
|
-
)
|
|
835
|
+
)
|
|
837
836
|
}
|
|
838
837
|
}
|
|
839
838
|
|
|
@@ -846,19 +845,19 @@ async function rewriteBakedPaths(
|
|
|
846
845
|
async function artifactSpecifiers(file: string): Promise<string[] | null> {
|
|
847
846
|
const sidecar = await readFile(`${file}${compiledSpecifiersManifestSuffix}`, 'utf8').catch(
|
|
848
847
|
() => null,
|
|
849
|
-
)
|
|
848
|
+
)
|
|
850
849
|
if (sidecar !== null) {
|
|
851
850
|
try {
|
|
852
|
-
const parsed: unknown = JSON.parse(sidecar)
|
|
851
|
+
const parsed: unknown = JSON.parse(sidecar)
|
|
853
852
|
if (Array.isArray(parsed) && parsed.every(entry => typeof entry === 'string')) {
|
|
854
|
-
return parsed
|
|
853
|
+
return parsed
|
|
855
854
|
}
|
|
856
855
|
} catch {
|
|
857
856
|
// malformed sidecar (partial write, foreign tool) — fall through to parse
|
|
858
857
|
}
|
|
859
858
|
}
|
|
860
|
-
const source = await readFile(file, 'utf8').catch(() => null)
|
|
861
|
-
return source === null ? null : importSpecifiers(source, file)
|
|
859
|
+
const source = await readFile(file, 'utf8').catch(() => null)
|
|
860
|
+
return source === null ? null : importSpecifiers(source, file)
|
|
862
861
|
}
|
|
863
862
|
|
|
864
863
|
// Ship only the node_modules packages the server can actually reach at request time: bare imports
|
|
@@ -875,9 +874,9 @@ async function traceNodeModulesClosure(
|
|
|
875
874
|
pack: PackRules,
|
|
876
875
|
log: VerboseLogger,
|
|
877
876
|
) {
|
|
878
|
-
const workspaceRoot = config.workspaceRoot
|
|
879
|
-
const specifiers = new Set<string>()
|
|
880
|
-
const tracedFiles = new Set<string>()
|
|
877
|
+
const workspaceRoot = config.workspaceRoot
|
|
878
|
+
const specifiers = new Set<string>()
|
|
879
|
+
const tracedFiles = new Set<string>()
|
|
881
880
|
// Compat-aliased specifiers whose target lives in the framework source
|
|
882
881
|
// (react, next/*) never resolve from node_modules at runtime — shipping
|
|
883
882
|
// them would drag in the full next/react trees. Aliases that point into
|
|
@@ -886,126 +885,126 @@ async function traceNodeModulesClosure(
|
|
|
886
885
|
Object.entries(getImportAliasExtensions().aliases(config, 'server'))
|
|
887
886
|
.filter(([, target]) => path.isAbsolute(target) && !target.includes('node_modules'))
|
|
888
887
|
.map(([specifier]) => specifier),
|
|
889
|
-
)
|
|
888
|
+
)
|
|
890
889
|
await log.step('trace runtime imports', async () => {
|
|
891
890
|
// Only code Bun imports raw at request time needs real node_modules: route handlers, the proxy,
|
|
892
891
|
// the config chain, and the compiled modules the warm step just wrote. Pages resolve through that
|
|
893
892
|
// compiled cache, whose externals are vendored, so their dependencies never load raw.
|
|
894
|
-
const queue: string[] = []
|
|
895
|
-
const seen = tracedFiles
|
|
893
|
+
const queue: string[] = []
|
|
894
|
+
const seen = tracedFiles
|
|
896
895
|
const enqueue = (file: string | undefined) => {
|
|
897
|
-
if (!file) return
|
|
898
|
-
const resolved = path.resolve(file)
|
|
899
|
-
if (seen.has(resolved) || !existsSync(resolved)) return
|
|
900
|
-
seen.add(resolved)
|
|
901
|
-
queue.push(resolved)
|
|
902
|
-
}
|
|
896
|
+
if (!file) return
|
|
897
|
+
const resolved = path.resolve(file)
|
|
898
|
+
if (seen.has(resolved) || !existsSync(resolved)) return
|
|
899
|
+
seen.add(resolved)
|
|
900
|
+
queue.push(resolved)
|
|
901
|
+
}
|
|
903
902
|
|
|
904
903
|
for (const route of manifest.routes) {
|
|
905
|
-
if (route.kind === 'handler') enqueue(route.file)
|
|
904
|
+
if (route.kind === 'handler') enqueue(route.file)
|
|
906
905
|
}
|
|
907
|
-
enqueue(findProxyFile(config) ?? undefined)
|
|
908
|
-
enqueue(path.join(config.root, 'pnext.config.ts'))
|
|
909
|
-
enqueue(path.join(config.root, 'next.config.js'))
|
|
910
|
-
for (const file of warmedModules) enqueue(file)
|
|
906
|
+
enqueue(findProxyFile(config) ?? undefined)
|
|
907
|
+
enqueue(path.join(config.root, 'pnext.config.ts'))
|
|
908
|
+
enqueue(path.join(config.root, 'next.config.js'))
|
|
909
|
+
for (const file of warmedModules) enqueue(file)
|
|
911
910
|
|
|
912
911
|
const visit = async (file: string) => {
|
|
913
|
-
if (!scriptFilePattern.test(file)) return
|
|
914
|
-
const found = await artifactSpecifiers(file)
|
|
915
|
-
if (found === null) return
|
|
912
|
+
if (!scriptFilePattern.test(file)) return
|
|
913
|
+
const found = await artifactSpecifiers(file)
|
|
914
|
+
if (found === null) return
|
|
916
915
|
for (const specifier of found) {
|
|
917
|
-
if (frameworkAliasedSpecifiers.has(specifier)) continue
|
|
916
|
+
if (frameworkAliasedSpecifiers.has(specifier)) continue
|
|
918
917
|
// Compiled modules import each other — and whatever the compiler left
|
|
919
918
|
// external — by absolute href. Follow the ones inside the build output
|
|
920
919
|
// and ship the packages the rest point into.
|
|
921
|
-
const absolute = absolutePathFromSpecifier(specifier)
|
|
920
|
+
const absolute = absolutePathFromSpecifier(specifier)
|
|
922
921
|
if (absolute) {
|
|
923
|
-
const name = packageNameFromPath(absolute)
|
|
924
|
-
if (name) specifiers.add(name)
|
|
925
|
-
else if (isInsideDir(config.outPath, absolute)) enqueue(absolute)
|
|
926
|
-
continue
|
|
922
|
+
const name = packageNameFromPath(absolute)
|
|
923
|
+
if (name) specifiers.add(name)
|
|
924
|
+
else if (isInsideDir(config.outPath, absolute)) enqueue(absolute)
|
|
925
|
+
continue
|
|
927
926
|
}
|
|
928
|
-
if (/^(?:node:|bun$|bun:|data:)/.test(specifier)) continue
|
|
927
|
+
if (/^(?:node:|bun$|bun:|data:)/.test(specifier)) continue
|
|
929
928
|
// resolveImport covers relative paths, tsconfig aliases, package imports and workspace
|
|
930
929
|
// packages - workspace source gets traced further. Bare package specifiers are also recorded
|
|
931
930
|
// so the closure ships (or workspace-links) their node_modules entries; workspace packages
|
|
932
931
|
// need the link even when their source is traced.
|
|
933
|
-
const resolved = resolveImport(config.root, file, specifier)
|
|
932
|
+
const resolved = resolveImport(config.root, file, specifier)
|
|
934
933
|
if (
|
|
935
934
|
resolved &&
|
|
936
935
|
isInsideDir(workspaceRoot, resolved) &&
|
|
937
936
|
!resolved.includes('node_modules')
|
|
938
937
|
) {
|
|
939
|
-
enqueue(resolved)
|
|
938
|
+
enqueue(resolved)
|
|
940
939
|
}
|
|
941
|
-
const name = packageNameFromSpecifier(specifier)
|
|
942
|
-
if (name) specifiers.add(name)
|
|
940
|
+
const name = packageNameFromSpecifier(specifier)
|
|
941
|
+
if (name) specifiers.add(name)
|
|
943
942
|
}
|
|
944
|
-
}
|
|
943
|
+
}
|
|
945
944
|
|
|
946
945
|
// A level's reads are independent, so the walk advances a level at a time, bounded so a wide
|
|
947
946
|
// graph cannot exhaust the fd table.
|
|
948
|
-
const READ_BATCH = 64
|
|
947
|
+
const READ_BATCH = 64
|
|
949
948
|
for (let frontier = queue.splice(0); frontier.length > 0; frontier = queue.splice(0)) {
|
|
950
949
|
for (let index = 0; index < frontier.length; index += READ_BATCH) {
|
|
951
|
-
await Promise.all(frontier.slice(index, index + READ_BATCH).map(visit))
|
|
950
|
+
await Promise.all(frontier.slice(index, index + READ_BATCH).map(visit))
|
|
952
951
|
}
|
|
953
952
|
}
|
|
954
|
-
log.log(`traced ${seen.size} runtime modules`)
|
|
955
|
-
})
|
|
953
|
+
log.log(`traced ${seen.size} runtime modules`)
|
|
954
|
+
})
|
|
956
955
|
|
|
957
|
-
const packageDirs = new Map<string, string>()
|
|
958
|
-
const workspaceLinks = new Map<string, string>()
|
|
956
|
+
const packageDirs = new Map<string, string>()
|
|
957
|
+
const workspaceLinks = new Map<string, string>()
|
|
959
958
|
// Native bindings resolved here are built for the build host; the function
|
|
960
959
|
// needs the ones for its own platform, fetched below.
|
|
961
|
-
const hostNatives = new Map<string, { dir: string; owner?: PackageVersion }>()
|
|
960
|
+
const hostNatives = new Map<string, { dir: string; owner?: PackageVersion }>()
|
|
962
961
|
// The framework ships whole, so what it can load at runtime is exactly what it declares as a
|
|
963
962
|
// runtime dependency - resolved from its own root, which is not the app's when it comes from a
|
|
964
963
|
// registry. Its optional deps back compat features that only load when the app uses them, so they
|
|
965
964
|
// ship only where the app declares them too.
|
|
966
|
-
const appDependencies = await declaredDependencies(config.root)
|
|
965
|
+
const appDependencies = await declaredDependencies(config.root)
|
|
967
966
|
const queue: { name: string; from: string; owner?: PackageVersion }[] = [
|
|
968
967
|
...framework.dependencies.map(name => ({ name, from: framework.root })),
|
|
969
968
|
...framework.optionalDependencies
|
|
970
969
|
.filter(name => appDependencies.has(name))
|
|
971
970
|
.map(name => ({ name, from: framework.root })),
|
|
972
971
|
...[...specifiers].map(name => ({ name, from: workspaceRoot })),
|
|
973
|
-
]
|
|
974
|
-
const seen = new Set<string>()
|
|
972
|
+
]
|
|
973
|
+
const seen = new Set<string>()
|
|
975
974
|
|
|
976
975
|
while (queue.length > 0) {
|
|
977
|
-
const { name, from, owner } = queue.shift()
|
|
978
|
-
if (seen.has(name)) continue
|
|
979
|
-
seen.add(name)
|
|
976
|
+
const { name, from, owner } = queue.shift()!
|
|
977
|
+
if (seen.has(name)) continue
|
|
978
|
+
seen.add(name)
|
|
980
979
|
// The framework is copied to its own location by the caller; copying it
|
|
981
980
|
// again under its package name collides with that (and with the workspace
|
|
982
981
|
// link it needs when it lives in the workspace).
|
|
983
982
|
if (name === framework.packageName) {
|
|
984
|
-
if (framework.inWorkspace) workspaceLinks.set(name, framework.targetRel)
|
|
985
|
-
continue
|
|
983
|
+
if (framework.inWorkspace) workspaceLinks.set(name, framework.targetRel)
|
|
984
|
+
continue
|
|
986
985
|
}
|
|
987
|
-
const dir = resolvePackageDir(name, [from, workspaceRoot, config.root])
|
|
988
|
-
if (!dir) continue
|
|
986
|
+
const dir = resolvePackageDir(name, [from, workspaceRoot, config.root])
|
|
987
|
+
if (!dir) continue
|
|
989
988
|
|
|
990
989
|
// Workspace packages ship at their workspace-relative path; link them so
|
|
991
990
|
// bare imports resolve to the same real path the compiled cache uses.
|
|
992
|
-
const workspaceRelative = path.relative(workspaceRoot, dir)
|
|
991
|
+
const workspaceRelative = path.relative(workspaceRoot, dir)
|
|
993
992
|
if (!workspaceRelative.startsWith('..') && !workspaceRelative.startsWith('node_modules')) {
|
|
994
|
-
workspaceLinks.set(name, workspaceRelative)
|
|
995
|
-
continue
|
|
993
|
+
workspaceLinks.set(name, workspaceRelative)
|
|
994
|
+
continue
|
|
996
995
|
}
|
|
997
996
|
|
|
998
|
-
const packageJson = await readPackageJson(dir)
|
|
997
|
+
const packageJson = await readPackageJson(dir)
|
|
999
998
|
// `os`/`cpu` are npm's own declaration that a package is platform-bound;
|
|
1000
999
|
// one that already fits the function's platform ships as-is.
|
|
1001
|
-
const platformBound = Boolean(packageJson?.os ?? packageJson?.cpu)
|
|
1000
|
+
const platformBound = Boolean(packageJson?.os ?? packageJson?.cpu)
|
|
1002
1001
|
if (packageJson && platformBound && !matchesFunctionPlatform(packageJson)) {
|
|
1003
|
-
hostNatives.set(name, { dir, owner })
|
|
1004
|
-
continue
|
|
1002
|
+
hostNatives.set(name, { dir, owner })
|
|
1003
|
+
continue
|
|
1005
1004
|
}
|
|
1006
|
-
packageDirs.set(name, dir)
|
|
1007
|
-
if (!packageJson) continue
|
|
1008
|
-
const self = { name, version: packageJson.version ?? '' }
|
|
1005
|
+
packageDirs.set(name, dir)
|
|
1006
|
+
if (!packageJson) continue
|
|
1007
|
+
const self = { name, version: packageJson.version ?? '' }
|
|
1009
1008
|
// Peer dependencies are deliberately not expanded — they fan out to whole
|
|
1010
1009
|
// ecosystems (react, next, ...) that are only shipped when something
|
|
1011
1010
|
// actually imports them.
|
|
@@ -1013,7 +1012,7 @@ async function traceNodeModulesClosure(
|
|
|
1013
1012
|
...Object.keys(packageJson.dependencies ?? {}),
|
|
1014
1013
|
...Object.keys(packageJson.optionalDependencies ?? {}),
|
|
1015
1014
|
]) {
|
|
1016
|
-
queue.push({ name: dependency, from: dir, owner: self })
|
|
1015
|
+
queue.push({ name: dependency, from: dir, owner: self })
|
|
1017
1016
|
}
|
|
1018
1017
|
}
|
|
1019
1018
|
|
|
@@ -1021,44 +1020,44 @@ async function traceNodeModulesClosure(
|
|
|
1021
1020
|
const targets = await log.step(
|
|
1022
1021
|
`resolve ${FUNCTION_PLATFORM.os}-${FUNCTION_PLATFORM.cpu} natives (${hostNatives.size} packages)`,
|
|
1023
1022
|
() => platformNativePackages(hostNatives, log),
|
|
1024
|
-
)
|
|
1025
|
-
for (const [name, dir] of targets) packageDirs.set(name, dir)
|
|
1023
|
+
)
|
|
1024
|
+
for (const [name, dir] of targets) packageDirs.set(name, dir)
|
|
1026
1025
|
}
|
|
1027
1026
|
|
|
1028
1027
|
const copy = async () => {
|
|
1029
|
-
const entries = skipDependencyClosure() ? [] : [...packageDirs]
|
|
1030
|
-
const batchSize = 16
|
|
1028
|
+
const entries = skipDependencyClosure() ? [] : [...packageDirs]
|
|
1029
|
+
const batchSize = 16
|
|
1031
1030
|
for (let index = 0; index < entries.length; index += batchSize) {
|
|
1032
1031
|
await Promise.all(
|
|
1033
1032
|
entries.slice(index, index + batchSize).map(async ([name, dir]) => {
|
|
1034
|
-
const target = path.join(functionPath, 'node_modules', name)
|
|
1035
|
-
await mkdir(path.dirname(target), { recursive: true })
|
|
1036
|
-
await copyTree(dir, target, pack)
|
|
1033
|
+
const target = path.join(functionPath, 'node_modules', name)
|
|
1034
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
1035
|
+
await copyTree(dir, target, pack)
|
|
1037
1036
|
}),
|
|
1038
|
-
)
|
|
1037
|
+
)
|
|
1039
1038
|
}
|
|
1040
1039
|
for (const [name, workspaceRelative] of workspaceLinks) {
|
|
1041
|
-
const linkPath = path.join(functionPath, 'node_modules', name)
|
|
1042
|
-
await mkdir(path.dirname(linkPath), { recursive: true })
|
|
1040
|
+
const linkPath = path.join(functionPath, 'node_modules', name)
|
|
1041
|
+
await mkdir(path.dirname(linkPath), { recursive: true })
|
|
1043
1042
|
const target = path.relative(
|
|
1044
1043
|
path.dirname(linkPath),
|
|
1045
1044
|
path.join(functionPath, workspaceRelative),
|
|
1046
|
-
)
|
|
1047
|
-
if (!existsSync(linkPath)) await symlink(target, linkPath, 'dir')
|
|
1045
|
+
)
|
|
1046
|
+
if (!existsSync(linkPath)) await symlink(target, linkPath, 'dir')
|
|
1048
1047
|
}
|
|
1049
|
-
}
|
|
1048
|
+
}
|
|
1050
1049
|
|
|
1051
1050
|
return {
|
|
1052
1051
|
tracedFiles,
|
|
1053
1052
|
workspacePackages: [...workspaceLinks.values()],
|
|
1054
1053
|
packageCount: packageDirs.size,
|
|
1055
1054
|
copy,
|
|
1056
|
-
}
|
|
1055
|
+
}
|
|
1057
1056
|
}
|
|
1058
1057
|
|
|
1059
1058
|
interface PackageVersion {
|
|
1060
|
-
name: string
|
|
1061
|
-
version: string
|
|
1059
|
+
name: string
|
|
1060
|
+
version: string
|
|
1062
1061
|
}
|
|
1063
1062
|
|
|
1064
1063
|
/**
|
|
@@ -1074,56 +1073,52 @@ async function platformNativePackages(
|
|
|
1074
1073
|
) {
|
|
1075
1074
|
// Install the package that *owns* the binding: only it lists every
|
|
1076
1075
|
// platform's build in its optional deps.
|
|
1077
|
-
const owners = new Map<string, string>()
|
|
1076
|
+
const owners = new Map<string, string>()
|
|
1078
1077
|
for (const [name, { dir, owner }] of hostNatives) {
|
|
1079
|
-
const spec = owner ?? { name, version: (await readPackageJson(dir))?.version ?? '' }
|
|
1080
|
-
if (spec.version) owners.set(spec.name, spec.version)
|
|
1078
|
+
const spec = owner ?? { name, version: (await readPackageJson(dir))?.version ?? '' }
|
|
1079
|
+
if (spec.version) owners.set(spec.name, spec.version)
|
|
1081
1080
|
}
|
|
1082
|
-
const specs = [...owners].map(([name, version]) => `${name}@${version}`).sort()
|
|
1083
|
-
const fallback = () => new Map([...hostNatives].map(([name, { dir }]) => [name, dir]))
|
|
1084
|
-
if (specs.length === 0) return fallback()
|
|
1081
|
+
const specs = [...owners].map(([name, version]) => `${name}@${version}`).sort()
|
|
1082
|
+
const fallback = () => new Map([...hostNatives].map(([name, { dir }]) => [name, dir]))
|
|
1083
|
+
if (specs.length === 0) return fallback()
|
|
1085
1084
|
|
|
1086
1085
|
try {
|
|
1087
|
-
const installed = await crossPlatformInstall(specs)
|
|
1088
|
-
const matches = new Map<string, string>()
|
|
1086
|
+
const installed = await crossPlatformInstall(specs)
|
|
1087
|
+
const matches = new Map<string, string>()
|
|
1089
1088
|
for (const [name, dir] of installed) {
|
|
1090
|
-
const packageJson = await readPackageJson(dir)
|
|
1091
|
-
if (packageJson && matchesFunctionPlatform(packageJson)) matches.set(name, dir)
|
|
1089
|
+
const packageJson = await readPackageJson(dir)
|
|
1090
|
+
if (packageJson && matchesFunctionPlatform(packageJson)) matches.set(name, dir)
|
|
1092
1091
|
}
|
|
1093
1092
|
if (matches.size === 0) {
|
|
1094
|
-
throw new Error(`no ${FUNCTION_PLATFORM.os} builds in ${specs.join(' ')}`)
|
|
1093
|
+
throw new Error(`no ${FUNCTION_PLATFORM.os} builds in ${specs.join(' ')}`)
|
|
1095
1094
|
}
|
|
1096
|
-
return matches
|
|
1095
|
+
return matches
|
|
1097
1096
|
} catch (error) {
|
|
1098
1097
|
console.warn(
|
|
1099
1098
|
`vercel adapter: could not fetch ${FUNCTION_PLATFORM.os}-${FUNCTION_PLATFORM.cpu} native packages; ` +
|
|
1100
1099
|
`the function will carry this machine's builds and fail at runtime:`,
|
|
1101
1100
|
error,
|
|
1102
|
-
)
|
|
1103
|
-
log.log('falling back to host native packages')
|
|
1104
|
-
return fallback()
|
|
1101
|
+
)
|
|
1102
|
+
log.log('falling back to host native packages')
|
|
1103
|
+
return fallback()
|
|
1105
1104
|
}
|
|
1106
1105
|
}
|
|
1107
1106
|
|
|
1108
|
-
function matchesFunctionPlatform(packageJson: {
|
|
1109
|
-
os?: string[];
|
|
1110
|
-
cpu?: string[];
|
|
1111
|
-
libc?: string[];
|
|
1112
|
-
}) {
|
|
1107
|
+
function matchesFunctionPlatform(packageJson: { os?: string[]; cpu?: string[]; libc?: string[] }) {
|
|
1113
1108
|
// npm's field semantics: an all-negated list excludes, anything else is an
|
|
1114
1109
|
// allow-list (`os: ["!win32"]` still fits linux).
|
|
1115
1110
|
const matches = (declared: string[] | undefined, value: string) => {
|
|
1116
|
-
if (!declared?.length) return true
|
|
1117
|
-
if (declared.every(entry => entry.startsWith('!'))) return !declared.includes(`!${value}`)
|
|
1118
|
-
return declared.includes(value)
|
|
1119
|
-
}
|
|
1111
|
+
if (!declared?.length) return true
|
|
1112
|
+
if (declared.every(entry => entry.startsWith('!'))) return !declared.includes(`!${value}`)
|
|
1113
|
+
return declared.includes(value)
|
|
1114
|
+
}
|
|
1120
1115
|
return (
|
|
1121
1116
|
matches(packageJson.os, FUNCTION_PLATFORM.os) &&
|
|
1122
1117
|
matches(packageJson.cpu, FUNCTION_PLATFORM.cpu) &&
|
|
1123
1118
|
// Both a glibc and a musl build exist for the same os/cpu; Vercel's
|
|
1124
1119
|
// function runtime is glibc.
|
|
1125
1120
|
matches(packageJson.libc, FUNCTION_PLATFORM.libc)
|
|
1126
|
-
)
|
|
1121
|
+
)
|
|
1127
1122
|
}
|
|
1128
1123
|
|
|
1129
1124
|
/**
|
|
@@ -1135,16 +1130,16 @@ function matchesFunctionPlatform(packageJson: {
|
|
|
1135
1130
|
async function crossPlatformInstall(specs: string[]) {
|
|
1136
1131
|
const key = Bun.hash
|
|
1137
1132
|
.xxHash3(`${FUNCTION_PLATFORM.os}-${FUNCTION_PLATFORM.cpu}\0${specs.join('\0')}`)
|
|
1138
|
-
.toString(16)
|
|
1139
|
-
const dir = path.join(cacheRoot(), 'vercel-natives', key)
|
|
1140
|
-
const modules = path.join(dir, 'node_modules')
|
|
1133
|
+
.toString(16)
|
|
1134
|
+
const dir = path.join(cacheRoot(), 'vercel-natives', key)
|
|
1135
|
+
const modules = path.join(dir, 'node_modules')
|
|
1141
1136
|
if (!existsSync(modules)) {
|
|
1142
1137
|
// Land under a temp name and rename: a concurrent build must never read a
|
|
1143
1138
|
// half-installed tree.
|
|
1144
|
-
const staging = `${dir}.${process.pid.toString(36)}
|
|
1145
|
-
await rm(staging, { recursive: true, force: true })
|
|
1146
|
-
await mkdir(staging, { recursive: true })
|
|
1147
|
-
await writeText(path.join(staging, 'package.json'), '{"name":"pnext-natives"}\n')
|
|
1139
|
+
const staging = `${dir}.${process.pid.toString(36)}`
|
|
1140
|
+
await rm(staging, { recursive: true, force: true })
|
|
1141
|
+
await mkdir(staging, { recursive: true })
|
|
1142
|
+
await writeText(path.join(staging, 'package.json'), '{"name":"pnext-natives"}\n')
|
|
1148
1143
|
const install = Bun.spawn(
|
|
1149
1144
|
[
|
|
1150
1145
|
'bun',
|
|
@@ -1157,46 +1152,46 @@ async function crossPlatformInstall(specs: string[]) {
|
|
|
1157
1152
|
...specs,
|
|
1158
1153
|
],
|
|
1159
1154
|
{ cwd: staging, stdout: 'ignore', stderr: 'pipe' },
|
|
1160
|
-
)
|
|
1155
|
+
)
|
|
1161
1156
|
if ((await install.exited) !== 0) {
|
|
1162
|
-
const stderr = await new Response(install.stderr).text()
|
|
1163
|
-
await rm(staging, { recursive: true, force: true })
|
|
1164
|
-
throw new Error(stderr.trim() || 'bun add failed')
|
|
1157
|
+
const stderr = await new Response(install.stderr).text()
|
|
1158
|
+
await rm(staging, { recursive: true, force: true })
|
|
1159
|
+
throw new Error(stderr.trim() || 'bun add failed')
|
|
1165
1160
|
}
|
|
1166
|
-
await rm(dir, { recursive: true, force: true })
|
|
1167
|
-
await rename(staging, dir)
|
|
1161
|
+
await rm(dir, { recursive: true, force: true })
|
|
1162
|
+
await rename(staging, dir)
|
|
1168
1163
|
}
|
|
1169
1164
|
|
|
1170
|
-
const installed = new Map<string, string>()
|
|
1165
|
+
const installed = new Map<string, string>()
|
|
1171
1166
|
for (const entry of readdirSync(modules)) {
|
|
1172
|
-
if (entry.startsWith('.')) continue
|
|
1167
|
+
if (entry.startsWith('.')) continue
|
|
1173
1168
|
if (!entry.startsWith('@')) {
|
|
1174
|
-
installed.set(entry, path.join(modules, entry))
|
|
1175
|
-
continue
|
|
1169
|
+
installed.set(entry, path.join(modules, entry))
|
|
1170
|
+
continue
|
|
1176
1171
|
}
|
|
1177
1172
|
for (const scoped of readdirSync(path.join(modules, entry))) {
|
|
1178
|
-
installed.set(`${entry}/${scoped}`, path.join(modules, entry, scoped))
|
|
1173
|
+
installed.set(`${entry}/${scoped}`, path.join(modules, entry, scoped))
|
|
1179
1174
|
}
|
|
1180
1175
|
}
|
|
1181
|
-
return installed
|
|
1176
|
+
return installed
|
|
1182
1177
|
}
|
|
1183
1178
|
|
|
1184
|
-
const scriptFilePattern = /\.(?:m?js|cjs|jsx|tsx?)
|
|
1179
|
+
const scriptFilePattern = /\.(?:m?js|cjs|jsx|tsx?)$/
|
|
1185
1180
|
|
|
1186
1181
|
// Directories the shipped tsconfigs can never live in; pruned during the walk —
|
|
1187
1182
|
// a naive recursive listing would crawl the function's own node_modules.
|
|
1188
|
-
const skippedScanDirs = new Set(['node_modules', '.git', '.next', '.pnext', '.turbo', '.vercel'])
|
|
1183
|
+
const skippedScanDirs = new Set(['node_modules', '.git', '.next', '.pnext', '.turbo', '.vercel'])
|
|
1189
1184
|
|
|
1190
1185
|
function* walkTsconfigFiles(root: string): Generator<string> {
|
|
1191
|
-
const stack = [root]
|
|
1186
|
+
const stack = [root]
|
|
1192
1187
|
while (stack.length > 0) {
|
|
1193
|
-
const dir = stack.pop()
|
|
1188
|
+
const dir = stack.pop()!
|
|
1194
1189
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1195
|
-
const full = path.join(dir, entry.name)
|
|
1190
|
+
const full = path.join(dir, entry.name)
|
|
1196
1191
|
if (entry.isDirectory()) {
|
|
1197
|
-
if (!skippedScanDirs.has(entry.name)) stack.push(full)
|
|
1192
|
+
if (!skippedScanDirs.has(entry.name)) stack.push(full)
|
|
1198
1193
|
} else if (entry.isFile() && entry.name === 'tsconfig.json') {
|
|
1199
|
-
yield full
|
|
1194
|
+
yield full
|
|
1200
1195
|
}
|
|
1201
1196
|
}
|
|
1202
1197
|
}
|
|
@@ -1205,62 +1200,61 @@ function* walkTsconfigFiles(root: string): Generator<string> {
|
|
|
1205
1200
|
/** Every dependency name an app's own package.json declares. */
|
|
1206
1201
|
async function declaredDependencies(root: string) {
|
|
1207
1202
|
const packageJson = (await readPackageJson(root)) as
|
|
1208
|
-
|
|
1209
|
-
| undefined;
|
|
1203
|
+
Record<string, Record<string, string> | undefined> | undefined
|
|
1210
1204
|
return new Set(
|
|
1211
1205
|
['dependencies', 'optionalDependencies', 'devDependencies'].flatMap(field =>
|
|
1212
1206
|
Object.keys(packageJson?.[field] ?? {}),
|
|
1213
1207
|
),
|
|
1214
|
-
)
|
|
1208
|
+
)
|
|
1215
1209
|
}
|
|
1216
1210
|
|
|
1217
1211
|
/** Absolute path behind an `import`, whether written as a path or a file URL. */
|
|
1218
1212
|
function absolutePathFromSpecifier(specifier: string) {
|
|
1219
|
-
if (specifier.startsWith('file://')) return fileURLToPath(specifier)
|
|
1220
|
-
return path.isAbsolute(specifier) ? specifier : undefined
|
|
1213
|
+
if (specifier.startsWith('file://')) return fileURLToPath(specifier)
|
|
1214
|
+
return path.isAbsolute(specifier) ? specifier : undefined
|
|
1221
1215
|
}
|
|
1222
1216
|
|
|
1223
1217
|
/** The package an absolute path belongs to, if it points inside node_modules. */
|
|
1224
1218
|
function packageNameFromPath(file: string) {
|
|
1225
|
-
const parts = toPosixPath(file).split('/node_modules/')
|
|
1226
|
-
const tail = parts.at(-1)
|
|
1227
|
-
if (parts.length < 2 || !tail) return undefined
|
|
1228
|
-
const segments = tail.split('/')
|
|
1229
|
-
return segments[0]?.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0]
|
|
1219
|
+
const parts = toPosixPath(file).split('/node_modules/')
|
|
1220
|
+
const tail = parts.at(-1)
|
|
1221
|
+
if (parts.length < 2 || !tail) return undefined
|
|
1222
|
+
const segments = tail.split('/')
|
|
1223
|
+
return segments[0]?.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0]
|
|
1230
1224
|
}
|
|
1231
1225
|
|
|
1232
1226
|
function packageNameFromSpecifier(specifier: string) {
|
|
1233
|
-
if (/^[./#]|^file:|^node:|^data:/.test(specifier)) return undefined
|
|
1234
|
-
if (specifier === 'bun' || specifier.startsWith('bun:')) return undefined
|
|
1235
|
-
if (specifier.includes(':')) return undefined
|
|
1227
|
+
if (/^[./#]|^file:|^node:|^data:/.test(specifier)) return undefined
|
|
1228
|
+
if (specifier === 'bun' || specifier.startsWith('bun:')) return undefined
|
|
1229
|
+
if (specifier.includes(':')) return undefined
|
|
1236
1230
|
// Scoped names need a real scope — `@/env`-style tsconfig aliases are not
|
|
1237
1231
|
// packages.
|
|
1238
|
-
if (specifier.startsWith('@') && !/^@[^/]+\/[^/]+/.test(specifier)) return undefined
|
|
1239
|
-
const parts = specifier.split('/')
|
|
1240
|
-
const name = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
1241
|
-
if (!name || builtinModules.includes(name)) return undefined
|
|
1242
|
-
return name
|
|
1232
|
+
if (specifier.startsWith('@') && !/^@[^/]+\/[^/]+/.test(specifier)) return undefined
|
|
1233
|
+
const parts = specifier.split('/')
|
|
1234
|
+
const name = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
1235
|
+
if (!name || builtinModules.includes(name)) return undefined
|
|
1236
|
+
return name
|
|
1243
1237
|
}
|
|
1244
1238
|
|
|
1245
1239
|
function resolvePackageDir(name: string, fromDirs: string[]) {
|
|
1246
1240
|
for (const from of fromDirs) {
|
|
1247
|
-
let dir = path.resolve(from)
|
|
1241
|
+
let dir = path.resolve(from)
|
|
1248
1242
|
while (true) {
|
|
1249
|
-
const candidate = path.join(dir, 'node_modules', name)
|
|
1250
|
-
if (existsSync(path.join(candidate, 'package.json'))) return realDir(candidate)
|
|
1251
|
-
const parent = path.dirname(dir)
|
|
1252
|
-
if (parent === dir) break
|
|
1253
|
-
dir = parent
|
|
1243
|
+
const candidate = path.join(dir, 'node_modules', name)
|
|
1244
|
+
if (existsSync(path.join(candidate, 'package.json'))) return realDir(candidate)
|
|
1245
|
+
const parent = path.dirname(dir)
|
|
1246
|
+
if (parent === dir) break
|
|
1247
|
+
dir = parent
|
|
1254
1248
|
}
|
|
1255
1249
|
}
|
|
1256
|
-
return undefined
|
|
1250
|
+
return undefined
|
|
1257
1251
|
}
|
|
1258
1252
|
|
|
1259
1253
|
function realDir(dir: string) {
|
|
1260
1254
|
try {
|
|
1261
|
-
return statSync(dir).isDirectory() ? realpathSync(dir) : undefined
|
|
1255
|
+
return statSync(dir).isDirectory() ? realpathSync(dir) : undefined
|
|
1262
1256
|
} catch {
|
|
1263
|
-
return undefined
|
|
1257
|
+
return undefined
|
|
1264
1258
|
}
|
|
1265
1259
|
}
|
|
1266
1260
|
|
|
@@ -1271,40 +1265,40 @@ async function copyStaticFiles(
|
|
|
1271
1265
|
) {
|
|
1272
1266
|
const files = (await listFiles(from)).filter(file =>
|
|
1273
1267
|
shouldCopy(toPosixPath(path.relative(from, file))),
|
|
1274
|
-
)
|
|
1268
|
+
)
|
|
1275
1269
|
// One mkdir per directory, not per file, then link them all at once.
|
|
1276
1270
|
const directories = new Set(
|
|
1277
1271
|
files.map(file => path.dirname(path.join(to, path.relative(from, file)))),
|
|
1278
|
-
)
|
|
1279
|
-
await Promise.all([...directories].map(dir => mkdir(dir, { recursive: true })))
|
|
1272
|
+
)
|
|
1273
|
+
await Promise.all([...directories].map(dir => mkdir(dir, { recursive: true })))
|
|
1280
1274
|
await Promise.all(
|
|
1281
1275
|
files.map(file => linkOrCopyFile(file, path.join(to, path.relative(from, file)))),
|
|
1282
|
-
)
|
|
1276
|
+
)
|
|
1283
1277
|
}
|
|
1284
1278
|
|
|
1285
1279
|
async function staticOverrides(
|
|
1286
1280
|
publicPath: string,
|
|
1287
1281
|
staticFiles: Record<string, StaticFileMetadata>,
|
|
1288
1282
|
) {
|
|
1289
|
-
const overrides: NonNullable<VercelConfig['overrides']> = {}
|
|
1283
|
+
const overrides: NonNullable<VercelConfig['overrides']> = {}
|
|
1290
1284
|
for (const file of await listFiles(publicPath)) {
|
|
1291
|
-
const relative = toPosixPath(path.relative(publicPath, file))
|
|
1292
|
-
if (!relative.endsWith('/index.html')) continue
|
|
1293
|
-
overrides[relative] = { path: relative.replace(/\/index\.html$/, '') }
|
|
1285
|
+
const relative = toPosixPath(path.relative(publicPath, file))
|
|
1286
|
+
if (!relative.endsWith('/index.html')) continue
|
|
1287
|
+
overrides[relative] = { path: relative.replace(/\/index\.html$/, '') }
|
|
1294
1288
|
}
|
|
1295
1289
|
for (const [relative, metadata] of Object.entries(staticFiles)) {
|
|
1296
|
-
if (!canServeStaticOnVercel(metadata)) continue
|
|
1290
|
+
if (!canServeStaticOnVercel(metadata)) continue
|
|
1297
1291
|
const contentType = metadata.headers.find(
|
|
1298
1292
|
([name]) => name.toLowerCase() === 'content-type',
|
|
1299
|
-
)?.[1]
|
|
1300
|
-
if (contentType) overrides[relative] = { ...overrides[relative], contentType }
|
|
1293
|
+
)?.[1]
|
|
1294
|
+
if (contentType) overrides[relative] = { ...overrides[relative], contentType }
|
|
1301
1295
|
}
|
|
1302
|
-
return overrides
|
|
1296
|
+
return overrides
|
|
1303
1297
|
}
|
|
1304
1298
|
|
|
1305
1299
|
function canServeStaticOnVercel(metadata: StaticFileMetadata) {
|
|
1306
1300
|
return (
|
|
1307
1301
|
metadata.status === 200 &&
|
|
1308
1302
|
metadata.headers.every(([name]) => name.toLowerCase() === 'content-type')
|
|
1309
|
-
)
|
|
1303
|
+
)
|
|
1310
1304
|
}
|