@nasti-toolchain/nasti 2.1.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -226,6 +226,92 @@ var init_defaults = __esm({
226
226
  }
227
227
  });
228
228
 
229
+ // src/core/plugin-api.ts
230
+ function orderPlugins(plugins) {
231
+ const baseline = plugins.map((plugin, index2) => ({ plugin, index: index2 })).sort((a, b) => enforceRank(a.plugin) - enforceRank(b.plugin) || a.index - b.index).map(({ plugin }) => plugin);
232
+ const indexesByName = /* @__PURE__ */ new Map();
233
+ baseline.forEach((plugin, index2) => {
234
+ const indexes = indexesByName.get(plugin.name) ?? [];
235
+ indexes.push(index2);
236
+ indexesByName.set(plugin.name, indexes);
237
+ });
238
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
239
+ const indegree = baseline.map(() => 0);
240
+ const addEdge = (from, to) => {
241
+ if (from === to || edges[from].has(to)) return;
242
+ edges[from].add(to);
243
+ indegree[to]++;
244
+ };
245
+ baseline.forEach((plugin, current) => {
246
+ for (const dependency of plugin.pre ?? []) {
247
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
248
+ }
249
+ for (const dependency of plugin.post ?? []) {
250
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
251
+ }
252
+ });
253
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
254
+ const ordered = [];
255
+ while (ready.length > 0) {
256
+ ready.sort((a, b) => a - b);
257
+ const current = ready.shift();
258
+ ordered.push(baseline[current]);
259
+ for (const next of edges[current]) {
260
+ indegree[next]--;
261
+ if (indegree[next] === 0) ready.push(next);
262
+ }
263
+ }
264
+ if (ordered.length !== baseline.length) {
265
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
266
+ throw new Error(
267
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
268
+ );
269
+ }
270
+ return ordered;
271
+ }
272
+ async function setupPluginApi(config, plugins) {
273
+ const exposed = /* @__PURE__ */ new Map();
274
+ const api = {
275
+ config,
276
+ logger: config.logger,
277
+ expose(key, value) {
278
+ if (exposed.has(key) && exposed.get(key) !== value) {
279
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
280
+ }
281
+ exposed.set(key, value);
282
+ },
283
+ useExposed(key) {
284
+ return exposed.get(key);
285
+ }
286
+ };
287
+ apiByConfig.set(config, api);
288
+ for (const plugin of plugins) {
289
+ await plugin.setup?.(api);
290
+ }
291
+ return api;
292
+ }
293
+ function getPluginApi(config) {
294
+ const api = apiByConfig.get(config);
295
+ if (!api) {
296
+ throw new Error(
297
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
298
+ );
299
+ }
300
+ return api;
301
+ }
302
+ function enforceRank(plugin) {
303
+ if (plugin.enforce === "pre") return 0;
304
+ if (plugin.enforce === "post") return 2;
305
+ return 1;
306
+ }
307
+ var apiByConfig;
308
+ var init_plugin_api = __esm({
309
+ "src/core/plugin-api.ts"() {
310
+ "use strict";
311
+ apiByConfig = /* @__PURE__ */ new WeakMap();
312
+ }
313
+ });
314
+
229
315
  // src/config/index.ts
230
316
  function loadTsconfigPaths(root) {
231
317
  const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
@@ -260,6 +346,43 @@ async function loadConfigFromFile(root) {
260
346
  }
261
347
  return {};
262
348
  }
