@nasti-toolchain/nasti 2.2.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) {
@@ -3922,6 +4087,7 @@ var build_exports = {};
3922
4087
  __export(build_exports, {
3923
4088
  build: () => build,
3924
4089
  getRolldownOptions: () => getRolldownOptions,
4090
+ replaceEntryScript: () => replaceEntryScript,
3925
4091
  resolveClientEntries: () => resolveClientEntries,
3926
4092
  toRolldownPlugins: () => toRolldownPlugins
3927
4093
  });
@@ -4005,13 +4171,20 @@ function toRolldownPlugins(plugins) {
4005
4171
  }));
4006
4172
  }
4007
4173
  function resolveClientEntries(config, html) {
4174
+ const configuredEntries = config.environments.client?.entry ?? [];
4175
+ if (configuredEntries.length > 0) return configuredEntries;
4008
4176
  const entryPoints = [];
4177
+ const htmlFile = config.environments.client?.html;
4178
+ const htmlDir = htmlFile ? path12.dirname(htmlFile) : config.root;
4009
4179
  if (html) {
4010
4180
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
4011
4181
  for (const match of scriptMatches) {
4012
4182
  const src = match[1];
4013
4183
  if (src && !src.startsWith("http")) {
4014
- 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
+ );
4015
4188
  }
4016
4189
  }
4017
4190
  }
@@ -4047,21 +4220,55 @@ async function build(inlineConfig = {}) {
4047
4220
  const startTime = performance.now();
4048
4221
  logger.info(
4049
4222
  pc6.cyan(`
4050
- nasti v${"2.2.0"} `) + pc6.green(`building for ${config.mode}...`)
4223
+ nasti v${"2.3.1"} `) + pc6.green(`building for ${config.mode}...`)
4051
4224
  );
4052
4225
  debug5?.(`root: ${config.root}`);
4053
4226
  const buildableNames = Object.keys(config.environments).filter(
4054
- (name) => name === "client" || config.environments[name].entry.length > 0
4227
+ (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
4055
4228
  );
4056
4229
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
4057
4230
  const environments = {};
4231
+ const environmentResults = {};
4232
+ const initializedEnvironments = [];
4058
4233
  let clientOutput = [];
4059
- for (const name of buildableNames) {
4060
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
4061
- environments[name] = output;
4062
- if (name === "client") clientOutput = output;
4063
- if (buildableNames.length > 1) {
4064
- 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;
4065
4272
  }
4066
4273
  }
4067
4274
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
@@ -4074,83 +4281,130 @@ nasti v${"2.2.0"} `) + pc6.green(`building for ${config.mode}...`)
4074
4281
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
4075
4282
  logger.info(pc6.green(`\u2713 built in ${elapsed}s`) + pc6.dim(envSuffix));
4076
4283
  logger.info(pc6.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
4077
- return { output: clientOutput, environments };
4284
+ return { output: clientOutput, environments, environmentResults };
4078
4285
  }
4079
4286
  async function buildClientEnvironment(config) {
4080
4287
  const logger = config.logger;
4081
4288
  const outDir = path12.resolve(config.root, config.build.outDir);
4082
- if (config.build.emptyOutDir && fs9.existsSync(outDir)) {
4083
- fs9.rmSync(outDir, { recursive: true, force: true });
4084
- }
4085
- fs9.mkdirSync(outDir, { recursive: true });
4086
- const html = await readHtmlFile(config.root);
4087
- const entryPoints = resolveClientEntries(config, html);
4088
- if (entryPoints.length === 0) {
4089
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
4090
- }
4091
4289
  const cssEngine = createCssEngine();
4092
4290
  const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
4093
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
4291
+ const clientEnv = new NastiEnvironment("client", config, {
4094
4292
  mode: "build",
4095
- plugins: pluginList
4293
+ plugins: pluginList,
4294
+ pluginApi: getPluginApi(config)
4096
4295
  });
4097
4296
  await clientEnv.init();
4098
- const allPlugins = clientEnv.plugins;
4099
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
4100
- const rolldownPlugins = [
4101
- createOxcTransformPlugin(config, clientEnv),
4102
- ...toRolldownPlugins(allPlugins),
4103
- ...nativeReporter ? [nativeReporter] : []
4104
- ];
4105
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
4106
- const bundle2 = await rolldown(inputOptions);
4107
- const { output } = await bundle2.write(outputOptions);
4108
- await bundle2.close();
4109
- if (html) {
4110
- let processedHtml = html;
4111
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
4112
- for (const p of htmlPlugins) {
4113
- const result = await p.transformIndexHtml(processedHtml);
4114
- if (typeof result === "string") {
4115
- processedHtml = result;
4116
- } else if (result && "html" in result) {
4117
- processedHtml = processHtml(result.html, result.tags);
4118
- } else if (Array.isArray(result)) {
4119
- processedHtml = processHtml(processedHtml, result);
4120
- }
4121
- }
4122
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
4123
- for (const chunk of output) {
4124
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
4125
- const originalEntry = path12.relative(config.root, chunk.facadeModuleId);
4126
- processedHtml = processedHtml.replace(
4127
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
4128
- `$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()`
4129
4302
  );
4130
4303
  }
4304
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
4305
+ return { environment: clientEnv, result };
4131
4306
  }
4132
- fs9.writeFileSync(path12.resolve(outDir, "index.html"), processedHtml);
4133
- }
4134
- if (!nativeReporter && config.logLevel !== "silent") {
4135
- 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;
4136
4375
  }
4137
- warnLargeChunks(output, config, logger);
4138
- return output;
4139
4376
  }
