@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.js CHANGED
@@ -231,6 +231,92 @@ var init_defaults = __esm({
231
231
  }
232
232
  });
233
233
 
234
+ // src/core/plugin-api.ts
235
+ function orderPlugins(plugins) {
236
+ 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);
237
+ const indexesByName = /* @__PURE__ */ new Map();
238
+ baseline.forEach((plugin, index2) => {
239
+ const indexes = indexesByName.get(plugin.name) ?? [];
240
+ indexes.push(index2);
241
+ indexesByName.set(plugin.name, indexes);
242
+ });
243
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
244
+ const indegree = baseline.map(() => 0);
245
+ const addEdge = (from, to) => {
246
+ if (from === to || edges[from].has(to)) return;
247
+ edges[from].add(to);
248
+ indegree[to]++;
249
+ };
250
+ baseline.forEach((plugin, current) => {
251
+ for (const dependency of plugin.pre ?? []) {
252
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
253
+ }
254
+ for (const dependency of plugin.post ?? []) {
255
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
256
+ }
257
+ });
258
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
259
+ const ordered = [];
260
+ while (ready.length > 0) {
261
+ ready.sort((a, b) => a - b);
262
+ const current = ready.shift();
263
+ ordered.push(baseline[current]);
264
+ for (const next of edges[current]) {
265
+ indegree[next]--;
266
+ if (indegree[next] === 0) ready.push(next);
267
+ }
268
+ }
269
+ if (ordered.length !== baseline.length) {
270
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
271
+ throw new Error(
272
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
273
+ );
274
+ }
275
+ return ordered;
276
+ }
277
+ async function setupPluginApi(config, plugins) {
278
+ const exposed = /* @__PURE__ */ new Map();
279
+ const api = {
280
+ config,
281
+ logger: config.logger,
282
+ expose(key, value) {
283
+ if (exposed.has(key) && exposed.get(key) !== value) {
284
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
285
+ }
286
+ exposed.set(key, value);
287
+ },
288
+ useExposed(key) {
289
+ return exposed.get(key);
290
+ }
291
+ };
292
+ apiByConfig.set(config, api);
293
+ for (const plugin of plugins) {
294
+ await plugin.setup?.(api);
295
+ }
296
+ return api;
297
+ }
298
+ function getPluginApi(config) {
299
+ const api = apiByConfig.get(config);
300
+ if (!api) {
301
+ throw new Error(
302
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
303
+ );
304
+ }
305
+ return api;
306
+ }
307
+ function enforceRank(plugin) {
308
+ if (plugin.enforce === "pre") return 0;
309
+ if (plugin.enforce === "post") return 2;
310
+ return 1;
311
+ }
312
+ var apiByConfig;
313
+ var init_plugin_api = __esm({
314
+ "src/core/plugin-api.ts"() {
315
+ "use strict";
316
+ apiByConfig = /* @__PURE__ */ new WeakMap();
317
+ }
318
+ });
319
+
234
320
  // src/config/index.ts
235
321
  import { pathToFileURL } from "url";
236
322
  import path from "path";
@@ -268,6 +354,43 @@ async function loadConfigFromFile(root) {
268
354
  }
269
355
  return {};
270
356
  }
