@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/index.cjs CHANGED
@@ -221,6 +221,92 @@ var init_logger = __esm({
221
221
  }
222
222
  });
223
223
 
224
+ // src/core/plugin-api.ts
225
+ function orderPlugins(plugins) {
226
+ 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);
227
+ const indexesByName = /* @__PURE__ */ new Map();
228
+ baseline.forEach((plugin, index2) => {
229
+ const indexes = indexesByName.get(plugin.name) ?? [];
230
+ indexes.push(index2);
231
+ indexesByName.set(plugin.name, indexes);
232
+ });
233
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
234
+ const indegree = baseline.map(() => 0);
235
+ const addEdge = (from, to) => {
236
+ if (from === to || edges[from].has(to)) return;
237
+ edges[from].add(to);
238
+ indegree[to]++;
239
+ };
240
+ baseline.forEach((plugin, current) => {
241
+ for (const dependency of plugin.pre ?? []) {
242
+ for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
243
+ }
244
+ for (const dependency of plugin.post ?? []) {
245
+ for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
246
+ }
247
+ });
248
+ const ready = indegree.map((degree, index2) => ({ degree, index: index2 })).filter(({ degree }) => degree === 0).map(({ index: index2 }) => index2);
249
+ const ordered = [];
250
+ while (ready.length > 0) {
251
+ ready.sort((a, b) => a - b);
252
+ const current = ready.shift();
253
+ ordered.push(baseline[current]);
254
+ for (const next of edges[current]) {
255
+ indegree[next]--;
256
+ if (indegree[next] === 0) ready.push(next);
257
+ }
258
+ }
259
+ if (ordered.length !== baseline.length) {
260
+ const cyclic = baseline.filter((_, index2) => indegree[index2] > 0).map((plugin) => plugin.name);
261
+ throw new Error(
262
+ `[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`
263
+ );
264
+ }
265
+ return ordered;
266
+ }
267
+ async function setupPluginApi(config, plugins) {
268
+ const exposed = /* @__PURE__ */ new Map();
269
+ const api = {
270
+ config,
271
+ logger: config.logger,
272
+ expose(key, value) {
273
+ if (exposed.has(key) && exposed.get(key) !== value) {
274
+ throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
275
+ }
276
+ exposed.set(key, value);
277
+ },
278
+ useExposed(key) {
279
+ return exposed.get(key);
280
+ }
281
+ };
282
+ apiByConfig.set(config, api);
283
+ for (const plugin of plugins) {
284
+ await plugin.setup?.(api);
285
+ }
286
+ return api;
287
+ }
288
+ function getPluginApi(config) {
289
+ const api = apiByConfig.get(config);
290
+ if (!api) {
291
+ throw new Error(
292
+ "[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy"
293
+ );
294
+ }
295
+ return api;
296
+ }
297
+ function enforceRank(plugin) {
298
+ if (plugin.enforce === "pre") return 0;
299
+ if (plugin.enforce === "post") return 2;
300
+ return 1;
301
+ }
302
+ var apiByConfig;
303
+ var init_plugin_api = __esm({
304
+ "src/core/plugin-api.ts"() {
305
+ "use strict";
306
+ apiByConfig = /* @__PURE__ */ new WeakMap();
307
+ }
308
+ });
309
+
224
310
  // src/config/index.ts
225
311
  function loadTsconfigPaths(root) {
226
312
  const tsconfigPath = import_node_path.default.resolve(root, "tsconfig.json");
@@ -258,6 +344,43 @@ async function loadConfigFromFile(root) {
258
344
  }
259
345
  return {};
260
346
  }
