env-runner 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +370 -20
  2. package/dist/_chunks/common-base-runner.d.mts +486 -15
  3. package/dist/_chunks/common-base-runner.mjs +712 -32
  4. package/dist/_chunks/common-process-data.mjs +13 -0
  5. package/dist/_chunks/common-worker-utils.mjs +47 -114
  6. package/dist/_chunks/deno-process-runner.d.mts +4 -1
  7. package/dist/_chunks/deno-process-runner.mjs +9 -6
  8. package/dist/_chunks/lexer.mjs +2 -0
  9. package/dist/_chunks/libs/cjs-module-lexer.mjs +6 -1
  10. package/dist/_chunks/miniflare-runner.d.mts +26 -14
  11. package/dist/_chunks/miniflare-runner.mjs +430 -134
  12. package/dist/_chunks/netlify-runner.d.mts +3 -1
  13. package/dist/_chunks/node-worker-runner.d.mts +3 -1
  14. package/dist/_chunks/node-worker-runner.mjs +8 -4
  15. package/dist/_chunks/plugin-hooks.mjs +1656 -0
  16. package/dist/_chunks/server.mjs +21 -5
  17. package/dist/_chunks/types.d.mts +37 -1
  18. package/dist/_chunks/vercel-runner.d.mts +3 -1
  19. package/dist/cli.mjs +0 -0
  20. package/dist/index.d.mts +21 -4
  21. package/dist/runners/bun-process/runner.d.mts +4 -1
  22. package/dist/runners/bun-process/runner.mjs +12 -9
  23. package/dist/runners/bun-process/worker.mjs +12 -11
  24. package/dist/runners/deno-process/worker.mjs +12 -11
  25. package/dist/runners/node-process/runner.d.mts +3 -1
  26. package/dist/runners/node-process/runner.mjs +2 -6
  27. package/dist/runners/node-process/worker.mjs +12 -11
  28. package/dist/runners/node-worker/worker.mjs +10 -10
  29. package/dist/runners/self/runner.d.mts +5 -2
  30. package/dist/runners/self/runner.mjs +12 -0
  31. package/package.json +26 -26
  32. package/dist/_chunks/common-host-env.mjs +0 -11
  33. package/dist/_chunks/virtual-loader.mjs +0 -71
@@ -1,6 +1,574 @@
1
- import { resolveVirtualModules } from "./virtual-loader.mjs";
1
+ import { PLUGINS_DATA_KEY, compileFilter, compileFilterExpressions, compilePrefilter, createPrefilter, encodeVirtualModules, moduleTypeOf, normalizeFilterId, normalizeVirtualModules, openTransformPort, openTransformSocket, queryOf, requiredMatch, resolveVirtualModules, satisfiesMatch, stripQuery, virtualModuleFormat, warnVirtualPathCollisions } from "./plugin-hooks.mjs";
2
+ import { isBuiltin } from "node:module";
2
3
  import { rm } from "node:fs/promises";
3
4
  import { proxyFetch, proxyUpgrade } from "httpxy";
