env-runner 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -9
- package/dist/_chunks/common-base-runner.d.mts +21 -53
- package/dist/_chunks/common-base-runner.mjs +4 -1
- package/dist/_chunks/common-runtime-deps.d.mts +9 -33
- package/dist/_chunks/common-worker-utils.mjs +50 -1
- package/dist/_chunks/deno-process-runner.d.mts +2 -3
- package/dist/_chunks/deno-process-runner.mjs +14 -36
- package/dist/_chunks/libs/es-module-lexer.mjs +225 -65
- package/dist/_chunks/miniflare-runner.d.mts +53 -114
- package/dist/_chunks/miniflare-runner.mjs +669 -80
- package/dist/_chunks/netlify-runner.d.mts +6 -25
- package/dist/_chunks/node-worker-runner.d.mts +2 -3
- package/dist/_chunks/types.d.mts +13 -23
- package/dist/_chunks/vercel-runner.d.mts +2 -3
- package/dist/_chunks/vercel-runner.mjs +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +33 -53
- package/dist/index.mjs +2 -1
- package/dist/runners/bun-process/runner.d.mts +2 -2
- package/dist/runners/bun-process/runner.mjs +1 -1
- package/dist/runners/bun-process/worker.mjs +8 -11
- package/dist/runners/deno-process/worker.mjs +17 -42
- package/dist/runners/node-process/runner.d.mts +2 -2
- package/dist/runners/node-process/worker.mjs +8 -11
- package/dist/runners/node-worker/worker.mjs +8 -11
- package/dist/runners/self/runner.d.mts +2 -2
- package/dist/runners/vercel/queue-dev.d.mts +16 -53
- package/dist/vite.d.mts +6 -17
- package/package.json +11 -11
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { expandVirtualInvalidation, stripVirtualTypeScript, virtualModuleFormat } from "./virtual-loader.mjs";
|
|
2
2
|
import { BaseEnvRunner } from "./common-base-runner.mjs";
|
|
3
|
+
import { isVirtualSpecifier } from "./common-worker-utils.mjs";
|
|
3
4
|
import { resolveRuntimeDep } from "./common-runtime-deps.mjs";
|
|
4
5
|
import { init, parse } from "./libs/cjs-module-lexer.mjs";
|
|
5
6
|
import { init as init$1, parse as parse$1 } from "./libs/es-module-lexer.mjs";
|
|
6
|
-
import { isVirtualSpecifier } from "./common-worker-utils.mjs";
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import { proxyUpgrade } from "httpxy";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
12
|
import { resolveModulePath } from "exsolve";
|
|
12
|
-
import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
|
+
import { parseEnv } from "node:util";
|
|
14
15
|
const IPC_PATH = "/__env_runner_ipc";
|
|
15
16
|
const IPC_BINDING = "__ENV_RUNNER_IPC";
|
|
17
|
+
const UNSAFE_EVAL_BINDING = "__ENV_RUNNER_UNSAFE_EVAL__";
|
|
16
18
|
function generateWrapper(entryPath, opts) {
|
|
19
|
+
const processShim = opts?.nodeCompat === false ? "" : `import __process from "node:process";
|
|
20
|
+
if (!globalThis.process) { globalThis.process = __process; }`;
|
|
17
21
|
const staticReExport = opts?.dynamicOnly ? "" : `export * from ${JSON.stringify(entryPath)};`;
|
|
18
22
|
const explicitExports = opts?.dynamicOnly && opts.exports?.length ? opts.exports.map((name) => `export { ${name} } from ${JSON.stringify(entryPath)};`).join("\n") : "";
|
|
19
23
|
const fetchBody = opts?.captureErrors ?? true ? `try {
|
|
20
|
-
return await
|
|
24
|
+
return await __server.fetch(request, env, ctx);
|
|
21
25
|
} catch (e) {
|
|
22
26
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
23
27
|
const body = JSON.stringify({
|
|
@@ -29,20 +33,94 @@ function generateWrapper(entryPath, opts) {
|
|
|
29
33
|
status: 500,
|
|
30
34
|
headers: { "Content-Type": "application/json", "X-Env-Runner-Error": "1" },
|
|
31
35
|
});
|
|
32
|
-
}` : `return
|
|
33
|
-
return
|
|
34
|
-
if (!globalThis.process) { globalThis.process = __process; }
|
|
36
|
+
}` : `return __server.fetch(request, env, ctx);`;
|
|
37
|
+
return `${processShim}
|
|
35
38
|
${staticReExport}
|
|
36
39
|
${explicitExports}
|
|
37
40
|
|
|
38
41
|
const __IPC_PATH = "${IPC_PATH}";
|
|
39
42
|
const __IPC_BINDING = "${IPC_BINDING}";
|
|
43
|
+
const __UNSAFE_EVAL_BINDING = "${UNSAFE_EVAL_BINDING}";
|
|
40
44
|
const __entryPath = ${JSON.stringify(entryPath)};
|
|
41
45
|
let __userEntry;
|
|
46
|
+
let __server;
|
|
42
47
|
let __ipcInitialized = false;
|
|
43
48
|
let __serverWs;
|
|
44
49
|
let __currentEnv;
|
|
45
50
|
|
|
51
|
+
const __userEnvs = new WeakMap();
|
|
52
|
+
|
|
53
|
+
// \`env\` without env-runner's internal bindings (cached per env object).
|
|
54
|
+
function __userEnv(env) {
|
|
55
|
+
let userEnv = __userEnvs.get(env);
|
|
56
|
+
if (!userEnv) {
|
|
57
|
+
userEnv = { ...env };
|
|
58
|
+
delete userEnv[__IPC_BINDING];
|
|
59
|
+
delete userEnv[__UNSAFE_EVAL_BINDING];
|
|
60
|
+
__userEnvs.set(env, userEnv);
|
|
61
|
+
}
|
|
62
|
+
return userEnv;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Mirrors srvx's CloudflareServer (srvx/cloudflare): plugins, then the
|
|
66
|
+
// \`error\` handler as the outermost middleware, then the middleware chain.
|
|
67
|
+
function __createServer(entry) {
|
|
68
|
+
const server = {
|
|
69
|
+
runtime: "cloudflare",
|
|
70
|
+
options: {
|
|
71
|
+
...entry,
|
|
72
|
+
middleware: [...(entry.middleware || [])],
|
|
73
|
+
fetch: entry.fetch
|
|
74
|
+
? (request) =>
|
|
75
|
+
entry.fetch(request, request.runtime?.cloudflare?.env, request.runtime?.cloudflare?.context)
|
|
76
|
+
: () => new Response("No fetch handler exported", { status: 500 }),
|
|
77
|
+
},
|
|
78
|
+
serve() {},
|
|
79
|
+
ready: () => Promise.resolve(server),
|
|
80
|
+
close: () => Promise.resolve(),
|
|
81
|
+
};
|
|
82
|
+
for (const plugin of entry.plugins || []) {
|
|
83
|
+
plugin(server);
|
|
84
|
+
}
|
|
85
|
+
const errorHandler = server.options.error;
|
|
86
|
+
if (errorHandler) {
|
|
87
|
+
server.options.middleware.unshift((_request, next) => {
|
|
88
|
+
try {
|
|
89
|
+
const res = next();
|
|
90
|
+
return typeof res?.then === "function" ? res.then(undefined, (error) => errorHandler(error)) : res;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return errorHandler(error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
let handler = server.options.fetch;
|
|
97
|
+
const middleware = server.options.middleware;
|
|
98
|
+
for (let i = middleware.length - 1; i >= 0; i--) {
|
|
99
|
+
const mw = middleware[i];
|
|
100
|
+
const next = handler;
|
|
101
|
+
handler = (request) => mw(request, () => next(request));
|
|
102
|
+
}
|
|
103
|
+
server.fetch = (request, env, context) => {
|
|
104
|
+
const userEnv = __userEnv(env);
|
|
105
|
+
Object.defineProperties(request, {
|
|
106
|
+
waitUntil: { value: context.waitUntil.bind(context) },
|
|
107
|
+
runtime: {
|
|
108
|
+
enumerable: true,
|
|
109
|
+
value: { name: "cloudflare", cloudflare: { env: userEnv, context } },
|
|
110
|
+
},
|
|
111
|
+
ip: {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
configurable: true,
|
|
114
|
+
get() {
|
|
115
|
+
return request.headers.get("cf-connecting-ip");
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return handler(request);
|
|
120
|
+
};
|
|
121
|
+
return server;
|
|
122
|
+
}
|
|
123
|
+
|
|
46
124
|
async function __loadEntry(env, path) {
|
|
47
125
|
globalThis.__ENV_RUNNER_UNSAFE_EVAL__ = env.__ENV_RUNNER_UNSAFE_EVAL__;
|
|
48
126
|
const importFn = env.__ENV_RUNNER_UNSAFE_EVAL__.newAsyncFunction(
|
|
@@ -84,10 +162,12 @@ async function __handleWsMessage(env, data) {
|
|
|
84
162
|
const version = msg.version || 0;
|
|
85
163
|
try {
|
|
86
164
|
const newEntry = await __loadEntry(env, __entryPath + "?t=" + version);
|
|
165
|
+
const newServer = __createServer(newEntry);
|
|
87
166
|
if (__userEntry?.ipc?.onClose) {
|
|
88
167
|
await __userEntry.ipc.onClose();
|
|
89
168
|
}
|
|
90
169
|
__userEntry = newEntry;
|
|
170
|
+
__server = newServer;
|
|
91
171
|
__crosswsAdapter = undefined;
|
|
92
172
|
__ipcInitialized = false;
|
|
93
173
|
if (__userEntry.ipc?.onOpen) {
|
|
@@ -130,7 +210,9 @@ export default {
|
|
|
130
210
|
if (url.pathname === __IPC_PATH && request.headers.get("upgrade") === "websocket") {
|
|
131
211
|
try {
|
|
132
212
|
if (!__userEntry) {
|
|
133
|
-
|
|
213
|
+
const entry = await __loadEntry(env, __entryPath);
|
|
214
|
+
__server = __createServer(entry);
|
|
215
|
+
__userEntry = entry;
|
|
134
216
|
}
|
|
135
217
|
} catch (e) {
|
|
136
218
|
return new Response("Failed to load entry: " + String(e), { status: 500 });
|
|
@@ -164,13 +246,9 @@ export default {
|
|
|
164
246
|
// Handle WebSocket upgrade via crossws cloudflare adapter
|
|
165
247
|
if (__userEntry.websocket && request.headers.get("upgrade") === "websocket") {
|
|
166
248
|
const adapter = await __initCrossws(env, __userEntry.websocket);
|
|
167
|
-
return adapter.handleUpgrade(request, env, ctx);
|
|
249
|
+
return adapter.handleUpgrade(request, __userEnv(env), ctx);
|
|
168
250
|
}
|
|
169
251
|
|
|
170
|
-
const entryFetch = __userEntry.fetch;
|
|
171
|
-
if (!entryFetch) {
|
|
172
|
-
return new Response("No fetch handler exported", { status: 500 });
|
|
173
|
-
}
|
|
174
252
|
__currentEnv = env;
|
|
175
253
|
try {
|
|
176
254
|
${fetchBody}
|
|
@@ -181,6 +259,67 @@ export default {
|
|
|
181
259
|
};
|
|
182
260
|
`;
|
|
183
261
|
}
|
|
262
|
+
function expandDotenv(parsed, processEnv) {
|
|
263
|
+
const running = {};
|
|
264
|
+
for (const [key, raw] of Object.entries(parsed)) running[key] = processEnv[key] = (processEnv[key] && processEnv[key] !== raw ? processEnv[key] : expandDotenvValue(raw, processEnv, running)).replace(/\\\$/g, "$");
|
|
265
|
+
return processEnv;
|
|
266
|
+
}
|
|
267
|
+
function expandDotenvValue(value, processEnv, running) {
|
|
268
|
+
const env = {
|
|
269
|
+
...running,
|
|
270
|
+
...processEnv
|
|
271
|
+
};
|
|
272
|
+
const regex = /(?<!\\)\$\{([^{}]+)\}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
273
|
+
const seen = /* @__PURE__ */ new Set();
|
|
274
|
+
let result = value;
|
|
275
|
+
let match;
|
|
276
|
+
while ((match = regex.exec(result)) !== null) {
|
|
277
|
+
seen.add(result);
|
|
278
|
+
const [template, braced, unbraced] = match;
|
|
279
|
+
const expression = braced || unbraced;
|
|
280
|
+
const splitter = expression.match(/(:\+|\+|:-|-)/)?.[0];
|
|
281
|
+
const parts = splitter ? expression.split(splitter) : [expression];
|
|
282
|
+
const key = parts.shift();
|
|
283
|
+
let replacement;
|
|
284
|
+
if (splitter === ":+" || splitter === "+") replacement = env[key] ? parts.join(splitter) : "";
|
|
285
|
+
else {
|
|
286
|
+
const found = env[key];
|
|
287
|
+
replacement = found && !seen.has(found) ? found : parts.join(splitter ?? "");
|
|
288
|
+
}
|
|
289
|
+
result = result.replace(template, replacement);
|
|
290
|
+
if (result === running[key]) break;
|
|
291
|
+
regex.lastIndex = 0;
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
function readDotenvFile(path) {
|
|
296
|
+
return existsSync(path) ? parseEnv(readFileSync(path, "utf8")) : void 0;
|
|
297
|
+
}
|
|
298
|
+
function booleanEnv(name, defaultValue) {
|
|
299
|
+
const value = process.env[name]?.toLowerCase();
|
|
300
|
+
return value === "true" ? true : value === "false" ? false : defaultValue;
|
|
301
|
+
}
|
|
302
|
+
function loadDevVars(opts) {
|
|
303
|
+
const { configDir, env, envFiles } = opts;
|
|
304
|
+
if (!envFiles?.length) {
|
|
305
|
+
const devVars = resolve(configDir, ".dev.vars");
|
|
306
|
+
const loaded = env !== void 0 && readDotenvFile(`${devVars}.${env}`) || readDotenvFile(devVars);
|
|
307
|
+
if (loaded) return loaded;
|
|
308
|
+
}
|
|
309
|
+
if (!booleanEnv("CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV", true)) return;
|
|
310
|
+
const files = envFiles ?? [
|
|
311
|
+
".env",
|
|
312
|
+
".env.local",
|
|
313
|
+
...env ? [`.env.${env}`, `.env.${env}.local`] : []
|
|
314
|
+
];
|
|
315
|
+
const parsed = {};
|
|
316
|
+
for (const file of files) Object.assign(parsed, readDotenvFile(resolve(configDir, file)));
|
|
317
|
+
const processEnv = {};
|
|
318
|
+
if (opts.hasSecrets || booleanEnv("CLOUDFLARE_INCLUDE_PROCESS_ENV", false)) {
|
|
319
|
+
for (const [key, value] of Object.entries(process.env)) if (typeof value === "string") processEnv[key] = value;
|
|
320
|
+
}
|
|
321
|
+
return expandDotenv(parsed, processEnv);
|
|
322
|
+
}
|
|
184
323
|
const WRANGLER_CONFIG_FILENAMES = [
|
|
185
324
|
"wrangler.json",
|
|
186
325
|
"wrangler.jsonc",
|
|
@@ -198,45 +337,143 @@ const WRANGLER_OPTION_DENYLIST = /* @__PURE__ */ new Set([
|
|
|
198
337
|
"unsafeModuleFallbackService",
|
|
199
338
|
"unsafeUseModuleFallbackService"
|
|
200
339
|
]);
|
|
340
|
+
const WRANGLER_OPTION_DROPLIST = /* @__PURE__ */ new Set([
|
|
341
|
+
"assets",
|
|
342
|
+
"serviceBindings",
|
|
343
|
+
"workflows",
|
|
344
|
+
"queueConsumers",
|
|
345
|
+
"tails",
|
|
346
|
+
"streamingTails"
|
|
347
|
+
]);
|
|
348
|
+
const WRANGLER_DROPPED_OPTION_NAMES = {
|
|
349
|
+
assets: "assets",
|
|
350
|
+
serviceBindings: "services",
|
|
351
|
+
workflows: "workflows",
|
|
352
|
+
queueConsumers: "queues.consumers",
|
|
353
|
+
tails: "tail_consumers",
|
|
354
|
+
streamingTails: "streaming_tail_consumers",
|
|
355
|
+
durableObjects: "durable_objects"
|
|
356
|
+
};
|
|
357
|
+
function addDropped(dropped, name, details) {
|
|
358
|
+
let set = dropped.get(name);
|
|
359
|
+
if (!set) {
|
|
360
|
+
set = /* @__PURE__ */ new Set();
|
|
361
|
+
dropped.set(name, set);
|
|
362
|
+
}
|
|
363
|
+
for (const detail of details) set.add(detail);
|
|
364
|
+
}
|
|
365
|
+
function warnDroppedWranglerOptions(dropped) {
|
|
366
|
+
if (dropped.size === 0) return;
|
|
367
|
+
const list = [...dropped].map(([name, details]) => details.size > 0 ? `${name} (${[...details].join(", ")})` : name).join(", ");
|
|
368
|
+
console.warn(`[env-runner] wrangler config options not supported by the miniflare dev runner were ignored: ${list}; pass them via miniflareOptions to opt in.`);
|
|
369
|
+
}
|
|
201
370
|
function isPlainObject(value) {
|
|
202
371
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
203
372
|
}
|
|
204
373
|
function isInlineWranglerConfig(opt) {
|
|
205
374
|
return isPlainObject(opt);
|
|
206
375
|
}
|
|
207
|
-
async function loadWranglerConfig(
|
|
208
|
-
|
|
376
|
+
async function loadWranglerConfig(opts) {
|
|
377
|
+
const { wrangler: opt, env, entryPath, wranglerModule, envFiles } = opts;
|
|
378
|
+
if (!opt) return {};
|
|
209
379
|
const inline = isInlineWranglerConfig(opt) ? opt : void 0;
|
|
210
380
|
let configPath;
|
|
211
|
-
|
|
212
|
-
|
|
381
|
+
const explicitPath = typeof opt === "string" ? opt : opts.configPath;
|
|
382
|
+
if (explicitPath) {
|
|
383
|
+
configPath = resolve(explicitPath);
|
|
213
384
|
if (!existsSync(configPath)) {
|
|
214
385
|
console.warn(`[env-runner] wrangler config requested but not found at "${configPath}"`);
|
|
215
|
-
return;
|
|
386
|
+
if (!inline) return {};
|
|
387
|
+
configPath = void 0;
|
|
216
388
|
}
|
|
217
|
-
} else
|
|
389
|
+
} else {
|
|
218
390
|
configPath = findWranglerConfig(entryPath);
|
|
219
|
-
if (!configPath) {
|
|
220
|
-
console.warn("[env-runner] wrangler config requested but none found
|
|
221
|
-
return;
|
|
391
|
+
if (!configPath && !inline) {
|
|
392
|
+
console.warn("[env-runner] wrangler config requested but none found (searched the entry's directory, then from the cwd up to the filesystem root)");
|
|
393
|
+
return {};
|
|
222
394
|
}
|
|
223
|
-
}
|
|
395
|
+
}
|
|
224
396
|
const wrangler = await resolveRuntimeDep({
|
|
225
397
|
name: "wrangler",
|
|
226
398
|
option: "wranglerModule",
|
|
227
399
|
value: wranglerModule
|
|
228
400
|
});
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
401
|
+
const dropped = /* @__PURE__ */ new Map();
|
|
402
|
+
if (!wrangler?.unstable_readConfig || !wrangler.unstable_getMiniflareWorkerOptions) {
|
|
403
|
+
const unsupported = /* @__PURE__ */ new Map();
|
|
404
|
+
const file = configPath ? readWranglerConfigMinimal(configPath, env) : void 0;
|
|
405
|
+
const inlineConfig = inline ? applyWranglerEnv(inline, env) : void 0;
|
|
406
|
+
let options = mergeWranglerMiniflareOptions(file && (mapWranglerConfigToMiniflare(file.config, dropped, unsupported) ?? {}), inlineConfig && mapWranglerConfigToMiniflare(inlineConfig, dropped, unsupported));
|
|
407
|
+
if (file || inlineConfig) options = applyMinimalDevVars(options, [file?.config, inlineConfig], {
|
|
408
|
+
configDir: configPath ? dirname(configPath) : process.cwd(),
|
|
409
|
+
env,
|
|
410
|
+
envFiles
|
|
411
|
+
});
|
|
412
|
+
filterLocalDurableObjects(options, dropped, (inline && wranglerWorkerName(inline, env)) ?? file?.workerName);
|
|
413
|
+
warnDroppedWranglerOptions(dropped);
|
|
414
|
+
warnUnsupportedMinimalBindings(unsupported);
|
|
415
|
+
return {
|
|
416
|
+
options,
|
|
417
|
+
configFile: file ? configPath : void 0
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
let fileLoaded = false;
|
|
421
|
+
let fileOptions;
|
|
422
|
+
let fileSecrets;
|
|
423
|
+
let fileWorkerName;
|
|
424
|
+
if (configPath) try {
|
|
425
|
+
const config = wrangler.unstable_readConfig({
|
|
232
426
|
config: configPath,
|
|
233
427
|
env
|
|
234
|
-
}, { hideWarnings:
|
|
428
|
+
}, { hideWarnings: !claimWranglerWarnings(configPath, env) });
|
|
429
|
+
fileOptions = pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(config, env, { envFiles }).workerOptions, dropped);
|
|
430
|
+
fileWorkerName = config?.name;
|
|
431
|
+
fileSecrets = config?.secrets;
|
|
432
|
+
fileLoaded = true;
|
|
235
433
|
} catch (error) {
|
|
236
|
-
|
|
237
|
-
console.warn(`[env-runner] failed to load wrangler config ${desc}: ${error.message}`);
|
|
238
|
-
return;
|
|
434
|
+
warnWranglerLoadError(`"${configPath}"`, error);
|
|
239
435
|
}
|
|
436
|
+
let inlineOptions;
|
|
437
|
+
if (inline) try {
|
|
438
|
+
const inlineEnv = env && isPlainObject(inline.env) && inline.env[env] ? env : void 0;
|
|
439
|
+
const { env: _env, ...inlineTopLevel } = inline;
|
|
440
|
+
const inlineConfig = readInlineWranglerConfig(wrangler, inlineEnv ? inline : inlineTopLevel, inlineEnv);
|
|
441
|
+
inlineConfig.userConfigPath = configPath;
|
|
442
|
+
if (fileLoaded && inlineConfig.secrets === void 0) inlineConfig.secrets = fileSecrets;
|
|
443
|
+
inlineOptions = pickWranglerMiniflareOptions(wrangler.unstable_getMiniflareWorkerOptions(inlineConfig, env, { envFiles }).workerOptions, dropped);
|
|
444
|
+
if (inlineOptions) {
|
|
445
|
+
delete inlineOptions.rootPath;
|
|
446
|
+
if (fileLoaded) filterInlineDevVarBindings(inlineOptions, fileOptions, inlineConfig);
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
warnWranglerLoadError("(inline)", error);
|
|
450
|
+
}
|
|
451
|
+
const options = mergeWranglerMiniflareOptions(fileOptions, inlineOptions);
|
|
452
|
+
filterLocalDurableObjects(options, dropped, (inline && wranglerWorkerName(inline, env)) ?? fileWorkerName);
|
|
453
|
+
warnDroppedWranglerOptions(dropped);
|
|
454
|
+
return {
|
|
455
|
+
options,
|
|
456
|
+
configFile: fileLoaded ? configPath : void 0
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const _shownWranglerWarnings = /* @__PURE__ */ new Set();
|
|
460
|
+
function claimWranglerWarnings(configPath, env) {
|
|
461
|
+
let version = "";
|
|
462
|
+
try {
|
|
463
|
+
const stat = statSync(configPath);
|
|
464
|
+
version = `${stat.mtimeMs}:${stat.size}`;
|
|
465
|
+
} catch {}
|
|
466
|
+
const key = JSON.stringify([
|
|
467
|
+
resolve(configPath),
|
|
468
|
+
env ?? null,
|
|
469
|
+
version
|
|
470
|
+
]);
|
|
471
|
+
if (_shownWranglerWarnings.has(key)) return false;
|
|
472
|
+
_shownWranglerWarnings.add(key);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
function warnWranglerLoadError(desc, error) {
|
|
476
|
+
console.warn(`[env-runner] failed to load wrangler config ${desc}: ${error.message}`);
|
|
240
477
|
}
|
|
241
478
|
function mergeWranglerMiniflareOptions(base, override) {
|
|
242
479
|
if (!base) return override;
|
|
@@ -253,6 +490,12 @@ function mergeWranglerMiniflareOptions(base, override) {
|
|
|
253
490
|
}
|
|
254
491
|
return out;
|
|
255
492
|
}
|
|
493
|
+
function filterInlineDevVarBindings(inlineOptions, fileOptions, inlineConfig) {
|
|
494
|
+
if (!isPlainObject(inlineOptions.bindings)) return;
|
|
495
|
+
const fileBindings = isPlainObject(fileOptions?.bindings) ? fileOptions.bindings : {};
|
|
496
|
+
const declared = /* @__PURE__ */ new Set([...Object.keys(inlineConfig.vars ?? {}), ...inlineConfig.secrets?.required ?? []]);
|
|
497
|
+
inlineOptions.bindings = Object.fromEntries(Object.entries(inlineOptions.bindings).filter(([key]) => declared.has(key) || Object.hasOwn(fileBindings, key)));
|
|
498
|
+
}
|
|
256
499
|
function readInlineWranglerConfig(wrangler, inline, env) {
|
|
257
500
|
const dir = mkdtempSync(join(tmpdir(), "env-runner-wrangler-"));
|
|
258
501
|
const file = join(dir, "wrangler.json");
|
|
@@ -269,29 +512,173 @@ function readInlineWranglerConfig(wrangler, inline, env) {
|
|
|
269
512
|
});
|
|
270
513
|
}
|
|
271
514
|
}
|
|
515
|
+
const _announcedWranglerConfigs = /* @__PURE__ */ new Set();
|
|
272
516
|
function findWranglerConfig(entryPath) {
|
|
273
|
-
const
|
|
517
|
+
const cwd = process.cwd();
|
|
518
|
+
let start = cwd;
|
|
274
519
|
if (entryPath) {
|
|
275
|
-
const
|
|
276
|
-
|
|
520
|
+
const entryDir = dirname(resolve(entryPath));
|
|
521
|
+
const rel = relative(cwd, entryDir);
|
|
522
|
+
if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)) start = entryDir;
|
|
523
|
+
else {
|
|
524
|
+
const local = findWranglerConfigInDir(entryDir);
|
|
525
|
+
if (local) return local;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
for (let dir = start;; dir = dirname(dir)) {
|
|
529
|
+
const found = findWranglerConfigInDir(dir);
|
|
530
|
+
if (found) {
|
|
531
|
+
if (dir !== cwd && dir !== start && !_announcedWranglerConfigs.has(found)) {
|
|
532
|
+
_announcedWranglerConfigs.add(found);
|
|
533
|
+
console.info(`[env-runner] using wrangler config from a parent directory: ${found}`);
|
|
534
|
+
}
|
|
535
|
+
return found;
|
|
536
|
+
}
|
|
537
|
+
if (dirname(dir) === dir) return;
|
|
277
538
|
}
|
|
278
|
-
|
|
279
|
-
|
|
539
|
+
}
|
|
540
|
+
function findWranglerConfigInDir(dir) {
|
|
541
|
+
for (const name of WRANGLER_CONFIG_FILENAMES) {
|
|
280
542
|
const candidate = join(dir, name);
|
|
281
543
|
if (existsSync(candidate)) return candidate;
|
|
282
544
|
}
|
|
283
545
|
}
|
|
284
|
-
function pickWranglerMiniflareOptions(workerOptions) {
|
|
546
|
+
function pickWranglerMiniflareOptions(workerOptions, dropped) {
|
|
285
547
|
const out = {};
|
|
286
548
|
for (const [key, value] of Object.entries(workerOptions)) {
|
|
287
549
|
if (value === void 0 || WRANGLER_OPTION_DENYLIST.has(key)) continue;
|
|
550
|
+
if (WRANGLER_OPTION_DROPLIST.has(key)) {
|
|
551
|
+
if (!isEmptyOption(value)) addDropped(dropped, WRANGLER_DROPPED_OPTION_NAMES[key], describeDroppedOption(value));
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (isEmptyPickedOption(key, value)) continue;
|
|
288
555
|
out[key] = value;
|
|
289
556
|
}
|
|
290
557
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
291
558
|
}
|
|
559
|
+
function isEmptyPickedOption(key, value) {
|
|
560
|
+
const isEmptyArray = (v) => Array.isArray(v) && v.length === 0;
|
|
561
|
+
if (isEmptyArray(value) || isPlainObject(value) && Object.keys(value).length === 0) return true;
|
|
562
|
+
return key !== "bindings" && isPlainObject(value) && Object.values(value).every(isEmptyArray);
|
|
563
|
+
}
|
|
564
|
+
function isEmptyOption(value) {
|
|
565
|
+
return !value || Array.isArray(value) && value.length === 0 || isPlainObject(value) && Object.keys(value).length === 0;
|
|
566
|
+
}
|
|
567
|
+
function describeDroppedOption(value) {
|
|
568
|
+
if (Array.isArray(value)) return value.map((entry) => entry?.name).filter((name) => typeof name === "string");
|
|
569
|
+
if (isPlainObject(value)) {
|
|
570
|
+
if ("directory" in value) return typeof value.binding === "string" ? [value.binding] : [];
|
|
571
|
+
return Object.keys(value);
|
|
572
|
+
}
|
|
573
|
+
return [];
|
|
574
|
+
}
|
|
575
|
+
function filterLocalDurableObjects(options, dropped, workerName) {
|
|
576
|
+
const value = options?.durableObjects;
|
|
577
|
+
if (!options || !isPlainObject(value)) return;
|
|
578
|
+
const out = {};
|
|
579
|
+
for (const [name, binding] of Object.entries(value)) if (typeof binding === "string" || isPlainObject(binding) && !binding.scriptName) out[name] = binding;
|
|
580
|
+
else if (isPlainObject(binding) && workerName && binding.scriptName === workerName) {
|
|
581
|
+
const { scriptName: _scriptName, ...local } = binding;
|
|
582
|
+
out[name] = local;
|
|
583
|
+
} else {
|
|
584
|
+
const scriptName = isPlainObject(binding) ? binding.scriptName : void 0;
|
|
585
|
+
addDropped(dropped, "durable_objects", [`${name} → script "${String(scriptName)}"`]);
|
|
586
|
+
}
|
|
587
|
+
if (Object.keys(out).length > 0) options.durableObjects = out;
|
|
588
|
+
else delete options.durableObjects;
|
|
589
|
+
}
|
|
590
|
+
const WRANGLER_NON_INHERITABLE_KEYS = /* @__PURE__ */ new Set([
|
|
591
|
+
"vars",
|
|
592
|
+
"secrets",
|
|
593
|
+
"define",
|
|
594
|
+
"durable_objects",
|
|
595
|
+
"workflows",
|
|
596
|
+
"kv_namespaces",
|
|
597
|
+
"cloudchamber",
|
|
598
|
+
"containers",
|
|
599
|
+
"send_email",
|
|
600
|
+
"queues",
|
|
601
|
+
"connect",
|
|
602
|
+
"r2_buckets",
|
|
603
|
+
"d1_databases",
|
|
604
|
+
"vectorize",
|
|
605
|
+
"ai_search_namespaces",
|
|
606
|
+
"ai_search",
|
|
607
|
+
"websearch",
|
|
608
|
+
"agent_memory",
|
|
609
|
+
"hyperdrive",
|
|
610
|
+
"services",
|
|
611
|
+
"analytics_engine_datasets",
|
|
612
|
+
"dispatch_namespaces",
|
|
613
|
+
"mtls_certificates",
|
|
614
|
+
"tail_consumers",
|
|
615
|
+
"streaming_tail_consumers",
|
|
616
|
+
"unsafe",
|
|
617
|
+
"browser",
|
|
618
|
+
"ai",
|
|
619
|
+
"images",
|
|
620
|
+
"stream",
|
|
621
|
+
"media",
|
|
622
|
+
"pipelines",
|
|
623
|
+
"secrets_store_secrets",
|
|
624
|
+
"artifacts",
|
|
625
|
+
"unsafe_hello_world",
|
|
626
|
+
"flagship",
|
|
627
|
+
"worker_loaders",
|
|
628
|
+
"ratelimits",
|
|
629
|
+
"vpc_services",
|
|
630
|
+
"vpc_networks",
|
|
631
|
+
"version_metadata"
|
|
632
|
+
]);
|
|
633
|
+
const MINIMAL_UNSUPPORTED_BINDING_KEYS = [
|
|
634
|
+
"hyperdrive",
|
|
635
|
+
"analytics_engine_datasets",
|
|
636
|
+
"ai",
|
|
637
|
+
"ai_search_namespaces",
|
|
638
|
+
"ai_search",
|
|
639
|
+
"websearch",
|
|
640
|
+
"agent_memory",
|
|
641
|
+
"version_metadata",
|
|
642
|
+
"ratelimits",
|
|
643
|
+
"send_email",
|
|
644
|
+
"secrets_store_secrets",
|
|
645
|
+
"vectorize",
|
|
646
|
+
"browser",
|
|
647
|
+
"images",
|
|
648
|
+
"stream",
|
|
649
|
+
"media",
|
|
650
|
+
"pipelines",
|
|
651
|
+
"dispatch_namespaces",
|
|
652
|
+
"mtls_certificates",
|
|
653
|
+
"worker_loaders",
|
|
654
|
+
"vpc_services",
|
|
655
|
+
"vpc_networks",
|
|
656
|
+
"artifacts",
|
|
657
|
+
"flagship",
|
|
658
|
+
"containers",
|
|
659
|
+
"unsafe",
|
|
660
|
+
"unsafe_hello_world",
|
|
661
|
+
"logfwdr",
|
|
662
|
+
"wasm_modules",
|
|
663
|
+
"text_blobs",
|
|
664
|
+
"data_blobs"
|
|
665
|
+
];
|
|
666
|
+
const MINIMAL_BINDING_OPTIONS = [
|
|
667
|
+
"kvNamespaces",
|
|
668
|
+
"r2Buckets",
|
|
669
|
+
"d1Databases",
|
|
670
|
+
"queueProducers",
|
|
671
|
+
"durableObjects"
|
|
672
|
+
];
|
|
673
|
+
function warnUnsupportedMinimalBindings(unsupported) {
|
|
674
|
+
if (unsupported.size === 0) return;
|
|
675
|
+
const list = [...unsupported].map(([name, details]) => details.size > 0 ? `${name} (${[...details].join(", ")})` : name).join(", ");
|
|
676
|
+
console.warn(`[env-runner] wrangler config bindings not supported by the built-in minimal wrangler config reader were ignored: ${list}; install 'wrangler' (or pass it as \`wranglerModule\`) to use them.`);
|
|
677
|
+
}
|
|
292
678
|
function readWranglerConfigMinimal(configPath, env) {
|
|
293
|
-
|
|
294
|
-
|
|
679
|
+
const ext = extname(configPath).toLowerCase();
|
|
680
|
+
if (ext !== ".json" && ext !== ".jsonc") {
|
|
681
|
+
console.warn(`[env-runner] reading "${basename(configPath)}" requires the 'wrangler' package; the built-in reader supports JSON/JSONC only (install 'wrangler' for TOML).`);
|
|
295
682
|
return;
|
|
296
683
|
}
|
|
297
684
|
let raw;
|
|
@@ -302,45 +689,203 @@ function readWranglerConfigMinimal(configPath, env) {
|
|
|
302
689
|
}
|
|
303
690
|
let config;
|
|
304
691
|
try {
|
|
305
|
-
config =
|
|
692
|
+
config = parseJSONC(raw);
|
|
306
693
|
} catch (error) {
|
|
307
694
|
console.warn(`[env-runner] failed to parse wrangler config "${configPath}": ${error.message}`);
|
|
308
695
|
return;
|
|
309
696
|
}
|
|
310
|
-
|
|
697
|
+
if (!isPlainObject(config)) {
|
|
698
|
+
warnWranglerLoadError(`"${configPath}"`, /* @__PURE__ */ new Error("config must be an object"));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
let envConfig;
|
|
702
|
+
try {
|
|
703
|
+
envConfig = applyWranglerEnv(config, env, configPath);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
warnWranglerLoadError(`"${configPath}"`, error);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
config: envConfig,
|
|
710
|
+
workerName: wranglerWorkerName(config, env)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function applyWranglerEnv(config, env, configPath) {
|
|
714
|
+
const { env: envs, ...topLevel } = config;
|
|
715
|
+
if (!env) return topLevel;
|
|
716
|
+
const rawEnv = isPlainObject(envs) ? envs[env] : void 0;
|
|
717
|
+
const warnings = [];
|
|
718
|
+
let out;
|
|
719
|
+
if (isPlainObject(rawEnv)) {
|
|
720
|
+
out = {};
|
|
721
|
+
for (const [key, value] of Object.entries(topLevel)) if (!WRANGLER_NON_INHERITABLE_KEYS.has(key)) out[key] = value;
|
|
722
|
+
else if (rawEnv[key] === void 0) warnings.push(`"${key}" exists at the top level, but is not inherited by "env.${env}"`);
|
|
723
|
+
Object.assign(out, rawEnv);
|
|
724
|
+
} else {
|
|
725
|
+
if (configPath && isPlainObject(envs)) throw new Error(`No environment found in configuration with name "${env}". The available configured environment names are: ${JSON.stringify(Object.keys(envs))}`);
|
|
726
|
+
warnings.push(`No environment found in configuration with name "${env}"`);
|
|
727
|
+
out = topLevel;
|
|
728
|
+
}
|
|
729
|
+
if (configPath && warnings.length > 0 && claimWranglerWarnings(configPath, env)) console.warn(`[env-runner] wrangler config "${configPath}": ${warnings.join("; ")}.`);
|
|
730
|
+
return out;
|
|
311
731
|
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
} : config;
|
|
732
|
+
function wranglerWorkerName(config, env) {
|
|
733
|
+
const envName = env ? config.env?.[env]?.name : void 0;
|
|
734
|
+
if (typeof envName === "string") return envName;
|
|
735
|
+
if (typeof config.name !== "string" || !config.name) return void 0;
|
|
736
|
+
return env ? `${config.name}-${env}` : config.name;
|
|
317
737
|
}
|
|
318
|
-
function mapWranglerConfigToMiniflare(config) {
|
|
738
|
+
function mapWranglerConfigToMiniflare(config, dropped, unsupported) {
|
|
319
739
|
const out = {};
|
|
740
|
+
for (const [name, value, field] of [
|
|
741
|
+
[
|
|
742
|
+
"services",
|
|
743
|
+
config.services,
|
|
744
|
+
"binding"
|
|
745
|
+
],
|
|
746
|
+
[
|
|
747
|
+
"assets",
|
|
748
|
+
config.assets,
|
|
749
|
+
"binding"
|
|
750
|
+
],
|
|
751
|
+
[
|
|
752
|
+
"queues.consumers",
|
|
753
|
+
config.queues?.consumers,
|
|
754
|
+
"queue"
|
|
755
|
+
],
|
|
756
|
+
[
|
|
757
|
+
"workflows",
|
|
758
|
+
config.workflows,
|
|
759
|
+
"binding"
|
|
760
|
+
],
|
|
761
|
+
[
|
|
762
|
+
"tail_consumers",
|
|
763
|
+
config.tail_consumers,
|
|
764
|
+
"service"
|
|
765
|
+
],
|
|
766
|
+
[
|
|
767
|
+
"streaming_tail_consumers",
|
|
768
|
+
config.streaming_tail_consumers,
|
|
769
|
+
"service"
|
|
770
|
+
]
|
|
771
|
+
]) if (!isEmptyOption(value)) addDropped(dropped, name, (Array.isArray(value) ? value : [value]).map((e) => e?.[field]).filter((d) => typeof d === "string"));
|
|
772
|
+
for (const key of MINIMAL_UNSUPPORTED_BINDING_KEYS) {
|
|
773
|
+
const value = config[key];
|
|
774
|
+
const bindings = isPlainObject(value) && Array.isArray(value.bindings) ? value.bindings : value;
|
|
775
|
+
if (!isEmptyOption(bindings)) addDropped(unsupported, key, describeBindingNames(bindings));
|
|
776
|
+
}
|
|
320
777
|
if (typeof config.compatibility_date === "string") out.compatibilityDate = config.compatibility_date;
|
|
321
778
|
if (Array.isArray(config.compatibility_flags)) out.compatibilityFlags = config.compatibility_flags;
|
|
322
779
|
if (config.vars && typeof config.vars === "object") out.bindings = { ...config.vars };
|
|
323
|
-
const kv = mapBindingArray(config.kv_namespaces, "binding", (n) => n.id
|
|
780
|
+
const kv = mapBindingArray(config.kv_namespaces, "binding", (n) => firstString(n.preview_id, n.id, n.binding));
|
|
324
781
|
if (kv) out.kvNamespaces = kv;
|
|
325
|
-
const r2 = mapBindingArray(config.r2_buckets, "binding", (n) => n.bucket_name
|
|
782
|
+
const r2 = mapBindingArray(config.r2_buckets, "binding", (n) => firstString(n.preview_bucket_name, n.bucket_name, n.binding));
|
|
326
783
|
if (r2) out.r2Buckets = r2;
|
|
327
|
-
const d1 = mapBindingArray(config.d1_databases, "binding", (n) => n.
|
|
784
|
+
const d1 = mapBindingArray(config.d1_databases, "binding", (n) => firstString(n.preview_database_id, n.database_id, n.binding));
|
|
328
785
|
if (d1) out.d1Databases = d1;
|
|
329
786
|
const queues = mapBindingArray(config.queues?.producers, "binding", (n) => n.queue);
|
|
330
787
|
if (queues) out.queueProducers = queues;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
788
|
+
const useSQLite = durableObjectClassStorage(config);
|
|
789
|
+
const doBindings = Array.isArray(config.durable_objects?.bindings) ? config.durable_objects.bindings : [];
|
|
790
|
+
const dos = {};
|
|
791
|
+
for (const b of doBindings) {
|
|
792
|
+
if (!b?.name || !b?.class_name) continue;
|
|
793
|
+
dos[b.name] = {
|
|
794
|
+
className: b.class_name,
|
|
795
|
+
...b.script_name ? { scriptName: b.script_name } : {},
|
|
796
|
+
...useSQLite.has(b.class_name) ? { useSQLite: useSQLite.get(b.class_name) } : {}
|
|
797
|
+
};
|
|
341
798
|
}
|
|
799
|
+
if (Object.keys(dos).length > 0) out.durableObjects = dos;
|
|
800
|
+
const unbound = [...useSQLite].filter(([className]) => !doBindings.some((b) => b?.class_name === className)).map(([className, sqlite]) => ({
|
|
801
|
+
className,
|
|
802
|
+
useSQLite: sqlite
|
|
803
|
+
}));
|
|
804
|
+
if (unbound.length > 0) out.additionalUnboundDurableObjects = unbound;
|
|
342
805
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
343
806
|
}
|
|
807
|
+
function durableObjectClassStorage(config) {
|
|
808
|
+
const classes = /* @__PURE__ */ new Map();
|
|
809
|
+
for (const migration of Array.isArray(config.migrations) ? config.migrations : []) {
|
|
810
|
+
for (const name of migration?.deleted_classes ?? []) classes.delete(name);
|
|
811
|
+
for (const { from, to } of migration?.renamed_classes ?? []) {
|
|
812
|
+
const sqlite = classes.get(from);
|
|
813
|
+
if (sqlite !== void 0) {
|
|
814
|
+
classes.delete(from);
|
|
815
|
+
classes.set(to, sqlite);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
for (const name of migration?.new_classes ?? []) classes.set(name, false);
|
|
819
|
+
for (const name of migration?.new_sqlite_classes ?? []) classes.set(name, true);
|
|
820
|
+
}
|
|
821
|
+
if (isPlainObject(config.exports)) for (const [name, entry] of Object.entries(config.exports)) {
|
|
822
|
+
const state = entry?.state;
|
|
823
|
+
if (entry?.type === "durable-object" && (state === void 0 || state === "created" || state === "expecting-transfer")) classes.set(name, entry.storage === "sqlite");
|
|
824
|
+
}
|
|
825
|
+
return classes;
|
|
826
|
+
}
|
|
827
|
+
function describeBindingNames(value) {
|
|
828
|
+
if (isPlainObject(value) && typeof value.binding !== "string") return Object.keys(value);
|
|
829
|
+
return (Array.isArray(value) ? value : [value]).map((e) => e?.binding ?? e?.name ?? e?.class_name).filter((name) => typeof name === "string");
|
|
830
|
+
}
|
|
831
|
+
function applyMinimalDevVars(options, configs, opts) {
|
|
832
|
+
const secrets = configs.map((c) => c?.secrets).filter(isPlainObject);
|
|
833
|
+
const loaded = loadDevVars({
|
|
834
|
+
...opts,
|
|
835
|
+
hasSecrets: secrets.length > 0
|
|
836
|
+
});
|
|
837
|
+
if (!loaded) return options;
|
|
838
|
+
const out = { ...options };
|
|
839
|
+
const bindings = isPlainObject(out.bindings) ? { ...out.bindings } : {};
|
|
840
|
+
const taken = new Set(MINIMAL_BINDING_OPTIONS.flatMap((key) => isPlainObject(out[key]) ? Object.keys(out[key]) : []));
|
|
841
|
+
const required = new Set(secrets.flatMap((s) => Array.isArray(s.required) ? s.required : []));
|
|
842
|
+
let changed = false;
|
|
843
|
+
for (const [key, value] of Object.entries(loaded)) {
|
|
844
|
+
if (taken.has(key) || secrets.length > 0 && !Object.hasOwn(bindings, key) && !required.has(key)) continue;
|
|
845
|
+
bindings[key] = value;
|
|
846
|
+
changed = true;
|
|
847
|
+
}
|
|
848
|
+
const missing = [...required].filter((key) => !Object.hasOwn(loaded, key));
|
|
849
|
+
if (missing.length > 0) console.warn(`[env-runner] missing required wrangler secrets: ${missing.join(", ")}. Add them to .dev.vars, .env, or set as environment variables.`);
|
|
850
|
+
if (!changed) return options;
|
|
851
|
+
out.bindings = bindings;
|
|
852
|
+
return out;
|
|
853
|
+
}
|
|
854
|
+
function firstString(...values) {
|
|
855
|
+
return values.find((value) => typeof value === "string");
|
|
856
|
+
}
|
|
857
|
+
function parseJSONC(text) {
|
|
858
|
+
let out = "";
|
|
859
|
+
let comma = -1;
|
|
860
|
+
const src = text.charCodeAt(0) === 65279 ? text.slice(1) : text;
|
|
861
|
+
for (let i = 0; i < src.length; i++) {
|
|
862
|
+
const ch = src[i];
|
|
863
|
+
if (ch === "/" && (src[i + 1] === "/" || src[i + 1] === "*")) {
|
|
864
|
+
const line = src[i + 1] === "/";
|
|
865
|
+
let end = line ? src.indexOf("\n", i) : src.indexOf("*/", i + 2);
|
|
866
|
+
end = end === -1 ? src.length : line ? end : end + 2;
|
|
867
|
+
out += src.slice(i, end).replace(/[^\n]/g, " ");
|
|
868
|
+
i = end - 1;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (/\s/.test(ch)) {
|
|
872
|
+
out += ch;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if ((ch === "}" || ch === "]") && comma !== -1) out = `${out.slice(0, comma)} ${out.slice(comma + 1)}`;
|
|
876
|
+
comma = -1;
|
|
877
|
+
if (ch === "\"") {
|
|
878
|
+
let end = i + 1;
|
|
879
|
+
while (end < src.length && src[end] !== "\"") end += src[end] === "\\" ? 2 : 1;
|
|
880
|
+
out += src.slice(i, end + 1);
|
|
881
|
+
i = end;
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (ch === ",") comma = out.length;
|
|
885
|
+
out += ch;
|
|
886
|
+
}
|
|
887
|
+
return JSON.parse(out);
|
|
888
|
+
}
|
|
344
889
|
function mapBindingArray(arr, keyField, value) {
|
|
345
890
|
if (!Array.isArray(arr)) return;
|
|
346
891
|
const out = {};
|
|
@@ -367,6 +912,9 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
367
912
|
#exportConditions;
|
|
368
913
|
#wrangler;
|
|
369
914
|
#wranglerEnv;
|
|
915
|
+
#wranglerConfigPath;
|
|
916
|
+
#wranglerEnvFiles;
|
|
917
|
+
#compatibilityDate;
|
|
370
918
|
#wranglerModule;
|
|
371
919
|
#miniflareModule;
|
|
372
920
|
constructor(opts) {
|
|
@@ -384,6 +932,9 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
384
932
|
this.#wrangler = opts.wrangler ?? false;
|
|
385
933
|
this.#wranglerEnv = opts.wranglerEnv ?? process.env.CLOUDFLARE_ENV;
|
|
386
934
|
this.#wranglerModule = opts.wranglerModule;
|
|
935
|
+
this.#wranglerConfigPath = opts.wranglerConfigPath;
|
|
936
|
+
this.#wranglerEnvFiles = opts.wranglerEnvFiles;
|
|
937
|
+
this.#compatibilityDate = opts.compatibilityDate;
|
|
387
938
|
this._initWithVirtualData(() => this.#init());
|
|
388
939
|
}
|
|
389
940
|
static async disposeAll() {
|
|
@@ -408,8 +959,12 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
408
959
|
for (let i = 0; i < 5 && !this._address && !this.closed; i++) await new Promise((r) => setTimeout(r, 100 * Math.pow(2, i)));
|
|
409
960
|
if (!this.#miniflare || this.closed) return new Response("miniflare env runner is unavailable", { status: 503 });
|
|
410
961
|
const resolved = this._resolveFetchInput(input);
|
|
411
|
-
const
|
|
412
|
-
|
|
962
|
+
const request = typeof resolved === "string" || resolved instanceof URL ? new Request(resolved, init) : new Request(new Request(resolved.url, resolved), {
|
|
963
|
+
...init,
|
|
964
|
+
referrer: init?.referrer ?? resolved.referrer,
|
|
965
|
+
referrerPolicy: init?.referrerPolicy ?? resolved.referrerPolicy
|
|
966
|
+
});
|
|
967
|
+
const res = await this.#miniflare.dispatchFetch(request.url, request);
|
|
413
968
|
if (res instanceof Response) return res;
|
|
414
969
|
return new Response(res.body, {
|
|
415
970
|
status: res.status,
|
|
@@ -529,20 +1084,28 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
529
1084
|
const entryPath = this._data?.entry;
|
|
530
1085
|
const virtual = await this.#prepareVirtualModules();
|
|
531
1086
|
this.#virtual = virtual;
|
|
532
|
-
const wranglerOptions = await loadWranglerConfig(
|
|
1087
|
+
const { options: wranglerOptions, configFile: wranglerConfigFile } = await loadWranglerConfig({
|
|
1088
|
+
wrangler: this.#wrangler,
|
|
1089
|
+
env: this.#wranglerEnv,
|
|
1090
|
+
entryPath,
|
|
1091
|
+
configPath: this.#wranglerConfigPath,
|
|
1092
|
+
wranglerModule: this.#wranglerModule,
|
|
1093
|
+
envFiles: this.#wranglerEnvFiles
|
|
1094
|
+
});
|
|
533
1095
|
const userFlags = this.#miniflareOptions.compatibilityFlags || [];
|
|
534
1096
|
const wranglerFlags = wranglerOptions?.compatibilityFlags || [];
|
|
535
1097
|
const userDirectSockets = this.#miniflareOptions.unsafeDirectSockets || [];
|
|
536
1098
|
const options = {
|
|
537
|
-
compatibilityDate: supportedCompatibilityDate,
|
|
538
1099
|
modules: true,
|
|
1100
|
+
...this.#wrangler && !hasUserPersistOptions(this.#miniflareOptions) ? { defaultPersistRoot: wranglerPersistRoot(wranglerConfigFile, typeof this.#wrangler === "string" ? this.#wrangler : this.#wranglerConfigPath) } : void 0,
|
|
539
1101
|
...wranglerOptions,
|
|
540
1102
|
...this.#miniflareOptions,
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
])
|
|
1103
|
+
compatibilityDate: resolveCompatibilityDate([
|
|
1104
|
+
this.#miniflareOptions.compatibilityDate,
|
|
1105
|
+
this.#compatibilityDate === "latest" ? supportedCompatibilityDate : this.#compatibilityDate,
|
|
1106
|
+
wranglerOptions?.compatibilityDate
|
|
1107
|
+
], supportedCompatibilityDate),
|
|
1108
|
+
compatibilityFlags: resolveCompatibilityFlags(wranglerFlags, userFlags),
|
|
546
1109
|
unsafeDirectSockets: [{
|
|
547
1110
|
host: "127.0.0.1",
|
|
548
1111
|
port: 0
|
|
@@ -563,22 +1126,25 @@ var MiniflareEnvRunner = class extends BaseEnvRunner {
|
|
|
563
1126
|
const entrySource = entryIsVirtual ? virtual[entryPath] : _tryReadFile(resolvedEntry);
|
|
564
1127
|
const detectedExports = this.#exports === false || this.#exports === void 0 ? [] : detectExportedClasses(entrySource, typeof this.#exports === "object" ? this.#exports : {});
|
|
565
1128
|
if (entryIsVirtual && detectedExports.length > 0) throw new Error(`[env-runner] named exports (${detectedExports.join(", ")}) are not supported with a virtual entry on the miniflare runner; pass \`exports: false\` or use a real entry file.`);
|
|
566
|
-
if (detectedExports.length > 0
|
|
567
|
-
const
|
|
1129
|
+
if (detectedExports.length > 0) {
|
|
1130
|
+
const existingDOs = isPlainObject(options.durableObjects) ? options.durableObjects : {};
|
|
1131
|
+
const boundClasses = new Set(Object.values(existingDOs).map((b) => typeof b === "string" ? b : isPlainObject(b) && !b.scriptName ? b.className : void 0));
|
|
1132
|
+
const autoDOs = { ...existingDOs };
|
|
568
1133
|
for (const name of detectedExports) {
|
|
569
1134
|
const bindingName = toScreamingSnakeCase(name);
|
|
570
|
-
if (!autoDOs[bindingName]) autoDOs[bindingName] = name;
|
|
1135
|
+
if (!autoDOs[bindingName] && !boundClasses.has(name)) autoDOs[bindingName] = name;
|
|
571
1136
|
}
|
|
572
1137
|
options.durableObjects = autoDOs;
|
|
573
1138
|
}
|
|
574
1139
|
options.script = generateWrapper(resolvedEntry, {
|
|
575
1140
|
dynamicOnly: true,
|
|
576
1141
|
captureErrors: this.#captureErrors,
|
|
577
|
-
exports: detectedExports
|
|
1142
|
+
exports: detectedExports,
|
|
1143
|
+
nodeCompat: !options.compatibilityFlags.includes("no_nodejs_compat")
|
|
578
1144
|
});
|
|
579
1145
|
options.scriptPath = entryDir + "/__env_runner_wrapper.mjs";
|
|
580
1146
|
if (!options.modulesRoot) options.modulesRoot = "/";
|
|
581
|
-
options.unsafeEvalBinding =
|
|
1147
|
+
options.unsafeEvalBinding = UNSAFE_EVAL_BINDING;
|
|
582
1148
|
options.serviceBindings = {
|
|
583
1149
|
...options.serviceBindings || {},
|
|
584
1150
|
[IPC_BINDING]: async (request) => {
|
|
@@ -773,6 +1339,30 @@ function detectExportedClasses(entrySource, explicit) {
|
|
|
773
1339
|
}
|
|
774
1340
|
return [...names];
|
|
775
1341
|
}
|
|
1342
|
+
function resolveCompatibilityDate(candidates, supported) {
|
|
1343
|
+
const date = candidates.find((d) => typeof d === "string" && d) ?? supported;
|
|
1344
|
+
if (date && supported && _isDateString(date) && _isDateString(supported) && date > supported) {
|
|
1345
|
+
console.warn(`[env-runner] compatibility date "${date}" is newer than the installed workerd supports; falling back to "${supported}".`);
|
|
1346
|
+
return supported;
|
|
1347
|
+
}
|
|
1348
|
+
return date;
|
|
1349
|
+
}
|
|
1350
|
+
function resolveCompatibilityFlags(wranglerFlags, userFlags) {
|
|
1351
|
+
const opposite = (flag) => flag === "nodejs_compat" ? "no_nodejs_compat" : flag === "no_nodejs_compat" ? "nodejs_compat" : void 0;
|
|
1352
|
+
const flags = [...wranglerFlags.filter((flag) => !userFlags.includes(opposite(flag))), ...userFlags];
|
|
1353
|
+
const defaults = flags.includes("no_nodejs_compat") ? [] : ["nodejs_compat"];
|
|
1354
|
+
return [.../* @__PURE__ */ new Set([...defaults, ...flags])];
|
|
1355
|
+
}
|
|
1356
|
+
function _isDateString(value) {
|
|
1357
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
1358
|
+
}
|
|
1359
|
+
function hasUserPersistOptions(options) {
|
|
1360
|
+
return Object.keys(options).some((key) => key === "defaultPersistRoot" || key.endsWith("Persist"));
|
|
1361
|
+
}
|
|
1362
|
+
function wranglerPersistRoot(configFile, explicitPath) {
|
|
1363
|
+
const file = configFile ?? (explicitPath ? resolve(explicitPath) : void 0);
|
|
1364
|
+
return join(file ? dirname(file) : process.cwd(), ".wrangler/state/v3");
|
|
1365
|
+
}
|
|
776
1366
|
function _tryReadFile(path) {
|
|
777
1367
|
try {
|
|
778
1368
|
return readFileSync(path, "utf8");
|
|
@@ -799,16 +1389,15 @@ function applyVirtualVersions(code, versions) {
|
|
|
799
1389
|
let out = "";
|
|
800
1390
|
let last = 0;
|
|
801
1391
|
for (const imp of imports) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
1392
|
+
if (imp.type === "import-meta") continue;
|
|
1393
|
+
const dynamic = imp.type === "dynamic";
|
|
1394
|
+
if (dynamic && imp.glob) continue;
|
|
1395
|
+
const specifier = imp.specifier;
|
|
807
1396
|
const version = specifier === void 0 ? void 0 : versions.get(specifier);
|
|
808
1397
|
if (!version) continue;
|
|
809
1398
|
const versioned = `${specifier}?v=${version}`;
|
|
810
|
-
out += code.slice(last, imp.
|
|
811
|
-
last = imp.
|
|
1399
|
+
out += code.slice(last, imp.start) + (dynamic ? JSON.stringify(versioned) : versioned);
|
|
1400
|
+
last = imp.end;
|
|
812
1401
|
}
|
|
813
1402
|
return out + code.slice(last);
|
|
814
1403
|
}
|