4140
4377
  async function buildServerEnvironment(config, name) {
4141
4378
  const envOptions = config.environments[name];
4142
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
+ }
4143
4402
  for (const entry of envOptions.entry) {
4144
4403
  if (!fs9.existsSync(entry)) {
4404
+ await environment.close();
4145
4405
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
4146
4406
  }
4147
4407
  }
4148
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
4149
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
4150
- mode: "build",
4151
- plugins: pluginList
4152
- });
4153
- await environment.init();
4154
4408
  const rolldownPlugins = [
4155
4409
  createOxcTransformPlugin(config, environment),
4156
4410
  ...toRolldownPlugins(environment.plugins)
@@ -4170,7 +4424,7 @@ async function buildServerEnvironment(config, name) {
4170
4424
  logger.info(
4171
4425
  pc6.dim(` [${name}] `) + output.map((o) => path12.join(envOptions.build.outDir, o.fileName)).join(pc6.dim(", "))
4172
4426
  );
4173
- return output;
4427
+ return { environment, result: { output } };
4174
4428
  }
4175
4429
  function injectCssLinks(html, cssEngine, config) {
4176
4430
  const cssLinkTags = [];
@@ -4196,6 +4450,25 @@ function injectCssLinks(html, cssEngine, config) {
4196
4450
  function escapeRegExp(string) {
4197
4451
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4198
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
+ }
4199
4472
  var debug5, NODE_BUILTINS2;
4200
4473
  var init_build = __esm({
4201
4474
  "src/build/index.ts"() {
@@ -4209,6 +4482,7 @@ var init_build = __esm({
4209
4482
  init_env();
4210
4483
  init_reporter();
4211
4484
  init_debug();
4485
+ init_plugin_api();
4212
4486
  debug5 = createDebugger("nasti:build");
4213
4487
  NODE_BUILTINS2 = /* @__PURE__ */ new Set([...builtinModules2, ...builtinModules2.map((m) => `node:${m}`)]);
4214
4488
  }
@@ -4240,7 +4514,7 @@ async function createBundledDevServer(opts) {
4240
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.`
4241
4515
  );
4242
4516
  }
4243
- const html = await readHtmlFile(config.root);
4517
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4244
4518
  const entryPoints = resolveClientEntries(config, html);
4245
4519
  if (entryPoints.length === 0) {
4246
4520
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4446,7 +4720,7 @@ async function createBundledDevServer(opts) {
4446
4720
  return;
4447
4721
  }
4448
4722
  if (pathname === "/" || pathname.endsWith(".html")) {
4449
- const rawHtml = await readHtmlFile(config.root);
4723
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4450
4724
  if (rawHtml) {
4451
4725
  res.setHeader("Content-Type", "text/html");
4452
4726
  res.setHeader("Cache-Control", "no-store");
@@ -4530,10 +4804,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4530
4804
  }
4531
4805
  }
4532
4806
  for (const [facadeModuleId, fileName] of entryFileNames) {
4533
- const originalEntry = path13.relative(config.root, facadeModuleId);
4534
- processed = processed.replace(
4535
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4536
- `$1/${fileName}$3`
4807
+ processed = replaceEntryScript(
4808
+ processed,
4809
+ facadeModuleId,
4810
+ fileName,
4811
+ config,
4812
+ config.environments.client?.html ?? "index.html",
4813
+ "/"
4537
4814
  );
4538
4815
  }
4539
4816
  return processed;
@@ -4677,10 +4954,12 @@ async function createServer(inlineConfig = {}) {
4677
4954
  const app = connect();
4678
4955
  const httpServer = http.createServer(app);
4679
4956
  const ws = createWebSocketServer(httpServer);
4680
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
4957
+ const pluginApi = getPluginApi(config);
4958
+ const clientEnv = new NastiEnvironment("client", config, {
4681
4959
  hot: createWsHotChannel(ws),
4682
4960
  mode: "dev",
4683
- plugins: allPlugins
4961
+ plugins: allPlugins,
4962
+ pluginApi
4684
4963
  });
4685
4964
  await clientEnv.init();
4686
4965
  const environments = { client: clientEnv };
@@ -4688,11 +4967,15 @@ async function createServer(inlineConfig = {}) {
4688
4967
  if (name === "client") continue;
4689
4968
  const consumer = config.environments[name].consumer;
4690
4969
  const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4691
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
4970
+ environments[name] = new NastiEnvironment(name, config, {
4692
4971
  mode: "dev",
4693
- plugins: envPlugins
4972
+ plugins: envPlugins,
4973
+ pluginApi
4694
4974
  });
4695
4975
  }
4976
+ for (const [name, environment] of Object.entries(environments)) {
4977
+ if (name !== "client" && environment.options.driver) await environment.init();
4978
+ }
4696
4979
  let ssrRunner = null;
4697
4980
  async function getSsrRunner() {
4698
4981
  if (ssrRunner) return ssrRunner;
@@ -4717,14 +5000,6 @@ async function createServer(inlineConfig = {}) {
4717
5000
  });
4718
5001
  app.use(bundledServer.middleware);
4719
5002
  }
4720
- app.use(transformMiddleware({
4721
- config: configWithPlugins,
4722
- pluginContainer,
4723
- moduleGraph
4724
- }));
4725
- const publicDir = path14.resolve(config.root, "public");
4726
- app.use(sirv(publicDir, { dev: true, etag: true }));
4727
- app.use(sirv(config.root, { dev: true, etag: true }));
4728
5003
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4729
5004
  const outDirAbs = path14.resolve(config.root, config.build.outDir);
4730
5005
  const watcher = watch(config.root, {
@@ -4741,13 +5016,72 @@ async function createServer(inlineConfig = {}) {
4741
5016
  ignoreInitial: true
4742
5017
  });
4743
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
+ };
4744
5072
  watcher.on("change", (file) => {
4745
5073
  ssrRunner?.invalidateFile(file);
4746
5074
  handleFileChange(file, server);
5075
+ notifyEnvironmentDrivers(file, "change");
4747
5076
  });
4748
5077
  watcher.on("add", (file) => {
4749
5078
  ssrRunner?.invalidateFile(file);
4750
5079
  handleFileChange(file, server);
5080
+ notifyEnvironmentDrivers(file, "add");
5081
+ });
5082
+ watcher.on("unlink", (file) => {
5083
+ ssrRunner?.invalidateFile(file);
5084
+ notifyEnvironmentDrivers(file, "unlink");
4751
5085
  });
4752
5086
  server = {
4753
5087
  config: configWithPlugins,
@@ -4756,10 +5090,12 @@ async function createServer(inlineConfig = {}) {
4756
5090
  watcher,
4757
5091
  ws,
4758
5092
  environments,
5093
+ environmentServices,
4759
5094
  async listen(port) {
4760
5095
  const finalPort = port ?? config.server.port;
4761
5096
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4762
5097
  await pluginContainer.buildStart();
5098
+ await startEnvironmentDrivers();
4763
5099
  return new Promise((resolve, reject) => {
4764
5100
  let currentPort = finalPort;
4765
5101
  const onListening = () => {
@@ -4767,15 +5103,20 @@ async function createServer(inlineConfig = {}) {
4767
5103
  config.server.port = actualPort;
4768
5104
  const localUrl = `http://localhost:${actualPort}/`;
4769
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 ?? []);
4770
5108
  logger.clearScreen("info");
4771
5109
  const readyIn = Math.ceil(performance.now() - startTime);
4772
5110
  logger.info(
4773
5111
  `
4774
- ${pc8.cyan(pc8.bold("NASTI"))} ${pc8.cyan(`v${"2.2.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")}
4775
5113
  `
4776
5114
  );
4777
5115
  printServerUrls(
4778
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5116
+ {
5117
+ local: [localUrl, ...driverLocalUrls],
5118
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5119
+ },
4779
5120
  logger.info
4780
5121
  );
4781
5122
  logger.info("");
@@ -4805,11 +5146,62 @@ async function createServer(inlineConfig = {}) {
4805
5146
  async close() {
4806
5147
  await pluginContainer.buildEnd();
4807
5148
  await bundledServer?.close();
4808
- 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();
4809
5163
  ws.close();
4810
5164
  httpServer.close();
5165
+ if (environmentCloseFailed) {
5166
+ throw firstEnvironmentCloseError;
5167
+ }
4811
5168
  }
4812
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 }));
4813
5205
  const postMiddlewares = [];
4814
5206
  for (const plugin of allPlugins) {
4815
5207
  if (plugin.configureServer) {
@@ -4844,6 +5236,7 @@ var init_server = __esm({
4844
5236
  init_middleware();
4845
5237
  init_hmr();
4846
5238
  init_builtins();
5239
+ init_plugin_api();
4847
5240
  }
4848
5241
  });
4849
5242
 
@@ -4890,6 +5283,7 @@ var init_electron = __esm({
4890
5283
  var electron_exports = {};
4891
5284
  __export(electron_exports, {
4892
5285
  buildElectron: () => buildElectron,
5286
+ createElectronRendererConfig: () => createElectronRendererConfig,
4893
5287
  detectInstalledElectron: () => detectInstalledElectron,
4894
5288
  normalizePreload: () => normalizePreload
4895
5289
  });
@@ -4901,7 +5295,7 @@ async function buildElectron(inlineConfig = {}) {
4901
5295
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4902
5296
  const startTime = performance.now();
4903
5297
  assertElectronVersion(config);
4904
- console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.2.0"}`));
5298
+ console.log(pc9.cyan("\n\u26A1 nasti build (electron)") + pc9.dim(` v${"2.3.1"}`));
4905
5299
  console.log(pc9.dim(` root: ${config.root}`));
4906
5300
  console.log(pc9.dim(` mode: ${config.mode}`));
4907
5301
  console.log(pc9.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -4912,15 +5306,13 @@ async function buildElectron(inlineConfig = {}) {
4912
5306
  fs10.mkdirSync(outDir, { recursive: true });
4913
5307
  const rendererOutDir = path15.join(outDir, "renderer");
4914
5308
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4915
- await build2({
4916
- ...inlineConfig,
4917
- target: "web",
5309
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4918
5310
  build: {
4919
5311
  ...inlineConfig.build,
4920
5312
  outDir: rendererOutDir,
4921
5313
  emptyOutDir: false
4922
5314
  }
4923
- });
5315
+ }));
4924
5316
  const mainEntry = path15.resolve(config.root, config.electron.main);
4925
5317
  if (!fs10.existsSync(mainEntry)) {
4926
5318
  throw new Error(
@@ -4974,7 +5366,8 @@ async function bundleNode(config, entry, opts) {
4974
5366
  const result = transformCode(id, code, {
4975
5367
  sourcemap: !!config.build.sourcemap,
4976
5368
  jsxRuntime: "automatic",
4977
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5369
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5370
+ target: config.electron.nodeTarget
4978
5371
  });
4979
5372
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
4980
5373
  }
@@ -4985,7 +5378,11 @@ async function bundleNode(config, entry, opts) {
4985
5378
  ...restInputOptions,
4986
5379
  input: entry,
4987
5380
  platform: "node",
4988
- transform: { ...userTransform, define: mergedDefine },
5381
+ transform: {
5382
+ ...userTransform,
5383
+ target: config.electron.nodeTarget,
5384
+ define: mergedDefine
5385
+ },
4989
5386
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
4990
5387
  });
4991
5388
  fs10.mkdirSync(path15.dirname(opts.outFile), { recursive: true });
@@ -5002,6 +5399,25 @@ async function bundleNode(config, entry, opts) {
5002
5399
  console.log(pc9.dim(` \u2713 ${opts.label} \u2192 ${path15.relative(config.root, opts.outFile)}`));
5003
5400
  return opts.outFile;
5004
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
+ }
5005
5421
  function outFileName(outDir, base, format) {
5006
5422
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5007
5423
  return path15.join(outDir, base + ext);
@@ -5046,6 +5462,7 @@ var init_electron2 = __esm({
5046
5462
  // src/server/electron-dev.ts
5047
5463
  var electron_dev_exports = {};
5048
5464
  __export(electron_dev_exports, {
5465
+ electronRendererDevPath: () => electronRendererDevPath,
5049
5466
  startElectronDev: () => startElectronDev
5050
5467
  });
5051
5468
  import path16 from "path";
@@ -5059,11 +5476,15 @@ async function startElectronDev(inlineConfig = {}) {
5059
5476
  const { noSpawn, ...rest } = inlineConfig;
5060
5477
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5061
5478
  warnElectronVersion(config);
5062
- console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.2.0"}`));
5479
+ console.log(pc10.cyan("\n\u26A1 nasti electron dev") + pc10.dim(` v${"2.3.1"}`));
5063
5480
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5064
- const server = await createServer2({ ...rest, target: "electron" });
5481
+ const server = await createServer2({
5482
+ ...rest,
5483
+ target: "electron",
5484
+ framework: config.framework
5485
+ });
5065
5486
  await server.listen();
5066
- const devUrl = `http://localhost:${server.config.server.port}/`;
5487
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5067
5488
  console.log(pc10.dim(` renderer: ${devUrl}`));
5068
5489
  const stageDir = path16.resolve(config.root, ".nasti");
5069
5490
  fs11.mkdirSync(stageDir, { recursive: true });
@@ -5185,14 +5606,18 @@ async function compileNode(config, entry, opts) {
5185
5606
  const result = transformCode(id, code, {
5186
5607
  sourcemap: true,
5187
5608
  jsxRuntime: "automatic",
5188
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5609
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5610
+ target: config.electron.nodeTarget
5189
5611
  });
5190
5612
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5191
5613
  }
5192
5614
  };
5193
5615
  const bundle2 = await rolldown3({
5194
5616
  input: entry,
5195
- transform: { define: envDefine },
5617
+ transform: {
5618
+ target: config.electron.nodeTarget,
5619
+ define: envDefine
5620
+ },
5196
5621
  platform: "node",
5197
5622
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5198
5623
  });
@@ -5208,6 +5633,10 @@ async function compileNode(config, entry, opts) {
5208
5633
  });
5209
5634
  await bundle2.close();
5210
5635
  }
5636
+ function electronRendererDevPath(renderer) {
5637
+ const normalized = renderer.split(path16.sep).join("/").replace(/^\.?\//, "");
5638
+ return normalized === "index.html" ? "/" : `/${normalized}`;
5639
+ }
5211
5640
  function resolveElectronBinary(config) {
5212
5641
  if (config.electron.electronPath && fs11.existsSync(config.electron.electronPath)) {
5213
5642
  return config.electron.electronPath;
@@ -5404,7 +5833,7 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5404
5833
  const host = options.host === true ? "0.0.0.0" : options.host ?? "localhost";
5405
5834
  http2.createServer(app).listen(port, host, () => {
5406
5835
  logger.info(`
5407
- ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.2.0"}`)} ${pc11.dim("preview")}
5836
+ ${pc11.cyan(pc11.bold("NASTI"))} ${pc11.cyan(`v${"2.3.1"}`)} ${pc11.dim("preview")}
5408
5837
  `);
5409
5838
  printServerUrls2(
5410
5839
  {
@@ -5421,6 +5850,6 @@ cli.command("preview [root]", "Preview production build").option("--port <port>"
5421
5850
  }
5422
5851
  });
5423
5852
  cli.help();
5424
- cli.version("2.2.0");
5853
+ cli.version("2.3.1");
5425
5854
  cli.parse();
5426
5855
  //# sourceMappingURL=cli.js.map