@solcreek/adapter-creek 0.1.3 → 0.2.0

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/dist/bundler.js CHANGED
@@ -10,6 +10,7 @@
10
10
  import * as fs from "node:fs/promises";
11
11
  import * as path from "node:path";
12
12
  import { execSync } from "node:child_process";
13
+ import { builtinModules, createRequire } from "node:module";
13
14
  /**
14
15
  * Patch Turbopack runtime to inline chunk loading.
15
16
  *
@@ -32,18 +33,31 @@ async function patchTurbopackRuntime(distDir) {
32
33
  path.join(distDir, "server", "chunks"),
33
34
  path.join(distDir, "server", "edge", "chunks"),
34
35
  ];
35
- for (const dir of searchDirs) {
36
+ async function walkRuntimes(dir) {
37
+ let entries;
36
38
  try {
37
- const files = await fs.readdir(dir);
38
- for (const f of files) {
39
- if (f.endsWith(".js") && (f.includes("[turbopack]_runtime") ||
40
- // Edge Turbopack runtimes are in turbopack-..._edge-wrapper files
41
- (f.startsWith("turbopack-") && f.includes("edge-wrapper")))) {
42
- runtimePaths.push(path.join(dir, f));
43
- }
39
+ entries = await fs.readdir(dir, { withFileTypes: true });
40
+ }
41
+ catch {
42
+ return;
43
+ }
44
+ for (const entry of entries) {
45
+ const full = path.join(dir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ await walkRuntimes(full);
48
+ continue;
49
+ }
50
+ if (!entry.name.endsWith(".js"))
51
+ continue;
52
+ if (entry.name.includes("[turbopack]_runtime") ||
53
+ (entry.name.startsWith("turbopack-") && entry.name.includes("edge-wrapper")) ||
54
+ entry.name.includes("edge-wrapper")) {
55
+ runtimePaths.push(full);
44
56
  }
45
57
  }
46
- catch { }
58
+ }
59
+ for (const dir of searchDirs) {
60
+ await walkRuntimes(dir);
47
61
  }
48
62
  if (runtimePaths.length === 0)
49
63
  return; // Not Turbopack
@@ -136,18 +150,501 @@ ${cases.join("\n")}
136
150
  }
137
151
  }
138
152
  }
153
+ async function patchNodeExternalRequireConditions(distDir) {
154
+ const serverChunksDir = path.join(distDir, "server", "chunks");
155
+ const projectRoot = path.dirname(distDir);
156
+ const projectRequire = createRequire(path.join(projectRoot, "package.json"));
157
+ const builtins = new Set([
158
+ ...builtinModules,
159
+ ...builtinModules.map((name) => `node:${name}`),
160
+ ]);
161
+ // Narrow scope: only `[externals]*.js` files in `server/chunks/`.
162
+ // Those are Turbopack's dedicated external-require-registration bundles,
163
+ // which contain nothing but `.x("pkg", () => require("pkg"))` lines —
164
+ // rewriting require() to an absolute path there has no module-identity
165
+ // risk. We tried extending this to `server/chunks/ssr/*.js` to fix the
166
+ // styled-jsx-subpath case, but those chunks contain real SSR render
167
+ // code whose require() calls participate in Turbopack's module graph
168
+ // (same package resolved two ways = two React instances). The
169
+ // styled-jsx case is now handled via wrangler `alias` entries instead —
170
+ // see collectSsrLazyRequireAliases() + the wranglerConfig assembly.
171
+ let entries;
172
+ try {
173
+ entries = await fs.readdir(serverChunksDir);
174
+ }
175
+ catch {
176
+ return;
177
+ }
178
+ const chunkFiles = [];
179
+ for (const entry of entries) {
180
+ if (!entry.startsWith("[externals]") || !entry.endsWith(".js"))
181
+ continue;
182
+ chunkFiles.push(path.join(serverChunksDir, entry));
183
+ }
184
+ if (chunkFiles.length === 0)
185
+ return;
186
+ const externalRequirePattern = /(\w+\.x\(\s*(["'])([^"']+)\2\s*,\s*\(\)\s*=>\s*)require\(\s*(["'])\3\4\s*\)(\s*\))/g;
187
+ const stripTurbopackPackageAlias = (specifier) => {
188
+ const parts = specifier.split("/");
189
+ const packageIndex = specifier.startsWith("@") ? 1 : 0;
190
+ const packageName = parts[packageIndex];
191
+ if (!packageName)
192
+ return null;
193
+ const match = /^(.*)-[0-9a-f]{16}$/i.exec(packageName);
194
+ if (!match || !match[1])
195
+ return null;
196
+ parts[packageIndex] = match[1];
197
+ return parts.join("/");
198
+ };
199
+ for (const filePath of chunkFiles) {
200
+ let code;
201
+ try {
202
+ code = await fs.readFile(filePath, "utf-8");
203
+ }
204
+ catch {
205
+ continue;
206
+ }
207
+ if (!code.includes(".x(") || !code.includes("require("))
208
+ continue;
209
+ const patched = code.replace(externalRequirePattern, (match, prefix, _quote, specifier, _requireQuote, suffix) => {
210
+ if (specifier.startsWith(".") ||
211
+ specifier.startsWith("/") ||
212
+ builtins.has(specifier) ||
213
+ specifier === "next" ||
214
+ specifier.startsWith("next/") ||
215
+ specifier.startsWith("@next/")) {
216
+ return match;
217
+ }
218
+ let resolved;
219
+ try {
220
+ resolved = projectRequire.resolve(specifier);
221
+ }
222
+ catch {
223
+ const unaliased = stripTurbopackPackageAlias(specifier);
224
+ if (!unaliased)
225
+ return match;
226
+ try {
227
+ resolved = projectRequire.resolve(unaliased);
228
+ }
229
+ catch {
230
+ return match;
231
+ }
232
+ }
233
+ if (resolved.endsWith(".node"))
234
+ return match;
235
+ return `${prefix}require(${JSON.stringify(resolved)})${suffix}`;
236
+ });
237
+ if (patched !== code) {
238
+ await fs.writeFile(filePath, patched);
239
+ }
240
+ }
241
+ }
242
+ /**
243
+ * Collect `.x("pkg", () => require("pkg"))` lazy-require specifiers out of
244
+ * `server/chunks/ssr/*.js` and return a map of specifiers → absolute paths
245
+ * that wrangler's esbuild should alias.
246
+ *
247
+ * Why this exists: Turbopack emits those registrations for SSR chunks when
248
+ * a transitive (indirect) dep like `styled-jsx/style.js` is needed. In
249
+ * pnpm projects the package lives at `.pnpm/styled-jsx@X/node_modules/...`
250
+ * and isn't hoisted to a top-level `node_modules/`. esbuild's walker
251
+ * therefore fails with `Could not resolve "styled-jsx/style.js"`.
252
+ *
253
+ * The previous attempt was to rewrite `require("pkg")` inline to an
254
+ * absolute path, mirroring what we do for `[externals]*.js` chunks — but
255
+ * `ssr/*.js` chunks also contain real SSR render code whose require()
256
+ * calls participate in Turbopack's module graph. Absolute-path rewrites
257
+ * there risk creating two module instances of the same package (once via
258
+ * Turbopack, once via our rewrite), which silently breaks React etc.
259
+ * and surfaced as "uncached or runtime data" prerender failures in
260
+ * `resume-data-cache` on Linux CI only.
261
+ *
262
+ * Returning an alias map instead keeps all source untouched. esbuild's
263
+ * alias is a global resolver override, so every reference to the same
264
+ * specifier goes through the same absolute path — module identity stays
265
+ * intact.
266
+ */
267
+ async function collectSsrLazyRequireAliases(distDir) {
268
+ const ssrDir = path.join(distDir, "server", "chunks", "ssr");
269
+ const projectRoot = path.dirname(distDir);
270
+ const projectRequire = createRequire(path.join(projectRoot, "package.json"));
271
+ const builtins = new Set([
272
+ ...builtinModules,
273
+ ...builtinModules.map((name) => `node:${name}`),
274
+ ]);
275
+ let entries;
276
+ try {
277
+ entries = await fs.readdir(ssrDir);
278
+ }
279
+ catch {
280
+ return {};
281
+ }
282
+ // Same regex as the [externals] patcher: `.x("X", () => require("X"))`
283
+ // where the two strings are identical.
284
+ const lazyRequirePattern = /\w+\.x\(\s*(["'])([^"']+)\1\s*,\s*\(\)\s*=>\s*require\(\s*(["'])\2\3\s*\)\s*\)/g;
285
+ const seen = new Set();
286
+ const aliasMap = {};
287
+ for (const entry of entries) {
288
+ if (!entry.endsWith(".js"))
289
+ continue;
290
+ const filePath = path.join(ssrDir, entry);
291
+ let code;
292
+ try {
293
+ code = await fs.readFile(filePath, "utf-8");
294
+ }
295
+ catch {
296
+ continue;
297
+ }
298
+ if (!code.includes(".x(") || !code.includes("require("))
299
+ continue;
300
+ let m;
301
+ while ((m = lazyRequirePattern.exec(code)) !== null) {
302
+ const specifier = m[2];
303
+ if (!specifier || seen.has(specifier))
304
+ continue;
305
+ seen.add(specifier);
306
+ if (specifier.startsWith(".") ||
307
+ specifier.startsWith("/") ||
308
+ builtins.has(specifier) ||
309
+ specifier === "next" ||
310
+ specifier.startsWith("next/") ||
311
+ specifier.startsWith("@next/")) {
312
+ continue;
313
+ }
314
+ let resolved = null;
315
+ try {
316
+ resolved = projectRequire.resolve(specifier);
317
+ }
318
+ catch {
319
+ // no-op — specifier will fail esbuild too, nothing we can fix
320
+ continue;
321
+ }
322
+ if (!resolved || resolved.endsWith(".node"))
323
+ continue;
324
+ aliasMap[specifier] = resolved;
325
+ }
326
+ }
327
+ return aliasMap;
328
+ }
329
+ async function patchAppPageManifestSingletons(distDir) {
330
+ const ssrDir = path.join(distDir, "server", "chunks", "ssr");
331
+ let entries = [];
332
+ try {
333
+ entries = await fs.readdir(ssrDir);
334
+ }
335
+ catch {
336
+ return;
337
+ }
338
+ const manifestProxyPattern = /case"moduleLoading":case"entryCSSFiles":case"entryJSFiles":\{if\(!(\w+)\)throw[\s\S]*?let (\w+)=(\w+)\.get\(\1\.route\);if\(!\2\)throw[\s\S]*?return \2\[(\w+)\]\}/g;
339
+ for (const entry of entries) {
340
+ if (!entry.endsWith(".js"))
341
+ continue;
342
+ const filePath = path.join(ssrDir, entry);
343
+ let code;
344
+ try {
345
+ code = await fs.readFile(filePath, "utf-8");
346
+ }
347
+ catch {
348
+ continue;
349
+ }
350
+ if (!code.includes('entryCSSFiles') || !code.includes('without a work store')) {
351
+ continue;
352
+ }
353
+ const patched = code.replace(manifestProxyPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar) => `case"moduleLoading":case"entryCSSFiles":case"entryJSFiles":{if(!${workStoreVar}){for(let a of ${manifestsVar}.values()){let b=a[${propVar}];if(void 0!==b)return b}return}let ${manifestVar}=${manifestsVar}.get(${workStoreVar}.route);if(!${manifestVar}){for(let a of ${manifestsVar}.values()){let b=a[${propVar}];if(void 0!==b)return b}return}return ${manifestVar}[${propVar}]}`);
354
+ if (patched !== code) {
355
+ await fs.writeFile(filePath, patched);
356
+ }
357
+ }
358
+ }
359
+ function patchBundledManifestSingleton(workerCode) {
360
+ const bundledManifestProxyPattern = /case "moduleLoading":\s*case "entryCSSFiles":\s*case "entryJSFiles": \{\s*if \(!(\w+)\) throw[\s\S]*?let (\w+) = (\w+)\.get\(\1\.route\);\s*if \(!\2\) throw[\s\S]*?return \2\[(\w+)\];\s*\}/g;
361
+ const bundledManifestLookupPattern = /if \((\w+)\) \{\s*let (\w+) = (\w+)\.get\(\1\.route\);\s*if \(null == \2 \? void 0 : \2\[(\w+)\]\[(\w+)\]\) return \2\[\4\]\[\5\];\s*\} else for \(let (\w+) of \3\.values\(\)\) \{\s*let (\w+) = \6\[\4\]\[\5\];\s*if \(void 0 !== \7\) return \7;\s*\}/g;
362
+ workerCode = workerCode.replace(bundledManifestProxyPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar) => `case "moduleLoading":
363
+ case "entryCSSFiles":
364
+ case "entryJSFiles": {
365
+ if (!${workStoreVar}) {
366
+ for (const manifest of ${manifestsVar}.values()) {
367
+ const entry = manifest[${propVar}];
368
+ if (entry !== undefined) {
369
+ return entry;
370
+ }
371
+ }
372
+ return undefined;
373
+ }
374
+ let ${manifestVar} = ${manifestsVar}.get(${workStoreVar}.route);
375
+ if (!${manifestVar}) {
376
+ for (const manifest of ${manifestsVar}.values()) {
377
+ const entry = manifest[${propVar}];
378
+ if (entry !== undefined) {
379
+ return entry;
380
+ }
381
+ }
382
+ return undefined;
383
+ }
384
+ return ${manifestVar}[${propVar}];
385
+ }`);
386
+ workerCode = workerCode.replace(bundledManifestLookupPattern, (_match, workStoreVar, manifestVar, manifestsVar, propVar, idVar, iterManifestVar, iterEntryVar) => `if (${workStoreVar}) {
387
+ let ${manifestVar} = ${manifestsVar}.get(${workStoreVar}.route);
388
+ let ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}[${propVar}][${idVar}];
389
+ if (void 0 === ${iterEntryVar} && ${propVar} === "edgeSSRModuleMapping") ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}.ssrModuleMapping[${idVar}];
390
+ if (void 0 === ${iterEntryVar} && ${propVar} === "edgeRscModuleMapping") ${iterEntryVar} = null == ${manifestVar} ? void 0 : ${manifestVar}.rscModuleMapping[${idVar}];
391
+ if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807 || String(${workStoreVar}.route || "").includes("basic-edge"))) {
392
+ console.error("[creek:bundled-manifest:route]", JSON.stringify({
393
+ route: ${workStoreVar}.route,
394
+ prop: ${propVar},
395
+ id: ${idVar},
396
+ routeHit: !!${manifestVar},
397
+ entryId: ${iterEntryVar} && typeof ${iterEntryVar} === "object" ? ${iterEntryVar}.id ?? (typeof ${iterEntryVar}["*"] === "object" ? ${iterEntryVar}["*"].id : undefined) : undefined,
398
+ }));
399
+ }
400
+ if (void 0 !== ${iterEntryVar}) return ${iterEntryVar};
401
+ }
402
+ let __creekNodeFallback;
403
+ for (let ${iterManifestVar} of ${manifestsVar}.values()) {
404
+ let ${iterEntryVar} = ${iterManifestVar}[${propVar}][${idVar}];
405
+ if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807) && void 0 !== ${iterEntryVar}) {
406
+ console.error("[creek:bundled-manifest:scan-hit]", JSON.stringify({
407
+ prop: ${propVar},
408
+ id: ${idVar},
409
+ entryId: ${iterEntryVar} && typeof ${iterEntryVar} === "object" ? ${iterEntryVar}.id ?? (typeof ${iterEntryVar}["*"] === "object" ? ${iterEntryVar}["*"].id : undefined) : undefined,
410
+ }));
411
+ }
412
+ if (void 0 !== ${iterEntryVar}) return ${iterEntryVar};
413
+ if (void 0 === __creekNodeFallback && ${propVar} === "edgeSSRModuleMapping") __creekNodeFallback = ${iterManifestVar}.ssrModuleMapping[${idVar}];
414
+ if (void 0 === __creekNodeFallback && ${propVar} === "edgeRscModuleMapping") __creekNodeFallback = ${iterManifestVar}.rscModuleMapping[${idVar}];
415
+ }
416
+ if (typeof process !== "undefined" && process.env.CREEK_DEBUG_MANIFESTS === "1" && (${idVar} === "99807" || ${idVar} === 99807) && void 0 !== __creekNodeFallback) {
417
+ console.error("[creek:bundled-manifest:node-fallback]", JSON.stringify({
418
+ prop: ${propVar},
419
+ id: ${idVar},
420
+ entryId: __creekNodeFallback && typeof __creekNodeFallback === "object" ? __creekNodeFallback.id ?? (typeof __creekNodeFallback["*"] === "object" ? __creekNodeFallback["*"].id : undefined) : undefined,
421
+ }));
422
+ }
423
+ if (void 0 !== __creekNodeFallback) return __creekNodeFallback;`);
424
+ // Cloudflare Workers executes the bundled app through the edge runtime path,
425
+ // but many app pages only populate the node/RSC module maps. Keep true edge
426
+ // routes on the edge maps when they exist, otherwise fall back to the node
427
+ // maps so React Server Consumer Manifest lookups can still resolve.
428
+ workerCode = workerCode.replace(/moduleMap: j2, serverModuleMap:/g, "moduleMap: Object.keys(i2 || {}).length ? i2 : j2, serverModuleMap:");
429
+ return workerCode;
430
+ }
431
+ function stripAsyncLocalStorageSnapshotTernaries(workerCode) {
432
+ const snapshotCall = ".snapshot()";
433
+ let out = "";
434
+ let last = 0;
435
+ let searchFrom = 0;
436
+ const isIdent = (ch) => /[A-Za-z0-9_$]/.test(ch);
437
+ const skipWsBack = (i) => {
438
+ while (i > 0 && /\s/.test(workerCode[i - 1]))
439
+ i--;
440
+ return i;
441
+ };
442
+ const skipWsForward = (i) => {
443
+ while (i < workerCode.length && /\s/.test(workerCode[i]))
444
+ i++;
445
+ return i;
446
+ };
447
+ while (true) {
448
+ const snapshotIndex = workerCode.indexOf(snapshotCall, searchFrom);
449
+ if (snapshotIndex === -1)
450
+ break;
451
+ let rightNameStart = snapshotIndex;
452
+ while (rightNameStart > 0 && isIdent(workerCode[rightNameStart - 1])) {
453
+ rightNameStart--;
454
+ }
455
+ const name = workerCode.slice(rightNameStart, snapshotIndex);
456
+ if (!name) {
457
+ searchFrom = snapshotIndex + snapshotCall.length;
458
+ continue;
459
+ }
460
+ let qIndex = skipWsBack(rightNameStart);
461
+ if (workerCode[qIndex - 1] !== "?") {
462
+ searchFrom = snapshotIndex + snapshotCall.length;
463
+ continue;
464
+ }
465
+ qIndex--;
466
+ let leftNameEnd = skipWsBack(qIndex);
467
+ let leftNameStart = leftNameEnd;
468
+ while (leftNameStart > 0 && isIdent(workerCode[leftNameStart - 1])) {
469
+ leftNameStart--;
470
+ }
471
+ if (workerCode.slice(leftNameStart, leftNameEnd) !== name) {
472
+ searchFrom = snapshotIndex + snapshotCall.length;
473
+ continue;
474
+ }
475
+ let colonIndex = skipWsForward(snapshotIndex + snapshotCall.length);
476
+ if (workerCode[colonIndex] !== ":") {
477
+ searchFrom = snapshotIndex + snapshotCall.length;
478
+ continue;
479
+ }
480
+ colonIndex = skipWsForward(colonIndex + 1);
481
+ out += workerCode.slice(last, leftNameStart);
482
+ last = colonIndex;
483
+ searchFrom = colonIndex;
484
+ }
485
+ return out ? out + workerCode.slice(last) : workerCode;
486
+ }
487
+ function patchAsyncLocalStorageBindSnapshot(workerCode) {
488
+ // Next's bindSnapshot() delegates to AsyncLocalStorage.bind() when the
489
+ // runtime provides a native ALS. In workerd that captures request-bound
490
+ // context, and App Page after() callbacks can then stall after response
491
+ // close before the user callback starts. Keep the fake-ALS behavior: return
492
+ // the callback directly.
493
+ workerCode = workerCode.replace(/function\s+bindSnapshot\((\w+)\)\s*\{\s*if\s*\(\s*\w+\s*\)\s*\{\s*return\s+\w+\.bind\(\1\);\s*\}\s*return\s+\w+\.bind\(\1\);\s*\}/g, (_match, fnArg) => `function bindSnapshot(${fnArg}) { return ${fnArg}; }`);
494
+ workerCode = workerCode.replace(/function\s+(\w+)\((\w+)\)\s*\{\s*return\s+\w+\s*\?\s*\w+\.bind\(\2\)\s*:\s*\w+\.bind\(\2\);\s*\}/g, (match, fnName, fnArg, offset, fullCode) => {
495
+ const followingCode = fullCode.slice(offset, offset + 800);
496
+ if (!new RegExp(String.raw `["']bindSnapshot["']\s*,\s*0\s*,\s*${fnName}\b`).test(followingCode)) {
497
+ return match;
498
+ }
499
+ return `function ${fnName}(${fnArg}) { return ${fnArg}; }`;
500
+ });
501
+ workerCode = workerCode.replace(/(server\/app-render\/async-local-storage\.js"[\s\S]{0,1800}?function\s+)(\w+)\((\w+)\)\s*\{\s*return\s+\w+\s*\?\s*\w+\.bind\(\3\)\s*:\s*\w+\.bind\(\3\);\s*\}/g, (_match, prefix, fnName, fnArg) => `${prefix}${fnName}(${fnArg}) { return ${fnArg}; }`);
502
+ return workerCode;
503
+ }
504
+ function patchAfterContextRunCallbacksOnClose(workerCode) {
505
+ // Next's AfterContext normally waits for close by resolving a Promise and
506
+ // then continuing to runCallbacks(). In workerd that continuation can stall
507
+ // for App Page renders even though the close listener fired. Run callbacks
508
+ // directly from the close listener and propagate completion/rejection to
509
+ // waitUntil.
510
+ const marker = "./dist/esm/server/after/after-context.js";
511
+ const pattern = /async\s+runCallbacksOnClose\(\)\s*\{\s*return\s+await\s+new\s+Promise\(\((\w+)\)\s*=>\s*this\.onClose\(\1\)\),\s*this\.runCallbacks\(\);\s*\}/g;
512
+ const replacement = "async runCallbacksOnClose() { return await new Promise((resolve, reject) => this.onClose(() => { Promise.resolve(this.runCallbacks()).then(resolve, reject); })); }";
513
+ let patched = "";
514
+ let last = 0;
515
+ let searchFrom = 0;
516
+ while (true) {
517
+ const markerIndex = workerCode.indexOf(marker, searchFrom);
518
+ if (markerIndex === -1)
519
+ break;
520
+ const windowEnd = Math.min(workerCode.length, markerIndex + 6000);
521
+ const chunk = workerCode.slice(markerIndex, windowEnd);
522
+ const nextChunk = chunk.replace(pattern, replacement);
523
+ if (nextChunk !== chunk) {
524
+ patched += workerCode.slice(last, markerIndex) + nextChunk;
525
+ last = windowEnd;
526
+ }
527
+ searchFrom = windowEnd;
528
+ }
529
+ return patched ? patched + workerCode.slice(last) : workerCode;
530
+ }
531
+ export function patchUseCachePrerenderDanglingPromiseBailout(workerCode) {
532
+ const findHangingPromiseCall = (followingCode, workUnitStoreVar) => {
533
+ const namespaceCallPattern = new RegExp(String.raw `\(0,\s*(\w+)\.makeHangingPromise\)\(\s*${workUnitStoreVar}\.renderSignal\s*,\s*(\w+)\.route`);
534
+ const namespaceCallMatch = namespaceCallPattern.exec(followingCode);
535
+ if (namespaceCallMatch) {
536
+ return {
537
+ callee: `(0, ${namespaceCallMatch[1]}.makeHangingPromise)`,
538
+ workStoreVar: namespaceCallMatch[2],
539
+ };
540
+ }
541
+ const directCallPattern = new RegExp(String.raw `\b(makeHangingPromise)\(\s*${workUnitStoreVar}\.renderSignal\s*,\s*(\w+)\.route`);
542
+ const directCallMatch = directCallPattern.exec(followingCode);
543
+ if (directCallMatch) {
544
+ return {
545
+ callee: directCallMatch[1],
546
+ workStoreVar: directCallMatch[2],
547
+ };
548
+ }
549
+ return undefined;
550
+ };
551
+ const danglingBailout = (serializedKeyVar, workUnitStoreVar, hangingCall) => `if (("prerender" === ${workUnitStoreVar}.type || "prerender-runtime" === ${workUnitStoreVar}.type) && "string" == typeof ${serializedKeyVar}) { let __creekDanglingThenableStart = ${serializedKeyVar}.lastIndexOf("$@"); if (__creekDanglingThenableStart !== -1 && ${serializedKeyVar}.indexOf(":", __creekDanglingThenableStart) === -1) return ${hangingCall.callee}(${workUnitStoreVar}.renderSignal, ${hangingCall.workStoreVar}.route, 'dynamic "use cache"'); }`;
552
+ const patchSerializedKeyMatch = (match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
553
+ if (match.includes("__creekDanglingThenableStart") ||
554
+ trueExprVar !== encodedKeyVar ||
555
+ encodeArgVar !== encodedKeyVar) {
556
+ return match;
557
+ }
558
+ const followingCode = fullCode.slice(offset, offset + 8000);
559
+ const hangingCall = findHangingPromiseCall(followingCode, workUnitStoreVar);
560
+ if (!hangingCall)
561
+ return match;
562
+ return `${declaration}${serializedKeyVar} = ${condition} ? ${trueExprVar} : await ${encodeFnVar}(${encodeArgVar}); ${danglingBailout(serializedKeyVar, workUnitStoreVar, hangingCall)} ${rootParamsDecl} ${rootParamsVar} = ${workUnitStoreVar}.rootParams`;
563
+ };
564
+ const minifiedSerializedKeyPattern = /((?:const|let|var)\s+)(\w+)\s*=\s*((?:(?:"string"|'string')\s*={2,3}\s*typeof\s+(\w+))|(?:typeof\s+(\w+)\s*={2,3}\s*(?:"string"|'string')))\s*\?\s*(\w+)\s*:\s*await\s+(\w+)\(\s*(\w+)\s*\)\s*,\s*(\w+)\s*=\s*(\w+)\.rootParams/g;
565
+ let patchedCode = workerCode.replace(minifiedSerializedKeyPattern, (match, declaration, serializedKeyVar, condition, encodedKeyVarFromStringFirst, encodedKeyVarFromTypeofFirst, trueExprVar, encodeFnVar, encodeArgVar, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
566
+ const encodedKeyVar = encodedKeyVarFromStringFirst ?? encodedKeyVarFromTypeofFirst;
567
+ if (!encodedKeyVar)
568
+ return match;
569
+ return patchSerializedKeyMatch(match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, declaration.trim(), rootParamsVar, workUnitStoreVar, offset, fullCode);
570
+ });
571
+ const readableSerializedKeyPattern = /((?:const|let|var)\s+)(\w+)\s*=\s*((?:(?:"string"|'string')\s*={2,3}\s*typeof\s+(\w+))|(?:typeof\s+(\w+)\s*={2,3}\s*(?:"string"|'string')))\s*\?\s*(?:(?:\/\/[^\r\n]*(?:\r?\n))|\/\*[\s\S]*?\*\/|\s)*?(\w+)\s*:\s*await\s+(\w+)\(\s*(\w+)\s*\)\s*;\s*(?:(?:\/\/[^\r\n]*(?:\r?\n))|\/\*[\s\S]*?\*\/|\s)*?(const|let|var)\s+(\w+)\s*=\s*(\w+)\.rootParams/g;
572
+ patchedCode = patchedCode.replace(readableSerializedKeyPattern, (match, declaration, serializedKeyVar, condition, encodedKeyVarFromStringFirst, encodedKeyVarFromTypeofFirst, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
573
+ const encodedKeyVar = encodedKeyVarFromStringFirst ?? encodedKeyVarFromTypeofFirst;
574
+ if (!encodedKeyVar)
575
+ return match;
576
+ return patchSerializedKeyMatch(match, declaration, serializedKeyVar, condition, encodedKeyVar, trueExprVar, encodeFnVar, encodeArgVar, rootParamsDecl, rootParamsVar, workUnitStoreVar, offset, fullCode);
577
+ });
578
+ return patchedCode;
579
+ }
580
+ export function patchNullFallbackPartialShellBlocking(workerCode) {
581
+ const minifiedPartialFallbackUpgradePattern = /true\s*!==\s*(\w+)\.experimental\.partialFallbacks\s*\|\|\s*\(null\s*==\s*(\w+)\s*\?\s*void\s*0\s*:\s*\2\.fallback\)\s*!==\s*null\s*\|\|\s*(\w+)\s*\|\|\s*(\w+)\s*\|\|\s*!\((\w+)\.length\s*>\s*0\)\s*\|\|\s*\((\w+)\s*=\s*(\w+)\.FallbackMode\.PRERENDER\)/g;
582
+ workerCode = workerCode.replace(minifiedPartialFallbackUpgradePattern, (_match, nextConfigVar, prerenderInfoVar, omittedFallbackParamVar, unresolvedRootParamsVar, remainingParamsVar, fallbackModeVar, fallbackEnumVar) => `true !== ${nextConfigVar}.experimental.partialFallbacks || (null == ${prerenderInfoVar} ? void 0 : ${prerenderInfoVar}.fallback) !== null || ${omittedFallbackParamVar} || ${unresolvedRootParamsVar} || !(${remainingParamsVar}.length > 0) || (${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER)`);
583
+ const minifiedNullFallbackUpgradePattern = /\(null\s*==\s*(\w+)\s*\?\s*void\s*0\s*:\s*\1\.fallback\)\s*!==\s*null\s*\|\|\s*(\w+)\s*\|\|\s*(\w+)\s*\|\|\s*!\((\w+)\.length\s*>\s*0\)\s*\|\|\s*\((\w+)\s*=\s*(\w+)\.FallbackMode\.PRERENDER\)/g;
584
+ workerCode = workerCode.replace(minifiedNullFallbackUpgradePattern, (_match, prerenderInfoVar, omittedFallbackParamVar, unresolvedRootParamsVar, remainingParamsVar, fallbackModeVar, fallbackEnumVar) => `(null == ${prerenderInfoVar} ? void 0 : ${prerenderInfoVar}.fallback) !== null || ${omittedFallbackParamVar} || ${unresolvedRootParamsVar} || !(${remainingParamsVar}.length > 0) || (${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER)`);
585
+ const readableNullFallbackUpgradePattern = /(if\s*\(\s*\(\s*(?:(?:null\s*==\s*\w+)|(?:\w+\s*==\s*null))\s*\?\s*void\s*0\s*:\s*\w+\.fallback\s*\)\s*={2,3}\s*null\s*&&\s*!\w+\s*&&\s*!\w+\s*&&\s*\w+\.length\s*>\s*0\s*\)\s*\{[\s\S]{0,1600}?)(\w+)\s*=\s*([\w$]+)\.FallbackMode\.PRERENDER\s*;/g;
586
+ return workerCode.replace(readableNullFallbackUpgradePattern, (_match, prefix, fallbackModeVar, fallbackEnumVar) => `${prefix}${fallbackModeVar} = ${fallbackEnumVar}.FallbackMode.BLOCKING_STATIC_RENDER;`);
587
+ }
588
+ export function patchAppPageRevalidationPostponedState(workerCode) {
589
+ const readableNeedle = ` let postponed =
590
+ !isOnDemandRevalidate && !isRevalidating && minimalPostponed
591
+ ? minimalPostponed
592
+ : undefined
593
+
594
+ if (
595
+ // If this is a dynamic RSC request or a server action request, we should
596
+ `;
597
+ const readableReplacement = ` let postponed =
598
+ !isOnDemandRevalidate && !isRevalidating && minimalPostponed
599
+ ? minimalPostponed
600
+ : undefined
601
+
602
+ if (
603
+ typeof postponed === 'undefined' &&
604
+ isRevalidating &&
605
+ previousIncrementalCacheEntry?.value?.kind === CachedRouteKind.APP_PAGE &&
606
+ typeof previousIncrementalCacheEntry.value.postponed === 'string'
607
+ ) {
608
+ postponed = previousIncrementalCacheEntry.value.postponed
609
+ }
610
+
611
+ if (
612
+ // If this is a dynamic RSC request or a server action request, we should
613
+ `;
614
+ workerCode = workerCode.replace(readableNeedle, readableReplacement);
615
+ const minifiedPattern = /(__name\(async\s*\(\{\s*hasResolved:\s*(\w+),\s*previousCacheEntry:\s*(\w+),\s*isRevalidating:\s*(\w+),\s*span:\s*(\w+),\s*forceStaticRender:\s*(\w+)\s*=\s*false\s*\}\)\s*=>\s*\{[\s\S]{0,6000}?)(let\s+(\w+)\s*=\s*(\w+)\s*\|\|\s*\4\s*\|\|\s*!(\w+)\s*\?\s*void 0\s*:\s*(\w+)\s*;)/g;
616
+ return workerCode.replace(minifiedPattern, (match, prefix, _hasResolvedVar, previousCacheEntryVar, isRevalidatingVar, _spanVar, _forceStaticRenderVar, declaration, postponedVar, _onDemandRevalidateVar, minimalPostponedVar, fallbackPostponedVar) => {
617
+ if (match.includes("__creekRevalidationPostponedState"))
618
+ return match;
619
+ if (minimalPostponedVar !== fallbackPostponedVar)
620
+ return match;
621
+ return `${prefix}${declaration}/* __creekRevalidationPostponedState */if(void 0===${postponedVar}&&${isRevalidatingVar}&&${previousCacheEntryVar}&&${previousCacheEntryVar}.value&&${previousCacheEntryVar}.value.kind==="APP_PAGE"&&"string"==typeof ${previousCacheEntryVar}.value.postponed)${postponedVar}=${previousCacheEntryVar}.value.postponed;`;
622
+ });
623
+ }
139
624
  export async function bundleForWorkers(opts) {
140
625
  // Patch Turbopack runtime BEFORE wrangler bundles.
141
626
  // Turbopack's R.c() dynamically loads chunks from the filesystem.
142
627
  // CF Workers has no filesystem, so we replace R.c() with a switch
143
628
  // statement that maps chunk paths to static require() calls.
144
629
  await patchTurbopackRuntime(opts.distDir);
630
+ await patchNodeExternalRequireConditions(opts.distDir);
631
+ await patchAppPageManifestSingletons(opts.distDir);
632
+ const ssrLazyRequireAliases = await collectSsrLazyRequireAliases(opts.distDir);
145
633
  // Write the generated worker entry
146
634
  const entryPath = path.join(opts.outputDir, "__entry.mjs");
147
635
  await fs.writeFile(entryPath, opts.workerSource);
148
636
  if (process.env.CREEK_DEBUG) {
149
637
  await fs.writeFile(path.join(opts.outputDir, "__entry_debug.mjs"), opts.workerSource);
150
638
  }
639
+ // Copy WASM files alongside the bundle BEFORE wrangler runs. wrangler's
640
+ // bundler needs the files resolvable from the entry `import __wasm_0 from
641
+ // "./wasm_<hex>.wasm"` to apply the CompiledWasm rule — copying later
642
+ // (after the bundle step) is too late.
643
+ for (const [name, absPath] of opts.wasmFiles) {
644
+ const destName = name.endsWith(".wasm") ? name : name + ".wasm";
645
+ const destPath = path.join(opts.outputDir, destName);
646
+ await fs.copyFile(absPath, destPath);
647
+ }
151
648
  // Resolve adapter paths
152
649
  const adapterDir = path.dirname(path.dirname(new URL(import.meta.url).pathname));
153
650
  // Generate wrangler config for the bundle step
@@ -164,34 +661,67 @@ export async function bundleForWorkers(opts) {
164
661
  },
165
662
  // Mark optional/unavailable deps as external to prevent build errors.
166
663
  // These are caught at runtime and handled gracefully.
664
+ //
665
+ // `ssrLazyRequireAliases` is spread FIRST so static entries below take
666
+ // precedence if they collide — our shims (fs, vm, critters, etc.)
667
+ // always win over a happenstance SSR lazy-require of the same name.
167
668
  alias: {
669
+ ...ssrLazyRequireAliases,
168
670
  "@opentelemetry/api": path.join(adapterDir, "src", "shims", "opentelemetry.js"),
169
671
  // fs shim — intercept both bare and node: prefixed imports.
170
672
  // Turbopack runtime uses require("fs") which wrangler must redirect
171
673
  // to our shim that reads from embedded __MANIFESTS.
172
674
  "fs": path.join(adapterDir, "src", "shims", "fs.js"),
173
675
  "node:fs": path.join(adapterDir, "src", "shims", "fs.js"),
676
+ "fs/promises": path.join(adapterDir, "src", "shims", "fs.js"),
677
+ "node:fs/promises": path.join(adapterDir, "src", "shims", "fs.js"),
174
678
  "vm": path.join(adapterDir, "src", "shims", "vm.js"),
175
679
  "node:vm": path.join(adapterDir, "src", "shims", "vm.js"),
176
680
  // critters is bundled by Next.js for CSS inlining — not needed on Workers.
177
681
  "critters": path.join(adapterDir, "src", "shims", "critters.js"),
178
- // sharp has native .node bindings that workerd can't load.
682
+ // sharp has native .node bindings that workerd can't load. Without this
683
+ // alias, wrangler pulls in ~1MB of sharp's JS wrapper and the module
684
+ // ends up non-callable at runtime — \`@vercel/og\`'s node path then
685
+ // throws \`sharp is not a function\`. Aliasing to a shim whose default
686
+ // is undefined makes \`@vercel/og\` fall back to its resvg.wasm path.
179
687
  "sharp": path.join(adapterDir, "src", "shims", "sharp.js"),
180
- // Dead-branch dynamic imports.
688
+ // Some packages intentionally leave unreachable dynamic imports such as
689
+ // `if (Math.random() < 0) import("fail")` in their ESM entry. Turbopack
690
+ // externalizes the package and never resolves that branch, but wrangler
691
+ // follows it once we preload the concrete ESM file. Stub it so the dead
692
+ // branch remains harmless.
181
693
  "fail": path.join(adapterDir, "src", "shims", "empty.js"),
182
- // track-module-loading: per-request AsyncLocalStorage to avoid IoContext leak.
694
+ // Replace Next's track-module-loading.{instance,external} with a
695
+ // per-request AsyncLocalStorage version. The original keeps a
696
+ // module-level CacheSignal whose internal setImmediate closure
697
+ // leaks IoContext across requests on workerd — second-and-later
698
+ // requests throw "Cannot perform I/O on behalf of a different
699
+ // request" when CacheSignal.pendingTimeoutCleanup fires
700
+ // clearImmediate on an Immediate from the first request. Repros
701
+ // on any route that does dynamic imports during render (notably
702
+ // \`new ImageResponse(...)\` — every \`@vercel/og\` call triggers
703
+ // trackPendingImport). We alias \`.external\` as well because
704
+ // call sites import from that module (the internal relative
705
+ // \`./track-module-loading.instance\` import never passes through
706
+ // esbuild's bare-specifier alias map). See
707
+ // src/shims/track-module-loading.js.
183
708
  "next/dist/server/app-render/module-loading/track-module-loading.external": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
184
709
  "next/dist/server/app-render/module-loading/track-module-loading.external.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
185
710
  "next/dist/server/app-render/module-loading/track-module-loading.instance": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
186
711
  "next/dist/server/app-render/module-loading/track-module-loading.instance.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
187
- // fast-set-immediate: workerd's frozen ESM namespace + scheduling order.
712
+ // Next's fast-set-immediate module patches node:timers/promises, whose
713
+ // ESM namespace is frozen in Workers. More importantly, workerd can run
714
+ // the next setTimeout(0) stage before a setImmediate scheduled by the
715
+ // previous cache-components stage. The shim keeps native setImmediate
716
+ // except during Next's explicit fast-immediate capture window.
188
717
  "next/dist/server/node-environment-extensions/fast-set-immediate.external": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
189
718
  "next/dist/server/node-environment-extensions/fast-set-immediate.external.js": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
190
- // CF Workers does NOT provide node:http / node:https even with
191
- // nodejs_compat. TCP-server-shaped APIs have no Workers equivalent.
192
- // Our shim provides IncomingMessage + ServerResponse stubs.
193
- "http": path.join(adapterDir, "src", "shims", "http.js"),
194
- "node:http": path.join(adapterDir, "src", "shims", "http.js"),
719
+ // NOTE: load-manifest shim exists in src/shims/ but is handled by the
720
+ // fs shim (manifest loading), so no alias needed.
721
+ // Keep http/node:http on workerd's nodejs_compat implementation. The
722
+ // worker entry extends the built-in IncomingMessage and ServerResponse;
723
+ // aliasing those imports to our minimal shim makes POST body flushing
724
+ // re-buffer chunks into itself and can OOM server-action requests.
195
725
  "https": path.join(adapterDir, "src", "shims", "http.js"),
196
726
  "node:https": path.join(adapterDir, "src", "shims", "http.js"),
197
727
  // net: Socket class needed by Next.js http bridge.
@@ -213,18 +743,39 @@ export async function bundleForWorkers(opts) {
213
743
  // Bundle with wrangler --dry-run
214
744
  // Wrangler internally uses esbuild but with Turbopack-aware resolution
215
745
  // and proper CJS/ESM interop for CF Workers.
216
- // Ensure @next/routing is resolvable from the project directory.
217
- // It's a dependency of the adapter, not the user's project.
218
- // Symlink it into the project's node_modules if missing.
746
+ // Ensure @next/routing is resolvable from the project directory. It's a
747
+ // dependency of the adapter, not the user's project, so wrangler can't
748
+ // find it when run from the project's cwd. Resolve via \`createRequire\`
749
+ // rather than guessing \`adapterDir/node_modules/@next/routing\` — pnpm's
750
+ // virtual-store layout means that path often doesn't exist (the real
751
+ // install lives under \`node_modules/.pnpm/@next+routing@X/\`), and the
752
+ // guess only works for the link-protocol install of the adapter.
219
753
  const projectNodeModules = path.join(path.dirname(opts.distDir), "node_modules");
220
754
  const routingDest = path.join(projectNodeModules, "@next", "routing");
221
- const routingSrc = path.join(adapterDir, "node_modules", "@next", "routing");
755
+ const adapterRequire = createRequire(path.join(adapterDir, "package.json"));
756
+ let routingSrc;
757
+ try {
758
+ routingSrc = path.dirname(adapterRequire.resolve("@next/routing/package.json"));
759
+ }
760
+ catch {
761
+ // Fallback to the legacy guess so a classic link-install still works.
762
+ routingSrc = path.join(adapterDir, "node_modules", "@next", "routing");
763
+ }
222
764
  try {
223
765
  await fs.access(routingDest);
224
766
  }
225
767
  catch {
226
768
  await fs.mkdir(path.join(projectNodeModules, "@next"), { recursive: true });
227
- await fs.symlink(routingSrc, routingDest, "junction");
769
+ try {
770
+ await fs.symlink(routingSrc, routingDest, "junction");
771
+ }
772
+ catch (err) {
773
+ // Racy repeat runs (same project dir rebuilt back-to-back) can leave a
774
+ // dangling symlink that \`access\` reports as missing while \`symlink\`
775
+ // still refuses to overwrite.
776
+ if (err?.code !== "EEXIST")
777
+ throw err;
778
+ }
228
779
  }
229
780
  const bundleDir = path.join(opts.outputDir, "__bundle");
230
781
  // Resolve wrangler binary from the adapter's own node_modules
@@ -280,13 +831,216 @@ export async function bundleForWorkers(opts) {
280
831
  // "Dynamic require of ... is not supported" without those codes.
281
832
  // Patch the catch to also handle "is not supported" errors.
282
833
  workerCode = workerCode.replace(/err\.code !== "ENOENT" && err\.code !== "MODULE_NOT_FOUND" && err\.code !== "ERR_MODULE_NOT_FOUND"/g, 'err.code !== "ENOENT" && err.code !== "MODULE_NOT_FOUND" && err.code !== "ERR_MODULE_NOT_FOUND" && !err.message?.includes("is not supported")');
834
+ // Some libraries hide builtin requires from the bundler, e.g.
835
+ // @typescript/vfs constructs `require("path")` via String.fromCharCode.
836
+ // Wrangler can't statically rewrite that, so esbuild's __require helper
837
+ // throws at runtime even though the node:path wrapper is already present
838
+ // elsewhere in the bundle. Route those builtin dynamic requires back to
839
+ // wrangler's generated require_path() module when available.
840
+ workerCode = workerCode.replace(/throw Error\('Dynamic require of "' \+ x \+ '" is not supported'\);/, 'if ((x === "path" || x === "node:path") && typeof require_path === "function") return require_path();\n throw Error(\'Dynamic require of "\' + x + \'" is not supported\');');
841
+ workerCode = workerCode.replace(/(throw Error\('Dynamic require of "' \+ x \+ '" is not supported'\);\n\}\);)/, '$1\n__require.resolve = (id) => id === "typescript" ? "node_modules/typescript/lib/typescript.js" : id;');
842
+ // `next/dist/server/node-environment.js` imports
843
+ // `./node-environment-extensions/fast-set-immediate.external` by relative
844
+ // path, so Wrangler's bare-specifier alias cannot intercept it. If the
845
+ // original module installs first, our scoped shim stores Next's patched
846
+ // timer functions as its "native" originals and the workerd ordering fix
847
+ // becomes re-entrant. Route that side-effect import to the aliased shim
848
+ // module that Wrangler emitted for Turbopack's external import.
849
+ workerCode = workerCode.replace(/\brequire_fast_set_immediate_external\(\);/g, 'typeof init_fast_set_immediate === "function" ? init_fast_set_immediate() : require_fast_set_immediate_external();');
850
+ // Next.js's \`getInstrumentationModule\` does
851
+ // \`await __require(path.join(projectDir, distDir, "server",
852
+ // \`\${INSTRUMENTATION_HOOK_FILENAME}.js\`))\`. workerd can't resolve
853
+ // dynamic-require paths — the call rejects, the catch above swallows it,
854
+ // and \`instrumentation.register()\` is never invoked. Worker-entry
855
+ // static-imports the user file onto \`globalThis.__CREEK_INSTRUMENTATION\`
856
+ // when present, so prefer that over \`__require\` here. Falls through
857
+ // to the original call when no user instrumentation is registered so
858
+ // the \`module.exports = {}\` placeholder path still works.
859
+ workerCode = workerCode.replace(/(cachedInstrumentationModule\s*=\s*\(0,\s*_interopdefault\.interopDefault\)\s*\()\s*await __require\(/g, "$1globalThis.__CREEK_INSTRUMENTATION || await __require(");
860
+ workerCode = patchBundledManifestSingleton(workerCode);
861
+ // Next's Pages Router error/404 path may dynamically import the configured
862
+ // cacheHandler via `file:/.solcreek-cache-handler.mjs`. workerd cannot load
863
+ // arbitrary file: modules at runtime, and the worker already ships the
864
+ // target-specific CreekCacheHandler inline. Redirect only that generated
865
+ // cache-handler import to the inline class.
866
+ workerCode = workerCode.replace(/__name\(\((\w+)\)\s*=>\s*import\(\1\)\.then\(\((\w+)\)\s*=>\s*\2\.default\s*\|\|\s*\2\),\s*"([^"]+)"\)/g, (_match, specifierVar, moduleVar, functionName) => `__name((${specifierVar}) => String(${specifierVar}).includes(".solcreek-cache-handler.mjs") ` +
867
+ `? Promise.resolve(globalThis.__CREEK_CACHE_HANDLER) ` +
868
+ `: import(${specifierVar}).then((${moduleVar}) => ${moduleVar}.default || ${moduleVar}), ${JSON.stringify(functionName)})`);
869
+ // depd (via raw-body) uses `eval("(function ("+args+") {...})")` to build a
870
+ // deprecation-wrapping thunk. workerd + CF Workers block code generation
871
+ // from strings ("Code generation from strings disallowed for this context"),
872
+ // so any module that pulls in raw-body — notably the Pages Router API
873
+ // body parser — throws at module load time, and the outer try/catch in
874
+ // parse-body.ts surfaces it to the browser as `400 Invalid body`.
875
+ //
876
+ // Replace the minified eval expression with a direct function literal
877
+ // that preserves the runtime semantics (log + call). We lose the
878
+ // `function.length` preservation the eval form achieved, but nothing
879
+ // in the raw-body → parse-body call chain reads it.
880
+ //
881
+ // Fixes middleware-redirects "should redirect to api route with locale"
882
+ // (and the /fr variant) — both fail because their navigation ends at
883
+ // an API route whose parse-body path can't load raw-body.
884
+ workerCode = workerCode.replace(/var\s+(\w+)\s*=\s*eval\("\(function \(".*?return\s+\1\s*;?\s*\}/s, "return function(){log.call(deprecate,message,site);return fn.apply(this,arguments)}}");
885
+ // Route \`externalImport(id)\` through \`globalThis.__CREEK_EXT_MODS\` so
886
+ // we can serve bundled-but-externalized-by-Turbopack modules from
887
+ // our worker-entry static imports. Turbopack emits chunks like
888
+ // \`[externals]_next_dist_compiled_@vercel_og_index_node_...\` that do
889
+ // \`await e.y("next/dist/compiled/@vercel/og/index.node.js")\`; on
890
+ // workerd that \`await import(id)\` path throws "No such module" and
891
+ // the handler 500s. When our entry registers the module in
892
+ // \`__CREEK_EXT_MODS\`, the patched \`externalImport\` returns it
893
+ // directly without going through workerd's external loader.
894
+ // Fixes og-api \`/og-node\` (node runtime) +
895
+ // use-cache-metadata-route-handler opengraph/icon tests.
896
+ workerCode = workerCode.replace(/async function externalImport\((\w+)\)\s*\{\s*let\s+raw;\s*try\s*\{\s*raw\s*=\s*await import\(\1\);/g, (match, idVar) => `async function externalImport(${idVar}) {\n` +
897
+ ` let raw;\n` +
898
+ ` { const __loaders = globalThis.__CREEK_EXT_LOADERS; if (__loaders && __loaders[${idVar}]) {\n` +
899
+ ` const __cached = globalThis.__CREEK_EXT_MODS = globalThis.__CREEK_EXT_MODS || {};\n` +
900
+ ` if (${idVar} in __cached) { raw = __cached[${idVar}]; }\n` +
901
+ ` else { try { raw = await __loaders[${idVar}](); __cached[${idVar}] = raw; } catch (err) { throw new Error(\`Failed to load external module \${${idVar}}: \${err}\`); } }\n` +
902
+ ` if (raw && raw.__esModule && raw.default && "default" in raw.default) { return interopEsm(raw.default, createNS(raw), true); }\n` +
903
+ ` return raw;\n` +
904
+ ` } }\n` +
905
+ ` try {\n` +
906
+ ` raw = await import(${idVar});`);
907
+ // \`@vercel/og/index.node.js\` evaluates at module load:
908
+ //
909
+ // var fontData = fs.readFileSync(fileURLToPath(new URL("./Geist-Regular.ttf", import.meta.url)));
910
+ // var resvg_wasm = fs.readFileSync(fileURLToPath(new URL("./resvg.wasm", import.meta.url)));
911
+ //
912
+ // workerd rejects \`new URL("./X", import.meta.url)\` with
913
+ // "Invalid URL string" in the bundled-worker context, so evaluation
914
+ // aborts before any request hits the route. Rewrite these two calls
915
+ // to pass literal paths into fs.readFileSync directly — our fs shim
916
+ // has a basename fallback for .wasm/.ttf, so the embedded bundled
917
+ // bytes resolve regardless of path.
918
+ workerCode = workerCode.replace(/fileURLToPath\(new URL\(("\.\/[^"]+\.(?:wasm|ttf|otf|woff2?|png|jpg|jpeg|gif|webp|svg|ico)")\s*,\s*import\.meta\.url\)\)/g, (_match, filename) => filename.replace(/^"\.\//, '"'));
919
+ // Strip `AsyncLocalStorage.snapshot()` bindings in Next's
920
+ // `server/app-render/async-local-storage.js` and its Turbopack-inlined
921
+ // variants. On workerd, `ALS.snapshot()` captures the CURRENT IoContext
922
+ // and invoking the returned function from a later request throws
923
+ // "Cannot perform I/O on behalf of a different request". Next uses
924
+ // `createSnapshot()` / `runInCleanSnapshot: ALS.snapshot()` for cache-
925
+ // invalidation + edge-action callback propagation — both paths cross
926
+ // request boundaries on workerd. Replace the snapshot binding with a
927
+ // direct passthrough, matching @opennextjs/cloudflare's `patchUseCacheIO`
928
+ // (their comment: "TODO: Find a better fix for this issue.").
929
+ //
930
+ // Four forms exist post-bundling:
931
+ // 1. `function createSnapshot() { if (X) return X.snapshot(); return function(fn,...args){...}; }`
932
+ // — clean esm form from next/dist/esm (one copy embedded by wrangler).
933
+ // 2. `function <name>() { return X ? X.snapshot() : function(a,...b){ return a(...b); }; }`
934
+ // — Turbopack's minified module-level shim (edge-side copies).
935
+ // 3. `a.s(["bindSnapshot", 0, q, "createSnapshot", 0, function(){ return p ? p.snapshot() : ...; }])`
936
+ // — Turbopack's export-binding form in the same shim.
937
+ // 4. `runInCleanSnapshot: X ? X.snapshot() : function(a,...b){ return a(...b); }`
938
+ // — inlined call-site in the bundled work-store constructor.
939
+ // A single regex `(\w+) ? \1.snapshot() : ` stripped from the minified
940
+ // forms covers 2/3/4; form 1 uses an if-guard so handle it separately.
941
+ workerCode = workerCode.replace(/if\s*\(\s*(\w+)\s*\)\s*\{\s*return\s+\1\.snapshot\(\);\s*\}/g, "// Ignored snapshot");
942
+ workerCode = stripAsyncLocalStorageSnapshotTernaries(workerCode);
943
+ workerCode = patchAsyncLocalStorageBindSnapshot(workerCode);
944
+ workerCode = patchAfterContextRunCallbacksOnClose(workerCode);
945
+ // `use cache` arguments that contain an unresolved React thenable marker
946
+ // (`$@`) during a prerender are request-bound values. Next's dynamic
947
+ // access instrumentation usually catches this before cache fill, but an
948
+ // async params transform can hide the access until after serialization.
949
+ // Node eventually trips the 50s cache-fill timeout; under workerd that
950
+ // blocks the whole response. Treat the cache function as a dynamic hole
951
+ // immediately so the PPR fallback shell can resume.
952
+ workerCode = patchUseCachePrerenderDanglingPromiseBailout(workerCode);
953
+ // Null-fallback partial routes don't have a concrete fallback artifact
954
+ // on disk; under workerd the follow-up path that tries to specialize a
955
+ // generic shell on the first request can surface as a static-generation
956
+ // bailout instead of completing the request. Keep these routes on the
957
+ // blocking path and let the concrete request render/cache directly.
958
+ workerCode = patchNullFallbackPartialShellBlocking(workerCode);
959
+ // During App PPR/cacheComponents background revalidation, Next 16 drops
960
+ // the existing APP_PAGE postponed state because it only resumes dynamic
961
+ // RSC and server-action requests. In Workers minimal output we need that
962
+ // postponed state to rerender the PPR shell; otherwise the revalidation
963
+ // becomes a full static render and hits dynamic APIs like connection().
964
+ workerCode = patchAppPageRevalidationPostponedState(workerCode);
965
+ // Unify Next's `*AsyncStorageInstance` singletons across Turbopack
966
+ // chunks via a `globalThis` key. Turbopack emits the `work-unit-async-
967
+ // storage-instance.js` / `work-async-storage-instance.js` / etc.
968
+ // factories INLINE into every chunk that references them (see the
969
+ // `'turbopack-transition': 'next-shared'` import attribute in the
970
+ // next-shared layer); each chunk evaluates its own
971
+ // `createAsyncLocalStorage()` call and gets a fresh ALS instance.
972
+ // On the edge-runtime server-action path this fragments the store:
973
+ // the action handler runs `workUnitAsyncStorage.run(d, fn)` inside
974
+ // chunk A's ALS, the inner `headers()` call reads from chunk B's
975
+ // ALS, finds no store, and throws "headers was called outside a
976
+ // request scope". Node action path survives because its caller +
977
+ // callee happen to resolve to the same chunk's copy.
978
+ //
979
+ // Fix: rewrite the module factory bodies to key the ALS on the
980
+ // exported instance name (`workUnitAsyncStorageInstance`, etc.)
981
+ // under a single `globalThis.__CREEK_ALS` bag, so all chunks share
982
+ // one instance per logical store. Name-keyed is narrower than
983
+ // "dedup every `new AsyncLocalStorage()`" — we don't accidentally
984
+ // merge unrelated per-store ALSes (tracing requestStorage,
985
+ // react-server-dom temporaryReferences, etc.).
986
+ //
987
+ // Target pattern (both `a.i(N)` and `a.r(N)` create-variants
988
+ // exist):
989
+ // let <v> = (0, a.i(43291).createAsyncLocalStorage)();
990
+ // …short gap…
991
+ // a.s(["<Name>AsyncStorageInstance", 0, <v>], …)
992
+ // Replacement keeps the original create call so the per-isolate
993
+ // fallback works if globalThis isn't carried through an unusual
994
+ // loader; the `??=` idempotently promotes the first copy into the
995
+ // shared bag.
996
+ workerCode = workerCode.replace(/let\s+(\w+)\s*=\s*(\(0,\s*a\.[ir]\(\d+\)\.createAsyncLocalStorage\)\(\));([\s\S]{0,400}?a\.s\(\["(\w+AsyncStorageInstance)",\s*0,\s*\1\])/g, (_match, varName, createCall, tail, storeName) => `let ${varName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall});${tail}`);
997
+ // Same dedup for the CJS-wrapped variants esbuild produces when it
998
+ // bundles Next's non-Turbopack ESM copy. Two observed shapes:
999
+ // a. `var <Name>AsyncStorageInstance = (0, _als.createAsyncLocalStorage)();`
1000
+ // — top-level from `work-unit-async-storage-instance.js` etc.
1001
+ // b. `Object.defineProperty(c, "<Name>AsyncStorageInstance", {…get…return <v>…}); let <v> = (0, a.r(N).createAsyncLocalStorage)();`
1002
+ // — Turbopack-emitted CJS compiled form (edge chunks).
1003
+ workerCode = workerCode.replace(/var\s+(\w+AsyncStorageInstance)\s*=\s*(\(0,\s*\w+\.createAsyncLocalStorage\)\(\));/g, (_match, storeName, createCall) => `var ${storeName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall});`);
1004
+ workerCode = workerCode.replace(/(Object\.defineProperty\(c,\s*"(\w+AsyncStorageInstance)",\s*\{[^}]*get:\s*(?:\/\*[^*]*\*\/\s*)?(?:__name\()?function\(\)\s*\{\s*return (\w+);[^}]*\}[\s\S]*?\}\);)\s*let\s+\3\s*=\s*(\(0,\s*a\.[ir]\(\d+\)\.createAsyncLocalStorage\)\(\))/g, (_match, prefix, storeName, varName, createCall) => `${prefix}\nlet ${varName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall})`);
1005
+ // Turbopack's node.js runtime implements top-level-await by assigning
1006
+ // \`module.exports = <Promise>\`. esbuild's \`__toESM\` wraps imports with
1007
+ // \`__create(__getProtoOf(mod))\`, and for a Promise that yields a plain
1008
+ // object whose \`__proto__\` is \`Promise.prototype\`. Awaiting that object
1009
+ // invokes \`Promise.prototype.then\` with a non-Promise receiver — workerd
1010
+ // rejects it as "incompatible receiver" and the route 500s. Detect a
1011
+ // Promise input and return it unchanged so await resolves the real
1012
+ // async-module promise instead. Fixes \`metadata-edge\` and
1013
+ // \`metadata-dynamic-routes-async-deps\` \`opengraph-image\` routes.
1014
+ workerCode = workerCode.replace(/var __toESM = \(mod, isNodeMode, target\) => \(target = mod != null \? __create\(__getProtoOf\(mod\)\) : \{\}, __copyProps\(\s*(?:\/\/[^\n]*\n\s*)*isNodeMode \|\| !mod \|\| !mod\.__esModule \? __defProp\(target, "default", \{ value: mod, enumerable: true \}\) : target,\s*mod\s*\)\);/, `var __toESM = (mod, isNodeMode, target) => {
1015
+ if (mod != null && typeof mod === "object" && typeof mod.then === "function" && __getProtoOf(mod) === Promise.prototype) {
1016
+ return mod;
1017
+ }
1018
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
1019
+ return __copyProps(
1020
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
1021
+ mod
1022
+ );
1023
+ };`);
283
1024
  await fs.writeFile(workerPath, workerCode);
284
1025
  }
285
1026
  catch { }
286
- // Copy WASM files alongside the bundle
287
- for (const [name, absPath] of opts.wasmFiles) {
288
- await fs.copyFile(absPath, path.join(opts.outputDir, name));
289
- }
1027
+ // Emit wrangler.toml that covers both local dev (workerd via wrangler dev)
1028
+ // and \`wrangler deploy\`. Dev and prod MUST share the same config so
1029
+ // deployment behavior stays predictable — that's what the adapter is
1030
+ // expected to guarantee.
1031
+ //
1032
+ // The file lives alongside worker.js in \`.creek/adapter-output/server/\`.
1033
+ // Users can override specific values by creating a \`wrangler.toml\`
1034
+ // at their project root — the adapter reads that at build time and
1035
+ // merges over our defaults (support added in a follow-up TODO).
1036
+ await emitWranglerConfig({
1037
+ outputDir: opts.outputDir,
1038
+ assetsRelPath: "../assets", // server/ → assets/
1039
+ // Same normalization as the copy loop above — ensure every wasm
1040
+ // filename declared in the rules ends with \`.wasm\` so wrangler's
1041
+ // CompiledWasm rule discovers the file we actually wrote to disk.
1042
+ wasmFilenames: [...opts.wasmFiles.keys()].map((n) => n.endsWith(".wasm") ? n : n + ".wasm"),
1043
+ });
290
1044
  // Clean up temp files
291
1045
  await fs.rm(entryPath, { force: true });
292
1046
  await fs.rm(configPath, { force: true });
@@ -295,4 +1049,96 @@ export async function bundleForWorkers(opts) {
295
1049
  const files = await fs.readdir(opts.outputDir);
296
1050
  return files.filter(f => !f.startsWith("__"));
297
1051
  }
1052
+ /**
1053
+ * Write a wrangler.toml next to worker.js so the same config drives both
1054
+ * \`wrangler dev\` (local dev / test harness) and \`wrangler deploy\` (prod).
1055
+ *
1056
+ * Contents:
1057
+ * - \`name\`, \`main\`, \`compatibility_date\`, \`compatibility_flags\` — runtime
1058
+ * - \`[assets]\` — binds the assets directory with \`run_worker_first\` so our
1059
+ * worker handles routing before workerd's static asset shortcut
1060
+ * - \`[[durable_objects.bindings]]\` + \`[[migrations]]\` — the three DO
1061
+ * classes the worker entry declares (DOQueueHandler, DOShardedTagCache,
1062
+ * BucketCachePurge)
1063
+ * - \`[[rules]] type = "CompiledWasm"\` — declares wasm siblings (next/og
1064
+ * yoga/resvg, sharp, etc.) as module imports for workerd's loader
1065
+ */
1066
+ async function emitWranglerConfig(opts) {
1067
+ const { outputDir, assetsRelPath, wasmFilenames } = opts;
1068
+ // Turbopack + wrangler both emit wasm siblings (hashed \`wasm_<xxh3>\`
1069
+ // from us, \`<sha1>-yoga.wasm\` / \`<sha1>-resvg.wasm\` from wrangler's
1070
+ // own esbuild pass when the app imports next/og). A wildcard glob
1071
+ // covers every sibling regardless of who emitted it. Listing specific
1072
+ // filenames fails because wrangler normalises import paths with a
1073
+ // leading \`./\` that literal-name globs don't match — the built-in
1074
+ // \`**/*.wasm\` rule then fires and aborts the build with
1075
+ // "ignored because a previous rule ... was not marked as fallthrough".
1076
+ let hasWasm = wasmFilenames.length > 0;
1077
+ if (!hasWasm) {
1078
+ try {
1079
+ const entries = await fs.readdir(outputDir);
1080
+ hasWasm = entries.some((e) => e.endsWith(".wasm"));
1081
+ }
1082
+ catch { }
1083
+ }
1084
+ const wasmRule = hasWasm
1085
+ ? [
1086
+ "",
1087
+ "# Declare every wasm sibling as a CompiledWasm module so \`import",
1088
+ "# foo from \"./<hash>.wasm\"\` in the bundle resolves at runtime.",
1089
+ "# Without this, Turbopack's runtime registry returns undefined and",
1090
+ "# throws \"dynamically loading WebAssembly is not supported\".",
1091
+ "[[rules]]",
1092
+ `globs = ["**/*.wasm"]`,
1093
+ `type = "CompiledWasm"`,
1094
+ `fallthrough = false`,
1095
+ ].join("\n")
1096
+ : "";
1097
+ const toml = `# Generated by @solcreek/adapter-creek. Hand edits will be overwritten on
1098
+ # the next \`next build\`. To extend (add KV, queues, env vars, etc.), create
1099
+ # a \`wrangler.toml\` at your project root — the adapter merges it on top.
1100
+
1101
+ name = "creek"
1102
+ main = "worker.js"
1103
+ compatibility_date = "2026-03-23"
1104
+ compatibility_flags = ["nodejs_compat"]
1105
+
1106
+ # Static assets from Next.js's \`.next/static\` + \`public/\` live alongside
1107
+ # this server dir under \`../assets/\`. \`run_worker_first = true\` tells
1108
+ # workerd to invoke our worker BEFORE serving static files — the adapter
1109
+ # handles middleware, routing, and cache headers before any asset shortcut.
1110
+ [assets]
1111
+ directory = "${assetsRelPath}"
1112
+ binding = "ASSETS"
1113
+ run_worker_first = true
1114
+
1115
+ # Durable Object classes declared by the adapter's worker-entry. The
1116
+ # binding \`name\` (what runtime code reads via \`env.<NAME>\`) must match
1117
+ # what Creek's control plane injects in production — see
1118
+ # \`packages/control-plane/src/modules/deployments/deploy.ts:129-140\` in
1119
+ # the Creek repo. Do NOT rename these without coordinating with Creek.
1120
+ #
1121
+ # \`class_name\` is our internal export name from worker-entry.ts.
1122
+ # \`new_sqlite_classes\` (not \`new_classes\`) selects SQLite-backed DO
1123
+ # storage — DOShardedTagCache and BucketCachePurge both use
1124
+ # \`ctx.storage.sql\`. DOQueueHandler is currently a placeholder but
1125
+ # declared under SQLite too so future queue persistence has no migration
1126
+ # cost.
1127
+ [[durable_objects.bindings]]
1128
+ name = "NEXT_CACHE_DO_QUEUE"
1129
+ class_name = "DOQueueHandler"
1130
+ [[durable_objects.bindings]]
1131
+ name = "NEXT_TAG_CACHE_DO_SHARDED"
1132
+ class_name = "DOShardedTagCache"
1133
+ [[durable_objects.bindings]]
1134
+ name = "NEXT_CACHE_DO_BUCKET_PURGE"
1135
+ class_name = "BucketCachePurge"
1136
+
1137
+ [[migrations]]
1138
+ tag = "v1"
1139
+ new_sqlite_classes = ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
1140
+ ${wasmRule}
1141
+ `;
1142
+ await fs.writeFile(path.join(outputDir, "wrangler.toml"), toml);
1143
+ }
298
1144
  //# sourceMappingURL=bundler.js.map