357
+ function detectFramework(root) {
358
+ const sourceRoot = path.resolve(root, "src");
359
+ if (containsVueFile(sourceRoot)) return "vue";
360
+ const packagePath = path.resolve(root, "package.json");
361
+ if (fs.existsSync(packagePath)) {
362
+ try {
363
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
364
+ const dependencies = {
365
+ ...pkg.dependencies ?? {},
366
+ ...pkg.devDependencies ?? {},
367
+ ...pkg.peerDependencies ?? {},
368
+ ...pkg.optionalDependencies ?? {}
369
+ };
370
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
371
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
372
+ if (hasVue && !hasReact) return "vue";
373
+ if (hasReact) return "react";
374
+ if (hasVue) return "vue";
375
+ } catch {
376
+ }
377
+ }
378
+ return "react";
379
+ }
380
+ function containsVueFile(dir, depth = 0) {
381
+ if (depth > 5 || !fs.existsSync(dir)) return false;
382
+ try {
383
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
384
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
385
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(path.join(dir, entry.name), depth + 1)) {
386
+ return true;
387
+ }
388
+ }
389
+ } catch {
390
+ return false;
391
+ }
392
+ return false;
393
+ }
271
394
  async function loadTsConfig(filePath) {
272
395
  const { transformSync: transformSync2 } = await import("oxc-transform");
273
396
  const code = fs.readFileSync(filePath, "utf-8");
@@ -314,7 +437,7 @@ async function resolveConfig(inlineConfig = {}, command) {
314
437
  base: merged.base ?? defaults.base,
315
438
  mode,
316
439
  target: merged.target ?? defaults.target,
317
- framework: merged.framework ?? defaults.framework,
440
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
318
441
  command,
319
442
  resolve: {
320
443
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -361,7 +484,12 @@ async function resolveConfig(inlineConfig = {}, command) {
361
484
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
362
485
  resolved.environments.client = {
363
486
  consumer,
364
- entry: [],
487
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
488
+ html: path.resolve(
489
+ root,
490
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
491
+ ),
492
+ driver: envOptions.driver,
365
493
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
366
494
  resolve: resolved.resolve,
367
495
  build: resolved.build
@@ -370,7 +498,9 @@ async function resolveConfig(inlineConfig = {}, command) {
370
498
  }
371
499
  resolved.environments[name] = {
372
500
  consumer,
373
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => path.resolve(root, e)),
501
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
502
+ html: envOptions.consumer === "client" && envOptions.html ? path.resolve(root, envOptions.html) : void 0,
503
+ driver: envOptions.driver,
374
504
  resolve: {
375
505
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
376
506
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -389,12 +519,13 @@ async function resolveConfig(inlineConfig = {}, command) {
389
519
  };
390
520
  }
391
521
  assertClientEnvironmentMirror(resolved);
392
- const filteredPlugins = rawPlugins.filter((p) => {
522
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
393
523
  if (!p.apply) return true;
394
524
  if (typeof p.apply === "function") return p.apply(resolved, env);
395
525
  return p.apply === command;
396
- });
526
+ }));
397
527
  resolved.plugins = filteredPlugins;
528
+ await setupPluginApi(resolved, filteredPlugins);
398
529
  if (resolved.target === "electron") {
399
530
  const autoExternal = detectNativeDeps(root);
400
531
  if (autoExternal.length > 0) {
@@ -410,6 +541,10 @@ async function resolveConfig(inlineConfig = {}, command) {
410
541
  }
411
542
  return resolved;
412
543
  }
544
+ function normalizeEnvironmentEntries(entry, root) {
545
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
546
+ return entries.map((item) => path.resolve(root, item));
547
+ }
413
548
  function detectNativeDeps(root) {
414
549
  const result = /* @__PURE__ */ new Set();
415
550
  const pkgJsonPath = path.resolve(root, "package.json");
@@ -527,6 +662,7 @@ var init_config = __esm({
527
662
  "use strict";
528
663
  init_defaults();
529
664
  init_logger();
665
+ init_plugin_api();
530
666
  CONFIG_FILES = [
531
667
  "nasti.config.ts",
532
668
  "nasti.config.js",
@@ -537,21 +673,11 @@ var init_config = __esm({
537
673
  });
538
674
 
539
675
  // src/core/plugin-container.ts
540
- function sortPlugins(plugins) {
541
- const pre = [];
542
- const normal = [];
543
- const post = [];
544
- for (const plugin of plugins) {
545
- if (plugin.enforce === "pre") pre.push(plugin);
546
- else if (plugin.enforce === "post") post.push(plugin);
547
- else normal.push(plugin);
548
- }
549
- return [...pre, ...normal, ...post];
550
- }
551
676
  var PluginContainer;
552
677
  var init_plugin_container = __esm({
553
678
  "src/core/plugin-container.ts"() {
554
679
  "use strict";
680
+ init_plugin_api();
555
681
  PluginContainer = class {
556
682
  plugins;
557
683
  config;
@@ -562,7 +688,7 @@ var init_plugin_container = __esm({
562
688
  constructor(config, environment) {
563
689
  this.config = config;
564
690
  this.environment = environment;
565
- this.plugins = sortPlugins(config.plugins);
691
+ this.plugins = orderPlugins(config.plugins);
566
692
  this.ctx = this.createContext();
567
693
  }
568
694
  createContext() {
@@ -901,6 +1027,7 @@ var init_environment = __esm({
901
1027
  init_module_graph();
902
1028
  init_hot_channel();
903
1029
  init_debug();
1030
+ init_plugin_api();
904
1031
  debug = createDebugger("nasti:environment");
905
1032
  NastiEnvironment = class {
906
1033
  name;
@@ -909,6 +1036,7 @@ var init_environment = __esm({
909
1036
  config;
910
1037
  options;
911
1038
  hot;
1039
+ driver;
912
1040
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
913
1041
  plugins = [];
914
1042
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -916,6 +1044,7 @@ var init_environment = __esm({
916
1044
  /** per-env 模块图(dev 管线使用) */
917
1045
  moduleGraph;
918
1046
  candidatePlugins;
1047
+ pluginApi;
919
1048
  initialized = false;
920
1049
  constructor(name, config, init = {}) {
921
1050
  const options = config.environments[name];
@@ -932,6 +1061,7 @@ var init_environment = __esm({
932
1061
  this.hot = init.hot ?? createNoopHotChannel();
933
1062
  this.moduleGraph = new ModuleGraph();
934
1063
  this.candidatePlugins = init.plugins ?? config.plugins;
1064
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
935
1065
  }
936
1066
  /** 过滤插件并建 per-env PluginContainer */
937
1067
  async init() {
@@ -942,10 +1072,41 @@ var init_environment = __esm({
942
1072
  { ...this.config, plugins: this.plugins },
943
1073
  this
944
1074
  );
1075
+ if (this.options.driver) {
1076
+ const claimed = [];
1077
+ for (const plugin of this.plugins) {
1078
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
1079
+ if (driver) claimed.push({ plugin, driver });
1080
+ }
1081
+ if (claimed.length === 0) {
1082
+ throw new Error(
1083
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
1084
+ );
1085
+ }
1086
+ if (claimed.length > 1) {
1087
+ throw new Error(
1088
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
1089
+ );
1090
+ }
1091
+ this.driver = claimed[0].driver;
1092
+ debug?.(`env "${this.name}" uses driver "${this.driver.name}"`);
1093
+ }
945
1094
  debug?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
946
1095
  }
1096
+ getDriverContext() {
1097
+ return {
1098
+ environment: this,
1099
+ config: this.config,
1100
+ api: this.pluginApi,
1101
+ logger: this.config.logger
1102
+ };
1103
+ }
947
1104
  async close() {
948
- await this.hot.close?.();
1105
+ try {
1106
+ await this.driver?.close?.(this.getDriverContext());
1107
+ } finally {
1108
+ await this.hot.close?.();
1109
+ }
949
1110
  }
950
1111
  };
951
1112
  }
@@ -1010,7 +1171,8 @@ function transformCode(filename, code, options = {}) {
1010
1171
  importSource: options.jsxImportSource ?? "react",
1011
1172
  refresh: options.reactRefresh ?? false
1012
1173
  } : void 0,
1013
- sourcemap: options.sourcemap ?? true
1174
+ sourcemap: options.sourcemap ?? true,
1175
+ target: options.target
1014
1176
  });
1015
1177
  if (result.errors && result.errors.length > 0) {
1016
1178
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -1042,7 +1204,7 @@ function htmlPlugin(config) {
1042
1204
  transformIndexHtml(html) {
1043
1205
  const tags = [];
1044
1206
  if (config.command === "serve") {
1045
- const isReactLike = config.framework === "react" || config.framework === "auto";
1207
+ const isReactLike = config.framework === "react";
1046
1208
  if (isReactLike) {
1047
1209
  tags.push({
1048
1210
  tag: "script",
@@ -1096,8 +1258,8 @@ function serializeTag(tag) {
1096
1258
  }
1097
1259
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
1098
1260
  }
1099
- async function readHtmlFile(root) {
1100
- const htmlPath = path2.resolve(root, "index.html");
1261
+ async function readHtmlFile(root, htmlFile = "index.html") {
1262
+ const htmlPath = path2.isAbsolute(htmlFile) ? htmlFile : path2.resolve(root, htmlFile);
1101
1263
  if (!fs2.existsSync(htmlPath)) return null;
1102
1264
  return fs2.readFileSync(htmlPath, "utf-8");
1103
1265
  }
@@ -1296,7 +1458,10 @@ function transformMiddleware(ctx) {
1296
1458
  return;
1297
1459
  }
1298
1460
  if (url === "/" || url.endsWith(".html")) {
1299
- const html = await readHtmlFile(ctx.config.root);
1461
+ const html = await readHtmlFile(
1462
+ ctx.config.root,
1463
+ ctx.config.environments.client?.html
1464
+ );
1300
1465
  if (html) {
1301
1466
  let processedHtml = html;
1302
1467
  for (const plugin of ctx.config.plugins) {
@@ -1867,7 +2032,7 @@ const hotModulesMap = new Map();
1867
2032
  const disposeMap = new Map();
1868
2033
  const pruneMap = new Map();
1869
2034
 
1870
- socket.addEventListener('message', ({ data }) => {
2035
+ socket.addEventListener('message', async ({ data }) => {
1871
2036
  const payload = JSON.parse(data);
1872
2037
  switch (payload.type) {
1873
2038
  case 'connected':
@@ -1875,14 +2040,21 @@ socket.addEventListener('message', ({ data }) => {
1875
2040
  clearErrorOverlay();
1876
2041
  break;
1877
2042
  case 'update':
1878
- payload.updates.forEach((update) => {
1879
- if (update.type === 'js-update') {
1880
- fetchUpdate(update);
1881
- } else if (update.type === 'css-update') {
1882
- updateCss(update.path);
1883
- }
1884
- });
1885
- clearErrorOverlay();
2043
+ try {
2044
+ await Promise.all(payload.updates.map((update) => {
2045
+ if (update.type === 'js-update') {
2046
+ return fetchUpdate(update);
2047
+ } else if (update.type === 'css-update') {
2048
+ return updateCss(update.path);
2049
+ }
2050
+ }));
2051
+ clearErrorOverlay();
2052
+ console.log('[nasti] HMR update complete, reloading page');
2053
+ location.reload();
2054
+ } catch (err) {
2055
+ console.error('[nasti] HMR update failed:', err);
2056
+ showErrorOverlay(err);
2057
+ }
1886
2058
  break;
1887
2059
  case 'full-reload':
1888
2060
  console.log('[nasti] full reload');
@@ -1924,7 +2096,7 @@ async function fetchUpdate(update) {
1924
2096
  function updateCss(path) {
1925
2097
  const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
1926
2098
  if (el) {
1927
- fetch(path + '?t=' + Date.now())
2099
+ return fetch(path + '?t=' + Date.now())
1928
2100
  .then(r => r.text())
1929
2101
  .then(css => { el.textContent = css; });
1930
2102
  }
@@ -3915,6 +4087,7 @@ var build_exports = {};
3915
4087
  __export(build_exports, {
3916
4088
  build: () => build,
3917
4089
  getRolldownOptions: () => getRolldownOptions,
4090
+ replaceEntryScript: () => replaceEntryScript,
3918
4091
  resolveClientEntries: () => resolveClientEntries,
3919
4092
  toRolldownPlugins: () => toRolldownPlugins
3920
4093
  });
@@ -3998,13 +4171,20 @@ function toRolldownPlugins(plugins) {
3998
4171
  }));
3999
4172
  }
4000
4173
  function resolveClientEntries(config, html) {
4174
+ const configuredEntries = config.environments.client?.entry ?? [];
4175
+ if (configuredEntries.length > 0) return configuredEntries;
4001
4176
  const entryPoints = [];
4177
+ const htmlFile = config.environments.client?.html;
4178
+ const htmlDir = htmlFile ? path12.dirname(htmlFile) : config.root;
4002
4179
  if (html) {
4003
4180
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4004
4181
  for (const match of scriptMatches) {
4005
4182
  const src = match[1];
4006
4183
  if (src && !src.startsWith("http")) {
4007
- entryPoints.push(path12.resolve(config.root, src.replace(/^\//, "")));
4184
+ const cleanSrc = src.split(/[?#]/, 1)[0];
4185
+ entryPoints.push(
4186
+ cleanSrc.startsWith("/") ? path12.resolve(config.root, cleanSrc.replace(/^\//, "")) : path12.resolve(htmlDir, cleanSrc)
4187
+ );
4008
4188
  }
4009
4189
  }
4010
4190
  }
@@ -4040,21 +4220,55 @@ async function build(inlineConfig = {}) {
4040
4220
  const startTime = performance.now();
4041
4221
  logger.info(
4042
4222
  pc6.cyan(`
4043
- nasti v${"2.1.0"} `) + pc6.green(`building for ${config.mode}...`)
4223
+ nasti v${"2.3.1"} `) + pc6.green(`building for ${config.mode}...`)
4044
4224
  );
4045
4225
  debug5?.(`root: ${config.root}`);
4046
4226
  const buildableNames = Object.keys(config.environments).filter(
4047
- (name) => name === "client" || config.environments[name].entry.length > 0
4227
+ (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
4048
4228
  );
4049
4229
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
4050
4230
  const environments = {};
4231
+ const environmentResults = {};
4232
+ const initializedEnvironments = [];
4051
4233
  let clientOutput = [];
4052
- for (const name of buildableNames) {
4053
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4054
- environments[name] = output;
4055
- if (name === "client") clientOutput = output;
4056
- if (buildableNames.length > 1) {
4057
- debug5?.(`environment "${name}" built (${output.length} files)`);
4234
+ let buildFailed = false;
4235
+ try {
4236
+ for (const name of buildableNames) {
4237
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4238
+ initializedEnvironments.push(built.environment);
4239
+ environments[name] = built.result.output;
4240
+ environmentResults[name] = built.result;
4241
+ if (name === "client") clientOutput = built.result.output;
4242
+ if (buildableNames.length > 1) {
4243
+ debug5?.(`environment "${name}" built (${built.result.output.length} files)`);
4244
+ }
4245
+ }
4246
+ const pluginApi = getPluginApi(config);
4247
+ for (const plugin of config.plugins) {
4248
+ await plugin.afterBuildApp?.(environmentResults, pluginApi);
4249
+ }
4250
+ } catch (error) {
4251
+ buildFailed = true;
4252
+ throw error;
4253
+ } finally {
4254
+ let closeFailed = false;
4255
+ let firstCloseError;
4256
+ for (const environment of [...initializedEnvironments].reverse()) {
4257
+ try {
4258
+ await environment.close();
4259
+ } catch (error) {
4260
+ if (!closeFailed) {
4261
+ closeFailed = true;
4262
+ firstCloseError = error;
4263
+ }
4264
+ const closeError = error instanceof Error ? error : new Error(String(error));
4265
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
4266
+ error: closeError
4267
+ });
4268
+ }
4269
+ }
4270
+ if (closeFailed && !buildFailed) {
4271
+ throw firstCloseError;
4058
4272
  }
4059
4273
  }
4060
4274
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
@@ -4067,83 +4281,130 @@ nasti v${"2.1.0"} `) + pc6.green(`building for ${config.mode}...`)
4067
4281
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4068
4282
  logger.info(pc6.green(`\u2713 built in ${elapsed}s`) + pc6.dim(envSuffix));
4069
4283
  logger.info(pc6.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4070
- return { output: clientOutput, environments };
4284
+ return { output: clientOutput, environments, environmentResults };
4071
4285
  }
4072
4286
  async function buildClientEnvironment(config) {
4073
4287
  const logger = config.logger;
4074
4288
  const outDir = path12.resolve(config.root, config.build.outDir);
4075
- if (config.build.emptyOutDir && fs9.existsSync(outDir)) {
4076
- fs9.rmSync(outDir, { recursive: true, force: true });
4077
- }
4078
- fs9.mkdirSync(outDir, { recursive: true });
4079
- const html = await readHtmlFile(config.root);
4080
- const entryPoints = resolveClientEntries(config, html);
4081
- if (entryPoints.length === 0) {
4082
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4083
- }
4084
4289
  const cssEngine = createCssEngine();
4085
4290
  const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
4086
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
4291
+ const clientEnv = new NastiEnvironment("client", config, {
4087
4292
  mode: "build",
4088
- plugins: pluginList
4293
+ plugins: pluginList,
4294
+ pluginApi: getPluginApi(config)
4089
4295
  });
4090
4296
  await clientEnv.init();
4091
- const allPlugins = clientEnv.plugins;
4092
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4093
- const rolldownPlugins = [
4094
- createOxcTransformPlugin(config, clientEnv),
4095
- ...toRolldownPlugins(allPlugins),
4096
- ...nativeReporter ? [nativeReporter] : []
4097
- ];
4098
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
4099
- const bundle2 = await rolldown(inputOptions);
4100
- const { output } = await bundle2.write(outputOptions);
4101
- await bundle2.close();
4102
- if (html) {
4103
- let processedHtml = html;
4104
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4105
- for (const p of htmlPlugins) {
4106
- const result = await p.transformIndexHtml(processedHtml);
4107
- if (typeof result === "string") {
4108
- processedHtml = result;
4109
- } else if (result && "html" in result) {
4110
- processedHtml = processHtml(result.html, result.tags);
4111
- } else if (Array.isArray(result)) {
4112
- processedHtml = processHtml(processedHtml, result);
4113
- }
4114
- }
4115
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4116
- for (const chunk of output) {
4117
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4118
- const originalEntry = path12.relative(config.root, chunk.facadeModuleId);
4119
- processedHtml = processedHtml.replace(
4120
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
4121
- `$1${config.base}${chunk.fileName}$3`
4297
+ try {
4298
+ if (clientEnv.driver) {
4299
+ if (!clientEnv.driver.build) {
4300
+ throw new Error(
4301
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
4122
4302
  );
4123
4303
  }
4304
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4305
+ return { environment: clientEnv, result };
4124
4306
  }
4125
- fs9.writeFileSync(path12.resolve(outDir, "index.html"), processedHtml);
4126
- }
4127
- if (!nativeReporter && config.logLevel !== "silent") {
4128
- reportBuildOutput(output, config, logger);
4307
+ if (config.build.emptyOutDir && fs9.existsSync(outDir)) {
4308
+ fs9.rmSync(outDir, { recursive: true, force: true });
4309
+ }
4310
+ fs9.mkdirSync(outDir, { recursive: true });
4311
+ const htmlFile = config.environments.client.html ?? path12.resolve(config.root, "index.html");
4312
+ const html = await readHtmlFile(config.root, htmlFile);
4313
+ const entryPoints = resolveClientEntries(config, html);
4314
+ if (entryPoints.length === 0) {
4315
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4316
+ }
4317
+ const allPlugins = clientEnv.plugins;
4318
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4319
+ const rolldownPlugins = [
4320
+ createOxcTransformPlugin(config, clientEnv),
4321
+ ...toRolldownPlugins(allPlugins),
4322
+ ...nativeReporter ? [nativeReporter] : []
4323
+ ];
4324
+ const { inputOptions, outputOptions } = getRolldownOptions(
4325
+ clientEnv,
4326
+ entryPoints,
4327
+ rolldownPlugins
4328
+ );
4329
+ const bundle2 = await rolldown(inputOptions);
4330
+ const { output } = await bundle2.write(outputOptions);
4331
+ await bundle2.close();
4332
+ if (html) {
4333
+ let processedHtml = html;
4334
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4335
+ for (const p of htmlPlugins) {
4336
+ const result = await p.transformIndexHtml(processedHtml);
4337
+ if (typeof result === "string") {
4338
+ processedHtml = result;
4339
+ } else if (result && "html" in result) {
4340
+ processedHtml = processHtml(result.html, result.tags);
4341
+ } else if (Array.isArray(result)) {
4342
+ processedHtml = processHtml(processedHtml, result);
4343
+ }
4344
+ }
4345
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4346
+ for (const chunk of output) {
4347
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4348
+ processedHtml = replaceEntryScript(
4349
+ processedHtml,
4350
+ chunk.facadeModuleId,
4351
+ chunk.fileName,
4352
+ config,
4353
+ htmlFile,
4354
+ config.base
4355
+ );
4356
+ }
4357
+ }
4358
+ fs9.writeFileSync(path12.resolve(outDir, "index.html"), processedHtml);
4359
+ }
4360
+ if (!nativeReporter && config.logLevel !== "silent") {
4361
+ reportBuildOutput(output, config, logger);
4362
+ }
4363
+ warnLargeChunks(output, config, logger);
4364
+ return { environment: clientEnv, result: { output } };
4365
+ } catch (error) {
4366
+ try {
4367
+ await clientEnv.close();
4368
+ } catch (closeError) {
4369
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
4370
+ logger.error("[nasti] failed to close client environment after build failure", {
4371
+ error: normalized
4372
+ });
4373
+ }
4374
+ throw error;
4129
4375
  }
4130
- warnLargeChunks(output, config, logger);
4131
- return output;
4132
4376
  }
4133
4377
  async function buildServerEnvironment(config, name) {
4134
4378
  const envOptions = config.environments[name];
4135
4379
  const logger = config.logger;
4380
+ const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4381
+ const environment = new NastiEnvironment(name, config, {
4382
+ mode: "build",
4383
+ plugins: pluginList,
4384
+ pluginApi: getPluginApi(config)
4385
+ });
4386
+ await environment.init();
4387
+ if (environment.driver) {
4388
+ if (!environment.driver.build) {
4389
+ await environment.close();
4390
+ throw new Error(
4391
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
4392
+ );
4393
+ }
4394
+ try {
4395
+ const result = await environment.driver.build(environment.getDriverContext());
4396
+ return { environment, result };
4397
+ } catch (error) {
4398
+ await environment.close();
4399
+ throw error;
4400
+ }
4401
+ }
4136
4402
  for (const entry of envOptions.entry) {
4137
4403
  if (!fs9.existsSync(entry)) {
4404
+ await environment.close();
4138
4405
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4139
4406
  }
4140
4407
  }
4141
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4142
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
4143
- mode: "build",
4144
- plugins: pluginList
4145
- });
4146
- await environment.init();
4147
4408
  const rolldownPlugins = [
4148
4409
  createOxcTransformPlugin(config, environment),
4149
4410
  ...toRolldownPlugins(environment.plugins)
@@ -4163,7 +4424,7 @@ async function buildServerEnvironment(config, name) {
4163
4424
  logger.info(
4164
4425
  pc6.dim(` [${name}] `) + output.map((o) => path12.join(envOptions.build.outDir, o.fileName)).join(pc6.dim(", "))
4165
4426
  );
4166
- return output;
4427
+ return { environment, result: { output } };
4167
4428
  }
4168
4429
  function injectCssLinks(html, cssEngine, config) {
4169
4430
  const cssLinkTags = [];
@@ -4189,6 +4450,25 @@ function injectCssLinks(html, cssEngine, config) {
4189
4450
  function escapeRegExp(string) {
4190
4451
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4191
4452
  }
4453
+ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
4454
+ const rootRelative = path12.relative(config.root, facadeModuleId).split(path12.sep).join("/");
4455
+ const resolvedHtmlFile = path12.resolve(config.root, htmlFile);
4456
+ const htmlRelative = path12.relative(path12.dirname(resolvedHtmlFile), facadeModuleId).split(path12.sep).join("/");
4457
+ const candidates = /* @__PURE__ */ new Set([
4458
+ rootRelative,
4459
+ `/${rootRelative}`,
4460
+ htmlRelative,
4461
+ `./${htmlRelative}`
4462
+ ]);
4463
+ let processed = html;
4464
+ for (const candidate of candidates) {
4465
+ processed = processed.replace(
4466
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
4467
+ `$1${urlPrefix}${fileName}$3`
4468
+ );
4469
+ }
4470
+ return processed;
4471
+ }
4192
4472
  var debug5, NODE_BUILTINS2;
4193
4473
  var init_build = __esm({
4194
4474
  "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
  debug5 = createDebugger("nasti:build");
4206
4487
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...builtinModules2, ...builtinModules2.map((m) => `node:${m}`)]);
4207
4488
  }
@@ -4233,7 +4514,7 @@ async function createBundledDevServer(opts) {
4233
4514
  `[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.`
4234
4515
  );
4235
4516
  }
4236
- const html = await readHtmlFile(config.root);
4517
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4237
4518
  const entryPoints = resolveClientEntries(config, html);
4238
4519
  if (entryPoints.length === 0) {
4239
4520
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4288,7 +4569,10 @@ async function createBundledDevServer(opts) {
4288
4569
  continue;
4289
4570
  }
4290
4571
  const patchPath = `__nasti_patch/${update.filename}`;
4291
- patches.set(patchPath, update.code + "\n;export {}");
4572
+ patches.set(
4573
+ patchPath,
4574
+ update.code + "\n;globalThis.location?.reload();\n;export {}"
4575
+ );
4292
4576
  if (update.sourcemap && update.sourcemapFilename) {
4293
4577
  patches.set(`__nasti_patch/${update.sourcemapFilename}`, update.sourcemap);
4294
4578
  }
@@ -4436,7 +4720,7 @@ async function createBundledDevServer(opts) {
4436
4720
  return;
4437
4721
  }
4438
4722
  if (pathname === "/" || pathname.endsWith(".html")) {
4439
- const rawHtml = await readHtmlFile(config.root);
4723
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4440
4724
  if (rawHtml) {
4441
4725
  res.setHeader("Content-Type", "text/html");
4442
4726
  res.setHeader("Cache-Control", "no-store");
@@ -4520,10 +4804,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4520
4804
  }
4521
4805
  }
4522
4806
  for (const [facadeModuleId, fileName] of entryFileNames) {
4523
- const originalEntry = path13.relative(config.root, facadeModuleId);
4524
- processed = processed.replace(
4525
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4526
- `$1/${fileName}$3`
4807
+ processed = replaceEntryScript(
4808
+ processed,
4809
+ facadeModuleId,
4810
+ fileName,
4811
+ config,
4812
+ config.environments.client?.html ?? "index.html",
4813
+ "/"
4527
4814
  );
4528
4815
  }
4529
4816
  return processed;
@@ -4667,10 +4954,12 @@ async function createServer(inlineConfig = {}) {
4667
4954
  const app = connect();
4668
4955
  const httpServer = http.createServer(app);
4669
4956
  const ws = createWebSocketServer(httpServer);
4670
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
4957
+ const pluginApi = getPluginApi(config);
4958
+ const clientEnv = new NastiEnvironment("client", config, {
4671
4959
  hot: createWsHotChannel(ws),
4672
4960
  mode: "dev",
4673
- plugins: allPlugins
4961
+ plugins: allPlugins,
4962
+ pluginApi
4674
4963
  });
4675
4964
  await clientEnv.init();
4676
4965
  const environments = { client: clientEnv };
@@ -4678,11 +4967,15 @@ async function createServer(inlineConfig = {}) {
4678
4967
  if (name === "client") continue;
4679
4968
  const consumer = config.environments[name].consumer;
4680
4969
  const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4681
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
4970
+ environments[name] = new NastiEnvironment(name, config, {
4682
4971
  mode: "dev",
4683
- plugins: envPlugins
4972
+ plugins: envPlugins,
4973
+ pluginApi
4684
4974
  });
4685
4975
  }
4976
+ for (const [name, environment] of Object.entries(environments)) {
4977
+ if (name !== "client" && environment.options.driver) await environment.init();
4978
+ }
4686
4979
  let ssrRunner = null;
4687
4980
  async function getSsrRunner() {
4688
4981
  if (ssrRunner) return ssrRunner;
@@ -4707,14 +5000,6 @@ async function createServer(inlineConfig = {}) {
4707
5000
  });
4708
5001
  app.use(bundledServer.middleware);
4709
5002
  }
4710
- app.use(transformMiddleware({
4711
- config: configWithPlugins,
4712
- pluginContainer,
4713
- moduleGraph
4714
- }));
4715
- const publicDir = path14.resolve(config.root, "public");
4716
- app.use(sirv(publicDir, { dev: true, etag: true }));
4717
- app.use(sirv(config.root, { dev: true, etag: true }));
4718
5003
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4719
5004
  const outDirAbs = path14.resolve(config.root, config.build.outDir);
4720
5005
  const watcher = watch(config.root, {
@@ -4731,13 +5016,72 @@ async function createServer(inlineConfig = {}) {
4731
5016
  ignoreInitial: true
4732
5017
  });
4733
5018
  let server;
5019
+ const environmentServices = {};
5020
+ let environmentDriversStarted = false;
5021
+ const logCloseError = (target, error) => {
5022
+ const normalized = error instanceof Error ? error : new Error(String(error));
5023
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5024
+ };
5025
+ const startEnvironmentDrivers = async () => {
5026
+ if (environmentDriversStarted) return;
5027
+ environmentDriversStarted = true;
5028
+ const started = [];
5029
+ const attempted = [];
5030
+ try {
5031
+ for (const [name, environment] of Object.entries(environments)) {
5032
+ if (!environment.driver?.serve) continue;
5033
+ attempted.push(environment);
5034
+ const result = await environment.driver.serve({
5035
+ ...environment.getDriverContext(),
5036
+ server
5037
+ });
5038
+ started.push({ name, environment, service: result ?? {} });
5039
+ }
5040
+ for (const { name, service } of started) {
5041
+ environmentServices[name] = service;
5042
+ if (service.middleware) app.use(service.middleware);
5043
+ }
5044
+ } catch (error) {
5045
+ environmentDriversStarted = false;
5046
+ for (const { name } of started) {
5047
+ delete environmentServices[name];
5048
+ }
5049
+ for (const environment of attempted.reverse()) {
5050
+ try {
5051
+ await environment.driver?.close?.(environment.getDriverContext());
5052
+ } catch (closeError) {
5053
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5054
+ }
5055
+ }
5056
+ throw error;
5057
+ }
5058
+ };
5059
+ const notifyEnvironmentDrivers = (file, event) => {
5060
+ for (const environment of Object.values(environments)) {
5061
+ if (!environment.driver?.watchChange) continue;
5062
+ void Promise.resolve(
5063
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5064
+ ).catch((error) => {
5065
+ logger.error(
5066
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5067
+ { error }
5068
+ );
5069
+ });
5070
+ }
5071
+ };
4734
5072
  watcher.on("change", (file) => {
4735
5073
  ssrRunner?.invalidateFile(file);
4736
5074
  handleFileChange(file, server);
5075
+ notifyEnvironmentDrivers(file, "change");
4737
5076
  });
4738
5077
  watcher.on("add", (file) => {
4739
5078
  ssrRunner?.invalidateFile(file);
4740
5079
  handleFileChange(file, server);
5080
+ notifyEnvironmentDrivers(file, "add");
5081
+ });
5082
+ watcher.on("unlink", (file) => {
5083
+ ssrRunner?.invalidateFile(file);
5084
+ notifyEnvironmentDrivers(file, "unlink");
4741
5085
  });
4742
5086
  server = {
4743
5087
  config: configWithPlugins,
@@ -4746,10 +5090,12 @@ async function createServer(inlineConfig = {}) {
4746
5090
  watcher,
4747
5091
  ws,
4748
5092
  environments,
5093
+ environmentServices,
4749
5094
  async listen(port) {
4750
5095
  const finalPort = port ?? config.server.port;
4751
5096
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4752
5097
  await pluginContainer.buildStart();
5098
+ await startEnvironmentDrivers();
4753
5099
  return new Promise((resolve, reject) => {
4754
5100
  let currentPort = finalPort;
4755
5101
  const onListening = () => {
@@ -4757,15 +5103,20 @@ async function createServer(inlineConfig = {}) {
4757
5103
  config.server.port = actualPort;
4758
5104
  const localUrl = `http://localhost:${actualPort}/`;
4759
5105
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5106
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5107
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4760
5108
  logger.clearScreen("info");
4761
5109
  const readyIn = Math.ceil(performance.now() - startTime);
4762
5110
  logger.info(
4763
5111
  `
4764
- ${pc8.cyan(pc8.bold("NASTI"))} ${pc8.cyan(`v${"2.1.0"}`)} ${pc8.dim("ready in")} ${pc8.bold(readyIn)} ${pc8.dim("ms")}
5112
+ ${pc8.cyan(pc8.bold("NASTI"))} ${pc8.cyan(`v${"2.3.1"}`)} ${pc8.dim("ready in")} ${pc8.bold(readyIn)} ${pc8.dim("ms")}
4765
5113
  `
4766
5114
  );
4767
5115
  printServerUrls(
4768
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5116
+ {
5117
+ local: [localUrl, ...driverLocalUrls],
5118
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5119
+ },
4769
5120
  logger.info
4770
5121
  );
4771
5122
  logger.info("");
@@ -4795,11 +5146,62 @@ async function createServer(inlineConfig = {}) {
4795
5146
  async close() {
4796
5147
  await pluginContainer.buildEnd();
4797
5148
  await bundledServer?.close();
4798
- watcher.close();
5149
+ let environmentCloseFailed = false;
5150
+ let firstEnvironmentCloseError;
5151
+ for (const environment of Object.values(environments).reverse()) {
5152
+ try {
5153
+ await environment.close();
5154
+ } catch (error) {
5155
+ if (!environmentCloseFailed) {
5156
+ environmentCloseFailed = true;
5157
+ firstEnvironmentCloseError = error;
5158
+ }
5159
+ logCloseError(`environment "${environment.name}"`, error);
5160
+ }
5161
+ }
5162
+ await watcher.close();
4799
5163
  ws.close();
4800
5164
  httpServer.close();
5165
+ if (environmentCloseFailed) {
5166
+ throw firstEnvironmentCloseError;
5167
+ }
4801
5168
  }
4802
5169
  };
5170
+ try {
5171
+ await startEnvironmentDrivers();
5172
+ } catch (error) {
5173
+ if (bundledServer) {
5174
+ try {
5175
+ await bundledServer.close();
5176
+ } catch (closeError) {
5177
+ logCloseError("bundled dev server after driver startup failure", closeError);
5178
+ }
5179
+ }
5180
+ try {
5181
+ await watcher.close();
5182
+ } catch (closeError) {
5183
+ logCloseError("file watcher after driver startup failure", closeError);
5184
+ }
5185
+ try {
5186
+ ws.close();
5187
+ } catch (closeError) {
5188
+ logCloseError("WebSocket server after driver startup failure", closeError);
5189
+ }
5190
+ try {
5191
+ httpServer.close();
5192
+ } catch (closeError) {
5193
+ logCloseError("HTTP server after driver startup failure", closeError);
5194
+ }
5195
+ throw error;
5196
+ }
5197
+ app.use(transformMiddleware({
5198
+ config: configWithPlugins,
5199
+ pluginContainer,
5200
+ moduleGraph
5201
+ }));
5202
+ const publicDir = path14.resolve(config.root, "public");
5203
+ app.use(sirv(publicDir, { dev: true, etag: true }));
5204
+ app.use(sirv(config.root, { dev: true, etag: true }));
4803
5205
  const postMiddlewares = [];
4804
5206
  for (const plugin of allPlugins) {
4805
5207
  if (plugin.configureServer) {
@@ -4834,6 +5236,7 @@ var init_server = __esm({
4834
5236
  init_middleware();
4835
5237
  init_hmr();
4836
5238
  init_builtins();
5239
+ init_plugin_api();
4837
5240
  }
4838
5241
  });
4839
5242
 
@@ -4880,6 +5283,7 @@ var init_electron = __esm({
4880
5283
  var electron_exports = {};
4881
5284
  __export(electron_exports, {
4882
5285
  buildElectron: () => buildElectron,
5286
+ createElectronRendererConfig: () => createElectronRendererConfig,
4883
5287
  detectInstalledElectron: () => detectInstalledElectron,
4884
5288
  normalizePreload: () => normalizePreload
4885
5289
  });
@@ -4891,7 +5295,7 @@ async function buildElectron(inlineConfig = {}) {
4891
5295
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4892
5296
  const startTime = performance.now();
4893
5297
  assertElectronVersion(config);
4894
- console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.1.0"}`));
5298
+ console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.3.1"}`));
4895
5299
  console.log(pc9.dim(` root: ${config.root}`));
4896
5300
  console.log(pc9.dim(` mode: ${config.mode}`));
4897
5301
  console.log(pc9.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -4902,15 +5306,13 @@ async function buildElectron(inlineConfig = {}) {
4902
5306
  fs10.mkdirSync(outDir, { recursive: true });
4903
5307
  const rendererOutDir = path15.join(outDir, "renderer");
4904
5308
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4905
- await build2({
4906
- ...inlineConfig,
4907
- target: "web",
5309
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4908
5310
  build: {
4909
5311
  ...inlineConfig.build,
4910
5312
  outDir: rendererOutDir,
4911
5313
  emptyOutDir: false
4912
5314
  }
4913
- });
5315
+ }));
4914
5316
  const mainEntry = path15.resolve(config.root, config.electron.main);
4915
5317
  if (!fs10.existsSync(mainEntry)) {
4916
5318
  throw new Error(
@@ -4964,7 +5366,8 @@ async function bundleNode(config, entry, opts) {
4964
5366
  const result = transformCode(id, code, {
4965
5367
  sourcemap: !!config.build.sourcemap,
4966
5368
  jsxRuntime: "automatic",
4967
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5369
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5370
+ target: config.electron.nodeTarget
4968
5371
  });
4969
5372
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
4970
5373
  }
@@ -4975,7 +5378,11 @@ async function bundleNode(config, entry, opts) {
4975
5378
  ...restInputOptions,
4976
5379
  input: entry,
4977
5380
  platform: "node",
4978
- transform: { ...userTransform, define: mergedDefine },
5381
+ transform: {
5382
+ ...userTransform,
5383
+ target: config.electron.nodeTarget,
5384
+ define: mergedDefine
5385
+ },
4979
5386
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
4980
5387
  });
4981
5388
  fs10.mkdirSync(path15.dirname(opts.outFile), { recursive: true });
@@ -4992,6 +5399,25 @@ async function bundleNode(config, entry, opts) {
4992
5399
  console.log(pc9.dim(` \u2713 ${opts.label} \u2192 ${path15.relative(config.root, opts.outFile)}`));
4993
5400
  return opts.outFile;
4994
5401
  }
5402
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
5403
+ const inlineClient = inlineConfig.environments?.client ?? {};
5404
+ return {
5405
+ ...inlineConfig,
5406
+ ...overrides,
5407
+ root: config.root,
5408
+ mode: config.mode,
5409
+ target: "web",
5410
+ framework: config.framework,
5411
+ base: config.base === "/" ? "./" : config.base,
5412
+ environments: {
5413
+ ...inlineConfig.environments ?? {},
5414
+ client: {
5415
+ ...inlineClient,
5416
+ html: config.electron.renderer
5417
+ }
5418
+ }
5419
+ };
5420
+ }
4995
5421
  function outFileName(outDir, base, format) {
4996
5422
  const ext = format === "cjs" ? ".cjs" : ".mjs";
4997
5423
  return path15.join(outDir, base + ext);
@@ -5036,6 +5462,7 @@ var init_electron2 = __esm({
5036
5462
  // src/server/electron-dev.ts
5037
5463
  var electron_dev_exports = {};
5038
5464
  __export(electron_dev_exports, {
5465
+ electronRendererDevPath: () => electronRendererDevPath,
5039
5466
  startElectronDev: () => startElectronDev
5040
5467
  });
5041
5468
  import path16 from "path";
@@ -5049,11 +5476,15 @@ async function startElectronDev(inlineConfig = {}) {
5049
5476
  const { noSpawn, ...rest } = inlineConfig;
5050
5477
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5051
5478
  warnElectronVersion(config);
5052
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.1.0"}`));
5479
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.3.1"}`));
5053
5480
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5054
- const server = await createServer2({ ...rest, target: "electron" });
5481
+ const server = await createServer2({
5482
+ ...rest,
5483
+ target: "electron",
5484
+ framework: config.framework
5485
+ });
5055
5486
  await server.listen();
5056
- const devUrl = `http://localhost:${server.config.server.port}/`;
5487
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5057
5488
  console.log(pc10.dim(` renderer: ${devUrl}`));
5058
5489
  const stageDir = path16.resolve(config.root, ".nasti");
5059
5490
  fs11.mkdirSync(stageDir, { recursive: true });
@@ -5175,14 +5606,18 @@ async function compileNode(config, entry, opts) {
5175
5606
  const result = transformCode(id, code, {
5176
5607
  sourcemap: true,
5177
5608
  jsxRuntime: "automatic",
5178
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5609
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5610
+ target: config.electron.nodeTarget
5179
5611
  });
5180
5612
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5181
5613
  }
5182
5614
  };
5183
5615
  const bundle2 = await rolldown3({
5184
5616
  input: entry,
5185
- transform: { define: envDefine },
5617
+ transform: {
5618
+ target: config.electron.nodeTarget,
5619
+ define: envDefine
5620
+ },
5186
5621
  platform: "node",
5187
5622
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5188
5623
  });
@@ -5198,6 +5633,10 @@ async function compileNode(config, entry, opts) {
5198
5633
  });
5199
5634
  await bundle2.close();
5200
5635
  }
5636
+ function electronRendererDevPath(renderer) {
5637
+ const normalized = renderer.split(path16.sep).join("/").replace(/^\.?\//, "");
5638
+ return normalized === "index.html" ? "/" : `/${normalized}`;
5639
+ }
5201
5640
  function resolveElectronBinary(config) {
5202
5641
  if (config.electron.electronPath && fs11.existsSync(config.electron.electronPath)) {
5203
5642
  return config.electron.electronPath;
@@ -5394,7 +5833,7 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5394
5833
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5395
5834
  http2.createServer(app).listen(port, host, () => {
5396
5835
  logger.info(`
5397
- ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.1.0"}`)} ${pc11.dim("preview")}
5836
+ ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.3.1"}`)} ${pc11.dim("preview")}
5398
5837
  `);
5399
5838
  printServerUrls2(
5400
5839
  {
@@ -5411,6 +5850,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5411
5850
  }
5412
5851
  });
5413
5852
  cli.help();
5414
- cli.version("2.1.0");
5853
+ cli.version("2.3.1");
5415
5854
  cli.parse();
5416
5855
  //# sourceMappingURL=cli.js.map