5
+ import * as nodePath from "node:path";
6
+ import { isAbsolute, normalize } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+ import { resolveModulePath } from "exsolve";
9
+ function hostEnv(extra) {
10
+ const env = {
11
+ ...process.env,
12
+ ...extra
13
+ };
14
+ const stdout = process.stdout;
15
+ if (!("FORCE_COLOR" in env) && !env.NO_COLOR && stdout?.isTTY) env.FORCE_COLOR = "1";
16
+ if (!("COLUMNS" in env) && stdout?.columns) env.COLUMNS = String(stdout.columns);
17
+ return env;
18
+ }
19
+ const SEP = String.raw`[\\/]`;
20
+ const SEGMENT_CHAR = String.raw`[^\\/]`;
21
+ function resolveGlob(pattern, cwd = process.cwd(), windows = process.platform === "win32") {
22
+ if (pattern.startsWith("**")) return pattern;
23
+ if ((windows ? nodePath.win32 : nodePath.posix).isAbsolute(pattern)) return windows && !pattern.includes("/") ? pattern.replaceAll("\\", "/") : pattern;
24
+ const base = cwd.replaceAll("\\", "/").replace(/[*?[\]{}()!+@,\\]/g, "\\$&");
25
+ return nodePath.posix.join(base, pattern);
26
+ }
27
+ function globToRegExp(glob) {
28
+ const closing = _braceMatches(glob);
29
+ let source = "";
30
+ let depth = 0;
31
+ for (let i = 0; i < glob.length; i++) {
32
+ const char = glob[i];
33
+ if (char === "\\" && i + 1 < glob.length) source += _escape(glob[++i]);
34
+ else if (char === "*") {
35
+ if (glob[i + 1] !== "*") {
36
+ source += `${SEGMENT_CHAR}*`;
37
+ continue;
38
+ }
39
+ if (!((i === 0 || glob[i - 1] === "/") && (i + 2 === glob.length || glob[i + 2] === "/"))) source += `${SEGMENT_CHAR}*`;
40
+ else if (i + 2 === glob.length) source += ".*";
41
+ else {
42
+ source += `(?:${SEGMENT_CHAR}*${SEP})*`;
43
+ i++;
44
+ }
45
+ i++;
46
+ } else if (char === "?") source += SEGMENT_CHAR;
47
+ else if (char === "[" && glob.indexOf("]", i + 2) !== -1) {
48
+ const end = glob.indexOf("]", i + 2);
49
+ let body = glob.slice(i + 1, end);
50
+ const negated = body[0] === "!" || body[0] === "^";
51
+ if (negated) body = body.slice(1);
52
+ source += `[${negated ? String.raw`^\\/` : ""}${body.replace(/[\\\]^]/g, "\\$&")}]`;
53
+ i = end;
54
+ } else if (char === "{" && closing.has(i)) {
55
+ depth++;
56
+ source += "(?:";
57
+ } else if (char === "}" && depth > 0) {
58
+ depth--;
59
+ source += ")";
60
+ } else if (char === "," && depth > 0) source += "|";
61
+ else source += char === "/" ? SEP : _escape(char);
62
+ }
63
+ return new RegExp(`^${source}$`);
64
+ }
65
+ function _braceMatches(glob) {
66
+ const open = [];
67
+ const matched = /* @__PURE__ */ new Set();
68
+ for (let i = 0; i < glob.length; i++) if (glob[i] === "\\") i++;
69
+ else if (glob[i] === "{") open.push(i);
70
+ else if (glob[i] === "}" && open.length > 0) matched.add(open.pop());
71
+ return matched;
72
+ }
73
+ function _escape(char) {
74
+ return char.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
75
+ }
76
+ const KNOWN_MODULE_TYPES = [
77
+ "js",
78
+ "jsx",
79
+ "ts",
80
+ "tsx",
81
+ "json"
82
+ ];
83
+ function createPluginPipeline(plugins, options = {}) {
84
+ const { resolveConditions } = options;
85
+ if (plugins == null) return;
86
+ if (!Array.isArray(plugins)) throw new TypeError("[env-runner] `plugins` must be an array of plugin objects.");
87
+ const normalized = _flattenPlugins(plugins, "").map(([plugin, path]) => _normalizePlugin(plugin, path));
88
+ const rank = {
89
+ pre: 0,
90
+ normal: 1,
91
+ post: 2
92
+ };
93
+ const enforced = normalized.toSorted((a, b) => rank[a.enforce] - rank[b.enforce]);
94
+ const ordered = (kind) => {
95
+ const hooks = enforced.flatMap((plugin) => plugin[kind] ? [plugin[kind]] : []);
96
+ const byOrder = (order) => hooks.filter((hook) => hook.order === order);
97
+ return [
98
+ ...byOrder("pre"),
99
+ ...byOrder("normal"),
100
+ ...byOrder("post")
101
+ ];
102
+ };
103
+ const resolveHooks = ordered("resolveId");
104
+ const loadHooks = ordered("load");
105
+ const transformHooks = ordered("transform");
106
+ if (resolveHooks.length + loadHooks.length + transformHooks.length === 0) return;
107
+ const prefilters = normalized.flatMap((plugin) => [plugin.load, plugin.transform].flatMap((hook) => hook ? [hook.prefilter] : []));
108
+ const resolvePrefilters = resolveHooks.map((hook) => hook.prefilter);
109
+ const fallbackPrefilter = createPrefilter(resolvePrefilters.filter((prefilter) => prefilter.fallback));
110
+ const prefilter = createPrefilter(prefilters);
111
+ const resolvePrefilter = createPrefilter(resolvePrefilters.filter((prefilter) => !prefilter.fallback));
112
+ const filter = (id, moduleType, resolved) => {
113
+ const matchId = normalizeFilterId(id);
114
+ return prefilter(matchId, moduleType ?? moduleTypeOf(matchId), resolved || moduleType !== void 0);
115
+ };
116
+ const contextResolve = (caller, skip) => async (source, importer, options = {}) => {
117
+ if (typeof source !== "string") throw new TypeError(`[env-runner] \`this.resolve()\` needs a string (got ${_describe(source)}).`);
118
+ const skipping = caller && options.skipSelf !== false ? [...skip, {
119
+ hook: caller,
120
+ source,
121
+ importer
122
+ }] : skip;
123
+ const skipped = new Set(skipping.filter((entry) => entry.source === source && entry.importer === importer).map((entry) => entry.hook));
124
+ const { skipSelf: _, ...rest } = options;
125
+ return await _resolveId(source, importer, rest, skipped, skipping) ?? _resolveLikeRuntime(source, importer, resolveConditions?.()) ?? await _resolveId(source, importer, {
126
+ ...rest,
127
+ fallback: true
128
+ }, skipped, skipping) ?? null;
129
+ };
130
+ const _resolveId = async (source, importer, options, skipped, skip) => {
131
+ const matchId = normalizeFilterId(source);
132
+ const extra = {
133
+ isEntry: options?.isEntry ?? false,
134
+ attributes: options?.attributes ?? {}
135
+ };
136
+ for (const hook of resolveHooks) {
137
+ if (Boolean(hook.fallback) !== Boolean(options?.fallback) || skipped.has(hook) || !hook.match(matchId, "js", void 0, true)) continue;
138
+ const context = _createContext(hook.name, source, "", contextResolve(hook, skip));
139
+ const result = await _callHook(hook.name, "failed to resolve", source, "", () => hook.handler.call(context, source, importer, extra));
140
+ if (result == null) continue;
141
+ if (result === false) return {
142
+ id: source,
143
+ external: true
144
+ };
145
+ const id = typeof result === "string" ? result : result.id;
146
+ if (typeof id !== "string" || id === "") throw new TypeError(`[env-runner] plugin "${hook.name}" resolved "${source}" to an invalid id (got ${_describe(id)}): return a string, \`{ id }\` or nullish.`);
147
+ return {
148
+ id,
149
+ external: typeof result === "object" && Boolean(result.external)
150
+ };
151
+ }
152
+ };
153
+ const resolveId = (source, importer, options) => _resolveId(source, importer, options, /* @__PURE__ */ new Set(), []);
154
+ const load = async (id, read, options) => {
155
+ const matchId = normalizeFilterId(id);
156
+ let moduleType = moduleTypeOf(id);
157
+ const required = read ? requiredMatch(moduleType) : void 0;
158
+ const resolved = !read || options?.resolved;
159
+ for (const hook of loadHooks) {
160
+ if (!satisfiesMatch(hook.match(matchId, moduleType, void 0, resolved), required)) continue;
161
+ const result = await _callHook(hook.name, "failed to load", id, "", () => hook.handler.call(_createContext(hook.name, id, "", contextResolve(void 0, [])), id));
162
+ if (result == null) continue;
163
+ const code = typeof result === "string" ? result : result.code;
164
+ if (typeof code !== "string") throw new TypeError(`[env-runner] plugin "${hook.name}" loaded non-string \`code\` for "${id}" (got ${_describe(code)}).`);
165
+ const map = typeof result === "object" ? _parseMap(result.map) : void 0;
166
+ if (typeof result === "object" && result.moduleType) moduleType = result.moduleType;
167
+ else if (!KNOWN_MODULE_TYPES.includes(moduleType)) moduleType = "js";
168
+ return _transform(id, code, moduleType, {
169
+ resolved,
170
+ changed: true,
171
+ map,
172
+ mappedBy: map ? hook.name : void 0
173
+ });
174
+ }
175
+ if (!read) throw new Error(`[env-runner] no plugin loaded "${id}": a \`resolveId\` hook resolved an import to it, so a \`load\` hook must return its code.`);
176
+ if (!filter(id, void 0, resolved)) return;
177
+ return _transform(id, read(), moduleType, {
178
+ resolved,
179
+ changed: false
180
+ });
181
+ };
182
+ const _transform = async (id, code, moduleType, state) => {
183
+ let { changed, map, mappedBy } = state;
184
+ const matchId = normalizeFilterId(id);
185
+ for (const hook of transformHooks) {
186
+ const match = hook.match(matchId, moduleType, code, state.resolved);
187
+ if (!satisfiesMatch(match, requiredMatch(moduleType))) continue;
188
+ const result = await _callHook(hook.name, "failed on", id, code, () => hook.handler.call(_createContext(hook.name, id, code, contextResolve(void 0, [])), code, id, { moduleType }));
189
+ const next = typeof result === "string" ? result : result?.code ?? code;
190
+ if (typeof next !== "string") throw new TypeError(`[env-runner] plugin "${hook.name}" returned non-string \`code\` for "${id}" (got ${_describe(next)}): convert it to a string (e.g. \`s.toString()\`, with \`map: s.generateMap()\`).`);
191
+ const resultType = result && typeof result === "object" ? result.moduleType : void 0;
192
+ if (resultType) moduleType = resultType;
193
+ if (next === code) continue;
194
+ code = next;
195
+ changed = true;
196
+ if (!resultType && !KNOWN_MODULE_TYPES.includes(moduleType)) moduleType = "js";
197
+ const nextMap = typeof result === "object" ? _parseMap(result?.map) : void 0;
198
+ if (nextMap) {
199
+ if (mappedBy === void 0) {
200
+ map = nextMap;
201
+ mappedBy = hook.name;
202
+ } else {
203
+ map = void 0;
204
+ _warnDroppedMap(mappedBy, hook.name);
205
+ }
206
+ }
207
+ }
208
+ if (!changed) return;
209
+ if (moduleType === "json") try {
210
+ JSON.parse(code);
211
+ return {
212
+ code,
213
+ moduleType: "json"
214
+ };
215
+ } catch {
216
+ moduleType = "js";
217
+ }
218
+ if (moduleType !== "js" && moduleType !== "ts") throw new TypeError(`[env-runner] "${id}" is still ${moduleType} after its plugins: add one that compiles it to JavaScript (returning \`moduleType: "js"\`) or narrow the plugins' filters.`);
219
+ if (map) {
220
+ const file = stripQuery(id);
221
+ const source = isAbsolute(file) ? pathToFileURL(file).href : file;
222
+ const json = JSON.stringify({
223
+ ...map,
224
+ sources: [source],
225
+ file: void 0
226
+ });
227
+ code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(json).toString("base64")}\n`;
228
+ }
229
+ return {
230
+ code,
231
+ moduleType
232
+ };
233
+ };
234
+ const transform = (id, code, sourceType) => _transform(id, code, sourceType ?? moduleTypeOf(id), {
235
+ resolved: true,
236
+ changed: false
237
+ });
238
+ return {
239
+ names: normalized.map((plugin) => plugin.name),
240
+ prefilters,
241
+ resolvePrefilters,
242
+ filter,
243
+ resolveFilter: (source, fallback) => (fallback ? fallbackPrefilter : resolvePrefilter)(normalizeFilterId(source), "js", true),
244
+ resolveId,
245
+ load,
246
+ transform
247
+ };
248
+ }
249
+ function _resolveLikeRuntime(source, importer, conditions) {
250
+ if (isBuiltin(source)) return {
251
+ id: source,
252
+ external: true
253
+ };
254
+ const from = importer && isAbsolute(stripQuery(importer)) ? stripQuery(importer) : pathToFileURL(`${process.cwd()}/`).href;
255
+ const path = resolveModulePath(stripQuery(source), {
256
+ from,
257
+ conditions,
258
+ try: true,
259
+ cache: false
260
+ });
261
+ return path ? {
262
+ id: normalize(path) + queryOf(source),
263
+ external: false
264
+ } : null;
265
+ }
266
+ async function _callHook(name, what, id, code, call) {
267
+ try {
268
+ return await call();
269
+ } catch (error) {
270
+ const message = error?.message || String(error);
271
+ if (message.startsWith("[env-runner]")) throw error;
272
+ const at = `${id}${_formatPosition(code, _logPosition(error, id, code))}`;
273
+ throw new Error(`[env-runner] plugin "${name}" ${what} "${at}": ${message}${_frame(error)}`, { cause: error });
274
+ }
275
+ }
276
+ function _logPosition(log, id, code) {
277
+ const { loc, pos } = log ?? {};
278
+ if (typeof loc?.line === "number" && typeof loc.column === "number") return typeof loc.file === "string" && loc.file !== stripQuery(id) && loc.file !== id ? void 0 : {
279
+ line: loc.line,
280
+ column: loc.column
281
+ };
282
+ return typeof pos === "number" && code ? pos : void 0;
283
+ }
284
+ function _frame(log) {
285
+ const frame = log?.frame;
286
+ return typeof frame === "string" && frame.trim() ? `\n\n${frame.replace(/\n+$/, "")}` : "";
287
+ }
288
+ const VIRTUAL_MODULE_TYPES = {
289
+ module: "js",
290
+ commonjs: "js",
291
+ "module-typescript": "ts",
292
+ "commonjs-typescript": "ts",
293
+ jsx: "jsx",
294
+ tsx: "tsx"
295
+ };
296
+ async function transformVirtualModules(pipeline, modules) {
297
+ const out = { ...modules };
298
+ for (const [key, module] of Object.entries(modules)) {
299
+ if (module === null) continue;
300
+ const format = virtualModuleFormat(key, module);
301
+ const moduleType = VIRTUAL_MODULE_TYPES[format];
302
+ if (!moduleType || !pipeline.filter(key, moduleType)) continue;
303
+ const source = typeof module === "string" ? module : module.source;
304
+ const result = await pipeline.transform(key, source, moduleType);
305
+ if (result) {
306
+ const system = format.startsWith("commonjs") ? "commonjs" : "module";
307
+ out[key] = {
308
+ source: result.code,
309
+ format: result.moduleType === "ts" ? `${system}-typescript` : system
310
+ };
311
+ }
312
+ }
313
+ return out;
314
+ }
315
+ function _flattenPlugins(plugins, path) {
316
+ return plugins.flatMap((plugin, index) => {
317
+ const at = `${path}[${index}]`;
318
+ return Array.isArray(plugin) ? _flattenPlugins(plugin, at) : plugin ? [[plugin, at]] : [];
319
+ });
320
+ }
321
+ const HOOK_KINDS = [
322
+ "resolveId",
323
+ "load",
324
+ "transform"
325
+ ];
326
+ function _normalizePlugin(plugin, path) {
327
+ const fail = (reason) => {
328
+ throw new TypeError(`[env-runner] \`plugins${path}\` ${reason}: expected a plugin object with \`resolveId\`, \`load\` or \`transform\` hooks, each a function or \`{ order?, filter?, handler }\`.`);
329
+ };
330
+ if (typeof plugin !== "object") return fail("is not a plugin object");
331
+ const { name: pluginName, enforce } = plugin;
332
+ const name = typeof pluginName === "string" ? pluginName : `plugins${path}`;
333
+ if (enforce != null && enforce !== "pre" && enforce !== "post") return fail(`has an invalid \`enforce\` (${JSON.stringify(enforce)})`);
334
+ const normalized = {
335
+ name,
336
+ enforce: enforce ?? "normal"
337
+ };
338
+ for (const kind of HOOK_KINDS) {
339
+ const hook = plugin[kind];
340
+ if (hook != null) normalized[kind] = _normalizeHook(kind, hook, name, path, fail);
341
+ }
342
+ return normalized;
343
+ }
344
+ function _normalizeHook(kind, hook, name, path, fail) {
345
+ const failFilter = (key, reason) => {
346
+ throw new TypeError(`[env-runner] \`plugins${path}\` has an invalid \`${kind}.filter${key}\` ${reason}.`);
347
+ };
348
+ let order = "normal";
349
+ let filter;
350
+ let fallback = false;
351
+ let handler;
352
+ if (typeof hook === "function") handler = hook;
353
+ else if (hook && typeof hook === "object" && typeof hook.handler === "function") {
354
+ const object = hook;
355
+ if (object.order != null && object.order !== "pre" && object.order !== "post") return fail(`has an invalid \`${kind}.order\` (${JSON.stringify(object.order)})`);
356
+ if (object.fallback != null && object.fallback !== false) {
357
+ if (kind !== "resolveId") return fail(`has a \`${kind}.fallback\` (only \`resolveId\` hooks take one)`);
358
+ if (object.fallback !== true) return fail(`has an invalid \`${kind}.fallback\` (${_describe(object.fallback)})`);
359
+ fallback = true;
360
+ }
361
+ order = object.order ?? "normal";
362
+ filter = object.filter;
363
+ handler = object.handler;
364
+ } else return fail(`has an invalid \`${kind}\` hook (got ${_describe(hook)})`);
365
+ const flags = {
366
+ ...kind === "load" ? { load: true } : {},
367
+ ...fallback && { fallback: true }
368
+ };
369
+ if (Array.isArray(filter)) {
370
+ const expr = _normalizeExpressions(filter, kind, failFilter);
371
+ const test = compileFilterExpressions(expr);
372
+ const prefilter = {
373
+ expr,
374
+ ...flags
375
+ };
376
+ const level = compilePrefilter(prefilter);
377
+ return {
378
+ name,
379
+ order,
380
+ prefilter,
381
+ fallback,
382
+ match: (id, moduleType, code, resolved) => test(id, moduleType, code) !== false && level(id, moduleType, resolved, code),
383
+ handler
384
+ };
385
+ }
386
+ const keys = kind === "transform" ? "`{ id?, code?, moduleType? }`" : "`{ id? }`";
387
+ if (filter != null && typeof filter !== "object") return failFilter("", `(got ${_describe(filter)}): expected ${keys} or an array of filter expressions`);
388
+ const { id: idValue, code: codeValue, moduleType: moduleTypeValue } = filter ?? {};
389
+ if (kind !== "transform") {
390
+ for (const [key, value] of [["code", codeValue], ["moduleType", moduleTypeValue]]) if (value) failFilter(`.${key}`, `(\`${kind}\` filters take \`id\` only)`);
391
+ }
392
+ const idPatterns = _stringFilter(idValue, ".id", failFilter);
393
+ const toPattern = (pattern) => _idPattern(pattern, kind !== "resolveId");
394
+ const id = idPatterns && {
395
+ include: idPatterns.include.map(toPattern),
396
+ exclude: idPatterns.exclude.map(toPattern)
397
+ };
398
+ const codePatterns = _stringFilter(codeValue, ".code", failFilter);
399
+ const code = codePatterns && compileFilter(codePatterns.include.map(_codePattern), codePatterns.exclude.map(_codePattern), _matchCode);
400
+ const prefilter = {
401
+ id,
402
+ moduleTypes: _moduleTypes(moduleTypeValue, failFilter),
403
+ ...flags
404
+ };
405
+ const level = compilePrefilter(prefilter);
406
+ return {
407
+ name,
408
+ order,
409
+ prefilter,
410
+ fallback,
411
+ match: (idValue, moduleType, codeValue, resolved) => (codeValue === void 0 || !code || code.test(codeValue)) && level(idValue, moduleType, resolved),
412
+ handler
413
+ };
414
+ }
415
+ const _isPattern = (value) => typeof value === "string" || value instanceof RegExp;
416
+ function _stringFilter(filter, key, fail) {
417
+ if (!filter) return;
418
+ const toList = (value) => {
419
+ const list = value ? Array.isArray(value) ? value : [value] : [];
420
+ const invalid = list.find((pattern) => !_isPattern(pattern));
421
+ if (invalid !== void 0) fail(key, `(got ${_describe(invalid)}): expected strings or RegExps, as a value, an array or \`{ include, exclude }\``);
422
+ return list;
423
+ };
424
+ if (typeof filter === "object" && !Array.isArray(filter) && !(filter instanceof RegExp)) {
425
+ const { include, exclude } = filter;
426
+ return {
427
+ include: toList(include),
428
+ exclude: toList(exclude)
429
+ };
430
+ }
431
+ return {
432
+ include: toList(filter),
433
+ exclude: []
434
+ };
435
+ }
436
+ function _moduleTypes(filter, fail) {
437
+ if (!filter) return;
438
+ const list = Array.isArray(filter) ? filter : typeof filter === "object" ? filter.include ?? [] : void 0;
439
+ if (!Array.isArray(list) || list.some((type) => typeof type !== "string")) return fail(".moduleType", `(got ${_describe(filter)}): expected an array of module types or \`{ include: [...] }\``);
440
+ if (list.length === 0) return fail(".moduleType", "(no module types): list at least one, or leave it out");
441
+ return list;
442
+ }
443
+ function _idPattern(pattern, resolve = true) {
444
+ return typeof pattern === "string" ? {
445
+ source: globToRegExp(resolve ? resolveGlob(pattern) : pattern).source,
446
+ flags: "",
447
+ glob: true
448
+ } : {
449
+ source: pattern.source,
450
+ flags: _statelessFlags(pattern)
451
+ };
452
+ }
453
+ function _codePattern(pattern) {
454
+ return typeof pattern === "string" ? pattern : new RegExp(pattern.source, _statelessFlags(pattern));
455
+ }
456
+ function _statelessFlags(pattern) {
457
+ return pattern.flags.replace(/[gy]/g, "");
458
+ }
459
+ function _normalizeExpressions(list, kind, fail) {
460
+ return list.map((value, index) => {
461
+ if (value?.kind !== "include" && value?.kind !== "exclude") return fail(`[${index}]`, `(got ${_describe(value?.kind ?? value)}): expected an \`include\` or \`exclude\` expression`);
462
+ return {
463
+ kind: value.kind,
464
+ expr: _normalizeNode(value.expr, `[${index}].expr`, kind, fail)
465
+ };
466
+ });
467
+ }
468
+ function _normalizeNode(node, key, hook, fail) {
469
+ const pattern = (allowed, expected) => {
470
+ if (!allowed(node.pattern)) fail(`${key}.pattern`, `(got ${_describe(node.pattern)}): expected ${expected}`);
471
+ return node.pattern;
472
+ };
473
+ switch (node?.kind) {
474
+ case "and":
475
+ case "or":
476
+ if (!Array.isArray(node.args) || node.args.length === 0) return fail(key, `(\`${node.kind}\` needs at least one argument)`);
477
+ return {
478
+ kind: node.kind,
479
+ args: node.args.map((arg, index) => _normalizeNode(arg, `${key}.args[${index}]`, hook, fail))
480
+ };
481
+ case "not": return {
482
+ kind: "not",
483
+ expr: _normalizeNode(node.expr, `${key}.expr`, hook, fail)
484
+ };
485
+ case "id": return {
486
+ kind: "id",
487
+ pattern: _idPattern(pattern(_isPattern, "a string or RegExp"), hook !== "resolveId")
488
+ };
489
+ case "code": {
490
+ if (hook !== "transform") return fail(key, `(\`code\` doesn't apply to \`${hook}\`)`);
491
+ const value = _codePattern(pattern(_isPattern, "a string or RegExp"));
492
+ return {
493
+ kind: "code",
494
+ pattern: typeof value === "string" ? value : {
495
+ source: value.source,
496
+ flags: value.flags
497
+ }
498
+ };
499
+ }
500
+ case "moduleType":
501
+ if (hook !== "transform") return fail(key, `(\`moduleType\` doesn't apply to \`${hook}\`)`);
502
+ return {
503
+ kind: "moduleType",
504
+ pattern: pattern((value) => typeof value === "string", "a string")
505
+ };
506
+ case "query": {
507
+ if (typeof node.key !== "string") return fail(`${key}.key`, `(got ${_describe(node.key)}): expected a string`);
508
+ const value = pattern((value) => _isPattern(value) || typeof value === "boolean", "a string, RegExp or boolean");
509
+ return {
510
+ kind: "query",
511
+ key: node.key,
512
+ pattern: value instanceof RegExp ? {
513
+ source: value.source,
514
+ flags: _statelessFlags(value)
515
+ } : value
516
+ };
517
+ }
518
+ case "importerId": return fail(key, `(\`importerId\` isn't supported)`);
519
+ default: return fail(key, `(got ${_describe(node?.kind ?? node)}): unknown expression kind`);
520
+ }
521
+ }
522
+ function _describe(value) {
523
+ if (value instanceof RegExp) return String(value);
524
+ if (Array.isArray(value)) return "an array";
525
+ if (value && typeof value === "object") {
526
+ const name = value.constructor?.name;
527
+ return name && name !== "Object" ? name : "an object";
528
+ }
529
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
530
+ }
531
+ function _createContext(name, id, code, resolve) {
532
+ const format = (log, pos) => typeof log === "string" ? `[env-runner] plugin "${name}" (${id}${_formatPosition(code, pos)}): ${log}` : `[env-runner] plugin "${name}" (${id}${_formatPosition(code, pos ?? _logPosition(log, id, code))}): ${log?.message}${_frame(log)}`;
533
+ return {
534
+ warn: (log, pos) => console.warn(format(log, pos)),
535
+ info: (log, pos) => console.info(format(log, pos)),
536
+ debug: () => {},
537
+ error: (log, pos) => {
538
+ throw new Error(format(log, pos), typeof log === "string" ? void 0 : { cause: log });
539
+ },
540
+ resolve,
541
+ addWatchFile: () => {},
542
+ getWatchFiles: () => [],
543
+ meta: { watchMode: false }
544
+ };
545
+ }
546
+ function _formatPosition(code, pos) {
547
+ if (typeof pos === "number") {
548
+ const before = code.slice(0, pos);
549
+ return `:${before.split("\n").length}:${pos - before.lastIndexOf("\n") - 1}`;
550
+ }
551
+ return pos && typeof pos === "object" ? `:${pos.line}:${pos.column}` : "";
552
+ }
553
+ function _matchCode(pattern, code) {
554
+ return typeof pattern === "string" ? code.includes(pattern) : pattern.test(code);
555
+ }
556
+ function _parseMap(map) {
557
+ if (typeof map === "string") try {
558
+ return JSON.parse(map);
559
+ } catch {
560
+ return;
561
+ }
562
+ return map ?? void 0;
563
+ }
564
+ const _warnedMaps = /* @__PURE__ */ new Set();
565
+ function _warnDroppedMap(first, second) {
566
+ const key = `${first}\0${second}`;
567
+ if (!_warnedMaps.has(key)) {
568
+ _warnedMaps.add(key);
569
+ console.warn(`[env-runner] plugins "${first}" and "${second}" both returned a source map; maps are not combined, so modules both change have none.`);
570
+ }
571
+ }
4
572
  var BaseEnvRunner = class {
5
573
  closed = false;
6
574
  _name;
@@ -11,11 +579,18 @@ var BaseEnvRunner = class {
11
579
  _address;
12
580
  _messageListeners;
13
581
  _pendingRequests;
582
+ _closeCause;
14
583
  _virtualResolved;
584
+ _virtualUpdates = Promise.resolve();
585
+ #virtualUpdateId = 0;
586
+ _processData;
587
+ _plugins;
588
+ _transformChannel;
15
589
  constructor(opts) {
16
590
  this._name = opts.name;
17
591
  this._workerEntry = opts.workerEntry;
18
592
  this._data = opts.data;
593
+ this._plugins = createPluginPipeline(opts.plugins, { resolveConditions: () => this._resolveConditions() });
19
594
  this._hooks = opts.hooks || {};
20
595
  this._messageListeners = /* @__PURE__ */ new Set();
21
596
  this._pendingRequests = /* @__PURE__ */ new Set();
@@ -49,24 +624,29 @@ var BaseEnvRunner = class {
49
624
  }
50
625
  waitForReady(timeout = 15e3) {
51
626
  if (this.ready) return Promise.resolve();
52
- if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
627
+ if (this.closed) return Promise.reject(closedBeforeReadyError(this._closeCause));
53
628
  return new Promise((resolve, reject) => {
54
629
  const timer = setTimeout(() => {
55
- this._messageListeners.delete(listener);
630
+ cleanup();
56
631
  reject(/* @__PURE__ */ new Error("Runner did not become ready in time"));
57
632
  }, timeout);
58
633
  const listener = () => {
59
634
  if (this.ready) {
60
- clearTimeout(timer);
61
- this._messageListeners.delete(listener);
635
+ cleanup();
62
636
  resolve();
63
- } else if (this.closed) {
64
- clearTimeout(timer);
65
- this._messageListeners.delete(listener);
66
- reject(/* @__PURE__ */ new Error("Runner closed before becoming ready"));
67
637
  }
68
638
  };
639
+ const onClose = (cause) => {
640
+ cleanup();
641
+ reject(closedBeforeReadyError(cause));
642
+ };
643
+ const cleanup = () => {
644
+ clearTimeout(timer);
645
+ this._messageListeners.delete(listener);
646
+ this._pendingRequests.delete(onClose);
647
+ };
69
648
  this._messageListeners.add(listener);
649
+ this._pendingRequests.add(onClose);
70
650
  });
71
651
  }
72
652
  rpc(name, data, opts) {
@@ -82,27 +662,27 @@ var BaseEnvRunner = class {
82
662
  }).then((msg) => msg.data);
83
663
  }
84
664
  async reloadModule(timeout = 5e3) {
665
+ await this._virtualUpdates;
85
666
  await this._request({ event: "reload-module" }, {
86
667
  match: (msg) => msg?.event === "module-reloaded",
87
668
  timeout,
88
669
  timeoutError: "Module reload timed out"
89
670
  });
90
671
  }
91
- async invalidateModule(specifier, timeout = 5e3) {
92
- const source = await this._refreshVirtualSource(specifier);
93
- await this._request({
94
- event: "invalidate-module",
95
- specifier,
96
- source
97
- }, {
98
- match: (msg) => msg?.event === "module-invalidated" && msg.specifier === specifier,
99
- timeout,
100
- timeoutError: `Module invalidation timed out for "${specifier}"`
101
- });
672
+ updateVirtualModules(changes, timeout = 5e3) {
673
+ return this._enqueueVirtualUpdate(() => changes, timeout);
674
+ }
675
+ invalidateModule(specifier, timeout = 5e3) {
676
+ return this._enqueueVirtualUpdate(() => {
677
+ const source = this._virtualSources?.[specifier];
678
+ if (source === void 0) throw new Error(`Cannot invalidate "${specifier}" (not a registered virtual module)`);
679
+ return { [specifier]: source };
680
+ }, timeout);
102
681
  }
103
682
  async close(cause) {
104
683
  if (this.closed) return;
105
684
  this.closed = true;
685
+ this._closeCause = cause;
106
686
  for (const rejectPending of this._pendingRequests) rejectPending(cause);
107
687
  this._pendingRequests.clear();
108
688
  this._hooks.onClose?.(this, cause);
@@ -110,6 +690,8 @@ var BaseEnvRunner = class {
110
690
  const onError = (error) => console.error(error);
111
691
  await this._closeRuntime().catch(onError);
112
692
  await this._closeSocket().catch(onError);
693
+ this._transformChannel?.close();
694
+ this._transformChannel = void 0;
113
695
  }
114
696
  async [Symbol.asyncDispose]() {
115
697
  await this.close();
@@ -133,6 +715,51 @@ var BaseEnvRunner = class {
133
715
  if (message?.event === "init-error" && !this.ready && !this.closed) this.close(new Error(String(message.error || "Worker initialization failed")));
134
716
  for (const listener of this._messageListeners) listener(message);
135
717
  }
718
+ _processEnv() {
719
+ let data = this._data || {};
720
+ const virtual = data.virtual;
721
+ if (virtual) data = {
722
+ ...data,
723
+ virtual: encodeVirtualModules(virtual)
724
+ };
725
+ if (this._plugins) data = {
726
+ ...data,
727
+ ...this._pluginWorkerData("socket")
728
+ };
729
+ try {
730
+ this._processData = JSON.stringify(data);
731
+ } catch (error) {
732
+ throw new TypeError(`Runner data must be JSON-serializable: ${error?.message || error}`, { cause: error });
733
+ }
734
+ return hostEnv({ ENV_RUNNER_NAME: this._name });
735
+ }
736
+ _resolveConditions() {}
737
+ _pluginWorkerData(kind) {
738
+ if (!this._plugins) return {};
739
+ this._transformChannel ??= kind === "port" ? openTransformPort(this._plugins) : openTransformSocket(this._plugins);
740
+ return { [PLUGINS_DATA_KEY]: {
741
+ prefilters: this._plugins.prefilters,
742
+ resolvePrefilters: this._plugins.resolvePrefilters,
743
+ ...this._transformChannel.channel
744
+ } };
745
+ }
746
+ _handleProcessMessage(message) {
747
+ if (message?.event !== "request-init-data") {
748
+ this._handleMessage(message);
749
+ return;
750
+ }
751
+ if (this.closed) return;
752
+ try {
753
+ this.sendMessage({
754
+ event: "init-data",
755
+ data: this._processData ?? "{}"
756
+ });
757
+ } catch (error) {
758
+ const cause = new Error(`Failed to send runner data to the worker over IPC: ${error?.message || error}`, { cause: error });
759
+ console.error(`[env-runner] ${cause.message}`);
760
+ this.close(cause);
761
+ }
762
+ }
136
763
  _request(message, opts) {
137
764
  if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("Runner is closed"));
138
765
  return new Promise((resolve, reject) => {
@@ -168,8 +795,27 @@ var BaseEnvRunner = class {
168
795
  }
169
796
  _resolveVirtualData() {
170
797
  const virtual = this._data?.virtual;
171
- this._virtualSources = virtual;
172
- if (!virtual || !Object.values(virtual).some((v) => typeof v === "function")) return;
798
+ this._virtualSources = { ...virtual };
799
+ this._data = { ...this._data };
800
+ warnVirtualPathCollisions(Object.keys(virtual ?? {}));
801
+ if (!virtual) return;
802
+ const plugins = this._plugins;
803
+ if (plugins) {
804
+ this._virtualResolved = resolveVirtualModules(virtual).then((resolved) => transformVirtualModules(plugins, resolved)).then((transformed) => {
805
+ this._data = {
806
+ ...this._data,
807
+ virtual: transformed
808
+ };
809
+ });
810
+ return this._virtualResolved;
811
+ }
812
+ if (!Object.values(virtual).some((v) => typeof v === "function")) try {
813
+ this._data.virtual = normalizeVirtualModules(virtual);
814
+ return;
815
+ } catch (error) {
816
+ this._virtualResolved = Promise.reject(error);
817
+ return this._virtualResolved;
818
+ }
173
819
  this._virtualResolved = resolveVirtualModules(virtual).then((resolved) => {
174
820
  this._data = {
175
821
  ...this._data,
@@ -178,20 +824,51 @@ var BaseEnvRunner = class {
178
824
  });
179
825
  return this._virtualResolved;
180
826
  }
181
- async _refreshVirtualSource(specifier) {
182
- await this._virtualResolved?.catch(() => {});
183
- const original = this._virtualSources?.[specifier];
184
- if (typeof original !== "function") return;
185
- const source = await original();
186
- const resolved = this._data?.virtual;
187
- if (resolved) resolved[specifier] = source;
188
- return source;
827
+ _enqueueVirtualUpdate(changes, timeout) {
828
+ const update = this._virtualUpdates.then(async () => {
829
+ await this._virtualResolved;
830
+ if (this.closed) throw new Error("Runner is closed");
831
+ const entries = Object.entries(changes()).filter(([, source]) => source !== void 0);
832
+ if (entries.length === 0) return;
833
+ const sets = Object.fromEntries(entries.filter((entry) => entry[1] !== null));
834
+ let resolved = await resolveVirtualModules(sets);
835
+ if (this._plugins) resolved = await transformVirtualModules(this._plugins, resolved);
836
+ const sources = this._virtualSources ??= {};
837
+ const virtual = (this._data ??= {}).virtual ??= {};
838
+ let added = false;
839
+ for (const [key, source] of entries) if (source === null) {
840
+ resolved[key] = null;
841
+ delete sources[key];
842
+ delete virtual[key];
843
+ } else {
844
+ added ||= !Object.hasOwn(sources, key);
845
+ sources[key] = source;
846
+ virtual[key] = resolved[key];
847
+ }
848
+ if (added) warnVirtualPathCollisions(Object.keys(virtual));
849
+ if (!this.ready) await this.waitForReady();
850
+ await this._applyVirtualUpdates(resolved, timeout);
851
+ });
852
+ this._virtualUpdates = update.catch(() => {});
853
+ return update;
854
+ }
855
+ async _applyVirtualUpdates(changes, timeout) {
856
+ const id = ++this.#virtualUpdateId;
857
+ await this._request({
858
+ event: "update-virtual-modules",
859
+ id,
860
+ changes: encodeVirtualModules(changes)
861
+ }, {
862
+ match: (msg) => msg?.event === "virtual-modules-updated" && msg.id === id,
863
+ timeout,
864
+ timeoutError: "Virtual module update timed out"
865
+ });
189
866
  }
190
867
  _initWithVirtualData(init) {
191
868
  const pending = this._resolveVirtualData();
192
869
  if (pending) pending.then(() => {
193
870
  if (!this.closed) init();
194
- }, (error) => this.close(error));
871
+ }).catch((error) => this.close(error));
195
872
  else init();
196
873
  }
197
874
  async _closeSocket() {
@@ -200,4 +877,7 @@ var BaseEnvRunner = class {
200
877
  this._address = void 0;
201
878
  }
202
879
  };
203
- export { BaseEnvRunner };
880
+ function closedBeforeReadyError(cause) {
881
+ return new Error("Runner closed before becoming ready", cause ? { cause } : void 0);
882
+ }
883
+ export { BaseEnvRunner, hostEnv };