349
+ function detectFramework(root) {
350
+ const sourceRoot = import_node_path.default.resolve(root, "src");
351
+ if (containsVueFile(sourceRoot)) return "vue";
352
+ const packagePath = import_node_path.default.resolve(root, "package.json");
353
+ if (import_node_fs.default.existsSync(packagePath)) {
354
+ try {
355
+ const pkg = JSON.parse(import_node_fs.default.readFileSync(packagePath, "utf-8"));
356
+ const dependencies = {
357
+ ...pkg.dependencies ?? {},
358
+ ...pkg.devDependencies ?? {},
359
+ ...pkg.peerDependencies ?? {},
360
+ ...pkg.optionalDependencies ?? {}
361
+ };
362
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
363
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
364
+ if (hasVue && !hasReact) return "vue";
365
+ if (hasReact) return "react";
366
+ if (hasVue) return "vue";
367
+ } catch {
368
+ }
369
+ }
370
+ return "react";
371
+ }
372
+ function containsVueFile(dir, depth = 0) {
373
+ if (depth > 5 || !import_node_fs.default.existsSync(dir)) return false;
374
+ try {
375
+ for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
376
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
377
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(import_node_path.default.join(dir, entry.name), depth + 1)) {
378
+ return true;
379
+ }
380
+ }
381
+ } catch {
382
+ return false;
383
+ }
384
+ return false;
385
+ }
263
386
  async function loadTsConfig(filePath) {
264
387
  const { transformSync: transformSync2 } = await import("oxc-transform");
265
388
  const code = import_node_fs.default.readFileSync(filePath, "utf-8");
@@ -306,7 +429,7 @@ async function resolveConfig(inlineConfig = {}, command) {
306
429
  base: merged.base ?? defaults.base,
307
430
  mode,
308
431
  target: merged.target ?? defaults.target,
309
- framework: merged.framework ?? defaults.framework,
432
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
310
433
  command,
311
434
  resolve: {
312
435
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -353,7 +476,12 @@ async function resolveConfig(inlineConfig = {}, command) {
353
476
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
354
477
  resolved.environments.client = {
355
478
  consumer,
356
- entry: [],
479
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
480
+ html: import_node_path.default.resolve(
481
+ root,
482
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
483
+ ),
484
+ driver: envOptions.driver,
357
485
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
358
486
  resolve: resolved.resolve,
359
487
  build: resolved.build
@@ -362,7 +490,9 @@ async function resolveConfig(inlineConfig = {}, command) {
362
490
  }
363
491
  resolved.environments[name] = {
364
492
  consumer,
365
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => import_node_path.default.resolve(root, e)),
493
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
494
+ html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
495
+ driver: envOptions.driver,
366
496
  resolve: {
367
497
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
368
498
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -381,12 +511,13 @@ async function resolveConfig(inlineConfig = {}, command) {
381
511
  };
382
512
  }
383
513
  assertClientEnvironmentMirror(resolved);
384
- const filteredPlugins = rawPlugins.filter((p) => {
514
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
385
515
  if (!p.apply) return true;
386
516
  if (typeof p.apply === "function") return p.apply(resolved, env);
387
517
  return p.apply === command;
388
- });
518
+ }));
389
519
  resolved.plugins = filteredPlugins;
520
+ await setupPluginApi(resolved, filteredPlugins);
390
521
  if (resolved.target === "electron") {
391
522
  const autoExternal = detectNativeDeps(root);
392
523
  if (autoExternal.length > 0) {
@@ -402,6 +533,10 @@ async function resolveConfig(inlineConfig = {}, command) {
402
533
  }
403
534
  return resolved;
404
535
  }
536
+ function normalizeEnvironmentEntries(entry, root) {
537
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
538
+ return entries.map((item) => import_node_path.default.resolve(root, item));
539
+ }
405
540
  function detectNativeDeps(root) {
406
541
  const result = /* @__PURE__ */ new Set();
407
542
  const pkgJsonPath = import_node_path.default.resolve(root, "package.json");
@@ -522,6 +657,7 @@ var init_config = __esm({
522
657
  import_node_fs = __toESM(require("fs"), 1);
523
658
  init_defaults();
524
659
  init_logger();
660
+ init_plugin_api();
525
661
  CONFIG_FILES = [
526
662
  "nasti.config.ts",
527
663
  "nasti.config.js",
@@ -532,21 +668,11 @@ var init_config = __esm({
532
668
  });
533
669
 
534
670
  // src/core/plugin-container.ts
535
- function sortPlugins(plugins) {
536
- const pre = [];
537
- const normal = [];
538
- const post = [];
539
- for (const plugin of plugins) {
540
- if (plugin.enforce === "pre") pre.push(plugin);
541
- else if (plugin.enforce === "post") post.push(plugin);
542
- else normal.push(plugin);
543
- }
544
- return [...pre, ...normal, ...post];
545
- }
546
671
  var PluginContainer;
547
672
  var init_plugin_container = __esm({
548
673
  "src/core/plugin-container.ts"() {
549
674
  "use strict";
675
+ init_plugin_api();
550
676
  PluginContainer = class {
551
677
  plugins;
552
678
  config;
@@ -557,7 +683,7 @@ var init_plugin_container = __esm({
557
683
  constructor(config, environment) {
558
684
  this.config = config;
559
685
  this.environment = environment;
560
- this.plugins = sortPlugins(config.plugins);
686
+ this.plugins = orderPlugins(config.plugins);
561
687
  this.ctx = this.createContext();
562
688
  }
563
689
  createContext() {
@@ -896,6 +1022,7 @@ var init_environment = __esm({
896
1022
  init_module_graph();
897
1023
  init_hot_channel();
898
1024
  init_debug();
1025
+ init_plugin_api();
899
1026
  debug = createDebugger("nasti:environment");
900
1027
  NastiEnvironment = class {
901
1028
  name;
@@ -904,6 +1031,7 @@ var init_environment = __esm({
904
1031
  config;
905
1032
  options;
906
1033
  hot;
1034
+ driver;
907
1035
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
908
1036
  plugins = [];
909
1037
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -911,6 +1039,7 @@ var init_environment = __esm({
911
1039
  /** per-env 模块图(dev 管线使用) */
912
1040
  moduleGraph;
913
1041
  candidatePlugins;
1042
+ pluginApi;
914
1043
  initialized = false;
915
1044
  constructor(name, config, init = {}) {
916
1045
  const options = config.environments[name];
@@ -927,6 +1056,7 @@ var init_environment = __esm({
927
1056
  this.hot = init.hot ?? createNoopHotChannel();
928
1057
  this.moduleGraph = new ModuleGraph();
929
1058
  this.candidatePlugins = init.plugins ?? config.plugins;
1059
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
930
1060
  }
931
1061
  /** 过滤插件并建 per-env PluginContainer */
932
1062
  async init() {
@@ -937,10 +1067,41 @@ var init_environment = __esm({
937
1067
  { ...this.config, plugins: this.plugins },
938
1068
  this
939
1069
  );
1070
+ if (this.options.driver) {
1071
+ const claimed = [];
1072
+ for (const plugin of this.plugins) {
1073
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
1074
+ if (driver) claimed.push({ plugin, driver });
1075
+ }
1076
+ if (claimed.length === 0) {
1077
+ throw new Error(
1078
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
1079
+ );
1080
+ }
1081
+ if (claimed.length > 1) {
1082
+ throw new Error(
1083
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
1084
+ );
1085
+ }
1086
+ this.driver = claimed[0].driver;
1087
+ debug?.(`env "${this.name}" uses driver "${this.driver.name}"`);
1088
+ }
940
1089
  debug?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
941
1090
  }
1091
+ getDriverContext() {
1092
+ return {
1093
+ environment: this,
1094
+ config: this.config,
1095
+ api: this.pluginApi,
1096
+ logger: this.config.logger
1097
+ };
1098
+ }
942
1099
  async close() {
943
- await this.hot.close?.();
1100
+ try {
1101
+ await this.driver?.close?.(this.getDriverContext());
1102
+ } finally {
1103
+ await this.hot.close?.();
1104
+ }
944
1105
  }
945
1106
  };
946
1107
  }
@@ -1005,7 +1166,8 @@ function transformCode(filename, code, options = {}) {
1005
1166
  importSource: options.jsxImportSource ?? "react",
1006
1167
  refresh: options.reactRefresh ?? false
1007
1168
  } : void 0,
1008
- sourcemap: options.sourcemap ?? true
1169
+ sourcemap: options.sourcemap ?? true,
1170
+ target: options.target
1009
1171
  });
1010
1172
  if (result.errors && result.errors.length > 0) {
1011
1173
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -1036,7 +1198,7 @@ function htmlPlugin(config) {
1036
1198
  transformIndexHtml(html) {
1037
1199
  const tags = [];
1038
1200
  if (config.command === "serve") {
1039
- const isReactLike = config.framework === "react" || config.framework === "auto";
1201
+ const isReactLike = config.framework === "react";
1040
1202
  if (isReactLike) {
1041
1203
  tags.push({
1042
1204
  tag: "script",
@@ -1090,8 +1252,8 @@ function serializeTag(tag) {
1090
1252
  }
1091
1253
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
1092
1254
  }
1093
- async function readHtmlFile(root) {
1094
- const htmlPath = import_node_path2.default.resolve(root, "index.html");
1255
+ async function readHtmlFile(root, htmlFile = "index.html") {
1256
+ const htmlPath = import_node_path2.default.isAbsolute(htmlFile) ? htmlFile : import_node_path2.default.resolve(root, htmlFile);
1095
1257
  if (!import_node_fs2.default.existsSync(htmlPath)) return null;
1096
1258
  return import_node_fs2.default.readFileSync(htmlPath, "utf-8");
1097
1259
  }
@@ -1288,7 +1450,10 @@ function transformMiddleware(ctx) {
1288
1450
  return;
1289
1451
  }
1290
1452
  if (url === "/" || url.endsWith(".html")) {
1291
- const html = await readHtmlFile(ctx.config.root);
1453
+ const html = await readHtmlFile(
1454
+ ctx.config.root,
1455
+ ctx.config.environments.client?.html
1456
+ );
1292
1457
  if (html) {
1293
1458
  let processedHtml = html;
1294
1459
  for (const plugin of ctx.config.plugins) {
@@ -1859,7 +2024,7 @@ const hotModulesMap = new Map();
1859
2024
  const disposeMap = new Map();
1860
2025
  const pruneMap = new Map();
1861
2026
 
1862
- socket.addEventListener('message', ({ data }) => {
2027
+ socket.addEventListener('message', async ({ data }) => {
1863
2028
  const payload = JSON.parse(data);
1864
2029
  switch (payload.type) {
1865
2030
  case 'connected':
@@ -1867,14 +2032,21 @@ socket.addEventListener('message', ({ data }) => {
1867
2032
  clearErrorOverlay();
1868
2033
  break;
1869
2034
  case 'update':
1870
- payload.updates.forEach((update) => {
1871
- if (update.type === 'js-update') {
1872
- fetchUpdate(update);
1873
- } else if (update.type === 'css-update') {
1874
- updateCss(update.path);
1875
- }
1876
- });
1877
- clearErrorOverlay();
2035
+ 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
+ }));
2043
+ clearErrorOverlay();
2044
+ console.log('[nasti] HMR update complete, reloading page');
2045
+ location.reload();
2046
+ } catch (err) {
2047
+ console.error('[nasti] HMR update failed:', err);
2048
+ showErrorOverlay(err);
2049
+ }
1878
2050
  break;
1879
2051
  case 'full-reload':
1880
2052
  console.log('[nasti] full reload');
@@ -1916,7 +2088,7 @@ async function fetchUpdate(update) {
1916
2088
  function updateCss(path) {
1917
2089
  const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
1918
2090
  if (el) {
1919
- fetch(path + '?t=' + Date.now())
2091
+ return fetch(path + '?t=' + Date.now())
1920
2092
  .then(r => r.text())
1921
2093
  .then(css => { el.textContent = css; });
1922
2094
  }
@@ -3916,6 +4088,7 @@ var build_exports = {};
3916
4088
  __export(build_exports, {
3917
4089
  build: () => build,
3918
4090
  getRolldownOptions: () => getRolldownOptions,
4091
+ replaceEntryScript: () => replaceEntryScript,
3919
4092
  resolveClientEntries: () => resolveClientEntries,
3920
4093
  toRolldownPlugins: () => toRolldownPlugins
3921
4094
  });
@@ -3994,13 +4167,20 @@ function toRolldownPlugins(plugins) {
3994
4167
  }));
3995
4168
  }
3996
4169
  function resolveClientEntries(config, html) {
4170
+ const configuredEntries = config.environments.client?.entry ?? [];
4171
+ if (configuredEntries.length > 0) return configuredEntries;
3997
4172
  const entryPoints = [];
4173
+ const htmlFile = config.environments.client?.html;
4174
+ const htmlDir = htmlFile ? import_node_path12.default.dirname(htmlFile) : config.root;
3998
4175
  if (html) {
3999
4176
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4000
4177
  for (const match of scriptMatches) {
4001
4178
  const src = match[1];
4002
4179
  if (src && !src.startsWith("http")) {
4003
- entryPoints.push(import_node_path12.default.resolve(config.root, src.replace(/^\//, "")));
4180
+ const cleanSrc = src.split(/[?#]/, 1)[0];
4181
+ entryPoints.push(
4182
+ cleanSrc.startsWith("/") ? import_node_path12.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path12.default.resolve(htmlDir, cleanSrc)
4183
+ );
4004
4184
  }
4005
4185
  }
4006
4186
  }
@@ -4036,21 +4216,55 @@ async function build(inlineConfig = {}) {
4036
4216
  const startTime = performance.now();
4037
4217
  logger.info(
4038
4218
  import_picocolors6.default.cyan(`
4039
- nasti v${"2.1.0"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4219
+ nasti v${"2.3.1"} `) + import_picocolors6.default.green(`building for ${config.mode}...`)
4040
4220
  );
4041
4221
  debug5?.(`root: ${config.root}`);
4042
4222
  const buildableNames = Object.keys(config.environments).filter(
4043
- (name) => name === "client" || config.environments[name].entry.length > 0
4223
+ (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
4044
4224
  );
4045
4225
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
4046
4226
  const environments = {};
4227
+ const environmentResults = {};
4228
+ const initializedEnvironments = [];
4047
4229
  let clientOutput = [];
4048
- for (const name of buildableNames) {
4049
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4050
- environments[name] = output;
4051
- if (name === "client") clientOutput = output;
4052
- if (buildableNames.length > 1) {
4053
- debug5?.(`environment "${name}" built (${output.length} files)`);
4230
+ let buildFailed = false;
4231
+ try {
4232
+ for (const name of buildableNames) {
4233
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4234
+ initializedEnvironments.push(built.environment);
4235
+ environments[name] = built.result.output;
4236
+ environmentResults[name] = built.result;
4237
+ if (name === "client") clientOutput = built.result.output;
4238
+ if (buildableNames.length > 1) {
4239
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
4240
+ }
4241
+ }
4242
+ const pluginApi = getPluginApi(config);
4243
+ for (const plugin of config.plugins) {
4244
+ await plugin.afterBuildApp?.(environmentResults, pluginApi);
4245
+ }
4246
+ } catch (error) {
4247
+ buildFailed = true;
4248
+ throw error;
4249
+ } finally {
4250
+ let closeFailed = false;
4251
+ let firstCloseError;
4252
+ for (const environment of [...initializedEnvironments].reverse()) {
4253
+ try {
4254
+ await environment.close();
4255
+ } catch (error) {
4256
+ if (!closeFailed) {
4257
+ closeFailed = true;
4258
+ firstCloseError = error;
4259
+ }
4260
+ const closeError = error instanceof Error ? error : new Error(String(error));
4261
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
4262
+ error: closeError
4263
+ });
4264
+ }
4265
+ }
4266
+ if (closeFailed && !buildFailed) {
4267
+ throw firstCloseError;
4054
4268
  }
4055
4269
  }
4056
4270
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
@@ -4063,83 +4277,130 @@ nasti v${"2.1.0"} `) + import_picocolors6.default.green(`building for ${config.m
4063
4277
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4064
4278
  logger.info(import_picocolors6.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors6.default.dim(envSuffix));
4065
4279
  logger.info(import_picocolors6.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4066
- return { output: clientOutput, environments };
4280
+ return { output: clientOutput, environments, environmentResults };
4067
4281
  }
4068
4282
  async function buildClientEnvironment(config) {
4069
4283
  const logger = config.logger;
4070
4284
  const outDir = import_node_path12.default.resolve(config.root, config.build.outDir);
4071
- if (config.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4072
- import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4073
- }
4074
- import_node_fs9.default.mkdirSync(outDir, { recursive: true });
4075
- const html = await readHtmlFile(config.root);
4076
- const entryPoints = resolveClientEntries(config, html);
4077
- if (entryPoints.length === 0) {
4078
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4079
- }
4080
4285
  const cssEngine = createCssEngine();
4081
4286
  const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
4082
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
4287
+ const clientEnv = new NastiEnvironment("client", config, {
4083
4288
  mode: "build",
4084
- plugins: pluginList
4289
+ plugins: pluginList,
4290
+ pluginApi: getPluginApi(config)
4085
4291
  });
4086
4292
  await clientEnv.init();
4087
- const allPlugins = clientEnv.plugins;
4088
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4089
- const rolldownPlugins = [
4090
- createOxcTransformPlugin(config, clientEnv),
4091
- ...toRolldownPlugins(allPlugins),
4092
- ...nativeReporter ? [nativeReporter] : []
4093
- ];
4094
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
4095
- const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4096
- const { output } = await bundle2.write(outputOptions);
4097
- await bundle2.close();
4098
- if (html) {
4099
- let processedHtml = html;
4100
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4101
- for (const p of htmlPlugins) {
4102
- const result = await p.transformIndexHtml(processedHtml);
4103
- if (typeof result === "string") {
4104
- processedHtml = result;
4105
- } else if (result && "html" in result) {
4106
- processedHtml = processHtml(result.html, result.tags);
4107
- } else if (Array.isArray(result)) {
4108
- processedHtml = processHtml(processedHtml, result);
4109
- }
4110
- }
4111
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4112
- for (const chunk of output) {
4113
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4114
- const originalEntry = import_node_path12.default.relative(config.root, chunk.facadeModuleId);
4115
- processedHtml = processedHtml.replace(
4116
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
4117
- `$1${config.base}${chunk.fileName}$3`
4293
+ try {
4294
+ if (clientEnv.driver) {
4295
+ if (!clientEnv.driver.build) {
4296
+ throw new Error(
4297
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
4118
4298
  );
4119
4299
  }
4300
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4301
+ return { environment: clientEnv, result };
4120
4302
  }
4121
- import_node_fs9.default.writeFileSync(import_node_path12.default.resolve(outDir, "index.html"), processedHtml);
4122
- }
4123
- if (!nativeReporter && config.logLevel !== "silent") {
4124
- reportBuildOutput(output, config, logger);
4303
+ if (config.build.emptyOutDir && import_node_fs9.default.existsSync(outDir)) {
4304
+ import_node_fs9.default.rmSync(outDir, { recursive: true, force: true });
4305
+ }
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");
4308
+ const html = await readHtmlFile(config.root, htmlFile);
4309
+ const entryPoints = resolveClientEntries(config, html);
4310
+ if (entryPoints.length === 0) {
4311
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4312
+ }
4313
+ const allPlugins = clientEnv.plugins;
4314
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4315
+ const rolldownPlugins = [
4316
+ createOxcTransformPlugin(config, clientEnv),
4317
+ ...toRolldownPlugins(allPlugins),
4318
+ ...nativeReporter ? [nativeReporter] : []
4319
+ ];
4320
+ const { inputOptions, outputOptions } = getRolldownOptions(
4321
+ clientEnv,
4322
+ entryPoints,
4323
+ rolldownPlugins
4324
+ );
4325
+ const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
4326
+ const { output } = await bundle2.write(outputOptions);
4327
+ await bundle2.close();
4328
+ if (html) {
4329
+ let processedHtml = html;
4330
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4331
+ for (const p of htmlPlugins) {
4332
+ const result = await p.transformIndexHtml(processedHtml);
4333
+ if (typeof result === "string") {
4334
+ processedHtml = result;
4335
+ } else if (result && "html" in result) {
4336
+ processedHtml = processHtml(result.html, result.tags);
4337
+ } else if (Array.isArray(result)) {
4338
+ processedHtml = processHtml(processedHtml, result);
4339
+ }
4340
+ }
4341
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4342
+ for (const chunk of output) {
4343
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4344
+ processedHtml = replaceEntryScript(
4345
+ processedHtml,
4346
+ chunk.facadeModuleId,
4347
+ chunk.fileName,
4348
+ config,
4349
+ htmlFile,
4350
+ config.base
4351
+ );
4352
+ }
4353
+ }
4354
+ import_node_fs9.default.writeFileSync(import_node_path12.default.resolve(outDir, "index.html"), processedHtml);
4355
+ }
4356
+ if (!nativeReporter && config.logLevel !== "silent") {
4357
+ reportBuildOutput(output, config, logger);
4358
+ }
4359
+ warnLargeChunks(output, config, logger);
4360
+ return { environment: clientEnv, result: { output } };
4361
+ } catch (error) {
4362
+ try {
4363
+ await clientEnv.close();
4364
+ } catch (closeError) {
4365
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
4366
+ logger.error("[nasti] failed to close client environment after build failure", {
4367
+ error: normalized
4368
+ });
4369
+ }
4370
+ throw error;
4125
4371
  }
4126
- warnLargeChunks(output, config, logger);
4127
- return output;
4128
4372
  }
4129
4373
  async function buildServerEnvironment(config, name) {
4130
4374
  const envOptions = config.environments[name];
4131
4375
  const logger = config.logger;
4376
+ const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4377
+ const environment = new NastiEnvironment(name, config, {
4378
+ mode: "build",
4379
+ plugins: pluginList,
4380
+ pluginApi: getPluginApi(config)
4381
+ });
4382
+ await environment.init();
4383
+ if (environment.driver) {
4384
+ if (!environment.driver.build) {
4385
+ await environment.close();
4386
+ throw new Error(
4387
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
4388
+ );
4389
+ }
4390
+ try {
4391
+ const result = await environment.driver.build(environment.getDriverContext());
4392
+ return { environment, result };
4393
+ } catch (error) {
4394
+ await environment.close();
4395
+ throw error;
4396
+ }
4397
+ }
4132
4398
  for (const entry of envOptions.entry) {
4133
4399
  if (!import_node_fs9.default.existsSync(entry)) {
4400
+ await environment.close();
4134
4401
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4135
4402
  }
4136
4403
  }
4137
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4138
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
4139
- mode: "build",
4140
- plugins: pluginList
4141
- });
4142
- await environment.init();
4143
4404
  const rolldownPlugins = [
4144
4405
  createOxcTransformPlugin(config, environment),
4145
4406
  ...toRolldownPlugins(environment.plugins)
@@ -4159,7 +4420,7 @@ async function buildServerEnvironment(config, name) {
4159
4420
  logger.info(
4160
4421
  import_picocolors6.default.dim(` [${name}] `) + output.map((o) => import_node_path12.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors6.default.dim(", "))
4161
4422
  );
4162
- return output;
4423
+ return { environment, result: { output } };
4163
4424
  }
4164
4425
  function injectCssLinks(html, cssEngine, config) {
4165
4426
  const cssLinkTags = [];
@@ -4185,6 +4446,25 @@ function injectCssLinks(html, cssEngine, config) {
4185
4446
  function escapeRegExp(string) {
4186
4447
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4187
4448
  }
4449
+ 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("/");
4453
+ const candidates = /* @__PURE__ */ new Set([
4454
+ rootRelative,
4455
+ `/${rootRelative}`,
4456
+ htmlRelative,
4457
+ `./${htmlRelative}`
4458
+ ]);
4459
+ let processed = html;
4460
+ for (const candidate of candidates) {
4461
+ processed = processed.replace(
4462
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
4463
+ `$1${urlPrefix}${fileName}$3`
4464
+ );
4465
+ }
4466
+ return processed;
4467
+ }
4188
4468
  var import_node_path12, import_node_fs9, import_node_module5, import_rolldown, import_picocolors6, debug5, NODE_BUILTINS2;
4189
4469
  var init_build = __esm({
4190
4470
  "src/build/index.ts"() {
@@ -4202,6 +4482,7 @@ var init_build = __esm({
4202
4482
  init_env();
4203
4483
  init_reporter();
4204
4484
  init_debug();
4485
+ init_plugin_api();
4205
4486
  import_picocolors6 = __toESM(require("picocolors"), 1);
4206
4487
  debug5 = createDebugger("nasti:build");
4207
4488
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...import_node_module5.builtinModules, ...import_node_module5.builtinModules.map((m) => `node:${m}`)]);
@@ -4230,7 +4511,7 @@ async function createBundledDevServer(opts) {
4230
4511
  `[nasti] experimental.bundledDev requires rolldown's experimental dev() API (locked to the installed rc; got: ${err.message}). Remove --bundle / experimental.bundledDev to use the default unbundled dev server.`
4231
4512
  );
4232
4513
  }
4233
- const html = await readHtmlFile(config.root);
4514
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4234
4515
  const entryPoints = resolveClientEntries(config, html);
4235
4516
  if (entryPoints.length === 0) {
4236
4517
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4285,7 +4566,10 @@ async function createBundledDevServer(opts) {
4285
4566
  continue;
4286
4567
  }
4287
4568
  const patchPath = `__nasti_patch/${update.filename}`;
4288
- patches.set(patchPath, update.code + "\n;export {}");
4569
+ patches.set(
4570
+ patchPath,
4571
+ update.code + "\n;globalThis.location?.reload();\n;export {}"
4572
+ );
4289
4573
  if (update.sourcemap && update.sourcemapFilename) {
4290
4574
  patches.set(`__nasti_patch/${update.sourcemapFilename}`, update.sourcemap);
4291
4575
  }
@@ -4433,7 +4717,7 @@ async function createBundledDevServer(opts) {
4433
4717
  return;
4434
4718
  }
4435
4719
  if (pathname === "/" || pathname.endsWith(".html")) {
4436
- const rawHtml = await readHtmlFile(config.root);
4720
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4437
4721
  if (rawHtml) {
4438
4722
  res.setHeader("Content-Type", "text/html");
4439
4723
  res.setHeader("Cache-Control", "no-store");
@@ -4517,10 +4801,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4517
4801
  }
4518
4802
  }
4519
4803
  for (const [facadeModuleId, fileName] of entryFileNames) {
4520
- const originalEntry = import_node_path13.default.relative(config.root, facadeModuleId);
4521
- processed = processed.replace(
4522
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4523
- `$1/${fileName}$3`
4804
+ processed = replaceEntryScript(
4805
+ processed,
4806
+ facadeModuleId,
4807
+ fileName,
4808
+ config,
4809
+ config.environments.client?.html ?? "index.html",
4810
+ "/"
4524
4811
  );
4525
4812
  }
4526
4813
  return processed;
@@ -4661,10 +4948,12 @@ async function createServer(inlineConfig = {}) {
4661
4948
  const app = (0, import_connect.default)();
4662
4949
  const httpServer = import_node_http.default.createServer(app);
4663
4950
  const ws = createWebSocketServer(httpServer);
4664
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
4951
+ const pluginApi = getPluginApi(config);
4952
+ const clientEnv = new NastiEnvironment("client", config, {
4665
4953
  hot: createWsHotChannel(ws),
4666
4954
  mode: "dev",
4667
- plugins: allPlugins
4955
+ plugins: allPlugins,
4956
+ pluginApi
4668
4957
  });
4669
4958
  await clientEnv.init();
4670
4959
  const environments = { client: clientEnv };
@@ -4672,11 +4961,15 @@ async function createServer(inlineConfig = {}) {
4672
4961
  if (name === "client") continue;
4673
4962
  const consumer = config.environments[name].consumer;
4674
4963
  const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4675
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
4964
+ environments[name] = new NastiEnvironment(name, config, {
4676
4965
  mode: "dev",
4677
- plugins: envPlugins
4966
+ plugins: envPlugins,
4967
+ pluginApi
4678
4968
  });
4679
4969
  }
4970
+ for (const [name, environment] of Object.entries(environments)) {
4971
+ if (name !== "client" && environment.options.driver) await environment.init();
4972
+ }
4680
4973
  let ssrRunner = null;
4681
4974
  async function getSsrRunner() {
4682
4975
  if (ssrRunner) return ssrRunner;
@@ -4701,14 +4994,6 @@ async function createServer(inlineConfig = {}) {
4701
4994
  });
4702
4995
  app.use(bundledServer.middleware);
4703
4996
  }
4704
- app.use(transformMiddleware({
4705
- config: configWithPlugins,
4706
- pluginContainer,
4707
- moduleGraph
4708
- }));
4709
- const publicDir = import_node_path14.default.resolve(config.root, "public");
4710
- app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
4711
- app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4712
4997
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4713
4998
  const outDirAbs = import_node_path14.default.resolve(config.root, config.build.outDir);
4714
4999
  const watcher = (0, import_chokidar.watch)(config.root, {
@@ -4725,13 +5010,72 @@ async function createServer(inlineConfig = {}) {
4725
5010
  ignoreInitial: true
4726
5011
  });
4727
5012
  let server;
5013
+ const environmentServices = {};
5014
+ let environmentDriversStarted = false;
5015
+ const logCloseError = (target, error) => {
5016
+ const normalized = error instanceof Error ? error : new Error(String(error));
5017
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5018
+ };
5019
+ const startEnvironmentDrivers = async () => {
5020
+ if (environmentDriversStarted) return;
5021
+ environmentDriversStarted = true;
5022
+ const started = [];
5023
+ const attempted = [];
5024
+ try {
5025
+ for (const [name, environment] of Object.entries(environments)) {
5026
+ if (!environment.driver?.serve) continue;
5027
+ attempted.push(environment);
5028
+ const result = await environment.driver.serve({
5029
+ ...environment.getDriverContext(),
5030
+ server
5031
+ });
5032
+ started.push({ name, environment, service: result ?? {} });
5033
+ }
5034
+ for (const { name, service } of started) {
5035
+ environmentServices[name] = service;
5036
+ if (service.middleware) app.use(service.middleware);
5037
+ }
5038
+ } catch (error) {
5039
+ environmentDriversStarted = false;
5040
+ for (const { name } of started) {
5041
+ delete environmentServices[name];
5042
+ }
5043
+ for (const environment of attempted.reverse()) {
5044
+ try {
5045
+ await environment.driver?.close?.(environment.getDriverContext());
5046
+ } catch (closeError) {
5047
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5048
+ }
5049
+ }
5050
+ throw error;
5051
+ }
5052
+ };
5053
+ const notifyEnvironmentDrivers = (file, event) => {
5054
+ for (const environment of Object.values(environments)) {
5055
+ if (!environment.driver?.watchChange) continue;
5056
+ void Promise.resolve(
5057
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5058
+ ).catch((error) => {
5059
+ logger.error(
5060
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5061
+ { error }
5062
+ );
5063
+ });
5064
+ }
5065
+ };
4728
5066
  watcher.on("change", (file) => {
4729
5067
  ssrRunner?.invalidateFile(file);
4730
5068
  handleFileChange(file, server);
5069
+ notifyEnvironmentDrivers(file, "change");
4731
5070
  });
4732
5071
  watcher.on("add", (file) => {
4733
5072
  ssrRunner?.invalidateFile(file);
4734
5073
  handleFileChange(file, server);
5074
+ notifyEnvironmentDrivers(file, "add");
5075
+ });
5076
+ watcher.on("unlink", (file) => {
5077
+ ssrRunner?.invalidateFile(file);
5078
+ notifyEnvironmentDrivers(file, "unlink");
4735
5079
  });
4736
5080
  server = {
4737
5081
  config: configWithPlugins,
@@ -4740,10 +5084,12 @@ async function createServer(inlineConfig = {}) {
4740
5084
  watcher,
4741
5085
  ws,
4742
5086
  environments,
5087
+ environmentServices,
4743
5088
  async listen(port) {
4744
5089
  const finalPort = port ?? config.server.port;
4745
5090
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4746
5091
  await pluginContainer.buildStart();
5092
+ await startEnvironmentDrivers();
4747
5093
  return new Promise((resolve, reject) => {
4748
5094
  let currentPort = finalPort;
4749
5095
  const onListening = () => {
@@ -4751,15 +5097,20 @@ async function createServer(inlineConfig = {}) {
4751
5097
  config.server.port = actualPort;
4752
5098
  const localUrl = `http://localhost:${actualPort}/`;
4753
5099
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5100
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5101
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4754
5102
  logger.clearScreen("info");
4755
5103
  const readyIn = Math.ceil(performance.now() - startTime);
4756
5104
  logger.info(
4757
5105
  `
4758
- ${import_picocolors8.default.cyan(import_picocolors8.default.bold("NASTI"))} ${import_picocolors8.default.cyan(`v${"2.1.0"}`)} ${import_picocolors8.default.dim("ready in")} ${import_picocolors8.default.bold(readyIn)} ${import_picocolors8.default.dim("ms")}
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")}
4759
5107
  `
4760
5108
  );
4761
5109
  printServerUrls(
4762
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5110
+ {
5111
+ local: [localUrl, ...driverLocalUrls],
5112
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5113
+ },
4763
5114
  logger.info
4764
5115
  );
4765
5116
  logger.info("");
@@ -4789,11 +5140,62 @@ async function createServer(inlineConfig = {}) {
4789
5140
  async close() {
4790
5141
  await pluginContainer.buildEnd();
4791
5142
  await bundledServer?.close();
4792
- watcher.close();
5143
+ let environmentCloseFailed = false;
5144
+ let firstEnvironmentCloseError;
5145
+ for (const environment of Object.values(environments).reverse()) {
5146
+ try {
5147
+ await environment.close();
5148
+ } catch (error) {
5149
+ if (!environmentCloseFailed) {
5150
+ environmentCloseFailed = true;
5151
+ firstEnvironmentCloseError = error;
5152
+ }
5153
+ logCloseError(`environment "${environment.name}"`, error);
5154
+ }
5155
+ }
5156
+ await watcher.close();
4793
5157
  ws.close();
4794
5158
  httpServer.close();
5159
+ if (environmentCloseFailed) {
5160
+ throw firstEnvironmentCloseError;
5161
+ }
4795
5162
  }
4796
5163
  };
5164
+ try {
5165
+ await startEnvironmentDrivers();
5166
+ } catch (error) {
5167
+ if (bundledServer) {
5168
+ try {
5169
+ await bundledServer.close();
5170
+ } catch (closeError) {
5171
+ logCloseError("bundled dev server after driver startup failure", closeError);
5172
+ }
5173
+ }
5174
+ try {
5175
+ await watcher.close();
5176
+ } catch (closeError) {
5177
+ logCloseError("file watcher after driver startup failure", closeError);
5178
+ }
5179
+ try {
5180
+ ws.close();
5181
+ } catch (closeError) {
5182
+ logCloseError("WebSocket server after driver startup failure", closeError);
5183
+ }
5184
+ try {
5185
+ httpServer.close();
5186
+ } catch (closeError) {
5187
+ logCloseError("HTTP server after driver startup failure", closeError);
5188
+ }
5189
+ throw error;
5190
+ }
5191
+ app.use(transformMiddleware({
5192
+ config: configWithPlugins,
5193
+ pluginContainer,
5194
+ moduleGraph
5195
+ }));
5196
+ const publicDir = import_node_path14.default.resolve(config.root, "public");
5197
+ app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5198
+ app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4797
5199
  const postMiddlewares = [];
4798
5200
  for (const plugin of allPlugins) {
4799
5201
  if (plugin.configureServer) {
@@ -4836,6 +5238,7 @@ var init_server = __esm({
4836
5238
  init_middleware();
4837
5239
  init_hmr();
4838
5240
  init_builtins();
5241
+ init_plugin_api();
4839
5242
  }
4840
5243
  });
4841
5244
 
@@ -4882,6 +5285,7 @@ var init_electron = __esm({
4882
5285
  var electron_exports = {};
4883
5286
  __export(electron_exports, {
4884
5287
  buildElectron: () => buildElectron,
5288
+ createElectronRendererConfig: () => createElectronRendererConfig,
4885
5289
  detectInstalledElectron: () => detectInstalledElectron,
4886
5290
  normalizePreload: () => normalizePreload
4887
5291
  });
@@ -4889,7 +5293,7 @@ async function buildElectron(inlineConfig = {}) {
4889
5293
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4890
5294
  const startTime = performance.now();
4891
5295
  assertElectronVersion(config);
4892
- console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.1.0"}`));
5296
+ console.log(import_picocolors9.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors9.default.dim(` v${"2.3.1"}`));
4893
5297
  console.log(import_picocolors9.default.dim(` root: ${config.root}`));
4894
5298
  console.log(import_picocolors9.default.dim(` mode: ${config.mode}`));
4895
5299
  console.log(import_picocolors9.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -4900,15 +5304,13 @@ async function buildElectron(inlineConfig = {}) {
4900
5304
  import_node_fs10.default.mkdirSync(outDir, { recursive: true });
4901
5305
  const rendererOutDir = import_node_path15.default.join(outDir, "renderer");
4902
5306
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4903
- await build2({
4904
- ...inlineConfig,
4905
- target: "web",
5307
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4906
5308
  build: {
4907
5309
  ...inlineConfig.build,
4908
5310
  outDir: rendererOutDir,
4909
5311
  emptyOutDir: false
4910
5312
  }
4911
- });
5313
+ }));
4912
5314
  const mainEntry = import_node_path15.default.resolve(config.root, config.electron.main);
4913
5315
  if (!import_node_fs10.default.existsSync(mainEntry)) {
4914
5316
  throw new Error(
@@ -4962,7 +5364,8 @@ async function bundleNode(config, entry, opts) {
4962
5364
  const result = transformCode(id, code, {
4963
5365
  sourcemap: !!config.build.sourcemap,
4964
5366
  jsxRuntime: "automatic",
4965
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5367
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5368
+ target: config.electron.nodeTarget
4966
5369
  });
4967
5370
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
4968
5371
  }
@@ -4973,7 +5376,11 @@ async function bundleNode(config, entry, opts) {
4973
5376
  ...restInputOptions,
4974
5377
  input: entry,
4975
5378
  platform: "node",
4976
- transform: { ...userTransform, define: mergedDefine },
5379
+ transform: {
5380
+ ...userTransform,
5381
+ target: config.electron.nodeTarget,
5382
+ define: mergedDefine
5383
+ },
4977
5384
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
4978
5385
  });
4979
5386
  import_node_fs10.default.mkdirSync(import_node_path15.default.dirname(opts.outFile), { recursive: true });
@@ -4990,6 +5397,25 @@ async function bundleNode(config, entry, opts) {
4990
5397
  console.log(import_picocolors9.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path15.default.relative(config.root, opts.outFile)}`));
4991
5398
  return opts.outFile;
4992
5399
  }
5400
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
5401
+ const inlineClient = inlineConfig.environments?.client ?? {};
5402
+ return {
5403
+ ...inlineConfig,
5404
+ ...overrides,
5405
+ root: config.root,
5406
+ mode: config.mode,
5407
+ target: "web",
5408
+ framework: config.framework,
5409
+ base: config.base === "/" ? "./" : config.base,
5410
+ environments: {
5411
+ ...inlineConfig.environments ?? {},
5412
+ client: {
5413
+ ...inlineClient,
5414
+ html: config.electron.renderer
5415
+ }
5416
+ }
5417
+ };
5418
+ }
4993
5419
  function outFileName(outDir, base, format) {
4994
5420
  const ext = format === "cjs" ? ".cjs" : ".mjs";
4995
5421
  return import_node_path15.default.join(outDir, base + ext);
@@ -5039,17 +5465,22 @@ var init_electron2 = __esm({
5039
5465
  // src/server/electron-dev.ts
5040
5466
  var electron_dev_exports = {};
5041
5467
  __export(electron_dev_exports, {
5468
+ electronRendererDevPath: () => electronRendererDevPath,
5042
5469
  startElectronDev: () => startElectronDev
5043
5470
  });
5044
5471
  async function startElectronDev(inlineConfig = {}) {
5045
5472
  const { noSpawn, ...rest } = inlineConfig;
5046
5473
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5047
5474
  warnElectronVersion(config);
5048
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.1.0"}`));
5475
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
5049
5476
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5050
- const server = await createServer2({ ...rest, target: "electron" });
5477
+ const server = await createServer2({
5478
+ ...rest,
5479
+ target: "electron",
5480
+ framework: config.framework
5481
+ });
5051
5482
  await server.listen();
5052
- const devUrl = `http://localhost:${server.config.server.port}/`;
5483
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5053
5484
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5054
5485
  const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5055
5486
  import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
@@ -5171,14 +5602,18 @@ async function compileNode(config, entry, opts) {
5171
5602
  const result = transformCode(id, code, {
5172
5603
  sourcemap: true,
5173
5604
  jsxRuntime: "automatic",
5174
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5605
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5606
+ target: config.electron.nodeTarget
5175
5607
  });
5176
5608
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5177
5609
  }
5178
5610
  };
5179
5611
  const bundle2 = await (0, import_rolldown3.rolldown)({
5180
5612
  input: entry,
5181
- transform: { define: envDefine },
5613
+ transform: {
5614
+ target: config.electron.nodeTarget,
5615
+ define: envDefine
5616
+ },
5182
5617
  platform: "node",
5183
5618
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5184
5619
  });
@@ -5194,6 +5629,10 @@ async function compileNode(config, entry, opts) {
5194
5629
  });
5195
5630
  await bundle2.close();
5196
5631
  }
5632
+ function electronRendererDevPath(renderer) {
5633
+ const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
5634
+ return normalized === "index.html" ? "/" : `/${normalized}`;
5635
+ }
5197
5636
  function resolveElectronBinary(config) {
5198
5637
  if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
5199
5638
  return config.electron.electronPath;
@@ -5398,7 +5837,7 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5398
5837
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5399
5838
  http2.createServer(app).listen(port, host, () => {
5400
5839
  logger.info(`
5401
- ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.1.0"}`)} ${import_picocolors11.default.dim("preview")}
5840
+ ${import_picocolors11.default.cyan(import_picocolors11.default.bold("NASTI"))} ${import_picocolors11.default.cyan(`v${"2.3.1"}`)} ${import_picocolors11.default.dim("preview")}
5402
5841
  `);
5403
5842
  printServerUrls2(
5404
5843
  {
@@ -5415,6 +5854,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5415
5854
  }
5416
5855
  });
5417
5856
  cli.help();
5418
- cli.version("2.1.0");
5857
+ cli.version("2.3.1");
5419
5858
  cli.parse();
5420
5859
  //# sourceMappingURL=cli.cjs.map