@wular/pnext 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Next-pixel-exact default error UI (COMPAT). The built-in HTTP-access fallback
|
|
2
|
+
// (not-found/forbidden/unauthorized) and the default global-error document, copied byte-for-byte from
|
|
3
|
+
// Next so suites that select `.next-error-h1`, the h1/h2 text and computed styles match. Core renders
|
|
4
|
+
// these through the httpAccessFallbackUi / defaultGlobalErrorUi render extensions; a pure-core app
|
|
5
|
+
// keeps its own compact fallback.
|
|
6
|
+
|
|
7
|
+
import { h } from 'preact';
|
|
8
|
+
import type { SerializedError } from '../../extensions';
|
|
9
|
+
|
|
10
|
+
// Next's HTTPAccessErrorFallback inline styles (access-error-styles.ts).
|
|
11
|
+
const styles = {
|
|
12
|
+
error: {
|
|
13
|
+
fontFamily:
|
|
14
|
+
'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
|
|
15
|
+
height: '100vh',
|
|
16
|
+
textAlign: 'center',
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
justifyContent: 'center',
|
|
21
|
+
},
|
|
22
|
+
desc: { display: 'inline-block' },
|
|
23
|
+
h1: {
|
|
24
|
+
display: 'inline-block',
|
|
25
|
+
margin: '0 20px 0 0',
|
|
26
|
+
padding: '0 23px 0 0',
|
|
27
|
+
fontSize: 24,
|
|
28
|
+
fontWeight: 500,
|
|
29
|
+
verticalAlign: 'top',
|
|
30
|
+
lineHeight: '49px',
|
|
31
|
+
},
|
|
32
|
+
h2: { fontSize: 14, fontWeight: 400, lineHeight: '49px', margin: 0 },
|
|
33
|
+
} as const;
|
|
34
|
+
|
|
35
|
+
const httpFallbackCss = `body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}`;
|
|
36
|
+
|
|
37
|
+
export function httpAccessFallbackUi(status: number, message: string): unknown {
|
|
38
|
+
return h(
|
|
39
|
+
'div',
|
|
40
|
+
{ style: styles.error },
|
|
41
|
+
h(
|
|
42
|
+
'div',
|
|
43
|
+
null,
|
|
44
|
+
h('style', { dangerouslySetInnerHTML: { __html: httpFallbackCss } }),
|
|
45
|
+
h('h1', { className: 'next-error-h1', style: styles.h1 }, String(status)),
|
|
46
|
+
h('div', { style: styles.desc }, h('h2', { style: styles.h2 }, message)),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Next's DefaultGlobalError (builtin/global-error.tsx + error-styles.tsx). The
|
|
52
|
+
// whole document, used when an error escapes the root layout and no
|
|
53
|
+
// global-error.* file exists. `error.digest` marks a server error.
|
|
54
|
+
const errorStyles = {
|
|
55
|
+
container: {
|
|
56
|
+
fontFamily:
|
|
57
|
+
'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
|
|
58
|
+
height: '100vh',
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
justifyContent: 'center',
|
|
62
|
+
},
|
|
63
|
+
card: { marginTop: '-32px', maxWidth: '325px', padding: '32px 28px', textAlign: 'left' },
|
|
64
|
+
title: {
|
|
65
|
+
fontSize: '24px',
|
|
66
|
+
fontWeight: 500,
|
|
67
|
+
letterSpacing: '-0.02em',
|
|
68
|
+
lineHeight: '32px',
|
|
69
|
+
margin: '0 0 12px 0',
|
|
70
|
+
color: 'var(--next-error-title)',
|
|
71
|
+
},
|
|
72
|
+
message: {
|
|
73
|
+
fontSize: '14px',
|
|
74
|
+
fontWeight: 400,
|
|
75
|
+
lineHeight: '21px',
|
|
76
|
+
margin: '0 0 20px 0',
|
|
77
|
+
color: 'var(--next-error-message)',
|
|
78
|
+
},
|
|
79
|
+
icon: { marginBottom: '24px' },
|
|
80
|
+
form: { margin: 0 },
|
|
81
|
+
buttonGroup: { display: 'flex', gap: '8px', alignItems: 'center' },
|
|
82
|
+
button: {
|
|
83
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: '32px',
|
|
84
|
+
padding: '0 12px', fontSize: '14px', fontWeight: 500, lineHeight: '20px',
|
|
85
|
+
borderRadius: '6px', cursor: 'pointer', color: 'var(--next-error-btn-text)',
|
|
86
|
+
background: 'var(--next-error-btn-bg)', border: 'var(--next-error-btn-border)',
|
|
87
|
+
},
|
|
88
|
+
buttonSecondary: {
|
|
89
|
+
display: 'inline-flex', alignItems: 'center', justifyContent: 'center', height: '32px',
|
|
90
|
+
padding: '0 12px', fontSize: '14px', fontWeight: 500, lineHeight: '20px',
|
|
91
|
+
borderRadius: '6px', cursor: 'pointer', color: 'var(--next-error-btn-secondary-text)',
|
|
92
|
+
background: 'var(--next-error-btn-secondary-bg)', border: 'var(--next-error-btn-secondary-border)',
|
|
93
|
+
},
|
|
94
|
+
digestFooter: {
|
|
95
|
+
position: 'fixed',
|
|
96
|
+
bottom: '32px',
|
|
97
|
+
left: '0',
|
|
98
|
+
right: '0',
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
fontFamily: 'ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace',
|
|
101
|
+
fontSize: '12px',
|
|
102
|
+
lineHeight: '18px',
|
|
103
|
+
fontWeight: 400,
|
|
104
|
+
margin: '0',
|
|
105
|
+
color: 'var(--next-error-digest)',
|
|
106
|
+
},
|
|
107
|
+
} as const;
|
|
108
|
+
|
|
109
|
+
const errorThemeCss =
|
|
110
|
+
`:root{--next-error-bg:#fff;--next-error-text:#171717;--next-error-title:#171717;--next-error-message:#171717;--next-error-digest:#666666;--next-error-btn-text:#fff;--next-error-btn-bg:#171717;--next-error-btn-border:none;--next-error-btn-secondary-text:#171717;--next-error-btn-secondary-bg:transparent;--next-error-btn-secondary-border:1px solid rgba(0,0,0,.08)}@media (prefers-color-scheme:dark){:root{--next-error-bg:#0a0a0a;--next-error-text:#ededed;--next-error-title:#ededed;--next-error-message:#ededed;--next-error-digest:#a0a0a0;--next-error-btn-text:#0a0a0a;--next-error-btn-bg:#ededed;--next-error-btn-border:none;--next-error-btn-secondary-text:#ededed;--next-error-btn-secondary-bg:transparent;--next-error-btn-secondary-border:1px solid rgba(255,255,255,.14)}}body{margin:0;color:var(--next-error-text);background:var(--next-error-bg);}`;
|
|
111
|
+
|
|
112
|
+
function warningIcon() {
|
|
113
|
+
return h('svg', { width: '32', height: '32', viewBox: '-0.2 -1.5 32 32', fill: 'none', style: errorStyles.icon }, h('path', {
|
|
114
|
+
d: 'M16.9328 0C18.0839.000116771 19.1334.658832 19.634 1.69531L31.4299 26.1309C32.0708 27.4588 31.1036 28.9999 29.6291 29H2.00215C.527541 29-.439628 27.4588.201371 26.1309L11.9973 1.69531C12.4979.658823 13.5474 7.75066e-05 14.6984 0H16.9328ZM3.59493 26H28.0363L16.9328 3H14.6984L3.59493 26ZM15.8156 19C16.9202 19.0001 17.8156 19.8955 17.8156 21C17.8156 22.1045 16.9202 22.9999 15.8156 23C14.7111 23 13.8156 22.1046 13.8156 21C13.8156 19.8954 14.7111 19 15.8156 19ZM17.3156 16.5H14.3156V8.5H17.3156V16.5Z',
|
|
115
|
+
fill: 'var(--next-error-title)',
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The default/global-error document is served as a fully static document with no page-hydration
|
|
120
|
+
// bootstrap script - but Next still surfaces the original error to the client as an uncaught exception
|
|
121
|
+
// once the client "hydrates" the escaped-error tree and replays the throw at the root. pnext serves this
|
|
122
|
+
// document without a live Preact root, so nothing would ever naturally re-throw; emit a small inline
|
|
123
|
+
// bootstrap that reports the same message/digest via `reportError` (falling back to a synthetic error
|
|
124
|
+
// event) so the client observes the same uncaught signal. Never runs in dev, where the redbox owns it.
|
|
125
|
+
export function globalErrorReportScript(error: SerializedError): string {
|
|
126
|
+
const message = JSON.stringify(error.message ?? '');
|
|
127
|
+
const digest = JSON.stringify(error.digest ?? '');
|
|
128
|
+
return `<script>(function(){try{var e=new Error(${message});if(${digest})e.digest=${digest};if(typeof reportError==='function'){reportError(e);}else{window.dispatchEvent(new ErrorEvent('error',{error:e,message:e.message}));}}catch(_){}})();</script>`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function defaultGlobalErrorUi(error: SerializedError): unknown {
|
|
132
|
+
const digest = error.digest;
|
|
133
|
+
const isServerError = Boolean(digest);
|
|
134
|
+
const message = isServerError
|
|
135
|
+
? 'A server error occurred. Reload to try again.'
|
|
136
|
+
: 'Reload to try again, or go back.';
|
|
137
|
+
return h(
|
|
138
|
+
'html',
|
|
139
|
+
{ id: '__next_error__' },
|
|
140
|
+
h(
|
|
141
|
+
'head',
|
|
142
|
+
null,
|
|
143
|
+
h('title', null, "500: This page couldn’t load"),
|
|
144
|
+
h('style', { dangerouslySetInnerHTML: { __html: errorThemeCss } }),
|
|
145
|
+
),
|
|
146
|
+
h(
|
|
147
|
+
'body',
|
|
148
|
+
null,
|
|
149
|
+
h(
|
|
150
|
+
'div',
|
|
151
|
+
{ style: errorStyles.container },
|
|
152
|
+
h(
|
|
153
|
+
'div',
|
|
154
|
+
{ style: errorStyles.card },
|
|
155
|
+
warningIcon(),
|
|
156
|
+
h('h1', { style: errorStyles.title }, "This page couldn’t load"),
|
|
157
|
+
h('p', { style: errorStyles.message }, message),
|
|
158
|
+
h('div', { style: errorStyles.buttonGroup },
|
|
159
|
+
h('form', { style: errorStyles.form }, h('button', { type: 'submit', style: errorStyles.button }, 'Reload')),
|
|
160
|
+
!isServerError ? h('button', { type: 'button', style: errorStyles.buttonSecondary }, 'Back') : null,
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
),
|
|
164
|
+
digest ? h('p', { style: errorStyles.digestFooter }, `ERROR ${digest}`) : null,
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// instrumentation-client.ts (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Next executes the app's instrumentation-client.(ts|js) in the browser BEFORE hydration, plus any
|
|
4
|
+
// `instrumentationClientInject` next.config entries before it, in array order; each module may export
|
|
5
|
+
// onRouterTransitionStart(href, navigateType) which the client router calls at the start of every soft
|
|
6
|
+
// navigation.
|
|
7
|
+
//
|
|
8
|
+
// pnext emulation:
|
|
9
|
+
// - BUILD: a build step (running before prerendering, so statically generated pages carry the tag
|
|
10
|
+
// too) bundles a synthetic entry importing the inject modules then the user module - static ESM
|
|
11
|
+
// imports preserve array order - into `<out>/public/assets/instrumentation-client.js`.
|
|
12
|
+
// - The synthetic entry collects every module's onRouterTransitionStart into
|
|
13
|
+
// `window.__PNEXT_ON_ROUTER_TRANSITION_START__`; the client router invokes that global when a
|
|
14
|
+
// navigation starts.
|
|
15
|
+
// - DOCUMENT: a `<script type="module">` head tag loads the bundle. Module scripts execute in document
|
|
16
|
+
// order, so the head module runs before the route entry module at the end of <body>.
|
|
17
|
+
//
|
|
18
|
+
// Zero-cost: apps without an instrumentation-client file get no bundle, no build work, no head tag.
|
|
19
|
+
|
|
20
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { build } from 'esbuild';
|
|
23
|
+
import type { ResolvedConfig } from '../../config';
|
|
24
|
+
import { getNextConfig } from '../next/config-loader';
|
|
25
|
+
import { instrumentationLookupRoots } from './instrumentation';
|
|
26
|
+
|
|
27
|
+
const CLIENT_BASENAMES = [
|
|
28
|
+
'instrumentation-client.ts',
|
|
29
|
+
'instrumentation-client.tsx',
|
|
30
|
+
'instrumentation-client.js',
|
|
31
|
+
'instrumentation-client.mjs',
|
|
32
|
+
'instrumentation-client.mts',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** The served pathname of the bundled instrumentation-client asset. */
|
|
36
|
+
export const INSTRUMENTATION_CLIENT_ASSET = 'assets/instrumentation-client.js';
|
|
37
|
+
|
|
38
|
+
function findInstrumentationClient(config: ResolvedConfig): string | undefined {
|
|
39
|
+
for (const root of instrumentationLookupRoots(config)) {
|
|
40
|
+
for (const name of CLIENT_BASENAMES) {
|
|
41
|
+
const candidate = path.join(root, name);
|
|
42
|
+
if (existsSync(candidate)) return candidate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `instrumentationClientInject` module paths from next.config, root-resolved. */
|
|
49
|
+
function injectEntries(config: ResolvedConfig): string[] {
|
|
50
|
+
const value = getNextConfig().instrumentationClientInject;
|
|
51
|
+
if (!Array.isArray(value)) return [];
|
|
52
|
+
return value
|
|
53
|
+
.filter((entry): entry is string => typeof entry === 'string')
|
|
54
|
+
.map(entry => path.resolve(config.root, entry));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The synthetic browser entry: inject modules then the user module (static
|
|
59
|
+
* imports evaluate in order), followed by the transition-start hook registry.
|
|
60
|
+
*/
|
|
61
|
+
function entrySource(modules: string[]): string {
|
|
62
|
+
const imports = modules
|
|
63
|
+
.map((file, index) => `import * as mod${index} from ${JSON.stringify(file)};`)
|
|
64
|
+
.join('\n');
|
|
65
|
+
const names = modules.map((_file, index) => `mod${index}`).join(', ');
|
|
66
|
+
return `${imports}
|
|
67
|
+
const hooks = [${names}]
|
|
68
|
+
.map(mod => (mod && typeof mod.onRouterTransitionStart === 'function' ? mod.onRouterTransitionStart : null))
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
if (hooks.length > 0) {
|
|
71
|
+
window.__PNEXT_ON_ROUTER_TRANSITION_START__ = (href, navigateType) => {
|
|
72
|
+
for (const hook of hooks) {
|
|
73
|
+
try {
|
|
74
|
+
hook(href, navigateType);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Bundle the instrumentation-client entry into the build output. No-op when
|
|
86
|
+
* the app has no instrumentation-client file. Called from the registered build
|
|
87
|
+
* step (before prerendering, so prerendered documents include the head tag).
|
|
88
|
+
*/
|
|
89
|
+
export async function buildInstrumentationClient(config: ResolvedConfig): Promise<void> {
|
|
90
|
+
const userFile = findInstrumentationClient(config);
|
|
91
|
+
if (!userFile) return;
|
|
92
|
+
const modules = [...injectEntries(config), userFile];
|
|
93
|
+
const workDir = path.join(config.outPath, 'cache', 'instrumentation-client');
|
|
94
|
+
mkdirSync(workDir, { recursive: true });
|
|
95
|
+
const entryFile = path.join(workDir, 'entry.ts');
|
|
96
|
+
const outfile = path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET);
|
|
97
|
+
mkdirSync(path.dirname(outfile), { recursive: true });
|
|
98
|
+
await Bun.write(entryFile, entrySource(modules));
|
|
99
|
+
const tsconfig = path.join(config.root, 'tsconfig.json');
|
|
100
|
+
await build({
|
|
101
|
+
entryPoints: [entryFile],
|
|
102
|
+
outfile,
|
|
103
|
+
bundle: true,
|
|
104
|
+
platform: 'browser',
|
|
105
|
+
format: 'esm',
|
|
106
|
+
target: 'es2020',
|
|
107
|
+
minify: false,
|
|
108
|
+
...(existsSync(tsconfig) ? { tsconfig } : {}),
|
|
109
|
+
logLevel: 'silent',
|
|
110
|
+
sourcemap: false,
|
|
111
|
+
});
|
|
112
|
+
bundlePresence = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let bundlePresence: boolean | undefined;
|
|
116
|
+
|
|
117
|
+
/** Whether the built instrumentation-client bundle exists (cached). */
|
|
118
|
+
function hasInstrumentationClientBundle(config: ResolvedConfig): boolean {
|
|
119
|
+
bundlePresence ??= existsSync(
|
|
120
|
+
path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET),
|
|
121
|
+
);
|
|
122
|
+
return bundlePresence;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Test-only reset for the bundle-presence cache. */
|
|
126
|
+
export function resetInstrumentationClientForTest(): void {
|
|
127
|
+
bundlePresence = undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The `<head>` tag loading the instrumentation-client bundle, or '' when the
|
|
132
|
+
* app has none. basePath-prefixed like every other asset href.
|
|
133
|
+
*/
|
|
134
|
+
export function instrumentationClientHeadTag(config: ResolvedConfig): string {
|
|
135
|
+
if (!hasInstrumentationClientBundle(config)) return '';
|
|
136
|
+
const prefix = config.basePath ?? '';
|
|
137
|
+
return `<script type="module" src="${prefix}/${INSTRUMENTATION_CLIENT_ASSET}"></script>`;
|
|
138
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// instrumentation.ts loader (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Loads the app's instrumentation module (root or src/), calls its async register() exactly once at
|
|
4
|
+
// server start, and exposes its onRequestError export to the error funnel. The module is bundled with
|
|
5
|
+
// esbuild (node_modules kept external) so TypeScript and ESM imports inside it work, then imported once.
|
|
6
|
+
// The register() promise is awaited before the first request via a gate the compat request interceptor
|
|
7
|
+
// consults.
|
|
8
|
+
//
|
|
9
|
+
// EDGE PASS: Next boots a SECOND instrumentation instance inside the edge sandbox whenever the app has
|
|
10
|
+
// any edge-runtime entity - middleware/proxy, or a route declaring an edge runtime. pnext runs one Bun
|
|
11
|
+
// process, so it emulates the pass by importing a FRESH module instance (a separate bundle file, so
|
|
12
|
+
// separate module state) and calling its register() with NEXT_RUNTIME='edge' set for the duration.
|
|
13
|
+
// Apps without edge entities never get the pass.
|
|
14
|
+
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import { build, type Plugin } from 'esbuild';
|
|
20
|
+
import type { ResolvedConfig } from '../../config';
|
|
21
|
+
import { resolveExternalLoadTarget } from '../../resolve/imports';
|
|
22
|
+
import { compatAliases, reactServerLayerAliases } from '../index';
|
|
23
|
+
|
|
24
|
+
const INSTRUMENTATION_BASENAMES = [
|
|
25
|
+
'instrumentation.ts',
|
|
26
|
+
'instrumentation.mts',
|
|
27
|
+
'instrumentation.js',
|
|
28
|
+
'instrumentation.mjs',
|
|
29
|
+
'instrumentation.tsx',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The instrumentation bundle keeps node_modules external, so every bare specifier it emits is resolved
|
|
34
|
+
* by Bun against the APP directory at import time - where `react`/`next/*` do not exist. Resolve the
|
|
35
|
+
* compat alias map here and hand esbuild an external `file://` URL for each hit, so the emitted import
|
|
36
|
+
* points straight at pnext's own module, loaded once and shared with the running server.
|
|
37
|
+
*
|
|
38
|
+
* `react`/`react-dom` take the react-server-layer overrides: Next compiles instrumentation in the
|
|
39
|
+
* server layer, so register() must observe the hook-free React.
|
|
40
|
+
*/
|
|
41
|
+
function instrumentationAliases(config: ResolvedConfig): Record<string, string> {
|
|
42
|
+
return { ...compatAliases(config, 'server'), ...reactServerLayerAliases(config) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function instrumentationPlugin(root: string, config?: ResolvedConfig): Plugin {
|
|
46
|
+
const appRequire = createRequire(path.join(root, 'package.json'));
|
|
47
|
+
const aliases = config ? instrumentationAliases(config) : {};
|
|
48
|
+
return {
|
|
49
|
+
name: 'pnext-instrumentation',
|
|
50
|
+
setup(builder) {
|
|
51
|
+
builder.onResolve({ filter: /^[^./]/ }, args => {
|
|
52
|
+
const alias = aliases[args.path];
|
|
53
|
+
if (!alias) return undefined;
|
|
54
|
+
return {
|
|
55
|
+
path: path.isAbsolute(alias) ? pathToFileURL(alias).href : alias,
|
|
56
|
+
external: true,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
builder.onResolve({ filter: /^server-only$/ }, () => ({
|
|
60
|
+
path: 'server-only',
|
|
61
|
+
namespace: 'pnext',
|
|
62
|
+
}));
|
|
63
|
+
builder.onLoad({ filter: /^server-only$/, namespace: 'pnext' }, () => ({ contents: '' }));
|
|
64
|
+
builder.onResolve({ filter: /^@vercel\/otel$/ }, () => {
|
|
65
|
+
try {
|
|
66
|
+
appRequire.resolve('@vercel/otel');
|
|
67
|
+
return undefined;
|
|
68
|
+
} catch {
|
|
69
|
+
return { path: '@vercel/otel', namespace: 'pnext' };
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
builder.onLoad({ filter: /^@vercel\/otel$/, namespace: 'pnext' }, () => ({
|
|
73
|
+
contents: 'export function registerOTel() {}',
|
|
74
|
+
}));
|
|
75
|
+
builder.onResolve({ filter: /^[^./]/ }, args => {
|
|
76
|
+
const target = resolveExternalLoadTarget({
|
|
77
|
+
root,
|
|
78
|
+
fromFile: args.importer || path.join(root, 'instrumentation.ts'),
|
|
79
|
+
specifier: args.path,
|
|
80
|
+
target: 'server',
|
|
81
|
+
});
|
|
82
|
+
return target ? { path: target } : undefined;
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type OnRequestErrorHook = (
|
|
89
|
+
error: unknown,
|
|
90
|
+
request: { path: string; method: string; headers: Record<string, string> },
|
|
91
|
+
context: Record<string, unknown>,
|
|
92
|
+
) => void | Promise<void>;
|
|
93
|
+
|
|
94
|
+
interface InstrumentationModule {
|
|
95
|
+
register?: () => void | Promise<void>;
|
|
96
|
+
onRequestError?: OnRequestErrorHook;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let loaded: InstrumentationModule | undefined;
|
|
100
|
+
let readyGate: Promise<void> | undefined;
|
|
101
|
+
|
|
102
|
+
/** The user onRequestError export, once instrumentation has loaded. */
|
|
103
|
+
export function getUserOnRequestError(): OnRequestErrorHook | undefined {
|
|
104
|
+
return loaded?.onRequestError;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Resolve once instrumentation register() has completed (or there is none). */
|
|
108
|
+
export function instrumentationReady(): Promise<void> {
|
|
109
|
+
return readyGate ?? Promise.resolve();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findInstrumentation(config: ResolvedConfig): string | undefined {
|
|
113
|
+
for (const root of instrumentationLookupRoots(config)) {
|
|
114
|
+
for (const name of INSTRUMENTATION_BASENAMES) {
|
|
115
|
+
const candidate = path.join(root, name);
|
|
116
|
+
if (existsSync(candidate)) return candidate;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Next colocates instrumentation with the source folder: when app/pages live
|
|
124
|
+
* under `src/`, only `src/instrumentation.*` is honored (a root file is
|
|
125
|
+
* ignored). Apps without a `src/` source tree read from the project root.
|
|
126
|
+
*/
|
|
127
|
+
export function instrumentationLookupRoots(config: ResolvedConfig): string[] {
|
|
128
|
+
const src = path.join(config.root, 'src');
|
|
129
|
+
const usesSrc = existsSync(path.join(src, 'app')) || existsSync(path.join(src, 'pages'));
|
|
130
|
+
return usesSrc ? [src] : [config.root];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function importInstrumentation(
|
|
134
|
+
file: string,
|
|
135
|
+
config: ResolvedConfig,
|
|
136
|
+
suffix = '',
|
|
137
|
+
): Promise<InstrumentationModule> {
|
|
138
|
+
const root = config.root;
|
|
139
|
+
const outDir = path.join(root, '.pnext', 'instrumentation');
|
|
140
|
+
mkdirSync(outDir, { recursive: true });
|
|
141
|
+
const outfile = path.join(outDir, `instrumentation.${Date.now()}${suffix}.mjs`);
|
|
142
|
+
const tsconfig = path.join(root, 'tsconfig.json');
|
|
143
|
+
try {
|
|
144
|
+
await build({
|
|
145
|
+
entryPoints: [file],
|
|
146
|
+
outfile,
|
|
147
|
+
bundle: true,
|
|
148
|
+
platform: 'node',
|
|
149
|
+
format: 'esm',
|
|
150
|
+
target: 'esnext',
|
|
151
|
+
packages: 'external',
|
|
152
|
+
plugins: [instrumentationPlugin(root, config)],
|
|
153
|
+
...(existsSync(tsconfig) ? { tsconfig } : {}),
|
|
154
|
+
define: {
|
|
155
|
+
__dirname: JSON.stringify(path.dirname(file)),
|
|
156
|
+
__filename: JSON.stringify(file),
|
|
157
|
+
'import.meta.url': JSON.stringify(pathToFileURL(file).href),
|
|
158
|
+
},
|
|
159
|
+
logLevel: 'silent',
|
|
160
|
+
sourcemap: false,
|
|
161
|
+
});
|
|
162
|
+
return (await import(pathToFileURL(outfile).href)) as InstrumentationModule;
|
|
163
|
+
} finally {
|
|
164
|
+
rmSync(outfile, { force: true });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Load instrumentation and run register() once. Idempotent: repeated calls
|
|
170
|
+
* (build + start both run init hooks) reuse the first load. Returns immediately;
|
|
171
|
+
* the register() completion is tracked by instrumentationReady().
|
|
172
|
+
*/
|
|
173
|
+
export function loadInstrumentation(config: ResolvedConfig): void {
|
|
174
|
+
if (readyGate) return;
|
|
175
|
+
const file = findInstrumentation(config);
|
|
176
|
+
if (!file) {
|
|
177
|
+
readyGate = Promise.resolve();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
readyGate = (async () => {
|
|
181
|
+
try {
|
|
182
|
+
// Next exposes the active runtime to register()/onRequestError via
|
|
183
|
+
// NEXT_RUNTIME. pnext serves everything from a single Node process, so the
|
|
184
|
+
// baseline runtime is 'nodejs' (edge-only fixtures still run here).
|
|
185
|
+
|
|
186
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
187
|
+
if (!process.env.NEXT_RUNTIME) process.env.NEXT_RUNTIME = 'nodejs';
|
|
188
|
+
loaded = await importInstrumentation(file, config);
|
|
189
|
+
await loaded.register?.();
|
|
190
|
+
if (appHasEdgeEntities(config)) await runEdgeRegisterPass(file, config);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('instrumentation register() failed:', error);
|
|
193
|
+
}
|
|
194
|
+
})();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The edge boot pass: a fresh instrumentation instance registered with NEXT_RUNTIME='edge' for the
|
|
199
|
+
* duration of the call. A double provider registration is harmless - @opentelemetry/api rejects a second
|
|
200
|
+
* global registration and keeps the first.
|
|
201
|
+
*/
|
|
202
|
+
async function runEdgeRegisterPass(file: string, config: ResolvedConfig): Promise<void> {
|
|
203
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
204
|
+
const previous = process.env.NEXT_RUNTIME;
|
|
205
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
206
|
+
process.env.NEXT_RUNTIME = 'edge';
|
|
207
|
+
try {
|
|
208
|
+
const edgeInstance = await importInstrumentation(file, config, '.edge');
|
|
209
|
+
await edgeInstance.register?.();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('instrumentation register() (edge pass) failed:', error);
|
|
212
|
+
} finally {
|
|
213
|
+
if (previous === undefined) {
|
|
214
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
215
|
+
delete process.env.NEXT_RUNTIME;
|
|
216
|
+
} else {
|
|
217
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
218
|
+
process.env.NEXT_RUNTIME = previous;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const SOURCE_FILE_RE = /\.(?:ts|tsx|js|jsx|mjs|mts|cjs|cts)$/;
|
|
224
|
+
// `export const runtime = 'edge'` (app router) or `config = { runtime:
|
|
225
|
+
// 'edge' | 'experimental-edge' }` (pages router / API routes).
|
|
226
|
+
const EDGE_RUNTIME_RE = /\bruntime\b\s*[:=]\s*['"](?:experimental-)?edge['"]/;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Whether the app contains anything Next would boot the edge runtime for:
|
|
230
|
+
* a middleware/proxy file, or any app/pages source declaring an edge runtime.
|
|
231
|
+
* Only evaluated when an instrumentation file exists (a bounded one-time scan
|
|
232
|
+
* at boot; apps without instrumentation never pay).
|
|
233
|
+
*/
|
|
234
|
+
function appHasEdgeEntities(config: ResolvedConfig): boolean {
|
|
235
|
+
const roots = [config.root, path.join(config.root, 'src')];
|
|
236
|
+
for (const root of roots) {
|
|
237
|
+
for (const name of ['middleware', 'proxy']) {
|
|
238
|
+
for (const ext of ['ts', 'js', 'mts', 'mjs']) {
|
|
239
|
+
if (existsSync(path.join(root, `${name}.${ext}`))) return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const dir of ['app', 'pages']) {
|
|
243
|
+
if (sourceTreeDeclaresEdgeRuntime(path.join(root, dir))) return true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function sourceTreeDeclaresEdgeRuntime(dir: string): boolean {
|
|
250
|
+
let entries: string[];
|
|
251
|
+
try {
|
|
252
|
+
entries = readdirSync(dir);
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
if (entry === 'node_modules' || entry.startsWith('.')) continue;
|
|
258
|
+
const full = path.join(dir, entry);
|
|
259
|
+
let stats;
|
|
260
|
+
try {
|
|
261
|
+
stats = statSync(full);
|
|
262
|
+
} catch {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (stats.isDirectory()) {
|
|
266
|
+
if (sourceTreeDeclaresEdgeRuntime(full)) return true;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!SOURCE_FILE_RE.test(entry)) continue;
|
|
270
|
+
try {
|
|
271
|
+
if (EDGE_RUNTIME_RE.test(readFileSync(full, 'utf8'))) return true;
|
|
272
|
+
} catch {
|
|
273
|
+
// unreadable file — skip
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { format } from 'node:util';
|
|
2
|
+
|
|
3
|
+
const installed = Symbol.for('pnext.node-console-format-installed');
|
|
4
|
+
|
|
5
|
+
export function installNodeConsoleFormat(): void {
|
|
6
|
+
const state = globalThis as typeof globalThis & Record<symbol, true | undefined>;
|
|
7
|
+
if (state[installed]) return;
|
|
8
|
+
state[installed] = true;
|
|
9
|
+
const log = console.log.bind(console);
|
|
10
|
+
console.log = (...args: unknown[]) => log(format(...args));
|
|
11
|
+
// Errors too: bun prints a thrown/logged Error as `error: message` (and uses
|
|
12
|
+
// `err.name` alone for a renamed one), while Node — and therefore every Next
|
|
13
|
+
// e2e expectation — prints util.inspect's `ClassName: message` /
|
|
14
|
+
// `ClassName [name]: message` header followed by the stack.
|
|
15
|
+
const error = console.error.bind(console);
|
|
16
|
+
console.error = (...args: unknown[]) => error(format(...args));
|
|
17
|
+
const warn = console.warn.bind(console);
|
|
18
|
+
console.warn = (...args: unknown[]) => warn(format(...args));
|
|
19
|
+
}
|