347
+ function detectFramework(root) {
348
+ const sourceRoot = import_node_path.default.resolve(root, "src");
349
+ if (containsVueFile(sourceRoot)) return "vue";
350
+ const packagePath = import_node_path.default.resolve(root, "package.json");
351
+ if (import_node_fs.default.existsSync(packagePath)) {
352
+ try {
353
+ const pkg = JSON.parse(import_node_fs.default.readFileSync(packagePath, "utf-8"));
354
+ const dependencies = {
355
+ ...pkg.dependencies ?? {},
356
+ ...pkg.devDependencies ?? {},
357
+ ...pkg.peerDependencies ?? {},
358
+ ...pkg.optionalDependencies ?? {}
359
+ };
360
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
361
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
362
+ if (hasVue && !hasReact) return "vue";
363
+ if (hasReact) return "react";
364
+ if (hasVue) return "vue";
365
+ } catch {
366
+ }
367
+ }
368
+ return "react";
369
+ }
370
+ function containsVueFile(dir, depth = 0) {
371
+ if (depth > 5 || !import_node_fs.default.existsSync(dir)) return false;
372
+ try {
373
+ for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
374
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
375
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(import_node_path.default.join(dir, entry.name), depth + 1)) {
376
+ return true;
377
+ }
378
+ }
379
+ } catch {
380
+ return false;
381
+ }
382
+ return false;
383
+ }
261
384
  async function loadTsConfig(filePath) {
262
385
  const { transformSync: transformSync2 } = await import("oxc-transform");
263
386
  const code = import_node_fs.default.readFileSync(filePath, "utf-8");
@@ -304,7 +427,7 @@ async function resolveConfig(inlineConfig = {}, command) {
304
427
  base: merged.base ?? defaults.base,
305
428
  mode,
306
429
  target: merged.target ?? defaults.target,
307
- framework: merged.framework ?? defaults.framework,
430
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
308
431
  command,
309
432
  resolve: {
310
433
  // tsconfig paths 优先级最低:tsconfig < defaults < user config
@@ -351,7 +474,12 @@ async function resolveConfig(inlineConfig = {}, command) {
351
474
  if (envOptions.build) Object.assign(resolved.build, envOptions.build);
352
475
  resolved.environments.client = {
353
476
  consumer,
354
- entry: [],
477
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
478
+ html: import_node_path.default.resolve(
479
+ root,
480
+ envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")
481
+ ),
482
+ driver: envOptions.driver,
355
483
  // 同引用 —— 精确镜像(assertClientEnvironmentMirror 校验)
356
484
  resolve: resolved.resolve,
357
485
  build: resolved.build
@@ -360,7 +488,9 @@ async function resolveConfig(inlineConfig = {}, command) {
360
488
  }
361
489
  resolved.environments[name] = {
362
490
  consumer,
363
- entry: (Array.isArray(envOptions.entry) ? envOptions.entry : envOptions.entry ? [envOptions.entry] : []).map((e) => import_node_path.default.resolve(root, e)),
491
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
492
+ html: envOptions.consumer === "client" && envOptions.html ? import_node_path.default.resolve(root, envOptions.html) : void 0,
493
+ driver: envOptions.driver,
364
494
  resolve: {
365
495
  alias: { ...resolved.resolve.alias, ...envOptions.resolve?.alias },
366
496
  extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
@@ -379,12 +509,13 @@ async function resolveConfig(inlineConfig = {}, command) {
379
509
  };
380
510
  }
381
511
  assertClientEnvironmentMirror(resolved);
382
- const filteredPlugins = rawPlugins.filter((p) => {
512
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
383
513
  if (!p.apply) return true;
384
514
  if (typeof p.apply === "function") return p.apply(resolved, env);
385
515
  return p.apply === command;
386
- });
516
+ }));
387
517
  resolved.plugins = filteredPlugins;
518
+ await setupPluginApi(resolved, filteredPlugins);
388
519
  if (resolved.target === "electron") {
389
520
  const autoExternal = detectNativeDeps(root);
390
521
  if (autoExternal.length > 0) {
@@ -400,6 +531,10 @@ async function resolveConfig(inlineConfig = {}, command) {
400
531
  }
401
532
  return resolved;
402
533
  }
534
+ function normalizeEnvironmentEntries(entry, root) {
535
+ const entries = Array.isArray(entry) ? entry : entry ? [entry] : [];
536
+ return entries.map((item) => import_node_path.default.resolve(root, item));
537
+ }
403
538
  function detectNativeDeps(root) {
404
539
  const result = /* @__PURE__ */ new Set();
405
540
  const pkgJsonPath = import_node_path.default.resolve(root, "package.json");
@@ -520,6 +655,7 @@ var init_config = __esm({
520
655
  import_node_fs = __toESM(require("fs"), 1);
521
656
  init_defaults();
522
657
  init_logger();
658
+ init_plugin_api();
523
659
  CONFIG_FILES = [
524
660
  "nasti.config.ts",
525
661
  "nasti.config.js",
@@ -1915,7 +2051,8 @@ function transformCode(filename, code, options = {}) {
1915
2051
  importSource: options.jsxImportSource ?? "react",
1916
2052
  refresh: options.reactRefresh ?? false
1917
2053
  } : void 0,
1918
- sourcemap: options.sourcemap ?? true
2054
+ sourcemap: options.sourcemap ?? true,
2055
+ target: options.target
1919
2056
  });
1920
2057
  if (result.errors && result.errors.length > 0) {
1921
2058
  const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
@@ -2118,7 +2255,7 @@ function htmlPlugin(config) {
2118
2255
  transformIndexHtml(html) {
2119
2256
  const tags = [];
2120
2257
  if (config.command === "serve") {
2121
- const isReactLike = config.framework === "react" || config.framework === "auto";
2258
+ const isReactLike = config.framework === "react";
2122
2259
  if (isReactLike) {
2123
2260
  tags.push({
2124
2261
  tag: "script",
@@ -2172,8 +2309,8 @@ function serializeTag(tag) {
2172
2309
  }
2173
2310
  return ` <${tag.tag}${attrs}>${children}</${tag.tag}>`;
2174
2311
  }
2175
- async function readHtmlFile(root) {
2176
- const htmlPath = import_node_path6.default.resolve(root, "index.html");
2312
+ async function readHtmlFile(root, htmlFile = "index.html") {
2313
+ const htmlPath = import_node_path6.default.isAbsolute(htmlFile) ? htmlFile : import_node_path6.default.resolve(root, htmlFile);
2177
2314
  if (!import_node_fs4.default.existsSync(htmlPath)) return null;
2178
2315
  return import_node_fs4.default.readFileSync(htmlPath, "utf-8");
2179
2316
  }
@@ -2221,21 +2358,11 @@ var init_builtins = __esm({
2221
2358
  });
2222
2359
 
2223
2360
  // src/core/plugin-container.ts
2224
- function sortPlugins(plugins) {
2225
- const pre = [];
2226
- const normal = [];
2227
- const post = [];
2228
- for (const plugin of plugins) {
2229
- if (plugin.enforce === "pre") pre.push(plugin);
2230
- else if (plugin.enforce === "post") post.push(plugin);
2231
- else normal.push(plugin);
2232
- }
2233
- return [...pre, ...normal, ...post];
2234
- }
2235
2361
  var PluginContainer;
2236
2362
  var init_plugin_container = __esm({
2237
2363
  "src/core/plugin-container.ts"() {
2238
2364
  "use strict";
2365
+ init_plugin_api();
2239
2366
  PluginContainer = class {
2240
2367
  plugins;
2241
2368
  config;
@@ -2246,7 +2373,7 @@ var init_plugin_container = __esm({
2246
2373
  constructor(config, environment) {
2247
2374
  this.config = config;
2248
2375
  this.environment = environment;
2249
- this.plugins = sortPlugins(config.plugins);
2376
+ this.plugins = orderPlugins(config.plugins);
2250
2377
  this.ctx = this.createContext();
2251
2378
  }
2252
2379
  createContext() {
@@ -2542,6 +2669,7 @@ var init_environment = __esm({
2542
2669
  init_module_graph();
2543
2670
  init_hot_channel();
2544
2671
  init_debug();
2672
+ init_plugin_api();
2545
2673
  debug2 = createDebugger("nasti:environment");
2546
2674
  NastiEnvironment = class {
2547
2675
  name;
@@ -2550,6 +2678,7 @@ var init_environment = __esm({
2550
2678
  config;
2551
2679
  options;
2552
2680
  hot;
2681
+ driver;
2553
2682
  /** applyToEnvironment 过滤后的插件(init() 后可用) */
2554
2683
  plugins = [];
2555
2684
  /** per-env 插件容器(init() 后可用;dev 管线使用) */
@@ -2557,6 +2686,7 @@ var init_environment = __esm({
2557
2686
  /** per-env 模块图(dev 管线使用) */
2558
2687
  moduleGraph;
2559
2688
  candidatePlugins;
2689
+ pluginApi;
2560
2690
  initialized = false;
2561
2691
  constructor(name, config, init = {}) {
2562
2692
  const options = config.environments[name];
@@ -2573,6 +2703,7 @@ var init_environment = __esm({
2573
2703
  this.hot = init.hot ?? createNoopHotChannel();
2574
2704
  this.moduleGraph = new ModuleGraph();
2575
2705
  this.candidatePlugins = init.plugins ?? config.plugins;
2706
+ this.pluginApi = init.pluginApi ?? getPluginApi(config);
2576
2707
  }
2577
2708
  /** 过滤插件并建 per-env PluginContainer */
2578
2709
  async init() {
@@ -2583,10 +2714,41 @@ var init_environment = __esm({
2583
2714
  { ...this.config, plugins: this.plugins },
2584
2715
  this
2585
2716
  );
2717
+ if (this.options.driver) {
2718
+ const claimed = [];
2719
+ for (const plugin of this.plugins) {
2720
+ const driver = await plugin.createEnvironmentDriver?.(this, this.pluginApi);
2721
+ if (driver) claimed.push({ plugin, driver });
2722
+ }
2723
+ if (claimed.length === 0) {
2724
+ throw new Error(
2725
+ `[nasti] environment "${this.name}" requested driver "${this.options.driver}", but no plugin provided it`
2726
+ );
2727
+ }
2728
+ if (claimed.length > 1) {
2729
+ throw new Error(
2730
+ `[nasti] environment "${this.name}" was claimed by multiple drivers: ` + claimed.map(({ plugin, driver }) => `${plugin.name} (${driver.name})`).join(", ")
2731
+ );
2732
+ }
2733
+ this.driver = claimed[0].driver;
2734
+ debug2?.(`env "${this.name}" uses driver "${this.driver.name}"`);
2735
+ }
2586
2736
  debug2?.(`env "${this.name}" initialized (${this.plugins.length} plugins)`);
2587
2737
  }
2738
+ getDriverContext() {
2739
+ return {
2740
+ environment: this,
2741
+ config: this.config,
2742
+ api: this.pluginApi,
2743
+ logger: this.config.logger
2744
+ };
2745
+ }
2588
2746
  async close() {
2589
- await this.hot.close?.();
2747
+ try {
2748
+ await this.driver?.close?.(this.getDriverContext());
2749
+ } finally {
2750
+ await this.hot.close?.();
2751
+ }
2590
2752
  }
2591
2753
  };
2592
2754
  }
@@ -2751,6 +2913,7 @@ var build_exports = {};
2751
2913
  __export(build_exports, {
2752
2914
  build: () => build,
2753
2915
  getRolldownOptions: () => getRolldownOptions,
2916
+ replaceEntryScript: () => replaceEntryScript,
2754
2917
  resolveClientEntries: () => resolveClientEntries,
2755
2918
  toRolldownPlugins: () => toRolldownPlugins
2756
2919
  });
@@ -2829,13 +2992,20 @@ function toRolldownPlugins(plugins) {
2829
2992
  }));
2830
2993
  }
2831
2994
  function resolveClientEntries(config, html) {
2995
+ const configuredEntries = config.environments.client?.entry ?? [];
2996
+ if (configuredEntries.length > 0) return configuredEntries;
2832
2997
  const entryPoints = [];
2998
+ const htmlFile = config.environments.client?.html;
2999
+ const htmlDir = htmlFile ? import_node_path9.default.dirname(htmlFile) : config.root;
2833
3000
  if (html) {
2834
3001
  const scriptMatches = html.matchAll(/<script[^>]+src=["']([^"']+)["'][^>]*>/gi);
2835
3002
  for (const match of scriptMatches) {
2836
3003
  const src = match[1];
2837
3004
  if (src && !src.startsWith("http")) {
2838
- entryPoints.push(import_node_path9.default.resolve(config.root, src.replace(/^\//, "")));
3005
+ const cleanSrc = src.split(/[?#]/, 1)[0];
3006
+ entryPoints.push(
3007
+ cleanSrc.startsWith("/") ? import_node_path9.default.resolve(config.root, cleanSrc.replace(/^\//, "")) : import_node_path9.default.resolve(htmlDir, cleanSrc)
3008
+ );
2839
3009
  }
2840
3010
  }
2841
3011
  }
@@ -2871,21 +3041,55 @@ async function build(inlineConfig = {}) {
2871
3041
  const startTime = performance.now();
2872
3042
  logger.info(
2873
3043
  import_picocolors4.default.cyan(`
2874
- nasti v${"2.2.0"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
3044
+ nasti v${"2.3.1"} `) + import_picocolors4.default.green(`building for ${config.mode}...`)
2875
3045
  );
2876
3046
  debug4?.(`root: ${config.root}`);
2877
3047
  const buildableNames = Object.keys(config.environments).filter(
2878
- (name) => name === "client" || config.environments[name].entry.length > 0
3048
+ (name) => name === "client" || config.environments[name].entry.length > 0 || !!config.environments[name].driver
2879
3049
  );
2880
3050
  buildableNames.sort((a, b) => a === "client" ? -1 : b === "client" ? 1 : 0);
2881
3051
  const environments = {};
3052
+ const environmentResults = {};
3053
+ const initializedEnvironments = [];
2882
3054
  let clientOutput = [];
2883
- for (const name of buildableNames) {
2884
- const output = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
2885
- environments[name] = output;
2886
- if (name === "client") clientOutput = output;
2887
- if (buildableNames.length > 1) {
2888
- debug4?.(`environment "${name}" built (${output.length} files)`);
3055
+ let buildFailed = false;
3056
+ try {
3057
+ for (const name of buildableNames) {
3058
+ const built = name === "client" ? await buildClientEnvironment(config) : await buildServerEnvironment(config, name);
3059
+ initializedEnvironments.push(built.environment);
3060
+ environments[name] = built.result.output;
3061
+ environmentResults[name] = built.result;
3062
+ if (name === "client") clientOutput = built.result.output;
3063
+ if (buildableNames.length > 1) {
3064
+ debug4?.(`environment "${name}" built (${built.result.output.length} files)`);
3065
+ }
3066
+ }
3067
+ const pluginApi = getPluginApi(config);
3068
+ for (const plugin of config.plugins) {
3069
+ await plugin.afterBuildApp?.(environmentResults, pluginApi);
3070
+ }
3071
+ } catch (error) {
3072
+ buildFailed = true;
3073
+ throw error;
3074
+ } finally {
3075
+ let closeFailed = false;
3076
+ let firstCloseError;
3077
+ for (const environment of [...initializedEnvironments].reverse()) {
3078
+ try {
3079
+ await environment.close();
3080
+ } catch (error) {
3081
+ if (!closeFailed) {
3082
+ closeFailed = true;
3083
+ firstCloseError = error;
3084
+ }
3085
+ const closeError = error instanceof Error ? error : new Error(String(error));
3086
+ logger.error(`[nasti] failed to close environment "${environment.name}"`, {
3087
+ error: closeError
3088
+ });
3089
+ }
3090
+ }
3091
+ if (closeFailed && !buildFailed) {
3092
+ throw firstCloseError;
2889
3093
  }
2890
3094
  }
2891
3095
  const elapsed = ((performance.now() - startTime) / 1e3).toFixed(2);
@@ -2898,83 +3102,130 @@ nasti v${"2.2.0"} `) + import_picocolors4.default.green(`building for ${config.m
2898
3102
  const envSuffix = buildableNames.length > 1 ? ` (${buildableNames.join(" + ")})` : "";
2899
3103
  logger.info(import_picocolors4.default.green(`\u2713 built in ${elapsed}s`) + import_picocolors4.default.dim(envSuffix));
2900
3104
  logger.info(import_picocolors4.default.dim(` ${fileCount} files, ${displaySize(totalSize)} total \u2192 ${config.build.outDir}/`));
2901
- return { output: clientOutput, environments };
3105
+ return { output: clientOutput, environments, environmentResults };
2902
3106
  }
2903
3107
  async function buildClientEnvironment(config) {
2904
3108
  const logger = config.logger;
2905
3109
  const outDir = import_node_path9.default.resolve(config.root, config.build.outDir);
2906
- if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
2907
- import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
2908
- }
2909
- import_node_fs6.default.mkdirSync(outDir, { recursive: true });
2910
- const html = await readHtmlFile(config.root);
2911
- const entryPoints = resolveClientEntries(config, html);
2912
- if (entryPoints.length === 0) {
2913
- throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
2914
- }
2915
3110
  const cssEngine = createCssEngine();
2916
3111
  const pluginList = resolvePluginList(config, config.plugins, { cssEngine });
2917
- const clientEnv = new NastiEnvironment("client", { ...config, plugins: pluginList }, {
3112
+ const clientEnv = new NastiEnvironment("client", config, {
2918
3113
  mode: "build",
2919
- plugins: pluginList
3114
+ plugins: pluginList,
3115
+ pluginApi: getPluginApi(config)
2920
3116
  });
2921
3117
  await clientEnv.init();
2922
- const allPlugins = clientEnv.plugins;
2923
- const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
2924
- const rolldownPlugins = [
2925
- createOxcTransformPlugin(config, clientEnv),
2926
- ...toRolldownPlugins(allPlugins),
2927
- ...nativeReporter ? [nativeReporter] : []
2928
- ];
2929
- const { inputOptions, outputOptions } = getRolldownOptions(clientEnv, entryPoints, rolldownPlugins);
2930
- const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
2931
- const { output } = await bundle2.write(outputOptions);
2932
- await bundle2.close();
2933
- if (html) {
2934
- let processedHtml = html;
2935
- const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
2936
- for (const p of htmlPlugins) {
2937
- const result = await p.transformIndexHtml(processedHtml);
2938
- if (typeof result === "string") {
2939
- processedHtml = result;
2940
- } else if (result && "html" in result) {
2941
- processedHtml = processHtml(result.html, result.tags);
2942
- } else if (Array.isArray(result)) {
2943
- processedHtml = processHtml(processedHtml, result);
2944
- }
2945
- }
2946
- processedHtml = injectCssLinks(processedHtml, cssEngine, config);
2947
- for (const chunk of output) {
2948
- if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
2949
- const originalEntry = import_node_path9.default.relative(config.root, chunk.facadeModuleId);
2950
- processedHtml = processedHtml.replace(
2951
- new RegExp(`(src=["'])/?(${escapeRegExp(originalEntry)})(["'])`, "g"),
2952
- `$1${config.base}${chunk.fileName}$3`
3118
+ try {
3119
+ if (clientEnv.driver) {
3120
+ if (!clientEnv.driver.build) {
3121
+ throw new Error(
3122
+ `[nasti] environment "client" driver "${clientEnv.driver.name}" does not implement build()`
2953
3123
  );
2954
3124
  }
3125
+ const result = await clientEnv.driver.build(clientEnv.getDriverContext());
3126
+ return { environment: clientEnv, result };
2955
3127
  }
2956
- import_node_fs6.default.writeFileSync(import_node_path9.default.resolve(outDir, "index.html"), processedHtml);
2957
- }
2958
- if (!nativeReporter && config.logLevel !== "silent") {
2959
- reportBuildOutput(output, config, logger);
3128
+ if (config.build.emptyOutDir && import_node_fs6.default.existsSync(outDir)) {
3129
+ import_node_fs6.default.rmSync(outDir, { recursive: true, force: true });
3130
+ }
3131
+ import_node_fs6.default.mkdirSync(outDir, { recursive: true });
3132
+ const htmlFile = config.environments.client.html ?? import_node_path9.default.resolve(config.root, "index.html");
3133
+ const html = await readHtmlFile(config.root, htmlFile);
3134
+ const entryPoints = resolveClientEntries(config, html);
3135
+ if (entryPoints.length === 0) {
3136
+ throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
3137
+ }
3138
+ const allPlugins = clientEnv.plugins;
3139
+ const nativeReporter = config.logLevel === "silent" ? null : await tryNativeReporterPlugin(config, logger);
3140
+ const rolldownPlugins = [
3141
+ createOxcTransformPlugin(config, clientEnv),
3142
+ ...toRolldownPlugins(allPlugins),
3143
+ ...nativeReporter ? [nativeReporter] : []
3144
+ ];
3145
+ const { inputOptions, outputOptions } = getRolldownOptions(
3146
+ clientEnv,
3147
+ entryPoints,
3148
+ rolldownPlugins
3149
+ );
3150
+ const bundle2 = await (0, import_rolldown.rolldown)(inputOptions);
3151
+ const { output } = await bundle2.write(outputOptions);
3152
+ await bundle2.close();
3153
+ if (html) {
3154
+ let processedHtml = html;
3155
+ const htmlPlugins = [...allPlugins.filter((p) => p.transformIndexHtml), htmlPlugin(config)];
3156
+ for (const p of htmlPlugins) {
3157
+ const result = await p.transformIndexHtml(processedHtml);
3158
+ if (typeof result === "string") {
3159
+ processedHtml = result;
3160
+ } else if (result && "html" in result) {
3161
+ processedHtml = processHtml(result.html, result.tags);
3162
+ } else if (Array.isArray(result)) {
3163
+ processedHtml = processHtml(processedHtml, result);
3164
+ }
3165
+ }
3166
+ processedHtml = injectCssLinks(processedHtml, cssEngine, config);
3167
+ for (const chunk of output) {
3168
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.facadeModuleId) {
3169
+ processedHtml = replaceEntryScript(
3170
+ processedHtml,
3171
+ chunk.facadeModuleId,
3172
+ chunk.fileName,
3173
+ config,
3174
+ htmlFile,
3175
+ config.base
3176
+ );
3177
+ }
3178
+ }
3179
+ import_node_fs6.default.writeFileSync(import_node_path9.default.resolve(outDir, "index.html"), processedHtml);
3180
+ }
3181
+ if (!nativeReporter && config.logLevel !== "silent") {
3182
+ reportBuildOutput(output, config, logger);
3183
+ }
3184
+ warnLargeChunks(output, config, logger);
3185
+ return { environment: clientEnv, result: { output } };
3186
+ } catch (error) {
3187
+ try {
3188
+ await clientEnv.close();
3189
+ } catch (closeError) {
3190
+ const normalized = closeError instanceof Error ? closeError : new Error(String(closeError));
3191
+ logger.error("[nasti] failed to close client environment after build failure", {
3192
+ error: normalized
3193
+ });
3194
+ }
3195
+ throw error;
2960
3196
  }
2961
- warnLargeChunks(output, config, logger);
2962
- return output;
2963
3197
  }
2964
3198
  async function buildServerEnvironment(config, name) {
2965
3199
  const envOptions = config.environments[name];
2966
3200
  const logger = config.logger;
3201
+ const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
3202
+ const environment = new NastiEnvironment(name, config, {
3203
+ mode: "build",
3204
+ plugins: pluginList,
3205
+ pluginApi: getPluginApi(config)
3206
+ });
3207
+ await environment.init();
3208
+ if (environment.driver) {
3209
+ if (!environment.driver.build) {
3210
+ await environment.close();
3211
+ throw new Error(
3212
+ `[nasti] environment "${name}" driver "${environment.driver.name}" does not implement build()`
3213
+ );
3214
+ }
3215
+ try {
3216
+ const result = await environment.driver.build(environment.getDriverContext());
3217
+ return { environment, result };
3218
+ } catch (error) {
3219
+ await environment.close();
3220
+ throw error;
3221
+ }
3222
+ }
2967
3223
  for (const entry of envOptions.entry) {
2968
3224
  if (!import_node_fs6.default.existsSync(entry)) {
3225
+ await environment.close();
2969
3226
  throw new Error(`[nasti] environment "${name}" entry not found: ${entry}`);
2970
3227
  }
2971
3228
  }
2972
- const pluginList = resolvePluginList(config, config.plugins, { consumer: envOptions.consumer });
2973
- const environment = new NastiEnvironment(name, { ...config, plugins: pluginList }, {
2974
- mode: "build",
2975
- plugins: pluginList
2976
- });
2977
- await environment.init();
2978
3229
  const rolldownPlugins = [
2979
3230
  createOxcTransformPlugin(config, environment),
2980
3231
  ...toRolldownPlugins(environment.plugins)
@@ -2994,7 +3245,7 @@ async function buildServerEnvironment(config, name) {
2994
3245
  logger.info(
2995
3246
  import_picocolors4.default.dim(` [${name}] `) + output.map((o) => import_node_path9.default.join(envOptions.build.outDir, o.fileName)).join(import_picocolors4.default.dim(", "))
2996
3247
  );
2997
- return output;
3248
+ return { environment, result: { output } };
2998
3249
  }
2999
3250
  function injectCssLinks(html, cssEngine, config) {
3000
3251
  const cssLinkTags = [];
@@ -3020,6 +3271,25 @@ function injectCssLinks(html, cssEngine, config) {
3020
3271
  function escapeRegExp(string) {
3021
3272
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3022
3273
  }
3274
+ function replaceEntryScript(html, facadeModuleId, fileName, config, htmlFile, urlPrefix) {
3275
+ const rootRelative = import_node_path9.default.relative(config.root, facadeModuleId).split(import_node_path9.default.sep).join("/");
3276
+ const resolvedHtmlFile = import_node_path9.default.resolve(config.root, htmlFile);
3277
+ const htmlRelative = import_node_path9.default.relative(import_node_path9.default.dirname(resolvedHtmlFile), facadeModuleId).split(import_node_path9.default.sep).join("/");
3278
+ const candidates = /* @__PURE__ */ new Set([
3279
+ rootRelative,
3280
+ `/${rootRelative}`,
3281
+ htmlRelative,
3282
+ `./${htmlRelative}`
3283
+ ]);
3284
+ let processed = html;
3285
+ for (const candidate of candidates) {
3286
+ processed = processed.replace(
3287
+ new RegExp(`(src=["'])${escapeRegExp(candidate)}([?#][^"']*)?(["'])`, "g"),
3288
+ `$1${urlPrefix}${fileName}$3`
3289
+ );
3290
+ }
3291
+ return processed;
3292
+ }
3023
3293
  var import_node_path9, import_node_fs6, import_node_module3, import_rolldown, import_picocolors4, debug4, NODE_BUILTINS;
3024
3294
  var init_build = __esm({
3025
3295
  "src/build/index.ts"() {
@@ -3037,6 +3307,7 @@ var init_build = __esm({
3037
3307
  init_env();
3038
3308
  init_reporter();
3039
3309
  init_debug();
3310
+ init_plugin_api();
3040
3311
  import_picocolors4 = __toESM(require("picocolors"), 1);
3041
3312
  debug4 = createDebugger("nasti:build");
3042
3313
  NODE_BUILTINS = /* @__PURE__ */ new Set([...import_node_module3.builtinModules, ...import_node_module3.builtinModules.map((m) => `node:${m}`)]);
@@ -3200,7 +3471,10 @@ function transformMiddleware(ctx) {
3200
3471
  return;
3201
3472
  }
3202
3473
  if (url === "/" || url.endsWith(".html")) {
3203
- const html = await readHtmlFile(ctx.config.root);
3474
+ const html = await readHtmlFile(
3475
+ ctx.config.root,
3476
+ ctx.config.environments.client?.html
3477
+ );
3204
3478
  if (html) {
3205
3479
  let processedHtml = html;
3206
3480
  for (const plugin of ctx.config.plugins) {
@@ -4235,7 +4509,7 @@ async function createBundledDevServer(opts) {
4235
4509
  `[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.`
4236
4510
  );
4237
4511
  }
4238
- const html = await readHtmlFile(config.root);
4512
+ const html = await readHtmlFile(config.root, config.environments.client?.html);
4239
4513
  const entryPoints = resolveClientEntries(config, html);
4240
4514
  if (entryPoints.length === 0) {
4241
4515
  throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
@@ -4441,7 +4715,7 @@ async function createBundledDevServer(opts) {
4441
4715
  return;
4442
4716
  }
4443
4717
  if (pathname === "/" || pathname.endsWith(".html")) {
4444
- const rawHtml = await readHtmlFile(config.root);
4718
+ const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
4445
4719
  if (rawHtml) {
4446
4720
  res.setHeader("Content-Type", "text/html");
4447
4721
  res.setHeader("Cache-Control", "no-store");
@@ -4525,10 +4799,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
4525
4799
  }
4526
4800
  }
4527
4801
  for (const [facadeModuleId, fileName] of entryFileNames) {
4528
- const originalEntry = import_node_path14.default.relative(config.root, facadeModuleId);
4529
- processed = processed.replace(
4530
- new RegExp(`(src=["'])/?(${originalEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(["'])`, "g"),
4531
- `$1/${fileName}$3`
4802
+ processed = replaceEntryScript(
4803
+ processed,
4804
+ facadeModuleId,
4805
+ fileName,
4806
+ config,
4807
+ config.environments.client?.html ?? "index.html",
4808
+ "/"
4532
4809
  );
4533
4810
  }
4534
4811
  return processed;
@@ -4669,10 +4946,12 @@ async function createServer(inlineConfig = {}) {
4669
4946
  const app = (0, import_connect.default)();
4670
4947
  const httpServer = import_node_http.default.createServer(app);
4671
4948
  const ws = createWebSocketServer(httpServer);
4672
- const clientEnv = new NastiEnvironment("client", configWithPlugins, {
4949
+ const pluginApi = getPluginApi(config);
4950
+ const clientEnv = new NastiEnvironment("client", config, {
4673
4951
  hot: createWsHotChannel(ws),
4674
4952
  mode: "dev",
4675
- plugins: allPlugins
4953
+ plugins: allPlugins,
4954
+ pluginApi
4676
4955
  });
4677
4956
  await clientEnv.init();
4678
4957
  const environments = { client: clientEnv };
@@ -4680,11 +4959,15 @@ async function createServer(inlineConfig = {}) {
4680
4959
  if (name === "client") continue;
4681
4960
  const consumer = config.environments[name].consumer;
4682
4961
  const envPlugins = resolvePluginList(config, config.plugins, { consumer });
4683
- environments[name] = new NastiEnvironment(name, { ...config, plugins: envPlugins }, {
4962
+ environments[name] = new NastiEnvironment(name, config, {
4684
4963
  mode: "dev",
4685
- plugins: envPlugins
4964
+ plugins: envPlugins,
4965
+ pluginApi
4686
4966
  });
4687
4967
  }
4968
+ for (const [name, environment] of Object.entries(environments)) {
4969
+ if (name !== "client" && environment.options.driver) await environment.init();
4970
+ }
4688
4971
  let ssrRunner = null;
4689
4972
  async function getSsrRunner() {
4690
4973
  if (ssrRunner) return ssrRunner;
@@ -4709,14 +4992,6 @@ async function createServer(inlineConfig = {}) {
4709
4992
  });
4710
4993
  app.use(bundledServer.middleware);
4711
4994
  }
4712
- app.use(transformMiddleware({
4713
- config: configWithPlugins,
4714
- pluginContainer,
4715
- moduleGraph
4716
- }));
4717
- const publicDir = import_node_path15.default.resolve(config.root, "public");
4718
- app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
4719
- app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4720
4995
  const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
4721
4996
  const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
4722
4997
  const watcher = (0, import_chokidar.watch)(config.root, {
@@ -4733,13 +5008,72 @@ async function createServer(inlineConfig = {}) {
4733
5008
  ignoreInitial: true
4734
5009
  });
4735
5010
  let server;
5011
+ const environmentServices = {};
5012
+ let environmentDriversStarted = false;
5013
+ const logCloseError = (target, error) => {
5014
+ const normalized = error instanceof Error ? error : new Error(String(error));
5015
+ logger.error(`[nasti] failed to close ${target}`, { error: normalized });
5016
+ };
5017
+ const startEnvironmentDrivers = async () => {
5018
+ if (environmentDriversStarted) return;
5019
+ environmentDriversStarted = true;
5020
+ const started = [];
5021
+ const attempted = [];
5022
+ try {
5023
+ for (const [name, environment] of Object.entries(environments)) {
5024
+ if (!environment.driver?.serve) continue;
5025
+ attempted.push(environment);
5026
+ const result = await environment.driver.serve({
5027
+ ...environment.getDriverContext(),
5028
+ server
5029
+ });
5030
+ started.push({ name, environment, service: result ?? {} });
5031
+ }
5032
+ for (const { name, service } of started) {
5033
+ environmentServices[name] = service;
5034
+ if (service.middleware) app.use(service.middleware);
5035
+ }
5036
+ } catch (error) {
5037
+ environmentDriversStarted = false;
5038
+ for (const { name } of started) {
5039
+ delete environmentServices[name];
5040
+ }
5041
+ for (const environment of attempted.reverse()) {
5042
+ try {
5043
+ await environment.driver?.close?.(environment.getDriverContext());
5044
+ } catch (closeError) {
5045
+ logCloseError(`environment driver "${environment.driver.name}"`, closeError);
5046
+ }
5047
+ }
5048
+ throw error;
5049
+ }
5050
+ };
5051
+ const notifyEnvironmentDrivers = (file, event) => {
5052
+ for (const environment of Object.values(environments)) {
5053
+ if (!environment.driver?.watchChange) continue;
5054
+ void Promise.resolve(
5055
+ environment.driver.watchChange(file, event, environment.getDriverContext())
5056
+ ).catch((error) => {
5057
+ logger.error(
5058
+ `[nasti] environment driver "${environment.driver.name}" watchChange failed`,
5059
+ { error }
5060
+ );
5061
+ });
5062
+ }
5063
+ };
4736
5064
  watcher.on("change", (file) => {
4737
5065
  ssrRunner?.invalidateFile(file);
4738
5066
  handleFileChange(file, server);
5067
+ notifyEnvironmentDrivers(file, "change");
4739
5068
  });
4740
5069
  watcher.on("add", (file) => {
4741
5070
  ssrRunner?.invalidateFile(file);
4742
5071
  handleFileChange(file, server);
5072
+ notifyEnvironmentDrivers(file, "add");
5073
+ });
5074
+ watcher.on("unlink", (file) => {
5075
+ ssrRunner?.invalidateFile(file);
5076
+ notifyEnvironmentDrivers(file, "unlink");
4743
5077
  });
4744
5078
  server = {
4745
5079
  config: configWithPlugins,
@@ -4748,10 +5082,12 @@ async function createServer(inlineConfig = {}) {
4748
5082
  watcher,
4749
5083
  ws,
4750
5084
  environments,
5085
+ environmentServices,
4751
5086
  async listen(port) {
4752
5087
  const finalPort = port ?? config.server.port;
4753
5088
  const host = config.server.host === true ? "0.0.0.0" : config.server.host;
4754
5089
  await pluginContainer.buildStart();
5090
+ await startEnvironmentDrivers();
4755
5091
  return new Promise((resolve, reject) => {
4756
5092
  let currentPort = finalPort;
4757
5093
  const onListening = () => {
@@ -4759,15 +5095,20 @@ async function createServer(inlineConfig = {}) {
4759
5095
  config.server.port = actualPort;
4760
5096
  const localUrl = `http://localhost:${actualPort}/`;
4761
5097
  const networkUrl = host === "0.0.0.0" ? `http://${getNetworkAddress()}:${actualPort}/` : null;
5098
+ const driverLocalUrls = Object.values(environmentServices).flatMap((service) => service.localUrls ?? []);
5099
+ const driverNetworkUrls = Object.values(environmentServices).flatMap((service) => service.networkUrls ?? []);
4762
5100
  logger.clearScreen("info");
4763
5101
  const readyIn = Math.ceil(performance.now() - startTime);
4764
5102
  logger.info(
4765
5103
  `
4766
- ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.2.0"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
5104
+ ${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.3.1"}`)} ${import_picocolors9.default.dim("ready in")} ${import_picocolors9.default.bold(readyIn)} ${import_picocolors9.default.dim("ms")}
4767
5105
  `
4768
5106
  );
4769
5107
  printServerUrls(
4770
- { local: [localUrl], network: networkUrl ? [networkUrl] : [] },
5108
+ {
5109
+ local: [localUrl, ...driverLocalUrls],
5110
+ network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
5111
+ },
4771
5112
  logger.info
4772
5113
  );
4773
5114
  logger.info("");
@@ -4797,11 +5138,62 @@ async function createServer(inlineConfig = {}) {
4797
5138
  async close() {
4798
5139
  await pluginContainer.buildEnd();
4799
5140
  await bundledServer?.close();
4800
- watcher.close();
5141
+ let environmentCloseFailed = false;
5142
+ let firstEnvironmentCloseError;
5143
+ for (const environment of Object.values(environments).reverse()) {
5144
+ try {
5145
+ await environment.close();
5146
+ } catch (error) {
5147
+ if (!environmentCloseFailed) {
5148
+ environmentCloseFailed = true;
5149
+ firstEnvironmentCloseError = error;
5150
+ }
5151
+ logCloseError(`environment "${environment.name}"`, error);
5152
+ }
5153
+ }
5154
+ await watcher.close();
4801
5155
  ws.close();
4802
5156
  httpServer.close();
5157
+ if (environmentCloseFailed) {
5158
+ throw firstEnvironmentCloseError;
5159
+ }
4803
5160
  }
4804
5161
  };
5162
+ try {
5163
+ await startEnvironmentDrivers();
5164
+ } catch (error) {
5165
+ if (bundledServer) {
5166
+ try {
5167
+ await bundledServer.close();
5168
+ } catch (closeError) {
5169
+ logCloseError("bundled dev server after driver startup failure", closeError);
5170
+ }
5171
+ }
5172
+ try {
5173
+ await watcher.close();
5174
+ } catch (closeError) {
5175
+ logCloseError("file watcher after driver startup failure", closeError);
5176
+ }
5177
+ try {
5178
+ ws.close();
5179
+ } catch (closeError) {
5180
+ logCloseError("WebSocket server after driver startup failure", closeError);
5181
+ }
5182
+ try {
5183
+ httpServer.close();
5184
+ } catch (closeError) {
5185
+ logCloseError("HTTP server after driver startup failure", closeError);
5186
+ }
5187
+ throw error;
5188
+ }
5189
+ app.use(transformMiddleware({
5190
+ config: configWithPlugins,
5191
+ pluginContainer,
5192
+ moduleGraph
5193
+ }));
5194
+ const publicDir = import_node_path15.default.resolve(config.root, "public");
5195
+ app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
5196
+ app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
4805
5197
  const postMiddlewares = [];
4806
5198
  for (const plugin of allPlugins) {
4807
5199
  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
 
@@ -4856,12 +5249,15 @@ __export(src_exports, {
4856
5249
  buildElectron: () => buildElectron,
4857
5250
  buildEnvDefine: () => buildEnvDefine,
4858
5251
  createDebugger: () => createDebugger,
5252
+ createElectronRendererConfig: () => createElectronRendererConfig,
4859
5253
  createLogger: () => createLogger,
4860
5254
  createNoopHotChannel: () => createNoopHotChannel,
4861
5255
  createServer: () => createServer,
4862
5256
  createWsHotChannel: () => createWsHotChannel,
4863
5257
  defineConfig: () => defineConfig,
5258
+ detectFramework: () => detectFramework,
4864
5259
  electronPlugin: () => electronPlugin,
5260
+ electronRendererDevPath: () => electronRendererDevPath,
4865
5261
  loadEnv: () => loadEnv,
4866
5262
  monacoEditorPlugin: () => monacoEditorPlugin,
4867
5263
  printServerUrls: () => printServerUrls,
@@ -4922,7 +5318,7 @@ async function buildElectron(inlineConfig = {}) {
4922
5318
  const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
4923
5319
  const startTime = performance.now();
4924
5320
  assertElectronVersion(config);
4925
- console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.2.0"}`));
5321
+ console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.3.1"}`));
4926
5322
  console.log(import_picocolors5.default.dim(` root: ${config.root}`));
4927
5323
  console.log(import_picocolors5.default.dim(` mode: ${config.mode}`));
4928
5324
  console.log(import_picocolors5.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
@@ -4933,15 +5329,13 @@ async function buildElectron(inlineConfig = {}) {
4933
5329
  import_node_fs7.default.mkdirSync(outDir, { recursive: true });
4934
5330
  const rendererOutDir = import_node_path10.default.join(outDir, "renderer");
4935
5331
  const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
4936
- await build2({
4937
- ...inlineConfig,
4938
- target: "web",
5332
+ await build2(createElectronRendererConfig(config, inlineConfig, {
4939
5333
  build: {
4940
5334
  ...inlineConfig.build,
4941
5335
  outDir: rendererOutDir,
4942
5336
  emptyOutDir: false
4943
5337
  }
4944
- });
5338
+ }));
4945
5339
  const mainEntry = import_node_path10.default.resolve(config.root, config.electron.main);
4946
5340
  if (!import_node_fs7.default.existsSync(mainEntry)) {
4947
5341
  throw new Error(
@@ -4995,7 +5389,8 @@ async function bundleNode(config, entry, opts) {
4995
5389
  const result = transformCode(id, code, {
4996
5390
  sourcemap: !!config.build.sourcemap,
4997
5391
  jsxRuntime: "automatic",
4998
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5392
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5393
+ target: config.electron.nodeTarget
4999
5394
  });
5000
5395
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5001
5396
  }
@@ -5006,7 +5401,11 @@ async function bundleNode(config, entry, opts) {
5006
5401
  ...restInputOptions,
5007
5402
  input: entry,
5008
5403
  platform: "node",
5009
- transform: { ...userTransform, define: mergedDefine },
5404
+ transform: {
5405
+ ...userTransform,
5406
+ target: config.electron.nodeTarget,
5407
+ define: mergedDefine
5408
+ },
5010
5409
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5011
5410
  });
5012
5411
  import_node_fs7.default.mkdirSync(import_node_path10.default.dirname(opts.outFile), { recursive: true });
@@ -5023,6 +5422,25 @@ async function bundleNode(config, entry, opts) {
5023
5422
  console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path10.default.relative(config.root, opts.outFile)}`));
5024
5423
  return opts.outFile;
5025
5424
  }
5425
+ function createElectronRendererConfig(config, inlineConfig = {}, overrides = {}) {
5426
+ const inlineClient = inlineConfig.environments?.client ?? {};
5427
+ return {
5428
+ ...inlineConfig,
5429
+ ...overrides,
5430
+ root: config.root,
5431
+ mode: config.mode,
5432
+ target: "web",
5433
+ framework: config.framework,
5434
+ base: config.base === "/" ? "./" : config.base,
5435
+ environments: {
5436
+ ...inlineConfig.environments ?? {},
5437
+ client: {
5438
+ ...inlineClient,
5439
+ html: config.electron.renderer
5440
+ }
5441
+ }
5442
+ };
5443
+ }
5026
5444
  function outFileName(outDir, base, format) {
5027
5445
  const ext = format === "cjs" ? ".cjs" : ".mjs";
5028
5446
  return import_node_path10.default.join(outDir, base + ext);
@@ -5073,11 +5491,15 @@ async function startElectronDev(inlineConfig = {}) {
5073
5491
  const { noSpawn, ...rest } = inlineConfig;
5074
5492
  const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
5075
5493
  warnElectronVersion(config);
5076
- console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.2.0"}`));
5494
+ console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
5077
5495
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
5078
- const server = await createServer2({ ...rest, target: "electron" });
5496
+ const server = await createServer2({
5497
+ ...rest,
5498
+ target: "electron",
5499
+ framework: config.framework
5500
+ });
5079
5501
  await server.listen();
5080
- const devUrl = `http://localhost:${server.config.server.port}/`;
5502
+ const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
5081
5503
  console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
5082
5504
  const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
5083
5505
  import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
@@ -5199,14 +5621,18 @@ async function compileNode(config, entry, opts) {
5199
5621
  const result = transformCode(id, code, {
5200
5622
  sourcemap: true,
5201
5623
  jsxRuntime: "automatic",
5202
- jsxImportSource: config.framework === "vue" ? "vue" : "react"
5624
+ jsxImportSource: config.framework === "vue" ? "vue" : "react",
5625
+ target: config.electron.nodeTarget
5203
5626
  });
5204
5627
  return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
5205
5628
  }
5206
5629
  };
5207
5630
  const bundle2 = await (0, import_rolldown3.rolldown)({
5208
5631
  input: entry,
5209
- transform: { define: envDefine },
5632
+ transform: {
5633
+ target: config.electron.nodeTarget,
5634
+ define: envDefine
5635
+ },
5210
5636
  platform: "node",
5211
5637
  plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
5212
5638
  });
@@ -5222,6 +5648,10 @@ async function compileNode(config, entry, opts) {
5222
5648
  });
5223
5649
  await bundle2.close();
5224
5650
  }
5651
+ function electronRendererDevPath(renderer) {
5652
+ const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
5653
+ return normalized === "index.html" ? "/" : `/${normalized}`;
5654
+ }
5225
5655
  function resolveElectronBinary(config) {
5226
5656
  if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
5227
5657
  return config.electron.electronPath;
@@ -5479,12 +5909,15 @@ init_env();
5479
5909
  buildElectron,
5480
5910
  buildEnvDefine,
5481
5911
  createDebugger,
5912
+ createElectronRendererConfig,
5482
5913
  createLogger,
5483
5914
  createNoopHotChannel,
5484
5915
  createServer,
5485
5916
  createWsHotChannel,
5486
5917
  defineConfig,
5918
+ detectFramework,
5487
5919
  electronPlugin,
5920
+ electronRendererDevPath,
5488
5921
  loadEnv,
5489
5922
  monacoEditorPlugin,
5490
5923
  printServerUrls,