@solcreek/adapter-creek 0.1.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.
@@ -0,0 +1,991 @@
1
+ /**
2
+ * Bundler for Cloudflare Workers.
3
+ *
4
+ * Uses wrangler (which internally uses esbuild + Turbopack-aware resolution)
5
+ * to bundle the generated worker entry into CF Workers-compatible output.
6
+ *
7
+ * This works with both webpack and Turbopack output — wrangler handles
8
+ * the custom chunk format that plain esbuild cannot follow.
9
+ */
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { execSync } from "node:child_process";
13
+ import { builtinModules, createRequire } from "node:module";
14
+ /**
15
+ * Patch Turbopack runtime to inline chunk loading.
16
+ *
17
+ * Turbopack generates a runtime that loads chunks via R.c("path").
18
+ * These dynamic loads fail in CF Workers (no filesystem).
19
+ *
20
+ * Solution (same as @opennextjs/cloudflare):
21
+ * 1. Find [turbopack]_runtime.js
22
+ * 2. Collect all chunk file paths
23
+ * 3. Replace the loadRuntimeChunkPath function's require(resolved) with requireChunk(chunkPath)
24
+ * 4. Append a requireChunk() switch that maps paths to static require()
25
+ */
26
+ async function patchTurbopackRuntime(distDir) {
27
+ // Find ALL Turbopack runtime files — there can be multiple:
28
+ // .next/server/chunks/ssr/[turbopack]_runtime.js
29
+ // .next/server/chunks/[turbopack]_runtime.js
30
+ const runtimePaths = [];
31
+ const searchDirs = [
32
+ path.join(distDir, "server", "chunks", "ssr"),
33
+ path.join(distDir, "server", "chunks"),
34
+ path.join(distDir, "server", "edge", "chunks"),
35
+ ];
36
+ async function walkRuntimes(dir) {
37
+ let entries;
38
+ try {
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);
56
+ }
57
+ }
58
+ }
59
+ for (const dir of searchDirs) {
60
+ await walkRuntimes(dir);
61
+ }
62
+ if (runtimePaths.length === 0)
63
+ return; // Not Turbopack
64
+ // Collect all chunk files from .next/server/chunks/ AND .next/server/edge/chunks/
65
+ const allChunks = [];
66
+ async function walkChunks(dir) {
67
+ let entries;
68
+ try {
69
+ entries = await fs.readdir(dir, { withFileTypes: true });
70
+ }
71
+ catch {
72
+ return;
73
+ }
74
+ for (const entry of entries) {
75
+ const full = path.join(dir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ await walkChunks(full);
78
+ }
79
+ else if (entry.name.endsWith(".js") && !entry.name.includes("[turbopack]_runtime")) {
80
+ allChunks.push(full);
81
+ }
82
+ }
83
+ }
84
+ await walkChunks(path.join(distDir, "server", "chunks"));
85
+ // Include edge chunks — required for middleware and edge runtime pages
86
+ await walkChunks(path.join(distDir, "server", "edge", "chunks"));
87
+ if (allChunks.length === 0)
88
+ return;
89
+ // Generate the requireChunk switch statement
90
+ const cases = [];
91
+ for (const chunk of allChunks) {
92
+ // Extract the relative path after .next/ for the case label
93
+ const relFromDotNext = chunk.replace(/.*\/\.next\//, "");
94
+ cases.push(` case "${relFromDotNext}": return require("${chunk}");`);
95
+ // For edge chunks, also add a short form (relative to server/edge/)
96
+ // because the edge Turbopack runtime resolves chunks relative to itself.
97
+ if (relFromDotNext.startsWith("server/edge/")) {
98
+ const shortRel = relFromDotNext.replace("server/edge/", "");
99
+ cases.push(` case "${shortRel}": return require("${chunk}");`);
100
+ }
101
+ }
102
+ const requireChunkFn = `
103
+ function requireChunk(chunkPath) {
104
+ // Decode URL-encoded paths (edge runtime encodes [, ] as %5B, %5D)
105
+ var decoded = decodeURIComponent(chunkPath);
106
+ switch(decoded) {
107
+ ${cases.join("\n")}
108
+ default:
109
+ // Try with original (encoded) path
110
+ switch(chunkPath) {
111
+ ${cases.join("\n")}
112
+ default:
113
+ throw new Error("Chunk not found: " + chunkPath);
114
+ }
115
+ }
116
+ }
117
+ `;
118
+ // Patch each Turbopack runtime file
119
+ for (const runtimePath of runtimePaths) {
120
+ const runtimeCode = await fs.readFile(runtimePath, "utf-8");
121
+ let patched = runtimeCode;
122
+ let modified = false;
123
+ // Standard SSR runtime: replace require(resolved) with requireChunk(chunkPath)
124
+ if (runtimeCode.includes("loadRuntimeChunkPath") && runtimeCode.includes("require(resolved)")) {
125
+ patched = patched.replace(/require\(resolved\)/g, "requireChunk(chunkPath)");
126
+ patched = patched + "\n" + requireChunkFn;
127
+ modified = true;
128
+ }
129
+ // Edge runtime: replace "chunk loading is not supported" with actual chunk loading.
130
+ // The edge Turbopack runtime has loadChunkCached that throws — we replace it
131
+ // to return a resolved promise after loading the chunk via requireChunk.
132
+ if (runtimeCode.includes("chunk loading is not supported")) {
133
+ patched = patched.replace(/loadChunkCached\([^)]*\)\s*\{[^}]*throw\s+Error\s*\(\s*"chunk loading is not supported"\s*\)[^}]*\}/, `loadChunkCached(e2, t2) {
134
+ try {
135
+ var decoded = decodeURIComponent(t2);
136
+ requireChunk(decoded);
137
+ } catch (err) {
138
+ console.error("[creek-chunk] Failed to load chunk:", t2, "decoded:", decodeURIComponent(t2), "error:", err.message);
139
+ }
140
+ return Promise.resolve();
141
+ }`);
142
+ // Also add requireChunk if not already appended
143
+ if (!modified) {
144
+ patched = patched + "\n" + requireChunkFn;
145
+ }
146
+ modified = true;
147
+ }
148
+ if (modified) {
149
+ await fs.writeFile(runtimePath, patched);
150
+ }
151
+ }
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 patchUseCachePrerenderDanglingPromiseBailout(workerCode) {
488
+ const serializedKeyPattern = /let\s+(\w+)\s*=\s*"string"\s*==\s*typeof\s+(\w+)\s*\?\s*\2\s*:\s*await\s+(\w+)\(\2\),\s*(\w+)\s*=\s*(\w+)\.rootParams/g;
489
+ return workerCode.replace(serializedKeyPattern, (match, serializedKeyVar, encodedKeyVar, encodeFnVar, rootParamsVar, workUnitStoreVar, offset, fullCode) => {
490
+ const followingCode = fullCode.slice(offset, offset + 3000);
491
+ const hangingCallPattern = new RegExp(String.raw `\(0,\s*(\w+)\.makeHangingPromise\)\(\s*${workUnitStoreVar}\.renderSignal\s*,\s*(\w+)\.route`);
492
+ const hangingCallMatch = hangingCallPattern.exec(followingCode);
493
+ if (!hangingCallMatch)
494
+ return match;
495
+ const makeHangingPromiseVar = hangingCallMatch[1];
496
+ const workStoreVar = hangingCallMatch[2];
497
+ return `let ${serializedKeyVar} = "string" == typeof ${encodedKeyVar} ? ${encodedKeyVar} : await ${encodeFnVar}(${encodedKeyVar}); if ("prerender" === ${workUnitStoreVar}.type && "string" == typeof ${serializedKeyVar}) { let __creekDanglingThenableStart = ${serializedKeyVar}.lastIndexOf("$@"); if (__creekDanglingThenableStart !== -1 && ${serializedKeyVar}.indexOf(":", __creekDanglingThenableStart) === -1) return (0, ${makeHangingPromiseVar}.makeHangingPromise)(${workUnitStoreVar}.renderSignal, ${workStoreVar}.route, 'dynamic "use cache"'); } let ${rootParamsVar} = ${workUnitStoreVar}.rootParams`;
498
+ });
499
+ }
500
+ function patchNullFallbackPartialShellBlocking(workerCode) {
501
+ 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;
502
+ return 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)`);
503
+ }
504
+ export async function bundleForWorkers(opts) {
505
+ // Patch Turbopack runtime BEFORE wrangler bundles.
506
+ // Turbopack's R.c() dynamically loads chunks from the filesystem.
507
+ // CF Workers has no filesystem, so we replace R.c() with a switch
508
+ // statement that maps chunk paths to static require() calls.
509
+ await patchTurbopackRuntime(opts.distDir);
510
+ await patchNodeExternalRequireConditions(opts.distDir);
511
+ await patchAppPageManifestSingletons(opts.distDir);
512
+ const ssrLazyRequireAliases = await collectSsrLazyRequireAliases(opts.distDir);
513
+ // Write the generated worker entry
514
+ const entryPath = path.join(opts.outputDir, "__entry.mjs");
515
+ await fs.writeFile(entryPath, opts.workerSource);
516
+ if (process.env.CREEK_DEBUG) {
517
+ await fs.writeFile(path.join(opts.outputDir, "__entry_debug.mjs"), opts.workerSource);
518
+ }
519
+ // Copy WASM files alongside the bundle BEFORE wrangler runs. wrangler's
520
+ // bundler needs the files resolvable from the entry `import __wasm_0 from
521
+ // "./wasm_<hex>.wasm"` to apply the CompiledWasm rule — copying later
522
+ // (after the bundle step) is too late.
523
+ for (const [name, absPath] of opts.wasmFiles) {
524
+ const destName = name.endsWith(".wasm") ? name : name + ".wasm";
525
+ const destPath = path.join(opts.outputDir, destName);
526
+ await fs.copyFile(absPath, destPath);
527
+ }
528
+ // Resolve adapter paths
529
+ const adapterDir = path.dirname(path.dirname(new URL(import.meta.url).pathname));
530
+ // Generate wrangler config for the bundle step
531
+ const wranglerConfig = {
532
+ name: "creek-adapter-build",
533
+ main: entryPath,
534
+ compatibility_date: "2026-03-28",
535
+ compatibility_flags: ["nodejs_compat"],
536
+ define: {
537
+ __dirname: '""',
538
+ __filename: '""',
539
+ "process.env.NODE_ENV": '"production"',
540
+ "process.env.NEXT_RUNTIME": '"nodejs"',
541
+ },
542
+ // Mark optional/unavailable deps as external to prevent build errors.
543
+ // These are caught at runtime and handled gracefully.
544
+ //
545
+ // `ssrLazyRequireAliases` is spread FIRST so static entries below take
546
+ // precedence if they collide — our shims (fs, vm, critters, etc.)
547
+ // always win over a happenstance SSR lazy-require of the same name.
548
+ alias: {
549
+ ...ssrLazyRequireAliases,
550
+ "@opentelemetry/api": path.join(adapterDir, "src", "shims", "opentelemetry.js"),
551
+ // fs shim — intercept both bare and node: prefixed imports.
552
+ // Turbopack runtime uses require("fs") which wrangler must redirect
553
+ // to our shim that reads from embedded __MANIFESTS.
554
+ "fs": path.join(adapterDir, "src", "shims", "fs.js"),
555
+ "node:fs": path.join(adapterDir, "src", "shims", "fs.js"),
556
+ "vm": path.join(adapterDir, "src", "shims", "vm.js"),
557
+ "node:vm": path.join(adapterDir, "src", "shims", "vm.js"),
558
+ // critters is bundled by Next.js for CSS inlining — not needed on Workers.
559
+ "critters": path.join(adapterDir, "src", "shims", "critters.js"),
560
+ // sharp has native .node bindings that workerd can't load. Without this
561
+ // alias, wrangler pulls in ~1MB of sharp's JS wrapper and the module
562
+ // ends up non-callable at runtime — \`@vercel/og\`'s node path then
563
+ // throws \`sharp is not a function\`. Aliasing to a shim whose default
564
+ // is undefined makes \`@vercel/og\` fall back to its resvg.wasm path.
565
+ "sharp": path.join(adapterDir, "src", "shims", "sharp.js"),
566
+ // Some packages intentionally leave unreachable dynamic imports such as
567
+ // `if (Math.random() < 0) import("fail")` in their ESM entry. Turbopack
568
+ // externalizes the package and never resolves that branch, but wrangler
569
+ // follows it once we preload the concrete ESM file. Stub it so the dead
570
+ // branch remains harmless.
571
+ "fail": path.join(adapterDir, "src", "shims", "empty.js"),
572
+ // Replace Next's track-module-loading.{instance,external} with a
573
+ // per-request AsyncLocalStorage version. The original keeps a
574
+ // module-level CacheSignal whose internal setImmediate closure
575
+ // leaks IoContext across requests on workerd — second-and-later
576
+ // requests throw "Cannot perform I/O on behalf of a different
577
+ // request" when CacheSignal.pendingTimeoutCleanup fires
578
+ // clearImmediate on an Immediate from the first request. Repros
579
+ // on any route that does dynamic imports during render (notably
580
+ // \`new ImageResponse(...)\` — every \`@vercel/og\` call triggers
581
+ // trackPendingImport). We alias \`.external\` as well because
582
+ // call sites import from that module (the internal relative
583
+ // \`./track-module-loading.instance\` import never passes through
584
+ // esbuild's bare-specifier alias map). See
585
+ // src/shims/track-module-loading.js.
586
+ "next/dist/server/app-render/module-loading/track-module-loading.external": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
587
+ "next/dist/server/app-render/module-loading/track-module-loading.external.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
588
+ "next/dist/server/app-render/module-loading/track-module-loading.instance": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
589
+ "next/dist/server/app-render/module-loading/track-module-loading.instance.js": path.join(adapterDir, "src", "shims", "track-module-loading.js"),
590
+ // Next's fast-set-immediate module patches node:timers/promises, whose
591
+ // ESM namespace is frozen in Workers. More importantly, workerd can run
592
+ // the next setTimeout(0) stage before a setImmediate scheduled by the
593
+ // previous cache-components stage. The shim keeps native setImmediate
594
+ // except during Next's explicit fast-immediate capture window.
595
+ "next/dist/server/node-environment-extensions/fast-set-immediate.external": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
596
+ "next/dist/server/node-environment-extensions/fast-set-immediate.external.js": path.join(adapterDir, "src", "shims", "fast-set-immediate.js"),
597
+ // NOTE: load-manifest shim exists in src/shims/ but is handled by the
598
+ // fs shim (manifest loading), so no alias needed.
599
+ // NOTE: http/node:http is NOT aliased — CF Workers nodejs_compat provides it.
600
+ // The worker entry uses our custom IncomingMessage/ServerResponse inline via
601
+ // the NODE_BRIDGE_CODE template, which imports from "http" (the built-in).
602
+ },
603
+ };
604
+ const configPath = path.join(opts.outputDir, "__wrangler.json");
605
+ await fs.writeFile(configPath, JSON.stringify(wranglerConfig));
606
+ // Bundle with wrangler --dry-run
607
+ // Wrangler internally uses esbuild but with Turbopack-aware resolution
608
+ // and proper CJS/ESM interop for CF Workers.
609
+ // Ensure @next/routing is resolvable from the project directory. It's a
610
+ // dependency of the adapter, not the user's project, so wrangler can't
611
+ // find it when run from the project's cwd. Resolve via \`createRequire\`
612
+ // rather than guessing \`adapterDir/node_modules/@next/routing\` — pnpm's
613
+ // virtual-store layout means that path often doesn't exist (the real
614
+ // install lives under \`node_modules/.pnpm/@next+routing@X/\`), and the
615
+ // guess only works for the link-protocol install of the adapter.
616
+ const projectNodeModules = path.join(path.dirname(opts.distDir), "node_modules");
617
+ const routingDest = path.join(projectNodeModules, "@next", "routing");
618
+ const adapterRequire = createRequire(path.join(adapterDir, "package.json"));
619
+ let routingSrc;
620
+ try {
621
+ routingSrc = path.dirname(adapterRequire.resolve("@next/routing/package.json"));
622
+ }
623
+ catch {
624
+ // Fallback to the legacy guess so a classic link-install still works.
625
+ routingSrc = path.join(adapterDir, "node_modules", "@next", "routing");
626
+ }
627
+ try {
628
+ await fs.access(routingDest);
629
+ }
630
+ catch {
631
+ await fs.mkdir(path.join(projectNodeModules, "@next"), { recursive: true });
632
+ try {
633
+ await fs.symlink(routingSrc, routingDest, "junction");
634
+ }
635
+ catch (err) {
636
+ // Racy repeat runs (same project dir rebuilt back-to-back) can leave a
637
+ // dangling symlink that \`access\` reports as missing while \`symlink\`
638
+ // still refuses to overwrite.
639
+ if (err?.code !== "EEXIST")
640
+ throw err;
641
+ }
642
+ }
643
+ const bundleDir = path.join(opts.outputDir, "__bundle");
644
+ // Resolve wrangler binary from the adapter's own node_modules
645
+ const wranglerBin = path.join(adapterDir, "node_modules", ".bin", "wrangler");
646
+ try {
647
+ execSync(`"${wranglerBin}" deploy --dry-run --outdir "${bundleDir}" --config "${configPath}"`, {
648
+ cwd: path.dirname(opts.distDir),
649
+ stdio: "pipe",
650
+ env: process.env,
651
+ });
652
+ }
653
+ catch (err) {
654
+ const stderr = err instanceof Error && "stderr" in err
655
+ ? err.stderr?.toString() ?? ""
656
+ : "";
657
+ const stdout = err instanceof Error && "stdout" in err
658
+ ? err.stdout?.toString() ?? ""
659
+ : "";
660
+ // Try to read wrangler log for details
661
+ let logContent = "";
662
+ try {
663
+ const { readdirSync, readFileSync } = await import("node:fs");
664
+ const logDir = path.join(process.env.HOME || "/tmp", ".wrangler/logs");
665
+ const logs = readdirSync(logDir).sort().reverse();
666
+ if (logs[0])
667
+ logContent = readFileSync(path.join(logDir, logs[0]), "utf-8").slice(-1000);
668
+ }
669
+ catch { }
670
+ throw new Error(`Wrangler bundle failed:\nSTDERR: ${stderr.slice(-2000)}\nSTDOUT: ${stdout.slice(-500)}\nLOG: ${logContent}`);
671
+ }
672
+ // Move bundled files to output directory
673
+ const bundledFiles = await fs.readdir(bundleDir);
674
+ for (const f of bundledFiles) {
675
+ if (f.endsWith(".map") || f === "README.md")
676
+ continue;
677
+ await fs.rename(path.join(bundleDir, f), path.join(opts.outputDir, f));
678
+ }
679
+ // Rename the main entry to worker.js
680
+ const mainFile = bundledFiles.find(f => f.endsWith(".mjs") || f.endsWith(".js"));
681
+ if (mainFile && mainFile !== "worker.js") {
682
+ const src = path.join(opts.outputDir, mainFile);
683
+ const dest = path.join(opts.outputDir, "worker.js");
684
+ if (await fs.access(src).then(() => true).catch(() => false)) {
685
+ await fs.rename(src, dest);
686
+ }
687
+ }
688
+ // Post-process bundled worker to fix CF Workers compatibility issues.
689
+ const workerPath = path.join(opts.outputDir, "worker.js");
690
+ try {
691
+ let workerCode = await fs.readFile(workerPath, "utf-8");
692
+ // Fix instrumentation module loading — Next.js's catch only handles
693
+ // ENOENT/MODULE_NOT_FOUND error codes, but CF Workers __require throws
694
+ // "Dynamic require of ... is not supported" without those codes.
695
+ // Patch the catch to also handle "is not supported" errors.
696
+ 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")');
697
+ // Some libraries hide builtin requires from the bundler, e.g.
698
+ // @typescript/vfs constructs `require("path")` via String.fromCharCode.
699
+ // Wrangler can't statically rewrite that, so esbuild's __require helper
700
+ // throws at runtime even though the node:path wrapper is already present
701
+ // elsewhere in the bundle. Route those builtin dynamic requires back to
702
+ // wrangler's generated require_path() module when available.
703
+ 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\');');
704
+ 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;');
705
+ // `next/dist/server/node-environment.js` imports
706
+ // `./node-environment-extensions/fast-set-immediate.external` by relative
707
+ // path, so Wrangler's bare-specifier alias cannot intercept it. If the
708
+ // original module installs first, our scoped shim stores Next's patched
709
+ // timer functions as its "native" originals and the workerd ordering fix
710
+ // becomes re-entrant. Route that side-effect import to the aliased shim
711
+ // module that Wrangler emitted for Turbopack's external import.
712
+ workerCode = workerCode.replace(/\brequire_fast_set_immediate_external\(\);/g, 'typeof init_fast_set_immediate === "function" ? init_fast_set_immediate() : require_fast_set_immediate_external();');
713
+ // Next.js's \`getInstrumentationModule\` does
714
+ // \`await __require(path.join(projectDir, distDir, "server",
715
+ // \`\${INSTRUMENTATION_HOOK_FILENAME}.js\`))\`. workerd can't resolve
716
+ // dynamic-require paths — the call rejects, the catch above swallows it,
717
+ // and \`instrumentation.register()\` is never invoked. Worker-entry
718
+ // static-imports the user file onto \`globalThis.__CREEK_INSTRUMENTATION\`
719
+ // when present, so prefer that over \`__require\` here. Falls through
720
+ // to the original call when no user instrumentation is registered so
721
+ // the \`module.exports = {}\` placeholder path still works.
722
+ workerCode = workerCode.replace(/(cachedInstrumentationModule\s*=\s*\(0,\s*_interopdefault\.interopDefault\)\s*\()\s*await __require\(/g, "$1globalThis.__CREEK_INSTRUMENTATION || await __require(");
723
+ workerCode = patchBundledManifestSingleton(workerCode);
724
+ // depd (via raw-body) uses `eval("(function ("+args+") {...})")` to build a
725
+ // deprecation-wrapping thunk. workerd + CF Workers block code generation
726
+ // from strings ("Code generation from strings disallowed for this context"),
727
+ // so any module that pulls in raw-body — notably the Pages Router API
728
+ // body parser — throws at module load time, and the outer try/catch in
729
+ // parse-body.ts surfaces it to the browser as `400 Invalid body`.
730
+ //
731
+ // Replace the minified eval expression with a direct function literal
732
+ // that preserves the runtime semantics (log + call). We lose the
733
+ // `function.length` preservation the eval form achieved, but nothing
734
+ // in the raw-body → parse-body call chain reads it.
735
+ //
736
+ // Fixes middleware-redirects "should redirect to api route with locale"
737
+ // (and the /fr variant) — both fail because their navigation ends at
738
+ // an API route whose parse-body path can't load raw-body.
739
+ 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)}}");
740
+ // Route \`externalImport(id)\` through \`globalThis.__CREEK_EXT_MODS\` so
741
+ // we can serve bundled-but-externalized-by-Turbopack modules from
742
+ // our worker-entry static imports. Turbopack emits chunks like
743
+ // \`[externals]_next_dist_compiled_@vercel_og_index_node_...\` that do
744
+ // \`await e.y("next/dist/compiled/@vercel/og/index.node.js")\`; on
745
+ // workerd that \`await import(id)\` path throws "No such module" and
746
+ // the handler 500s. When our entry registers the module in
747
+ // \`__CREEK_EXT_MODS\`, the patched \`externalImport\` returns it
748
+ // directly without going through workerd's external loader.
749
+ // Fixes og-api \`/og-node\` (node runtime) +
750
+ // use-cache-metadata-route-handler opengraph/icon tests.
751
+ 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` +
752
+ ` let raw;\n` +
753
+ ` { const __loaders = globalThis.__CREEK_EXT_LOADERS; if (__loaders && __loaders[${idVar}]) {\n` +
754
+ ` const __cached = globalThis.__CREEK_EXT_MODS = globalThis.__CREEK_EXT_MODS || {};\n` +
755
+ ` if (${idVar} in __cached) { raw = __cached[${idVar}]; }\n` +
756
+ ` else { try { raw = await __loaders[${idVar}](); __cached[${idVar}] = raw; } catch (err) { throw new Error(\`Failed to load external module \${${idVar}}: \${err}\`); } }\n` +
757
+ ` if (raw && raw.__esModule && raw.default && "default" in raw.default) { return interopEsm(raw.default, createNS(raw), true); }\n` +
758
+ ` return raw;\n` +
759
+ ` } }\n` +
760
+ ` try {\n` +
761
+ ` raw = await import(${idVar});`);
762
+ // \`@vercel/og/index.node.js\` evaluates at module load:
763
+ //
764
+ // var fontData = fs.readFileSync(fileURLToPath(new URL("./Geist-Regular.ttf", import.meta.url)));
765
+ // var resvg_wasm = fs.readFileSync(fileURLToPath(new URL("./resvg.wasm", import.meta.url)));
766
+ //
767
+ // workerd rejects \`new URL("./X", import.meta.url)\` with
768
+ // "Invalid URL string" in the bundled-worker context, so evaluation
769
+ // aborts before any request hits the route. Rewrite these two calls
770
+ // to pass literal paths into fs.readFileSync directly — our fs shim
771
+ // has a basename fallback for .wasm/.ttf, so the embedded bundled
772
+ // bytes resolve regardless of path.
773
+ 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(/^"\.\//, '"'));
774
+ // Strip `AsyncLocalStorage.snapshot()` bindings in Next's
775
+ // `server/app-render/async-local-storage.js` and its Turbopack-inlined
776
+ // variants. On workerd, `ALS.snapshot()` captures the CURRENT IoContext
777
+ // and invoking the returned function from a later request throws
778
+ // "Cannot perform I/O on behalf of a different request". Next uses
779
+ // `createSnapshot()` / `runInCleanSnapshot: ALS.snapshot()` for cache-
780
+ // invalidation + edge-action callback propagation — both paths cross
781
+ // request boundaries on workerd. Replace the snapshot binding with a
782
+ // direct passthrough, matching @opennextjs/cloudflare's `patchUseCacheIO`
783
+ // (their comment: "TODO: Find a better fix for this issue.").
784
+ //
785
+ // Four forms exist post-bundling:
786
+ // 1. `function createSnapshot() { if (X) return X.snapshot(); return function(fn,...args){...}; }`
787
+ // — clean esm form from next/dist/esm (one copy embedded by wrangler).
788
+ // 2. `function <name>() { return X ? X.snapshot() : function(a,...b){ return a(...b); }; }`
789
+ // — Turbopack's minified module-level shim (edge-side copies).
790
+ // 3. `a.s(["bindSnapshot", 0, q, "createSnapshot", 0, function(){ return p ? p.snapshot() : ...; }])`
791
+ // — Turbopack's export-binding form in the same shim.
792
+ // 4. `runInCleanSnapshot: X ? X.snapshot() : function(a,...b){ return a(...b); }`
793
+ // — inlined call-site in the bundled work-store constructor.
794
+ // A single regex `(\w+) ? \1.snapshot() : ` stripped from the minified
795
+ // forms covers 2/3/4; form 1 uses an if-guard so handle it separately.
796
+ workerCode = workerCode.replace(/if\s*\(\s*(\w+)\s*\)\s*\{\s*return\s+\1\.snapshot\(\);\s*\}/g, "// Ignored snapshot");
797
+ workerCode = stripAsyncLocalStorageSnapshotTernaries(workerCode);
798
+ // `use cache` arguments that contain an unresolved React thenable marker
799
+ // (`$@`) during a prerender are request-bound values. Next's dynamic
800
+ // access instrumentation usually catches this before cache fill, but an
801
+ // async params transform can hide the access until after serialization.
802
+ // Node eventually trips the 50s cache-fill timeout; under workerd that
803
+ // blocks the whole response. Treat the cache function as a dynamic hole
804
+ // immediately so the PPR fallback shell can resume.
805
+ workerCode = patchUseCachePrerenderDanglingPromiseBailout(workerCode);
806
+ // Null-fallback partial routes don't have a concrete fallback artifact
807
+ // on disk; under workerd the follow-up path that tries to specialize a
808
+ // generic shell on the first request can surface as a static-generation
809
+ // bailout instead of completing the request. Keep these routes on the
810
+ // blocking path and let the concrete request render/cache directly.
811
+ workerCode = patchNullFallbackPartialShellBlocking(workerCode);
812
+ // Unify Next's `*AsyncStorageInstance` singletons across Turbopack
813
+ // chunks via a `globalThis` key. Turbopack emits the `work-unit-async-
814
+ // storage-instance.js` / `work-async-storage-instance.js` / etc.
815
+ // factories INLINE into every chunk that references them (see the
816
+ // `'turbopack-transition': 'next-shared'` import attribute in the
817
+ // next-shared layer); each chunk evaluates its own
818
+ // `createAsyncLocalStorage()` call and gets a fresh ALS instance.
819
+ // On the edge-runtime server-action path this fragments the store:
820
+ // the action handler runs `workUnitAsyncStorage.run(d, fn)` inside
821
+ // chunk A's ALS, the inner `headers()` call reads from chunk B's
822
+ // ALS, finds no store, and throws "headers was called outside a
823
+ // request scope". Node action path survives because its caller +
824
+ // callee happen to resolve to the same chunk's copy.
825
+ //
826
+ // Fix: rewrite the module factory bodies to key the ALS on the
827
+ // exported instance name (`workUnitAsyncStorageInstance`, etc.)
828
+ // under a single `globalThis.__CREEK_ALS` bag, so all chunks share
829
+ // one instance per logical store. Name-keyed is narrower than
830
+ // "dedup every `new AsyncLocalStorage()`" — we don't accidentally
831
+ // merge unrelated per-store ALSes (tracing requestStorage,
832
+ // react-server-dom temporaryReferences, etc.).
833
+ //
834
+ // Target pattern (both `a.i(N)` and `a.r(N)` create-variants
835
+ // exist):
836
+ // let <v> = (0, a.i(43291).createAsyncLocalStorage)();
837
+ // …short gap…
838
+ // a.s(["<Name>AsyncStorageInstance", 0, <v>], …)
839
+ // Replacement keeps the original create call so the per-isolate
840
+ // fallback works if globalThis isn't carried through an unusual
841
+ // loader; the `??=` idempotently promotes the first copy into the
842
+ // shared bag.
843
+ 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}`);
844
+ // Same dedup for the CJS-wrapped variants esbuild produces when it
845
+ // bundles Next's non-Turbopack ESM copy. Two observed shapes:
846
+ // a. `var <Name>AsyncStorageInstance = (0, _als.createAsyncLocalStorage)();`
847
+ // — top-level from `work-unit-async-storage-instance.js` etc.
848
+ // b. `Object.defineProperty(c, "<Name>AsyncStorageInstance", {…get…return <v>…}); let <v> = (0, a.r(N).createAsyncLocalStorage)();`
849
+ // — Turbopack-emitted CJS compiled form (edge chunks).
850
+ workerCode = workerCode.replace(/var\s+(\w+AsyncStorageInstance)\s*=\s*(\(0,\s*\w+\.createAsyncLocalStorage\)\(\));/g, (_match, storeName, createCall) => `var ${storeName} = ((globalThis.__CREEK_ALS ??= {})["${storeName}"] ??= ${createCall});`);
851
+ 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})`);
852
+ // Turbopack's node.js runtime implements top-level-await by assigning
853
+ // \`module.exports = <Promise>\`. esbuild's \`__toESM\` wraps imports with
854
+ // \`__create(__getProtoOf(mod))\`, and for a Promise that yields a plain
855
+ // object whose \`__proto__\` is \`Promise.prototype\`. Awaiting that object
856
+ // invokes \`Promise.prototype.then\` with a non-Promise receiver — workerd
857
+ // rejects it as "incompatible receiver" and the route 500s. Detect a
858
+ // Promise input and return it unchanged so await resolves the real
859
+ // async-module promise instead. Fixes \`metadata-edge\` and
860
+ // \`metadata-dynamic-routes-async-deps\` \`opengraph-image\` routes.
861
+ 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) => {
862
+ if (mod != null && typeof mod === "object" && typeof mod.then === "function" && __getProtoOf(mod) === Promise.prototype) {
863
+ return mod;
864
+ }
865
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
866
+ return __copyProps(
867
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
868
+ mod
869
+ );
870
+ };`);
871
+ await fs.writeFile(workerPath, workerCode);
872
+ }
873
+ catch { }
874
+ // Emit wrangler.toml that covers both local dev (workerd via wrangler dev)
875
+ // and \`wrangler deploy\`. Dev and prod MUST share the same config so
876
+ // deployment behavior stays predictable — that's what the adapter is
877
+ // expected to guarantee.
878
+ //
879
+ // The file lives alongside worker.js in \`.creek/adapter-output/server/\`.
880
+ // Users can override specific values by creating a \`wrangler.toml\`
881
+ // at their project root — the adapter reads that at build time and
882
+ // merges over our defaults (support added in a follow-up TODO).
883
+ await emitWranglerConfig({
884
+ outputDir: opts.outputDir,
885
+ assetsRelPath: "../assets", // server/ → assets/
886
+ // Same normalization as the copy loop above — ensure every wasm
887
+ // filename declared in the rules ends with \`.wasm\` so wrangler's
888
+ // CompiledWasm rule discovers the file we actually wrote to disk.
889
+ wasmFilenames: [...opts.wasmFiles.keys()].map((n) => n.endsWith(".wasm") ? n : n + ".wasm"),
890
+ });
891
+ // Clean up temp files
892
+ await fs.rm(entryPath, { force: true });
893
+ await fs.rm(configPath, { force: true });
894
+ await fs.rm(bundleDir, { recursive: true, force: true });
895
+ // List output files
896
+ const files = await fs.readdir(opts.outputDir);
897
+ return files.filter(f => !f.startsWith("__"));
898
+ }
899
+ /**
900
+ * Write a wrangler.toml next to worker.js so the same config drives both
901
+ * \`wrangler dev\` (local dev / test harness) and \`wrangler deploy\` (prod).
902
+ *
903
+ * Contents:
904
+ * - \`name\`, \`main\`, \`compatibility_date\`, \`compatibility_flags\` — runtime
905
+ * - \`[assets]\` — binds the assets directory with \`run_worker_first\` so our
906
+ * worker handles routing before workerd's static asset shortcut
907
+ * - \`[[durable_objects.bindings]]\` + \`[[migrations]]\` — the three DO
908
+ * classes the worker entry declares (DOQueueHandler, DOShardedTagCache,
909
+ * BucketCachePurge)
910
+ * - \`[[rules]] type = "CompiledWasm"\` — declares wasm siblings (next/og
911
+ * yoga/resvg, sharp, etc.) as module imports for workerd's loader
912
+ */
913
+ async function emitWranglerConfig(opts) {
914
+ const { outputDir, assetsRelPath, wasmFilenames } = opts;
915
+ // Turbopack + wrangler both emit wasm siblings (hashed \`wasm_<xxh3>\`
916
+ // from us, \`<sha1>-yoga.wasm\` / \`<sha1>-resvg.wasm\` from wrangler's
917
+ // own esbuild pass when the app imports next/og). A wildcard glob
918
+ // covers every sibling regardless of who emitted it. Listing specific
919
+ // filenames fails because wrangler normalises import paths with a
920
+ // leading \`./\` that literal-name globs don't match — the built-in
921
+ // \`**/*.wasm\` rule then fires and aborts the build with
922
+ // "ignored because a previous rule ... was not marked as fallthrough".
923
+ let hasWasm = wasmFilenames.length > 0;
924
+ if (!hasWasm) {
925
+ try {
926
+ const entries = await fs.readdir(outputDir);
927
+ hasWasm = entries.some((e) => e.endsWith(".wasm"));
928
+ }
929
+ catch { }
930
+ }
931
+ const wasmRule = hasWasm
932
+ ? [
933
+ "",
934
+ "# Declare every wasm sibling as a CompiledWasm module so \`import",
935
+ "# foo from \"./<hash>.wasm\"\` in the bundle resolves at runtime.",
936
+ "# Without this, Turbopack's runtime registry returns undefined and",
937
+ "# throws \"dynamically loading WebAssembly is not supported\".",
938
+ "[[rules]]",
939
+ `globs = ["**/*.wasm"]`,
940
+ `type = "CompiledWasm"`,
941
+ `fallthrough = false`,
942
+ ].join("\n")
943
+ : "";
944
+ const toml = `# Generated by @solcreek/adapter-creek. Hand edits will be overwritten on
945
+ # the next \`next build\`. To extend (add KV, queues, env vars, etc.), create
946
+ # a \`wrangler.toml\` at your project root — the adapter merges it on top.
947
+
948
+ name = "creek"
949
+ main = "worker.js"
950
+ compatibility_date = "2026-03-23"
951
+ compatibility_flags = ["nodejs_compat"]
952
+
953
+ # Static assets from Next.js's \`.next/static\` + \`public/\` live alongside
954
+ # this server dir under \`../assets/\`. \`run_worker_first = true\` tells
955
+ # workerd to invoke our worker BEFORE serving static files — the adapter
956
+ # handles middleware, routing, and cache headers before any asset shortcut.
957
+ [assets]
958
+ directory = "${assetsRelPath}"
959
+ binding = "ASSETS"
960
+ run_worker_first = true
961
+
962
+ # Durable Object classes declared by the adapter's worker-entry. The
963
+ # binding \`name\` (what runtime code reads via \`env.<NAME>\`) must match
964
+ # what Creek's control plane injects in production — see
965
+ # \`packages/control-plane/src/modules/deployments/deploy.ts:129-140\` in
966
+ # the Creek repo. Do NOT rename these without coordinating with Creek.
967
+ #
968
+ # \`class_name\` is our internal export name from worker-entry.ts.
969
+ # \`new_sqlite_classes\` (not \`new_classes\`) selects SQLite-backed DO
970
+ # storage — DOShardedTagCache and BucketCachePurge both use
971
+ # \`ctx.storage.sql\`. DOQueueHandler is currently a placeholder but
972
+ # declared under SQLite too so future queue persistence has no migration
973
+ # cost.
974
+ [[durable_objects.bindings]]
975
+ name = "NEXT_CACHE_DO_QUEUE"
976
+ class_name = "DOQueueHandler"
977
+ [[durable_objects.bindings]]
978
+ name = "NEXT_TAG_CACHE_DO_SHARDED"
979
+ class_name = "DOShardedTagCache"
980
+ [[durable_objects.bindings]]
981
+ name = "NEXT_CACHE_DO_BUCKET_PURGE"
982
+ class_name = "BucketCachePurge"
983
+
984
+ [[migrations]]
985
+ tag = "v1"
986
+ new_sqlite_classes = ["DOQueueHandler", "DOShardedTagCache", "BucketCachePurge"]
987
+ ${wasmRule}
988
+ `;
989
+ await fs.writeFile(path.join(outputDir, "wrangler.toml"), toml);
990
+ }
991
+ //# sourceMappingURL=bundler.js.map