@thomas.f/prnext 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +54 -0
  2. package/build/action-exports.mjs +107 -0
  3. package/build/actions.mjs +214 -0
  4. package/build/app-config.mjs +138 -0
  5. package/build/app-routing.mjs +119 -0
  6. package/build/app-static.mjs +158 -0
  7. package/build/app.mjs +536 -0
  8. package/build/cache-components.mjs +91 -0
  9. package/build/cache-handlers.mjs +54 -0
  10. package/build/check.mjs +40 -0
  11. package/build/compiler.mjs +222 -0
  12. package/build/config.mjs +193 -0
  13. package/build/css-modules.mjs +114 -0
  14. package/build/custom-routes.mjs +363 -0
  15. package/build/dev-worker.mjs +18 -0
  16. package/build/dev.mjs +76 -0
  17. package/build/directory-cache.mjs +29 -0
  18. package/build/dynamic.mjs +125 -0
  19. package/build/edge.mjs +141 -0
  20. package/build/font-data.LICENSE.txt +25 -0
  21. package/build/font-data.json +1 -0
  22. package/build/font-files.mjs +221 -0
  23. package/build/fonts.mjs +131 -0
  24. package/build/framework-imports.mjs +17 -0
  25. package/build/i18n.mjs +39 -0
  26. package/build/images-config.mjs +62 -0
  27. package/build/images.mjs +86 -0
  28. package/build/incremental.mjs +76 -0
  29. package/build/index.mjs +356 -0
  30. package/build/loader-cache.mjs +69 -0
  31. package/build/metadata-images.mjs +72 -0
  32. package/build/metadata-routes.mjs +77 -0
  33. package/build/middleware.mjs +231 -0
  34. package/build/migrate.mjs +289 -0
  35. package/build/module-resolution.mjs +78 -0
  36. package/build/native-discovery.mjs +134 -0
  37. package/build/native-packages.mjs +105 -0
  38. package/build/npm-packages.mjs +110 -0
  39. package/build/og.mjs +14 -0
  40. package/build/page-extensions.mjs +12 -0
  41. package/build/passthrough-loader.cjs +4 -0
  42. package/build/precompress.mjs +122 -0
  43. package/build/scan.mjs +178 -0
  44. package/build/script-workers.mjs +25 -0
  45. package/build/server-actions-config.mjs +27 -0
  46. package/build/source-maps.mjs +34 -0
  47. package/build/standalone-config.mjs +19 -0
  48. package/build/standalone-runner.mjs +21 -0
  49. package/build/standalone-worker.mjs +5 -0
  50. package/build/standalone.mjs +240 -0
  51. package/build/static-export.mjs +93 -0
  52. package/build/styles.mjs +174 -0
  53. package/build/transform-cache.mjs +50 -0
  54. package/build/transform.mjs +155 -0
  55. package/build/turbopack-conditions.mjs +59 -0
  56. package/build/webpack-bridge.cjs +3 -0
  57. package/build/webpack-graph.mjs +256 -0
  58. package/build/webpack-loader.cjs +9 -0
  59. package/cli.mjs +210 -0
  60. package/compat/app-client-page.cjs +32 -0
  61. package/compat/app-context.cjs +40 -0
  62. package/compat/app-context.d.cts +20 -0
  63. package/compat/app-dynamic.cjs +34 -0
  64. package/compat/app-error-boundary.cjs +53 -0
  65. package/compat/app-global-error-boundary.cjs +34 -0
  66. package/compat/app-layout-context.cjs +27 -0
  67. package/compat/app-navigation-boundary.cjs +47 -0
  68. package/compat/app.cjs +33 -0
  69. package/compat/app.d.cts +9 -0
  70. package/compat/cache-handlers.cjs +223 -0
  71. package/compat/cache-life.cjs +34 -0
  72. package/compat/cache.cjs +99 -0
  73. package/compat/cache.d.cts +36 -0
  74. package/compat/compat/router.d.cts +2 -0
  75. package/compat/compat-router.cjs +7 -0
  76. package/compat/constants.cjs +8 -0
  77. package/compat/constants.d.cts +7 -0
  78. package/compat/cookies.cjs +129 -0
  79. package/compat/data-cache.cjs +389 -0
  80. package/compat/dist/shared/lib/app-router-context.shared-runtime.d.cts +3 -0
  81. package/compat/dist/shared/lib/router-context.d.cts +1 -0
  82. package/compat/dist/shared/lib/router-context.shared-runtime.d.cts +3 -0
  83. package/compat/document.cjs +73 -0
  84. package/compat/document.d.cts +41 -0
  85. package/compat/draft.cjs +28 -0
  86. package/compat/dynamic-bailout.cjs +13 -0
  87. package/compat/dynamic.cjs +119 -0
  88. package/compat/dynamic.d.cts +32 -0
  89. package/compat/error.cjs +36 -0
  90. package/compat/error.d.cts +15 -0
  91. package/compat/fetch.d.cts +10 -0
  92. package/compat/font/google.d.cts +1 -0
  93. package/compat/font/local.d.cts +2 -0
  94. package/compat/font-google.cjs +2 -0
  95. package/compat/font-google.d.cts +1944 -0
  96. package/compat/font-local.cjs +5 -0
  97. package/compat/font-local.d.cts +3 -0
  98. package/compat/font-types.d.cts +25 -0
  99. package/compat/head.cjs +180 -0
  100. package/compat/head.d.cts +3 -0
  101. package/compat/headers.cjs +89 -0
  102. package/compat/headers.d.cts +26 -0
  103. package/compat/image-config.cjs +3 -0
  104. package/compat/image-imports.d.ts +12 -0
  105. package/compat/image-shared.cjs +62 -0
  106. package/compat/image.cjs +64 -0
  107. package/compat/image.d.cts +35 -0
  108. package/compat/incremental-cache.cjs +186 -0
  109. package/compat/index.d.ts +159 -0
  110. package/compat/instant-samples.cjs +39 -0
  111. package/compat/instant.cjs +13 -0
  112. package/compat/link.cjs +68 -0
  113. package/compat/link.d.cts +17 -0
  114. package/compat/locale.cjs +30 -0
  115. package/compat/metadata.d.cts +77 -0
  116. package/compat/navigation-controls.cjs +27 -0
  117. package/compat/navigation-server.cjs +25 -0
  118. package/compat/navigation.cjs +52 -0
  119. package/compat/navigation.d.cts +28 -0
  120. package/compat/next-app-router-context.cjs +6 -0
  121. package/compat/next-router-context.cjs +3 -0
  122. package/compat/og.cjs +32 -0
  123. package/compat/og.d.cts +1 -0
  124. package/compat/paths.cjs +33 -0
  125. package/compat/preview.cjs +41 -0
  126. package/compat/rewrite.cjs +41 -0
  127. package/compat/route-request.cjs +64 -0
  128. package/compat/router.cjs +137 -0
  129. package/compat/router.d.cts +58 -0
  130. package/compat/script-context.cjs +5 -0
  131. package/compat/script-loader.cjs +135 -0
  132. package/compat/script.cjs +69 -0
  133. package/compat/script.d.cts +17 -0
  134. package/compat/server-primitives.cjs +109 -0
  135. package/compat/server.cjs +13 -0
  136. package/compat/server.d.cts +37 -0
  137. package/compat/static-generation.cjs +108 -0
  138. package/compat/use-cache.cjs +129 -0
  139. package/index.mjs +1 -0
  140. package/native/platforms.json +6 -0
  141. package/native/resolve.mjs +81 -0
  142. package/package.json +127 -0
  143. package/release/verify.mjs +59 -0
  144. package/runtime/action-client.mjs +19 -0
  145. package/runtime/action-crypto.mjs +138 -0
  146. package/runtime/action-reference.mjs +2 -0
  147. package/runtime/action-server.mjs +161 -0
  148. package/runtime/action-ssr.mjs +5 -0
  149. package/runtime/api.mjs +326 -0
  150. package/runtime/app-client.mjs +539 -0
  151. package/runtime/app-content.mjs +8 -0
  152. package/runtime/app-errors.mjs +12 -0
  153. package/runtime/app-instant.mjs +30 -0
  154. package/runtime/app-metadata.mjs +410 -0
  155. package/runtime/app-partial-model.mjs +228 -0
  156. package/runtime/app-partial.mjs +130 -0
  157. package/runtime/app-prefetch.mjs +34 -0
  158. package/runtime/app-recovery.mjs +87 -0
  159. package/runtime/app-render.mjs +544 -0
  160. package/runtime/app-routing-access.mjs +143 -0
  161. package/runtime/app-routing-context.mjs +35 -0
  162. package/runtime/app-routing-state.mjs +61 -0
  163. package/runtime/app-routing.mjs +323 -0
  164. package/runtime/app-segments.mjs +22 -0
  165. package/runtime/app-static-params.mjs +120 -0
  166. package/runtime/app-static.mjs +86 -0
  167. package/runtime/app-stream-channel.mjs +92 -0
  168. package/runtime/app-stream-html.mjs +279 -0
  169. package/runtime/build-directory.mjs +48 -0
  170. package/runtime/cache-arguments.mjs +33 -0
  171. package/runtime/cache-components-rsc.mjs +64 -0
  172. package/runtime/cache-request.mjs +23 -0
  173. package/runtime/client-navigation.mjs +40 -0
  174. package/runtime/dev-client.mjs +129 -0
  175. package/runtime/dev-file-changes.mjs +110 -0
  176. package/runtime/document.mjs +96 -0
  177. package/runtime/edge.mjs +105 -0
  178. package/runtime/env.mjs +68 -0
  179. package/runtime/fetch-cache.mjs +379 -0
  180. package/runtime/font-preload.mjs +4 -0
  181. package/runtime/http.mjs +190 -0
  182. package/runtime/incremental-cache.mjs +83 -0
  183. package/runtime/metadata-route.mjs +83 -0
  184. package/runtime/middleware.mjs +186 -0
  185. package/runtime/module-loader.mjs +21 -0
  186. package/runtime/navigation.mjs +16 -0
  187. package/runtime/og-loader.mjs +17 -0
  188. package/runtime/pages-client.mjs +197 -0
  189. package/runtime/pages-error-props.mjs +6 -0
  190. package/runtime/pages-errors.mjs +85 -0
  191. package/runtime/pages-initial-props.mjs +43 -0
  192. package/runtime/pages-navigation.mjs +565 -0
  193. package/runtime/pages-paths.mjs +80 -0
  194. package/runtime/pages-revalidate.mjs +33 -0
  195. package/runtime/partial-artifact-cache.mjs +37 -0
  196. package/runtime/partial-template.mjs +174 -0
  197. package/runtime/profile.mjs +9 -0
  198. package/runtime/profiles.json +47 -0
  199. package/runtime/render.mjs +304 -0
  200. package/runtime/request-transport.mjs +41 -0
  201. package/runtime/request-work.mjs +7 -0
  202. package/runtime/route-static.mjs +117 -0
  203. package/runtime/rsc-worker.mjs +426 -0
  204. package/runtime/script-html.mjs +61 -0
  205. package/runtime/stream-select.mjs +25 -0
  206. package/runtime/stream-utils.mjs +127 -0
  207. package/runtime/transport.mjs +108 -0
  208. package/runtime/watch-project.mjs +130 -0
  209. package/runtime/worker-artifacts.mjs +22 -0
  210. package/runtime/worker-metadata.mjs +31 -0
  211. package/runtime/worker.mjs +197 -0
