@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.
- package/LICENSE +190 -0
- package/README.md +184 -0
- package/dist/build.d.ts +57 -0
- package/dist/build.js +1382 -0
- package/dist/bundler.d.ts +20 -0
- package/dist/bundler.js +991 -0
- package/dist/cache-handler.d.ts +32 -0
- package/dist/cache-handler.js +100 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +247 -0
- package/dist/manifest.d.ts +40 -0
- package/dist/manifest.js +27 -0
- package/dist/worker-entry.d.ts +133 -0
- package/dist/worker-entry.js +7734 -0
- package/package.json +64 -0
- package/src/shims/als-polyfill.js +7 -0
- package/src/shims/critters.js +7 -0
- package/src/shims/empty.js +2 -0
- package/src/shims/env.js +3 -0
- package/src/shims/fast-set-immediate.js +285 -0
- package/src/shims/fs.js +225 -0
- package/src/shims/http.js +240 -0
- package/src/shims/image-optimizer.js +18 -0
- package/src/shims/load-manifest.js +123 -0
- package/src/shims/opentelemetry.js +229 -0
- package/src/shims/sharp.js +12 -0
- package/src/shims/sqlite3-binding.js +517 -0
- package/src/shims/track-module-loading.js +68 -0
- package/src/shims/vm.js +49 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solcreek/adapter-creek",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js deployment adapter for Creek (Cloudflare Workers)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./cache-handler": {
|
|
14
|
+
"default": "./dist/cache-handler.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src/shims",
|
|
20
|
+
"!dist/**/*.test.*",
|
|
21
|
+
"!dist/**/*.map"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"clean": "rm -rf dist"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"creek",
|
|
33
|
+
"nextjs",
|
|
34
|
+
"adapter",
|
|
35
|
+
"cloudflare",
|
|
36
|
+
"workers",
|
|
37
|
+
"deployment"
|
|
38
|
+
],
|
|
39
|
+
"author": "SolCreek",
|
|
40
|
+
"license": "Apache-2.0",
|
|
41
|
+
"homepage": "https://creek.dev",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/solcreek/adapter-creek.git"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@next/routing": "16.2.3",
|
|
48
|
+
"@node-rs/xxhash": "^1.7.6",
|
|
49
|
+
"sql.js": "^1.14.1",
|
|
50
|
+
"wrangler": "^4.82.2"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"next": ">=16.2.3"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.13.10",
|
|
57
|
+
"miniflare": "^4.20260410.0",
|
|
58
|
+
"next": "16.2.3",
|
|
59
|
+
"typescript": "^5.8.2",
|
|
60
|
+
"urlpattern-polyfill": "^10.1.0",
|
|
61
|
+
"vitest": "^4.1.2"
|
|
62
|
+
},
|
|
63
|
+
"packageManager": "pnpm@10.33.0"
|
|
64
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// AsyncLocalStorage polyfill — must be imported FIRST.
|
|
2
|
+
// Sets globalThis.AsyncLocalStorage before any Next.js code evaluates.
|
|
3
|
+
// CF Workers has AsyncLocalStorage via node:async_hooks but not on globalThis.
|
|
4
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
if (!globalThis.AsyncLocalStorage) {
|
|
6
|
+
globalThis.AsyncLocalStorage = AsyncLocalStorage;
|
|
7
|
+
}
|
package/src/shims/env.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// Shim for next/dist/server/node-environment-extensions/fast-set-immediate.
|
|
2
|
+
//
|
|
3
|
+
// Next's cache-components renderer relies on "fast immediates": setImmediate()
|
|
4
|
+
// calls made during a runInSequentialTasks stage must flush before the next
|
|
5
|
+
// setTimeout(0) stage. workerd's event loop can run the next timeout first,
|
|
6
|
+
// which cuts PPR static RSC boundaries before cached content has resolved.
|
|
7
|
+
//
|
|
8
|
+
// Keep the behavior scoped to Next's explicit capture window. Outside
|
|
9
|
+
// DANGEROUSLY_runPendingImmediatesAfterCurrentTask(), setImmediate stays native.
|
|
10
|
+
const ORIGINALS_KEY = Symbol.for("creek.fast-set-immediate.originals");
|
|
11
|
+
const INTERNALS = Symbol.for("creek.fast-set-immediate.internals");
|
|
12
|
+
|
|
13
|
+
const originals = globalThis[ORIGINALS_KEY] || (globalThis[ORIGINALS_KEY] = {
|
|
14
|
+
setImmediate: globalThis.setImmediate,
|
|
15
|
+
clearImmediate: globalThis.clearImmediate,
|
|
16
|
+
nextTick: globalThis.process?.nextTick,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const originalSetImmediate =
|
|
20
|
+
typeof originals.setImmediate === "function"
|
|
21
|
+
? originals.setImmediate.bind(globalThis)
|
|
22
|
+
: (callback, ...args) => setTimeout(callback, 0, ...args);
|
|
23
|
+
const originalClearImmediate =
|
|
24
|
+
typeof originals.clearImmediate === "function"
|
|
25
|
+
? originals.clearImmediate.bind(globalThis)
|
|
26
|
+
: clearTimeout;
|
|
27
|
+
const originalNextTick =
|
|
28
|
+
typeof originals.nextTick === "function"
|
|
29
|
+
? originals.nextTick.bind(globalThis.process)
|
|
30
|
+
: (callback, ...args) => queueMicrotask(() => callback(...args));
|
|
31
|
+
|
|
32
|
+
let currentExecution = null;
|
|
33
|
+
let pendingNextTicks = 0;
|
|
34
|
+
let installed = false;
|
|
35
|
+
|
|
36
|
+
class CreekImmediate {
|
|
37
|
+
constructor() {
|
|
38
|
+
this[INTERNALS] = {
|
|
39
|
+
hasRef: true,
|
|
40
|
+
nativeImmediate: null,
|
|
41
|
+
queueItem: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
hasRef() {
|
|
46
|
+
const internals = this[INTERNALS];
|
|
47
|
+
if (internals.queueItem) return internals.hasRef;
|
|
48
|
+
if (internals.nativeImmediate?.hasRef) return internals.nativeImmediate.hasRef();
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
ref() {
|
|
53
|
+
const internals = this[INTERNALS];
|
|
54
|
+
if (internals.queueItem) internals.hasRef = true;
|
|
55
|
+
else internals.nativeImmediate?.ref?.();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
unref() {
|
|
60
|
+
const internals = this[INTERNALS];
|
|
61
|
+
if (internals.queueItem) internals.hasRef = false;
|
|
62
|
+
else internals.nativeImmediate?.unref?.();
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_onImmediate() {}
|
|
67
|
+
|
|
68
|
+
[Symbol.dispose]() {
|
|
69
|
+
const internals = this[INTERNALS];
|
|
70
|
+
if (internals.queueItem) {
|
|
71
|
+
const item = internals.queueItem;
|
|
72
|
+
internals.queueItem = null;
|
|
73
|
+
clearQueueItem(item);
|
|
74
|
+
} else if (internals.nativeImmediate) {
|
|
75
|
+
if (typeof internals.nativeImmediate[Symbol.dispose] === "function") {
|
|
76
|
+
internals.nativeImmediate[Symbol.dispose]();
|
|
77
|
+
} else {
|
|
78
|
+
originalClearImmediate(internals.nativeImmediate);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function install() {
|
|
85
|
+
if (installed) return;
|
|
86
|
+
installed = true;
|
|
87
|
+
|
|
88
|
+
globalThis.setImmediate = patchedSetImmediate;
|
|
89
|
+
globalThis.clearImmediate = patchedClearImmediate;
|
|
90
|
+
if (globalThis.process && typeof globalThis.process.nextTick === "function") {
|
|
91
|
+
globalThis.process.nextTick = patchedNextTick;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Best effort for consumers that import from node:timers. Avoid touching
|
|
95
|
+
// node:timers/promises because that namespace is frozen in Workers.
|
|
96
|
+
try {
|
|
97
|
+
const nodeTimers = require("node:timers");
|
|
98
|
+
nodeTimers.setImmediate = patchedSetImmediate;
|
|
99
|
+
nodeTimers.clearImmediate = patchedClearImmediate;
|
|
100
|
+
} catch {}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function DANGEROUSLY_runPendingImmediatesAfterCurrentTask() {
|
|
104
|
+
if (currentExecution !== null) {
|
|
105
|
+
expectNoPendingImmediates();
|
|
106
|
+
}
|
|
107
|
+
const execution = {
|
|
108
|
+
queuedImmediates: [],
|
|
109
|
+
abandoned: false,
|
|
110
|
+
};
|
|
111
|
+
currentExecution = execution;
|
|
112
|
+
scheduleWorkAfterNextTicksAndMicrotasks(execution);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function expectNoPendingImmediates() {
|
|
116
|
+
if (currentExecution === null) return;
|
|
117
|
+
|
|
118
|
+
const execution = currentExecution;
|
|
119
|
+
drainReadyImmediatesSynchronously(execution);
|
|
120
|
+
|
|
121
|
+
if (currentExecution === execution) {
|
|
122
|
+
scheduleQueuedImmediatesAsNative(execution);
|
|
123
|
+
currentExecution = null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { install, originalSetImmediate as unpatchedSetImmediate };
|
|
128
|
+
|
|
129
|
+
function scheduleWorkAfterNextTicksAndMicrotasks(execution) {
|
|
130
|
+
queueMicrotask(() => {
|
|
131
|
+
originalNextTick(() => {
|
|
132
|
+
if (currentExecution !== execution || execution.abandoned) return;
|
|
133
|
+
if (pendingNextTicks > 0) {
|
|
134
|
+
scheduleWorkAfterNextTicksAndMicrotasks(execution);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
performWork(execution);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function performWork(execution) {
|
|
143
|
+
if (currentExecution !== execution || execution.abandoned) return;
|
|
144
|
+
|
|
145
|
+
const queueItem = takeNextActiveQueueItem(execution);
|
|
146
|
+
if (!queueItem) {
|
|
147
|
+
if (currentExecution === execution) currentExecution = null;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
runQueueItem(queueItem);
|
|
152
|
+
scheduleWorkAfterNextTicksAndMicrotasks(execution);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function drainReadyImmediatesSynchronously(execution) {
|
|
156
|
+
for (let i = 0; i < 1000; i++) {
|
|
157
|
+
const queueItem = takeNextActiveQueueItem(execution);
|
|
158
|
+
if (!queueItem) return;
|
|
159
|
+
runQueueItem(queueItem);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function runQueueItem(queueItem) {
|
|
164
|
+
const { callback, args, immediateObject } = queueItem;
|
|
165
|
+
immediateObject[INTERNALS].queueItem = null;
|
|
166
|
+
clearQueueItem(queueItem);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
callback(...args);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
queueMicrotask(() => {
|
|
172
|
+
throw err;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function bindCurrentAsyncStores(callback) {
|
|
178
|
+
const stores = [];
|
|
179
|
+
const bag = globalThis.__CREEK_ALS;
|
|
180
|
+
if (bag && typeof bag === "object") {
|
|
181
|
+
for (const als of Object.values(bag)) {
|
|
182
|
+
if (
|
|
183
|
+
als &&
|
|
184
|
+
typeof als.getStore === "function" &&
|
|
185
|
+
typeof als.run === "function"
|
|
186
|
+
) {
|
|
187
|
+
const store = als.getStore();
|
|
188
|
+
if (store !== undefined) stores.push([als, store]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (stores.length === 0) return callback;
|
|
193
|
+
|
|
194
|
+
return (...args) => {
|
|
195
|
+
let invoke = () => callback(...args);
|
|
196
|
+
for (let i = stores.length - 1; i >= 0; i--) {
|
|
197
|
+
const [als, store] = stores[i];
|
|
198
|
+
const next = invoke;
|
|
199
|
+
invoke = () => als.run(store, next);
|
|
200
|
+
}
|
|
201
|
+
return invoke();
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function takeNextActiveQueueItem(execution) {
|
|
206
|
+
while (execution.queuedImmediates.length > 0) {
|
|
207
|
+
const item = execution.queuedImmediates.shift();
|
|
208
|
+
if (!item.isCleared) return item;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function clearQueueItem(item) {
|
|
214
|
+
item.isCleared = true;
|
|
215
|
+
item.callback = null;
|
|
216
|
+
item.args = null;
|
|
217
|
+
item.immediateObject = null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function scheduleQueuedImmediatesAsNative(execution) {
|
|
221
|
+
execution.abandoned = true;
|
|
222
|
+
for (const queueItem of execution.queuedImmediates) {
|
|
223
|
+
if (queueItem.isCleared) continue;
|
|
224
|
+
const nativeImmediate = originalSetImmediate(queueItem.callback, ...queueItem.args);
|
|
225
|
+
const internals = queueItem.immediateObject[INTERNALS];
|
|
226
|
+
internals.queueItem = null;
|
|
227
|
+
internals.nativeImmediate = nativeImmediate;
|
|
228
|
+
if (!internals.hasRef) nativeImmediate?.unref?.();
|
|
229
|
+
clearQueueItem(queueItem);
|
|
230
|
+
}
|
|
231
|
+
execution.queuedImmediates.length = 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function patchedNextTick(callback, ...args) {
|
|
235
|
+
if (currentExecution === null || typeof callback !== "function") {
|
|
236
|
+
return originalNextTick(callback, ...args);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pendingNextTicks++;
|
|
240
|
+
return originalNextTick(() => {
|
|
241
|
+
pendingNextTicks--;
|
|
242
|
+
try {
|
|
243
|
+
callback(...args);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
queueMicrotask(() => {
|
|
246
|
+
throw err;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function patchedSetImmediate(callback, ...args) {
|
|
253
|
+
if (currentExecution === null) {
|
|
254
|
+
return originalSetImmediate(callback, ...args);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (typeof callback !== "function") {
|
|
258
|
+
return originalSetImmediate(callback, ...args);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const immediateObject = new CreekImmediate();
|
|
262
|
+
const queueItem = {
|
|
263
|
+
isCleared: false,
|
|
264
|
+
callback: bindCurrentAsyncStores(callback),
|
|
265
|
+
args,
|
|
266
|
+
immediateObject,
|
|
267
|
+
};
|
|
268
|
+
immediateObject[INTERNALS].queueItem = queueItem;
|
|
269
|
+
currentExecution.queuedImmediates.push(queueItem);
|
|
270
|
+
return immediateObject;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function patchedClearImmediate(immediateObject) {
|
|
274
|
+
if (
|
|
275
|
+
immediateObject &&
|
|
276
|
+
typeof immediateObject === "object" &&
|
|
277
|
+
Object.prototype.hasOwnProperty.call(immediateObject, INTERNALS)
|
|
278
|
+
) {
|
|
279
|
+
immediateObject[Symbol.dispose]();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
return originalClearImmediate(immediateObject);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
install();
|
package/src/shims/fs.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Minimal fs shim for CF Workers — Next.js server needs basic fs operations
|
|
2
|
+
// for manifest loading and incremental cache. We provide no-ops since
|
|
3
|
+
// manifests are embedded and cache uses DO (Phase 2).
|
|
4
|
+
const noop = () => {};
|
|
5
|
+
const noopSync = () => undefined;
|
|
6
|
+
|
|
7
|
+
// Binary files in __USER_FILES are base64-encoded with a sentinel prefix
|
|
8
|
+
// so readFile callers (e.g. next/og node-runtime routes reading font
|
|
9
|
+
// files) receive real Uint8Array/Buffer bytes instead of a UTF-8 string.
|
|
10
|
+
const BINARY_SENTINEL = "__CREEK_B64__";
|
|
11
|
+
|
|
12
|
+
function decodeBase64(b64) {
|
|
13
|
+
// CF Workers and Node.js both support atob; the result is a binary
|
|
14
|
+
// string that we map to a Uint8Array byte-by-byte.
|
|
15
|
+
const binStr = atob(b64);
|
|
16
|
+
const out = new Uint8Array(binStr.length);
|
|
17
|
+
for (let i = 0; i < binStr.length; i++) out[i] = binStr.charCodeAt(i);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function maybeDecodeBinary(value, enc) {
|
|
22
|
+
if (typeof value !== "string" || !value.startsWith(BINARY_SENTINEL)) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
const bytes = decodeBase64(value.slice(BINARY_SENTINEL.length));
|
|
26
|
+
if (!enc) return bytes;
|
|
27
|
+
if (enc === "utf8" || enc === "utf-8" || enc === "UTF-8") {
|
|
28
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
29
|
+
}
|
|
30
|
+
if (enc === "base64") return value.slice(BINARY_SENTINEL.length);
|
|
31
|
+
if (typeof enc === "object" && enc.encoding) return maybeDecodeBinary(value, enc.encoding);
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizePath(filePath) {
|
|
36
|
+
return String(filePath).replace(/\\/g, "/");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function trimLeadingSlashes(filePath) {
|
|
40
|
+
return normalizePath(filePath).replace(/^\/+/, "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function directoryCandidates(dirPath) {
|
|
44
|
+
const normalized = trimLeadingSlashes(dirPath).replace(/\/+$/, "");
|
|
45
|
+
const candidates = [normalized];
|
|
46
|
+
for (const marker of ["node_modules/", ".next/"]) {
|
|
47
|
+
const index = normalized.lastIndexOf(marker);
|
|
48
|
+
if (index > 0) candidates.push(normalized.slice(index));
|
|
49
|
+
}
|
|
50
|
+
return [...new Set(candidates.filter(Boolean))];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function relativeToEmbeddedDirectory(key, dirPath) {
|
|
54
|
+
const normalizedKey = trimLeadingSlashes(key);
|
|
55
|
+
for (const candidate of directoryCandidates(dirPath)) {
|
|
56
|
+
if (normalizedKey.startsWith(candidate + "/")) {
|
|
57
|
+
return normalizedKey.slice(candidate.length + 1);
|
|
58
|
+
}
|
|
59
|
+
const marker = "/" + candidate + "/";
|
|
60
|
+
const index = normalizedKey.lastIndexOf(marker);
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
return normalizedKey.slice(index + marker.length);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function listUserFilesInDirectory(dirPath) {
|
|
69
|
+
const files = globalThis.__USER_FILES;
|
|
70
|
+
if (!files) return [];
|
|
71
|
+
const entries = new Set();
|
|
72
|
+
for (const key in files) {
|
|
73
|
+
const relative = relativeToEmbeddedDirectory(key, dirPath);
|
|
74
|
+
if (!relative) continue;
|
|
75
|
+
const entry = relative.split("/")[0];
|
|
76
|
+
if (entry) entries.add(entry);
|
|
77
|
+
}
|
|
78
|
+
return [...entries];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isUserFilesDirectory(filePath) {
|
|
82
|
+
return listUserFilesInDirectory(filePath).length > 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function joinPath(dirPath, entry) {
|
|
86
|
+
return normalizePath(dirPath).replace(/\/+$/, "") + "/" + entry;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Look up a path in __USER_FILES (user-side text files like data.json that
|
|
90
|
+
// route handlers read via fs.readFileSync). Embedded keys are paths relative
|
|
91
|
+
// to outputFileTracingRoot; runtime requests come through process.cwd()
|
|
92
|
+
// joined with a relative path. We do bidirectional suffix matching so both
|
|
93
|
+
// single-app and monorepo setups resolve correctly.
|
|
94
|
+
function findInUserFiles(filePath) {
|
|
95
|
+
const files = globalThis.__USER_FILES;
|
|
96
|
+
if (!files) return undefined;
|
|
97
|
+
const requestedPath = normalizePath(filePath);
|
|
98
|
+
if (files[requestedPath] !== undefined) return files[requestedPath];
|
|
99
|
+
// Normalize the requested path to a relative form for comparison.
|
|
100
|
+
const requestedTail = trimLeadingSlashes(requestedPath);
|
|
101
|
+
for (const key in files) {
|
|
102
|
+
const normalizedKey = normalizePath(key);
|
|
103
|
+
// Embedded key is a suffix of the requested path:
|
|
104
|
+
// key = "app/dashboard/data.json"
|
|
105
|
+
// req = "/app/dashboard/data.json" (cwd "/" + relative)
|
|
106
|
+
if (requestedPath.endsWith("/" + normalizedKey) || requestedPath === normalizedKey) return files[key];
|
|
107
|
+
// Requested tail is a suffix of the embedded key (monorepo case):
|
|
108
|
+
// key = "apps/www/app/data.json"
|
|
109
|
+
// req = "/app/data.json" (page used a project-relative path)
|
|
110
|
+
if (normalizedKey.endsWith("/" + requestedTail) || normalizedKey === requestedTail) return files[key];
|
|
111
|
+
}
|
|
112
|
+
// Last-resort: match on basename for bundled sibling assets whose
|
|
113
|
+
// request path is derived from `fileURLToPath(new URL('./X', import.meta.url))`
|
|
114
|
+
// and ends up with a chunk-relative path that shares nothing with the
|
|
115
|
+
// embedded `node_modules/...` key except the filename. Only applies to
|
|
116
|
+
// binary assets (wasm / fonts / images) and TypeScript's default lib
|
|
117
|
+
// declaration files. The latter may be requested as bare `lib.es5.d.ts`
|
|
118
|
+
// because the bundled TypeScript module has no meaningful __filename.
|
|
119
|
+
// Fixes next/og node-runtime `fs.readFileSync` of bundled wasm/ttf and
|
|
120
|
+
// twoslash loading `typescript/lib/lib.*.d.ts` in Workers.
|
|
121
|
+
const reqBase = requestedPath.split("/").pop() || "";
|
|
122
|
+
if (/\.(wasm|ttf|otf|woff2?)$/i.test(reqBase) || /^lib\..*\.d\.ts$/i.test(reqBase)) {
|
|
123
|
+
for (const key in files) {
|
|
124
|
+
if (normalizePath(key).split("/").pop() === reqBase) return files[key];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const existsSync = (filePath) => {
|
|
131
|
+
if (findInUserFiles(filePath) !== undefined) return true;
|
|
132
|
+
if (isUserFilesDirectory(filePath)) return true;
|
|
133
|
+
if (typeof globalThis.__MANIFESTS === "undefined") return false;
|
|
134
|
+
for (const key of Object.keys(globalThis.__MANIFESTS)) {
|
|
135
|
+
if (key === filePath || key.endsWith(filePath)) return true;
|
|
136
|
+
if (filePath.includes(".next/")) {
|
|
137
|
+
const tail = ".next/" + filePath.split(".next/").pop();
|
|
138
|
+
const keyTail = key.includes(".next/") ? ".next/" + key.split(".next/").pop() : "";
|
|
139
|
+
if (tail === keyTail) return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
};
|
|
144
|
+
export const readFileSync = (filePath, enc) => {
|
|
145
|
+
// Try reading from embedded manifests
|
|
146
|
+
if (typeof globalThis.__MANIFESTS !== "undefined") {
|
|
147
|
+
for (const [key, val] of Object.entries(globalThis.__MANIFESTS)) {
|
|
148
|
+
if (key === filePath || key.endsWith(filePath)) return val;
|
|
149
|
+
// Match by .next/ relative tail — handles different path prefixes
|
|
150
|
+
// e.g. /bundle/.next/routes-manifest.json → .next/routes-manifest.json
|
|
151
|
+
if (filePath.includes(".next/")) {
|
|
152
|
+
const tail = ".next/" + filePath.split(".next/").pop();
|
|
153
|
+
const keyTail = key.includes(".next/") ? ".next/" + key.split(".next/").pop() : "";
|
|
154
|
+
if (tail === keyTail) return val;
|
|
155
|
+
}
|
|
156
|
+
// Last resort: match by filename
|
|
157
|
+
if (filePath.split("/").pop() === key.split("/").pop()) return val;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Then try user-side data files (data.json, fixtures, fonts, etc.).
|
|
161
|
+
// Binary files are stored as base64 with a sentinel prefix — decode
|
|
162
|
+
// them lazily based on the caller's requested encoding.
|
|
163
|
+
const userContent = findInUserFiles(filePath);
|
|
164
|
+
if (userContent !== undefined) return maybeDecodeBinary(userContent, enc);
|
|
165
|
+
// Throw ENOENT like real fs — Next.js loadManifest relies on this
|
|
166
|
+
// to distinguish between missing and empty files.
|
|
167
|
+
const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`);
|
|
168
|
+
err.code = "ENOENT";
|
|
169
|
+
throw err;
|
|
170
|
+
};
|
|
171
|
+
export const writeFileSync = noop;
|
|
172
|
+
export const mkdirSync = noop;
|
|
173
|
+
export const unlinkSync = noop;
|
|
174
|
+
export const readdirSync = (dirPath, options) => {
|
|
175
|
+
const entries = listUserFilesInDirectory(dirPath);
|
|
176
|
+
if (options && typeof options === "object" && options.withFileTypes) {
|
|
177
|
+
return entries.map((name) => ({
|
|
178
|
+
name,
|
|
179
|
+
isFile: () => findInUserFiles(joinPath(dirPath, name)) !== undefined,
|
|
180
|
+
isDirectory: () => isUserFilesDirectory(joinPath(dirPath, name)),
|
|
181
|
+
isSymbolicLink: () => false,
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
return entries;
|
|
185
|
+
};
|
|
186
|
+
export const realpathSync = Object.assign((filePath) => String(filePath), {
|
|
187
|
+
native: (filePath) => String(filePath),
|
|
188
|
+
});
|
|
189
|
+
export const statSync = (filePath) => {
|
|
190
|
+
const userContent = findInUserFiles(filePath);
|
|
191
|
+
const fileExists = userContent !== undefined;
|
|
192
|
+
const directoryExists = !fileExists && isUserFilesDirectory(filePath);
|
|
193
|
+
const exists = fileExists || directoryExists;
|
|
194
|
+
return {
|
|
195
|
+
isFile: () => fileExists,
|
|
196
|
+
isDirectory: () => directoryExists,
|
|
197
|
+
isSymbolicLink: () => false,
|
|
198
|
+
mtime: new Date(),
|
|
199
|
+
size: fileExists ? (maybeDecodeBinary(userContent, "utf8")?.length || 0) : 0,
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
export const accessSync = noop;
|
|
203
|
+
export const createReadStream = () => { throw new Error("fs.createReadStream not available in CF Workers"); };
|
|
204
|
+
export const createWriteStream = () => { throw new Error("fs.createWriteStream not available in CF Workers"); };
|
|
205
|
+
|
|
206
|
+
// readAll is used by Turbopack app-route runtime
|
|
207
|
+
export const readAll = readFileSync;
|
|
208
|
+
|
|
209
|
+
export const promises = {
|
|
210
|
+
readFile: async (filePath, enc) => readFileSync(filePath, enc),
|
|
211
|
+
readAll: async (filePath, enc) => readFileSync(filePath, enc),
|
|
212
|
+
writeFile: async () => {},
|
|
213
|
+
mkdir: async () => {},
|
|
214
|
+
readdir: async (dirPath, options) => readdirSync(dirPath, options),
|
|
215
|
+
stat: async (filePath) => statSync(filePath),
|
|
216
|
+
access: async () => {},
|
|
217
|
+
unlink: async () => {},
|
|
218
|
+
rm: async () => {},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export default {
|
|
222
|
+
existsSync, readFileSync, readAll, writeFileSync, mkdirSync, unlinkSync,
|
|
223
|
+
readdirSync, realpathSync, statSync, accessSync, createReadStream, createWriteStream,
|
|
224
|
+
promises,
|
|
225
|
+
};
|