@nasti-toolchain/nasti 2.3.1 → 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/cli.cjs CHANGED
@@ -5,10 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __glob = (map) => (path17) => {
9
- var fn = map[path17];
8
+ var __glob = (map) => (path18) => {
9
+ var fn = map[path18];
10
10
  if (fn) return fn();
11
- throw new Error("Module not found in bundle: " + path17);
11
+ throw new Error("Module not found in bundle: " + path18);
12
12
  };
13
13
  var __esm = (fn, res) => function __init() {
14
14
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -476,6 +476,7 @@ async function resolveConfig(inlineConfig = {}, command) {
476
476
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
477
477
  resolved.environments.client = {
478
478
  consumer,
479
+ buildEnabled: envOptions.buildEnabled ?? true,
479
480
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
480
481
  html: import_node_path.default.resolve(
481
482
  root,
@@ -490,6 +491,7 @@ async function resolveConfig(inlineConfig = {}, command) {
490
491
  }
491
492
  resolved.environments[name] = {
492
493
  consumer,
494
+ buildEnabled: envOptions.buildEnabled ?? true,
493
495
  entry: normalizeEnvironmentEntries(envOptions.entry, root),
494
496
  html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
495
497
  driver: envOptions.driver,
@@ -782,17 +784,35 @@ var init_plugin_container = __esm({
782
784
  }
783
785
  });
784
786
 
787
+ // src/core/url.ts
788
+ function removeTimestampQuery(url) {
789
+ const hashIndex = url.indexOf("#");
790
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
791
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
792
+ const queryIndex = withoutHash.indexOf("?");
793
+ if (queryIndex < 0) return url;
794
+ const pathname = withoutHash.slice(0, queryIndex);
795
+ const query = withoutHash.slice(queryIndex + 1).split("&").filter((part) => !/^t=\d+$/.test(part)).join("&");
796
+ return pathname + (query ? `?${query}` : "") + hash;
797
+ }
798
+ var init_url = __esm({
799
+ "src/core/url.ts"() {
800
+ "use strict";
801
+ }
802
+ });
803
+
785
804
  // src/core/module-graph.ts
786
805
  var ModuleGraph;
787
806
  var init_module_graph = __esm({
788
807
  "src/core/module-graph.ts"() {
789
808
  "use strict";
809
+ init_url();
790
810
  ModuleGraph = class {
791
811
  urlToModuleMap = /* @__PURE__ */ new Map();
792
812
  idToModuleMap = /* @__PURE__ */ new Map();
793
813
  fileToModulesMap = /* @__PURE__ */ new Map();
794
814
  getModuleByUrl(url) {
795
- return this.urlToModuleMap.get(url);
815
+ return this.urlToModuleMap.get(removeTimestampQuery(url));
796
816
  }
797
817
  getModuleById(id) {
798
818
  return this.idToModuleMap.get(id);
@@ -801,10 +821,11 @@ var init_module_graph = __esm({
801
821
  return this.fileToModulesMap.get(file);
802
822
  }
803
823
  async ensureEntryFromUrl(url) {
804
- let mod = this.urlToModuleMap.get(url);
824
+ const normalizedUrl = removeTimestampQuery(url);
825
+ let mod = this.urlToModuleMap.get(normalizedUrl);
805
826
  if (mod) return mod;
806
- mod = this.createModule(url);
807
- this.urlToModuleMap.set(url, mod);
827
+ mod = this.createModule(normalizedUrl);
828
+ this.urlToModuleMap.set(normalizedUrl, mod);
808
829
  return mod;
809
830
  }
810
831
  createModule(url, id) {
@@ -818,6 +839,7 @@ var init_module_graph = __esm({
818
839
  acceptedHmrDeps: /* @__PURE__ */ new Set(),
819
840
  transformResult: null,
820
841
  lastHMRTimestamp: 0,
842
+ invalidationVersion: 0,
821
843
  isSelfAccepting: false
822
844
  };
823
845
  this.idToModuleMap.set(mod.id, mod);
@@ -860,10 +882,64 @@ var init_module_graph = __esm({
860
882
  }
861
883
  }
862
884
  }
885
+ /**
886
+ * 用一次转换得到的信息原子更新 import 与 HMR accept 关系。
887
+ * 依赖节点会在真正被浏览器请求前预先创建,这样入口模块先转换时也能建立完整图。
888
+ */
889
+ async updateModuleInfo(mod, importedUrls, acceptedUrls, isSelfAccepting, expectedInvalidationVersion) {
890
+ const importedModules = await Promise.all(
891
+ [...importedUrls].map((url) => this.ensureEntryFromUrl(url))
892
+ );
893
+ const acceptedModules = await Promise.all(
894
+ [...acceptedUrls].map((url) => this.ensureEntryFromUrl(url))
895
+ );
896
+ if (expectedInvalidationVersion !== void 0 && mod.invalidationVersion !== expectedInvalidationVersion) {
897
+ return null;
898
+ }
899
+ const previousImports = new Set(mod.importedModules);
900
+ for (const imported of previousImports) {
901
+ imported.importers.delete(mod);
902
+ }
903
+ mod.importedModules.clear();
904
+ mod.acceptedHmrDeps.clear();
905
+ for (const imported of importedModules) {
906
+ mod.importedModules.add(imported);
907
+ imported.importers.add(mod);
908
+ }
909
+ for (const accepted of acceptedModules) {
910
+ mod.acceptedHmrDeps.add(accepted);
911
+ }
912
+ mod.isSelfAccepting = isSelfAccepting;
913
+ const pruned = /* @__PURE__ */ new Set();
914
+ for (const imported of previousImports) {
915
+ if (!mod.importedModules.has(imported) && imported.importers.size === 0) {
916
+ pruned.add(imported);
917
+ }
918
+ }
919
+ return pruned;
920
+ }
863
921
  /** 使模块的转换缓存失效 */
864
- invalidateModule(mod) {
922
+ invalidateModule(mod, timestamp = Date.now()) {
865
923
  mod.transformResult = null;
866
- mod.lastHMRTimestamp = Date.now();
924
+ mod.lastHMRTimestamp = timestamp;
925
+ mod.invalidationVersion++;
926
+ }
927
+ /**
928
+ * 仅失效到 HMR 边界:显式接受依赖的模块本身不会重执行;自接受模块需要失效,
929
+ * 但不再继续影响其 importer。这样既能传播依赖时间戳,也不会隐式重复副作用。
930
+ */
931
+ invalidateModuleAndImporters(mod, timestamp = Date.now(), seen = /* @__PURE__ */ new Set()) {
932
+ if (seen.has(mod)) return;
933
+ seen.add(mod);
934
+ this.invalidateModule(mod, timestamp);
935
+ for (const importer of mod.importers) {
936
+ if (importer.acceptedHmrDeps.has(mod)) continue;
937
+ if (importer.isSelfAccepting) {
938
+ this.invalidateModule(importer, timestamp);
939
+ continue;
940
+ }
941
+ this.invalidateModuleAndImporters(importer, timestamp, seen);
942
+ }
867
943
  }
868
944
  /** 使所有模块缓存失效 */
869
945
  invalidateAll() {
@@ -874,34 +950,32 @@ var init_module_graph = __esm({
874
950
  /** 获取 HMR 传播边界 - 从变更模块向上遍历找到接受更新的边界 */
875
951
  getHmrBoundaries(mod) {
876
952
  const boundaries = [];
877
- const visited = /* @__PURE__ */ new Set();
878
- const propagate = (node, via) => {
879
- if (visited.has(node)) return true;
880
- visited.add(node);
881
- if (node.isSelfAccepting) {
882
- boundaries.push({ boundary: node, acceptedVia: via });
883
- return true;
953
+ const traversed = /* @__PURE__ */ new Set();
954
+ const addBoundary = (boundary, acceptedVia) => {
955
+ if (!boundaries.some(
956
+ (item) => item.boundary === boundary && item.acceptedVia === acceptedVia
957
+ )) {
958
+ boundaries.push({ boundary, acceptedVia });
884
959
  }
885
- if (node.acceptedHmrDeps.has(via)) {
886
- boundaries.push({ boundary: node, acceptedVia: via });
960
+ };
961
+ const propagate = (node) => {
962
+ if (traversed.has(node)) return true;
963
+ traversed.add(node);
964
+ if (node.isSelfAccepting) {
965
+ addBoundary(node, node);
887
966
  return true;
888
967
  }
889
968
  if (node.importers.size === 0) return false;
890
969
  for (const importer of node.importers) {
891
- if (!propagate(importer, node)) return false;
970
+ if (importer.acceptedHmrDeps.has(node)) {
971
+ addBoundary(importer, node);
972
+ continue;
973
+ }
974
+ if (!propagate(importer)) return false;
892
975
  }
893
976
  return true;
894
977
  };
895
- if (mod.isSelfAccepting) {
896
- boundaries.push({ boundary: mod, acceptedVia: mod });
897
- return boundaries;
898
- }
899
- for (const importer of mod.importers) {
900
- if (!propagate(importer, mod)) {
901
- return [];
902
- }
903
- }
904
- return boundaries;
978
+ return propagate(mod) ? boundaries : [];
905
979
  }
906
980
  };
907
981
  }
@@ -1040,6 +1114,7 @@ var init_environment = __esm({
1040
1114
  moduleGraph;
1041
1115
  candidatePlugins;
1042
1116
  pluginApi;
1117
+ buildMetadata = {};
1043
1118
  initialized = false;
1044
1119
  constructor(name, config, init = {}) {
1045
1120
  const options = config.environments[name];
@@ -1096,6 +1171,22 @@ var init_environment = __esm({
1096
1171
  logger: this.config.logger
1097
1172
  };
1098
1173
  }
1174
+ setBuildMetadata(metadata) {
1175
+ const { entries: currentEntries, ...currentMetadata } = this.buildMetadata;
1176
+ const { entries, ...nextMetadata } = metadata;
1177
+ this.buildMetadata = {
1178
+ ...currentMetadata,
1179
+ ...nextMetadata,
1180
+ ...currentEntries || entries ? { entries: { ...currentEntries, ...entries } } : {}
1181
+ };
1182
+ }
1183
+ getBuildMetadata() {
1184
+ const { entries, ...metadata } = this.buildMetadata;
1185
+ return {
1186
+ ...metadata,
1187
+ ...entries ? { entries: { ...entries } } : {}
1188
+ };
1189
+ }
1099
1190
  async close() {
1100
1191
  try {
1101
1192
  await this.driver?.close?.(this.getDriverContext());
@@ -1346,8 +1437,10 @@ __export(middleware_exports, {
1346
1437
  transformMiddleware: () => transformMiddleware,
1347
1438
  transformRequest: () => transformRequest
1348
1439
  });
1349
- function getReactRefreshRuntimeEsm() {
1350
- if (__refreshRuntimeCache) return __refreshRuntimeCache;
1440
+ function getReactRefreshRuntimeEsm(includeBoundaryHelpers = false) {
1441
+ if (__refreshRuntimeCache) {
1442
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1443
+ }
1351
1444
  let cjsPath;
1352
1445
  try {
1353
1446
  const pkgPath = __require.resolve("react-refresh/package.json");
@@ -1382,7 +1475,7 @@ export const findAffectedHostInstances = __rt.findAffectedHostInstances;
1382
1475
  export const collectCustomHooksForSignature = __rt.collectCustomHooksForSignature;
1383
1476
  export default __rt;
1384
1477
  `;
1385
- return __refreshRuntimeCache;
1478
+ return includeBoundaryHelpers ? __refreshRuntimeCache + REACT_REFRESH_BOUNDARY_HELPERS : __refreshRuntimeCache;
1386
1479
  }
1387
1480
  function buildReactRefreshWrapper(moduleUrl, transformedCode) {
1388
1481
  const urlLit = JSON.stringify(moduleUrl);
@@ -1408,22 +1501,40 @@ window.$RefreshReg$ = prevRefreshReg;
1408
1501
  window.$RefreshSig$ = prevRefreshSig;
1409
1502
 
1410
1503
  if (__nasti_hot__) {
1411
- __nasti_hot__.accept(() => {
1412
- clearTimeout(window.__nasti_refresh_timer__);
1413
- window.__nasti_refresh_timer__ = setTimeout(() => {
1414
- RefreshRuntime.performReactRefresh();
1415
- }, 30);
1504
+ let __nasti_current_exports__;
1505
+ __nasti_hot__.accept((nextExports) => {
1506
+ if (!nextExports) return;
1507
+ if (!__nasti_current_exports__) {
1508
+ __nasti_hot__.invalidate('Could not Fast Refresh (previous exports unavailable)');
1509
+ return;
1510
+ }
1511
+ const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(
1512
+ ${urlLit},
1513
+ __nasti_current_exports__,
1514
+ nextExports,
1515
+ );
1516
+ if (invalidateMessage) __nasti_hot__.invalidate(invalidateMessage);
1517
+ });
1518
+ RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
1519
+ __nasti_current_exports__ = currentExports;
1520
+ RefreshRuntime.registerExportsForReactRefresh(${urlLit}, currentExports);
1416
1521
  });
1417
1522
  }
1418
1523
  `;
1419
1524
  }
1420
1525
  function injectImportMetaHot(code, moduleUrl) {
1421
- if (!/\bimport\.meta\.hot\b/.test(code)) return code;
1526
+ const hotRE = /\bimport\.meta\.hot\b/g;
1527
+ const matches = [...maskStringsAndComments(code).matchAll(hotRE)];
1528
+ if (matches.length === 0) return code;
1529
+ for (const match of matches.reverse()) {
1530
+ const start = match.index;
1531
+ code = code.slice(0, start) + "__nasti_hot__" + code.slice(start + match[0].length);
1532
+ }
1422
1533
  const urlLit = JSON.stringify(moduleUrl);
1423
1534
  const header = `import { createHotContext as __nasti_createHotContext__ } from "/@nasti/client";
1424
1535
  const __nasti_hot__ = __nasti_createHotContext__(${urlLit});
1425
1536
  `;
1426
- return header + code.replace(/\bimport\.meta\.hot\b/g, "__nasti_hot__");
1537
+ return header + code;
1427
1538
  }
1428
1539
  function transformMiddleware(ctx) {
1429
1540
  ctx.envDefine = buildEnvDefine(
@@ -1499,13 +1610,14 @@ function transformMiddleware(ctx) {
1499
1610
  }
1500
1611
  async function transformRequest(url, ctx) {
1501
1612
  const { config, pluginContainer, moduleGraph } = ctx;
1613
+ url = removeTimestampQuery(url);
1502
1614
  const cleanReqUrl = url.split("?")[0];
1503
1615
  const cached2 = moduleGraph.getModuleByUrl(url);
1504
1616
  if (cached2?.transformResult) {
1505
1617
  return cached2.transformResult;
1506
1618
  }
1507
1619
  if (cleanReqUrl === "/@react-refresh") {
1508
- return { code: getReactRefreshRuntimeEsm() };
1620
+ return { code: getReactRefreshRuntimeEsm(true) };
1509
1621
  }
1510
1622
  if (cleanReqUrl.startsWith("/@modules/") && url.includes("?")) {
1511
1623
  const idParam = new URLSearchParams(url.slice(url.indexOf("?") + 1)).get("id");
@@ -1541,6 +1653,8 @@ async function transformRequest(url, ctx) {
1541
1653
  }
1542
1654
  const rawQuery = url.includes("?") ? url.slice(url.indexOf("?") + 1) : "";
1543
1655
  if (rawQuery && !/^t=\d+$/.test(rawQuery)) {
1656
+ const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1657
+ const transformVersion2 = mod2.invalidationVersion;
1544
1658
  const loaded = await pluginContainer.load(url);
1545
1659
  if (loaded != null) {
1546
1660
  let code2 = typeof loaded === "string" ? loaded : loaded.code;
@@ -1548,16 +1662,28 @@ async function transformRequest(url, ctx) {
1548
1662
  if (transformed != null) {
1549
1663
  code2 = typeof transformed === "string" ? transformed : transformed.code;
1550
1664
  }
1551
- const mod2 = await moduleGraph.ensureEntryFromUrl(url);
1552
- moduleGraph.registerModule(mod2, cleanReqUrl);
1553
- code2 = injectImportMetaHot(code2, url);
1665
+ const parentFile = resolveUrlToFile(cleanReqUrl, config.root) ?? cleanReqUrl;
1666
+ moduleGraph.registerModule(mod2, parentFile);
1667
+ const hotInfo2 = rewriteHotAcceptDeps(code2, config, parentFile);
1668
+ code2 = injectImportMetaHot(hotInfo2.code, url);
1554
1669
  code2 = replaceEnvInCode(code2, ctx.envDefine ?? buildEnvDefine(
1555
1670
  loadEnv(config.mode, config.root, config.envPrefix),
1556
1671
  config.mode
1557
1672
  ));
1558
- code2 = rewriteImports(code2, config, cleanReqUrl);
1673
+ const importedUrls2 = /* @__PURE__ */ new Set();
1674
+ code2 = rewriteImports(code2, config, parentFile, importedUrls2, moduleGraph);
1675
+ const pruned2 = await moduleGraph.updateModuleInfo(
1676
+ mod2,
1677
+ importedUrls2,
1678
+ hotInfo2.acceptedUrls,
1679
+ hotInfo2.isSelfAccepting,
1680
+ transformVersion2
1681
+ );
1559
1682
  const transformResult2 = { code: code2 };
1560
- mod2.transformResult = transformResult2;
1683
+ if (pruned2) {
1684
+ if (pruned2.size > 0) ctx.onPrune?.([...pruned2].map((item) => item.url));
1685
+ mod2.transformResult = transformResult2;
1686
+ }
1561
1687
  return transformResult2;
1562
1688
  }
1563
1689
  }
@@ -1565,6 +1691,7 @@ async function transformRequest(url, ctx) {
1565
1691
  if (!filePath || !import_node_fs4.default.existsSync(filePath)) return null;
1566
1692
  const mod = await moduleGraph.ensureEntryFromUrl(url);
1567
1693
  moduleGraph.registerModule(mod, filePath);
1694
+ const transformVersion = mod.invalidationVersion;
1568
1695
  if (cleanReqUrl.startsWith("/@modules/")) {
1569
1696
  const code2 = await bundlePackageAsEsm(filePath, config.root);
1570
1697
  const transformResult2 = { code: code2 };
@@ -1591,9 +1718,10 @@ async function transformRequest(url, ctx) {
1591
1718
  if (useRefresh) {
1592
1719
  code = buildReactRefreshWrapper(stableUrl, code);
1593
1720
  wrappedWithRefresh = true;
1594
- mod.isSelfAccepting = true;
1595
1721
  }
1596
1722
  }
1723
+ const hotInfo = rewriteHotAcceptDeps(code, config, filePath);
1724
+ code = hotInfo.code;
1597
1725
  if (!wrappedWithRefresh) {
1598
1726
  code = injectImportMetaHot(code, stableUrl);
1599
1727
  }
@@ -1602,9 +1730,20 @@ async function transformRequest(url, ctx) {
1602
1730
  config.mode
1603
1731
  );
1604
1732
  code = replaceEnvInCode(code, envDefine);
1605
- code = rewriteImports(code, config, filePath);
1733
+ const importedUrls = /* @__PURE__ */ new Set();
1734
+ code = rewriteImports(code, config, filePath, importedUrls, moduleGraph);
1735
+ const pruned = await moduleGraph.updateModuleInfo(
1736
+ mod,
1737
+ importedUrls,
1738
+ hotInfo.acceptedUrls,
1739
+ wrappedWithRefresh || hotInfo.isSelfAccepting,
1740
+ transformVersion
1741
+ );
1606
1742
  const transformResult = { code };
1607
- mod.transformResult = transformResult;
1743
+ if (pruned) {
1744
+ if (pruned.size > 0) ctx.onPrune?.([...pruned].map((item) => item.url));
1745
+ mod.transformResult = transformResult;
1746
+ }
1608
1747
  return transformResult;
1609
1748
  }
1610
1749
  async function loadVirtualModule(spec, ctx) {
@@ -1809,49 +1948,202 @@ async function injectCjsNamedExports(code, entryFile) {
1809
1948
  return code;
1810
1949
  }
1811
1950
  }
1812
- function rewriteImports(code, config, filePath) {
1951
+ function rewriteImports(code, config, filePath, importedUrls, moduleGraph) {
1952
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
1953
+ const transformSpec = (spec) => {
1954
+ const resolved = removeTimestampQuery(resolveSpec(spec));
1955
+ importedUrls?.add(resolved);
1956
+ const timestamp = moduleGraph?.getModuleByUrl(resolved)?.lastHMRTimestamp ?? 0;
1957
+ return timestamp > 0 ? appendTimestampQuery(resolved, timestamp) : resolved;
1958
+ };
1959
+ return code.replace(
1960
+ /\bfrom\s+(['"])([^'"]+)\1/g,
1961
+ (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1962
+ ).replace(
1963
+ /\bimport\s+(['"])([^'"]+)\1/g,
1964
+ (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1965
+ ).replace(
1966
+ /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1967
+ (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1968
+ );
1969
+ }
1970
+ function createModuleSpecifierResolver(config, filePath) {
1813
1971
  const root = config.root;
1814
1972
  const fileDir = import_node_path4.default.dirname(filePath);
1815
1973
  const aliasEntries = Object.entries(config.resolve.alias).sort(
1816
1974
  ([a], [b]) => b.length - a.length
1817
1975
  );
1818
1976
  const toRootUrl = (abs) => "/" + import_node_path4.default.relative(root, abs).replace(/\\/g, "/");
1819
- const transformSpec = (spec) => {
1820
- const suffixMatch = spec.match(/[?#].*$/);
1977
+ return (specifier) => {
1978
+ const suffixMatch = specifier.match(/[?#].*$/);
1821
1979
  const suffix = suffixMatch ? suffixMatch[0] : "";
1822
- const baseSpec = suffix ? spec.slice(0, -suffix.length) : spec;
1980
+ const baseSpec = suffix ? specifier.slice(0, -suffix.length) : specifier;
1823
1981
  for (const [key, value] of aliasEntries) {
1824
1982
  if (baseSpec === key || baseSpec.startsWith(key + "/")) {
1825
1983
  const aliasBase = resolveAliasTarget(value, root);
1826
1984
  const sub = baseSpec.slice(key.length).replace(/^\//, "");
1827
1985
  const target = sub ? import_node_path4.default.join(aliasBase, sub) : aliasBase;
1828
1986
  const resolved = tryResolveDiskPath(target);
1829
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1987
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1830
1988
  }
1831
1989
  }
1832
1990
  if (baseSpec.startsWith("./") || baseSpec.startsWith("../")) {
1833
- const target = import_node_path4.default.resolve(fileDir, baseSpec);
1834
- const resolved = tryResolveDiskPath(target);
1835
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1991
+ const resolved = tryResolveDiskPath(import_node_path4.default.resolve(fileDir, baseSpec));
1992
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1836
1993
  }
1837
1994
  if (baseSpec.startsWith("/") && !baseSpec.startsWith("/@")) {
1838
- const target = import_node_path4.default.join(root, baseSpec.replace(/^\//, ""));
1839
- const resolved = tryResolveDiskPath(target);
1840
- return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : spec;
1995
+ const resolved = tryResolveDiskPath(import_node_path4.default.join(root, baseSpec.replace(/^\//, "")));
1996
+ return resolved && isUnderRoot(resolved, root) ? toRootUrl(resolved) + suffix : specifier;
1841
1997
  }
1842
- if (baseSpec.startsWith("/")) return spec;
1843
- return `/@modules/${spec}`;
1998
+ if (baseSpec.startsWith("/")) return specifier;
1999
+ return `/@modules/${specifier}`;
1844
2000
  };
1845
- return code.replace(
1846
- /\bfrom\s+(['"])([^'"]+)\1/g,
1847
- (_m, q, s) => `from ${q}${transformSpec(s)}${q}`
1848
- ).replace(
1849
- /\bimport\s+(['"])([^'"]+)\1/g,
1850
- (_m, q, s) => `import ${q}${transformSpec(s)}${q}`
1851
- ).replace(
1852
- /\bimport\s*\(\s*(['"])([^'"]+)\1\s*\)/g,
1853
- (_m, q, s) => `import(${q}${transformSpec(s)}${q})`
1854
- );
2001
+ }
2002
+ function rewriteHotAcceptDeps(code, config, filePath) {
2003
+ const acceptedUrls = /* @__PURE__ */ new Set();
2004
+ const edits = [];
2005
+ const resolveSpec = createModuleSpecifierResolver(config, filePath);
2006
+ const acceptRE = /(?:\bimport\.meta\.hot|\b__nasti_hot__)(?:(?:\?\.)|\.)accept\s*\(/g;
2007
+ const searchableCode = maskStringsAndComments(code);
2008
+ let isSelfAccepting = false;
2009
+ let match;
2010
+ while (match = acceptRE.exec(searchableCode)) {
2011
+ let cursor = match.index + match[0].length;
2012
+ const skipTrivia = () => {
2013
+ while (cursor < code.length) {
2014
+ if (/\s/.test(code[cursor])) {
2015
+ cursor++;
2016
+ continue;
2017
+ }
2018
+ if (code[cursor] === "/" && code[cursor + 1] === "/") {
2019
+ cursor += 2;
2020
+ while (cursor < code.length && code[cursor] !== "\n") cursor++;
2021
+ continue;
2022
+ }
2023
+ if (code[cursor] === "/" && code[cursor + 1] === "*") {
2024
+ cursor += 2;
2025
+ while (cursor < code.length && !(code[cursor] === "*" && code[cursor + 1] === "/")) cursor++;
2026
+ cursor += 2;
2027
+ continue;
2028
+ }
2029
+ break;
2030
+ }
2031
+ };
2032
+ skipTrivia();
2033
+ const first = code[cursor];
2034
+ if (!first || first === ")" || first !== "[" && first !== "'" && first !== '"' && first !== "`") {
2035
+ isSelfAccepting = true;
2036
+ continue;
2037
+ }
2038
+ const readLiteral = () => {
2039
+ const quote = code[cursor];
2040
+ if (quote !== "'" && quote !== '"' && quote !== "`") return;
2041
+ const start = cursor;
2042
+ cursor++;
2043
+ let raw = "";
2044
+ while (cursor < code.length) {
2045
+ const char = code[cursor];
2046
+ if (char === "\\") {
2047
+ raw += code[cursor + 1] ?? "";
2048
+ cursor += 2;
2049
+ continue;
2050
+ }
2051
+ if (char === quote) {
2052
+ cursor++;
2053
+ const resolved = removeTimestampQuery(resolveSpec(raw));
2054
+ acceptedUrls.add(resolved);
2055
+ edits.push({ start, end: cursor, value: JSON.stringify(resolved) });
2056
+ return;
2057
+ }
2058
+ if (quote === "`" && char === "$" && code[cursor + 1] === "{") return;
2059
+ raw += char;
2060
+ cursor++;
2061
+ }
2062
+ };
2063
+ if (first === "[") {
2064
+ cursor++;
2065
+ while (cursor < code.length) {
2066
+ skipTrivia();
2067
+ if (code[cursor] === ",") {
2068
+ cursor++;
2069
+ skipTrivia();
2070
+ }
2071
+ if (code[cursor] === "]") break;
2072
+ const before = cursor;
2073
+ readLiteral();
2074
+ if (cursor === before) break;
2075
+ }
2076
+ } else {
2077
+ readLiteral();
2078
+ }
2079
+ }
2080
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
2081
+ code = code.slice(0, edit.start) + edit.value + code.slice(edit.end);
2082
+ }
2083
+ return { code, acceptedUrls, isSelfAccepting };
2084
+ }
2085
+ function maskStringsAndComments(code) {
2086
+ const masked = code.split("");
2087
+ let state = "code";
2088
+ const isRegexStart = (index2) => {
2089
+ let previous = index2 - 1;
2090
+ while (previous >= 0 && /\s/.test(code[previous])) previous--;
2091
+ return previous < 0 || "=(:,!&|?{};[]+-*%^~<>".includes(code[previous]);
2092
+ };
2093
+ for (let i = 0; i < code.length; i++) {
2094
+ const char = code[i];
2095
+ const next = code[i + 1];
2096
+ if (state === "code") {
2097
+ if (char === "'") state = "single";
2098
+ else if (char === '"') state = "double";
2099
+ else if (char === "`") state = "template";
2100
+ else if (char === "/" && next === "/") state = "line-comment";
2101
+ else if (char === "/" && next === "*") state = "block-comment";
2102
+ else if (char === "/" && isRegexStart(i)) state = "regex";
2103
+ else continue;
2104
+ masked[i] = " ";
2105
+ continue;
2106
+ }
2107
+ if (state === "line-comment") {
2108
+ if (char === "\n") {
2109
+ state = "code";
2110
+ } else {
2111
+ masked[i] = " ";
2112
+ }
2113
+ continue;
2114
+ }
2115
+ if (state === "block-comment") {
2116
+ masked[i] = char === "\n" ? "\n" : " ";
2117
+ if (char === "*" && next === "/") {
2118
+ masked[i + 1] = " ";
2119
+ i++;
2120
+ state = "code";
2121
+ }
2122
+ continue;
2123
+ }
2124
+ if (state === "regex" || state === "regex-class") {
2125
+ masked[i] = char === "\n" ? "\n" : " ";
2126
+ if (char === "\\") {
2127
+ if (i + 1 < code.length) masked[++i] = " ";
2128
+ } else if (state === "regex" && char === "[") {
2129
+ state = "regex-class";
2130
+ } else if (state === "regex-class" && char === "]") {
2131
+ state = "regex";
2132
+ } else if (state === "regex" && char === "/") {
2133
+ state = "code";
2134
+ }
2135
+ continue;
2136
+ }
2137
+ masked[i] = char === "\n" ? "\n" : " ";
2138
+ if (char === "\\") {
2139
+ if (i + 1 < code.length) masked[++i] = " ";
2140
+ continue;
2141
+ }
2142
+ if (state === "single" && char === "'" || state === "double" && char === '"' || state === "template" && char === "`") {
2143
+ state = "code";
2144
+ }
2145
+ }
2146
+ return masked.join("");
1855
2147
  }
1856
2148
  function resolveAliasTarget(value, root) {
1857
2149
  if (import_node_path4.default.isAbsolute(value) && import_node_fs4.default.existsSync(value)) return value;
@@ -1876,6 +2168,12 @@ function isUnderRoot(abs, root) {
1876
2168
  const rel = import_node_path4.default.relative(root, abs);
1877
2169
  return !!rel && !rel.startsWith("..") && !import_node_path4.default.isAbsolute(rel);
1878
2170
  }
2171
+ function appendTimestampQuery(url, timestamp) {
2172
+ const hashIndex = url.indexOf("#");
2173
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : "";
2174
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2175
+ return `${withoutHash}${withoutHash.includes("?") ? "&" : "?"}t=${timestamp}${hash}`;
2176
+ }
1879
2177
  function externalSpecToModuleUrl(spec, baseDir, root) {
1880
2178
  const resolved = resolveNodeModule(baseDir, spec);
1881
2179
  if (!resolved) return `/@modules/${spec}`;
@@ -2019,30 +2317,29 @@ function isModuleRequest(url) {
2019
2317
  function getHmrClientCode() {
2020
2318
  return `
2021
2319
  // Nasti HMR Client
2022
- const socket = new WebSocket(\`ws://\${location.host}\`, 'nasti-hmr');
2320
+ const socketProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
2321
+ const socket = new WebSocket(socketProtocol + '://' + location.host, 'nasti-hmr');
2023
2322
  const hotModulesMap = new Map();
2024
2323
  const disposeMap = new Map();
2025
2324
  const pruneMap = new Map();
2325
+ const dataMap = new Map();
2326
+ let updateQueue = [];
2327
+ let pendingUpdateQueue = false;
2026
2328
 
2027
2329
  socket.addEventListener('message', async ({ data }) => {
2028
2330
  const payload = JSON.parse(data);
2029
2331
  switch (payload.type) {
2030
2332
  case 'connected':
2031
- console.log('[nasti] connected.');
2333
+ console.debug('[nasti] connected.');
2032
2334
  clearErrorOverlay();
2033
2335
  break;
2034
2336
  case 'update':
2035
2337
  try {
2036
- await Promise.all(payload.updates.map((update) => {
2037
- if (update.type === 'js-update') {
2038
- return fetchUpdate(update);
2039
- } else if (update.type === 'css-update') {
2040
- return updateCss(update.path);
2041
- }
2042
- }));
2338
+ // CSS \u5728 unbundled \u6A21\u5F0F\u4E0B\u4E5F\u662F\u4F1A\u6CE8\u5165 <style> \u7684 JS \u6A21\u5757\uFF0C\u548C\u666E\u901A JS
2339
+ // \u4E00\u6837\u91CD\u65B0 import \u624D\u80FD\u6267\u884C dispose/accept \u5E76\u4FDD\u6301\u9875\u9762\u72B6\u6001\u3002
2340
+ await Promise.all(payload.updates.map(queueUpdate));
2043
2341
  clearErrorOverlay();
2044
- console.log('[nasti] HMR update complete, reloading page');
2045
- location.reload();
2342
+ console.debug('[nasti] HMR update complete.');
2046
2343
  } catch (err) {
2047
2344
  console.error('[nasti] HMR update failed:', err);
2048
2345
  showErrorOverlay(err);
@@ -2053,10 +2350,17 @@ socket.addEventListener('message', async ({ data }) => {
2053
2350
  location.reload();
2054
2351
  break;
2055
2352
  case 'prune':
2056
- payload.paths.forEach((p) => {
2057
- const cb = pruneMap.get(p);
2058
- if (cb) cb();
2059
- });
2353
+ await Promise.all(payload.paths.map(async (path) => {
2354
+ const data = dataMap.get(path);
2355
+ const dispose = disposeMap.get(path);
2356
+ const prune = pruneMap.get(path);
2357
+ if (dispose) await dispose(data);
2358
+ if (prune) await prune(data);
2359
+ hotModulesMap.delete(path);
2360
+ disposeMap.delete(path);
2361
+ pruneMap.delete(path);
2362
+ dataMap.delete(path);
2363
+ }));
2060
2364
  break;
2061
2365
  case 'error':
2062
2366
  console.error('[nasti] error:', payload.err.message);
@@ -2065,33 +2369,64 @@ socket.addEventListener('message', async ({ data }) => {
2065
2369
  }
2066
2370
  });
2067
2371
 
2068
- // \u81EA\u52A8\u91CD\u8FDE\uFF08\u65AD\u7EBF\u65F6\u6307\u6570\u9000\u907F\uFF09
2372
+ // \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
2069
2373
  let reconnectTimer = 0;
2070
2374
  socket.addEventListener('close', () => {
2071
2375
  clearTimeout(reconnectTimer);
2072
2376
  reconnectTimer = setTimeout(() => location.reload(), 1000);
2073
2377
  });
2074
2378
 
2379
+ /**
2380
+ * \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
2381
+ * \u6539\u53D8\u6A21\u5757\u5E94\u7528\u987A\u5E8F\u3002\u8FD9\u4E0E Vite HMRClient \u7684 fetch/apply \u4E24\u9636\u6BB5\u4E00\u81F4\u3002
2382
+ */
2383
+ async function queueUpdate(update) {
2384
+ updateQueue.push(fetchUpdate(update));
2385
+ if (pendingUpdateQueue) return;
2386
+
2387
+ pendingUpdateQueue = true;
2388
+ await Promise.resolve();
2389
+ pendingUpdateQueue = false;
2390
+ const loading = updateQueue;
2391
+ updateQueue = [];
2392
+ const applyUpdates = await Promise.all(loading);
2393
+ for (const apply of applyUpdates) {
2394
+ if (apply) apply();
2395
+ }
2396
+ }
2397
+
2075
2398
  async function fetchUpdate(update) {
2076
2399
  const mod = hotModulesMap.get(update.path);
2077
- // \u5148\u8DD1 dispose\uFF08\u7ED9\u6A21\u5757\u673A\u4F1A\u6E05\u7406\u526F\u4F5C\u7528\uFF09
2078
- const dispose = disposeMap.get(update.path);
2079
- if (dispose) dispose();
2400
+ // \u5C1A\u672A\u5728\u5F53\u524D\u9875\u9762\u52A0\u8F7D\u7684\u52A8\u6001\u6A21\u5757\u4E0D\u9700\u8981\u66F4\u65B0\u3002
2401
+ if (!mod) return;
2080
2402
 
2081
- const newMod = await import(update.acceptedPath + '?t=' + update.timestamp);
2082
- if (mod) {
2083
- // \u590D\u5236\u56DE\u8C03\u6570\u7EC4\u907F\u514D\u56DE\u8C03\u5185\u90E8\u53C8\u4FEE\u6539 hotModulesMap \u9020\u6210\u8FED\u4EE3\u5F02\u5E38
2084
- [...mod.callbacks].forEach((cb) => cb(newMod));
2085
- }
2403
+ // \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
2404
+ const qualifiedCallbacks = mod.callbacks.filter(({ deps }) =>
2405
+ deps.includes(update.acceptedPath)
2406
+ );
2407
+ const isSelfUpdate = update.path === update.acceptedPath;
2408
+ if (!isSelfUpdate && qualifiedCallbacks.length === 0) return;
2409
+
2410
+ const dispose = disposeMap.get(update.acceptedPath);
2411
+ if (dispose) await dispose(dataMap.get(update.acceptedPath));
2412
+ const newMod = await import(appendTimestampQuery(update.acceptedPath, update.timestamp));
2413
+
2414
+ return () => {
2415
+ for (const { deps, fn } of qualifiedCallbacks) {
2416
+ fn(deps.map((dep) => dep === update.acceptedPath ? newMod : undefined));
2417
+ }
2418
+ const detail = isSelfUpdate
2419
+ ? update.path
2420
+ : update.acceptedPath + ' via ' + update.path;
2421
+ console.debug('[nasti] hot updated:', detail);
2422
+ };
2086
2423
  }
2087
2424
 
2088
- function updateCss(path) {
2089
- const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
2090
- if (el) {
2091
- return fetch(path + '?t=' + Date.now())
2092
- .then(r => r.text())
2093
- .then(css => { el.textContent = css; });
2094
- }
2425
+ function appendTimestampQuery(url, timestamp) {
2426
+ const hashIndex = url.indexOf('#');
2427
+ const hash = hashIndex >= 0 ? url.slice(hashIndex) : '';
2428
+ const withoutHash = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
2429
+ return withoutHash + (withoutHash.includes('?') ? '&' : '?') + 't=' + timestamp + hash;
2095
2430
  }
2096
2431
 
2097
2432
  function clearErrorOverlay() {
@@ -2119,23 +2454,30 @@ function showErrorOverlay(err) {
2119
2454
  document.body.appendChild(overlay);
2120
2455
  }
2121
2456
 
2122
- /**
2123
- * \u751F\u6210 import.meta.hot \u7684 hot context\u3002
2124
- * \u5173\u952E\u7EA6\u675F\uFF1A\u540C\u4E00 ownerPath \u7684 accept \u56DE\u8C03\u5FC5\u987B\u66FF\u6362\uFF08\u4E0D\u662F append\uFF09\u3002
2125
- * \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
2126
- * \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
2127
- */
2128
2457
  export function createHotContext(ownerPath) {
2458
+ if (!dataMap.has(ownerPath)) dataMap.set(ownerPath, {});
2459
+
2460
+ // \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
2461
+ const existing = hotModulesMap.get(ownerPath);
2462
+ if (existing) existing.callbacks = [];
2463
+
2464
+ const acceptDeps = (deps, callback = () => {}) => {
2465
+ const mod = hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] };
2466
+ mod.callbacks.push({ deps, fn: callback });
2467
+ hotModulesMap.set(ownerPath, mod);
2468
+ };
2469
+
2129
2470
  return {
2130
2471
  accept(deps, callback) {
2131
- // \u81EA\u63A5\u53D7: hot.accept() \u6216 hot.accept(callback)
2132
2472
  if (typeof deps === 'function' || deps === undefined) {
2133
- hotModulesMap.set(ownerPath, { callbacks: [deps || (() => {})] });
2134
- return;
2473
+ acceptDeps([ownerPath], ([mod]) => deps?.(mod));
2474
+ } else if (typeof deps === 'string') {
2475
+ acceptDeps([deps], ([mod]) => callback?.(mod));
2476
+ } else if (Array.isArray(deps)) {
2477
+ acceptDeps(deps, callback);
2478
+ } else {
2479
+ throw new Error('invalid hot.accept() usage');
2135
2480
  }
2136
- // \u4F9D\u8D56\u63A5\u53D7: hot.accept(deps, callback)\uFF0C\u591A\u6B21\u8C03\u7528\u8FFD\u52A0
2137
- const existing = hotModulesMap.get(ownerPath)?.callbacks ?? [];
2138
- hotModulesMap.set(ownerPath, { callbacks: [...existing, callback] });
2139
2481
  },
2140
2482
  prune(callback) {
2141
2483
  pruneMap.set(ownerPath, callback);
@@ -2146,12 +2488,12 @@ export function createHotContext(ownerPath) {
2146
2488
  invalidate() {
2147
2489
  location.reload();
2148
2490
  },
2149
- data: {},
2491
+ data: dataMap.get(ownerPath),
2150
2492
  };
2151
2493
  }
2152
2494
  `;
2153
2495
  }
2154
- var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2496
+ var import_node_path4, import_node_fs4, import_node_module, import_node_url2, import_picocolors3, import_meta, __dirname_esm, __require, __refreshRuntimeCache, REACT_REFRESH_BOUNDARY_HELPERS, REACT_REFRESH_GLOBAL_PREAMBLE, esmBundleCache, VALID_IDENT, RESOLVE_EXTENSIONS, ESM_CONDITIONS;
2155
2497
  var init_middleware = __esm({
2156
2498
  "src/server/middleware.ts"() {
2157
2499
  "use strict";
@@ -2163,10 +2505,74 @@ var init_middleware = __esm({
2163
2505
  init_transformer();
2164
2506
  init_html();
2165
2507
  init_env();
2508
+ init_url();
2166
2509
  import_meta = {};
2167
2510
  __dirname_esm = import_node_path4.default.dirname((0, import_node_url2.fileURLToPath)(import_meta.url));
2168
2511
  __require = (0, import_node_module.createRequire)(import_meta.url);
2169
2512
  __refreshRuntimeCache = null;
2513
+ REACT_REFRESH_BOUNDARY_HELPERS = `
2514
+ function __nastiIsPlainObject(obj) {
2515
+ return Object.prototype.toString.call(obj) === '[object Object]' &&
2516
+ (obj.constructor === Object || obj.constructor === undefined);
2517
+ }
2518
+ function __nastiIsCompoundComponent(type) {
2519
+ if (!__nastiIsPlainObject(type)) return false;
2520
+ for (const key in type) {
2521
+ if (!isLikelyComponentType(type[key])) return false;
2522
+ }
2523
+ return true;
2524
+ }
2525
+ export function registerExportsForReactRefresh(filename, moduleExports) {
2526
+ for (const key in moduleExports) {
2527
+ if (key === '__esModule') continue;
2528
+ const value = moduleExports[key];
2529
+ if (isLikelyComponentType(value)) {
2530
+ register(value, filename + ' export ' + key);
2531
+ } else if (__nastiIsCompoundComponent(value)) {
2532
+ for (const subKey in value) {
2533
+ register(value[subKey], filename + ' export ' + key + '-' + subKey);
2534
+ }
2535
+ }
2536
+ }
2537
+ }
2538
+ let __nastiRefreshTimer;
2539
+ function __nastiEnqueueRefresh() {
2540
+ clearTimeout(__nastiRefreshTimer);
2541
+ __nastiRefreshTimer = setTimeout(() => performReactRefresh(), 16);
2542
+ }
2543
+ function __nastiCheckExports(ignored, exports, predicate) {
2544
+ for (const key in exports) {
2545
+ if (ignored.includes(key)) continue;
2546
+ if (!predicate(key, exports[key])) return key;
2547
+ }
2548
+ return true;
2549
+ }
2550
+ export function validateRefreshBoundaryAndEnqueueUpdate(id, prevExports, nextExports) {
2551
+ const ignored = window.__getReactRefreshIgnoredExports?.({ id }) ?? [];
2552
+ if (__nastiCheckExports(ignored, prevExports, (key) => key in nextExports) !== true) {
2553
+ return 'Could not Fast Refresh (export removed)';
2554
+ }
2555
+ if (__nastiCheckExports(ignored, nextExports, (key) => key in prevExports) !== true) {
2556
+ return 'Could not Fast Refresh (new export)';
2557
+ }
2558
+ let hasExports = false;
2559
+ const compatible = __nastiCheckExports(ignored, nextExports, (key, value) => {
2560
+ hasExports = true;
2561
+ return isLikelyComponentType(value) ||
2562
+ __nastiIsCompoundComponent(value) ||
2563
+ prevExports[key] === value;
2564
+ });
2565
+ if (!hasExports) {
2566
+ return 'Could not Fast Refresh (no exports)';
2567
+ }
2568
+ if (compatible === true) {
2569
+ __nastiEnqueueRefresh();
2570
+ return;
2571
+ }
2572
+ return 'Could not Fast Refresh ("' + compatible + '" export is incompatible)';
2573
+ }
2574
+ export const __hmr_import = (module) => import(module);
2575
+ `;
2170
2576
  REACT_REFRESH_GLOBAL_PREAMBLE = `
2171
2577
  import RefreshRuntime from "/@react-refresh";
2172
2578
  RefreshRuntime.injectIntoGlobalHook(window);
@@ -2193,8 +2599,10 @@ async function handleFileChange(file, server) {
2193
2599
  }
2194
2600
  const updates = [];
2195
2601
  const timestamp = Date.now();
2602
+ const graph = moduleGraph;
2603
+ const invalidatedModules = /* @__PURE__ */ new Set();
2196
2604
  for (const mod of mods) {
2197
- moduleGraph.invalidateModule(mod);
2605
+ graph.invalidateModuleAndImporters(mod, timestamp, invalidatedModules);
2198
2606
  const ctx = {
2199
2607
  file,
2200
2608
  timestamp,
@@ -2212,19 +2620,25 @@ async function handleFileChange(file, server) {
2212
2620
  }
2213
2621
  }
2214
2622
  for (const affected of affectedModules) {
2215
- const boundaries = moduleGraph.getHmrBoundaries(affected);
2623
+ graph.invalidateModuleAndImporters(affected, timestamp, invalidatedModules);
2624
+ const boundaries = graph.getHmrBoundaries(affected);
2216
2625
  if (boundaries.length === 0) {
2217
2626
  logger.info(import_picocolors4.default.green("page reload ") + import_picocolors4.default.dim(shortFile), { timestamp: true });
2218
2627
  ws.send({ type: "full-reload", path: relativePath });
2219
2628
  return;
2220
2629
  }
2221
- for (const { boundary } of boundaries) {
2222
- updates.push({
2630
+ for (const { boundary, acceptedVia } of boundaries) {
2631
+ const update = {
2223
2632
  type: boundary.type === "css" ? "css-update" : "js-update",
2224
2633
  path: boundary.url,
2225
- acceptedPath: affected.url,
2634
+ acceptedPath: acceptedVia.url,
2226
2635
  timestamp
2227
- });
2636
+ };
2637
+ if (!updates.some(
2638
+ (existing) => existing.type === update.type && existing.path === update.path && existing.acceptedPath === update.acceptedPath
2639
+ )) {
2640
+ updates.push(update);
2641
+ }
2228
2642
  }
2229
2643
  }
2230
2644
  }
@@ -2295,6 +2709,7 @@ function resolvePlugin(config) {
2295
2709
  }
2296
2710
  if (!source.startsWith("/") && !source.startsWith(".")) {
2297
2711
  if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
2712
+ if (config.command === "build") return null;
2298
2713
  try {
2299
2714
  const resolved = require2.resolve(source, {
2300
2715
  paths: [importer ? import_node_path6.default.dirname(importer) : config.root]
@@ -2387,27 +2802,27 @@ var require_process = __commonJS({
2387
2802
  var require_filesystem = __commonJS({
2388
2803
  "node_modules/detect-libc/lib/filesystem.js"(exports2, module2) {
2389
2804
  "use strict";
2390
- var fs12 = require("fs");
2805
+ var fs13 = require("fs");
2391
2806
  var LDD_PATH = "/usr/bin/ldd";
2392
2807
  var SELF_PATH = "/proc/self/exe";
2393
2808
  var MAX_LENGTH = 2048;
2394
- var readFileSync = (path17) => {
2395
- const fd = fs12.openSync(path17, "r");
2809
+ var readFileSync = (path18) => {
2810
+ const fd = fs13.openSync(path18, "r");
2396
2811
  const buffer = Buffer.alloc(MAX_LENGTH);
2397
- const bytesRead = fs12.readSync(fd, buffer, 0, MAX_LENGTH, 0);
2398
- fs12.close(fd, () => {
2812
+ const bytesRead = fs13.readSync(fd, buffer, 0, MAX_LENGTH, 0);
2813
+ fs13.close(fd, () => {
2399
2814
  });
2400
2815
  return buffer.subarray(0, bytesRead);
2401
2816
  };
2402
- var readFile = (path17) => new Promise((resolve, reject) => {
2403
- fs12.open(path17, "r", (err, fd) => {
2817
+ var readFile = (path18) => new Promise((resolve, reject) => {
2818
+ fs13.open(path18, "r", (err, fd) => {
2404
2819
  if (err) {
2405
2820
  reject(err);
2406
2821
  } else {
2407
2822
  const buffer = Buffer.alloc(MAX_LENGTH);
2408
- fs12.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
2823
+ fs13.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
2409
2824
  resolve(buffer.subarray(0, bytesRead));
2410
- fs12.close(fd, () => {
2825
+ fs13.close(fd, () => {
2411
2826
  });
2412
2827
  });
2413
2828
  }
@@ -2519,11 +2934,11 @@ var require_detect_libc = __commonJS({
2519
2934
  }
2520
2935
  return null;
2521
2936
  };
2522
- var familyFromInterpreterPath = (path17) => {
2523
- if (path17) {
2524
- if (path17.includes("/ld-musl-")) {
2937
+ var familyFromInterpreterPath = (path18) => {
2938
+ if (path18) {
2939
+ if (path18.includes("/ld-musl-")) {
2525
2940
  return MUSL;
2526
- } else if (path17.includes("/ld-linux-")) {
2941
+ } else if (path18.includes("/ld-linux-")) {
2527
2942
  return GLIBC;
2528
2943
  }
2529
2944
  }
@@ -2570,8 +2985,8 @@ var require_detect_libc = __commonJS({
2570
2985
  cachedFamilyInterpreter = null;
2571
2986
  try {
2572
2987
  const selfContent = await readFile(SELF_PATH);
2573
- const path17 = interpreterPath(selfContent);
2574
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
2988
+ const path18 = interpreterPath(selfContent);
2989
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2575
2990
  } catch (e) {
2576
2991
  }
2577
2992
  return cachedFamilyInterpreter;
@@ -2583,8 +2998,8 @@ var require_detect_libc = __commonJS({
2583
2998
  cachedFamilyInterpreter = null;
2584
2999
  try {
2585
3000
  const selfContent = readFileSync(SELF_PATH);
2586
- const path17 = interpreterPath(selfContent);
2587
- cachedFamilyInterpreter = familyFromInterpreterPath(path17);
3001
+ const path18 = interpreterPath(selfContent);
3002
+ cachedFamilyInterpreter = familyFromInterpreterPath(path18);
2588
3003
  } catch (e) {
2589
3004
  }
2590
3005
  return cachedFamilyInterpreter;
@@ -3610,8 +4025,8 @@ function vuePlugin(config) {
3610
4025
  let descriptor = descriptorCache.get(filePath);
3611
4026
  if (!descriptor) {
3612
4027
  try {
3613
- const fs12 = await import("fs");
3614
- const source = fs12.readFileSync(filePath, "utf-8");
4028
+ const fs13 = await import("fs");
4029
+ const source = fs13.readFileSync(filePath, "utf-8");
3615
4030
  const parsed = sfc.parse(source, { filename: filePath });
3616
4031
  if (parsed.errors.length) return null;
3617
4032
  descriptor = parsed.descriptor;
@@ -3750,16 +4165,27 @@ var init_vue = __esm({
3750
4165
  // src/plugins/builtins.ts
3751
4166
  function resolvePluginList(config, userPlugins, opts = {}) {
3752
4167
  const isServe = config.command === "serve";
4168
+ let environmentOptions;
4169
+ if (opts.environmentName) {
4170
+ environmentOptions = config.environments[opts.environmentName];
4171
+ if (!environmentOptions) {
4172
+ throw new Error(
4173
+ `[nasti] unknown environment "${opts.environmentName}" \u2014 declare it in config.environments`
4174
+ );
4175
+ }
4176
+ }
4177
+ const pluginConfig = environmentOptions ? { ...config, resolve: environmentOptions.resolve, build: environmentOptions.build } : config;
4178
+ const consumer = opts.consumer ?? environmentOptions?.consumer;
3753
4179
  return [
3754
4180
  // vuePlugin 排最前(enforce: 'pre' 语义):.vue 先编译成 JS 再走后续管道
3755
- ...config.framework === "vue" ? [vuePlugin(config)] : [],
3756
- resolvePlugin(config),
3757
- cssPlugin(config, opts.cssEngine, opts.consumer),
3758
- assetsPlugin(config),
3759
- ...isServe ? [htmlPlugin(config)] : [],
4181
+ ...config.framework === "vue" ? [vuePlugin(pluginConfig)] : [],
4182
+ resolvePlugin(pluginConfig),
4183
+ cssPlugin(pluginConfig, opts.cssEngine, consumer),
4184
+ assetsPlugin(pluginConfig),
4185
+ ...isServe ? [htmlPlugin(pluginConfig)] : [],
3760
4186
  ...userPlugins,
3761
4187
  // cssPostPlugin 最后(enforce: 'post' 语义):renderChunk 聚合抽取
3762
- ...!isServe && opts.cssEngine ? [cssPostPlugin(config, opts.cssEngine)] : []
4188
+ ...!isServe && opts.cssEngine ? [cssPostPlugin(pluginConfig, opts.cssEngine)] : []
3763
4189
  ];
3764
4190
  }
3765
4191
  var init_builtins = __esm({
@@ -4083,6 +4509,135 @@ var init_reporter = __esm({
4083
4509
  }
4084
4510
  });
4085
4511
 
4512
+ // src/core/build-app-context.ts
4513
+ function createBuildAppContext(config, results) {
4514
+ const output = [];
4515
+ const emitted = /* @__PURE__ */ new Set();
4516
+ const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4517
+ let environmentArtifacts;
4518
+ return {
4519
+ config,
4520
+ results,
4521
+ get output() {
4522
+ return Object.freeze([...output]);
4523
+ },
4524
+ getResult(environmentName) {
4525
+ return results[environmentName];
4526
+ },
4527
+ getArtifact(environmentName, fileName) {
4528
+ const normalized = normalizeEnvironmentFileName(fileName);
4529
+ return results[environmentName]?.output.find(
4530
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalized
4531
+ );
4532
+ },
4533
+ getEntry(environmentName, entryName) {
4534
+ const result = results[environmentName];
4535
+ const fileName = result?.entries?.[entryName];
4536
+ if (!fileName) return void 0;
4537
+ return result.output.find(
4538
+ (artifact) => normalizeEnvironmentFileName(artifact.fileName) === normalizeEnvironmentFileName(fileName)
4539
+ );
4540
+ },
4541
+ getManifest(environmentName) {
4542
+ return results[environmentName]?.manifest;
4543
+ },
4544
+ emitFile(file) {
4545
+ const fileName = normalizeAppFileName(file.fileName);
4546
+ const collisionKey = artifactCollisionKey(fileName);
4547
+ if (emitted.has(collisionKey)) {
4548
+ throw new Error(`[nasti] app artifact already emitted: ${fileName}`);
4549
+ }
4550
+ environmentArtifacts ??= collectEnvironmentArtifacts(config, results, outDir);
4551
+ if (environmentArtifacts.has(collisionKey)) {
4552
+ throw new Error(`[nasti] app artifact conflicts with environment output: ${fileName}`);
4553
+ }
4554
+ const target = import_node_path12.default.resolve(outDir, ...fileName.split("/"));
4555
+ const relative = import_node_path12.default.relative(outDir, target);
4556
+ if (relative.startsWith("..") || import_node_path12.default.isAbsolute(relative)) {
4557
+ throw new Error(`[nasti] app artifact must stay inside build.outDir: ${file.fileName}`);
4558
+ }
4559
+ assertNoSymlinkComponents(outDir, fileName);
4560
+ import_node_fs9.default.mkdirSync(import_node_path12.default.dirname(target), { recursive: true });
4561
+ import_node_fs9.default.writeFileSync(target, file.source);
4562
+ const artifact = {
4563
+ ...file,
4564
+ fileName,
4565
+ type: "asset"
4566
+ };
4567
+ emitted.add(collisionKey);
4568
+ output.push(artifact);
4569
+ return fileName;
4570
+ }
4571
+ };
4572
+ }
4573
+ function normalizeEnvironmentFileName(fileName) {
4574
+ return import_node_path12.default.posix.normalize(fileName.replace(/\\/g, "/").replace(/^\.\//, ""));
4575
+ }
4576
+ function isInvalidEnvironmentFileName(fileName) {
4577
+ return !fileName || fileName === "." || fileName === ".." || fileName.startsWith("../") || import_node_path12.default.posix.isAbsolute(fileName) || /^[A-Za-z]:\//.test(fileName);
4578
+ }
4579
+ function normalizeAppFileName(fileName) {
4580
+ const normalized = normalizeEnvironmentFileName(fileName);
4581
+ if (isInvalidEnvironmentFileName(normalized)) {
4582
+ throw new Error(`[nasti] invalid app artifact fileName: ${fileName}`);
4583
+ }
4584
+ return normalized;
4585
+ }
4586
+ function artifactCollisionKey(fileName) {
4587
+ return normalizeEnvironmentFileName(fileName).toLowerCase();
4588
+ }
4589
+ function collectEnvironmentArtifacts(config, results, appOutDir) {
4590
+ const occupied = /* @__PURE__ */ new Set();
4591
+ for (const [environmentName, result] of Object.entries(results)) {
4592
+ const environment = config.environments[environmentName];
4593
+ if (!environment) continue;
4594
+ const environmentOutDir = import_node_path12.default.resolve(config.root, environment.build.outDir);
4595
+ for (const artifact of result.output) {
4596
+ const artifactPath = import_node_path12.default.resolve(
4597
+ environmentOutDir,
4598
+ ...normalizeEnvironmentFileName(artifact.fileName).split("/")
4599
+ );
4600
+ const relative = import_node_path12.default.relative(appOutDir, artifactPath);
4601
+ if (!relative.startsWith("..") && !import_node_path12.default.isAbsolute(relative)) {
4602
+ occupied.add(artifactCollisionKey(relative));
4603
+ }
4604
+ }
4605
+ }
4606
+ return occupied;
4607
+ }
4608
+ function assertNoSymlinkComponents(outDir, fileName) {
4609
+ let current = outDir;
4610
+ for (const segment of fileName.split("/")) {
4611
+ current = import_node_path12.default.join(current, segment);
4612
+ let stats;
4613
+ try {
4614
+ stats = import_node_fs9.default.lstatSync(current);
4615
+ } catch (error) {
4616
+ if (error.code === "ENOENT") continue;
4617
+ throw error;
4618
+ }
4619
+ if (stats.isSymbolicLink()) {
4620
+ throw new Error(`[nasti] app artifact path cannot traverse a symlink: ${fileName}`);
4621
+ }
4622
+ }
4623
+ }
4624
+ function inferEnvironmentEntries(output) {
4625
+ const entries = {};
4626
+ for (const artifact of output) {
4627
+ if (artifact.type !== "chunk" || !artifact.isEntry || !artifact.name) continue;
4628
+ entries[artifact.name] = normalizeEnvironmentFileName(artifact.fileName);
4629
+ }
4630
+ return Object.keys(entries).length > 0 ? entries : void 0;
4631
+ }
4632
+ var import_node_fs9, import_node_path12;
4633
+ var init_build_app_context = __esm({
4634
+ "src/core/build-app-context.ts"() {
4635
+ "use strict";
4636
+ import_node_fs9 = __toESM(require("fs"), 1);
4637
+ import_node_path12 = __toESM(require("path"), 1);
4638
+ }
4639
+ });
4640
+
4086
4641
  // src/build/index.ts
4087
4642
  var build_exports = {};
4088
4643
  __export(build_exports, {
@@ -4096,9 +4651,14 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4096
4651
  const config = environment.config;
4097
4652
  const envOptions = environment.options;
4098
4653
  const isServer = environment.consumer === "server";
4099
- const outDir = import_node_path12.default.resolve(config.root, envOptions.build.outDir);
4654
+ const outDir = import_node_path13.default.resolve(config.root, envOptions.build.outDir);
4100
4655
  const assetsDir = envOptions.build.assetsDir;
4101
- const { output: userOutput, transform: userTransform, ...restInputOptions } = envOptions.build.rolldownOptions;
4656
+ const {
4657
+ output: userOutput,
4658
+ transform: userTransform,
4659
+ resolve: userResolve,
4660
+ ...restInputOptions
4661
+ } = envOptions.build.rolldownOptions;
4102
4662
  const vueDefine = config.framework === "vue" ? {
4103
4663
  __VUE_OPTIONS_API__: "true",
4104
4664
  __VUE_PROD_DEVTOOLS__: "false",
@@ -4112,19 +4672,22 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4112
4672
  input: entryPoints,
4113
4673
  transform: { ...userTransform, define: mergedDefine },
4114
4674
  plugins: rolldownPlugins,
4675
+ // client/server consumer 都必须使用当前环境的 conditions/mainFields;Lynx
4676
+ // BG/MT 通常同为 client consumer,但仍可能声明不同的运行时条件。
4677
+ resolve: {
4678
+ ...userResolve ?? {},
4679
+ // Environment API 是条件解析的唯一高层入口,优先于继承来的底层选项。
4680
+ conditionNames: envOptions.resolve.conditions,
4681
+ mainFields: envOptions.resolve.mainFields
4682
+ },
4115
4683
  ...isServer ? {
4116
4684
  platform: restInputOptions.platform ?? "node",
4117
- resolve: {
4118
- conditionNames: envOptions.resolve.conditions,
4119
- mainFields: envOptions.resolve.mainFields,
4120
- ...restInputOptions.resolve
4121
- },
4122
4685
  // server 产物:node 内建恒外部化;bare specifier 默认外部化
4123
4686
  //(同 Vite ssr.external 默认 —— 依赖由 node_modules 运行时解析),
4124
4687
  // 相对/绝对/虚拟模块照常打包。需要内联依赖时经 rolldownOptions.external 覆盖。
4125
4688
  external: restInputOptions.external ?? ((id) => {
4126
4689
  if (NODE_BUILTINS2.has(id)) return true;
4127
- return !id.startsWith(".") && !import_node_path12.default.isAbsolute(id) && !id.startsWith("\0");
4690
+ return !id.startsWith(".") && !import_node_path13.default.isAbsolute(id) && !id.startsWith("\0");
4128
4691
  })
4129
4692
  } : {}
4130
4693
  };
@@ -4151,27 +4714,122 @@ function getRolldownOptions(environment, entryPoints, rolldownPlugins) {
4151
4714
  };
4152
4715
  return { inputOptions, outputOptions, outDir };
4153
4716
  }
4154
- function toRolldownPlugins(plugins) {
4717
+ function toRolldownPlugins(plugins, environment) {
4718
+ const wrap = (hook) => {
4719
+ if (!hook) return hook;
4720
+ return function(...args) {
4721
+ return hook.apply(attachEnvironment(this, environment), args);
4722
+ };
4723
+ };
4155
4724
  return plugins.map((p) => ({
4156
4725
  name: p.name,
4157
- resolveId: p.resolveId,
4158
- load: p.load,
4159
- transform: p.transform,
4160
- buildStart: p.buildStart,
4161
- buildEnd: p.buildEnd,
4726
+ resolveId: wrap(p.resolveId),
4727
+ load: wrap(p.load),
4728
+ transform: wrap(p.transform),
4729
+ buildStart: wrap(p.buildStart),
4730
+ buildEnd: wrap(p.buildEnd),
4162
4731
  // closeBundle 在 bundle.close() 时触发 —— PWA manifest/SW 等终态产物依赖
4163
- closeBundle: p.closeBundle,
4164
- renderChunk: p.renderChunk,
4165
- augmentChunkHash: p.augmentChunkHash,
4166
- generateBundle: p.generateBundle
4732
+ closeBundle: wrap(p.closeBundle),
4733
+ renderChunk: wrap(p.renderChunk),
4734
+ augmentChunkHash: wrap(p.augmentChunkHash),
4735
+ generateBundle: wrap(p.generateBundle)
4167
4736
  }));
4168
4737
  }
4738
+ function attachEnvironment(context, environment) {
4739
+ if (context?.environment === environment) return context;
4740
+ try {
4741
+ Object.defineProperty(context, "environment", {
4742
+ configurable: true,
4743
+ enumerable: false,
4744
+ writable: false,
4745
+ value: environment
4746
+ });
4747
+ return context;
4748
+ } catch {
4749
+ return new Proxy(context, {
4750
+ get(target, property) {
4751
+ if (property === "environment") return environment;
4752
+ const value = Reflect.get(target, property, target);
4753
+ return typeof value === "function" ? value.bind(target) : value;
4754
+ },
4755
+ set(target, property, value) {
4756
+ return Reflect.set(target, property, value, target);
4757
+ }
4758
+ });
4759
+ }
4760
+ }
4761
+ function finalizeEnvironmentResult(environment, result) {
4762
+ const metadata = environment.getBuildMetadata();
4763
+ const inferredEntries = inferEnvironmentEntries(result.output);
4764
+ const entries = {
4765
+ ...inferredEntries,
4766
+ ...metadata.entries,
4767
+ ...result.entries
4768
+ };
4769
+ const normalizedEntries = Object.fromEntries(
4770
+ Object.entries(entries).map(([name, fileName]) => {
4771
+ const normalized = normalizeEnvironmentFileName(fileName);
4772
+ if (isInvalidEnvironmentFileName(normalized)) {
4773
+ throw new Error(
4774
+ `[nasti] environment "${environment.name}" returned invalid entry "${name}": ${fileName}`
4775
+ );
4776
+ }
4777
+ return [name, normalized];
4778
+ })
4779
+ );
4780
+ return {
4781
+ ...metadata,
4782
+ ...result,
4783
+ output: result.output,
4784
+ ...Object.keys(normalizedEntries).length > 0 ? { entries: normalizedEntries } : {}
4785
+ };
4786
+ }
4787
+ function prepareBuildOutputDirectories(config, buildableNames) {
4788
+ const directories = /* @__PURE__ */ new Set();
4789
+ const protectedPaths = /* @__PURE__ */ new Set();
4790
+ const clientIsBuilt = buildableNames.includes("client");
4791
+ if (!clientIsBuilt && config.build.emptyOutDir) {
4792
+ directories.add(import_node_path13.default.resolve(config.root, config.build.outDir));
4793
+ }
4794
+ for (const name of buildableNames) {
4795
+ const environment = config.environments[name];
4796
+ const outDir = import_node_path13.default.resolve(config.root, environment.build.outDir);
4797
+ if (!environment.build.emptyOutDir) {
4798
+ protectedPaths.add(outDir);
4799
+ continue;
4800
+ }
4801
+ if (!environment.driver) directories.add(outDir);
4802
+ }
4803
+ const containsPath = (parent, child) => {
4804
+ const relative = import_node_path13.default.relative(parent, child);
4805
+ return relative === "" || !relative.startsWith("..") && !import_node_path13.default.isAbsolute(relative);
4806
+ };
4807
+ const roots = [...directories].filter(
4808
+ (directory) => ![...protectedPaths].some((protectedPath) => containsPath(directory, protectedPath))
4809
+ ).sort((a, b) => a.length - b.length).filter(
4810
+ (directory, index2, all) => !all.slice(0, index2).some((parent) => containsPath(parent, directory))
4811
+ );
4812
+ for (const directory of roots) {
4813
+ if (import_node_fs10.default.existsSync(directory)) import_node_fs10.default.rmSync(directory, { recursive: true, force: true });
4814
+ }
4815
+ }
4816
+ function assertDriverBuildResult(environment, result) {
4817
+ const output = result != null && typeof result === "object" ? result.output : void 0;
4818
+ const hasValidOutput = Array.isArray(output) && output.every(
4819
+ (artifact) => artifact != null && typeof artifact === "object" && typeof artifact.fileName === "string" && typeof artifact.type === "string"
4820
+ );
4821
+ if (!hasValidOutput) {
4822
+ throw new Error(
4823
+ `[nasti] environment "${environment.name}" driver "${environment.driver?.name}" returned an invalid build result; expected { output: EnvironmentBuildOutput[] }`
4824
+ );
4825
+ }
4826
+ }
4169
4827
  function resolveClientEntries(config, html) {
4170
4828
  const configuredEntries = config.environments.client?.entry ?? [];
4171
4829
  if (configuredEntries.length > 0) return configuredEntries;
4172
4830
  const entryPoints = [];
4173
4831
  const htmlFile = config.environments.client?.html;
4174
- const htmlDir = htmlFile ? import_node_path12.default.dirname(htmlFile) : config.root;
4832
+ const htmlDir = htmlFile ? import_node_path13.default.dirname(htmlFile) : config.root;
4175
4833
  if (html) {
4176
4834
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4177
4835
  for (const match of scriptMatches) {
@@ -4179,7 +4837,7 @@ function resolveClientEntries(config, html) {
4179
4837
  if (src && !src.startsWith("http")) {
4180
4838
  const cleanSrc = src.split(/[?#]/, 1)[0];
4181
4839
  entryPoints.push(
4182
- cleanSrc.startsWith("/") ? import_node_path12.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path12.default.resolve(htmlDir, cleanSrc)
4840
+ cleanSrc.startsWith("/") ? import_node_path13.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path13.default.resolve(htmlDir, cleanSrc)
4183
4841
  );
4184
4842
  }
4185
4843
  }
@@ -4187,8 +4845,8 @@ function resolveClientEntries(config, html) {
4187
4845
  if (entryPoints.length === 0) {
4188
4846
  const fallbackEntries = ["src/main.ts", "src/main.tsx", "src/main.js", "src/index.ts", "src/index.tsx", "src/index.js"];
4189
4847
  for (const entry of fallbackEntries) {
4190
- const fullPath = import_node_path12.default.resolve(config.root, entry);
4191
- if (import_node_fs9.default.existsSync(fullPath)) {
4848
+ const fullPath = import_node_path13.default.resolve(config.root, entry);
4849
+ if (import_node_fs10.default.existsSync(fullPath)) {
4192
4850
  entryPoints.push(fullPath);
4193
4851
  break;
4194
4852
  }
@@ -4216,16 +4874,20 @@ async function build(inlineConfig = {}) {
4216
4874
  const startTime = performance.now();
4217
4875
  logger.info(
4218
4876
  import_picocolors6.default.cyan(`
4219
- nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4877
+ nasti v${"2.4.0"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4220
4878
  );
4221
4879
  debug5?.(`root: ${config.root}`);
4222
- const buildableNames = Object.keys(config.environments).filter(
4223
- (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
4224
- );
4880
+ const buildableNames = Object.keys(config.environments).filter((name) => {
4881
+ const environment = config.environments[name];
4882
+ if (!environment.buildEnabled) return false;
4883
+ return name === "client" || environment.entry.length > 0 || !!environment.driver;
4884
+ });
4225
4885
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
4886
+ prepareBuildOutputDirectories(config, buildableNames);
4226
4887
  const environments = {};
4227
4888
  const environmentResults = {};
4228
4889
  const initializedEnvironments = [];
4890
+ const buildAppContext = createBuildAppContext(config, environmentResults);
4229
4891
  let clientOutput = [];
4230
4892
  let buildFailed = false;
4231
4893
  try {
@@ -4241,7 +4903,7 @@ nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.m
4241
4903
  }
4242
4904
  const pluginApi = getPluginApi(config);
4243
4905
  for (const plugin of config.plugins) {
4244
- await plugin.afterBuildApp?.(environmentResults, pluginApi);
4906
+ await plugin.afterBuildApp?.(environmentResults, pluginApi, buildAppContext);
4245
4907
  }
4246
4908
  } catch (error) {
4247
4909
  buildFailed = true;
@@ -4268,22 +4930,31 @@ nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.m
4268
4930
  }
4269
4931
  }
4270
4932
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
4271
- const totalSize = Object.values(environments).flat().reduce((sum, chunk) => {
4933
+ const allOutput = [...Object.values(environments).flat(), ...buildAppContext.output];
4934
+ const totalSize = allOutput.reduce((sum, chunk) => {
4272
4935
  const content = chunk.type === "chunk" ? chunk.code : chunk.source;
4273
4936
  if (content == null) return sum;
4274
4937
  return sum + (typeof content === "string" ? Buffer.byteLength(content) : content.byteLength);
4275
4938
  }, 0);
4276
- const fileCount = Object.values(environments).flat().length;
4939
+ const fileCount = allOutput.length;
4277
4940
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4278
4941
  logger.info(import_picocolors6.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors6.default.dim(envSuffix));
4279
4942
  logger.info(import_picocolors6.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4280
- return { output: clientOutput, environments, environmentResults };
4943
+ return {
4944
+ output: clientOutput,
4945
+ environments,
4946
+ environmentResults,
4947
+ appOutput: [...buildAppContext.output]
4948
+ };
4281
4949
  }
4282
4950
  async function buildClientEnvironment(config) {
4283
4951
  const logger = config.logger;
4284
- const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4952
+ const outDir = import_node_path13.default.resolve(config.root, config.build.outDir);
4285
4953
  const cssEngine = createCssEngine();
4286
- const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
4954
+ const pluginList = resolvePluginList(config, config.plugins, {
4955
+ cssEngine,
4956
+ environmentName: "client"
4957
+ });
4287
4958
  const clientEnv = new NastiEnvironment("client", config, {
4288
4959
  mode: "build",
4289
4960
  plugins: pluginList,
@@ -4298,13 +4969,11 @@ async function buildClientEnvironment(config) {
4298
4969
  );
4299
4970
  }
4300
4971
  const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4301
- return { environment: clientEnv, result };
4302
- }
4303
- if (config.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4304
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4972
+ assertDriverBuildResult(clientEnv, result);
4973
+ return { environment: clientEnv, result: finalizeEnvironmentResult(clientEnv, result) };
4305
4974
  }
4306
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
4307
- const htmlFile = config.environments.client.html ?? import_node_path12.default.resolve(config.root, "index.html");
4975
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4976
+ const htmlFile = config.environments.client.html ?? import_node_path13.default.resolve(config.root, "index.html");
4308
4977
  const html = await readHtmlFile(config.root, htmlFile);
4309
4978
  const entryPoints = resolveClientEntries(config, html);
4310
4979
  if (entryPoints.length === 0) {
@@ -4314,7 +4983,7 @@ async function buildClientEnvironment(config) {
4314
4983
  const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4315
4984
  const rolldownPlugins = [
4316
4985
  createOxcTransformPlugin(config, clientEnv),
4317
- ...toRolldownPlugins(allPlugins),
4986
+ ...toRolldownPlugins(allPlugins, clientEnv),
4318
4987
  ...nativeReporter ? [nativeReporter] : []
4319
4988
  ];
4320
4989
  const { inputOptions, outputOptions } = getRolldownOptions(
@@ -4351,13 +5020,16 @@ async function buildClientEnvironment(config) {
4351
5020
  );
4352
5021
  }
4353
5022
  }
4354
- import_node_fs9.default.writeFileSync(import_node_path12.default.resolve(outDir, "index.html"), processedHtml);
5023
+ import_node_fs10.default.writeFileSync(import_node_path13.default.resolve(outDir, "index.html"), processedHtml);
4355
5024
  }
4356
5025
  if (!nativeReporter && config.logLevel !== "silent") {
4357
5026
  reportBuildOutput(output, config, logger);
4358
5027
  }
4359
5028
  warnLargeChunks(output, config, logger);
4360
- return { environment: clientEnv, result: { output } };
5029
+ return {
5030
+ environment: clientEnv,
5031
+ result: finalizeEnvironmentResult(clientEnv, { output })
5032
+ };
4361
5033
  } catch (error) {
4362
5034
  try {
4363
5035
  await clientEnv.close();
@@ -4373,7 +5045,10 @@ async function buildClientEnvironment(config) {
4373
5045
  async function buildServerEnvironment(config, name) {
4374
5046
  const envOptions = config.environments[name];
4375
5047
  const logger = config.logger;
4376
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
5048
+ const pluginList = resolvePluginList(config, config.plugins, {
5049
+ consumer: envOptions.consumer,
5050
+ environmentName: name
5051
+ });
4377
5052
  const environment = new NastiEnvironment(name, config, {
4378
5053
  mode: "build",
4379
5054
  plugins: pluginList,
@@ -4389,38 +5064,39 @@ async function buildServerEnvironment(config, name) {
4389
5064
  }
4390
5065
  try {
4391
5066
  const result = await environment.driver.build(environment.getDriverContext());
4392
- return { environment, result };
5067
+ assertDriverBuildResult(environment, result);
5068
+ return { environment, result: finalizeEnvironmentResult(environment, result) };
4393
5069
  } catch (error) {
4394
5070
  await environment.close();
4395
5071
  throw error;
4396
5072
  }
4397
5073
  }
4398
5074
  for (const entry of envOptions.entry) {
4399
- if (!import_node_fs9.default.existsSync(entry)) {
5075
+ if (!import_node_fs10.default.existsSync(entry)) {
4400
5076
  await environment.close();
4401
5077
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4402
5078
  }
4403
5079
  }
4404
5080
  const rolldownPlugins = [
4405
5081
  createOxcTransformPlugin(config, environment),
4406
- ...toRolldownPlugins(environment.plugins)
5082
+ ...toRolldownPlugins(environment.plugins, environment)
4407
5083
  ];
4408
5084
  const { inputOptions, outputOptions, outDir } = getRolldownOptions(
4409
5085
  environment,
4410
5086
  envOptions.entry,
4411
5087
  rolldownPlugins
4412
5088
  );
4413
- if (envOptions.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4414
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4415
- }
4416
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
5089
+ import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4417
5090
  const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4418
5091
  const { output } = await bundle2.write(outputOptions);
4419
5092
  await bundle2.close();
4420
5093
  logger.info(
4421
- import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path12.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
5094
+ import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path13.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
4422
5095
  );
4423
- return { environment, result: { output } };
5096
+ return {
5097
+ environment,
5098
+ result: finalizeEnvironmentResult(environment, { output })
5099
+ };
4424
5100
  }
4425
5101
  function injectCssLinks(html, cssEngine, config) {
4426
5102
  const cssLinkTags = [];
@@ -4447,9 +5123,9 @@ function escapeRegExp(string) {
4447
5123
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4448
5124
  }
4449
5125
  function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
4450
- const rootRelative = import_node_path12.default.relative(config.root, facadeModuleId).split(import_node_path12.default.sep).join("/");
4451
- const resolvedHtmlFile = import_node_path12.default.resolve(config.root, htmlFile);
4452
- const htmlRelative = import_node_path12.default.relative(import_node_path12.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path12.default.sep).join("/");
5126
+ const rootRelative = import_node_path13.default.relative(config.root, facadeModuleId).split(import_node_path13.default.sep).join("/");
5127
+ const resolvedHtmlFile = import_node_path13.default.resolve(config.root, htmlFile);
5128
+ const htmlRelative = import_node_path13.default.relative(import_node_path13.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path13.default.sep).join("/");
4453
5129
  const candidates = /* @__PURE__ */ new Set([
4454
5130
  rootRelative,
4455
5131
  `/${rootRelative}`,
@@ -4465,12 +5141,12 @@ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, ur
4465
5141
  }
4466
5142
  return processed;
4467
5143
  }
4468
- var import_node_path12, import_node_fs9, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
5144
+ var import_node_path13, import_node_fs10, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
4469
5145
  var init_build = __esm({
4470
5146
  "src/build/index.ts"() {
4471
5147
  "use strict";
4472
- import_node_path12 = __toESM(require("path"), 1);
4473
- import_node_fs9 = __toESM(require("fs"), 1);
5148
+ import_node_path13 = __toESM(require("path"), 1);
5149
+ import_node_fs10 = __toESM(require("fs"), 1);
4474
5150
  import_node_module5 = require("module");
4475
5151
  import_rolldown = require("rolldown");
4476
5152
  init_config();
@@ -4483,6 +5159,7 @@ var init_build = __esm({
4483
5159
  init_reporter();
4484
5160
  init_debug();
4485
5161
  init_plugin_api();
5162
+ init_build_app_context();
4486
5163
  import_picocolors6 = __toESM(require("picocolors"), 1);
4487
5164
  debug5 = createDebugger("nasti:build");
4488
5165
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...import_node_module5.builtinModules, ...import_node_module5.builtinModules.map((m) => `node:${m}`)]);
@@ -4526,7 +5203,7 @@ async function createBundledDevServer(opts) {
4526
5203
  createReactRefreshRuntimePlugin(entryPoints),
4527
5204
  createBundledOxcRefreshPlugin()
4528
5205
  ] : [],
4529
- ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins)),
5206
+ ...stripCatchAllLoad(toRolldownPlugins(clientEnv.plugins, clientEnv)),
4530
5207
  ...useReactRefresh ? [
4531
5208
  refreshWrapperFn({
4532
5209
  cwd: config.root,
@@ -4575,7 +5252,7 @@ async function createBundledDevServer(opts) {
4575
5252
  }
4576
5253
  const url = `/${patchPath}`;
4577
5254
  logger.info(
4578
- import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path13.default.relative(config.root, f)).join(", ")),
5255
+ import_picocolors7.default.green("hmr update ") + import_picocolors7.default.dim(changedFiles.map((f) => import_node_path14.default.relative(config.root, f)).join(", ")),
4579
5256
  { timestamp: true }
4580
5257
  );
4581
5258
  sendTo(clientId, { type: "hmr:update", path: url, url });
@@ -4711,7 +5388,7 @@ async function createBundledDevServer(opts) {
4711
5388
  return;
4712
5389
  }
4713
5390
  res.setHeader("ETag", hit.etag);
4714
- res.setHeader("Content-Type", MIME_TYPES[import_node_path13.default.extname(fileName)] ?? "application/octet-stream");
5391
+ res.setHeader("Content-Type", MIME_TYPES[import_node_path14.default.extname(fileName)] ?? "application/octet-stream");
4715
5392
  res.setHeader("Cache-Control", "no-cache");
4716
5393
  res.end(hit.content);
4717
5394
  return;
@@ -4747,7 +5424,7 @@ function stripCatchAllLoad(plugins) {
4747
5424
  );
4748
5425
  }
4749
5426
  function createReactRefreshRuntimePlugin(entryPoints) {
4750
- const entryIds = new Set(entryPoints.map((p) => import_node_path13.default.resolve(p)));
5427
+ const entryIds = new Set(entryPoints.map((p) => import_node_path14.default.resolve(p)));
4751
5428
  return {
4752
5429
  name: "nasti:bundled-react-refresh",
4753
5430
  resolveId(source) {
@@ -4765,7 +5442,7 @@ function createReactRefreshRuntimePlugin(entryPoints) {
4765
5442
  return null;
4766
5443
  },
4767
5444
  transform(code, id) {
4768
- if (!entryIds.has(import_node_path13.default.resolve(id.split("?")[0]))) return null;
5445
+ if (!entryIds.has(import_node_path14.default.resolve(id.split("?")[0]))) return null;
4769
5446
  return { code: `import ${JSON.stringify(PREAMBLE_SPEC)};
4770
5447
  ${code}`, map: null };
4771
5448
  }
@@ -4812,11 +5489,11 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4812
5489
  }
4813
5490
  return processed;
4814
5491
  }
4815
- var import_node_path13, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
5492
+ var import_node_path14, import_node_crypto3, import_ws2, import_picocolors7, debug6, MIME_TYPES, MemoryFiles, PREAMBLE_SPEC, RESOLVED_PREAMBLE_ID, REFRESH_RUNTIME_URL, BUNDLED_PREAMBLE_CODE, WRAPPER_RUNTIME_HELPERS;
4816
5493
  var init_dev_engine = __esm({
4817
5494
  "src/server/bundled/dev-engine.ts"() {
4818
5495
  "use strict";
4819
- import_node_path13 = __toESM(require("path"), 1);
5496
+ import_node_path14 = __toESM(require("path"), 1);
4820
5497
  import_node_crypto3 = __toESM(require("crypto"), 1);
4821
5498
  import_ws2 = require("ws");
4822
5499
  import_picocolors7 = __toESM(require("picocolors"), 1);
@@ -4943,7 +5620,9 @@ async function createServer(inlineConfig = {}) {
4943
5620
  const startTime = performance.now();
4944
5621
  const config = await resolveConfig(inlineConfig, "serve");
4945
5622
  const logger = config.logger;
4946
- const allPlugins = resolvePluginList(config, config.plugins);
5623
+ const allPlugins = resolvePluginList(config, config.plugins, {
5624
+ environmentName: "client"
5625
+ });
4947
5626
  const configWithPlugins = { ...config, plugins: allPlugins };
4948
5627
  const app = (0, import_connect.default)();
4949
5628
  const httpServer = import_node_http.default.createServer(app);
@@ -4960,7 +5639,10 @@ async function createServer(inlineConfig = {}) {
4960
5639
  for (const name of Object.keys(config.environments)) {
4961
5640
  if (name === "client") continue;
4962
5641
  const consumer = config.environments[name].consumer;
4963
- const envPlugins = resolvePluginList(config, config.plugins, { consumer });
5642
+ const envPlugins = resolvePluginList(config, config.plugins, {
5643
+ consumer,
5644
+ environmentName: name
5645
+ });
4964
5646
  environments[name] = new NastiEnvironment(name, config, {
4965
5647
  mode: "dev",
4966
5648
  plugins: envPlugins,
@@ -4995,14 +5677,14 @@ async function createServer(inlineConfig = {}) {
4995
5677
  app.use(bundledServer.middleware);
4996
5678
  }
4997
5679
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4998
- const outDirAbs = import_node_path14.default.resolve(config.root, config.build.outDir);
5680
+ const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
4999
5681
  const watcher = (0, import_chokidar.watch)(config.root, {
5000
5682
  ignored: (filePath) => {
5001
5683
  if (filePath === config.root) return false;
5002
- if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path14.default.sep)) return true;
5003
- const rel = import_node_path14.default.relative(config.root, filePath);
5004
- if (!rel || rel.startsWith("..") || import_node_path14.default.isAbsolute(rel)) return false;
5005
- for (const seg of rel.split(import_node_path14.default.sep)) {
5684
+ if (filePath === outDirAbs || filePath.startsWith(outDirAbs + import_node_path15.default.sep)) return true;
5685
+ const rel = import_node_path15.default.relative(config.root, filePath);
5686
+ if (!rel || rel.startsWith("..") || import_node_path15.default.isAbsolute(rel)) return false;
5687
+ for (const seg of rel.split(import_node_path15.default.sep)) {
5006
5688
  if (ignoredSegments.has(seg)) return true;
5007
5689
  }
5008
5690
  return false;
@@ -5103,7 +5785,7 @@ async function createServer(inlineConfig = {}) {
5103
5785
  const readyIn = Math.ceil(performance.now() - startTime);
5104
5786
  logger.info(
5105
5787
  `
5106
- ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.3.1"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
5788
+ ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.4.0"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
5107
5789
  `
5108
5790
  );
5109
5791
  printServerUrls(
@@ -5131,7 +5813,12 @@ async function createServer(inlineConfig = {}) {
5131
5813
  },
5132
5814
  async transformRequest(url) {
5133
5815
  const { transformRequest: transformRequest2 } = await Promise.resolve().then(() => (init_middleware(), middleware_exports));
5134
- return transformRequest2(url, { config: configWithPlugins, pluginContainer, moduleGraph });
5816
+ return transformRequest2(url, {
5817
+ config: configWithPlugins,
5818
+ pluginContainer,
5819
+ moduleGraph,
5820
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5821
+ });
5135
5822
  },
5136
5823
  async ssrLoadModule(url) {
5137
5824
  const runner = await getSsrRunner();
@@ -5191,9 +5878,10 @@ async function createServer(inlineConfig = {}) {
5191
5878
  app.use(transformMiddleware({
5192
5879
  config: configWithPlugins,
5193
5880
  pluginContainer,
5194
- moduleGraph
5881
+ moduleGraph,
5882
+ onPrune: (paths) => ws.send({ type: "prune", paths })
5195
5883
  }));
5196
- const publicDir = import_node_path14.default.resolve(config.root, "public");
5884
+ const publicDir = import_node_path15.default.resolve(config.root, "public");
5197
5885
  app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5198
5886
  app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
5199
5887
  const postMiddlewares = [];
@@ -5219,12 +5907,12 @@ function getNetworkAddress() {
5219
5907
  }
5220
5908
  return "localhost";
5221
5909
  }
5222
- var import_node_http, import_node_path14, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
5910
+ var import_node_http, import_node_path15, import_node_os, import_connect, import_sirv, import_chokidar, import_picocolors8;
5223
5911
  var init_server = __esm({
5224
5912
  "src/server/index.ts"() {
5225
5913
  "use strict";
5226
5914
  import_node_http = __toESM(require("http"), 1);
5227
- import_node_path14 = __toESM(require("path"), 1);
5915
+ import_node_path15 = __toESM(require("path"), 1);
5228
5916
  import_node_os = __toESM(require("os"), 1);
5229
5917
  import_connect = __toESM(require("connect"), 1);
5230
5918
  import_sirv = __toESM(require("sirv"), 1);
@@ -5293,16 +5981,16 @@ async function buildElectron(inlineConfig = {}) {
5293
5981
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
5294
5982
  const startTime = performance.now();
5295
5983
  assertElectronVersion(config);
5296
- console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.3.1"}`));
5984
+ console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.4.0"}`));
5297
5985
  console.log(import_picocolors9.default.dim(` root: ${config.root}`));
5298
5986
  console.log(import_picocolors9.default.dim(` mode: ${config.mode}`));
5299
5987
  console.log(import_picocolors9.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
5300
- const outDir = import_node_path15.default.resolve(config.root, config.build.outDir);
5301
- if (config.build.emptyOutDir && import_node_fs10.default.existsSync(outDir)) {
5302
- import_node_fs10.default.rmSync(outDir, { recursive: true, force: true });
5988
+ const outDir = import_node_path16.default.resolve(config.root, config.build.outDir);
5989
+ if (config.build.emptyOutDir && import_node_fs11.default.existsSync(outDir)) {
5990
+ import_node_fs11.default.rmSync(outDir, { recursive: true, force: true });
5303
5991
  }
5304
- import_node_fs10.default.mkdirSync(outDir, { recursive: true });
5305
- const rendererOutDir = import_node_path15.default.join(outDir, "renderer");
5992
+ import_node_fs11.default.mkdirSync(outDir, { recursive: true });
5993
+ const rendererOutDir = import_node_path16.default.join(outDir, "renderer");
5306
5994
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
5307
5995
  await build2(createElectronRendererConfig(config, inlineConfig, {
5308
5996
  build: {
@@ -5311,8 +5999,8 @@ async function buildElectron(inlineConfig = {}) {
5311
5999
  emptyOutDir: false
5312
6000
  }
5313
6001
  }));
5314
- const mainEntry = import_node_path15.default.resolve(config.root, config.electron.main);
5315
- if (!import_node_fs10.default.existsSync(mainEntry)) {
6002
+ const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6003
+ if (!import_node_fs11.default.existsSync(mainEntry)) {
5316
6004
  throw new Error(
5317
6005
  `Electron main entry not found: ${config.electron.main}
5318
6006
  \u5728 nasti.config.ts \u7684 electron.main \u6307\u5B9A\u4E3B\u8FDB\u7A0B\u5165\u53E3\u6587\u4EF6\u3002`
@@ -5326,11 +6014,11 @@ async function buildElectron(inlineConfig = {}) {
5326
6014
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5327
6015
  const preloadFiles = [];
5328
6016
  for (const entry of preloadEntries) {
5329
- if (!import_node_fs10.default.existsSync(entry)) {
6017
+ if (!import_node_fs11.default.existsSync(entry)) {
5330
6018
  console.warn(import_picocolors9.default.yellow(` \u26A0 preload entry not found, skipped: ${entry}`));
5331
6019
  continue;
5332
6020
  }
5333
- const base = import_node_path15.default.basename(entry).replace(/\.[^.]+$/, "");
6021
+ const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5334
6022
  const out = outFileName(outDir, base, config.electron.preloadFormat);
5335
6023
  await bundleNode(config, entry, {
5336
6024
  outFile: out,
@@ -5342,10 +6030,10 @@ async function buildElectron(inlineConfig = {}) {
5342
6030
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
5343
6031
  console.log(import_picocolors9.default.green(`
5344
6032
  \u2713 Electron build complete in ${elapsed}s`));
5345
- console.log(import_picocolors9.default.dim(` renderer: ${import_node_path15.default.relative(config.root, rendererOutDir)}/`));
5346
- console.log(import_picocolors9.default.dim(` main: ${import_node_path15.default.relative(config.root, mainFile)}`));
6033
+ console.log(import_picocolors9.default.dim(` renderer: ${import_node_path16.default.relative(config.root, rendererOutDir)}/`));
6034
+ console.log(import_picocolors9.default.dim(` main: ${import_node_path16.default.relative(config.root, mainFile)}`));
5347
6035
  for (const pf of preloadFiles) {
5348
- console.log(import_picocolors9.default.dim(` preload: ${import_node_path15.default.relative(config.root, pf)}`));
6036
+ console.log(import_picocolors9.default.dim(` preload: ${import_node_path16.default.relative(config.root, pf)}`));
5349
6037
  }
5350
6038
  console.log();
5351
6039
  return { rendererOutDir, mainFile, preloadFiles };
@@ -5383,7 +6071,7 @@ async function bundleNode(config, entry, opts) {
5383
6071
  },
5384
6072
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5385
6073
  });
5386
- import_node_fs10.default.mkdirSync(import_node_path15.default.dirname(opts.outFile), { recursive: true });
6074
+ import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
5387
6075
  await bundle2.write({
5388
6076
  sourcemap: !!config.build.sourcemap,
5389
6077
  minify: !!config.build.minify,
@@ -5394,7 +6082,7 @@ async function bundleNode(config, entry, opts) {
5394
6082
  codeSplitting: false
5395
6083
  });
5396
6084
  await bundle2.close();
5397
- console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path15.default.relative(config.root, opts.outFile)}`));
6085
+ console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path16.default.relative(config.root, opts.outFile)}`));
5398
6086
  return opts.outFile;
5399
6087
  }
5400
6088
  function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
@@ -5418,11 +6106,11 @@ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {})
5418
6106
  }
5419
6107
  function outFileName(outDir, base, format) {
5420
6108
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5421
- return import_node_path15.default.join(outDir, base + ext);
6109
+ return import_node_path16.default.join(outDir, base + ext);
5422
6110
  }
5423
6111
  function normalizePreload(preload, root) {
5424
6112
  const list = Array.isArray(preload) ? preload : preload ? [preload] : [];
5425
- return list.map((p) => import_node_path15.default.resolve(root, p));
6113
+ return list.map((p) => import_node_path16.default.resolve(root, p));
5426
6114
  }
5427
6115
  function assertElectronVersion(config) {
5428
6116
  const min = config.electron.minVersion;
@@ -5437,21 +6125,21 @@ function assertElectronVersion(config) {
5437
6125
  }
5438
6126
  function detectInstalledElectron(root) {
5439
6127
  try {
5440
- const pkgPath = import_node_path15.default.resolve(root, "node_modules/electron/package.json");
5441
- if (!import_node_fs10.default.existsSync(pkgPath)) return null;
5442
- const pkg = JSON.parse(import_node_fs10.default.readFileSync(pkgPath, "utf-8"));
6128
+ const pkgPath = import_node_path16.default.resolve(root, "node_modules/electron/package.json");
6129
+ if (!import_node_fs11.default.existsSync(pkgPath)) return null;
6130
+ const pkg = JSON.parse(import_node_fs11.default.readFileSync(pkgPath, "utf-8"));
5443
6131
  const major = parseInt(String(pkg.version).split(".")[0], 10);
5444
6132
  return Number.isFinite(major) ? major : null;
5445
6133
  } catch {
5446
6134
  return null;
5447
6135
  }
5448
6136
  }
5449
- var import_node_path15, import_node_fs10, import_rolldown2, import_picocolors9;
6137
+ var import_node_path16, import_node_fs11, import_rolldown2, import_picocolors9;
5450
6138
  var init_electron2 = __esm({
5451
6139
  "src/build/electron.ts"() {
5452
6140
  "use strict";
5453
- import_node_path15 = __toESM(require("path"), 1);
5454
- import_node_fs10 = __toESM(require("fs"), 1);
6141
+ import_node_path16 = __toESM(require("path"), 1);
6142
+ import_node_fs11 = __toESM(require("fs"), 1);
5455
6143
  import_rolldown2 = require("rolldown");
5456
6144
  import_picocolors9 = __toESM(require("picocolors"), 1);
5457
6145
  init_config();
@@ -5472,7 +6160,7 @@ async function startElectronDev(inlineConfig = {}) {
5472
6160
  const { noSpawn, ...rest } = inlineConfig;
5473
6161
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5474
6162
  warnElectronVersion(config);
5475
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
6163
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.4.0"}`));
5476
6164
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5477
6165
  const server = await createServer2({
5478
6166
  ...rest,
@@ -5482,11 +6170,11 @@ async function startElectronDev(inlineConfig = {}) {
5482
6170
  await server.listen();
5483
6171
  const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5484
6172
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5485
- const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5486
- import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
5487
- const mainEntry = import_node_path16.default.resolve(config.root, config.electron.main);
6173
+ const stageDir = import_node_path17.default.resolve(config.root, ".nasti");
6174
+ import_node_fs12.default.mkdirSync(stageDir, { recursive: true });
6175
+ const mainEntry = import_node_path17.default.resolve(config.root, config.electron.main);
5488
6176
  const preloadEntries = normalizePreload(config.electron.preload, config.root);
5489
- const builtMainFile = import_node_path16.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
6177
+ const builtMainFile = import_node_path17.default.join(stageDir, "main" + extFor(config.electron.mainFormat));
5490
6178
  const builtPreloadFiles = [];
5491
6179
  const compileAll = async () => {
5492
6180
  await compileNode(config, mainEntry, {
@@ -5496,9 +6184,9 @@ async function startElectronDev(inlineConfig = {}) {
5496
6184
  });
5497
6185
  builtPreloadFiles.length = 0;
5498
6186
  for (const entry of preloadEntries) {
5499
- if (!import_node_fs11.default.existsSync(entry)) continue;
5500
- const base = import_node_path16.default.basename(entry).replace(/\.[^.]+$/, "");
5501
- const out = import_node_path16.default.join(stageDir, base + extFor(config.electron.preloadFormat));
6187
+ if (!import_node_fs12.default.existsSync(entry)) continue;
6188
+ const base = import_node_path17.default.basename(entry).replace(/\.[^.]+$/, "");
6189
+ const out = import_node_path17.default.join(stageDir, base + extFor(config.electron.preloadFormat));
5502
6190
  await compileNode(config, entry, {
5503
6191
  outFile: out,
5504
6192
  format: config.electron.preloadFormat,
@@ -5537,7 +6225,7 @@ async function startElectronDev(inlineConfig = {}) {
5537
6225
  };
5538
6226
  spawnElectron();
5539
6227
  if (config.electron.autoRestart) {
5540
- const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs11.default.existsSync);
6228
+ const watchTargets = [mainEntry, ...preloadEntries].filter(import_node_fs12.default.existsSync);
5541
6229
  const watcher = import_chokidar2.default.watch(watchTargets, { ignoreInitial: true });
5542
6230
  let restarting = null;
5543
6231
  let pending = false;
@@ -5617,7 +6305,7 @@ async function compileNode(config, entry, opts) {
5617
6305
  platform: "node",
5618
6306
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5619
6307
  });
5620
- import_node_fs11.default.mkdirSync(import_node_path16.default.dirname(opts.outFile), { recursive: true });
6308
+ import_node_fs12.default.mkdirSync(import_node_path17.default.dirname(opts.outFile), { recursive: true });
5621
6309
  await bundle2.write({
5622
6310
  file: opts.outFile,
5623
6311
  format: opts.format === "cjs" ? "cjs" : "esm",
@@ -5630,18 +6318,18 @@ async function compileNode(config, entry, opts) {
5630
6318
  await bundle2.close();
5631
6319
  }
5632
6320
  function electronRendererDevPath(renderer) {
5633
- const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
6321
+ const normalized = renderer.split(import_node_path17.default.sep).join("/").replace(/^\.?\//, "");
5634
6322
  return normalized === "index.html" ? "/" : `/${normalized}`;
5635
6323
  }
5636
6324
  function resolveElectronBinary(config) {
5637
- if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
6325
+ if (config.electron.electronPath && import_node_fs12.default.existsSync(config.electron.electronPath)) {
5638
6326
  return config.electron.electronPath;
5639
6327
  }
5640
6328
  try {
5641
- const require2 = (0, import_node_module7.createRequire)(import_node_path16.default.resolve(config.root, "package.json"));
6329
+ const require2 = (0, import_node_module7.createRequire)(import_node_path17.default.resolve(config.root, "package.json"));
5642
6330
  const pathFile = require2.resolve("electron");
5643
6331
  const electronModule = require2(pathFile);
5644
- if (typeof electronModule === "string" && import_node_fs11.default.existsSync(electronModule)) {
6332
+ if (typeof electronModule === "string" && import_node_fs12.default.existsSync(electronModule)) {
5645
6333
  return electronModule;
5646
6334
  }
5647
6335
  } catch {
@@ -5666,12 +6354,12 @@ function warnElectronVersion(config) {
5666
6354
  );
5667
6355
  }
5668
6356
  }
5669
- var import_node_path16, import_node_fs11, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
6357
+ var import_node_path17, import_node_fs12, import_node_module7, import_node_child_process, import_chokidar2, import_picocolors10, import_rolldown3;
5670
6358
  var init_electron_dev = __esm({
5671
6359
  "src/server/electron-dev.ts"() {
5672
6360
  "use strict";
5673
- import_node_path16 = __toESM(require("path"), 1);
5674
- import_node_fs11 = __toESM(require("fs"), 1);
6361
+ import_node_path17 = __toESM(require("path"), 1);
6362
+ import_node_fs12 = __toESM(require("fs"), 1);
5675
6363
  import_node_module7 = require("module");
5676
6364
  import_node_child_process = require("child_process");
5677
6365
  import_chokidar2 = __toESM(require("chokidar"), 1);
@@ -5824,20 +6512,20 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5824
6512
  const logger = createCliLogger(options);
5825
6513
  try {
5826
6514
  const http2 = await import("http");
5827
- const path17 = await import("path");
6515
+ const path18 = await import("path");
5828
6516
  const os2 = await import("os");
5829
6517
  const sirv2 = (await import("sirv")).default;
5830
6518
  const connect2 = (await import("connect")).default;
5831
6519
  const { printServerUrls: printServerUrls2 } = await Promise.resolve().then(() => (init_logger(), logger_exports));
5832
- const resolvedRoot = path17.resolve(root ?? ".");
5833
- const outDir = path17.resolve(resolvedRoot, options.outDir);
6520
+ const resolvedRoot = path18.resolve(root ?? ".");
6521
+ const outDir = path18.resolve(resolvedRoot, options.outDir);
5834
6522
  const app = connect2();
5835
6523
  app.use(sirv2(outDir, { single: true, etag: true, gzip: true, brotli: true }));
5836
6524
  const port = options.port;
5837
6525
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5838
6526
  http2.createServer(app).listen(port, host, () => {
5839
6527
  logger.info(`
5840
- ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.3.1"}`)} ${import_picocolors11.default.dim("preview")}
6528
+ ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.4.0"}`)} ${import_picocolors11.default.dim("preview")}
5841
6529
  `);
5842
6530
  printServerUrls2(
5843
6531
  {
@@ -5854,6 +6542,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5854
6542
  }
5855
6543
  });
5856
6544
  cli.help();
5857
- cli.version("2.3.1");
6545
+ cli.version("2.4.0");
5858
6546
  cli.parse();
5859
6547
  //# sourceMappingURL=cli.cjs.map