@nasti-toolchain/nasti 2.5.0 → 2.5.2

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.
Files changed (60) hide show
  1. package/README.md +34 -2
  2. package/dist/build-CENae4VM.js +748 -0
  3. package/dist/build-CENae4VM.js.map +1 -0
  4. package/dist/build-vjh6h7PB.cjs +786 -0
  5. package/dist/build-vjh6h7PB.cjs.map +1 -0
  6. package/dist/cli.cjs +166 -7608
  7. package/dist/cli.cjs.map +1 -1
  8. package/dist/cli.d.cts +1 -2
  9. package/dist/cli.d.ts +1 -2
  10. package/dist/cli.js +166 -7606
  11. package/dist/cli.js.map +1 -1
  12. package/dist/client/hmr.cjs +0 -19
  13. package/dist/client/hmr.d.cts +14 -12
  14. package/dist/client/hmr.d.cts.map +1 -0
  15. package/dist/client/hmr.d.ts +14 -12
  16. package/dist/client/hmr.d.ts.map +1 -0
  17. package/dist/client/hmr.js +1 -1
  18. package/dist/dev-engine-DJL9sfv5.cjs +473 -0
  19. package/dist/dev-engine-DJL9sfv5.cjs.map +1 -0
  20. package/dist/dev-engine-DapuTPIz.js +469 -0
  21. package/dist/dev-engine-DapuTPIz.js.map +1 -0
  22. package/dist/electron-Bj8H-BJ6.js +278 -0
  23. package/dist/electron-Bj8H-BJ6.js.map +1 -0
  24. package/dist/electron-D0PSXz-a.cjs +316 -0
  25. package/dist/electron-D0PSXz-a.cjs.map +1 -0
  26. package/dist/electron-dev-BzI2IFnR.cjs +272 -0
  27. package/dist/electron-dev-BzI2IFnR.cjs.map +1 -0
  28. package/dist/electron-dev-CM15gsFp.js +251 -0
  29. package/dist/electron-dev-CM15gsFp.js.map +1 -0
  30. package/dist/env-GY7K1zPK.js +833 -0
  31. package/dist/env-GY7K1zPK.js.map +1 -0
  32. package/dist/env-lOj3yw8v.cjs +924 -0
  33. package/dist/env-lOj3yw8v.cjs.map +1 -0
  34. package/dist/environment-B2jCrhY2.js +1361 -0
  35. package/dist/environment-B2jCrhY2.js.map +1 -0
  36. package/dist/environment-CjmvoHIG.cjs +1443 -0
  37. package/dist/environment-CjmvoHIG.cjs.map +1 -0
  38. package/dist/index.cjs +453 -7976
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +872 -817
  41. package/dist/index.d.cts.map +1 -0
  42. package/dist/index.d.ts +872 -817
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +418 -7939
  45. package/dist/index.js.map +1 -1
  46. package/dist/logger-BVZwdqqT.js +116 -0
  47. package/dist/logger-BVZwdqqT.js.map +1 -0
  48. package/dist/logger-M7AuVVTV.cjs +171 -0
  49. package/dist/logger-M7AuVVTV.cjs.map +1 -0
  50. package/dist/runnable-environment-BjoOvdDa.js +1345 -0
  51. package/dist/runnable-environment-BjoOvdDa.js.map +1 -0
  52. package/dist/runnable-environment-ByA3DHQ6.cjs +1351 -0
  53. package/dist/runnable-environment-ByA3DHQ6.cjs.map +1 -0
  54. package/dist/server-Bqgl25z0.js +1871 -0
  55. package/dist/server-Bqgl25z0.js.map +1 -0
  56. package/dist/server-BynlMdFc.cjs +1895 -0
  57. package/dist/server-BynlMdFc.cjs.map +1 -0
  58. package/package.json +20 -10
  59. package/dist/client/hmr.cjs.map +0 -1
  60. package/dist/client/hmr.js.map +0 -1