@@ -0,0 +1,64 @@
1
+ import { renderToReadableStream, decodeReply, createTemporaryReferenceSet as serverReferences, registerClientReference } from 'react-server-dom-webpack/server.node';
2
+ import { encodeReply, createTemporaryReferenceSet as clientReferences, createFromReadableStream } from 'react-server-dom-webpack/client.edge';
3
+ import { actionManifest, registerCacheClientModule } from './action-server.mjs';
4
+ import { codecSymbol } from '../compat/use-cache.cjs';
5
+ import { encodeCacheArguments } from './cache-arguments.mjs';
6
+ import { flightBundler } from './worker-metadata.mjs';
7
+ const consumers = new WeakMap();
8
+ const emptyModules = Object.freeze({});
9
+
10
+ export function flightConsumer(context) {
11
+ const clientModules = context.clientModules || emptyModules;
12
+ let moduleMap = consumers.get(clientModules);
13
+ if (!moduleMap) {
14
+ moduleMap = Object.create(null);
15
+ for (const id of Object.keys(clientModules)) {
16
+ const cacheId = 'prnext-cache-client:' + id;
17
+ const values = new Map();
18
+ const namespace = new Proxy(Object.create(null), {
19
+ get(_target, name) {
20
+ if (typeof name !== 'string' || name === 'then') return;
21
+ if (!values.has(name)) values.set(name, registerClientReference(function () { throw new Error('Cannot invoke a Client Component inside a cached Server Component'); }, id, name));
22
+ return values.get(name);
23
+ },
24
+ getOwnPropertyDescriptor(_target, name) { if (typeof name === 'string' && name !== 'then') return { configurable: true, enumerable: true }; },
25
+ });
26
+ registerCacheClientModule(cacheId, namespace);
27
+ moduleMap[id] = { '*': { id: cacheId, chunks: [], name: '*' } };
28
+ }
29
+ consumers.set(clientModules, moduleMap);
30
+ }
31
+ return { moduleMap, moduleLoading: null, serverModuleMap: actionManifest(context) };
32
+ }
33
+
34
+ globalThis[codecSymbol] = {
35
+ async arguments(values, context) {
36
+ const temporaryReferences = clientReferences();
37
+ const body = await encodeReply(values, { temporaryReferences, signal: context.signal });
38
+ const key = await encodeCacheArguments(body);
39
+ const serverTemporaryReferences = serverReferences();
40
+ const decoded = await decodeReply(body, actionManifest(context), { temporaryReferences: serverTemporaryReferences, arraySizeLimit: 10000 });
41
+ return { key, values: decoded, temporaryReferences, serverTemporaryReferences };
42
+ },
43
+ async render(callback, encoded, context) {
44
+ const bundler = flightBundler(context.clientModules, false);
45
+ let failure;
46
+ // React calls then inside its rendering context, preserving React.cache isolation.
47
+ const result = { then(resolve, reject) { Promise.resolve().then(callback).then(resolve, reject); } };
48
+ const stream = renderToReadableStream(result, bundler, { signal: context.signal, temporaryReferences: encoded.serverTemporaryReferences, onError(error) { failure ||= error; return 'PRNextCacheRenderError'; } });
49
+ const chunks = [];
50
+ let size = 0;
51
+ for await (const value of stream) {
52
+ size += value.byteLength;
53
+ if (size > 16 * 1024 * 1024) throw new Error('Cached React result exceeds the 16 MiB rendering limit');
54
+ chunks.push(Buffer.from(value));
55
+ }
56
+ if (failure) throw failure;
57
+ return Buffer.concat(chunks, size);
58
+ },
59
+ async decode(bytes, encoded, context) {
60
+ return createFromReadableStream(new ReadableStream({ start(controller) { controller.enqueue(bytes); controller.close(); } }), {
61
+ serverConsumerManifest: flightConsumer(context), temporaryReferences: encoded.temporaryReferences, replayConsoleLogs: false,
62
+ });
63
+ },
64
+ };
@@ -0,0 +1,23 @@
1
+ import { runRequestContext, currentRequest } from '../compat/headers.cjs';
2
+ import { flushCacheInvalidations, flushCacheWork } from '../compat/data-cache.cjs';
3
+
4
+ // Keep background fills alive until the transport has sent the response. In
5
+ // particular, serving stale data must not wait for its replacement to finish.
6
+ export function withCacheRequest(options, phase, callback) {
7
+ // Raw upload bytes belong to the IncomingMessage/Request. Async cache work
8
+ // only needs metadata, and may survive the response for stale revalidation.
9
+ return runRequestContext({ ...options, body: undefined, routePattern: options.route?.pattern, cacheConfig: options.route?.cacheConfig, phase }, async () => {
10
+ const context = currentRequest();
11
+ try {
12
+ const response = await callback(context);
13
+ await flushCacheInvalidations(context);
14
+ context.cacheState.closed = true;
15
+ return { ...response,
16
+ ...(context.draftMode || context.draftChanged ? { headers: { ...response.headers, 'cache-control': 'private, no-cache, no-store, max-age=0' } } : {}),
17
+ finalizeCache: () => flushCacheWork(context) };
18
+ } catch (error) {
19
+ await flushCacheWork(context);
20
+ throw error;
21
+ }
22
+ });
23
+ }
@@ -0,0 +1,40 @@
1
+ import { addBasePath, removeBasePath, hasBasePath, normalizeTrailingSlash } from '../compat/paths.cjs';
2
+
3
+ function httpURL(value, base) {
4
+ const target = new URL(value, base);
5
+ if (!['http:', 'https:'].includes(target.protocol)) throw new Error('Unsupported navigation protocol');
6
+ return target;
7
+ }
8
+
9
+ /** User router hrefs are logical; URL objects and absolute URLs are already public. */
10
+ export function publicNavigationURL(value, visibleURL, basePath = '', policy = {}) {
11
+ if (value instanceof URL || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(value)) return httpURL(value, visibleURL);
12
+ const internal = httpURL(visibleURL);
13
+ internal.pathname = removeBasePath(internal.pathname, basePath);
14
+ const target = httpURL(value, internal);
15
+ target.pathname = addBasePath(target.pathname, basePath);
16
+ if (basePath && target.pathname === `${basePath}/` && !policy.trailingSlash && !policy.skipTrailingSlashRedirect) target.pathname = basePath;
17
+ target.pathname = normalizeTrailingSlash(target.pathname, policy);
18
+ return target;
19
+ }
20
+
21
+ export function isApplicationURL(target, origin, basePath = '') {
22
+ return target.origin === origin && hasBasePath(target.pathname, basePath);
23
+ }
24
+
25
+ /** A document redirect may leave the site; Flight must stay on this origin. */
26
+ export function flightResponseURL(response, requested) {
27
+ const source = httpURL(requested);
28
+ const target = httpURL(response.url || source.href, source);
29
+ if (target.origin !== source.origin) throw new Error('Cross-origin navigation requires a document request');
30
+ // Fetch omits the request fragment. Keep it unless a final URL supplies one.
31
+ if (!target.hash) target.hash = source.hash;
32
+ return target;
33
+ }
34
+
35
+ /** Data redirects carry a public destination instead of a fetch-follow Location. */
36
+ export function pageDataRedirectURL(response, visibleURL) {
37
+ const target = response.headers.get('x-nextjs-redirect') ||
38
+ ([301, 302, 303, 307, 308].includes(response.status) ? response.headers.get('location') : null);
39
+ return target === null ? null : httpURL(target, visibleURL);
40
+ }
@@ -0,0 +1,129 @@
1
+ import Refresh from 'react-refresh/runtime';
2
+
3
+ const state = globalThis.__PRNEXT_DEV__ ||= {
4
+ applications: new Map(), modules: new Map(), buildId: null, manifest: null,
5
+ pending: null, applying: false, unsafe: false, queued: null, source: null,
6
+ };
7
+ if (!state.injected) { Refresh.injectIntoGlobalHook(globalThis); state.injected = true; }
8
+ export const register = (type, id) => Refresh.register(type, id);
9
+ export const signature = () => Refresh.createSignatureFunctionForTransform();
10
+ export function registerModule(id, hash, boundary) {
11
+ const previous = state.modules.get(id);
12
+ if (previous && previous.hash !== hash && (!previous.boundary || !boundary)) state.unsafe = true;
13
+ state.modules.set(id, { hash, boundary });
14
+ }
15
+
16
+ function clearError() { document.getElementById('__prnext_dev_error__')?.remove(); state.overlayError = undefined; }
17
+ function errorKey(error) {
18
+ return typeof error?.digest === 'string' ? `${error.digest.slice(0, 256)}:${String(error.message || '').slice(0, 512)}` : null;
19
+ }
20
+ function showError(error) {
21
+ const key = errorKey(error);
22
+ if (error && typeof error === 'object' && (state.dismissedErrors?.has(error) || (key && state.dismissedDigests?.has(key)))) return;
23
+ state.overlayError = error;
24
+ let host = document.getElementById('__prnext_dev_error__');
25
+ if (!host) {
26
+ host = document.createElement('div'); host.id = '__prnext_dev_error__';
27
+ Object.assign(host.style, { position: 'fixed', inset: '0', zIndex: '2147483647' });
28
+ const shadow = host.attachShadow({ mode: 'open' });
29
+ const style = document.createElement('style');
30
+ style.textContent = ':host{color-scheme:dark}section{box-sizing:border-box;overflow:auto;height:100%;padding:32px;background:#17191fee;color:#f4f5f7;font:14px/1.6 ui-monospace,monospace}h1{font:600 22px system-ui;color:#ff7474}pre{white-space:pre-wrap;overflow-wrap:anywhere}button{position:fixed;right:24px;top:24px;padding:8px 14px;border:1px solid #697080;border-radius:6px;background:#292e39;color:white;cursor:pointer}';
31
+ const section = document.createElement('section'); section.setAttribute('role', 'alertdialog'); section.setAttribute('aria-label', 'PRNext development error');
32
+ const heading = document.createElement('h1'); heading.textContent = 'PRNext — development error';
33
+ const close = document.createElement('button'); close.textContent = 'Dismiss'; close.onclick = () => {
34
+ // React may report the same failed model again while its boundary retries.
35
+ // Closing that diagnostic must not block the recovered application.
36
+ if (state.overlayError && typeof state.overlayError === 'object') (state.dismissedErrors ||= new WeakSet()).add(state.overlayError);
37
+ const key = errorKey(state.overlayError);
38
+ if (key) {
39
+ const entries = state.dismissedDigests ||= new Set();
40
+ if (entries.size >= 32) entries.delete(entries.values().next().value);
41
+ entries.add(key);
42
+ }
43
+ clearError();
44
+ };
45
+ const details = document.createElement('pre'); details.id = 'details';
46
+ section.append(heading, close, details); shadow.append(style, section); document.documentElement.append(host);
47
+ }
48
+ host.shadowRoot.getElementById('details').textContent = String(error?.stack || error?.message || error).slice(0, 48 * 1024);
49
+ }
50
+
51
+ async function updateStyles(urls) {
52
+ const wanted = new Set(urls.map(url => new URL(url, location.href).href));
53
+ const old = [...document.querySelectorAll('link[rel="stylesheet"]')].filter(link => link.href.includes('/_prnext/assets/'));
54
+ const existing = new Set(old.map(link => link.href));
55
+ await Promise.all([...wanted].filter(href => !existing.has(href)).map(href => new Promise((resolve, reject) => {
56
+ const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href;
57
+ const timer = setTimeout(() => { link.remove(); reject(new Error('Stylesheet update timed out')); }, 15_000);
58
+ link.onload = () => { clearTimeout(timer); resolve(); };
59
+ link.onerror = () => { clearTimeout(timer); link.remove(); reject(new Error(`Could not load updated stylesheet ${href}`)); };
60
+ document.head.append(link);
61
+ })));
62
+ for (const link of old) if (!wanted.has(link.href)) link.remove();
63
+ }
64
+
65
+ async function applyUpdate(message) {
66
+ if (state.applying) { state.queued = message; return; }
67
+ if (!message.clientManifest) { location.reload(); return; }
68
+ const changed = message.changed || [];
69
+ if (changed.some(file => /(?:^|\/)(?:(?:next|prnext)\.config\.|package(?:-lock)?\.json|\.env|pages\/_document\.)/.test(file))) { location.reload(); return; }
70
+ state.applying = true; state.unsafe = false; state.pending = null;
71
+ state.dismissedErrors = new WeakSet();
72
+ state.dismissedDigests = new Set();
73
+ try {
74
+ const response = await fetch(message.clientManifest, { cache: 'no-store', credentials: 'same-origin' });
75
+ if (!response.ok) throw new Error(`Development manifest failed (${response.status})`);
76
+ const manifest = await response.json();
77
+ if (manifest.buildId !== message.buildId) throw new Error('Development build changed during refresh');
78
+ const kind = state.currentKind;
79
+ const application = await state.applications.get(kind);
80
+ const route = kind === 'pages' ? manifest.pages.find(route => route.pattern === application.router.snapshot().pathname) : null;
81
+ const entry = kind === 'pages' ? route?.client : manifest.app?.client;
82
+ if (!entry) { location.reload(); return; }
83
+ await import(/* webpackIgnore: true */ entry);
84
+ const pending = state.pending;
85
+ if (pending) await application.devPrepare?.(pending.options);
86
+ if (state.unsafe) { location.reload(); return; }
87
+ await updateStyles(kind === 'pages' ? route.css : manifest.app.css);
88
+ clearError();
89
+ Refresh.performReactRefresh();
90
+ if (pending) await application.devCommit?.(pending.options);
91
+ state.buildId = message.buildId;
92
+ state.manifest = manifest;
93
+ document.documentElement.dataset.prnextDevBuild = message.buildId;
94
+ globalThis.dispatchEvent(new CustomEvent('prnext:refresh', { detail: { buildId: message.buildId } }));
95
+ } catch (error) { showError(error); }
96
+ finally {
97
+ state.applying = false; state.pending = null;
98
+ if (state.queued) { const queued = state.queued; state.queued = null; if (queued.buildId !== state.buildId) void applyUpdate(queued); }
99
+ }
100
+ }
101
+
102
+ function connect(options) {
103
+ if (state.source) return;
104
+ state.buildId = options.dev.buildId;
105
+ const source = new EventSource(`${options.basePath || ''}/_prnext/dev`);
106
+ state.source = source;
107
+ source.onmessage = event => {
108
+ let message; try { message = JSON.parse(event.data); } catch { return; }
109
+ if (message.state === 'error') { showError(message.error || 'Build failed'); return; }
110
+ if (message.state !== 'ready') return;
111
+ if (message.buildId === state.buildId) return;
112
+ void applyUpdate(message);
113
+ };
114
+ globalThis.addEventListener('error', event => { if (event.error) showError(event.error); });
115
+ globalThis.addEventListener('unhandledrejection', event => showError(event.reason));
116
+ globalThis.addEventListener('pagehide', () => source.close(), { once: true });
117
+ }
118
+
119
+ state.reportError = showError;
120
+ state.bootstrap = (kind, options, start) => {
121
+ state.currentKind = kind;
122
+ if (!state.applications.has(kind)) {
123
+ const application = Promise.resolve().then(start);
124
+ state.applications.set(kind, application);
125
+ connect(options);
126
+ application.catch(showError);
127
+ } else if (state.applying) state.pending = { kind, options };
128
+ return state.applications.get(kind);
129
+ };
@@ -0,0 +1,110 @@
1
+ import path from 'node:path';
2
+ import {createHash} from 'node:crypto';
3
+ import {createReadStream} from 'node:fs';
4
+ import {stat, readdir} from 'node:fs/promises';
5
+ import {shouldWatchProjectFile} from './env.mjs';
6
+
7
+ function directoryFingerprint(entries) {
8
+ const hash=createHash('sha256');
9
+ for (const entry of entries.sort((a,b)=>a.name.localeCompare(b.name))) hash.update(`${entry.isDirectory()?'d':entry.isSymbolicLink()?'l':'f'}:${entry.name}\0`);
10
+ return 'directory:'+hash.digest('hex');
11
+ }
12
+
13
+ // Remember the bytes actually consumed by webpack, not the file's contents at
14
+ // the end of a build: an edit made after a read must still trigger a rebuild.
15
+ export function createDevInputSnapshot(roots, {maxEntries=512,maxBytes=1024*1024}={}) {
16
+ const inputs=new Map();
17
+ let bytes=0;
18
+ function remember(relative,value) {
19
+ if (inputs.has(relative)) {
20
+ if (inputs.get(relative)!==value) inputs.set(relative,null);
21
+ return;
22
+ }
23
+ const size=relative.length*2+value.length*2+64;
24
+ if (inputs.size>=maxEntries || bytes+size>maxBytes) return;
25
+ inputs.set(relative,value);bytes+=size;
26
+ }
27
+ return {
28
+ record(file, source) {
29
+ const root=roots.find(root=>file.startsWith(root+path.sep));
30
+ if (!root) return;
31
+ const relative=path.relative(root,file).replaceAll(path.sep,'/');
32
+ if (!shouldWatchProjectFile(relative)) return;
33
+ const value='file:'+createHash('sha256').update(source).digest('hex');
34
+ // Different targets consuming different bytes cannot acknowledge this
35
+ // event, even when the file is changed back before compilation ends.
36
+ remember(relative,value);
37
+ },
38
+ async captureDirectory(relative) {
39
+ // Called after an awaited content generator and before graph resolution.
40
+ // Include its types/aggregate indexes, which may not be runtime imports.
41
+ if (!shouldWatchProjectFile(relative) || inputs.size>=maxEntries || bytes>=maxBytes) return;
42
+ const file=path.join(roots[0],relative);
43
+ let entries;
44
+ try {entries=await readdir(file,{withFileTypes:true});}
45
+ catch(error){if(error.code==='ENOENT')return;throw error;}
46
+ remember(relative,directoryFingerprint(entries));
47
+ for(const entry of entries) {
48
+ if(inputs.size>=maxEntries || bytes>=maxBytes)break;
49
+ const child=relative+'/'+entry.name;
50
+ if(!shouldWatchProjectFile(child))continue;
51
+ if(entry.isDirectory())await this.captureDirectory(child);
52
+ else if(entry.isFile()) {
53
+ const hash=createHash('sha256');
54
+ try {
55
+ for await(const chunk of createReadStream(path.join(file,entry.name),{highWaterMark:64*1024}))hash.update(chunk);
56
+ remember(child,'file:'+hash.digest('hex'));
57
+ } catch(error) {if(error.code!=='ENOENT')throw error;}
58
+ }
59
+ }
60
+ },
61
+ entries:()=>[...inputs],
62
+ };
63
+ }
64
+
65
+ // Generators may rewrite identical JSON/modules on every compilation. Retain
66
+ // only bounded path/digest pairs, never file contents or full directory lists.
67
+ export function createDevChangeFilter(root, {maxEntries=512,maxBytes=1024*1024}={}) {
68
+ const fingerprints=new Map();
69
+ let bytes=0;
70
+ function remember(file,value) {
71
+ const previous=fingerprints.get(file);
72
+ if (previous) {fingerprints.delete(file);bytes-=previous.size;}
73
+ if (value==null) return;
74
+ const size=file.length*2+value.length*2+64;
75
+ if (size>maxBytes || maxEntries<1) return;
76
+ while(fingerprints.size>=maxEntries || bytes+size>maxBytes) {
77
+ const [key,oldest]=fingerprints.entries().next().value;
78
+ fingerprints.delete(key);bytes-=oldest.size;
79
+ }
80
+ fingerprints.set(file,{value,size});bytes+=size;
81
+ }
82
+ async function fingerprint(relative) {
83
+ const file=path.join(root,relative), hash=createHash('sha256');
84
+ try {
85
+ const info=await stat(file);
86
+ if (info.isDirectory()) {
87
+ const entries=await readdir(file,{withFileTypes:true});
88
+ return directoryFingerprint(entries);
89
+ }
90
+ if (!info.isFile()) return `special:${info.mode}:${info.mtimeMs}`;
91
+ for await (const chunk of createReadStream(file,{highWaterMark:64*1024})) hash.update(chunk);
92
+ return 'file:'+hash.digest('hex');
93
+ } catch (error) {
94
+ if (error.code==='ENOENT' || error.code==='ENOTDIR') return 'missing';
95
+ // An unreadable/changing file must still trigger the compiler diagnostic.
96
+ return undefined;
97
+ }
98
+ }
99
+ const filter=async files => {
100
+ const changed=[];
101
+ for (const file of files) {
102
+ const value=await fingerprint(file), previous=fingerprints.get(file);
103
+ if (value===undefined || !previous || previous.value!==value) changed.push(file);
104
+ remember(file,value);
105
+ }
106
+ return changed;
107
+ };
108
+ filter.acceptInputs=inputs=>{for(const [file,value] of inputs)remember(file,value);};
109
+ return filter;
110
+ }
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import DefaultDocument, { DocumentContext } from '../compat/document.cjs';
3
+ import DefaultApp from '../compat/app.cjs';
4
+ import { RouterProvider, useRouter } from '../compat/router.cjs';
5
+ import { HeadProvider, dedupeHead } from '../compat/head.cjs';
6
+ import { DynamicProvider, preloadAll } from '../compat/dynamic.cjs';
7
+ import { MAX_RESPONSE_BYTES } from './http.mjs';
8
+ import { ScriptContext } from '../compat/script-context.cjs';
9
+ import { completePageScripts } from './script-html.mjs';
10
+
11
+ function PageEntry({ App, Component, pageProps, appProps }) {
12
+ const router = useRouter();
13
+ return React.createElement(App, { ...appProps, Component, pageProps, router });
14
+ }
15
+ function defaultHead() {
16
+ return [React.createElement('meta', { charSet: 'utf-8' }),
17
+ React.createElement('meta', { name: 'viewport', content: 'width=device-width' })];
18
+ }
19
+
20
+ export async function renderDocument({ Document = DefaultDocument, App = DefaultApp, Page, props, appProps = {}, snapshot, route,
21
+ request, response, error, production, buildId, manifest, isStatic, hasServerProps, hasPageInitialProps, hasAppInitialProps }) {
22
+ if (typeof Document !== 'function' && (!Document || typeof Document !== 'object')) throw new TypeError('pages/_document must export a React component as default');
23
+ const { renderToString, renderToStaticMarkup } = await import('react-dom/server');
24
+ await preloadAll();
25
+ const dynamicModules = new Set();
26
+ const pageScripts = [], documentScripts = [];
27
+ const scriptContext = { appDir: false, ssr: true, document: false, collect: props => pageScripts.push(props) };
28
+ let renderedHead = defaultHead();
29
+ const tree = (EnhancedApp, EnhancedPage, pageProps, collector, appProps) =>
30
+ React.createElement(manifest?.config?.reactStrictMode ? React.StrictMode : React.Fragment, null, React.createElement(RouterProvider, { router: snapshot }, React.createElement(HeadProvider, { collector },
31
+ React.createElement(DynamicProvider, { modules: dynamicModules },
32
+ React.createElement(ScriptContext.Provider, { value: scriptContext },
33
+ React.createElement(PageEntry, { App: EnhancedApp, Component: EnhancedPage, pageProps, appProps }))))));
34
+ const renderPage = async (options = {}) => {
35
+ if (typeof options === 'function') options = { enhanceComponent: options };
36
+ if (!options || typeof options !== 'object') throw new TypeError('Document renderPage expects component enhancers');
37
+ const EnhancedApp = options.enhanceApp ? options.enhanceApp(App) : App;
38
+ const EnhancedPage = options.enhanceComponent ? options.enhanceComponent(Page) : Page;
39
+ await preloadAll();
40
+ pageScripts.length = 0;
41
+ const collector = [];
42
+ const html = `<div id="__prnext">${renderToString(tree(EnhancedApp, EnhancedPage, props, collector, appProps))}</div>`;
43
+ if (Buffer.byteLength(html) > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit');
44
+ renderedHead = dedupeHead([...defaultHead(), ...collector]);
45
+ return { html, head: renderedHead };
46
+ };
47
+ const AppTree = ({ pageProps = {}, ...appProps }) => tree(App, Page, pageProps, [], appProps);
48
+ const builtinError = route.internal && route.errorStatus;
49
+ const autoStatic = !builtinError && !isStatic && !hasServerProps && !hasPageInitialProps && !hasAppInitialProps;
50
+ const autoAsPath = snapshot.pathname + (request.url.endsWith('/') && !snapshot.pathname.endsWith('/') && !snapshot.pathname.includes('[') ? '/' : '');
51
+ const documentAsPath = builtinError ? `/${route.errorStatus}` : autoStatic ? autoAsPath : snapshot.asPath;
52
+ const context = { err: error, req: autoStatic ? undefined : request, res: autoStatic ? undefined : response, pathname: snapshot.pathname,
53
+ query: autoStatic ? {} : snapshot.query, asPath: documentAsPath, AppTree, renderPage,
54
+ async defaultGetInitialProps(ctx) {
55
+ const result = await ctx.renderPage({ enhanceApp: Component => Component });
56
+ return { html: result.html, head: result.head, styles: [] };
57
+ } };
58
+ let initial;
59
+ const hasInitialProps = typeof Document.getInitialProps === 'function';
60
+ if (hasInitialProps) initial = await Document.getInitialProps(context);
61
+ else initial = { ...await renderPage(), styles: [] };
62
+ if (response.writableEnded) return { ended: true };
63
+ if (!initial || typeof initial !== 'object' || Array.isArray(initial) || typeof initial.html !== 'string') {
64
+ throw new TypeError('Document.getInitialProps must return an object with an html string');
65
+ }
66
+ if (Buffer.byteLength(initial.html) > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit');
67
+ const payload = { props, appProps, router: snapshot, buildId, route: { pattern: snapshot.pathname }, dynamicIds: [...dynamicModules] };
68
+ const assetPrefix = manifest?.config?.assetPrefix || manifest?.config?.basePath || '';
69
+ const nextData = { props: { ...appProps, pageProps: props }, page: snapshot.pathname, query: snapshot.query, buildId,
70
+ ...(snapshot.locale ? {locale:snapshot.locale,locales:snapshot.locales,defaultLocale:snapshot.defaultLocale} : {}),
71
+ ...(assetPrefix ? { assetPrefix } : {}),
72
+ isFallback: snapshot.isFallback, ...(isStatic ? { gsp: true } : {}), ...(hasServerProps ? { gssp: true } : {}),
73
+ ...(hasPageInitialProps ? { gip: true } : {}), ...(hasAppInitialProps ? { appGip: true } : {}) };
74
+ const head = React.Children.toArray(initial.head).map((element, key) => React.isValidElement(element)
75
+ ? React.cloneElement(element, { key, 'data-prnext-head': '' }) : element);
76
+ const state = { html: initial.html, head, styles: initial.styles, rendered: new Set(), payload,
77
+ client: route.client, css: route.css || [], fonts: route.fonts || [], __NEXT_DATA__: nextData };
78
+ const documentProps = { __NEXT_DATA__: nextData, assetPrefix,
79
+ dangerousAsPath: documentAsPath, isDevelopment: !production, ...initial };
80
+ if (!hasInitialProps) delete documentProps.html;
81
+ state.nonce = documentProps.nonce;
82
+ state.crossOrigin = documentProps.crossOrigin;
83
+ let markup = renderToStaticMarkup(React.createElement(DocumentContext.Provider, { value: state },
84
+ React.createElement(ScriptContext.Provider, { value: { ...scriptContext, document: true, collect: props => documentScripts.push(props) } },
85
+ React.createElement(Document, documentProps))))
86
+ .replace('<prnext-document-body-target></prnext-document-body-target>', () => initial.html);
87
+ markup = completePageScripts(markup, { pageScripts, documentScripts, worker: manifest?.scriptWorkers,
88
+ nonce: state.headNonce || state.nonce, crossOrigin: state.headCrossOrigin || state.crossOrigin }, renderToStaticMarkup);
89
+ if (!production) {
90
+ const missing = ['Html', 'Head', 'Main', 'NextScript'].filter(name => !state.rendered.has(name));
91
+ if (missing.length) console.warn(`Your custom Document did not render the required components: ${missing.map(name => `<${name} />`).join(', ')}`);
92
+ }
93
+ const html = /^<!doctype html>/i.test(markup) ? markup : `<!DOCTYPE html>${markup}`;
94
+ if (Buffer.byteLength(html) > MAX_RESPONSE_BYTES) throw new Error('Response exceeds the 16 MiB PRNext limit');
95
+ return { html };
96
+ }
@@ -0,0 +1,105 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { runInContext } from 'node:vm';
3
+ import { createRequire } from 'node:module';
4
+ import * as headerApi from '../compat/headers.cjs';
5
+ import * as cacheApi from '../compat/cache.cjs';
6
+ import * as navigationApi from '../compat/navigation-server.cjs';
7
+ import { connection } from '../compat/server.cjs';
8
+
9
+ function responseHeaders(headers) {
10
+ const values = [...headers].filter(([name]) => name.toLowerCase() !== 'set-cookie');
11
+ for (const cookie of headers.getSetCookie()) values.push(['set-cookie', cookie]);
12
+ return values;
13
+ }
14
+
15
+ function responseMetadata(target, source) {
16
+ const metadata = { url: source.url, redirected: source.redirected, type: source.type };
17
+ const clone = target.clone;
18
+ Object.defineProperties(target, {
19
+ ...Object.fromEntries(Object.entries(metadata).map(([name, value]) => [name, { value, configurable: true }])),
20
+ clone: { configurable: true, value() { return responseMetadata(Reflect.apply(clone, this, []), metadata); } },
21
+ });
22
+ return target;
23
+ }
24
+
25
+ const reactVMs = new Map();
26
+
27
+ async function createEdgeVM(reactMode) {
28
+ const vendor = await import('@edge-runtime/vm');
29
+ const { EdgeVM } = vendor.default || vendor;
30
+ const react = {}, framework = {};
31
+ if (reactMode) {
32
+ const require = createRequire(import.meta.url);
33
+ for (const name of ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-dom']) react[name] = require(name);
34
+ if (reactMode === 'rsc') react['react-server-dom-webpack/server'] = Object.fromEntries(['registerClientReference', 'registerServerReference'].map(name => [name, require('react-server-dom-webpack/server.node')[name]]));
35
+ if (reactMode === 'rsc') framework['app-dynamic'] = require('../compat/app-dynamic.cjs');
36
+ if (reactMode === 'ssr') for (const name of ['link', 'image', 'head', 'router', 'compat-router', 'next-router-context', 'next-app-router-context', 'navigation', 'script', 'app-context', 'app-layout-context', 'app-navigation-boundary', 'app-client-page', 'app-error-boundary', 'app-dynamic']) framework[name] = require('../compat/' + name + '.cjs');
37
+ }
38
+ const actionHelpers = reactMode ? { 'action-crypto': await import('./action-crypto.mjs'), 'action-ssr': await import('./action-ssr.mjs') } : {};
39
+ return new EdgeVM({ codeGeneration: { strings: false, wasm: false }, extend(context) {
40
+ context.process = { env: { ...process.env, NEXT_RUNTIME: 'edge' } };
41
+ context.AsyncLocalStorage = AsyncLocalStorage;
42
+ if (reactMode) {
43
+ context.__PRNEXT_EDGE_REACT = react;
44
+ context.__PRNEXT_EDGE_ACTIONS = actionHelpers;
45
+ context.__PRNEXT_EDGE_FRAMEWORK = framework;
46
+ // React's host Flight serializer recognizes these Web value types by
47
+ // constructor identity; keep their semantics across the two realms.
48
+ Object.assign(context, { Date, Map, Set, FormData, ArrayBuffer, Uint8Array, Uint8ClampedArray, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array, DataView,
49
+ Error, TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError, AggregateError });
50
+ }
51
+ context.__PRNEXT_EDGE_HOST = {
52
+ headers: { headers: async () => {
53
+ const values = new context.Headers(await headerApi.headers());
54
+ return new Proxy(values, { get(target, key) {
55
+ if (['set', 'append', 'delete'].includes(key)) return () => { throw new Error('Headers cannot be modified'); };
56
+ const value = Reflect.get(target, key, target);
57
+ return typeof value === 'function' ? value.bind(target) : value;
58
+ } });
59
+ }, cookies: headerApi.cookies, draftMode: headerApi.draftMode },
60
+ cache: { revalidateTag: cacheApi.revalidateTag, revalidatePath: cacheApi.revalidatePath, unstable_noStore: cacheApi.unstable_noStore },
61
+ navigation: { redirect: navigationApi.redirect, permanentRedirect: navigationApi.permanentRedirect,
62
+ notFound: navigationApi.notFound, RedirectType: navigationApi.RedirectType, unstable_rethrow: navigationApi.unstable_rethrow },
63
+ server: { connection },
64
+ };
65
+ context.fetch = async (input, init) => {
66
+ if (input instanceof context.Request) input = new Request(input.url, { method: input.method, headers: [...input.headers], body: input.body, signal: input.signal, duplex: 'half',
67
+ cache: input.cache, credentials: input.credentials, mode: input.mode, redirect: input.redirect, referrer: input.referrer, referrerPolicy: input.referrerPolicy, integrity: input.integrity });
68
+ const response = await fetch(input, init);
69
+ return responseMetadata(new context.Response(response.body, { status: response.status, statusText: response.statusText, headers: responseHeaders(response.headers) }), response);
70
+ };
71
+ return context;
72
+ } });
73
+ }
74
+
75
+ export async function loadEdgeModule(code, name, reactMode) {
76
+ if (reactMode !== undefined && reactMode !== 'rsc' && reactMode !== 'ssr') throw new Error('Unknown Edge React mode');
77
+ let pending;
78
+ if (reactMode) {
79
+ pending = reactVMs.get(reactMode);
80
+ if (!pending) {
81
+ pending = createEdgeVM(reactMode);
82
+ reactVMs.set(reactMode, pending);
83
+ void pending.catch(() => { if (reactVMs.get(reactMode) === pending) reactVMs.delete(reactMode); });
84
+ }
85
+ } else pending = createEdgeVM();
86
+ // This runtime module belongs to one compiled build. Its two React realms
87
+ // are shared lazily by all pages/boundaries; each bundle has its own factory.
88
+ const vm = await pending;
89
+ const module = await runInContext(code, vm.context, { filename: name, timeout: 5000 });
90
+ if (reactMode) return module;
91
+ const handlers = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'middleware', 'proxy', 'default']);
92
+ const result = {};
93
+ for (const [key, value] of Object.entries(module)) {
94
+ if (!handlers.has(key) || typeof value !== 'function') { result[key] = value; continue; }
95
+ result[key] = async (request, ...args) => {
96
+ const init = { method: request.method, headers: [...request.headers], signal: request.signal, nextConfig: request.nextUrl?._nextConfig };
97
+ if (!['GET', 'HEAD'].includes(request.method) && request.body) { init.body = request.body; init.duplex = 'half'; }
98
+ const response = await value(module.__prnext_createRequest(request.url, init), ...args);
99
+ if (response == null) return response;
100
+ if (!(response instanceof vm.context.Response)) throw new TypeError('Edge handler must return a Web Response');
101
+ return new Response(response.body, { status: response.status, statusText: response.statusText, headers: responseHeaders(response.headers) });
102
+ };
103
+ }
104
+ return result;
105
+ }
@@ -0,0 +1,68 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse } from 'dotenv';
4
+ import { expand } from 'dotenv-expand';
5
+
6
+ // Track only values installed here, preserving shell inputs and runtime changes.
7
+ const loadedValues = new WeakMap();
8
+
9
+ /** Scope build-time env mutations so later child servers inherit only callers' env. */
10
+ export function snapshotEnvConfig(env = process.env) {
11
+ const snapshot = { ...env };
12
+ const provenance = loadedValues.get(env);
13
+ return () => {
14
+ for (const key of Object.keys(env)) if (!Object.hasOwn(snapshot, key)) delete env[key];
15
+ Object.assign(env, snapshot);
16
+ if (provenance) loadedValues.set(env, provenance);
17
+ else loadedValues.delete(env);
18
+ };
19
+ }
20
+
21
+ export function envFileNames({ dev = false, env = process.env, mode = env.NODE_ENV === 'test' ? 'test' : dev ? 'development' : 'production' } = {}) {
22
+ if (!['development', 'production', 'test'].includes(mode)) throw new Error('Environment file mode must be development, production or test.');
23
+ return [`.env.${mode}.local`, ...(mode === 'test' ? [] : ['.env.local']), `.env.${mode}`, '.env'];
24
+ }
25
+
26
+ /** Load root env files in Next's precedence order, without logging values. */
27
+ export function loadEnvConfig(projectRoot, { dev = false, env = process.env, mode } = {}) {
28
+ for (const [key, value] of loadedValues.get(env) || []) {
29
+ if (env[key] === value) delete env[key];
30
+ }
31
+ const original = { ...env };
32
+ const combined = { ...original };
33
+ const parsedEnv = Object.create(null);
34
+ const loadedEnvFiles = [];
35
+ for (const name of envFileNames({ dev, env, mode })) {
36
+ const filename = path.join(projectRoot, name);
37
+ let contents;
38
+ try {
39
+ if (!statSync(filename).isFile()) continue;
40
+ contents = readFileSync(filename, 'utf8');
41
+ } catch (error) {
42
+ if (error.code === 'ENOENT') continue;
43
+ throw new Error(`Could not load ${filename}: ${error.code || error.message}`, { cause: error });
44
+ }
45
+ let parsed;
46
+ try { parsed = expand({ parsed: parse(contents), processEnv: combined }).parsed; }
47
+ catch (error) { throw new Error(`Could not expand variables in ${filename}. Check for circular references.`, { cause: error }); }
48
+ for (const [key, value] of Object.entries(parsed)) {
49
+ if (!Object.hasOwn(original, key) && !Object.hasOwn(parsedEnv, key)) parsedEnv[key] = value;
50
+ }
51
+ loadedEnvFiles.push(name);
52
+ }
53
+ // Expansion uses a scratch object so caller-owned escaped dollars stay intact.
54
+ for (const [key, value] of Object.entries(parsedEnv)) env[key] = value;
55
+ loadedValues.set(env, new Map(Object.entries(parsedEnv)));
56
+ return { combinedEnv: env, parsedEnv, loadedEnvFiles };
57
+ }
58
+
59
+ export function shouldWatchProjectFile(filename) {
60
+ if (!filename) return false;
61
+ const parts = String(filename).split(/[\\/]/);
62
+ // Contentlayer's generated modules are real application inputs. Its cache
63
+ // and temporary config bundles stay excluded to avoid generator feedback.
64
+ if (parts[0] === '.contentlayer' && parts[1] === 'generated') return !parts.slice(2).some(part => part.startsWith('.') || part === 'node_modules');
65
+ if (parts.length === 1 && /^\.env(?:\.(?:development|production|test))?(?:\.local)?$/.test(parts[0])) return true;
66
+ if (parts.length === 1 && /^(?:\.postcssrc(?:\.(?:js|mjs|cjs|json))?|\.browserslistrc)$/.test(parts[0])) return true;
67
+ return !parts.some(part => part === 'node_modules' || part === 'target' || part.startsWith('.'));
68
+ }