@nasti-toolchain/nasti 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,10 +10,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
10
10
  if (typeof require !== "undefined") return require.apply(this, arguments);
11
11
  throw Error('Dynamic require of "' + x + '" is not supported');
12
12
  });
13
- var __glob = (map) => (path18) => {
14
- var fn = map[path18];
13
+ var __glob = (map) => (path19) => {
14
+ var fn = map[path19];
15
15
  if (fn) return fn();
16
- throw new Error("Module not found in bundle: " + path18);
16
+ throw new Error("Module not found in bundle: " + path19);
17
17
  };
18
18
  var __esm = (fn, res) => function __init() {
19
19
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -225,6 +225,92 @@ var init_logger = __esm({
225
225
  }
226
226
  });
227
227
 
228
+ // src/core/plugin-api.ts
229
+ function orderPlugins(plugins) {
230
+ const baseline = plugins.map((plugin, index2) => ({ plugin, index: index2 })).sort((a, b) => enforceRank(a.plugin) - enforceRank(b.plugin) || a.index - b.index).map(({ plugin }) => plugin);
231
+ const indexesByName = /* @__PURE__ */ new Map();
232
+ baseline.forEach((plugin, index2) => {
233
+ const indexes = indexesByName.get(plugin.name) ?? [];
234
+ indexes.push(index2);
235
+ indexesByName.set(plugin.name, indexes);
236
+ });
237
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
238
+ const indegree = baseline.map(() => 0);
239
+ const addEdge = (from, to) => {
240
+ if (from === to || edges[from].has(to)) return;
241
+ edges[from].add(to);
242
+ indegree[to]++;
243
+ };
244
+ baseline.forEach((plugin, current) => {
245
+ for (const dependency of plugin.pre ?? []) {
246
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
247
+ }
248
+ for (const dependency of plugin.post ?? []) {
249
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
250
+ }
251
+ });
252
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
253
+ const ordered = [];
254
+ while (ready.length > 0) {
255
+ ready.sort((a, b) => a - b);
256
+ const current = ready.shift();
257
+ ordered.push(baseline[current]);
258
+ for (const next of edges[current]) {
259
+ indegree[next]--;
260
+ if (indegree[next] === 0) ready.push(next);
261
+ }
262
+ }
263
+ if (ordered.length !== baseline.length) {
264
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
265
+ throw new Error(
266
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
267
+ );
268
+ }
269
+ return ordered;
270
+ }
271
+ async function setupPluginApi(config, plugins) {
272
+ const exposed = /* @__PURE__ */ new Map();
273
+ const api = {
274
+ config,
275
+ logger: config.logger,
276
+ expose(key, value) {
277
+ if (exposed.has(key) && exposed.get(key) !== value) {
278
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
279
+ }
280
+ exposed.set(key, value);
281
+ },
282
+ useExposed(key) {
283
+ return exposed.get(key);
284
+ }
285
+ };
286
+ apiByConfig.set(config, api);
287
+ for (const plugin of plugins) {
288
+ await plugin.setup?.(api);
289
+ }
290
+ return api;
291
+ }
292
+ function getPluginApi(config) {
293
+ const api = apiByConfig.get(config);
294
+ if (!api) {
295
+ throw new Error(
296
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
297
+ );
298
+ }
299
+ return api;
300
+ }
301
+ function enforceRank(plugin) {
302
+ if (plugin.enforce === "pre") return 0;
303
+ if (plugin.enforce === "post") return 2;
304
+ return 1;
305
+ }
306
+ var apiByConfig;
307
+ var init_plugin_api = __esm({
308
+ "src/core/plugin-api.ts"() {
309
+ "use strict";
310
+ apiByConfig = /* @__PURE__ */ new WeakMap();
311
+ }
312
+ });
313
+
228
314
  // src/config/index.ts
229
315
  import { pathToFileURL } from "url";
230
316
  import path from "path";
@@ -265,6 +351,43 @@ async function loadConfigFromFile(root) {
265
351
  }
266
352
  return {};
267
353
  }