@@ -0,0 +1,924 @@
1
+ const require_logger = require("./logger-M7AuVVTV.cjs");
2
+ let node_url = require("node:url");
3
+ let node_path = require("node:path");
4
+ node_path = require_logger.__toESM(node_path, 1);
5
+ let node_fs = require("node:fs");
6
+ node_fs = require_logger.__toESM(node_fs, 1);
7
+ let node_module = require("node:module");
8
+ let oxc_transform = require("oxc-transform");
9
+ //#region src/config/defaults.ts
10
+ const defaultResolve = {
11
+ alias: {},
12
+ extensions: [
13
+ ".ts",
14
+ ".tsx",
15
+ ".js",
16
+ ".jsx",
17
+ ".mjs",
18
+ ".cjs",
19
+ ".json",
20
+ ".vue"
21
+ ],
22
+ conditions: [
23
+ "import",
24
+ "module",
25
+ "browser",
26
+ "default"
27
+ ],
28
+ mainFields: [
29
+ "module",
30
+ "jsnext:main",
31
+ "jsnext",
32
+ "main"
33
+ ]
34
+ };
35
+ const defaultServer = {
36
+ port: 3e3,
37
+ host: "localhost",
38
+ https: false,
39
+ open: false,
40
+ proxy: {},
41
+ cors: true,
42
+ hmr: true
43
+ };
44
+ const defaultBuild = {
45
+ outDir: "dist",
46
+ assetsDir: "assets",
47
+ minify: true,
48
+ sourcemap: false,
49
+ target: "es2022",
50
+ rolldownOptions: {},
51
+ emptyOutDir: true,
52
+ css: {
53
+ inject: true,
54
+ emit: true
55
+ },
56
+ reportCompressedSize: true,
57
+ chunkSizeWarningLimit: 500,
58
+ cssCodeSplit: true,
59
+ cssMinify: true
60
+ };
61
+ const defaultElectron = {
62
+ main: "src/electron/main.ts",
63
+ preload: "src/electron/preload.ts",
64
+ renderer: "index.html",
65
+ nodeTarget: "node22",
66
+ mainFormat: "cjs",
67
+ preloadFormat: "cjs",
68
+ electronPath: "",
69
+ electronArgs: [],
70
+ autoRestart: true,
71
+ minVersion: 41,
72
+ external: ["electron"]
73
+ };
74
+ const defaultExperimental = { bundledDev: false };
75
+ const defaultReact = {
76
+ include: /\.[tj]sx?$/,
77
+ exclude: /node_modules/,
78
+ jsxImportSource: "react",
79
+ jsxRuntime: "automatic",
80
+ compiler: false
81
+ };
82
+ const defaults = {
83
+ root: ".",
84
+ base: "/",
85
+ mode: "development",
86
+ target: "web",
87
+ framework: "auto",
88
+ react: defaultReact,
89
+ resolve: defaultResolve,
90
+ server: defaultServer,
91
+ build: defaultBuild,
92
+ electron: defaultElectron,
93
+ plugins: [],
94
+ envPrefix: ["NASTI_", "VITE_"],
95
+ logLevel: "info",
96
+ clearScreen: true,
97
+ experimental: defaultExperimental
98
+ };
99
+ //#endregion
100
+ //#region src/core/minify.ts
101
+ /**
102
+ * 压缩是否开启 —— 供 `cssMinify` 跟随、体积报告等只关心开关的场合使用。
103
+ *
104
+ * 注意不能简单用 `!!`:`'dce-only'` 与空对象 `{}` 都是真值,但前者只做
105
+ * 死代码消除、后者是「按 OXC 默认项压缩」,二者都算开启;只有 `false`
106
+ * 才是关闭。
107
+ */
108
+ function isMinifyEnabled(minify) {
109
+ return minify !== false && minify !== void 0;
110
+ }
111
+ /**
112
+ * 归一化为 Rolldown `OutputOptions.minify` 接受的值。
113
+ *
114
+ * - `'oxc'`:2.x 之前的历史写法,语义等同 `true`(Rolldown 本来就只有
115
+ * OXC 一个 minifier),归一为 `true` 保持向后兼容;
116
+ * - 对象:原样透传,交给 OXC minifier(`compress` / `mangle` / `codegen`)。
117
+ * 其中 `mangle.keepNames.class` 控制类名是否参与最小化,
118
+ * `mangle.mangleProps` 是 Rolldown 1.2.6 / OXC 0.147 新增的属性名最小化。
119
+ */
120
+ function resolveMinifyOption(minify) {
121
+ if (minify === void 0) return false;
122
+ if (minify === "oxc") return true;
123
+ return minify;
124
+ }
125
+ //#endregion
126
+ //#region src/core/plugin-api.ts
127
+ const apiByConfig = /* @__PURE__ */ new WeakMap();
128
+ /**
129
+ * 按 enforce 与显式插件依赖稳定排序。
130
+ *
131
+ * `pre` 表示列出的插件必须先于当前插件,`post` 表示列出的插件必须后于
132
+ * 当前插件。未安装的可选依赖会被忽略;依赖环会在配置阶段直接报错。
133
+ */
134
+ function orderPlugins(plugins) {
135
+ const baseline = plugins.map((plugin, index) => ({
136
+ plugin,
137
+ index
138
+ })).sort((a, b) => enforceRank(a.plugin) - enforceRank(b.plugin) || a.index - b.index).map(({ plugin }) => plugin);
139
+ const indexesByName = /* @__PURE__ */ new Map();
140
+ baseline.forEach((plugin, index) => {
141
+ const indexes = indexesByName.get(plugin.name) ?? [];
142
+ indexes.push(index);
143
+ indexesByName.set(plugin.name, indexes);
144
+ });
145
+ const edges = baseline.map(() => /* @__PURE__ */ new Set());
146
+ const indegree = baseline.map(() => 0);
147
+ const addEdge = (from, to) => {
148
+ if (from === to || edges[from].has(to)) return;
149
+ edges[from].add(to);
150
+ indegree[to]++;
151
+ };
152
+ baseline.forEach((plugin, current) => {
153
+ for (const dependency of plugin.pre ?? []) for (const before of indexesByName.get(dependency) ?? []) addEdge(before, current);
154
+ for (const dependency of plugin.post ?? []) for (const after of indexesByName.get(dependency) ?? []) addEdge(current, after);
155
+ });
156
+ const ready = indegree.map((degree, index) => ({
157
+ degree,
158
+ index
159
+ })).filter(({ degree }) => degree === 0).map(({ index }) => index);
160
+ const ordered = [];
161
+ while (ready.length > 0) {
162
+ ready.sort((a, b) => a - b);
163
+ const current = ready.shift();
164
+ ordered.push(baseline[current]);
165
+ for (const next of edges[current]) {
166
+ indegree[next]--;
167
+ if (indegree[next] === 0) ready.push(next);
168
+ }
169
+ }
170
+ if (ordered.length !== baseline.length) {
171
+ const cyclic = baseline.filter((_, index) => indegree[index] > 0).map((plugin) => plugin.name);
172
+ throw new Error(`[nasti] circular plugin setup dependency: ${[...new Set(cyclic)].join(", ")}`);
173
+ }
174
+ return ordered;
175
+ }
176
+ /** 创建并注册一次配置解析周期内共享的插件 API。 */
177
+ async function setupPluginApi(config, plugins) {
178
+ const exposed = /* @__PURE__ */ new Map();
179
+ const api = {
180
+ config,
181
+ logger: config.logger,
182
+ expose(key, value) {
183
+ if (exposed.has(key) && exposed.get(key) !== value) throw new Error(`[nasti] plugin API key already exposed: ${String(key)}`);
184
+ exposed.set(key, value);
185
+ },
186
+ useExposed(key) {
187
+ return exposed.get(key);
188
+ }
189
+ };
190
+ apiByConfig.set(config, api);
191
+ for (const plugin of plugins) await plugin.setup?.(api);
192
+ return api;
193
+ }
194
+ function getPluginApi(config) {
195
+ const api = apiByConfig.get(config);
196
+ if (!api) throw new Error("[nasti] internal: plugin API requested before setup; getPluginApi requires the original ResolvedConfig reference registered by setupPluginApi, not a shallow copy");
197
+ return api;
198
+ }
199
+ function enforceRank(plugin) {
200
+ if (plugin.enforce === "pre") return 0;
201
+ if (plugin.enforce === "post") return 2;
202
+ return 1;
203
+ }
204
+ //#endregion
205
+ //#region src/config/index.ts
206
+ /** 读取 tsconfig.json 中的 paths,转换为 Nasti alias 格式 */
207
+ function loadTsconfigPaths(root) {
208
+ const tsconfigPath = node_path.default.resolve(root, "tsconfig.json");
209
+ if (!node_fs.default.existsSync(tsconfigPath)) return {};
210
+ try {
211
+ const stripped = node_fs.default.readFileSync(tsconfigPath, "utf-8").replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
212
+ const tsconfig = JSON.parse(stripped);
213
+ const paths = tsconfig?.compilerOptions?.paths ?? {};
214
+ const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? ".";
215
+ const alias = {};
216
+ for (const [pattern, targets] of Object.entries(paths)) {
217
+ if (!targets.length) continue;
218
+ const cleanKey = pattern.replace(/\/\*$/, "");
219
+ const cleanTarget = targets[0].replace(/\/\*$/, "");
220
+ alias[cleanKey] = node_path.default.resolve(root, baseUrl, cleanTarget);
221
+ }
222
+ return alias;
223
+ } catch {
224
+ return {};
225
+ }
226
+ }
227
+ function defineConfig(config) {
228
+ return config;
229
+ }
230
+ const CONFIG_FILES = [
231
+ "nasti.config.ts",
232
+ "nasti.config.js",
233
+ "nasti.config.mjs",
234
+ "nasti.config.mts"
235
+ ];
236
+ async function loadConfigFromFile(root) {
237
+ for (const file of CONFIG_FILES) {
238
+ const filePath = node_path.default.resolve(root, file);
239
+ if (!node_fs.default.existsSync(filePath)) continue;
240
+ if (file.endsWith(".ts") || file.endsWith(".mts")) return await loadTsConfig(filePath);
241
+ const mod = await import((0, node_url.pathToFileURL)(filePath).href);
242
+ return mod.default ?? mod;
243
+ }
244
+ return {};
245
+ }
246
+ /**
247
+ * 将 framework:auto 收敛为具体框架。
248
+ *
249
+ * 优先使用源码中的 .vue 文件消除同时安装 React/Vue 时的歧义,其次读取
250
+ * package.json dependencies,最后回退 React 以保持 1.x 默认行为。
251
+ */
252
+ function detectFramework(root) {
253
+ if (containsVueFile(node_path.default.resolve(root, "src"))) return "vue";
254
+ const packagePath = node_path.default.resolve(root, "package.json");
255
+ if (node_fs.default.existsSync(packagePath)) try {
256
+ const pkg = JSON.parse(node_fs.default.readFileSync(packagePath, "utf-8"));
257
+ const dependencies = {
258
+ ...pkg.dependencies ?? {},
259
+ ...pkg.devDependencies ?? {},
260
+ ...pkg.peerDependencies ?? {},
261
+ ...pkg.optionalDependencies ?? {}
262
+ };
263
+ const hasVue = "vue" in dependencies || "@vue/runtime-dom" in dependencies;
264
+ const hasReact = "react" in dependencies || "react-dom" in dependencies;
265
+ if (hasVue && !hasReact) return "vue";
266
+ if (hasReact) return "react";
267
+ if (hasVue) return "vue";
268
+ } catch {}
269
+ return "react";
270
+ }
271
+ function containsVueFile(dir, depth = 0) {
272
+ if (depth > 5 || !node_fs.default.existsSync(dir)) return false;
273
+ try {
274
+ for (const entry of node_fs.default.readdirSync(dir, { withFileTypes: true })) {
275
+ if (entry.isFile() && entry.name.endsWith(".vue")) return true;
276
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && containsVueFile(node_path.default.join(dir, entry.name), depth + 1)) return true;
277
+ }
278
+ } catch {
279
+ return false;
280
+ }
281
+ return false;
282
+ }
283
+ async function loadTsConfig(filePath) {
284
+ const { transformSync } = await import("oxc-transform");
285
+ const result = transformSync(filePath, node_fs.default.readFileSync(filePath, "utf-8"), { typescript: {} });
286
+ const tmpFile = filePath + ".timestamp-" + Date.now() + ".mjs";
287
+ try {
288
+ node_fs.default.writeFileSync(tmpFile, result.code);
289
+ const mod = await import((0, node_url.pathToFileURL)(tmpFile).href);
290
+ return mod.default ?? mod;
291
+ } finally {
292
+ node_fs.default.unlinkSync(tmpFile);
293
+ }
294
+ }
295
+ /**
296
+ * Resolve the final configuration by loading file-based config, merging with inline config, and applying plugin hooks.
297
+ *
298
+ * Loads configuration from the project root (derived from `inlineConfig.root` or defaults), deep-merges file config with `inlineConfig` (inline takes precedence), runs each plugin's `config` hook and incorporates any returned partial config, constructs the complete `ResolvedConfig` (filling defaults for missing values and resolving tsconfig paths), filters plugins according to their `apply` field, and then invokes each included plugin's `configResolved` hook.
299
+ *
300
+ * @param inlineConfig - User-provided configuration that overrides file config and defaults
301
+ * @param command - The current command, either `"build"` or `"serve"`, which influences mode and plugin filtering
302
+ * @returns The fully resolved configuration with defaults applied, plugins filtered, and plugin hooks executed
303
+ */
304
+ async function resolveConfig(inlineConfig = {}, command) {
305
+ const root = node_path.default.resolve(inlineConfig.root ?? defaults.root);
306
+ const fileConfig = await loadConfigFromFile(root);
307
+ const merged = deepMerge(deepMerge({}, fileConfig), inlineConfig);
308
+ const rawPlugins = [...fileConfig.plugins ?? [], ...inlineConfig.plugins ?? []];
309
+ const mode = merged.mode ?? (command === "build" ? "production" : "development");
310
+ const env = {
311
+ mode,
312
+ command
313
+ };
314
+ for (const plugin of rawPlugins) if (plugin.config) {
315
+ const result = await plugin.config(merged, env);
316
+ if (result) Object.assign(merged, result);
317
+ }
318
+ const logLevel = merged.logLevel ?? defaults.logLevel;
319
+ const clearScreen = merged.clearScreen ?? defaults.clearScreen;
320
+ const logger = require_logger.createLogger(logLevel, {
321
+ allowClearScreen: clearScreen,
322
+ customLogger: merged.customLogger
323
+ });
324
+ const mergedBuild = {
325
+ ...defaults.build,
326
+ ...merged.build,
327
+ css: {
328
+ ...defaults.build.css,
329
+ ...merged.build?.css
330
+ }
331
+ };
332
+ if (merged.build?.cssMinify === void 0) mergedBuild.cssMinify = isMinifyEnabled(mergedBuild.minify);
333
+ const resolved = {
334
+ root,
335
+ base: merged.base ?? defaults.base,
336
+ mode,
337
+ target: merged.target ?? defaults.target,
338
+ framework: (merged.framework ?? defaults.framework) === "auto" ? detectFramework(root) : merged.framework,
339
+ react: {
340
+ include: merged.react?.include ?? defaultReact.include,
341
+ exclude: merged.react?.exclude ?? defaultReact.exclude,
342
+ jsxImportSource: merged.react?.jsxImportSource ?? defaultReact.jsxImportSource,
343
+ jsxRuntime: merged.react?.jsxRuntime ?? defaultReact.jsxRuntime,
344
+ compiler: merged.react?.compiler === true ? {} : merged.react?.compiler ?? defaultReact.compiler
345
+ },
346
+ command,
347
+ resolve: {
348
+ alias: {
349
+ ...loadTsconfigPaths(root),
350
+ ...defaults.resolve.alias,
351
+ ...merged.resolve?.alias
352
+ },
353
+ extensions: merged.resolve?.extensions ?? defaults.resolve.extensions,
354
+ conditions: merged.resolve?.conditions ?? defaults.resolve.conditions,
355
+ mainFields: merged.resolve?.mainFields ?? defaults.resolve.mainFields
356
+ },
357
+ plugins: [],
358
+ server: {
359
+ ...defaults.server,
360
+ ...merged.server
361
+ },
362
+ build: mergedBuild,
363
+ electron: {
364
+ ...defaults.electron,
365
+ ...merged.electron
366
+ },
367
+ envPrefix: Array.isArray(merged.envPrefix) ? merged.envPrefix : merged.envPrefix ? [merged.envPrefix] : [...defaults.envPrefix],
368
+ logLevel,
369
+ clearScreen,
370
+ logger,
371
+ environments: {},
372
+ experimental: { bundledDev: merged.experimental?.bundledDev ?? defaults.experimental.bundledDev }
373
+ };
374
+ const rawUserEnvironments = {
375
+ client: {},
376
+ ssr: {},
377
+ ...merged.environments ?? {}
378
+ };
379
+ const userEnvironments = Object.fromEntries(Object.entries(rawUserEnvironments).map(([name, options]) => [name, deepMerge({}, options)]));
380
+ for (const [name, envOptions] of Object.entries(userEnvironments)) for (const plugin of rawPlugins) if (plugin.configEnvironment) {
381
+ const result = await plugin.configEnvironment(name, envOptions, env);
382
+ if (result) Object.assign(envOptions, deepMerge(envOptions, result));
383
+ }
384
+ for (const [name, envOptions] of Object.entries(userEnvironments)) {
385
+ const consumer = envOptions.consumer ?? (name === "client" ? "client" : "server");
386
+ const vueOptions = deepMerge({}, envOptions.vue ?? {});
387
+ if (name === "client") {
388
+ if (envOptions.resolve) Object.assign(resolved.resolve, {
389
+ ...envOptions.resolve,
390
+ alias: {
391
+ ...resolved.resolve.alias,
392
+ ...envOptions.resolve.alias
393
+ }
394
+ });
395
+ if (envOptions.build) {
396
+ const { css, ...environmentBuild } = envOptions.build;
397
+ Object.assign(resolved.build, environmentBuild);
398
+ if (css) resolved.build.css = {
399
+ ...resolved.build.css,
400
+ ...css
401
+ };
402
+ }
403
+ resolved.environments.client = {
404
+ consumer,
405
+ buildEnabled: envOptions.buildEnabled ?? true,
406
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
407
+ html: node_path.default.resolve(root, envOptions.html ?? (resolved.target === "electron" ? resolved.electron.renderer : "index.html")),
408
+ driver: envOptions.driver,
409
+ resolve: resolved.resolve,
410
+ build: resolved.build,
411
+ vue: vueOptions
412
+ };
413
+ continue;
414
+ }
415
+ resolved.environments[name] = {
416
+ consumer,
417
+ buildEnabled: envOptions.buildEnabled ?? true,
418
+ entry: normalizeEnvironmentEntries(envOptions.entry, root),
419
+ html: envOptions.consumer === "client" && envOptions.html ? node_path.default.resolve(root, envOptions.html) : void 0,
420
+ driver: envOptions.driver,
421
+ vue: vueOptions,
422
+ resolve: {
423
+ alias: {
424
+ ...resolved.resolve.alias,
425
+ ...envOptions.resolve?.alias
426
+ },
427
+ extensions: envOptions.resolve?.extensions ?? [...resolved.resolve.extensions],
428
+ conditions: envOptions.resolve?.conditions ?? (consumer === "server" ? ["node", ...resolved.resolve.conditions.filter((c) => c !== "browser")] : [...resolved.resolve.conditions]),
429
+ mainFields: envOptions.resolve?.mainFields ?? (consumer === "server" ? ["module", "main"] : [...resolved.resolve.mainFields])
430
+ },
431
+ build: {
432
+ ...resolved.build,
433
+ ...envOptions.build,
434
+ css: {
435
+ ...resolved.build.css,
436
+ ...envOptions.build?.css
437
+ },
438
+ outDir: envOptions.build?.outDir ?? node_path.default.join(resolved.build.outDir, name),
439
+ minify: envOptions.build?.minify ?? (consumer === "server" ? false : resolved.build.minify)
440
+ }
441
+ };
442
+ }
443
+ assertClientEnvironmentMirror(resolved);
444
+ const filteredPlugins = orderPlugins(rawPlugins.filter((p) => {
445
+ if (!p.apply) return true;
446
+ if (typeof p.apply === "function") return p.apply(resolved, env);
447
+ return p.apply === command;
448
+ }));
449
+ resolved.plugins = filteredPlugins;
450
+ await setupPluginApi(resolved, filteredPlugins);
451
+ if (resolved.target === "electron") {
452
+ const autoExternal = detectNativeDeps(root);
453
+ if (autoExternal.length > 0) {
454
+ const current = new Set(resolved.electron.external ?? []);
455
+ for (const dep of autoExternal) current.add(dep);
456
+ resolved.electron.external = [...current];
457
+ }
458
+ }
459
+ for (const plugin of resolved.plugins) if (plugin.configResolved) await plugin.configResolved(resolved);
460
+ return resolved;
461
+ }
462
+ function normalizeEnvironmentEntries(entry, root) {
463
+ return (Array.isArray(entry) ? entry : entry ? [entry] : []).map((item) => node_path.default.resolve(root, item));
464
+ }
465
+ /**
466
+ * 扫描项目 dependencies,返回需要从 Electron 主进程 bundle 中外部化的 native 模块列表。
467
+ *
468
+ * 判定依据(任一满足即视为 native):
469
+ * - 包根目录存在 binding.gyp
470
+ * - package.json 有 gypfile: true
471
+ * - 包内存在 *.node 预编译产物
472
+ * - 常见已知 native 包白名单(对付有些包不走 node-gyp 但运行时仍需原生 addon 的场景)
473
+ */
474
+ function detectNativeDeps(root) {
475
+ const result = /* @__PURE__ */ new Set();
476
+ const pkgJsonPath = node_path.default.resolve(root, "package.json");
477
+ if (!node_fs.default.existsSync(pkgJsonPath)) return [];
478
+ let pkg;
479
+ try {
480
+ pkg = JSON.parse(node_fs.default.readFileSync(pkgJsonPath, "utf-8"));
481
+ } catch {
482
+ return [];
483
+ }
484
+ const deps = {
485
+ ...pkg.dependencies ?? {},
486
+ ...pkg.optionalDependencies ?? {}
487
+ };
488
+ const KNOWN_NATIVE = /* @__PURE__ */ new Set([
489
+ "node-pty",
490
+ "better-sqlite3",
491
+ "sharp",
492
+ "serialport",
493
+ "@vscode/tree-sitter-wasm",
494
+ "keytar",
495
+ "nodegit",
496
+ "sqlite3",
497
+ "fsevents"
498
+ ]);
499
+ for (const dep of Object.keys(deps)) {
500
+ if (KNOWN_NATIVE.has(dep)) {
501
+ result.add(dep);
502
+ continue;
503
+ }
504
+ const depDir = node_path.default.resolve(root, "node_modules", dep);
505
+ if (!node_fs.default.existsSync(depDir)) continue;
506
+ if (node_fs.default.existsSync(node_path.default.join(depDir, "binding.gyp"))) {
507
+ result.add(dep);
508
+ continue;
509
+ }
510
+ const subPkg = node_path.default.join(depDir, "package.json");
511
+ if (node_fs.default.existsSync(subPkg)) try {
512
+ const sub = JSON.parse(node_fs.default.readFileSync(subPkg, "utf-8"));
513
+ if (sub.gypfile === true || sub.binary?.module_name) {
514
+ result.add(dep);
515
+ continue;
516
+ }
517
+ } catch {}
518
+ if (hasDotNodeFile(depDir)) result.add(dep);
519
+ }
520
+ return [...result];
521
+ }
522
+ function hasDotNodeFile(dir, depth = 0) {
523
+ if (depth > 3) return false;
524
+ try {
525
+ const entries = node_fs.default.readdirSync(dir, { withFileTypes: true });
526
+ for (const e of entries) {
527
+ if (e.isFile() && e.name.endsWith(".node")) return true;
528
+ if (e.isDirectory() && (e.name === "build" || e.name === "prebuilds" || e.name === "Release")) {
529
+ if (hasDotNodeFile(node_path.default.join(dir, e.name), depth + 1)) return true;
530
+ }
531
+ }
532
+ } catch {}
533
+ return false;
534
+ }
535
+ /**
536
+ * 运行时镜像断言(Phase 1 检查,NASTI_2.0_PLAN.md §1.2):
537
+ * top-level `resolve`/`build` 与 `environments.client` 必须**精确镜像**,
538
+ * 否则读 flat config 的既有插件与 Environment API 会看到两份配置。
539
+ *
540
+ * 比较规则(文档化):
541
+ * - 快路径:引用相等(resolveConfig 保证同引用,正常永远走这里)
542
+ * - 慢路径:JSON 序列化逐字节比较 resolve{alias,extensions,conditions,
543
+ * mainFields} 与 build 全字段(含 sourcemap);函数值(如
544
+ * rolldownOptions.output.advancedChunks 的 test 回调)会被 JSON 丢弃,
545
+ * 不参与比较 —— 不要在 per-env build 覆盖里只改函数字段
546
+ * - 设 NASTI_DISABLE_MIRROR_ASSERT=1 可关闭(不建议)
547
+ */
548
+ function assertClientEnvironmentMirror(config) {
549
+ if (process.env.NASTI_DISABLE_MIRROR_ASSERT) return;
550
+ const client = config.environments.client;
551
+ if (!client) throw new Error("[nasti] internal: environments.client missing after resolveConfig");
552
+ if (client.resolve === config.resolve && client.build === config.build) return;
553
+ const pairs = [[
554
+ "resolve",
555
+ config.resolve,
556
+ client.resolve
557
+ ], [
558
+ "build",
559
+ config.build,
560
+ client.build
561
+ ]];
562
+ for (const [field, top, env] of pairs) {
563
+ const a = JSON.stringify(top);
564
+ const b = JSON.stringify(env);
565
+ if (a !== b) throw new Error(`[nasti] config mirror violation: top-level \`${field}\` and \`environments.client.${field}\` diverged.\n top-level: ${a}\n client: ${b}\ntop-level 与 client 环境必须精确镜像 —— 请通过 top-level 或 environments.client 之一配置,不要在解析后分别修改两者。`);
566
+ }
567
+ }
568
+ /** 仅普通对象字面量返回 true —— 类实例 / 函数 / Date / Map 等不算,
569
+ * 避免 deepMerge 递归剥掉它们的原型方法(如自定义 customLogger 实例)。 */
570
+ function isPlainObject(val) {
571
+ if (val === null || typeof val !== "object") return false;
572
+ const proto = Object.getPrototypeOf(val);
573
+ return proto === Object.prototype || proto === null;
574
+ }
575
+ function deepMerge(target, source) {
576
+ const result = { ...target };
577
+ for (const key of Object.keys(source)) {
578
+ const val = source[key];
579
+ if (isPlainObject(val)) result[key] = deepMerge(result[key] ?? {}, val);
580
+ else if (val !== void 0) result[key] = val;
581
+ }
582
+ return result;
583
+ }
584
+ //#endregion
585
+ //#region src/plugins/resolve.ts
586
+ function resolvePlugin(config) {
587
+ const { alias, extensions } = config.resolve;
588
+ const require$1 = (0, node_module.createRequire)(node_path.default.resolve(config.root, "package.json"));
589
+ const aliasEntries = Object.entries(alias).sort(([a], [b]) => b.length - a.length);
590
+ let vueRuntimeEntry = null;
591
+ if (config.framework === "vue") try {
592
+ const vuePkgJson = require$1.resolve("vue/package.json", { paths: [config.root] });
593
+ const vueDir = node_path.default.dirname(vuePkgJson);
594
+ const mod = JSON.parse(node_fs.default.readFileSync(vuePkgJson, "utf-8")).module;
595
+ const entry = node_path.default.join(vueDir, mod ?? "dist/vue.runtime.esm-bundler.js");
596
+ if (node_fs.default.existsSync(entry)) vueRuntimeEntry = entry;
597
+ } catch {}
598
+ return {
599
+ name: "nasti:resolve",
600
+ enforce: "pre",
601
+ resolveId(source, importer) {
602
+ for (const [key, value] of aliasEntries) if (source === key || source.startsWith(key + "/")) {
603
+ const aliasBase = resolveAliasTarget(value, config.root);
604
+ const sub = source.slice(key.length).replace(/^\//, "");
605
+ const resolved = tryResolveFile(sub ? node_path.default.join(aliasBase, sub) : aliasBase, extensions);
606
+ if (resolved) return resolved;
607
+ break;
608
+ }
609
+ if (source.startsWith("/") && !source.startsWith("//")) {
610
+ const resolved = tryResolveFile(node_path.default.join(config.root, source.slice(1)), extensions);
611
+ if (resolved) return resolved;
612
+ }
613
+ if (node_path.default.isAbsolute(source) && node_fs.default.existsSync(source)) {
614
+ const resolved = tryResolveFile(source, extensions);
615
+ if (resolved) return resolved;
616
+ }
617
+ if (source.startsWith(".")) {
618
+ const dir = importer ? node_path.default.dirname(importer) : config.root;
619
+ const resolved = tryResolveFile(node_path.default.resolve(dir, source), extensions);
620
+ if (resolved) return resolved;
621
+ }
622
+ if (!source.startsWith("/") && !source.startsWith(".")) {
623
+ if (vueRuntimeEntry && source === "vue") return vueRuntimeEntry;
624
+ if (config.command === "build") return null;
625
+ try {
626
+ return require$1.resolve(source, { paths: [importer ? node_path.default.dirname(importer) : config.root] });
627
+ } catch {
628
+ return null;
629
+ }
630
+ }
631
+ return null;
632
+ },
633
+ load(id) {
634
+ if (id.startsWith("\0")) return null;
635
+ if (!node_fs.default.existsSync(id)) return null;
636
+ if (id.endsWith(".json")) return `export default ${node_fs.default.readFileSync(id, "utf-8")}`;
637
+ return null;
638
+ }
639
+ };
640
+ }
641
+ /**
642
+ * 把 alias 值统一成磁盘绝对路径,匹配 dev server 的语义:
643
+ * - `/Users/.../src` 真正的绝对路径 → 直接用
644
+ * - `/src` 用户写的项目根相对 → 按 `<root>/src` 处理
645
+ * - 其余字符串相对 root 解析
646
+ */
647
+ function resolveAliasTarget(value, root) {
648
+ if (node_path.default.isAbsolute(value) && node_fs.default.existsSync(value)) return value;
649
+ if (value.startsWith("/")) return node_path.default.join(root, value.slice(1));
650
+ return node_path.default.resolve(root, value);
651
+ }
652
+ /** 尝试解析文件,带扩展名补全和 index 文件查找 */
653
+ function tryResolveFile(file, extensions) {
654
+ if (node_fs.default.existsSync(file) && node_fs.default.statSync(file).isFile()) return file;
655
+ for (const ext of extensions) {
656
+ const withExt = file + ext;
657
+ if (node_fs.default.existsSync(withExt) && node_fs.default.statSync(withExt).isFile()) return withExt;
658
+ }
659
+ if (node_fs.default.existsSync(file) && node_fs.default.statSync(file).isDirectory()) for (const ext of extensions) {
660
+ const indexFile = node_path.default.join(file, "index" + ext);
661
+ if (node_fs.default.existsSync(indexFile)) return indexFile;
662
+ }
663
+ return null;
664
+ }
665
+ //#endregion
666
+ //#region src/core/transformer.ts
667
+ const JS_EXTENSIONS = /\.(js|mjs|cjs)$/;
668
+ const TS_EXTENSIONS = /\.(ts|mts|cts)$/;
669
+ const JSX_EXTENSIONS = /\.(jsx|tsx)$/;
670
+ function shouldTransform(id) {
671
+ return TS_EXTENSIONS.test(id) || JSX_EXTENSIONS.test(id) || JS_EXTENSIONS.test(id) && false;
672
+ }
673
+ function transformCode(filename, code, options = {}) {
674
+ const isTS = TS_EXTENSIONS.test(filename) || /\.tsx$/.test(filename);
675
+ const isJSX = JSX_EXTENSIONS.test(filename);
676
+ const result = (0, oxc_transform.transformSync)(filename, code, {
677
+ typescript: isTS ? {} : void 0,
678
+ jsx: isJSX || /\.tsx$/.test(filename) ? {
679
+ runtime: options.jsxRuntime ?? "automatic",
680
+ importSource: options.jsxImportSource ?? "react",
681
+ refresh: options.reactRefresh ?? false
682
+ } : void 0,
683
+ sourcemap: options.sourcemap ?? true,
684
+ target: options.target
685
+ });
686
+ if (result.errors && result.errors.length > 0) {
687
+ const msg = result.errors.map((e) => e.message ?? String(e)).join("\n");
688
+ throw new Error(`OXC transform failed for ${filename}:\n${msg}`);
689
+ }
690
+ return {
691
+ code: result.code,
692
+ map: result.map ? JSON.stringify(result.map) : null
693
+ };
694
+ }
695
+ /** 与 @vitejs/plugin-react 一致的线性预筛选,宁可误报也不漏掉组件或 Hook。 */
696
+ const defaultReactCompilerCodeFilter = /forwardRef|memo|\b(?:[A-Z]|use[A-Z0-9])/;
697
+ let reactCompilerImplementation;
698
+ /**
699
+ * React 专用转换入口。默认仍走既有同步 OXC 管线;仅在显式开启 Compiler 时
700
+ * 加载可选的原生编译器,并且只对 client consumer 启用 memo 编译。
701
+ */
702
+ async function transformReactCode(filename, code, options) {
703
+ if (!matchesReactFilter(filename, options.react.include, options.react.exclude)) return null;
704
+ if (!options.react.compiler) {
705
+ if (!shouldTransform(filename)) return null;
706
+ return transformCode(filename, code, {
707
+ sourcemap: options.sourcemap,
708
+ jsxRuntime: options.react.jsxRuntime,
709
+ jsxImportSource: options.react.jsxImportSource,
710
+ reactRefresh: options.reactRefresh,
711
+ target: options.target
712
+ });
713
+ }
714
+ if (!shouldTransform(filename)) return null;
715
+ const compiler = await loadReactCompiler();
716
+ const compilerOptions = options.react.compiler;
717
+ const shouldCompile = options.consumer === "client" && (compilerOptions.compilationMode === "annotation" ? /['"]use memo['"]/.test(code) : defaultReactCompilerCodeFilter.test(code));
718
+ const result = await compiler.transform(cleanTransformId(filename), code, {
719
+ jsx: {
720
+ runtime: options.react.jsxRuntime,
721
+ development: options.development,
722
+ importSource: options.react.jsxImportSource,
723
+ refresh: options.consumer === "client" && !!options.reactRefresh
724
+ },
725
+ reactCompiler: shouldCompile ? compilerOptions : false,
726
+ sourcemap: options.sourcemap ?? true
727
+ });
728
+ const diagnostics = result.errors.map((error) => `${error.message}${error.codeframe ? `\n${error.codeframe}` : ""}`);
729
+ if (result.fatal) throw new Error(diagnostics.join("\n\n") || `React Compiler transform failed for ${filename}`);
730
+ for (const diagnostic of diagnostics) options.onWarning?.(diagnostic);
731
+ return {
732
+ code: result.code,
733
+ map: result.map ? JSON.stringify(result.map) : null
734
+ };
735
+ }
736
+ function matchesReactFilter(id, include, exclude) {
737
+ const cleanId = cleanTransformId(id);
738
+ return matchesFilter(cleanId, include) && !matchesFilter(cleanId, exclude);
739
+ }
740
+ function matchesFilter(id, filter) {
741
+ return (Array.isArray(filter) ? filter : [filter]).some((pattern) => {
742
+ if (pattern instanceof RegExp) {
743
+ pattern.lastIndex = 0;
744
+ return pattern.test(id);
745
+ }
746
+ if (!pattern.includes("*")) return id.includes(pattern);
747
+ const expression = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\0/g, ".*");
748
+ return new RegExp(`^${expression}$`).test(id);
749
+ });
750
+ }
751
+ function cleanTransformId(id) {
752
+ return id.split(/[?#]/, 1)[0];
753
+ }
754
+ async function loadReactCompiler() {
755
+ if (reactCompilerImplementation) return reactCompilerImplementation;
756
+ try {
757
+ reactCompilerImplementation = await import("oxc-transform-react");
758
+ return reactCompilerImplementation;
759
+ } catch (error) {
760
+ throw new Error("[nasti] React Compiler requires the optional \"oxc-transform-react\" package. Install it before setting react.compiler." + (error instanceof Error ? `\n${error.message}` : ""));
761
+ }
762
+ }
763
+ //#endregion
764
+ //#region src/core/env.ts
765
+ /**
766
+ * 从 .env 文件加载环境变量,按优先级合并:
767
+ * .env < .env.[mode] < .env.local < .env.[mode].local
768
+ */
769
+ function loadEnv(mode, root, prefixes) {
770
+ const envFiles = [
771
+ ".env",
772
+ `.env.${mode}`,
773
+ ".env.local",
774
+ `.env.${mode}.local`
775
+ ];
776
+ const raw = {};
777
+ for (const file of envFiles) {
778
+ const filePath = node_path.default.resolve(root, file);
779
+ if (!node_fs.default.existsSync(filePath)) continue;
780
+ const content = node_fs.default.readFileSync(filePath, "utf-8");
781
+ for (const line of content.split("\n")) {
782
+ const trimmed = line.trim();
783
+ if (!trimmed || trimmed.startsWith("#")) continue;
784
+ const eqIdx = trimmed.indexOf("=");
785
+ if (eqIdx === -1) continue;
786
+ const key = trimmed.slice(0, eqIdx).trim();
787
+ let value = trimmed.slice(eqIdx + 1).trim();
788
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
789
+ raw[key] = value;
790
+ }
791
+ }
792
+ const filtered = {};
793
+ for (const [key, value] of Object.entries(raw)) if (prefixes.some((prefix) => key.startsWith(prefix))) filtered[key] = value;
794
+ return filtered;
795
+ }
796
+ /**
797
+ * 将过滤后的 env 转为 Rolldown define 对象
798
+ * 例: { VITE_FOO: 'bar' } → { 'import.meta.env.VITE_FOO': '"bar"' }
799
+ * 同时注入 import.meta.env.MODE / DEV / PROD / SSR
800
+ *
801
+ * per-env define 钩子(Phase1→Phase2 接口契约):`overrides` 允许调用方按
802
+ * 环境覆盖任意 define —— server 环境传 `ssrDefineOverrides('server')` 注入
803
+ * `import.meta.env.SSR='true'`,不再写死 'false'。
804
+ */
805
+ function buildEnvDefine(env, mode, overrides = {}) {
806
+ const define = {};
807
+ for (const [key, value] of Object.entries(env)) define[`import.meta.env.${key}`] = JSON.stringify(value);
808
+ define["import.meta.env.MODE"] = JSON.stringify(mode);
809
+ define["import.meta.env.DEV"] = mode !== "production" ? "true" : "false";
810
+ define["import.meta.env.PROD"] = mode === "production" ? "true" : "false";
811
+ define["import.meta.env.SSR"] = "false";
812
+ return {
813
+ ...define,
814
+ ...overrides
815
+ };
816
+ }
817
+ /** 按环境 consumer 派生 import.meta.env.SSR 的 define 覆盖 */
818
+ function ssrDefineOverrides(consumer) {
819
+ return { "import.meta.env.SSR": consumer === "server" ? "true" : "false" };
820
+ }
821
+ /**
822
+ * 在代码字符串中替换 import.meta.env.KEY(用于 dev 服务器按需转换)
823
+ */
824
+ function replaceEnvInCode(code, define) {
825
+ let result = code;
826
+ for (const [key, value] of Object.entries(define)) {
827
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
828
+ result = result.replace(new RegExp(escaped, "g"), value);
829
+ }
830
+ return result;
831
+ }
832
+ //#endregion
833
+ Object.defineProperty(exports, "buildEnvDefine", {
834
+ enumerable: true,
835
+ get: function() {
836
+ return buildEnvDefine;
837
+ }
838
+ });
839
+ Object.defineProperty(exports, "defineConfig", {
840
+ enumerable: true,
841
+ get: function() {
842
+ return defineConfig;
843
+ }
844
+ });
845
+ Object.defineProperty(exports, "detectFramework", {
846
+ enumerable: true,
847
+ get: function() {
848
+ return detectFramework;
849
+ }
850
+ });
851
+ Object.defineProperty(exports, "getPluginApi", {
852
+ enumerable: true,
853
+ get: function() {
854
+ return getPluginApi;
855
+ }
856
+ });
857
+ Object.defineProperty(exports, "loadEnv", {
858
+ enumerable: true,
859
+ get: function() {
860
+ return loadEnv;
861
+ }
862
+ });
863
+ Object.defineProperty(exports, "matchesReactFilter", {
864
+ enumerable: true,
865
+ get: function() {
866
+ return matchesReactFilter;
867
+ }
868
+ });
869
+ Object.defineProperty(exports, "orderPlugins", {
870
+ enumerable: true,
871
+ get: function() {
872
+ return orderPlugins;
873
+ }
874
+ });
875
+ Object.defineProperty(exports, "replaceEnvInCode", {
876
+ enumerable: true,
877
+ get: function() {
878
+ return replaceEnvInCode;
879
+ }
880
+ });
881
+ Object.defineProperty(exports, "resolveConfig", {
882
+ enumerable: true,
883
+ get: function() {
884
+ return resolveConfig;
885
+ }
886
+ });
887
+ Object.defineProperty(exports, "resolveMinifyOption", {
888
+ enumerable: true,
889
+ get: function() {
890
+ return resolveMinifyOption;
891
+ }
892
+ });
893
+ Object.defineProperty(exports, "resolvePlugin", {
894
+ enumerable: true,
895
+ get: function() {
896
+ return resolvePlugin;
897
+ }
898
+ });
899
+ Object.defineProperty(exports, "shouldTransform", {
900
+ enumerable: true,
901
+ get: function() {
902
+ return shouldTransform;
903
+ }
904
+ });
905
+ Object.defineProperty(exports, "ssrDefineOverrides", {
906
+ enumerable: true,
907
+ get: function() {
908
+ return ssrDefineOverrides;
909
+ }
910
+ });
911
+ Object.defineProperty(exports, "transformCode", {
912
+ enumerable: true,
913
+ get: function() {
914
+ return transformCode;
915
+ }
916
+ });
917
+ Object.defineProperty(exports, "transformReactCode", {
918
+ enumerable: true,
919
+ get: function() {
920
+ return transformReactCode;
921
+ }
922
+ });
923
+
924
+ //# sourceMappingURL=env-lOj3yw8v.cjs.map