@nasti-toolchain/nasti 2.1.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/dist/cli.cjs +573 -134
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +573 -134
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +575 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -11
- package/dist/index.d.ts +109 -11
- package/dist/index.js +572 -132
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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: (
|
|
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"
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.1
|
|
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
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
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.1.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",
|
|
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
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
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
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
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(
|
|
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) {
|
|
@@ -3771,7 +4045,7 @@ const hotModulesMap = new Map();
|
|
|
3771
4045
|
const disposeMap = new Map();
|
|
3772
4046
|
const pruneMap = new Map();
|
|
3773
4047
|
|
|
3774
|
-
socket.addEventListener('message', ({ data }) => {
|
|
4048
|
+
socket.addEventListener('message', async ({ data }) => {
|
|
3775
4049
|
const payload = JSON.parse(data);
|
|
3776
4050
|
switch (payload.type) {
|
|
3777
4051
|
case 'connected':
|
|
@@ -3779,14 +4053,21 @@ socket.addEventListener('message', ({ data }) => {
|
|
|
3779
4053
|
clearErrorOverlay();
|
|
3780
4054
|
break;
|
|
3781
4055
|
case 'update':
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
4056
|
+
try {
|
|
4057
|
+
await Promise.all(payload.updates.map((update) => {
|
|
4058
|
+
if (update.type === 'js-update') {
|
|
4059
|
+
return fetchUpdate(update);
|
|
4060
|
+
} else if (update.type === 'css-update') {
|
|
4061
|
+
return updateCss(update.path);
|
|
4062
|
+
}
|
|
4063
|
+
}));
|
|
4064
|
+
clearErrorOverlay();
|
|
4065
|
+
console.log('[nasti] HMR update complete, reloading page');
|
|
4066
|
+
location.reload();
|
|
4067
|
+
} catch (err) {
|
|
4068
|
+
console.error('[nasti] HMR update failed:', err);
|
|
4069
|
+
showErrorOverlay(err);
|
|
4070
|
+
}
|
|
3790
4071
|
break;
|
|
3791
4072
|
case 'full-reload':
|
|
3792
4073
|
console.log('[nasti] full reload');
|
|
@@ -3828,7 +4109,7 @@ async function fetchUpdate(update) {
|
|
|
3828
4109
|
function updateCss(path) {
|
|
3829
4110
|
const el = document.querySelector(\`style[data-nasti-css="\${path}"]\`);
|
|
3830
4111
|
if (el) {
|
|
3831
|
-
fetch(path + '?t=' + Date.now())
|
|
4112
|
+
return fetch(path + '?t=' + Date.now())
|
|
3832
4113
|
.then(r => r.text())
|
|
3833
4114
|
.then(css => { el.textContent = css; });
|
|
3834
4115
|
}
|
|
@@ -4228,7 +4509,7 @@ async function createBundledDevServer(opts) {
|
|
|
4228
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.`
|
|
4229
4510
|
);
|
|
4230
4511
|
}
|
|
4231
|
-
const html = await readHtmlFile(config.root);
|
|
4512
|
+
const html = await readHtmlFile(config.root, config.environments.client?.html);
|
|
4232
4513
|
const entryPoints = resolveClientEntries(config, html);
|
|
4233
4514
|
if (entryPoints.length === 0) {
|
|
4234
4515
|
throw new Error("No entry point found. Add a <script> tag to index.html or create src/main.ts");
|
|
@@ -4283,7 +4564,10 @@ async function createBundledDevServer(opts) {
|
|
|
4283
4564
|
continue;
|
|
4284
4565
|
}
|
|
4285
4566
|
const patchPath = `__nasti_patch/${update.filename}`;
|
|
4286
|
-
patches.set(
|
|
4567
|
+
patches.set(
|
|
4568
|
+
patchPath,
|
|
4569
|
+
update.code + "\n;globalThis.location?.reload();\n;export {}"
|
|
4570
|
+
);
|
|
4287
4571
|
if (update.sourcemap && update.sourcemapFilename) {
|
|
4288
4572
|
patches.set(`__nasti_patch/${update.sourcemapFilename}`, update.sourcemap);
|
|
4289
4573
|
}
|
|
@@ -4431,7 +4715,7 @@ async function createBundledDevServer(opts) {
|
|
|
4431
4715
|
return;
|
|
4432
4716
|
}
|
|
4433
4717
|
if (pathname === "/" || pathname.endsWith(".html")) {
|
|
4434
|
-
const rawHtml = await readHtmlFile(config.root);
|
|
4718
|
+
const rawHtml = await readHtmlFile(config.root, config.environments.client?.html);
|
|
4435
4719
|
if (rawHtml) {
|
|
4436
4720
|
res.setHeader("Content-Type", "text/html");
|
|
4437
4721
|
res.setHeader("Cache-Control", "no-store");
|
|
@@ -4515,10 +4799,13 @@ async function renderBundledIndexHtml(html, config, entryFileNames) {
|
|
|
4515
4799
|
}
|
|
4516
4800
|
}
|
|
4517
4801
|
for (const [facadeModuleId, fileName] of entryFileNames) {
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4802
|
+
processed = replaceEntryScript(
|
|
4803
|
+
processed,
|
|
4804
|
+
facadeModuleId,
|
|
4805
|
+
fileName,
|
|
4806
|
+
config,
|
|
4807
|
+
config.environments.client?.html ?? "index.html",
|
|
4808
|
+
"/"
|
|
4522
4809
|
);
|
|
4523
4810
|
}
|
|
4524
4811
|
return processed;
|
|
@@ -4659,10 +4946,12 @@ async function createServer(inlineConfig = {}) {
|
|
|
4659
4946
|
const app = (0, import_connect.default)();
|
|
4660
4947
|
const httpServer = import_node_http.default.createServer(app);
|
|
4661
4948
|
const ws = createWebSocketServer(httpServer);
|
|
4662
|
-
const
|
|
4949
|
+
const pluginApi = getPluginApi(config);
|
|
4950
|
+
const clientEnv = new NastiEnvironment("client", config, {
|
|
4663
4951
|
hot: createWsHotChannel(ws),
|
|
4664
4952
|
mode: "dev",
|
|
4665
|
-
plugins: allPlugins
|
|
4953
|
+
plugins: allPlugins,
|
|
4954
|
+
pluginApi
|
|
4666
4955
|
});
|
|
4667
4956
|
await clientEnv.init();
|
|
4668
4957
|
const environments = { client: clientEnv };
|
|
@@ -4670,11 +4959,15 @@ async function createServer(inlineConfig = {}) {
|
|
|
4670
4959
|
if (name === "client") continue;
|
|
4671
4960
|
const consumer = config.environments[name].consumer;
|
|
4672
4961
|
const envPlugins = resolvePluginList(config, config.plugins, { consumer });
|
|
4673
|
-
environments[name] = new NastiEnvironment(name,
|
|
4962
|
+
environments[name] = new NastiEnvironment(name, config, {
|
|
4674
4963
|
mode: "dev",
|
|
4675
|
-
plugins: envPlugins
|
|
4964
|
+
plugins: envPlugins,
|
|
4965
|
+
pluginApi
|
|
4676
4966
|
});
|
|
4677
4967
|
}
|
|
4968
|
+
for (const [name, environment] of Object.entries(environments)) {
|
|
4969
|
+
if (name !== "client" && environment.options.driver) await environment.init();
|
|
4970
|
+
}
|
|
4678
4971
|
let ssrRunner = null;
|
|
4679
4972
|
async function getSsrRunner() {
|
|
4680
4973
|
if (ssrRunner) return ssrRunner;
|
|
@@ -4699,14 +4992,6 @@ async function createServer(inlineConfig = {}) {
|
|
|
4699
4992
|
});
|
|
4700
4993
|
app.use(bundledServer.middleware);
|
|
4701
4994
|
}
|
|
4702
|
-
app.use(transformMiddleware({
|
|
4703
|
-
config: configWithPlugins,
|
|
4704
|
-
pluginContainer,
|
|
4705
|
-
moduleGraph
|
|
4706
|
-
}));
|
|
4707
|
-
const publicDir = import_node_path15.default.resolve(config.root, "public");
|
|
4708
|
-
app.use((0, import_sirv.default)(publicDir, { dev: true, etag: true }));
|
|
4709
|
-
app.use((0, import_sirv.default)(config.root, { dev: true, etag: true }));
|
|
4710
4995
|
const ignoredSegments = /* @__PURE__ */ new Set(["node_modules", ".git", ".nasti"]);
|
|
4711
4996
|
const outDirAbs = import_node_path15.default.resolve(config.root, config.build.outDir);
|
|
4712
4997
|
const watcher = (0, import_chokidar.watch)(config.root, {
|
|
@@ -4723,13 +5008,72 @@ async function createServer(inlineConfig = {}) {
|
|
|
4723
5008
|
ignoreInitial: true
|
|
4724
5009
|
});
|
|
4725
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
|
+
};
|
|
4726
5064
|
watcher.on("change", (file) => {
|
|
4727
5065
|
ssrRunner?.invalidateFile(file);
|
|
4728
5066
|
handleFileChange(file, server);
|
|
5067
|
+
notifyEnvironmentDrivers(file, "change");
|
|
4729
5068
|
});
|
|
4730
5069
|
watcher.on("add", (file) => {
|
|
4731
5070
|
ssrRunner?.invalidateFile(file);
|
|
4732
5071
|
handleFileChange(file, server);
|
|
5072
|
+
notifyEnvironmentDrivers(file, "add");
|
|
5073
|
+
});
|
|
5074
|
+
watcher.on("unlink", (file) => {
|
|
5075
|
+
ssrRunner?.invalidateFile(file);
|
|
5076
|
+
notifyEnvironmentDrivers(file, "unlink");
|
|
4733
5077
|
});
|
|
4734
5078
|
server = {
|
|
4735
5079
|
config: configWithPlugins,
|
|
@@ -4738,10 +5082,12 @@ async function createServer(inlineConfig = {}) {
|
|
|
4738
5082
|
watcher,
|
|
4739
5083
|
ws,
|
|
4740
5084
|
environments,
|
|
5085
|
+
environmentServices,
|
|
4741
5086
|
async listen(port) {
|
|
4742
5087
|
const finalPort = port ?? config.server.port;
|
|
4743
5088
|
const host = config.server.host === true ? "0.0.0.0" : config.server.host;
|
|
4744
5089
|
await pluginContainer.buildStart();
|
|
5090
|
+
await startEnvironmentDrivers();
|
|
4745
5091
|
return new Promise((resolve, reject) => {
|
|
4746
5092
|
let currentPort = finalPort;
|
|
4747
5093
|
const onListening = () => {
|
|
@@ -4749,15 +5095,20 @@ async function createServer(inlineConfig = {}) {
|
|
|
4749
5095
|
config.server.port = actualPort;
|
|
4750
5096
|
const localUrl = `http://localhost:${actualPort}/`;
|
|
4751
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 ?? []);
|
|
4752
5100
|
logger.clearScreen("info");
|
|
4753
5101
|
const readyIn = Math.ceil(performance.now() - startTime);
|
|
4754
5102
|
logger.info(
|
|
4755
5103
|
`
|
|
4756
|
-
${import_picocolors9.default.cyan(import_picocolors9.default.bold("NASTI"))} ${import_picocolors9.default.cyan(`v${"2.1
|
|
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")}
|
|
4757
5105
|
`
|
|
4758
5106
|
);
|
|
4759
5107
|
printServerUrls(
|
|
4760
|
-
{
|
|
5108
|
+
{
|
|
5109
|
+
local: [localUrl, ...driverLocalUrls],
|
|
5110
|
+
network: [...networkUrl ? [networkUrl] : [], ...driverNetworkUrls]
|
|
5111
|
+
},
|
|
4761
5112
|
logger.info
|
|
4762
5113
|
);
|
|
4763
5114
|
logger.info("");
|
|
@@ -4787,11 +5138,62 @@ async function createServer(inlineConfig = {}) {
|
|
|
4787
5138
|
async close() {
|
|
4788
5139
|
await pluginContainer.buildEnd();
|
|
4789
5140
|
await bundledServer?.close();
|
|
4790
|
-
|
|
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();
|
|
4791
5155
|
ws.close();
|
|
4792
5156
|
httpServer.close();
|
|
5157
|
+
if (environmentCloseFailed) {
|
|
5158
|
+
throw firstEnvironmentCloseError;
|
|
5159
|
+
}
|
|
4793
5160
|
}
|
|
4794
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 }));
|
|
4795
5197
|
const postMiddlewares = [];
|
|
4796
5198
|
for (const plugin of allPlugins) {
|
|
4797
5199
|
if (plugin.configureServer) {
|
|
@@ -4834,6 +5236,7 @@ var init_server = __esm({
|
|
|
4834
5236
|
init_middleware();
|
|
4835
5237
|
init_hmr();
|
|
4836
5238
|
init_builtins();
|
|
5239
|
+
init_plugin_api();
|
|
4837
5240
|
}
|
|
4838
5241
|
});
|
|
4839
5242
|
|
|
@@ -4846,12 +5249,15 @@ __export(src_exports, {
|
|
|
4846
5249
|
buildElectron: () => buildElectron,
|
|
4847
5250
|
buildEnvDefine: () => buildEnvDefine,
|
|
4848
5251
|
createDebugger: () => createDebugger,
|
|
5252
|
+
createElectronRendererConfig: () => createElectronRendererConfig,
|
|
4849
5253
|
createLogger: () => createLogger,
|
|
4850
5254
|
createNoopHotChannel: () => createNoopHotChannel,
|
|
4851
5255
|
createServer: () => createServer,
|
|
4852
5256
|
createWsHotChannel: () => createWsHotChannel,
|
|
4853
5257
|
defineConfig: () => defineConfig,
|
|
5258
|
+
detectFramework: () => detectFramework,
|
|
4854
5259
|
electronPlugin: () => electronPlugin,
|
|
5260
|
+
electronRendererDevPath: () => electronRendererDevPath,
|
|
4855
5261
|
loadEnv: () => loadEnv,
|
|
4856
5262
|
monacoEditorPlugin: () => monacoEditorPlugin,
|
|
4857
5263
|
printServerUrls: () => printServerUrls,
|
|
@@ -4912,7 +5318,7 @@ async function buildElectron(inlineConfig = {}) {
|
|
|
4912
5318
|
const config = await resolveConfig({ ...inlineConfig, target: "electron" }, "build");
|
|
4913
5319
|
const startTime = performance.now();
|
|
4914
5320
|
assertElectronVersion(config);
|
|
4915
|
-
console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.1
|
|
5321
|
+
console.log(import_picocolors5.default.cyan("\n\u26A1 nasti build (electron)") + import_picocolors5.default.dim(` v${"2.3.1"}`));
|
|
4916
5322
|
console.log(import_picocolors5.default.dim(` root: ${config.root}`));
|
|
4917
5323
|
console.log(import_picocolors5.default.dim(` mode: ${config.mode}`));
|
|
4918
5324
|
console.log(import_picocolors5.default.dim(` target: electron (\u2265 ${config.electron.minVersion})`));
|
|
@@ -4923,15 +5329,13 @@ async function buildElectron(inlineConfig = {}) {
|
|
|
4923
5329
|
import_node_fs7.default.mkdirSync(outDir, { recursive: true });
|
|
4924
5330
|
const rendererOutDir = import_node_path10.default.join(outDir, "renderer");
|
|
4925
5331
|
const { build: build2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
4926
|
-
await build2({
|
|
4927
|
-
...inlineConfig,
|
|
4928
|
-
target: "web",
|
|
5332
|
+
await build2(createElectronRendererConfig(config, inlineConfig, {
|
|
4929
5333
|
build: {
|
|
4930
5334
|
...inlineConfig.build,
|
|
4931
5335
|
outDir: rendererOutDir,
|
|
4932
5336
|
emptyOutDir: false
|
|
4933
5337
|
}
|
|
4934
|
-
});
|
|
5338
|
+
}));
|
|
4935
5339
|
const mainEntry = import_node_path10.default.resolve(config.root, config.electron.main);
|
|
4936
5340
|
if (!import_node_fs7.default.existsSync(mainEntry)) {
|
|
4937
5341
|
throw new Error(
|
|
@@ -4985,7 +5389,8 @@ async function bundleNode(config, entry, opts) {
|
|
|
4985
5389
|
const result = transformCode(id, code, {
|
|
4986
5390
|
sourcemap: !!config.build.sourcemap,
|
|
4987
5391
|
jsxRuntime: "automatic",
|
|
4988
|
-
jsxImportSource: config.framework === "vue" ? "vue" : "react"
|
|
5392
|
+
jsxImportSource: config.framework === "vue" ? "vue" : "react",
|
|
5393
|
+
target: config.electron.nodeTarget
|
|
4989
5394
|
});
|
|
4990
5395
|
return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
|
|
4991
5396
|
}
|
|
@@ -4996,7 +5401,11 @@ async function bundleNode(config, entry, opts) {
|
|
|
4996
5401
|
...restInputOptions,
|
|
4997
5402
|
input: entry,
|
|
4998
5403
|
platform: "node",
|
|
4999
|
-
transform: {
|
|
5404
|
+
transform: {
|
|
5405
|
+
...userTransform,
|
|
5406
|
+
target: config.electron.nodeTarget,
|
|
5407
|
+
define: mergedDefine
|
|
5408
|
+
},
|
|
5000
5409
|
plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
|
|
5001
5410
|
});
|
|
5002
5411
|
import_node_fs7.default.mkdirSync(import_node_path10.default.dirname(opts.outFile), { recursive: true });
|
|
@@ -5013,6 +5422,25 @@ async function bundleNode(config, entry, opts) {
|
|
|
5013
5422
|
console.log(import_picocolors5.default.dim(` \u2713 ${opts.label} \u2192 ${import_node_path10.default.relative(config.root, opts.outFile)}`));
|
|
5014
5423
|
return opts.outFile;
|
|
5015
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
|
+
}
|
|
5016
5444
|
function outFileName(outDir, base, format) {
|
|
5017
5445
|
const ext = format === "cjs" ? ".cjs" : ".mjs";
|
|
5018
5446
|
return import_node_path10.default.join(outDir, base + ext);
|
|
@@ -5063,11 +5491,15 @@ async function startElectronDev(inlineConfig = {}) {
|
|
|
5063
5491
|
const { noSpawn, ...rest } = inlineConfig;
|
|
5064
5492
|
const config = await resolveConfig({ ...rest, target: "electron" }, "serve");
|
|
5065
5493
|
warnElectronVersion(config);
|
|
5066
|
-
console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.1
|
|
5494
|
+
console.log(import_picocolors10.default.cyan("\n\u26A1 nasti electron dev") + import_picocolors10.default.dim(` v${"2.3.1"}`));
|
|
5067
5495
|
const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
5068
|
-
const server = await createServer2({
|
|
5496
|
+
const server = await createServer2({
|
|
5497
|
+
...rest,
|
|
5498
|
+
target: "electron",
|
|
5499
|
+
framework: config.framework
|
|
5500
|
+
});
|
|
5069
5501
|
await server.listen();
|
|
5070
|
-
const devUrl = `http://localhost:${server.config.server.port}
|
|
5502
|
+
const devUrl = `http://localhost:${server.config.server.port}` + electronRendererDevPath(config.electron.renderer);
|
|
5071
5503
|
console.log(import_picocolors10.default.dim(` renderer: ${devUrl}`));
|
|
5072
5504
|
const stageDir = import_node_path16.default.resolve(config.root, ".nasti");
|
|
5073
5505
|
import_node_fs11.default.mkdirSync(stageDir, { recursive: true });
|
|
@@ -5189,14 +5621,18 @@ async function compileNode(config, entry, opts) {
|
|
|
5189
5621
|
const result = transformCode(id, code, {
|
|
5190
5622
|
sourcemap: true,
|
|
5191
5623
|
jsxRuntime: "automatic",
|
|
5192
|
-
jsxImportSource: config.framework === "vue" ? "vue" : "react"
|
|
5624
|
+
jsxImportSource: config.framework === "vue" ? "vue" : "react",
|
|
5625
|
+
target: config.electron.nodeTarget
|
|
5193
5626
|
});
|
|
5194
5627
|
return { code: result.code, map: result.map ? JSON.parse(result.map) : void 0 };
|
|
5195
5628
|
}
|
|
5196
5629
|
};
|
|
5197
5630
|
const bundle2 = await (0, import_rolldown3.rolldown)({
|
|
5198
5631
|
input: entry,
|
|
5199
|
-
transform: {
|
|
5632
|
+
transform: {
|
|
5633
|
+
target: config.electron.nodeTarget,
|
|
5634
|
+
define: envDefine
|
|
5635
|
+
},
|
|
5200
5636
|
platform: "node",
|
|
5201
5637
|
plugins: [oxcTransformPlugin, electronPlugin(config), resolvePlugin(config)]
|
|
5202
5638
|
});
|
|
@@ -5212,6 +5648,10 @@ async function compileNode(config, entry, opts) {
|
|
|
5212
5648
|
});
|
|
5213
5649
|
await bundle2.close();
|
|
5214
5650
|
}
|
|
5651
|
+
function electronRendererDevPath(renderer) {
|
|
5652
|
+
const normalized = renderer.split(import_node_path16.default.sep).join("/").replace(/^\.?\//, "");
|
|
5653
|
+
return normalized === "index.html" ? "/" : `/${normalized}`;
|
|
5654
|
+
}
|
|
5215
5655
|
function resolveElectronBinary(config) {
|
|
5216
5656
|
if (config.electron.electronPath && import_node_fs11.default.existsSync(config.electron.electronPath)) {
|
|
5217
5657
|
return config.electron.electronPath;
|
|
@@ -5469,12 +5909,15 @@ init_env();
|
|
|
5469
5909
|
buildElectron,
|
|
5470
5910
|
buildEnvDefine,
|
|
5471
5911
|
createDebugger,
|
|
5912
|
+
createElectronRendererConfig,
|
|
5472
5913
|
createLogger,
|
|
5473
5914
|
createNoopHotChannel,
|
|
5474
5915
|
createServer,
|
|
5475
5916
|
createWsHotChannel,
|
|
5476
5917
|
defineConfig,
|
|
5918
|
+
detectFramework,
|
|
5477
5919
|
electronPlugin,
|
|
5920
|
+
electronRendererDevPath,
|
|
5478
5921
|
loadEnv,
|
|
5479
5922
|
monacoEditorPlugin,
|
|
5480
5923
|
printServerUrls,
|