354
+ function detectFramework(root) {
355
+ const sourceRoot = path.resolve(root, "src");
356
+ if (containsVueFile(sourceRoot)) return "vue";
357
+ const packagePath = path.resolve(root, "package.json");
358
+ if (fs.existsSync(packagePath)) {
359
+ try {
360
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
361
+ const dependencies = {
362
+ ...pkg.dependencies ?? {},
363
+ ...pkg.devDependencies ?? {},
364
+ ...pkg.peerDependencies ?? {},
365
+ ...pkg.optionalDependencies ?? {}
366
+ };
367
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
368
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
369
+ if (hasVue && !hasReact) return "vue";
370
+ if (hasReact) return "react";
371
+ if (hasVue) return "vue";
372
+ } catch {
373
+ }
374
+ }
375
+ return "react";
376
+ }
377
+ function containsVueFile(dir, depth = 0) {
378
+ if (depth > 5 || !fs.existsSync(dir)) return false;
379
+ try {
380
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
381
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
382
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(path.join(dir, entry.name), depth + 1)) {
383
+ return true;
384
+ }
385
+ }
386
+ } catch {
387
+ return false;
388
+ }
389
+ return false;
390
+ }
268
391
  async function loadTsConfig(filePath) {
269
392
  const { transformSync: transformSync2 } = await import("oxc-transform");
270
393
  const code = fs.readFileSync(filePath, "utf-8");
@@ -311,7 +434,7 @@ async function resolveConfig(inlineConfig = {}, command) {
311
434
  base: merged.base ?? defaults.base,
312
435
  mode,
313
436
  target: merged.target ?? defaults.target,
314
- framework: merged.framework ?? defaults.framework,
437
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
315
438
  command,
316
439
  resolve: {
317
440
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -358,7 +481,13 @@ async function resolveConfig(inlineConfig = {}, command) {
358
481
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
359
482
  resolved.environments.client = {
360
483
  consumer,
361
- entry: [],
484
+ buildEnabled: envOptions.buildEnabled ?? true,
485
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
486
+ html: path.resolve(
487
+ root,
488
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
489
+ ),
490
+ driver: envOptions.driver,
362
491
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
363
492
  resolve: resolved.resolve,
364
493
  build: resolved.build
@@ -367,7 +496,10 @@ async function resolveConfig(inlineConfig = {}, command) {
367
496
  }
368
497
  resolved.environments[name] = {
369
498
  consumer,
370
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => path.resolve(root, e)),
499
+ buildEnabled: envOptions.buildEnabled ?? true,
500
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
501
+ html: envOptions.consumer === "client" && envOptions.html ? path.resolve(root, envOptions.html) : void 0,
502
+ driver: envOptions.driver,
371
503
  resolve: {
372
504
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
373
505
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -386,12 +518,13 @@ async function resolveConfig(inlineConfig = {}, command) {
386
518
  };
387
519
  }
388
520
  assertClientEnvironmentMirror(resolved);
389
- const filteredPlugins = rawPlugins.filter((p) => {
521
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
390
522
  if (!p.apply) return true;
391
523
  if (typeof p.apply === "function") return p.apply(resolved, env);
392
524
  return p.apply === command;
393
- });
525
+ }));
394
526
  resolved.plugins = filteredPlugins;
527
+ await setupPluginApi(resolved, filteredPlugins);
395
528
  if (resolved.target === "electron") {
396
529
  const autoExternal = detectNativeDeps(root);
397
530
  if (autoExternal.length > 0) {
@@ -407,6 +540,10 @@ async function resolveConfig(inlineConfig = {}, command) {
407
540
  }
408
541
  return resolved;
409
542
  }
543
+ function normalizeEnvironmentEntries(entry, root) {
544
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
545
+ return entries.map((item) => path.resolve(root, item));
546
+ }
410
547
  function detectNativeDeps(root) {
411
548
  const result = /* @__PURE__ */ new Set();
412
549
  const pkgJsonPath = path.resolve(root, "package.json");
@@ -524,6 +661,7 @@ var init_config = __esm({
524
661
  "use strict";
525
662
  init_defaults();
526
663
  init_logger();
664
+ init_plugin_api();
527
665
  CONFIG_FILES = [
528
666
  "nasti.config.ts",
529
667
  "nasti.config.js",
@@ -585,6 +723,7 @@ function resolvePlugin(config) {
585
723
  }
586
724
  if (!source.startsWith("/") && !source.startsWith(".")) {
587
725
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
726
+ if (config.command === "build") return null;
588
727
  try {
589
728
  const resolved = require2.resolve(source, {
590
729
  paths: [importer ? path2.dirname(importer) : config.root]
@@ -716,27 +855,27 @@ var require_process = __commonJS({
716
855
  var require_filesystem = __commonJS({
717
856
  "node_modules/detect-libc/lib/filesystem.js"(exports, module) {
718
857
  "use strict";
719
- var fs13 = __require("fs");
858
+ var fs14 = __require("fs");
720
859
  var LDD_PATH = "/usr/bin/ldd";
721
860
  var SELF_PATH = "/proc/self/exe";
722
861
  var MAX_LENGTH = 2048;
723
- var readFileSync = (path18) => {
724
- const fd = fs13.openSync(path18, "r");
862
+ var readFileSync = (path19) => {
863
+ const fd = fs14.openSync(path19, "r");
725
864
  const buffer = Buffer.alloc(MAX_LENGTH);
726
- const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
727
- fs13.close(fd, () => {
865
+ const bytesRead = fs14.readSync(fd, buffer, 0, MAX_LENGTH, 0);
866
+ fs14.close(fd, () => {
728
867
  });
729
868
  return buffer.subarray(0, bytesRead);
730
869
  };
731
- var readFile = (path18) => new Promise((resolve, reject) => {
732
- fs13.open(path18, "r", (err, fd) => {
870
+ var readFile = (path19) => new Promise((resolve, reject) => {
871
+ fs14.open(path19, "r", (err, fd) => {
733
872
  if (err) {
734
873
  reject(err);
735
874
  } else {
736
875
  const buffer = Buffer.alloc(MAX_LENGTH);
737
- fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
876
+ fs14.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
738
877
  resolve(buffer.subarray(0, bytesRead));
739
- fs13.close(fd, () => {
878
+ fs14.close(fd, () => {
740
879
  });
741
880
  });
742
881
  }
@@ -848,11 +987,11 @@ var require_detect_libc = __commonJS({
848
987
  }
849
988
  return null;
850
989
  };
851
- var familyFromInterpreterPath = (path18) => {
852
- if (path18) {
853
- if (path18.includes("/ld-musl-")) {
990
+ var familyFromInterpreterPath = (path19) => {
991
+ if (path19) {
992
+ if (path19.includes("/ld-musl-")) {
854
993
  return MUSL;
855
- } else if (path18.includes("/ld-linux-")) {
994
+ } else if (path19.includes("/ld-linux-")) {
856
995
  return GLIBC;
857
996
  }
858
997
  }
@@ -899,8 +1038,8 @@ var require_detect_libc = __commonJS({
899
1038
  cachedFamilyInterpreter = null;
900
1039
  try {
901
1040
  const selfContent = await readFile(SELF_PATH);
902
- const path18 = interpreterPath(selfContent);
903
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1041
+ const path19 = interpreterPath(selfContent);
1042
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
904
1043
  } catch (e) {
905
1044
  }
906
1045
  return cachedFamilyInterpreter;
@@ -912,8 +1051,8 @@ var require_detect_libc = __commonJS({
912
1051
  cachedFamilyInterpreter = null;
913
1052
  try {
914
1053
  const selfContent = readFileSync(SELF_PATH);
915
- const path18 = interpreterPath(selfContent);
916
- cachedFamilyInterpreter = familyFromInterpreterPath(path18);
1054
+ const path19 = interpreterPath(selfContent);
1055
+ cachedFamilyInterpreter = familyFromInterpreterPath(path19);
917
1056
  } catch (e) {
918
1057
  }
919
1058
  return cachedFamilyInterpreter;
@@ -1918,7 +2057,8 @@ function transformCode(filename, code, options = {}) {
1918
2057
  importSource: options.jsxImportSource ?? "react",
1919
2058
  refresh: options.reactRefresh ?? false
1920
2059
  } : void 0,
1921
- sourcemap: options.sourcemap ?? true
2060
+ sourcemap: options.sourcemap ?? true,
2061
+ target: options.target
1922
2062
  });
1923
2063
  if (result.errors && result.errors.length > 0) {
1924
2064
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -1976,8 +2116,8 @@ function vuePlugin(config) {
1976
2116
  let descriptor = descriptorCache.get(filePath);
1977
2117
  if (!descriptor) {
1978
2118
  try {
1979
- const fs13 = await import("fs");
1980
- const source = fs13.readFileSync(filePath, "utf-8");
2119
+ const fs14 = await import("fs");
2120
+ const source = fs14.readFileSync(filePath, "utf-8");
1981
2121
  const parsed = sfc.parse(source, { filename: filePath });
1982
2122
  if (parsed.errors.length) return null;
1983
2123
  descriptor = parsed.descriptor;
@@ -2122,7 +2262,7 @@ function htmlPlugin(config) {
2122
2262
  transformIndexHtml(html) {
2123
2263
  const tags = [];
2124
2264
  if (config.command === "serve") {
2125
- const isReactLike = config.framework === "react" || config.framework === "auto";
2265
+ const isReactLike = config.framework === "react";
2126
2266
  if (isReactLike) {
2127
2267
  tags.push({
2128
2268
  tag: "script",
@@ -2176,8 +2316,8 @@ function serializeTag(tag) {
2176
2316
  }
2177
2317
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
2178
2318
  }
2179
- async function readHtmlFile(root) {
2180
- const htmlPath = path6.resolve(root, "index.html");
2319
+ async function readHtmlFile(root, htmlFile = "index.html") {
2320
+ const htmlPath = path6.isAbsolute(htmlFile) ? htmlFile : path6.resolve(root, htmlFile);
2181
2321
  if (!fs4.existsSync(htmlPath)) return null;
2182
2322
  return fs4.readFileSync(htmlPath, "utf-8");
2183
2323
  }
@@ -2198,16 +2338,27 @@ window.__vite_plugin_react_preamble_installed__ = true;
2198
2338
  // src/plugins/builtins.ts
2199
2339
  function resolvePluginList(config, userPlugins, opts = {}) {
2200
2340
  const isServe = config.command === "serve";
2341
+ let environmentOptions;
2342
+ if (opts.environmentName) {
2343
+ environmentOptions = config.environments[opts.environmentName];
2344
+ if (!environmentOptions) {
2345
+ throw new Error(
2346
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
2347
+ );
2348
+ }
2349
+ }
2350
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
2351
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
2201
2352
  return [
2202
2353
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
2203
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
2204
- resolvePlugin(config),
2205
- cssPlugin(config, opts.cssEngine, opts.consumer),
2206
- assetsPlugin(config),
2207
- ...isServe ? [htmlPlugin(config)] : [],
2354
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
2355
+ resolvePlugin(pluginConfig),
2356
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
2357
+ assetsPlugin(pluginConfig),
2358
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
2208
2359
  ...userPlugins,
2209
2360
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
2210
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
2361
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
2211
2362
  ];
2212
2363
  }
2213
2364
  var init_builtins = __esm({
@@ -2223,21 +2374,11 @@ var init_builtins = __esm({
2223
2374
  });
2224
2375
 
2225
2376
  // src/core/plugin-container.ts
2226
- function sortPlugins(plugins) {
2227
- const pre = [];
2228
- const normal = [];
2229
- const post = [];
2230
- for (const plugin of plugins) {
2231
- if (plugin.enforce === "pre") pre.push(plugin);
2232
- else if (plugin.enforce === "post") post.push(plugin);
2233
- else normal.push(plugin);
2234
- }
2235
- return [...pre, ...normal, ...post];
2236
- }
2237
2377
  var PluginContainer;
2238
2378
  var init_plugin_container = __esm({
2239
2379
  "src/core/plugin-container.ts"() {
2240
2380
  "use strict";
2381
+ init_plugin_api();
2241
2382
  PluginContainer = class {
2242
2383
  plugins;
2243
2384
  config;
@@ -2248,7 +2389,7 @@ var init_plugin_container = __esm({
2248
2389
  constructor(config, environment) {
2249
2390
  this.config = config;
2250
2391
  this.environment = environment;
2251
- this.plugins = sortPlugins(config.plugins);
2392
+ this.plugins = orderPlugins(config.plugins);
2252
2393
  this.ctx = this.createContext();
2253
2394
  }
2254
2395
  createContext() {
@@ -2347,17 +2488,35 @@ var init_plugin_container = __esm({
2347
2488
  }
2348
2489
  });
2349
2490
 
2491
+ // src/core/url.ts
2492
+ function removeTimestampQuery(url) {
2493
+ const hashIndex = url.indexOf("#");
2494
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2495
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2496
+ const queryIndex = withoutHash.indexOf("?");
2497
+ if (queryIndex < 0) return url;
2498
+ const pathname = withoutHash.slice(0, queryIndex);
2499
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
2500
+ return pathname + (query ? `?${query}` : "") + hash;
2501
+ }
2502
+ var init_url = __esm({
2503
+ "src/core/url.ts"() {
2504
+ "use strict";
2505
+ }
2506
+ });
2507
+
2350
2508
  // src/core/module-graph.ts
2351
2509
  var ModuleGraph;
2352
2510
  var init_module_graph = __esm({
2353
2511
  "src/core/module-graph.ts"() {
2354
2512
  "use strict";
2513
+ init_url();
2355
2514
  ModuleGraph = class {
2356
2515
  urlToModuleMap = /* @__PURE__ */ new Map();
2357
2516
  idToModuleMap = /* @__PURE__ */ new Map();
2358
2517
  fileToModulesMap = /* @__PURE__ */ new Map();
2359
2518
  getModuleByUrl(url) {
2360
- return this.urlToModuleMap.get(url);
2519
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
2361
2520
  }
2362
2521
  getModuleById(id) {
2363
2522
  return this.idToModuleMap.get(id);
@@ -2366,10 +2525,11 @@ var init_module_graph = __esm({
2366
2525
  return this.fileToModulesMap.get(file);
2367
2526
  }
2368
2527
  async ensureEntryFromUrl(url) {
2369
- let mod = this.urlToModuleMap.get(url);
2528
+ const normalizedUrl = removeTimestampQuery(url);
2529
+ let mod = this.urlToModuleMap.get(normalizedUrl);
2370
2530
  if (mod) return mod;
2371
- mod = this.createModule(url);
2372
- this.urlToModuleMap.set(url, mod);
2531
+ mod = this.createModule(normalizedUrl);
2532
+ this.urlToModuleMap.set(normalizedUrl, mod);
2373
2533
  return mod;
2374
2534
  }
2375
2535
  createModule(url, id) {
@@ -2383,6 +2543,7 @@ var init_module_graph = __esm({
2383
2543
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
2384
2544
  transformResult: null,
2385
2545
  lastHMRTimestamp: 0,
2546
+ invalidationVersion: 0,
2386
2547
  isSelfAccepting: false
2387
2548
  };
2388
2549
  this.idToModuleMap.set(mod.id, mod);
@@ -2425,10 +2586,64 @@ var init_module_graph = __esm({
2425
2586
  }
2426
2587
  }
2427
2588
  }
2589
+ /**
2590
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
2591
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
2592
+ */
2593
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
2594
+ const importedModules = await Promise.all(
2595
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
2596
+ );
2597
+ const acceptedModules = await Promise.all(
2598
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
2599
+ );
2600
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
2601
+ return null;
2602
+ }
2603
+ const previousImports = new Set(mod.importedModules);
2604
+ for (const imported of previousImports) {
2605
+ imported.importers.delete(mod);
2606
+ }
2607
+ mod.importedModules.clear();
2608
+ mod.acceptedHmrDeps.clear();
2609
+ for (const imported of importedModules) {
2610
+ mod.importedModules.add(imported);
2611
+ imported.importers.add(mod);
2612
+ }
2613
+ for (const accepted of acceptedModules) {
2614
+ mod.acceptedHmrDeps.add(accepted);
2615
+ }
2616
+ mod.isSelfAccepting = isSelfAccepting;
2617
+ const pruned = /* @__PURE__ */ new Set();
2618
+ for (const imported of previousImports) {
2619
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
2620
+ pruned.add(imported);
2621
+ }
2622
+ }
2623
+ return pruned;
2624
+ }
2428
2625
  /** 使模块的转换缓存失效 */
2429
- invalidateModule(mod) {
2626
+ invalidateModule(mod, timestamp = Date.now()) {
2430
2627
  mod.transformResult = null;
2431
- mod.lastHMRTimestamp = Date.now();
2628
+ mod.lastHMRTimestamp = timestamp;
2629
+ mod.invalidationVersion++;
2630
+ }
2631
+ /**
2632
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
2633
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
2634
+ */
2635
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
2636
+ if (seen.has(mod)) return;
2637
+ seen.add(mod);
2638
+ this.invalidateModule(mod, timestamp);
2639
+ for (const importer of mod.importers) {
2640
+ if (importer.acceptedHmrDeps.has(mod)) continue;
2641
+ if (importer.isSelfAccepting) {
2642
+ this.invalidateModule(importer, timestamp);
2643
+ continue;
2644
+ }
2645
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
2646
+ }
2432
2647
  }
2433
2648
  /** 使所有模块缓存失效 */
2434
2649
  invalidateAll() {
@@ -2439,34 +2654,32 @@ var init_module_graph = __esm({
2439
2654
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
2440
2655
  getHmrBoundaries(mod) {
2441
2656
  const boundaries = [];
2442
- const visited = /* @__PURE__ */ new Set();
2443
- const propagate = (node, via) => {
2444
- if (visited.has(node)) return true;
2445
- visited.add(node);
2446
- if (node.isSelfAccepting) {
2447
- boundaries.push({ boundary: node, acceptedVia: via });
2448
- return true;
2657
+ const traversed = /* @__PURE__ */ new Set();
2658
+ const addBoundary = (boundary, acceptedVia) => {
2659
+ if (!boundaries.some(
2660
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
2661
+ )) {
2662
+ boundaries.push({ boundary, acceptedVia });
2449
2663
  }
2450
- if (node.acceptedHmrDeps.has(via)) {
2451
- boundaries.push({ boundary: node, acceptedVia: via });
2664
+ };
2665
+ const propagate = (node) => {
2666
+ if (traversed.has(node)) return true;
2667
+ traversed.add(node);
2668
+ if (node.isSelfAccepting) {
2669
+ addBoundary(node, node);
2452
2670
  return true;
2453
2671
  }
2454
2672
  if (node.importers.size === 0) return false;
2455
2673
  for (const importer of node.importers) {
2456
- if (!propagate(importer, node)) return false;
2674
+ if (importer.acceptedHmrDeps.has(node)) {
2675
+ addBoundary(importer, node);
2676
+ continue;
2677
+ }
2678
+ if (!propagate(importer)) return false;
2457
2679
  }
2458
2680
  return true;
2459
2681
  };
2460
- if (mod.isSelfAccepting) {
2461
- boundaries.push({ boundary: mod, acceptedVia: mod });
2462
- return boundaries;
2463
- }
2464
- for (const importer of mod.importers) {
2465
- if (!propagate(importer, mod)) {
2466
- return [];
2467
- }
2468
- }
2469
- return boundaries;
2682
+ return propagate(mod) ? boundaries : [];
2470
2683
  }
2471
2684
  };
2472
2685
  }
@@ -2544,6 +2757,7 @@ var init_environment = __esm({
2544
2757
  init_module_graph();
2545
2758
  init_hot_channel();
2546
2759
  init_debug();
2760
+ init_plugin_api();
2547
2761
  debug2 = createDebugger("nasti:environment");
2548
2762
  NastiEnvironment = class {
2549
2763
  name;
@@ -2552,6 +2766,7 @@ var init_environment = __esm({
2552
2766
  config;
2553
2767
  options;
2554
2768
  hot;
2769
+ driver;
2555
2770
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
2556
2771
  plugins = [];
2557
2772
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -2559,6 +2774,8 @@ var init_environment = __esm({
2559
2774
  /** per-env 模块图(dev 管线使用) */
2560
2775
  moduleGraph;
2561
2776
  candidatePlugins;
2777
+ pluginApi;
2778
+ buildMetadata = {};
2562
2779
  initialized = false;
2563
2780
  constructor(name, config, init = {}) {
2564
2781
  const options = config.environments[name];
@@ -2575,6 +2792,7 @@ var init_environment = __esm({
2575
2792
  this.hot = init.hot ?? createNoopHotChannel();
2576
2793
  this.moduleGraph = new ModuleGraph();
2577
2794
  this.candidatePlugins = init.plugins ?? config.plugins;
2795
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
2578
2796
  }
2579
2797
  /** 过滤插件并建 per-env PluginContainer */
2580
2798
  async init() {
@@ -2585,10 +2803,57 @@ var init_environment = __esm({
2585
2803
  { ...this.config, plugins: this.plugins },
2586
2804
  this
2587
2805
  );
2806
+ if (this.options.driver) {
2807
+ const claimed = [];
2808
+ for (const plugin of this.plugins) {
2809
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
2810
+ if (driver) claimed.push({ plugin, driver });
2811
+ }
2812
+ if (claimed.length === 0) {
2813
+ throw new Error(
2814
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
2815
+ );
2816
+ }
2817
+ if (claimed.length > 1) {
2818
+ throw new Error(
2819
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
2820
+ );
2821
+ }
2822
+ this.driver = claimed[0].driver;
2823
+ debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2824
+ }
2588
2825
  debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2589
2826
  }
2827
+ getDriverContext() {
2828
+ return {
2829
+ environment: this,
2830
+ config: this.config,
2831
+ api: this.pluginApi,
2832
+ logger: this.config.logger
2833
+ };
2834
+ }
2835
+ setBuildMetadata(metadata) {
2836
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
2837
+ const { entries, ...nextMetadata } = metadata;
2838
+ this.buildMetadata = {
2839
+ ...currentMetadata,
2840
+ ...nextMetadata,
2841
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
2842
+ };
2843
+ }
2844
+ getBuildMetadata() {
2845
+ const { entries, ...metadata } = this.buildMetadata;
2846
+ return {
2847
+ ...metadata,
2848
+ ...entries ? { entries: { ...entries } } : {}
2849
+ };
2850
+ }
2590
2851
  async close() {
2591
- await this.hot.close?.();
2852
+ try {
2853
+ await this.driver?.close?.(this.getDriverContext());
2854
+ } finally {
2855
+ await this.hot.close?.();
2856
+ }
2592
2857
  }
2593
2858
  };
2594
2859
  }
@@ -2747,16 +3012,145 @@ var init_reporter = __esm({
2747
3012
  }
2748
3013
  });
2749
3014
 
3015
+ // src/core/build-app-context.ts
3016
+ import fs6 from "fs";
3017
+ import path9 from "path";
3018
+ function createBuildAppContext(config, results) {
3019
+ const output = [];
3020
+ const emitted = /* @__PURE__ */ new Set();
3021
+ const outDir = path9.resolve(config.root, config.build.outDir);
3022
+ let environmentArtifacts;
3023
+ return {
3024
+ config,
3025
+ results,
3026
+ get output() {
3027
+ return Object.freeze([...output]);
3028
+ },
3029
+ getResult(environmentName) {
3030
+ return results[environmentName];
3031
+ },
3032
+ getArtifact(environmentName, fileName) {
3033
+ const normalized = normalizeEnvironmentFileName(fileName);
3034
+ return results[environmentName]?.output.find(
3035
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
3036
+ );
3037
+ },
3038
+ getEntry(environmentName, entryName) {
3039
+ const result = results[environmentName];
3040
+ const fileName = result?.entries?.[entryName];
3041
+ if (!fileName) return void 0;
3042
+ return result.output.find(
3043
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
3044
+ );
3045
+ },
3046
+ getManifest(environmentName) {
3047
+ return results[environmentName]?.manifest;
3048
+ },
3049
+ emitFile(file) {
3050
+ const fileName = normalizeAppFileName(file.fileName);
3051
+ const collisionKey = artifactCollisionKey(fileName);
3052
+ if (emitted.has(collisionKey)) {
3053
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
3054
+ }
3055
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
3056
+ if (environmentArtifacts.has(collisionKey)) {
3057
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
3058
+ }
3059
+ const target = path9.resolve(outDir, ...fileName.split("/"));
3060
+ const relative = path9.relative(outDir, target);
3061
+ if (relative.startsWith("..") || path9.isAbsolute(relative)) {
3062
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
3063
+ }
3064
+ assertNoSymlinkComponents(outDir, fileName);
3065
+ fs6.mkdirSync(path9.dirname(target), { recursive: true });
3066
+ fs6.writeFileSync(target, file.source);
3067
+ const artifact = {
3068
+ ...file,
3069
+ fileName,
3070
+ type: "asset"
3071
+ };
3072
+ emitted.add(collisionKey);
3073
+ output.push(artifact);
3074
+ return fileName;
3075
+ }
3076
+ };
3077
+ }
3078
+ function normalizeEnvironmentFileName(fileName) {
3079
+ return path9.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
3080
+ }
3081
+ function isInvalidEnvironmentFileName(fileName) {
3082
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || path9.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
3083
+ }
3084
+ function normalizeAppFileName(fileName) {
3085
+ const normalized = normalizeEnvironmentFileName(fileName);
3086
+ if (isInvalidEnvironmentFileName(normalized)) {
3087
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
3088
+ }
3089
+ return normalized;
3090
+ }
3091
+ function artifactCollisionKey(fileName) {
3092
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
3093
+ }
3094
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
3095
+ const occupied = /* @__PURE__ */ new Set();
3096
+ for (const [environmentName, result] of Object.entries(results)) {
3097
+ const environment = config.environments[environmentName];
3098
+ if (!environment) continue;
3099
+ const environmentOutDir = path9.resolve(config.root, environment.build.outDir);
3100
+ for (const artifact of result.output) {
3101
+ const artifactPath = path9.resolve(
3102
+ environmentOutDir,
3103
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
3104
+ );
3105
+ const relative = path9.relative(appOutDir, artifactPath);
3106
+ if (!relative.startsWith("..") && !path9.isAbsolute(relative)) {
3107
+ occupied.add(artifactCollisionKey(relative));
3108
+ }
3109
+ }
3110
+ }
3111
+ return occupied;
3112
+ }
3113
+ function assertNoSymlinkComponents(outDir, fileName) {
3114
+ let current = outDir;
3115
+ for (const segment of fileName.split("/")) {
3116
+ current = path9.join(current, segment);
3117
+ let stats;
3118
+ try {
3119
+ stats = fs6.lstatSync(current);
3120
+ } catch (error) {
3121
+ if (error.code === "ENOENT") continue;
3122
+ throw error;
3123
+ }
3124
+ if (stats.isSymbolicLink()) {
3125
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
3126
+ }
3127
+ }
3128
+ }
3129
+ function inferEnvironmentEntries(output) {
3130
+ const entries = {};
3131
+ for (const artifact of output) {
3132
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
3133
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
3134
+ }
3135
+ return Object.keys(entries).length > 0 ? entries : void 0;
3136
+ }
3137
+ var init_build_app_context = __esm({
3138
+ "src/core/build-app-context.ts"() {
3139
+ "use strict";
3140
+ }
3141
+ });
3142
+
2750
3143
  // src/build/index.ts
2751
3144
  var build_exports = {};
2752
3145
  __export(build_exports, {
2753
3146
  build: () => build,
2754
3147
  getRolldownOptions: () => getRolldownOptions,
3148
+ replaceEntryScript: () => replaceEntryScript,
2755
3149
  resolveClientEntries: () => resolveClientEntries,
2756
3150
  toRolldownPlugins: () => toRolldownPlugins
2757
3151
  });
2758
- import path9 from "path";
2759
- import fs6 from "fs";
3152
+ import path10 from "path";
3153
+ import fs7 from "fs";
2760
3154
  import { builtinModules } from "module";
2761
3155
  import { rolldown } from "rolldown";
2762
3156
  import pc4 from "picocolors";
@@ -2764,9 +3158,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2764
3158
  const config = environment.config;
2765
3159
  const envOptions = environment.options;
2766
3160
  const isServer = environment.consumer === "server";
2767
- const outDir = path9.resolve(config.root, envOptions.build.outDir);
3161
+ const outDir = path10.resolve(config.root, envOptions.build.outDir);
2768
3162
  const assetsDir = envOptions.build.assetsDir;
2769
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
3163
+ const {
3164
+ output: userOutput,
3165
+ transform: userTransform,
3166
+ resolve: userResolve,
3167
+ ...restInputOptions
3168
+ } = envOptions.build.rolldownOptions;
2770
3169
  const vueDefine = config.framework === "vue" ? {
2771
3170
  __VUE_OPTIONS_API__: "true",
2772
3171
  __VUE_PROD_DEVTOOLS__: "false",
@@ -2780,19 +3179,22 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2780
3179
  input: entryPoints,
2781
3180
  transform: { ...userTransform, define: mergedDefine },
2782
3181
  plugins: rolldownPlugins,
3182
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
3183
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
3184
+ resolve: {
3185
+ ...userResolve ?? {},
3186
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
3187
+ conditionNames: envOptions.resolve.conditions,
3188
+ mainFields: envOptions.resolve.mainFields
3189
+ },
2783
3190
  ...isServer ? {
2784
3191
  platform: restInputOptions.platform ?? "node",
2785
- resolve: {
2786
- conditionNames: envOptions.resolve.conditions,
2787
- mainFields: envOptions.resolve.mainFields,
2788
- ...restInputOptions.resolve
2789
- },
2790
3192
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
2791
3193
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
2792
3194
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
2793
3195
  external: restInputOptions.external ?? ((id) => {
2794
3196
  if (NODE_BUILTINS.has(id)) return true;
2795
- return !id.startsWith(".") && !path9.isAbsolute(id) && !id.startsWith("\0");
3197
+ return !id.startsWith(".") && !path10.isAbsolute(id) && !id.startsWith("\0");
2796
3198
  })
2797
3199
  } : {}
2798
3200
  };
@@ -2819,37 +3221,139 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
2819
3221
  };
2820
3222
  return { inputOptions, outputOptions, outDir };
2821
3223
  }
2822
- function toRolldownPlugins(plugins) {
3224
+ function toRolldownPlugins(plugins, environment) {
3225
+ const wrap = (hook) => {
3226
+ if (!hook) return hook;
3227
+ return function(...args) {
3228
+ return hook.apply(attachEnvironment(this, environment), args);
3229
+ };
3230
+ };
2823
3231
  return plugins.map((p) => ({
2824
3232
  name: p.name,
2825
- resolveId: p.resolveId,
2826
- load: p.load,
2827
- transform: p.transform,
2828
- buildStart: p.buildStart,
2829
- buildEnd: p.buildEnd,
3233
+ resolveId: wrap(p.resolveId),
3234
+ load: wrap(p.load),
3235
+ transform: wrap(p.transform),
3236
+ buildStart: wrap(p.buildStart),
3237
+ buildEnd: wrap(p.buildEnd),
2830
3238
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
2831
- closeBundle: p.closeBundle,
2832
- renderChunk: p.renderChunk,
2833
- augmentChunkHash: p.augmentChunkHash,
2834
- generateBundle: p.generateBundle
3239
+ closeBundle: wrap(p.closeBundle),
3240
+ renderChunk: wrap(p.renderChunk),
3241
+ augmentChunkHash: wrap(p.augmentChunkHash),
3242
+ generateBundle: wrap(p.generateBundle)
2835
3243
  }));
2836
3244
  }
3245
+ function attachEnvironment(context, environment) {
3246
+ if (context?.environment === environment) return context;
3247
+ try {
3248
+ Object.defineProperty(context, "environment", {
3249
+ configurable: true,
3250
+ enumerable: false,
3251
+ writable: false,
3252
+ value: environment
3253
+ });
3254
+ return context;
3255
+ } catch {
3256
+ return new Proxy(context, {
3257
+ get(target, property) {
3258
+ if (property === "environment") return environment;
3259
+ const value = Reflect.get(target, property, target);
3260
+ return typeof value === "function" ? value.bind(target) : value;
3261
+ },
3262
+ set(target, property, value) {
3263
+ return Reflect.set(target, property, value, target);
3264
+ }
3265
+ });
3266
+ }
3267
+ }
3268
+ function finalizeEnvironmentResult(environment, result) {
3269
+ const metadata = environment.getBuildMetadata();
3270
+ const inferredEntries = inferEnvironmentEntries(result.output);
3271
+ const entries = {
3272
+ ...inferredEntries,
3273
+ ...metadata.entries,
3274
+ ...result.entries
3275
+ };
3276
+ const normalizedEntries = Object.fromEntries(
3277
+ Object.entries(entries).map(([name, fileName]) => {
3278
+ const normalized = normalizeEnvironmentFileName(fileName);
3279
+ if (isInvalidEnvironmentFileName(normalized)) {
3280
+ throw new Error(
3281
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
3282
+ );
3283
+ }
3284
+ return [name, normalized];
3285
+ })
3286
+ );
3287
+ return {
3288
+ ...metadata,
3289
+ ...result,
3290
+ output: result.output,
3291
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
3292
+ };
3293
+ }
3294
+ function prepareBuildOutputDirectories(config, buildableNames) {
3295
+ const directories = /* @__PURE__ */ new Set();
3296
+ const protectedPaths = /* @__PURE__ */ new Set();
3297
+ const clientIsBuilt = buildableNames.includes("client");
3298
+ if (!clientIsBuilt && config.build.emptyOutDir) {
3299
+ directories.add(path10.resolve(config.root, config.build.outDir));
3300
+ }
3301
+ for (const name of buildableNames) {
3302
+ const environment = config.environments[name];
3303
+ const outDir = path10.resolve(config.root, environment.build.outDir);
3304
+ if (!environment.build.emptyOutDir) {
3305
+ protectedPaths.add(outDir);
3306
+ continue;
3307
+ }
3308
+ if (!environment.driver) directories.add(outDir);
3309
+ }
3310
+ const containsPath = (parent, child) => {
3311
+ const relative = path10.relative(parent, child);
3312
+ return relative === "" || !relative.startsWith("..") && !path10.isAbsolute(relative);
3313
+ };
3314
+ const roots = [...directories].filter(
3315
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
3316
+ ).sort((a, b) => a.length - b.length).filter(
3317
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
3318
+ );
3319
+ for (const directory of roots) {
3320
+ if (fs7.existsSync(directory)) fs7.rmSync(directory, { recursive: true, force: true });
3321
+ }
3322
+ }
3323
+ function assertDriverBuildResult(environment, result) {
3324
+ const output = result != null && typeof result === "object" ? result.output : void 0;
3325
+ const hasValidOutput = Array.isArray(output) && output.every(
3326
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
3327
+ );
3328
+ if (!hasValidOutput) {
3329
+ throw new Error(
3330
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
3331
+ );
3332
+ }
3333
+ }
2837
3334
  function resolveClientEntries(config, html) {
3335
+ const configuredEntries = config.environments.client?.entry ?? [];
3336
+ if (configuredEntries.length > 0) return configuredEntries;
2838
3337
  const entryPoints = [];
3338
+ const htmlFile = config.environments.client?.html;
3339
+ const htmlDir = htmlFile ? path10.dirname(htmlFile) : config.root;
2839
3340
  if (html) {
2840
3341
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
2841
3342
  for (const match of scriptMatches) {
2842
3343
  const src = match[1];
2843
3344
  if (src && !src.startsWith("http")) {
2844
- entryPoints.push(path9.resolve(config.root, src.replace(/^\//, "")));
3345
+ const cleanSrc = src.split(/[?#]/, 1)[0];
3346
+ entryPoints.push(
3347
+ cleanSrc.startsWith("/") ? path10.resolve(config.root, cleanSrc.replace(/^\//, "")) : path10.resolve(htmlDir, cleanSrc)
3348
+ );
2845
3349
  }
2846
3350
  }
2847
3351
  }
2848
3352
  if (entryPoints.length === 0) {
2849
3353
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
2850
3354
  for (const entry of fallbackEntries) {
2851
- const fullPath = path9.resolve(config.root, entry);
2852
- if (fs6.existsSync(fullPath)) {
3355
+ const fullPath = path10.resolve(config.root, entry);
3356
+ if (fs7.existsSync(fullPath)) {
2853
3357
  entryPoints.push(fullPath);
2854
3358
  break;
2855
3359
  }
@@ -2877,130 +3381,229 @@ async function build(inlineConfig = {}) {
2877
3381
  const startTime = performance.now();
2878
3382
  logger.info(
2879
3383
  pc4.cyan(`
2880
- nasti v${"2.2.0"} `) + pc4.green(`building for ${config.mode}...`)
3384
+ nasti v${"2.4.0"} `) + pc4.green(`building for ${config.mode}...`)
2881
3385
  );
2882
3386
  debug4?.(`root: ${config.root}`);
2883
- const buildableNames = Object.keys(config.environments).filter(
2884
- (name) => name === "client" || config.environments[name].entry.length > 0
2885
- );
3387
+ const buildableNames = Object.keys(config.environments).filter((name) => {
3388
+ const environment = config.environments[name];
3389
+ if (!environment.buildEnabled) return false;
3390
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
3391
+ });
2886
3392
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
3393
+ prepareBuildOutputDirectories(config, buildableNames);
2887
3394
  const environments = {};
3395
+ const environmentResults = {};
3396
+ const initializedEnvironments = [];
3397
+ const buildAppContext = createBuildAppContext(config, environmentResults);
2888
3398
  let clientOutput = [];
2889
- for (const name of buildableNames) {
2890
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
2891
- environments[name] = output;
2892
- if (name === "client") clientOutput = output;
2893
- if (buildableNames.length > 1) {
2894
- debug4?.(`environment "${name}" built (${output.length} files)`);
3399
+ let buildFailed = false;
3400
+ try {
3401
+ for (const name of buildableNames) {
3402
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
3403
+ initializedEnvironments.push(built.environment);
3404
+ environments[name] = built.result.output;
3405
+ environmentResults[name] = built.result;
3406
+ if (name === "client") clientOutput = built.result.output;
3407
+ if (buildableNames.length > 1) {
3408
+ debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3409
+ }
3410
+ }
3411
+ const pluginApi = getPluginApi(config);
3412
+ for (const plugin of config.plugins) {
3413
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
3414
+ }
3415
+ } catch (error) {
3416
+ buildFailed = true;
3417
+ throw error;
3418
+ } finally {
3419
+ let closeFailed = false;
3420
+ let firstCloseError;
3421
+ for (const environment of [...initializedEnvironments].reverse()) {
3422
+ try {
3423
+ await environment.close();
3424
+ } catch (error) {
3425
+ if (!closeFailed) {
3426
+ closeFailed = true;
3427
+ firstCloseError = error;
3428
+ }
3429
+ const closeError = error instanceof Error ? error : new Error(String(error));
3430
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
3431
+ error: closeError
3432
+ });
3433
+ }
3434
+ }
3435
+ if (closeFailed && !buildFailed) {
3436
+ throw firstCloseError;
2895
3437
  }
2896
3438
  }
2897
3439
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
2898
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
3440
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
3441
+ const totalSize = allOutput.reduce((sum, chunk) => {
2899
3442
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
2900
3443
  if (content == null) return sum;
2901
3444
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
2902
3445
  }, 0);
2903
- const fileCount = Object.values(environments).flat().length;
3446
+ const fileCount = allOutput.length;
2904
3447
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
2905
3448
  logger.info(pc4.green(`\u2713 built in ${elapsed}s`) + pc4.dim(envSuffix));
2906
3449
  logger.info(pc4.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
2907
- return { output: clientOutput, environments };
3450
+ return {
3451
+ output: clientOutput,
3452
+ environments,
3453
+ environmentResults,
3454
+ appOutput: [...buildAppContext.output]
3455
+ };
2908
3456
  }
2909
3457
  async function buildClientEnvironment(config) {
2910
3458
  const logger = config.logger;
2911
- const outDir = path9.resolve(config.root, config.build.outDir);
2912
- if (config.build.emptyOutDir && fs6.existsSync(outDir)) {
2913
- fs6.rmSync(outDir, { recursive: true, force: true });
2914
- }
2915
- fs6.mkdirSync(outDir, { recursive: true });
2916
- const html = await readHtmlFile(config.root);
2917
- const entryPoints = resolveClientEntries(config, html);
2918
- if (entryPoints.length === 0) {
2919
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
2920
- }
3459
+ const outDir = path10.resolve(config.root, config.build.outDir);
2921
3460
  const cssEngine = createCssEngine();
2922
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
2923
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
3461
+ const pluginList = resolvePluginList(config, config.plugins, {
3462
+ cssEngine,
3463
+ environmentName: "client"
3464
+ });
3465
+ const clientEnv = new NastiEnvironment("client", config, {
2924
3466
  mode: "build",
2925
- plugins: pluginList
3467
+ plugins: pluginList,
3468
+ pluginApi: getPluginApi(config)
2926
3469
  });
2927
3470
  await clientEnv.init();
2928
- const allPlugins = clientEnv.plugins;
2929
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
2930
- const rolldownPlugins = [
2931
- createOxcTransformPlugin(config, clientEnv),
2932
- ...toRolldownPlugins(allPlugins),
2933
- ...nativeReporter ? [nativeReporter] : []
2934
- ];
2935
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
2936
- const bundle2 = await rolldown(inputOptions);
2937
- const { output } = await bundle2.write(outputOptions);
2938
- await bundle2.close();
2939
- if (html) {
2940
- let processedHtml = html;
2941
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
2942
- for (const p of htmlPlugins) {
2943
- const result = await p.transformIndexHtml(processedHtml);
2944
- if (typeof result === "string") {
2945
- processedHtml = result;
2946
- } else if (result && "html" in result) {
2947
- processedHtml = processHtml(result.html, result.tags);
2948
- } else if (Array.isArray(result)) {
2949
- processedHtml = processHtml(processedHtml, result);
2950
- }
2951
- }
2952
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
2953
- for (const chunk of output) {
2954
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
2955
- const originalEntry = path9.relative(config.root, chunk.facadeModuleId);
2956
- processedHtml = processedHtml.replace(
2957
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
2958
- `$1${config.base}${chunk.fileName}$3`
3471
+ try {
3472
+ if (clientEnv.driver) {
3473
+ if (!clientEnv.driver.build) {
3474
+ throw new Error(
3475
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
2959
3476
  );
2960
3477
  }
3478
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
3479
+ assertDriverBuildResult(clientEnv, result);
3480
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
2961
3481
  }
2962
- fs6.writeFileSync(path9.resolve(outDir, "index.html"), processedHtml);
2963
- }
2964
- if (!nativeReporter && config.logLevel !== "silent") {
2965
- reportBuildOutput(output, config, logger);
3482
+ fs7.mkdirSync(outDir, { recursive: true });
3483
+ const htmlFile = config.environments.client.html ?? path10.resolve(config.root, "index.html");
3484
+ const html = await readHtmlFile(config.root, htmlFile);
3485
+ const entryPoints = resolveClientEntries(config, html);
3486
+ if (entryPoints.length === 0) {
3487
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
3488
+ }
3489
+ const allPlugins = clientEnv.plugins;
3490
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
3491
+ const rolldownPlugins = [
3492
+ createOxcTransformPlugin(config, clientEnv),
3493
+ ...toRolldownPlugins(allPlugins, clientEnv),
3494
+ ...nativeReporter ? [nativeReporter] : []
3495
+ ];
3496
+ const { inputOptions, outputOptions } = getRolldownOptions(
3497
+ clientEnv,
3498
+ entryPoints,
3499
+ rolldownPlugins
3500
+ );
3501
+ const bundle2 = await rolldown(inputOptions);
3502
+ const { output } = await bundle2.write(outputOptions);
3503
+ await bundle2.close();
3504
+ if (html) {
3505
+ let processedHtml = html;
3506
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
3507
+ for (const p of htmlPlugins) {
3508
+ const result = await p.transformIndexHtml(processedHtml);
3509
+ if (typeof result === "string") {
3510
+ processedHtml = result;
3511
+ } else if (result && "html" in result) {
3512
+ processedHtml = processHtml(result.html, result.tags);
3513
+ } else if (Array.isArray(result)) {
3514
+ processedHtml = processHtml(processedHtml, result);
3515
+ }
3516
+ }
3517
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3518
+ for (const chunk of output) {
3519
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3520
+ processedHtml = replaceEntryScript(
3521
+ processedHtml,
3522
+ chunk.facadeModuleId,
3523
+ chunk.fileName,
3524
+ config,
3525
+ htmlFile,
3526
+ config.base
3527
+ );
3528
+ }
3529
+ }
3530
+ fs7.writeFileSync(path10.resolve(outDir, "index.html"), processedHtml);
3531
+ }
3532
+ if (!nativeReporter && config.logLevel !== "silent") {
3533
+ reportBuildOutput(output, config, logger);
3534
+ }
3535
+ warnLargeChunks(output, config, logger);
3536
+ return {
3537
+ environment: clientEnv,
3538
+ result: finalizeEnvironmentResult(clientEnv, { output })
3539
+ };
3540
+ } catch (error) {
3541
+ try {
3542
+ await clientEnv.close();
3543
+ } catch (closeError) {
3544
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
3545
+ logger.error("[nasti] failed to close client environment after build failure", {
3546
+ error: normalized
3547
+ });
3548
+ }
3549
+ throw error;
2966
3550
  }
2967
- warnLargeChunks(output, config, logger);
2968
- return output;
2969
3551
  }
2970
3552
  async function buildServerEnvironment(config, name) {
2971
3553
  const envOptions = config.environments[name];
2972
3554
  const logger = config.logger;
3555
+ const pluginList = resolvePluginList(config, config.plugins, {
3556
+ consumer: envOptions.consumer,
3557
+ environmentName: name
3558
+ });
3559
+ const environment = new NastiEnvironment(name, config, {
3560
+ mode: "build",
3561
+ plugins: pluginList,
3562
+ pluginApi: getPluginApi(config)
3563
+ });
3564
+ await environment.init();
3565
+ if (environment.driver) {
3566
+ if (!environment.driver.build) {
3567
+ await environment.close();
3568
+ throw new Error(
3569
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
3570
+ );
3571
+ }
3572
+ try {
3573
+ const result = await environment.driver.build(environment.getDriverContext());
3574
+ assertDriverBuildResult(environment, result);
3575
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
3576
+ } catch (error) {
3577
+ await environment.close();
3578
+ throw error;
3579
+ }
3580
+ }
2973
3581
  for (const entry of envOptions.entry) {
2974
- if (!fs6.existsSync(entry)) {
3582
+ if (!fs7.existsSync(entry)) {
3583
+ await environment.close();
2975
3584
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
2976
3585
  }
2977
3586
  }
2978
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
2979
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
2980
- mode: "build",
2981
- plugins: pluginList
2982
- });
2983
- await environment.init();
2984
3587
  const rolldownPlugins = [
2985
3588
  createOxcTransformPlugin(config, environment),
2986
- ...toRolldownPlugins(environment.plugins)
3589
+ ...toRolldownPlugins(environment.plugins, environment)
2987
3590
  ];
2988
3591
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
2989
3592
  environment,
2990
3593
  envOptions.entry,
2991
3594
  rolldownPlugins
2992
3595
  );
2993
- if (envOptions.build.emptyOutDir && fs6.existsSync(outDir)) {
2994
- fs6.rmSync(outDir, { recursive: true, force: true });
2995
- }
2996
- fs6.mkdirSync(outDir, { recursive: true });
3596
+ fs7.mkdirSync(outDir, { recursive: true });
2997
3597
  const bundle2 = await rolldown(inputOptions);
2998
3598
  const { output } = await bundle2.write(outputOptions);
2999
3599
  await bundle2.close();
3000
3600
  logger.info(
3001
- pc4.dim(` [${name}] `) + output.map((o) => path9.join(envOptions.build.outDir, o.fileName)).join(pc4.dim(", "))
3601
+ pc4.dim(` [${name}] `) + output.map((o) => path10.join(envOptions.build.outDir, o.fileName)).join(pc4.dim(", "))
3002
3602
  );
3003
- return output;
3603
+ return {
3604
+ environment,
3605
+ result: finalizeEnvironmentResult(environment, { output })
3606
+ };
3004
3607
  }
3005
3608
  function injectCssLinks(html, cssEngine, config) {
3006
3609
  const cssLinkTags = [];
@@ -3026,6 +3629,25 @@ function injectCssLinks(html, cssEngine, config) {
3026
3629
  function escapeRegExp(string) {
3027
3630
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3028
3631
  }
3632
+ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
3633
+ const rootRelative = path10.relative(config.root, facadeModuleId).split(path10.sep).join("/");
3634
+ const resolvedHtmlFile = path10.resolve(config.root, htmlFile);
3635
+ const htmlRelative = path10.relative(path10.dirname(resolvedHtmlFile), facadeModuleId).split(path10.sep).join("/");
3636
+ const candidates = /* @__PURE__ */ new Set([
3637
+ rootRelative,
3638
+ `/${rootRelative}`,
3639
+ htmlRelative,
3640
+ `./${htmlRelative}`
3641
+ ]);
3642
+ let processed = html;
3643
+ for (const candidate of candidates) {
3644
+ processed = processed.replace(
3645
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
3646
+ `$1${urlPrefix}${fileName}$3`
3647
+ );
3648
+ }
3649
+ return processed;
3650
+ }
3029
3651
  var debug4, NODE_BUILTINS;
3030
3652
  var init_build = __esm({
3031
3653
  "src/build/index.ts"() {
@@ -3039,6 +3661,8 @@ var init_build = __esm({
3039
3661
  init_env();
3040
3662
  init_reporter();
3041
3663
  init_debug();
3664
+ init_plugin_api();
3665
+ init_build_app_context();
3042
3666
  debug4 = createDebugger("nasti:build");
3043
3667
  NODE_BUILTINS = /* @__PURE__ */ new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)]);
3044
3668
  }
@@ -3096,20 +3720,22 @@ __export(middleware_exports, {
3096
3720
  transformMiddleware: () => transformMiddleware,
3097
3721
  transformRequest: () => transformRequest
3098
3722
  });
3099
- import path11 from "path";
3100
- import fs8 from "fs";
3723
+ import path12 from "path";
3724
+ import fs9 from "fs";
3101
3725
  import { createRequire as createRequire3 } from "module";
3102
3726
  import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "url";
3103
3727
  import pc6 from "picocolors";
3104
- function getReactRefreshRuntimeEsm() {
3105
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
3728
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
3729
+ if (__refreshRuntimeCache) {
3730
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3731
+ }
3106
3732
  let cjsPath;
3107
3733
  try {
3108
3734
  const pkgPath = __require2.resolve("react-refresh/package.json");
3109
- cjsPath = path11.join(path11.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3735
+ cjsPath = path12.join(path12.dirname(pkgPath), "cjs", "react-refresh-runtime.development.js");
3110
3736
  } catch (err) {
3111
- cjsPath = path11.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3112
- if (!fs8.existsSync(cjsPath)) {
3737
+ cjsPath = path12.resolve(__dirname_esm, "../../node_modules/react-refresh/cjs/react-refresh-runtime.development.js");
3738
+ if (!fs9.existsSync(cjsPath)) {
3113
3739
  const origMsg = err instanceof Error ? err.message : String(err);
3114
3740
  throw new Error(
3115
3741
  `[nasti] Missing dependency "react-refresh". Install it with: npm install react-refresh
@@ -3117,7 +3743,7 @@ Original resolve error: ${origMsg}`
3117
3743
  );
3118
3744
  }
3119
3745
  }
3120
- const cjsSource = fs8.readFileSync(cjsPath, "utf-8");
3746
+ const cjsSource = fs9.readFileSync(cjsPath, "utf-8");
3121
3747
  __refreshRuntimeCache = `// Wrapped react-refresh runtime -> ESM
3122
3748
  const exports = {};
3123
3749
  const module = { exports };
@@ -3137,7 +3763,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
3137
3763
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
3138
3764
  export default __rt;
3139
3765
  `;
3140
- return __refreshRuntimeCache;
3766
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
3141
3767
  }
3142
3768
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
3143
3769
  const urlLit = JSON.stringify(moduleUrl);
@@ -3163,22 +3789,40 @@ window.$RefreshReg$ = prevRefreshReg;
3163
3789
  window.$RefreshSig$ = prevRefreshSig;
3164
3790
 
3165
3791
  if (__nasti_hot__) {
3166
- __nasti_hot__.accept(() => {
3167
- clearTimeout(window.__nasti_refresh_timer__);
3168
- window.__nasti_refresh_timer__ = setTimeout(() => {
3169
- RefreshRuntime.performReactRefresh();
3170
- }, 30);
3792
+ let __nasti_current_exports__;
3793
+ __nasti_hot__.accept((nextExports) => {
3794
+ if (!nextExports) return;
3795
+ if (!__nasti_current_exports__) {
3796
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
3797
+ return;
3798
+ }
3799
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
3800
+ ${urlLit},
3801
+ __nasti_current_exports__,
3802
+ nextExports,
3803
+ );
3804
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
3805
+ });
3806
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
3807
+ __nasti_current_exports__ = currentExports;
3808
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
3171
3809
  });
3172
3810
  }
3173
3811
  `;
3174
3812
  }
3175
3813
  function injectImportMetaHot(code, moduleUrl) {
3176
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
3814
+ const hotRE = /\bimport\.meta\.hot\b/g;
3815
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
3816
+ if (matches.length === 0) return code;
3817
+ for (const match of matches.reverse()) {
3818
+ const start = match.index;
3819
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
3820
+ }
3177
3821
  const urlLit = JSON.stringify(moduleUrl);
3178
3822
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
3179
3823
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
3180
3824
  `;
3181
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
3825
+ return header + code;
3182
3826
  }
3183
3827
  function transformMiddleware(ctx) {
3184
3828
  ctx.envDefine = buildEnvDefine(
@@ -3205,7 +3849,10 @@ function transformMiddleware(ctx) {
3205
3849
  return;
3206
3850
  }
3207
3851
  if (url === "/" || url.endsWith(".html")) {
3208
- const html = await readHtmlFile(ctx.config.root);
3852
+ const html = await readHtmlFile(
3853
+ ctx.config.root,
3854
+ ctx.config.environments.client?.html
3855
+ );
3209
3856
  if (html) {
3210
3857
  let processedHtml = html;
3211
3858
  for (const plugin of ctx.config.plugins) {
@@ -3251,13 +3898,14 @@ function transformMiddleware(ctx) {
3251
3898
  }
3252
3899
  async function transformRequest(url, ctx) {
3253
3900
  const { config, pluginContainer, moduleGraph } = ctx;
3901
+ url = removeTimestampQuery(url);
3254
3902
  const cleanReqUrl = url.split("?")[0];
3255
3903
  const cached2 = moduleGraph.getModuleByUrl(url);
3256
3904
  if (cached2?.transformResult) {
3257
3905
  return cached2.transformResult;
3258
3906
  }
3259
3907
  if (cleanReqUrl === "/@react-refresh") {
3260
- return { code: getReactRefreshRuntimeEsm() };
3908
+ return { code: getReactRefreshRuntimeEsm(true) };
3261
3909
  }
3262
3910
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
3263
3911
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -3265,8 +3913,8 @@ async function transformRequest(url, ctx) {
3265
3913
  let realIdValid = false;
3266
3914
  try {
3267
3915
  if (idParam) {
3268
- realId = fs8.realpathSync(idParam);
3269
- realIdValid = fs8.statSync(realId).isFile() && (realId.includes(`${path11.sep}node_modules${path11.sep}`) || isUnderRoot(realId, config.root));
3916
+ realId = fs9.realpathSync(idParam);
3917
+ realIdValid = fs9.statSync(realId).isFile() && (realId.includes(`${path12.sep}node_modules${path12.sep}`) || isUnderRoot(realId, config.root));
3270
3918
  }
3271
3919
  } catch {
3272
3920
  realId = null;
@@ -3293,6 +3941,8 @@ async function transformRequest(url, ctx) {
3293
3941
  }
3294
3942
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
3295
3943
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
3944
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3945
+ const transformVersion2 = mod2.invalidationVersion;
3296
3946
  const loaded = await pluginContainer.load(url);
3297
3947
  if (loaded != null) {
3298
3948
  let code2 = typeof loaded === "string" ? loaded : loaded.code;
@@ -3300,30 +3950,43 @@ async function transformRequest(url, ctx) {
3300
3950
  if (transformed != null) {
3301
3951
  code2 = typeof transformed === "string" ? transformed : transformed.code;
3302
3952
  }
3303
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
3304
- moduleGraph.registerModule(mod2, cleanReqUrl);
3305
- code2 = injectImportMetaHot(code2, url);
3953
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
3954
+ moduleGraph.registerModule(mod2, parentFile);
3955
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
3956
+ code2 = injectImportMetaHot(hotInfo2.code, url);
3306
3957
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
3307
3958
  loadEnv(config.mode, config.root, config.envPrefix),
3308
3959
  config.mode
3309
3960
  ));
3310
- code2 = rewriteImports(code2, config, cleanReqUrl);
3961
+ const importedUrls2 = /* @__PURE__ */ new Set();
3962
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
3963
+ const pruned2 = await moduleGraph.updateModuleInfo(
3964
+ mod2,
3965
+ importedUrls2,
3966
+ hotInfo2.acceptedUrls,
3967
+ hotInfo2.isSelfAccepting,
3968
+ transformVersion2
3969
+ );
3311
3970
  const transformResult2 = { code: code2 };
3312
- mod2.transformResult = transformResult2;
3971
+ if (pruned2) {
3972
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
3973
+ mod2.transformResult = transformResult2;
3974
+ }
3313
3975
  return transformResult2;
3314
3976
  }
3315
3977
  }
3316
3978
  const filePath = resolveUrlToFile(url, config.root);
3317
- if (!filePath || !fs8.existsSync(filePath)) return null;
3979
+ if (!filePath || !fs9.existsSync(filePath)) return null;
3318
3980
  const mod = await moduleGraph.ensureEntryFromUrl(url);
3319
3981
  moduleGraph.registerModule(mod, filePath);
3982
+ const transformVersion = mod.invalidationVersion;
3320
3983
  if (cleanReqUrl.startsWith("/@modules/")) {
3321
3984
  const code2 = await bundlePackageAsEsm(filePath, config.root);
3322
3985
  const transformResult2 = { code: code2 };
3323
3986
  mod.transformResult = transformResult2;
3324
3987
  return transformResult2;
3325
3988
  }
3326
- let code = fs8.readFileSync(filePath, "utf-8");
3989
+ let code = fs9.readFileSync(filePath, "utf-8");
3327
3990
  const pluginResult = await pluginContainer.transform(code, filePath);
3328
3991
  if (pluginResult) {
3329
3992
  code = typeof pluginResult === "string" ? pluginResult : pluginResult.code;
@@ -3343,9 +4006,10 @@ async function transformRequest(url, ctx) {
3343
4006
  if (useRefresh) {
3344
4007
  code = buildReactRefreshWrapper(stableUrl, code);
3345
4008
  wrappedWithRefresh = true;
3346
- mod.isSelfAccepting = true;
3347
4009
  }
3348
4010
  }
4011
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
4012
+ code = hotInfo.code;
3349
4013
  if (!wrappedWithRefresh) {
3350
4014
  code = injectImportMetaHot(code, stableUrl);
3351
4015
  }
@@ -3354,9 +4018,20 @@ async function transformRequest(url, ctx) {
3354
4018
  config.mode
3355
4019
  );
3356
4020
  code = replaceEnvInCode(code, envDefine);
3357
- code = rewriteImports(code, config, filePath);
4021
+ const importedUrls = /* @__PURE__ */ new Set();
4022
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
4023
+ const pruned = await moduleGraph.updateModuleInfo(
4024
+ mod,
4025
+ importedUrls,
4026
+ hotInfo.acceptedUrls,
4027
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
4028
+ transformVersion
4029
+ );
3358
4030
  const transformResult = { code };
3359
- mod.transformResult = transformResult;
4031
+ if (pruned) {
4032
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
4033
+ mod.transformResult = transformResult;
4034
+ }
3360
4035
  return transformResult;
3361
4036
  }
3362
4037
  async function loadVirtualModule(spec, ctx) {
@@ -3364,7 +4039,7 @@ async function loadVirtualModule(spec, ctx) {
3364
4039
  const resolved = await pluginContainer.resolveId(spec);
3365
4040
  if (resolved == null) return null;
3366
4041
  const resolvedId = typeof resolved === "string" ? resolved : resolved.id;
3367
- const looksVirtual = resolvedId.startsWith("\0") || !fs8.existsSync(resolvedId);
4042
+ const looksVirtual = resolvedId.startsWith("\0") || !fs9.existsSync(resolvedId);
3368
4043
  if (!looksVirtual) return null;
3369
4044
  const loadResult = await pluginContainer.load(resolvedId);
3370
4045
  if (loadResult == null) return null;
@@ -3377,7 +4052,7 @@ async function loadVirtualModule(spec, ctx) {
3377
4052
  loadEnv(config.mode, config.root, config.envPrefix),
3378
4053
  config.mode
3379
4054
  ));
3380
- const anchor = path11.join(config.root, "__nasti_virtual__.ts");
4055
+ const anchor = path12.join(config.root, "__nasti_virtual__.ts");
3381
4056
  code = rewriteImports(code, config, anchor);
3382
4057
  return { id: resolvedId, result: { code } };
3383
4058
  }
@@ -3403,7 +4078,7 @@ async function doBundlePackage(entryFile, root) {
3403
4078
  await bundle2.close();
3404
4079
  let code = result.output[0].code;
3405
4080
  code = code.replace(/process\.env\.NODE_ENV/g, '"development"');
3406
- const externalBaseDir = path11.dirname(entryFile);
4081
+ const externalBaseDir = path12.dirname(entryFile);
3407
4082
  code = code.replace(
3408
4083
  /^(import\b[^;'"]*?\bfrom\s+)(['"])([^'"./][^'"]*)(\2)/gm,
3409
4084
  (_, prefix, q, spec) => `${prefix}${q}${externalSpecToModuleUrl(spec, externalBaseDir, root)}${q}`
@@ -3421,16 +4096,16 @@ async function doBundlePackage(entryFile, root) {
3421
4096
  return code;
3422
4097
  }
3423
4098
  async function tryGenerateSubpathShim(entryFile, root) {
3424
- const NM = `${path11.sep}node_modules${path11.sep}`;
4099
+ const NM = `${path12.sep}node_modules${path12.sep}`;
3425
4100
  if (!entryFile.includes(NM)) return null;
3426
4101
  let pkgDir = null;
3427
4102
  let pkgName = null;
3428
- let dir = path11.dirname(entryFile);
4103
+ let dir = path12.dirname(entryFile);
3429
4104
  while (true) {
3430
- const pkgJsonPath = path11.join(dir, "package.json");
3431
- if (fs8.existsSync(pkgJsonPath)) {
4105
+ const pkgJsonPath = path12.join(dir, "package.json");
4106
+ if (fs9.existsSync(pkgJsonPath)) {
3432
4107
  try {
3433
- const pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4108
+ const pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3434
4109
  if (typeof pkg?.name === "string" && pkg.name) {
3435
4110
  pkgDir = dir;
3436
4111
  pkgName = pkg.name;
@@ -3439,16 +4114,16 @@ async function tryGenerateSubpathShim(entryFile, root) {
3439
4114
  } catch {
3440
4115
  }
3441
4116
  }
3442
- const parent = path11.dirname(dir);
4117
+ const parent = path12.dirname(dir);
3443
4118
  if (parent === dir) return null;
3444
4119
  dir = parent;
3445
4120
  if (!dir.includes(NM)) return null;
3446
4121
  }
3447
4122
  if (!pkgDir || !pkgName) return null;
3448
- const entryExt = path11.extname(entryFile);
4123
+ const entryExt = path12.extname(entryFile);
3449
4124
  const mainEntry = pickMainEntryByExtension(pkgDir, entryExt);
3450
4125
  if (!mainEntry) return null;
3451
- if (path11.resolve(mainEntry) === path11.resolve(entryFile)) return null;
4126
+ if (path12.resolve(mainEntry) === path12.resolve(entryFile)) return null;
3452
4127
  let mainNs;
3453
4128
  let subNs;
3454
4129
  try {
@@ -3472,7 +4147,7 @@ async function tryGenerateSubpathShim(entryFile, root) {
3472
4147
  if (mainNs["default"] !== subNs["default"]) return null;
3473
4148
  }
3474
4149
  const rootMain = resolveNodeModule(root, pkgName);
3475
- const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path11.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
4150
+ const mainEntryUrl = rootMain && rootMain.startsWith(pkgDir + path12.sep) ? `/@modules/${pkgName}` : `/@modules/${pkgName}?id=${encodeURIComponent(mainEntry)}`;
3476
4151
  const lines = [
3477
4152
  `// Nasti subpath shim \u2192 ${pkgName} (avoid duplicate bundling)`,
3478
4153
  `import * as __pkg from "${mainEntryUrl}";`
@@ -3486,10 +4161,10 @@ async function tryGenerateSubpathShim(entryFile, root) {
3486
4161
  return lines.join("\n") + "\n";
3487
4162
  }
3488
4163
  function pickMainEntryByExtension(pkgDir, preferredExt) {
3489
- const pkgJsonPath = path11.join(pkgDir, "package.json");
4164
+ const pkgJsonPath = path12.join(pkgDir, "package.json");
3490
4165
  let pkg;
3491
4166
  try {
3492
- pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4167
+ pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3493
4168
  } catch {
3494
4169
  return null;
3495
4170
  }
@@ -3508,14 +4183,14 @@ function pickMainEntryByExtension(pkgDir, preferredExt) {
3508
4183
  if (typeof pkg.module === "string") candidates.push(pkg.module);
3509
4184
  if (typeof pkg.main === "string") candidates.push(pkg.main);
3510
4185
  for (const cand of candidates) {
3511
- if (path11.extname(cand) === preferredExt) {
3512
- const full = path11.resolve(pkgDir, cand);
3513
- if (fs8.existsSync(full)) return full;
4186
+ if (path12.extname(cand) === preferredExt) {
4187
+ const full = path12.resolve(pkgDir, cand);
4188
+ if (fs9.existsSync(full)) return full;
3514
4189
  }
3515
4190
  }
3516
4191
  for (const cand of candidates) {
3517
- const full = path11.resolve(pkgDir, cand);
3518
- if (fs8.existsSync(full)) return full;
4192
+ const full = path12.resolve(pkgDir, cand);
4193
+ if (fs9.existsSync(full)) return full;
3519
4194
  }
3520
4195
  return null;
3521
4196
  }
@@ -3561,72 +4236,231 @@ async function injectCjsNamedExports(code, entryFile) {
3561
4236
  return code;
3562
4237
  }
3563
4238
  }
3564
- function rewriteImports(code, config, filePath) {
4239
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
4240
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4241
+ const transformSpec = (spec) => {
4242
+ const resolved = removeTimestampQuery(resolveSpec(spec));
4243
+ importedUrls?.add(resolved);
4244
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
4245
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
4246
+ };
4247
+ return code.replace(
4248
+ /\bfrom\s+(['"])([^'"]+)\1/g,
4249
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
4250
+ ).replace(
4251
+ /\bimport\s+(['"])([^'"]+)\1/g,
4252
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
4253
+ ).replace(
4254
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
4255
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
4256
+ );
4257
+ }
4258
+ function createModuleSpecifierResolver(config, filePath) {
3565
4259
  const root = config.root;
3566
- const fileDir = path11.dirname(filePath);
4260
+ const fileDir = path12.dirname(filePath);
3567
4261
  const aliasEntries = Object.entries(config.resolve.alias).sort(
3568
4262
  ([a], [b]) => b.length - a.length
3569
4263
  );
3570
- const toRootUrl = (abs) => "/" + path11.relative(root, abs).replace(/\\/g, "/");
3571
- const transformSpec = (spec) => {
3572
- const suffixMatch = spec.match(/[?#].*$/);
4264
+ const toRootUrl = (abs) => "/" + path12.relative(root, abs).replace(/\\/g, "/");
4265
+ return (specifier) => {
4266
+ const suffixMatch = specifier.match(/[?#].*$/);
3573
4267
  const suffix = suffixMatch ? suffixMatch[0] : "";
3574
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
4268
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
3575
4269
  for (const [key, value] of aliasEntries) {
3576
4270
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
3577
4271
  const aliasBase = resolveAliasTarget2(value, root);
3578
4272
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
3579
- const target = sub ? path11.join(aliasBase, sub) : aliasBase;
4273
+ const target = sub ? path12.join(aliasBase, sub) : aliasBase;
3580
4274
  const resolved = tryResolveDiskPath(target);
3581
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4275
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3582
4276
  }
3583
4277
  }
3584
4278
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
3585
- const target = path11.resolve(fileDir, baseSpec);
3586
- const resolved = tryResolveDiskPath(target);
3587
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4279
+ const resolved = tryResolveDiskPath(path12.resolve(fileDir, baseSpec));
4280
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3588
4281
  }
3589
4282
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
3590
- const target = path11.join(root, baseSpec.replace(/^\//, ""));
3591
- const resolved = tryResolveDiskPath(target);
3592
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
4283
+ const resolved = tryResolveDiskPath(path12.join(root, baseSpec.replace(/^\//, "")));
4284
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
3593
4285
  }
3594
- if (baseSpec.startsWith("/")) return spec;
3595
- return `/@modules/${spec}`;
4286
+ if (baseSpec.startsWith("/")) return specifier;
4287
+ return `/@modules/${specifier}`;
3596
4288
  };
3597
- return code.replace(
3598
- /\bfrom\s+(['"])([^'"]+)\1/g,
3599
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
3600
- ).replace(
3601
- /\bimport\s+(['"])([^'"]+)\1/g,
3602
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
3603
- ).replace(
3604
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
3605
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
3606
- );
4289
+ }
4290
+ function rewriteHotAcceptDeps(code, config, filePath) {
4291
+ const acceptedUrls = /* @__PURE__ */ new Set();
4292
+ const edits = [];
4293
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
4294
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
4295
+ const searchableCode = maskStringsAndComments(code);
4296
+ let isSelfAccepting = false;
4297
+ let match;
4298
+ while (match = acceptRE.exec(searchableCode)) {
4299
+ let cursor = match.index + match[0].length;
4300
+ const skipTrivia = () => {
4301
+ while (cursor < code.length) {
4302
+ if (/\s/.test(code[cursor])) {
4303
+ cursor++;
4304
+ continue;
4305
+ }
4306
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
4307
+ cursor += 2;
4308
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
4309
+ continue;
4310
+ }
4311
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
4312
+ cursor += 2;
4313
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
4314
+ cursor += 2;
4315
+ continue;
4316
+ }
4317
+ break;
4318
+ }
4319
+ };
4320
+ skipTrivia();
4321
+ const first = code[cursor];
4322
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
4323
+ isSelfAccepting = true;
4324
+ continue;
4325
+ }
4326
+ const readLiteral = () => {
4327
+ const quote = code[cursor];
4328
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
4329
+ const start = cursor;
4330
+ cursor++;
4331
+ let raw = "";
4332
+ while (cursor < code.length) {
4333
+ const char = code[cursor];
4334
+ if (char === "\\") {
4335
+ raw += code[cursor + 1] ?? "";
4336
+ cursor += 2;
4337
+ continue;
4338
+ }
4339
+ if (char === quote) {
4340
+ cursor++;
4341
+ const resolved = removeTimestampQuery(resolveSpec(raw));
4342
+ acceptedUrls.add(resolved);
4343
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
4344
+ return;
4345
+ }
4346
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
4347
+ raw += char;
4348
+ cursor++;
4349
+ }
4350
+ };
4351
+ if (first === "[") {
4352
+ cursor++;
4353
+ while (cursor < code.length) {
4354
+ skipTrivia();
4355
+ if (code[cursor] === ",") {
4356
+ cursor++;
4357
+ skipTrivia();
4358
+ }
4359
+ if (code[cursor] === "]") break;
4360
+ const before = cursor;
4361
+ readLiteral();
4362
+ if (cursor === before) break;
4363
+ }
4364
+ } else {
4365
+ readLiteral();
4366
+ }
4367
+ }
4368
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
4369
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
4370
+ }
4371
+ return { code, acceptedUrls, isSelfAccepting };
4372
+ }
4373
+ function maskStringsAndComments(code) {
4374
+ const masked = code.split("");
4375
+ let state = "code";
4376
+ const isRegexStart = (index2) => {
4377
+ let previous = index2 - 1;
4378
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
4379
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
4380
+ };
4381
+ for (let i = 0; i < code.length; i++) {
4382
+ const char = code[i];
4383
+ const next = code[i + 1];
4384
+ if (state === "code") {
4385
+ if (char === "'") state = "single";
4386
+ else if (char === '"') state = "double";
4387
+ else if (char === "`") state = "template";
4388
+ else if (char === "/" && next === "/") state = "line-comment";
4389
+ else if (char === "/" && next === "*") state = "block-comment";
4390
+ else if (char === "/" && isRegexStart(i)) state = "regex";
4391
+ else continue;
4392
+ masked[i] = " ";
4393
+ continue;
4394
+ }
4395
+ if (state === "line-comment") {
4396
+ if (char === "\n") {
4397
+ state = "code";
4398
+ } else {
4399
+ masked[i] = " ";
4400
+ }
4401
+ continue;
4402
+ }
4403
+ if (state === "block-comment") {
4404
+ masked[i] = char === "\n" ? "\n" : " ";
4405
+ if (char === "*" && next === "/") {
4406
+ masked[i + 1] = " ";
4407
+ i++;
4408
+ state = "code";
4409
+ }
4410
+ continue;
4411
+ }
4412
+ if (state === "regex" || state === "regex-class") {
4413
+ masked[i] = char === "\n" ? "\n" : " ";
4414
+ if (char === "\\") {
4415
+ if (i + 1 < code.length) masked[++i] = " ";
4416
+ } else if (state === "regex" && char === "[") {
4417
+ state = "regex-class";
4418
+ } else if (state === "regex-class" && char === "]") {
4419
+ state = "regex";
4420
+ } else if (state === "regex" && char === "/") {
4421
+ state = "code";
4422
+ }
4423
+ continue;
4424
+ }
4425
+ masked[i] = char === "\n" ? "\n" : " ";
4426
+ if (char === "\\") {
4427
+ if (i + 1 < code.length) masked[++i] = " ";
4428
+ continue;
4429
+ }
4430
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
4431
+ state = "code";
4432
+ }
4433
+ }
4434
+ return masked.join("");
3607
4435
  }
3608
4436
  function resolveAliasTarget2(value, root) {
3609
- if (path11.isAbsolute(value) && fs8.existsSync(value)) return value;
3610
- if (value.startsWith("/")) return path11.join(root, value.slice(1));
3611
- return path11.resolve(root, value);
4437
+ if (path12.isAbsolute(value) && fs9.existsSync(value)) return value;
4438
+ if (value.startsWith("/")) return path12.join(root, value.slice(1));
4439
+ return path12.resolve(root, value);
3612
4440
  }
3613
4441
  function tryResolveDiskPath(target) {
3614
- if (fs8.existsSync(target) && fs8.statSync(target).isFile()) return target;
4442
+ if (fs9.existsSync(target) && fs9.statSync(target).isFile()) return target;
3615
4443
  for (const ext of RESOLVE_EXTENSIONS) {
3616
4444
  const withExt = target + ext;
3617
- if (fs8.existsSync(withExt) && fs8.statSync(withExt).isFile()) return withExt;
4445
+ if (fs9.existsSync(withExt) && fs9.statSync(withExt).isFile()) return withExt;
3618
4446
  }
3619
- if (fs8.existsSync(target) && fs8.statSync(target).isDirectory()) {
4447
+ if (fs9.existsSync(target) && fs9.statSync(target).isDirectory()) {
3620
4448
  for (const ext of RESOLVE_EXTENSIONS) {
3621
- const idx = path11.join(target, "index" + ext);
3622
- if (fs8.existsSync(idx) && fs8.statSync(idx).isFile()) return idx;
4449
+ const idx = path12.join(target, "index" + ext);
4450
+ if (fs9.existsSync(idx) && fs9.statSync(idx).isFile()) return idx;
3623
4451
  }
3624
4452
  }
3625
4453
  return null;
3626
4454
  }
3627
4455
  function isUnderRoot(abs, root) {
3628
- const rel = path11.relative(root, abs);
3629
- return !!rel && !rel.startsWith("..") && !path11.isAbsolute(rel);
4456
+ const rel = path12.relative(root, abs);
4457
+ return !!rel && !rel.startsWith("..") && !path12.isAbsolute(rel);
4458
+ }
4459
+ function appendTimestampQuery(url, timestamp) {
4460
+ const hashIndex = url.indexOf("#");
4461
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
4462
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4463
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
3630
4464
  }
3631
4465
  function externalSpecToModuleUrl(spec, baseDir, root) {
3632
4466
  const resolved = resolveNodeModule(baseDir, spec);
@@ -3639,7 +4473,7 @@ function resolveNodeModule(baseDir, moduleName) {
3639
4473
  const resolved = resolveNodeModuleEntry(baseDir, moduleName);
3640
4474
  if (!resolved) return null;
3641
4475
  try {
3642
- return fs8.realpathSync(resolved);
4476
+ return fs9.realpathSync(resolved);
3643
4477
  } catch {
3644
4478
  return resolved;
3645
4479
  }
@@ -3659,21 +4493,21 @@ function resolveNodeModuleEntry(root, moduleName) {
3659
4493
  let pkgDir = null;
3660
4494
  let dir = root;
3661
4495
  for (; ; ) {
3662
- const candidate = path11.join(dir, "node_modules", pkgName);
3663
- if (fs8.existsSync(candidate)) {
4496
+ const candidate = path12.join(dir, "node_modules", pkgName);
4497
+ if (fs9.existsSync(candidate)) {
3664
4498
  pkgDir = candidate;
3665
4499
  break;
3666
4500
  }
3667
- const parent = path11.dirname(dir);
4501
+ const parent = path12.dirname(dir);
3668
4502
  if (parent === dir) break;
3669
4503
  dir = parent;
3670
4504
  }
3671
4505
  if (!pkgDir) return null;
3672
- const pkgJsonPath = path11.join(pkgDir, "package.json");
3673
- if (!fs8.existsSync(pkgJsonPath)) return null;
4506
+ const pkgJsonPath = path12.join(pkgDir, "package.json");
4507
+ if (!fs9.existsSync(pkgJsonPath)) return null;
3674
4508
  let pkg;
3675
4509
  try {
3676
- pkg = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
4510
+ pkg = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3677
4511
  } catch {
3678
4512
  return null;
3679
4513
  }
@@ -3686,32 +4520,32 @@ function resolveNodeModuleEntry(root, moduleName) {
3686
4520
  const subDirs = [""];
3687
4521
  for (const field of ["module", "main"]) {
3688
4522
  if (typeof pkg[field] === "string") {
3689
- const dir2 = path11.dirname(pkg[field]);
4523
+ const dir2 = path12.dirname(pkg[field]);
3690
4524
  if (dir2 && dir2 !== "." && !subDirs.includes(dir2)) subDirs.push(dir2);
3691
4525
  }
3692
4526
  }
3693
4527
  for (const dir2 of subDirs) {
3694
- const direct = path11.join(pkgDir, dir2, subpath);
3695
- if (fs8.existsSync(direct) && fs8.statSync(direct).isFile()) return direct;
4528
+ const direct = path12.join(pkgDir, dir2, subpath);
4529
+ if (fs9.existsSync(direct) && fs9.statSync(direct).isFile()) return direct;
3696
4530
  for (const ext of RESOLVE_EXTENSIONS) {
3697
- if (fs8.existsSync(direct + ext)) return direct + ext;
4531
+ if (fs9.existsSync(direct + ext)) return direct + ext;
3698
4532
  }
3699
4533
  }
3700
4534
  return null;
3701
4535
  }
3702
4536
  for (const field of ["module", "jsnext:main", "jsnext", "main"]) {
3703
4537
  if (typeof pkg[field] === "string") {
3704
- const entry = path11.join(pkgDir, pkg[field]);
3705
- if (fs8.existsSync(entry)) return entry;
4538
+ const entry = path12.join(pkgDir, pkg[field]);
4539
+ if (fs9.existsSync(entry)) return entry;
3706
4540
  }
3707
4541
  }
3708
- const indexFallback = path11.join(pkgDir, "index.js");
3709
- if (fs8.existsSync(indexFallback)) return indexFallback;
4542
+ const indexFallback = path12.join(pkgDir, "index.js");
4543
+ if (fs9.existsSync(indexFallback)) return indexFallback;
3710
4544
  return null;
3711
4545
  }
3712
4546
  function resolvePackageExports(exports, key, pkgDir) {
3713
4547
  if (typeof exports === "string") {
3714
- return key === "." ? path11.join(pkgDir, exports) : null;
4548
+ return key === "." ? path12.join(pkgDir, exports) : null;
3715
4549
  }
3716
4550
  const entry = exports[key];
3717
4551
  if (entry === void 0) {
@@ -3723,7 +4557,7 @@ function resolvePackageExports(exports, key, pkgDir) {
3723
4557
  return resolveExportValue(entry, pkgDir);
3724
4558
  }
3725
4559
  function resolveExportValue(value, pkgDir) {
3726
- if (typeof value === "string") return path11.join(pkgDir, value);
4560
+ if (typeof value === "string") return path12.join(pkgDir, value);
3727
4561
  if (Array.isArray(value)) {
3728
4562
  for (const item of value) {
3729
4563
  const r = resolveExportValue(item, pkgDir);
@@ -3747,17 +4581,17 @@ function resolveUrlToFile(url, root) {
3747
4581
  const moduleName = cleanUrl.slice("/@modules/".length);
3748
4582
  return resolveNodeModule(root, moduleName);
3749
4583
  }
3750
- const filePath = path11.resolve(root, cleanUrl.replace(/^\//, ""));
3751
- if (fs8.existsSync(filePath) && fs8.statSync(filePath).isFile()) {
4584
+ const filePath = path12.resolve(root, cleanUrl.replace(/^\//, ""));
4585
+ if (fs9.existsSync(filePath) && fs9.statSync(filePath).isFile()) {
3752
4586
  return filePath;
3753
4587
  }
3754
4588
  for (const ext of RESOLVE_EXTENSIONS) {
3755
4589
  const withExt = filePath + ext;
3756
- if (fs8.existsSync(withExt)) return withExt;
4590
+ if (fs9.existsSync(withExt)) return withExt;
3757
4591
  }
3758
4592
  for (const ext of RESOLVE_EXTENSIONS) {
3759
- const indexFile = path11.join(filePath, "index" + ext);
3760
- if (fs8.existsSync(indexFile)) return indexFile;
4593
+ const indexFile = path12.join(filePath, "index" + ext);
4594
+ if (fs9.existsSync(indexFile)) return indexFile;
3761
4595
  }
3762
4596
  return null;
3763
4597
  }
@@ -3765,36 +4599,35 @@ function isModuleRequest(url) {
3765
4599
  const cleanUrl = url.split("?")[0];
3766
4600
  if (/\.(ts|tsx|jsx|js|mjs|vue|css|json)$/.test(cleanUrl)) return true;
3767
4601
  if (cleanUrl.startsWith("/@modules/")) return true;
3768
- if (!path11.extname(cleanUrl)) return true;
4602
+ if (!path12.extname(cleanUrl)) return true;
3769
4603
  return false;
3770
4604
  }
3771
4605
  function getHmrClientCode() {
3772
4606
  return `
3773
4607
  // Nasti HMR Client
3774
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
4608
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
4609
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
3775
4610
  const hotModulesMap = new Map();
3776
4611
  const disposeMap = new Map();
3777
4612
  const pruneMap = new Map();
4613
+ const dataMap = new Map();
4614
+ let updateQueue = [];
4615
+ let pendingUpdateQueue = false;
3778
4616
 
3779
4617
  socket.addEventListener('message', async ({ data }) => {
3780
4618
  const payload = JSON.parse(data);
3781
4619
  switch (payload.type) {
3782
4620
  case 'connected':
3783
- console.log('[nasti] connected.');
4621
+ console.debug('[nasti] connected.');
3784
4622
  clearErrorOverlay();
3785
4623
  break;
3786
4624
  case 'update':
3787
4625
  try {
3788
- await Promise.all(payload.updates.map((update) => {
3789
- if (update.type === 'js-update') {
3790
- return fetchUpdate(update);
3791
- } else if (update.type === 'css-update') {
3792
- return updateCss(update.path);
3793
- }
3794
- }));
4626
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
4627
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
4628
+ await Promise.all(payload.updates.map(queueUpdate));
3795
4629
  clearErrorOverlay();
3796
- console.log('[nasti] HMR update complete, reloading page');
3797
- location.reload();
4630
+ console.debug('[nasti] HMR update complete.');
3798
4631
  } catch (err) {
3799
4632
  console.error('[nasti] HMR update failed:', err);
3800
4633
  showErrorOverlay(err);
@@ -3805,10 +4638,17 @@ socket.addEventListener('message', async ({ data }) => {
3805
4638
  location.reload();
3806
4639
  break;
3807
4640
  case 'prune':
3808
- payload.paths.forEach((p) => {
3809
- const cb = pruneMap.get(p);
3810
- if (cb) cb();
3811
- });
4641
+ await Promise.all(payload.paths.map(async (path) => {
4642
+ const data = dataMap.get(path);
4643
+ const dispose = disposeMap.get(path);
4644
+ const prune = pruneMap.get(path);
4645
+ if (dispose) await dispose(data);
4646
+ if (prune) await prune(data);
4647
+ hotModulesMap.delete(path);
4648
+ disposeMap.delete(path);
4649
+ pruneMap.delete(path);
4650
+ dataMap.delete(path);
4651
+ }));
3812
4652
  break;
3813
4653
  case 'error':
3814
4654
  console.error('[nasti] error:', payload.err.message);
@@ -3817,33 +4657,64 @@ socket.addEventListener('message', async ({ data }) => {
3817
4657
  }
3818
4658
  });
3819
4659
 
3820
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
4660
+ // \u670D\u52A1\u91CD\u542F\u540E\u65E7\u6A21\u5757\u56FE\u5DF2\u5931\u6548\uFF0C\u91CD\u8FDE\u65F6\u6574\u9875\u5237\u65B0\u662F\u5FC5\u8981\u515C\u5E95\uFF1B\u6B63\u5E38 update \u4E0D\u518D\u5237\u65B0\u3002
3821
4661
  let reconnectTimer = 0;
3822
4662
  socket.addEventListener('close', () => {
3823
4663
  clearTimeout(reconnectTimer);
3824
4664
  reconnectTimer = setTimeout(() => location.reload(), 1000);
3825
4665
  });
3826
4666
 
4667
+ /**
4668
+ * \u540C\u4E00\u6279\u66F4\u65B0\u5148\u5168\u90E8\u62C9\u53D6\uFF0C\u518D\u6309\u670D\u52A1\u7AEF\u6D88\u606F\u987A\u5E8F\u6267\u884C accept \u56DE\u8C03\uFF0C\u907F\u514D HTTP \u5F80\u8FD4\u901F\u5EA6
4669
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
4670
+ */
4671
+ async function queueUpdate(update) {
4672
+ updateQueue.push(fetchUpdate(update));
4673
+ if (pendingUpdateQueue) return;
4674
+
4675
+ pendingUpdateQueue = true;
4676
+ await Promise.resolve();
4677
+ pendingUpdateQueue = false;
4678
+ const loading = updateQueue;
4679
+ updateQueue = [];
4680
+ const applyUpdates = await Promise.all(loading);
4681
+ for (const apply of applyUpdates) {
4682
+ if (apply) apply();
4683
+ }
4684
+ }
4685
+
3827
4686
  async function fetchUpdate(update) {
3828
4687
  const mod = hotModulesMap.get(update.path);
3829
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
3830
- const dispose = disposeMap.get(update.path);
3831
- if (dispose) dispose();
4688
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
4689
+ if (!mod) return;
3832
4690
 
3833
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
3834
- if (mod) {
3835
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
3836
- [...mod.callbacks].forEach((cb) => cb(newMod));
3837
- }
4691
+ // \u5FC5\u987B\u5728\u91CD\u65B0 import \u524D\u786E\u5B9A\u65E7\u56DE\u8C03\uFF1B\u65B0\u6A21\u5757\u6267\u884C createHotContext \u65F6\u4F1A\u6E05\u7A7A\u5E76\u6CE8\u518C\u65B0\u56DE\u8C03\u3002
4692
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
4693
+ deps.includes(update.acceptedPath)
4694
+ );
4695
+ const isSelfUpdate = update.path === update.acceptedPath;
4696
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
4697
+
4698
+ const dispose = disposeMap.get(update.acceptedPath);
4699
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
4700
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
4701
+
4702
+ return () => {
4703
+ for (const { deps, fn } of qualifiedCallbacks) {
4704
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
4705
+ }
4706
+ const detail = isSelfUpdate
4707
+ ? update.path
4708
+ : update.acceptedPath + ' via ' + update.path;
4709
+ console.debug('[nasti] hot updated:', detail);
4710
+ };
3838
4711
  }
3839
4712
 
3840
- function updateCss(path) {
3841
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
3842
- if (el) {
3843
- return fetch(path + '?t=' + Date.now())
3844
- .then(r => r.text())
3845
- .then(css => { el.textContent = css; });
3846
- }
4713
+ function appendTimestampQuery(url, timestamp) {
4714
+ const hashIndex = url.indexOf('#');
4715
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
4716
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
4717
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
3847
4718
  }
3848
4719
 
3849
4720
  function clearErrorOverlay() {
@@ -3871,23 +4742,30 @@ function showErrorOverlay(err) {
3871
4742
  document.body.appendChild(overlay);
3872
4743
  }
3873
4744
 
3874
- /**
3875
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
3876
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
3877
- * \u6BCF\u6B21\u6A21\u5757\u91CD\u65B0 import \u90FD\u4F1A\u8C03\u7528 createHotContext\uFF0C\u65E7\u56DE\u8C03\u4F1A\u88AB fetchUpdate \u8C03\u7528\u540E\u7ACB\u5373\u88AB\u65B0 import
3878
- * \u91CC\u7684 accept \u66FF\u6362\u3002\u4E0D\u66FF\u6362\u7684\u8BDD\u6BCF\u7F16\u8F91\u4E00\u6B21\u5C31\u591A\u4E00\u4E2A\u56DE\u8C03\uFF0C\u8D8A\u8DD1\u8D8A\u6162\u3002
3879
- */
3880
4745
  export function createHotContext(ownerPath) {
4746
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
4747
+
4748
+ // \u6A21\u5757\u91CD\u65B0\u6267\u884C\u65F6\u4E22\u5F03\u65E7 accept \u56DE\u8C03\uFF0C\u4F46\u4FDD\u7559\u540C\u4E00\u4E2A hot.data \u5BF9\u8C61\u3002
4749
+ const existing = hotModulesMap.get(ownerPath);
4750
+ if (existing) existing.callbacks = [];
4751
+
4752
+ const acceptDeps = (deps, callback = () => {}) => {
4753
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
4754
+ mod.callbacks.push({ deps, fn: callback });
4755
+ hotModulesMap.set(ownerPath, mod);
4756
+ };
4757
+
3881
4758
  return {
3882
4759
  accept(deps, callback) {
3883
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
3884
4760
  if (typeof deps === 'function' || deps === undefined) {
3885
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
3886
- return;
4761
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
4762
+ } else if (typeof deps === 'string') {
4763
+ acceptDeps([deps], ([mod]) => callback?.(mod));
4764
+ } else if (Array.isArray(deps)) {
4765
+ acceptDeps(deps, callback);
4766
+ } else {
4767
+ throw new Error('invalid hot.accept() usage');
3887
4768
  }
3888
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
3889
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
3890
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
3891
4769
  },
3892
4770
  prune(callback) {
3893
4771
  pruneMap.set(ownerPath, callback);
@@ -3898,21 +4776,85 @@ export function createHotContext(ownerPath) {
3898
4776
  invalidate() {
3899
4777
  location.reload();
3900
4778
  },
3901
- data: {},
4779
+ data: dataMap.get(ownerPath),
3902
4780
  };
3903
4781
  }
3904
4782
  `;
3905
4783
  }
3906
- var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
4784
+ var __dirname_esm, __require2, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
3907
4785
  var init_middleware = __esm({
3908
4786
  "src/server/middleware.ts"() {
3909
4787
  "use strict";
3910
4788
  init_transformer();
3911
4789
  init_html();
3912
4790
  init_env();
3913
- __dirname_esm = path11.dirname(fileURLToPath(import.meta.url));
4791
+ init_url();
4792
+ __dirname_esm = path12.dirname(fileURLToPath(import.meta.url));
3914
4793
  __require2 = createRequire3(import.meta.url);
3915
4794
  __refreshRuntimeCache = null;
4795
+ REACT_REFRESH_BOUNDARY_HELPERS = `
4796
+ function __nastiIsPlainObject(obj) {
4797
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
4798
+ (obj.constructor === Object || obj.constructor === undefined);
4799
+ }
4800
+ function __nastiIsCompoundComponent(type) {
4801
+ if (!__nastiIsPlainObject(type)) return false;
4802
+ for (const key in type) {
4803
+ if (!isLikelyComponentType(type[key])) return false;
4804
+ }
4805
+ return true;
4806
+ }
4807
+ export function registerExportsForReactRefresh(filename, moduleExports) {
4808
+ for (const key in moduleExports) {
4809
+ if (key === '__esModule') continue;
4810
+ const value = moduleExports[key];
4811
+ if (isLikelyComponentType(value)) {
4812
+ register(value, filename + ' export ' + key);
4813
+ } else if (__nastiIsCompoundComponent(value)) {
4814
+ for (const subKey in value) {
4815
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
4816
+ }
4817
+ }
4818
+ }
4819
+ }
4820
+ let __nastiRefreshTimer;
4821
+ function __nastiEnqueueRefresh() {
4822
+ clearTimeout(__nastiRefreshTimer);
4823
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
4824
+ }
4825
+ function __nastiCheckExports(ignored, exports, predicate) {
4826
+ for (const key in exports) {
4827
+ if (ignored.includes(key)) continue;
4828
+ if (!predicate(key, exports[key])) return key;
4829
+ }
4830
+ return true;
4831
+ }
4832
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
4833
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
4834
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
4835
+ return 'Could not Fast Refresh (export removed)';
4836
+ }
4837
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
4838
+ return 'Could not Fast Refresh (new export)';
4839
+ }
4840
+ let hasExports = false;
4841
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
4842
+ hasExports = true;
4843
+ return isLikelyComponentType(value) ||
4844
+ __nastiIsCompoundComponent(value) ||
4845
+ prevExports[key] === value;
4846
+ });
4847
+ if (!hasExports) {
4848
+ return 'Could not Fast Refresh (no exports)';
4849
+ }
4850
+ if (compatible === true) {
4851
+ __nastiEnqueueRefresh();
4852
+ return;
4853
+ }
4854
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
4855
+ }
4856
+ export const __hmr_import = (module) => import(module);
4857
+ `;
3916
4858
  REACT_REFRESH_GLOBAL_PREAMBLE = `
3917
4859
  import RefreshRuntime from "/@react-refresh";
3918
4860
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -3928,27 +4870,29 @@ window.__vite_plugin_react_preamble_installed__ = true;
3928
4870
  });
3929
4871
 
3930
4872
  // src/server/hmr.ts
3931
- import path12 from "path";
3932
- import fs9 from "fs";
4873
+ import path13 from "path";
4874
+ import fs10 from "fs";
3933
4875
  import pc7 from "picocolors";
3934
4876
  async function handleFileChange(file, server) {
3935
4877
  const { moduleGraph, ws, config } = server;
3936
4878
  const logger = config.logger;
3937
- const relativePath = "/" + path12.relative(config.root, file);
3938
- const shortFile = path12.relative(config.root, file);
4879
+ const relativePath = "/" + path13.relative(config.root, file);
4880
+ const shortFile = path13.relative(config.root, file);
3939
4881
  const mods = moduleGraph.getModulesByFile(file);
3940
4882
  if (!mods || mods.size === 0) {
3941
4883
  return;
3942
4884
  }
3943
4885
  const updates = [];
3944
4886
  const timestamp = Date.now();
4887
+ const graph = moduleGraph;
4888
+ const invalidatedModules = /* @__PURE__ */ new Set();
3945
4889
  for (const mod of mods) {
3946
- moduleGraph.invalidateModule(mod);
4890
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
3947
4891
  const ctx = {
3948
4892
  file,
3949
4893
  timestamp,
3950
4894
  modules: [mod],
3951
- read: () => fs9.readFileSync(file, "utf-8"),
4895
+ read: () => fs10.readFileSync(file, "utf-8"),
3952
4896
  server
3953
4897
  };
3954
4898
  let affectedModules = [mod];
@@ -3961,19 +4905,25 @@ async function handleFileChange(file, server) {
3961
4905
  }
3962
4906
  }
3963
4907
  for (const affected of affectedModules) {
3964
- const boundaries = moduleGraph.getHmrBoundaries(affected);
4908
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
4909
+ const boundaries = graph.getHmrBoundaries(affected);
3965
4910
  if (boundaries.length === 0) {
3966
4911
  logger.info(pc7.green("page reload ") + pc7.dim(shortFile), { timestamp: true });
3967
4912
  ws.send({ type: "full-reload", path: relativePath });
3968
4913
  return;
3969
4914
  }
3970
- for (const { boundary } of boundaries) {
3971
- updates.push({
4915
+ for (const { boundary, acceptedVia } of boundaries) {
4916
+ const update = {
3972
4917
  type: boundary.type === "css" ? "css-update" : "js-update",
3973
4918
  path: boundary.url,
3974
- acceptedPath: affected.url,
4919
+ acceptedPath: acceptedVia.url,
3975
4920
  timestamp
3976
- });
4921
+ };
4922
+ if (!updates.some(
4923
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
4924
+ )) {
4925
+ updates.push(update);
4926
+ }
3977
4927
  }
3978
4928
  }
3979
4929
  }
@@ -3997,8 +4947,8 @@ __export(runnable_environment_exports, {
3997
4947
  NastiModuleRunner: () => NastiModuleRunner,
3998
4948
  createModuleRunner: () => createModuleRunner
3999
4949
  });
4000
- import path13 from "path";
4001
- import fs10 from "fs";
4950
+ import path14 from "path";
4951
+ import fs11 from "fs";
4002
4952
  import { builtinModules as builtinModules3, createRequire as createRequire4 } from "module";
4003
4953
  import { pathToFileURL as pathToFileURL4 } from "url";
4004
4954
  function createModuleRunner(environment) {
@@ -4032,7 +4982,7 @@ var init_runnable_environment = __esm({
4032
4982
  this.config.mode,
4033
4983
  ssrDefineOverrides(environment.consumer)
4034
4984
  );
4035
- this.require = createRequire4(path13.join(this.config.root, "package.json"));
4985
+ this.require = createRequire4(path14.join(this.config.root, "package.json"));
4036
4986
  const handlers = {
4037
4987
  fetchModule: async (id, importer) => this.fetchModule(id, importer),
4038
4988
  getBuiltins: () => [/^node:/, ...builtinModules3]
@@ -4056,9 +5006,9 @@ var init_runnable_environment = __esm({
4056
5006
  this.cache.clear();
4057
5007
  }
4058
5008
  resolveToId(rawUrl) {
4059
- if (path13.isAbsolute(rawUrl) && fs10.existsSync(rawUrl.split("?")[0])) return rawUrl;
5009
+ if (path14.isAbsolute(rawUrl) && fs11.existsSync(rawUrl.split("?")[0])) return rawUrl;
4060
5010
  const clean = rawUrl.replace(/^\//, "");
4061
- return path13.resolve(this.config.root, clean);
5011
+ return path14.resolve(this.config.root, clean);
4062
5012
  }
4063
5013
  /**
4064
5014
  * fetchModule(invoke 契约方法):环境管线产出 runner 可执行代码。
@@ -4067,14 +5017,14 @@ var init_runnable_environment = __esm({
4067
5017
  */
4068
5018
  async fetchModule(id, importer) {
4069
5019
  if (NODE_BUILTINS3.has(id)) return { externalize: id };
4070
- if (!id.startsWith(".") && !path13.isAbsolute(id) && !id.startsWith("\0")) {
5020
+ if (!id.startsWith(".") && !path14.isAbsolute(id) && !id.startsWith("\0")) {
4071
5021
  return { externalize: id };
4072
5022
  }
4073
5023
  const container = this.environment.pluginContainer;
4074
5024
  let resolvedId = id;
4075
5025
  if (id.startsWith(".") && importer) {
4076
5026
  const resolved = await container.resolveId(id, importer);
4077
- resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : path13.resolve(path13.dirname(importer.split("?")[0]), id);
5027
+ resolvedId = resolved ? typeof resolved === "string" ? resolved : resolved.id : path14.resolve(path14.dirname(importer.split("?")[0]), id);
4078
5028
  }
4079
5029
  resolvedId = this.completeExtension(resolvedId);
4080
5030
  const cleanId = resolvedId.split("?")[0];
@@ -4082,8 +5032,8 @@ var init_runnable_environment = __esm({
4082
5032
  const loaded = await container.load(resolvedId);
4083
5033
  if (loaded != null) {
4084
5034
  code = typeof loaded === "string" ? loaded : loaded.code;
4085
- } else if (fs10.existsSync(cleanId)) {
4086
- code = fs10.readFileSync(cleanId, "utf-8");
5035
+ } else if (fs11.existsSync(cleanId)) {
5036
+ code = fs11.readFileSync(cleanId, "utf-8");
4087
5037
  } else {
4088
5038
  throw new Error(`[nasti:ssr] cannot load module: ${resolvedId}`);
4089
5039
  }
@@ -4116,19 +5066,19 @@ var init_runnable_environment = __esm({
4116
5066
  completeExtension(id) {
4117
5067
  const clean = id.split("?")[0];
4118
5068
  const query = id.includes("?") ? id.slice(id.indexOf("?")) : "";
4119
- if (fs10.existsSync(clean) && fs10.statSync(clean).isFile()) return id;
5069
+ if (fs11.existsSync(clean) && fs11.statSync(clean).isFile()) return id;
4120
5070
  const jsMatch = clean.match(/^(.*)\.([mc]?)jsx?$/);
4121
5071
  if (jsMatch) {
4122
5072
  for (const tsExt of [`.${jsMatch[2]}ts`, `.${jsMatch[2]}tsx`]) {
4123
- if (fs10.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
5073
+ if (fs11.existsSync(jsMatch[1] + tsExt)) return jsMatch[1] + tsExt + query;
4124
5074
  }
4125
5075
  }
4126
5076
  for (const ext of this.config.resolve.extensions) {
4127
- if (fs10.existsSync(clean + ext)) return clean + ext + query;
5077
+ if (fs11.existsSync(clean + ext)) return clean + ext + query;
4128
5078
  }
4129
5079
  for (const ext of this.config.resolve.extensions) {
4130
- const indexPath = path13.join(clean, `index${ext}`);
4131
- if (fs10.existsSync(indexPath)) return indexPath;
5080
+ const indexPath = path14.join(clean, `index${ext}`);
5081
+ if (fs11.existsSync(indexPath)) return indexPath;
4132
5082
  }
4133
5083
  return id;
4134
5084
  }
@@ -4155,10 +5105,10 @@ var init_runnable_environment = __esm({
4155
5105
  return;
4156
5106
  }
4157
5107
  const ssrImport = async (dep) => {
4158
- if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !path13.isAbsolute(dep) && !dep.startsWith("\0")) {
5108
+ if (NODE_BUILTINS3.has(dep) || !dep.startsWith(".") && !path14.isAbsolute(dep) && !dep.startsWith("\0")) {
4159
5109
  return this.importExternal(dep);
4160
5110
  }
4161
- const depId = dep.startsWith(".") ? this.completeExtension(path13.resolve(path13.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
5111
+ const depId = dep.startsWith(".") ? this.completeExtension(path14.resolve(path14.dirname(fetched.id.split("?")[0]), dep)) : this.completeExtension(dep);
4162
5112
  return this.instantiate(depId);
4163
5113
  };
4164
5114
  const ssrExportAll = (sourceModule) => {
@@ -4190,7 +5140,7 @@ var init_runnable_environment = __esm({
4190
5140
  }
4191
5141
  async importExternal(spec) {
4192
5142
  try {
4193
- return await (spec.startsWith("node:") || !path13.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import(pathToFileURL4(spec).href));
5143
+ return await (spec.startsWith("node:") || !path14.isAbsolute(spec) ? import(this.resolveExternalSpecifier(spec)) : import(pathToFileURL4(spec).href));
4194
5144
  } catch (err) {
4195
5145
  throw new Error(`[nasti:ssr] failed to import external "${spec}": ${err.message}`);
4196
5146
  }
@@ -4216,7 +5166,7 @@ var dev_engine_exports = {};
4216
5166
  __export(dev_engine_exports, {
4217
5167
  createBundledDevServer: () => createBundledDevServer
4218
5168
  });
4219
- import path14 from "path";
5169
+ import path15 from "path";
4220
5170
  import crypto3 from "crypto";
4221
5171
  import { WebSocketServer as WsServer2 } from "ws";
4222
5172
  import pc8 from "picocolors";
@@ -4237,7 +5187,7 @@ async function createBundledDevServer(opts) {
4237
5187
  `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
4238
5188
  );
4239
5189
  }
4240
- const html = await readHtmlFile(config.root);
5190
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4241
5191
  const entryPoints = resolveClientEntries(config, html);
4242
5192
  if (entryPoints.length === 0) {
4243
5193
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4252,7 +5202,7 @@ async function createBundledDevServer(opts) {
4252
5202
  createReactRefreshRuntimePlugin(entryPoints),
4253
5203
  createBundledOxcRefreshPlugin()
4254
5204
  ] : [],
4255
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5205
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4256
5206
  ...useReactRefresh ? [
4257
5207
  refreshWrapperFn({
4258
5208
  cwd: config.root,
@@ -4301,7 +5251,7 @@ async function createBundledDevServer(opts) {
4301
5251
  }
4302
5252
  const url = `/${patchPath}`;
4303
5253
  logger.info(
4304
- pc8.green("hmr update ") + pc8.dim(changedFiles.map((f) => path14.relative(config.root, f)).join(", ")),
5254
+ pc8.green("hmr update ") + pc8.dim(changedFiles.map((f) => path15.relative(config.root, f)).join(", ")),
4305
5255
  { timestamp: true }
4306
5256
  );
4307
5257
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4437,13 +5387,13 @@ async function createBundledDevServer(opts) {
4437
5387
  return;
4438
5388
  }
4439
5389
  res.setHeader("ETag", hit.etag);
4440
- res.setHeader("Content-Type", MIME_TYPES[path14.extname(fileName)] ?? "application/octet-stream");
5390
+ res.setHeader("Content-Type", MIME_TYPES[path15.extname(fileName)] ?? "application/octet-stream");
4441
5391
  res.setHeader("Cache-Control", "no-cache");
4442
5392
  res.end(hit.content);
4443
5393
  return;
4444
5394
  }
4445
5395
  if (pathname === "/" || pathname.endsWith(".html")) {
4446
- const rawHtml = await readHtmlFile(config.root);
5396
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4447
5397
  if (rawHtml) {
4448
5398
  res.setHeader("Content-Type", "text/html");
4449
5399
  res.setHeader("Cache-Control", "no-store");
@@ -4473,7 +5423,7 @@ function stripCatchAllLoad(plugins) {
4473
5423
  );
4474
5424
  }
4475
5425
  function createReactRefreshRuntimePlugin(entryPoints) {
4476
- const entryIds = new Set(entryPoints.map((p) => path14.resolve(p)));
5426
+ const entryIds = new Set(entryPoints.map((p) => path15.resolve(p)));
4477
5427
  return {
4478
5428
  name: "nasti:bundled-react-refresh",
4479
5429
  resolveId(source) {
@@ -4491,7 +5441,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4491
5441
  return null;
4492
5442
  },
4493
5443
  transform(code, id) {
4494
- if (!entryIds.has(path14.resolve(id.split("?")[0]))) return null;
5444
+ if (!entryIds.has(path15.resolve(id.split("?")[0]))) return null;
4495
5445
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4496
5446
  ${code}`, map: null };
4497
5447
  }
@@ -4527,10 +5477,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4527
5477
  }
4528
5478
  }
4529
5479
  for (const [facadeModuleId, fileName] of entryFileNames) {
4530
- const originalEntry = path14.relative(config.root, facadeModuleId);
4531
- processed = processed.replace(
4532
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4533
- `$1/${fileName}$3`
5480
+ processed = replaceEntryScript(
5481
+ processed,
5482
+ facadeModuleId,
5483
+ fileName,
5484
+ config,
5485
+ config.environments.client?.html ?? "index.html",
5486
+ "/"
4534
5487
  );
4535
5488
  }
4536
5489
  return processed;
@@ -4659,7 +5612,7 @@ __export(server_exports, {
4659
5612
  createServer: () => createServer
4660
5613
  });
4661
5614
  import http from "http";
4662
- import path15 from "path";
5615
+ import path16 from "path";
4663
5616
  import os from "os";
4664
5617
  import connect from "connect";
4665
5618
  import sirv from "sirv";
@@ -4669,27 +5622,38 @@ async function createServer(inlineConfig = {}) {
4669
5622
  const startTime = performance.now();
4670
5623
  const config = await resolveConfig(inlineConfig, "serve");
4671
5624
  const logger = config.logger;
4672
- const allPlugins = resolvePluginList(config, config.plugins);
5625
+ const allPlugins = resolvePluginList(config, config.plugins, {
5626
+ environmentName: "client"
5627
+ });
4673
5628
  const configWithPlugins = { ...config, plugins: allPlugins };
4674
5629
  const app = connect();
4675
5630
  const httpServer = http.createServer(app);
4676
5631
  const ws = createWebSocketServer(httpServer);
4677
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
5632
+ const pluginApi = getPluginApi(config);
5633
+ const clientEnv = new NastiEnvironment("client", config, {
4678
5634
  hot: createWsHotChannel(ws),
4679
5635
  mode: "dev",
4680
- plugins: allPlugins
5636
+ plugins: allPlugins,
5637
+ pluginApi
4681
5638
  });
4682
5639
  await clientEnv.init();
4683
5640
  const environments = { client: clientEnv };
4684
5641
  for (const name of Object.keys(config.environments)) {
4685
5642
  if (name === "client") continue;
4686
5643
  const consumer = config.environments[name].consumer;
4687
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4688
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
5644
+ const envPlugins = resolvePluginList(config, config.plugins, {
5645
+ consumer,
5646
+ environmentName: name
5647
+ });
5648
+ environments[name] = new NastiEnvironment(name, config, {
4689
5649
  mode: "dev",
4690
- plugins: envPlugins
5650
+ plugins: envPlugins,
5651
+ pluginApi
4691
5652
  });
4692
5653
  }
5654
+ for (const [name, environment] of Object.entries(environments)) {
5655
+ if (name !== "client" && environment.options.driver) await environment.init();
5656
+ }
4693
5657
  let ssrRunner = null;
4694
5658
  async function getSsrRunner() {
4695
5659
  if (ssrRunner) return ssrRunner;
@@ -4714,23 +5678,15 @@ async function createServer(inlineConfig = {}) {
4714
5678
  });
4715
5679
  app.use(bundledServer.middleware);
4716
5680
  }
4717
- app.use(transformMiddleware({
4718
- config: configWithPlugins,
4719
- pluginContainer,
4720
- moduleGraph
4721
- }));
4722
- const publicDir = path15.resolve(config.root, "public");
4723
- app.use(sirv(publicDir, { dev: true, etag: true }));
4724
- app.use(sirv(config.root, { dev: true, etag: true }));
4725
5681
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4726
- const outDirAbs = path15.resolve(config.root, config.build.outDir);
5682
+ const outDirAbs = path16.resolve(config.root, config.build.outDir);
4727
5683
  const watcher = watch(config.root, {
4728
5684
  ignored: (filePath) => {
4729
5685
  if (filePath === config.root) return false;
4730
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path15.sep)) return true;
4731
- const rel = path15.relative(config.root, filePath);
4732
- if (!rel || rel.startsWith("..") || path15.isAbsolute(rel)) return false;
4733
- for (const seg of rel.split(path15.sep)) {
5686
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + path16.sep)) return true;
5687
+ const rel = path16.relative(config.root, filePath);
5688
+ if (!rel || rel.startsWith("..") || path16.isAbsolute(rel)) return false;
5689
+ for (const seg of rel.split(path16.sep)) {
4734
5690
  if (ignoredSegments.has(seg)) return true;
4735
5691
  }
4736
5692
  return false;
@@ -4738,13 +5694,72 @@ async function createServer(inlineConfig = {}) {
4738
5694
  ignoreInitial: true
4739
5695
  });
4740
5696
  let server;
5697
+ const environmentServices = {};
5698
+ let environmentDriversStarted = false;
5699
+ const logCloseError = (target, error) => {
5700
+ const normalized = error instanceof Error ? error : new Error(String(error));
5701
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5702
+ };
5703
+ const startEnvironmentDrivers = async () => {
5704
+ if (environmentDriversStarted) return;
5705
+ environmentDriversStarted = true;
5706
+ const started = [];
5707
+ const attempted = [];
5708
+ try {
5709
+ for (const [name, environment] of Object.entries(environments)) {
5710
+ if (!environment.driver?.serve) continue;
5711
+ attempted.push(environment);
5712
+ const result = await environment.driver.serve({
5713
+ ...environment.getDriverContext(),
5714
+ server
5715
+ });
5716
+ started.push({ name, environment, service: result ?? {} });
5717
+ }
5718
+ for (const { name, service } of started) {
5719
+ environmentServices[name] = service;
5720
+ if (service.middleware) app.use(service.middleware);
5721
+ }
5722
+ } catch (error) {
5723
+ environmentDriversStarted = false;
5724
+ for (const { name } of started) {
5725
+ delete environmentServices[name];
5726
+ }
5727
+ for (const environment of attempted.reverse()) {
5728
+ try {
5729
+ await environment.driver?.close?.(environment.getDriverContext());
5730
+ } catch (closeError) {
5731
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5732
+ }
5733
+ }
5734
+ throw error;
5735
+ }
5736
+ };
5737
+ const notifyEnvironmentDrivers = (file, event) => {
5738
+ for (const environment of Object.values(environments)) {
5739
+ if (!environment.driver?.watchChange) continue;
5740
+ void Promise.resolve(
5741
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5742
+ ).catch((error) => {
5743
+ logger.error(
5744
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5745
+ { error }
5746
+ );
5747
+ });
5748
+ }
5749
+ };
4741
5750
  watcher.on("change", (file) => {
4742
5751
  ssrRunner?.invalidateFile(file);
4743
5752
  handleFileChange(file, server);
5753
+ notifyEnvironmentDrivers(file, "change");
4744
5754
  });
4745
5755
  watcher.on("add", (file) => {
4746
5756
  ssrRunner?.invalidateFile(file);
4747
5757
  handleFileChange(file, server);
5758
+ notifyEnvironmentDrivers(file, "add");
5759
+ });
5760
+ watcher.on("unlink", (file) => {
5761
+ ssrRunner?.invalidateFile(file);
5762
+ notifyEnvironmentDrivers(file, "unlink");
4748
5763
  });
4749
5764
  server = {
4750
5765
  config: configWithPlugins,
@@ -4753,10 +5768,12 @@ async function createServer(inlineConfig = {}) {
4753
5768
  watcher,
4754
5769
  ws,
4755
5770
  environments,
5771
+ environmentServices,
4756
5772
  async listen(port) {
4757
5773
  const finalPort = port ?? config.server.port;
4758
5774
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4759
5775
  await pluginContainer.buildStart();
5776
+ await startEnvironmentDrivers();
4760
5777
  return new Promise((resolve, reject) => {
4761
5778
  let currentPort = finalPort;
4762
5779
  const onListening = () => {
@@ -4764,15 +5781,20 @@ async function createServer(inlineConfig = {}) {
4764
5781
  config.server.port = actualPort;
4765
5782
  const localUrl = `http://localhost:${actualPort}/`;
4766
5783
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5784
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5785
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4767
5786
  logger.clearScreen("info");
4768
5787
  const readyIn = Math.ceil(performance.now() - startTime);
4769
5788
  logger.info(
4770
5789
  `
4771
- ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.2.0"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
5790
+ ${pc9.cyan(pc9.bold("NASTI"))} ${pc9.cyan(`v${"2.4.0"}`)} ${pc9.dim("ready in")} ${pc9.bold(readyIn)} ${pc9.dim("ms")}
4772
5791
  `
4773
5792
  );
4774
5793
  printServerUrls(
4775
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5794
+ {
5795
+ local: [localUrl, ...driverLocalUrls],
5796
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5797
+ },
4776
5798
  logger.info
4777
5799
  );
4778
5800
  logger.info("");
@@ -4793,7 +5815,12 @@ async function createServer(inlineConfig = {}) {
4793
5815
  },
4794
5816
  async transformRequest(url) {
4795
5817
  const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
4796
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
5818
+ return transformRequest2(url, {
5819
+ config: configWithPlugins,
5820
+ pluginContainer,
5821
+ moduleGraph,
5822
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5823
+ });
4797
5824
  },
4798
5825
  async ssrLoadModule(url) {
4799
5826
  const runner = await getSsrRunner();
@@ -4802,11 +5829,63 @@ async function createServer(inlineConfig = {}) {
4802
5829
  async close() {
4803
5830
  await pluginContainer.buildEnd();
4804
5831
  await bundledServer?.close();
4805
- watcher.close();
5832
+ let environmentCloseFailed = false;
5833
+ let firstEnvironmentCloseError;
5834
+ for (const environment of Object.values(environments).reverse()) {
5835
+ try {
5836
+ await environment.close();
5837
+ } catch (error) {
5838
+ if (!environmentCloseFailed) {
5839
+ environmentCloseFailed = true;
5840
+ firstEnvironmentCloseError = error;
5841
+ }
5842
+ logCloseError(`environment "${environment.name}"`, error);
5843
+ }
5844
+ }
5845
+ await watcher.close();
4806
5846
  ws.close();
4807
5847
  httpServer.close();
5848
+ if (environmentCloseFailed) {
5849
+ throw firstEnvironmentCloseError;
5850
+ }
4808
5851
  }
4809
5852
  };
5853
+ try {
5854
+ await startEnvironmentDrivers();
5855
+ } catch (error) {
5856
+ if (bundledServer) {
5857
+ try {
5858
+ await bundledServer.close();
5859
+ } catch (closeError) {
5860
+ logCloseError("bundled dev server after driver startup failure", closeError);
5861
+ }
5862
+ }
5863
+ try {
5864
+ await watcher.close();
5865
+ } catch (closeError) {
5866
+ logCloseError("file watcher after driver startup failure", closeError);
5867
+ }
5868
+ try {
5869
+ ws.close();
5870
+ } catch (closeError) {
5871
+ logCloseError("WebSocket server after driver startup failure", closeError);
5872
+ }
5873
+ try {
5874
+ httpServer.close();
5875
+ } catch (closeError) {
5876
+ logCloseError("HTTP server after driver startup failure", closeError);
5877
+ }
5878
+ throw error;
5879
+ }
5880
+ app.use(transformMiddleware({
5881
+ config: configWithPlugins,
5882
+ pluginContainer,
5883
+ moduleGraph,
5884
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5885
+ }));
5886
+ const publicDir = path16.resolve(config.root, "public");
5887
+ app.use(sirv(publicDir, { dev: true, etag: true }));
5888
+ app.use(sirv(config.root, { dev: true, etag: true }));
4810
5889
  const postMiddlewares = [];
4811
5890
  for (const plugin of allPlugins) {
4812
5891
  if (plugin.configureServer) {
@@ -4841,6 +5920,7 @@ var init_server = __esm({
4841
5920
  init_middleware();
4842
5921
  init_hmr();
4843
5922
  init_builtins();
5923
+ init_plugin_api();
4844
5924
  }
4845
5925
  });
4846
5926
 
@@ -4851,8 +5931,8 @@ init_build();
4851
5931
  // src/build/electron.ts
4852
5932
  init_config();
4853
5933
  init_resolve();
4854
- import path10 from "path";
4855
- import fs7 from "fs";
5934
+ import path11 from "path";
5935
+ import fs8 from "fs";
4856
5936
  import { rolldown as rolldown2 } from "rolldown";
4857
5937
  import pc5 from "picocolors";
4858
5938
 
@@ -4896,28 +5976,26 @@ async function buildElectron(inlineConfig = {}) {
4896
5976
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4897
5977
  const startTime = performance.now();
4898
5978
  assertElectronVersion(config);
4899
- console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.2.0"}`));
5979
+ console.log(pc5.cyan("\n\u26A1 nasti build (electron)") + pc5.dim(` v${"2.4.0"}`));
4900
5980
  console.log(pc5.dim(` root: ${config.root}`));
4901
5981
  console.log(pc5.dim(` mode: ${config.mode}`));
4902
5982
  console.log(pc5.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
4903
- const outDir = path10.resolve(config.root, config.build.outDir);
4904
- if (config.build.emptyOutDir && fs7.existsSync(outDir)) {
4905
- fs7.rmSync(outDir, { recursive: true, force: true });
5983
+ const outDir = path11.resolve(config.root, config.build.outDir);
5984
+ if (config.build.emptyOutDir && fs8.existsSync(outDir)) {
5985
+ fs8.rmSync(outDir, { recursive: true, force: true });
4906
5986
  }
4907
- fs7.mkdirSync(outDir, { recursive: true });
4908
- const rendererOutDir = path10.join(outDir, "renderer");
5987
+ fs8.mkdirSync(outDir, { recursive: true });
5988
+ const rendererOutDir = path11.join(outDir, "renderer");
4909
5989
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4910
- await build2({
4911
- ...inlineConfig,
4912
- target: "web",
5990
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4913
5991
  build: {
4914
5992
  ...inlineConfig.build,
4915
5993
  outDir: rendererOutDir,
4916
5994
  emptyOutDir: false
4917
5995
  }
4918
- });
4919
- const mainEntry = path10.resolve(config.root, config.electron.main);
4920
- if (!fs7.existsSync(mainEntry)) {
5996
+ }));
5997
+ const mainEntry = path11.resolve(config.root, config.electron.main);
5998
+ if (!fs8.existsSync(mainEntry)) {
4921
5999
  throw new Error(
4922
6000
  `Electron main entry not found: ${config.electron.main}
4923
6001
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -4931,11 +6009,11 @@ async function buildElectron(inlineConfig = {}) {
4931
6009
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
4932
6010
  const preloadFiles = [];
4933
6011
  for (const entry of preloadEntries) {
4934
- if (!fs7.existsSync(entry)) {
6012
+ if (!fs8.existsSync(entry)) {
4935
6013
  console.warn(pc5.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
4936
6014
  continue;
4937
6015
  }
4938
- const base = path10.basename(entry).replace(/\.[^.]+$/, "");
6016
+ const base = path11.basename(entry).replace(/\.[^.]+$/, "");
4939
6017
  const out = outFileName(outDir, base, config.electron.preloadFormat);
4940
6018
  await bundleNode(config, entry, {
4941
6019
  outFile: out,
@@ -4947,10 +6025,10 @@ async function buildElectron(inlineConfig = {}) {
4947
6025
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4948
6026
  console.log(pc5.green(`
4949
6027
  \u2713 Electron build complete in ${elapsed}s`));
4950
- console.log(pc5.dim(` renderer: ${path10.relative(config.root, rendererOutDir)}/`));
4951
- console.log(pc5.dim(` main: ${path10.relative(config.root, mainFile)}`));
6028
+ console.log(pc5.dim(` renderer: ${path11.relative(config.root, rendererOutDir)}/`));
6029
+ console.log(pc5.dim(` main: ${path11.relative(config.root, mainFile)}`));
4952
6030
  for (const pf of preloadFiles) {
4953
- console.log(pc5.dim(` preload: ${path10.relative(config.root, pf)}`));
6031
+ console.log(pc5.dim(` preload: ${path11.relative(config.root, pf)}`));
4954
6032
  }
4955
6033
  console.log();
4956
6034
  return { rendererOutDir, mainFile, preloadFiles };
@@ -4969,7 +6047,8 @@ async function bundleNode(config, entry, opts) {
4969
6047
  const result = transformCode(id, code, {
4970
6048
  sourcemap: !!config.build.sourcemap,
4971
6049
  jsxRuntime: "automatic",
4972
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6050
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6051
+ target: config.electron.nodeTarget
4973
6052
  });
4974
6053
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
4975
6054
  }
@@ -4980,10 +6059,14 @@ async function bundleNode(config, entry, opts) {
4980
6059
  ...restInputOptions,
4981
6060
  input: entry,
4982
6061
  platform: "node",
4983
- transform: { ...userTransform, define: mergedDefine },
6062
+ transform: {
6063
+ ...userTransform,
6064
+ target: config.electron.nodeTarget,
6065
+ define: mergedDefine
6066
+ },
4984
6067
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
4985
6068
  });
4986
- fs7.mkdirSync(path10.dirname(opts.outFile), { recursive: true });
6069
+ fs8.mkdirSync(path11.dirname(opts.outFile), { recursive: true });
4987
6070
  await bundle2.write({
4988
6071
  sourcemap: !!config.build.sourcemap,
4989
6072
  minify: !!config.build.minify,
@@ -4994,16 +6077,35 @@ async function bundleNode(config, entry, opts) {
4994
6077
  codeSplitting: false
4995
6078
  });
4996
6079
  await bundle2.close();
4997
- console.log(pc5.dim(` \u2713 ${opts.label} \u2192 ${path10.relative(config.root, opts.outFile)}`));
6080
+ console.log(pc5.dim(` \u2713 ${opts.label} \u2192 ${path11.relative(config.root, opts.outFile)}`));
4998
6081
  return opts.outFile;
4999
6082
  }
6083
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
6084
+ const inlineClient = inlineConfig.environments?.client ?? {};
6085
+ return {
6086
+ ...inlineConfig,
6087
+ ...overrides,
6088
+ root: config.root,
6089
+ mode: config.mode,
6090
+ target: "web",
6091
+ framework: config.framework,
6092
+ base: config.base === "/" ? "./" : config.base,
6093
+ environments: {
6094
+ ...inlineConfig.environments ?? {},
6095
+ client: {
6096
+ ...inlineClient,
6097
+ html: config.electron.renderer
6098
+ }
6099
+ }
6100
+ };
6101
+ }
5000
6102
  function outFileName(outDir, base, format) {
5001
6103
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5002
- return path10.join(outDir, base + ext);
6104
+ return path11.join(outDir, base + ext);
5003
6105
  }
5004
6106
  function normalizePreload(preload, root) {
5005
6107
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5006
- return list.map((p) => path10.resolve(root, p));
6108
+ return list.map((p) => path11.resolve(root, p));
5007
6109
  }
5008
6110
  function assertElectronVersion(config) {
5009
6111
  const min = config.electron.minVersion;
@@ -5018,9 +6120,9 @@ function assertElectronVersion(config) {
5018
6120
  }
5019
6121
  function detectInstalledElectron(root) {
5020
6122
  try {
5021
- const pkgPath = path10.resolve(root, "node_modules/electron/package.json");
5022
- if (!fs7.existsSync(pkgPath)) return null;
5023
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
6123
+ const pkgPath = path11.resolve(root, "node_modules/electron/package.json");
6124
+ if (!fs8.existsSync(pkgPath)) return null;
6125
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
5024
6126
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5025
6127
  return Number.isFinite(major) ? major : null;
5026
6128
  } catch {
@@ -5033,8 +6135,8 @@ init_server();
5033
6135
 
5034
6136
  // src/server/electron-dev.ts
5035
6137
  init_config();
5036
- import path16 from "path";
5037
- import fs11 from "fs";
6138
+ import path17 from "path";
6139
+ import fs12 from "fs";
5038
6140
  import { createRequire as createRequire5 } from "module";
5039
6141
  import { spawn } from "child_process";
5040
6142
  import chokidar from "chokidar";
@@ -5047,17 +6149,21 @@ async function startElectronDev(inlineConfig = {}) {
5047
6149
  const { noSpawn, ...rest } = inlineConfig;
5048
6150
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5049
6151
  warnElectronVersion(config);
5050
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.2.0"}`));
6152
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.4.0"}`));
5051
6153
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5052
- const server = await createServer2({ ...rest, target: "electron" });
6154
+ const server = await createServer2({
6155
+ ...rest,
6156
+ target: "electron",
6157
+ framework: config.framework
6158
+ });
5053
6159
  await server.listen();
5054
- const devUrl = `http://localhost:${server.config.server.port}/`;
6160
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5055
6161
  console.log(pc10.dim(` renderer: ${devUrl}`));
5056
- const stageDir = path16.resolve(config.root, ".nasti");
5057
- fs11.mkdirSync(stageDir, { recursive: true });
5058
- const mainEntry = path16.resolve(config.root, config.electron.main);
6162
+ const stageDir = path17.resolve(config.root, ".nasti");
6163
+ fs12.mkdirSync(stageDir, { recursive: true });
6164
+ const mainEntry = path17.resolve(config.root, config.electron.main);
5059
6165
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5060
- const builtMainFile = path16.join(stageDir, "main" + extFor(config.electron.mainFormat));
6166
+ const builtMainFile = path17.join(stageDir, "main" + extFor(config.electron.mainFormat));
5061
6167
  const builtPreloadFiles = [];
5062
6168
  const compileAll = async () => {
5063
6169
  await compileNode(config, mainEntry, {
@@ -5067,9 +6173,9 @@ async function startElectronDev(inlineConfig = {}) {
5067
6173
  });
5068
6174
  builtPreloadFiles.length = 0;
5069
6175
  for (const entry of preloadEntries) {
5070
- if (!fs11.existsSync(entry)) continue;
5071
- const base = path16.basename(entry).replace(/\.[^.]+$/, "");
5072
- const out = path16.join(stageDir, base + extFor(config.electron.preloadFormat));
6176
+ if (!fs12.existsSync(entry)) continue;
6177
+ const base = path17.basename(entry).replace(/\.[^.]+$/, "");
6178
+ const out = path17.join(stageDir, base + extFor(config.electron.preloadFormat));
5073
6179
  await compileNode(config, entry, {
5074
6180
  outFile: out,
5075
6181
  format: config.electron.preloadFormat,
@@ -5108,7 +6214,7 @@ async function startElectronDev(inlineConfig = {}) {
5108
6214
  };
5109
6215
  spawnElectron();
5110
6216
  if (config.electron.autoRestart) {
5111
- const watchTargets = [mainEntry, ...preloadEntries].filter(fs11.existsSync);
6217
+ const watchTargets = [mainEntry, ...preloadEntries].filter(fs12.existsSync);
5112
6218
  const watcher = chokidar.watch(watchTargets, { ignoreInitial: true });
5113
6219
  let restarting = null;
5114
6220
  let pending = false;
@@ -5173,18 +6279,22 @@ async function compileNode(config, entry, opts) {
5173
6279
  const result = transformCode(id, code, {
5174
6280
  sourcemap: true,
5175
6281
  jsxRuntime: "automatic",
5176
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
6282
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
6283
+ target: config.electron.nodeTarget
5177
6284
  });
5178
6285
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5179
6286
  }
5180
6287
  };
5181
6288
  const bundle2 = await rolldown3({
5182
6289
  input: entry,
5183
- transform: { define: envDefine },
6290
+ transform: {
6291
+ target: config.electron.nodeTarget,
6292
+ define: envDefine
6293
+ },
5184
6294
  platform: "node",
5185
6295
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5186
6296
  });
5187
- fs11.mkdirSync(path16.dirname(opts.outFile), { recursive: true });
6297
+ fs12.mkdirSync(path17.dirname(opts.outFile), { recursive: true });
5188
6298
  await bundle2.write({
5189
6299
  file: opts.outFile,
5190
6300
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5196,15 +6306,19 @@ async function compileNode(config, entry, opts) {
5196
6306
  });
5197
6307
  await bundle2.close();
5198
6308
  }
6309
+ function electronRendererDevPath(renderer) {
6310
+ const normalized = renderer.split(path17.sep).join("/").replace(/^\.?\//, "");
6311
+ return normalized === "index.html" ? "/" : `/${normalized}`;
6312
+ }
5199
6313
  function resolveElectronBinary(config) {
5200
- if (config.electron.electronPath && fs11.existsSync(config.electron.electronPath)) {
6314
+ if (config.electron.electronPath && fs12.existsSync(config.electron.electronPath)) {
5201
6315
  return config.electron.electronPath;
5202
6316
  }
5203
6317
  try {
5204
- const require2 = createRequire5(path16.resolve(config.root, "package.json"));
6318
+ const require2 = createRequire5(path17.resolve(config.root, "package.json"));
5205
6319
  const pathFile = require2.resolve("electron");
5206
6320
  const electronModule = require2(pathFile);
5207
- if (typeof electronModule === "string" && fs11.existsSync(electronModule)) {
6321
+ if (typeof electronModule === "string" && fs12.existsSync(electronModule)) {
5208
6322
  return electronModule;
5209
6323
  }
5210
6324
  } catch {
@@ -5231,8 +6345,8 @@ function warnElectronVersion(config) {
5231
6345
  }
5232
6346
 
5233
6347
  // src/plugins/monaco-editor.ts
5234
- import path17 from "path";
5235
- import fs12 from "fs";
6348
+ import path18 from "path";
6349
+ import fs13 from "fs";
5236
6350
  import crypto4 from "crypto";
5237
6351
  import { createRequire as createRequire6 } from "module";
5238
6352
  var DEFAULT_WORKERS = {
@@ -5253,9 +6367,9 @@ function normalizePublicPath(p) {
5253
6367
  }
5254
6368
  function readMonacoVersion(root) {
5255
6369
  try {
5256
- const require2 = createRequire6(path17.resolve(root, "package.json"));
6370
+ const require2 = createRequire6(path18.resolve(root, "package.json"));
5257
6371
  const pkgJsonPath = require2.resolve("monaco-editor/package.json", { paths: [root] });
5258
- const pkg = JSON.parse(fs12.readFileSync(pkgJsonPath, "utf-8"));
6372
+ const pkg = JSON.parse(fs13.readFileSync(pkgJsonPath, "utf-8"));
5259
6373
  return typeof pkg.version === "string" ? pkg.version : "unknown";
5260
6374
  } catch {
5261
6375
  return "unknown";
@@ -5275,20 +6389,20 @@ function monacoEditorPlugin(options = {}) {
5275
6389
  let cacheDir = "";
5276
6390
  const building = /* @__PURE__ */ new Map();
5277
6391
  async function buildWorker(worker) {
5278
- const cacheFile = path17.join(cacheDir, `${worker.label}.worker.js`);
5279
- if (fs12.existsSync(cacheFile)) return cacheFile;
6392
+ const cacheFile = path18.join(cacheDir, `${worker.label}.worker.js`);
6393
+ if (fs13.existsSync(cacheFile)) return cacheFile;
5280
6394
  const existing = building.get(worker.label);
5281
6395
  if (existing) return existing;
5282
6396
  const task = (async () => {
5283
6397
  const { rolldown: rolldown4 } = await import("rolldown");
5284
- const require2 = createRequire6(path17.resolve(resolvedConfig.root, "package.json"));
6398
+ const require2 = createRequire6(path18.resolve(resolvedConfig.root, "package.json"));
5285
6399
  let entry;
5286
6400
  try {
5287
6401
  entry = require2.resolve(worker.entry, { paths: [resolvedConfig.root] });
5288
6402
  } catch {
5289
6403
  entry = require2.resolve(worker.entry + ".js", { paths: [resolvedConfig.root] });
5290
6404
  }
5291
- fs12.mkdirSync(cacheDir, { recursive: true });
6405
+ fs13.mkdirSync(cacheDir, { recursive: true });
5292
6406
  const bundle2 = await rolldown4({
5293
6407
  input: entry,
5294
6408
  platform: "browser"
@@ -5348,12 +6462,12 @@ function monacoEditorPlugin(options = {}) {
5348
6462
  resolvedConfig = config;
5349
6463
  const version = readMonacoVersion(config.root);
5350
6464
  const key = crypto4.createHash("sha1").update(version + "|" + publicPath).digest("hex").slice(0, 8);
5351
- cacheDir = path17.resolve(config.root, "node_modules/.nasti/monaco", key);
6465
+ cacheDir = path18.resolve(config.root, "node_modules/.nasti/monaco", key);
5352
6466
  },
5353
6467
  async configureServer(server) {
5354
6468
  const shouldBuild = !isCDN(publicPath) || forceBuildCDN;
5355
6469
  const watcher = server.watcher;
5356
- const monacoDir = path17.resolve(resolvedConfig.root, "node_modules/monaco-editor");
6470
+ const monacoDir = path18.resolve(resolvedConfig.root, "node_modules/monaco-editor");
5357
6471
  try {
5358
6472
  watcher?.unwatch?.(monacoDir);
5359
6473
  } catch {
@@ -5383,7 +6497,7 @@ function monacoEditorPlugin(options = {}) {
5383
6497
  const file = await buildWorker(worker);
5384
6498
  res.setHeader("Content-Type", "application/javascript; charset=utf-8");
5385
6499
  res.setHeader("Cache-Control", "public, max-age=604800, immutable");
5386
- fs12.createReadStream(file).pipe(res);
6500
+ fs13.createReadStream(file).pipe(res);
5387
6501
  } catch (e) {
5388
6502
  res.statusCode = 500;
5389
6503
  res.end(`Monaco worker build failed: ${e.message}`);
@@ -5418,16 +6532,16 @@ self.monaco = monaco;`,
5418
6532
  resolvedConfig.root,
5419
6533
  resolvedConfig.build.outDir,
5420
6534
  resolvedConfig.base
5421
- ) : isCDN(publicPath) ? path17.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : path17.resolve(
6535
+ ) : isCDN(publicPath) ? path18.resolve(resolvedConfig.root, resolvedConfig.build.outDir, "monaco") : path18.resolve(
5422
6536
  resolvedConfig.root,
5423
6537
  resolvedConfig.build.outDir,
5424
6538
  publicPath.replace(/^\//, "")
5425
6539
  );
5426
- fs12.mkdirSync(outDir, { recursive: true });
6540
+ fs13.mkdirSync(outDir, { recursive: true });
5427
6541
  for (const worker of workers) {
5428
6542
  try {
5429
6543
  const cacheFile = await buildWorker(worker);
5430
- fs12.copyFileSync(cacheFile, path17.join(outDir, `${worker.label}.worker.js`));
6544
+ fs13.copyFileSync(cacheFile, path18.join(outDir, `${worker.label}.worker.js`));
5431
6545
  } catch (e) {
5432
6546
  throw new Error(
5433
6547
  `[nasti:monaco-editor] worker build failed for "${worker.label}": ${e.message}
@@ -5452,12 +6566,15 @@ export {
5452
6566
  buildElectron,
5453
6567
  buildEnvDefine,
5454
6568
  createDebugger,
6569
+ createElectronRendererConfig,
5455
6570
  createLogger,
5456
6571
  createNoopHotChannel,
5457
6572
  createServer,
5458
6573
  createWsHotChannel,
5459
6574
  defineConfig,
6575
+ detectFramework,
5460
6576
  electronPlugin,
6577
+ electronRendererDevPath,
5461
6578
  loadEnv,
5462
6579
  monacoEditorPlugin,
5463
6580
  printServerUrls,