env-runner 0.3.0 → 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.
@@ -1,6 +1,11 @@
1
- import { encodeVirtualModules, normalizeVirtualModules, resolveVirtualModules, warnVirtualPathCollisions } 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";
4
9
  function hostEnv(extra) {
5
10
  const env = {
6
11
  ...process.env,
@@ -11,6 +16,559 @@ function hostEnv(extra) {
11
16
  if (!("COLUMNS" in env) && stdout?.columns) env.COLUMNS = String(stdout.columns);
12
17
  return env;
13
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
+ }
14
572
  var BaseEnvRunner = class {
15
573
  closed = false;
16
574
  _name;
@@ -26,10 +584,13 @@ var BaseEnvRunner = class {
26
584
  _virtualUpdates = Promise.resolve();
27
585
  #virtualUpdateId = 0;
28
586
  _processData;
587
+ _plugins;
588
+ _transformChannel;
29
589
  constructor(opts) {
30
590
  this._name = opts.name;
31
591
  this._workerEntry = opts.workerEntry;
32
592
  this._data = opts.data;
593
+ this._plugins = createPluginPipeline(opts.plugins, { resolveConditions: () => this._resolveConditions() });
33
594
  this._hooks = opts.hooks || {};
34
595
  this._messageListeners = /* @__PURE__ */ new Set();
35
596
  this._pendingRequests = /* @__PURE__ */ new Set();
@@ -129,6 +690,8 @@ var BaseEnvRunner = class {
129
690
  const onError = (error) => console.error(error);
130
691
  await this._closeRuntime().catch(onError);
131
692
  await this._closeSocket().catch(onError);
693
+ this._transformChannel?.close();
694
+ this._transformChannel = void 0;
132
695
  }
133
696
  async [Symbol.asyncDispose]() {
134
697
  await this.close();
@@ -153,18 +716,33 @@ var BaseEnvRunner = class {
153
716
  for (const listener of this._messageListeners) listener(message);
154
717
  }
155
718
  _processEnv() {
156
- const data = this._data || {};
719
+ let data = this._data || {};
157
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
+ };
158
729
  try {
159
- this._processData = JSON.stringify(virtual ? {
160
- ...data,
161
- virtual: encodeVirtualModules(virtual)
162
- } : data);
730
+ this._processData = JSON.stringify(data);
163
731
  } catch (error) {
164
732
  throw new TypeError(`Runner data must be JSON-serializable: ${error?.message || error}`, { cause: error });
165
733
  }
166
734
  return hostEnv({ ENV_RUNNER_NAME: this._name });
167
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
+ }
168
746
  _handleProcessMessage(message) {
169
747
  if (message?.event !== "request-init-data") {
170
748
  this._handleMessage(message);
@@ -221,6 +799,16 @@ var BaseEnvRunner = class {
221
799
  this._data = { ...this._data };
222
800
  warnVirtualPathCollisions(Object.keys(virtual ?? {}));
223
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
+ }
224
812
  if (!Object.values(virtual).some((v) => typeof v === "function")) try {
225
813
  this._data.virtual = normalizeVirtualModules(virtual);
226
814
  return;
@@ -243,7 +831,8 @@ var BaseEnvRunner = class {
243
831
  const entries = Object.entries(changes()).filter(([, source]) => source !== void 0);
244
832
  if (entries.length === 0) return;
245
833
  const sets = Object.fromEntries(entries.filter((entry) => entry[1] !== null));
246
- const resolved = await resolveVirtualModules(sets);
834
+ let resolved = await resolveVirtualModules(sets);
835
+ if (this._plugins) resolved = await transformVirtualModules(this._plugins, resolved);
247
836
  const sources = this._virtualSources ??= {};
248
837
  const virtual = (this._data ??= {}).virtual ??= {};
249
838
  let added = false;