@lark-apaas/fullstack-cli 1.1.59-alpha.3 → 1.1.59-beta.0

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.
@@ -3,16 +3,8 @@
3
3
 
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { spawn, execFileSync, execSync } = require('child_process');
6
+ const { spawn, execSync } = require('child_process');
7
7
  const readline = require('readline');
8
- const {
9
- buildListeningPortLookupArgs,
10
- createPreviewPhaseReporter,
11
- normalizeTcpPort,
12
- parseTscWatchSummary,
13
- resolvePreviewServerCommand,
14
- waitForTcpReady,
15
- } = require('./preview-startup-timing.cjs');
16
8
 
17
9
  // ── Project root ──────────────────────────────────────────────────────────────
18
10
  const PROJECT_ROOT = path.resolve(__dirname, '..');
@@ -44,12 +36,8 @@ const MAX_RESTART_COUNT = process.env.MAX_RESTART_COUNT != null && process.env.M
44
36
  : Infinity;
45
37
  const RESTART_DELAY = parseInt(process.env.RESTART_DELAY, 10) || 2;
46
38
  const MAX_DELAY = 8;
47
- const SERVER_PORT = normalizeTcpPort(process.env.SERVER_PORT, 3000, 'SERVER_PORT');
48
- const CLIENT_DEV_PORT = normalizeTcpPort(
49
- process.env.CLIENT_DEV_PORT,
50
- 8080,
51
- 'CLIENT_DEV_PORT'
52
- );
39
+ const SERVER_PORT = process.env.SERVER_PORT || '3000';
40
+ const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8080';
53
41
 
54
42
  fs.mkdirSync(LOG_DIR, { recursive: true });
55
43
 
@@ -90,10 +78,6 @@ function writeOutput(msg) {
90
78
  try { fs.write(1, msg, () => { _stdoutInFlight--; }); } catch { _stdoutInFlight--; }
91
79
  }
92
80
 
93
- const previewPhaseReporter = createPreviewPhaseReporter({
94
- write: (line) => writeOutput(`${line}\n`),
95
- });
96
-
97
81
  /** Structured event log → terminal + dev.std.log + dev.log */
98
82
  function logEvent(level, name, message) {
99
83
  const msg = `[${timestamp()}] [${level}] [${name}] ${message}\n`;
@@ -110,38 +94,16 @@ function killProcessGroup(pid, signal) {
110
94
 
111
95
  function killOrphansByPort(port) {
112
96
  try {
113
- const output = execFileSync('lsof', buildListeningPortLookupArgs(port), {
114
- encoding: 'utf8',
115
- timeout: 5000,
116
- }).trim();
117
- const killedPids = [];
118
- const failures = [];
119
- if (output) {
120
- for (const pidText of output.split('\n').filter(Boolean)) {
121
- const pid = Number(pidText);
122
- try {
123
- process.kill(pid, 'SIGKILL');
124
- killedPids.push(pidText);
125
- } catch (error) {
126
- if (error?.code !== 'ESRCH') {
127
- failures.push(
128
- `${pidText}: ${error instanceof Error ? error.message : String(error)}`
129
- );
130
- }
131
- }
97
+ const pids = execSync(`lsof -ti :${port}`, { encoding: 'utf8', timeout: 5000 }).trim();
98
+ if (pids) {
99
+ const pidList = pids.split('\n').filter(Boolean);
100
+ for (const p of pidList) {
101
+ try { process.kill(parseInt(p, 10), 'SIGKILL'); } catch {}
132
102
  }
103
+ return pidList;
133
104
  }
134
- return {
135
- pids: killedPids,
136
- error: failures.length > 0 ? failures.join('; ') : null,
137
- };
138
- } catch (error) {
139
- if (error?.status === 1) return { pids: [], error: null };
140
- return {
141
- pids: [],
142
- error: error instanceof Error ? error.message : String(error),
143
- };
144
- }
105
+ } catch {}
106
+ return [];
145
107
  }
146
108
 
147
109
  // ── Process supervision ───────────────────────────────────────────────────────
@@ -156,7 +118,7 @@ function sleep(ms) {
156
118
  * Start and supervise a process with auto-restart and log piping.
157
119
  * Returns a promise that resolves when the process loop ends.
158
120
  */
159
- function startProcess({ name, command, args, cleanupPort, phaseDetail = {}, onReady }) {
121
+ function startProcess({ name, command, args, cleanupPort }) {
160
122
  const logFilePath = path.join(LOG_DIR, `${name}.std.log`);
161
123
  const logFd = fs.openSync(logFilePath, 'a');
162
124
 
@@ -166,30 +128,6 @@ function startProcess({ name, command, args, cleanupPort, phaseDetail = {}, onRe
166
128
  const run = async () => {
167
129
  let restartCount = 0;
168
130
 
169
- if (cleanupPort) {
170
- const portCleanup = killOrphansByPort(cleanupPort);
171
- const stalePids = portCleanup.pids;
172
- logEvent(
173
- portCleanup.error ? 'ERROR' : stalePids.length > 0 ? 'WARN' : 'INFO',
174
- name,
175
- portCleanup.error
176
- ? `Port ${cleanupPort} preflight failed: ${portCleanup.error}`
177
- : stalePids.length > 0
178
- ? `Killed stale processes on port ${cleanupPort} before first spawn: ${stalePids.join(' ')}`
179
- : `Port ${cleanupPort} is clear before first spawn`
180
- );
181
- previewPhaseReporter.emit(
182
- name === 'server' ? 'backend_port_preflight' : 'client_port_preflight',
183
- portCleanup.error ? 'error' : 'success',
184
- {
185
- exact: true,
186
- cleanup_count: stalePids.length,
187
- ...(portCleanup.error ? { error: portCleanup.error } : {}),
188
- }
189
- );
190
- if (stalePids.length > 0) await sleep(500);
191
- }
192
-
193
131
  while (!stopping) {
194
132
  const child = spawn(command, args, {
195
133
  detached: true,
@@ -203,80 +141,12 @@ function startProcess({ name, command, args, cleanupPort, phaseDetail = {}, onRe
203
141
  entry.child = child;
204
142
 
205
143
  const startTime = Date.now();
206
- const attempt = restartCount + 1;
207
144
  logEvent('INFO', name, `Process started (PGID: ${child.pid}): ${command} ${args.join(' ')}`);
208
- const processPhase =
209
- name === 'server'
210
- ? 'backend_process_spawn'
211
- : name === 'server-typecheck'
212
- ? 'backend_typecheck_process_spawn'
213
- : 'client_process_spawn';
214
- previewPhaseReporter.emit(processPhase, 'success', {
215
- at_ms: startTime,
216
- attempt,
217
- exact: true,
218
- ...phaseDetail,
219
- });
220
- if (name === 'server') {
221
- void waitForTcpReady({
222
- port: Number(SERVER_PORT),
223
- started_at_ms: startTime,
224
- interval_ms: 50,
225
- timeout_ms: 120000,
226
- should_continue: () => entry.child === child && !stopping,
227
- })
228
- .then(result => {
229
- if (result.cancelled) return;
230
- previewPhaseReporter.emit(
231
- 'backend_tcp_ready',
232
- result.ready ? 'success' : 'error',
233
- { ...result, attempt, exact: false }
234
- );
235
- if (result.ready && onReady) {
236
- try {
237
- onReady(result);
238
- } catch (error) {
239
- logEvent(
240
- 'ERROR',
241
- name,
242
- `Backend ready callback failed: ${error instanceof Error ? error.message : String(error)}`
243
- );
244
- }
245
- }
246
- })
247
- .catch(error => {
248
- if (entry.child !== child || stopping) return;
249
- previewPhaseReporter.emit('backend_tcp_ready', 'error', {
250
- duration_ms: Date.now() - startTime,
251
- attempt,
252
- exact: false,
253
- precision_ms: 50,
254
- error: error instanceof Error ? error.message : String(error),
255
- });
256
- });
257
- }
258
145
 
259
146
  // Pipe stdout and stderr through readline for timestamped logging
260
- let typecheckSummaryReported = false;
261
147
  const pipeLines = (stream) => {
262
148
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
263
149
  rl.on('line', (line) => {
264
- if (name === 'server-typecheck' && !typecheckSummaryReported) {
265
- const summary = parseTscWatchSummary(line);
266
- if (summary) {
267
- typecheckSummaryReported = true;
268
- previewPhaseReporter.emit(
269
- 'backend_typecheck_ready',
270
- summary.passed ? 'success' : 'error',
271
- {
272
- duration_ms: Date.now() - startTime,
273
- attempt,
274
- exact: true,
275
- ...summary,
276
- }
277
- );
278
- }
279
- }
280
150
  const msg = `[${timestamp()}] [${name}] ${line}\n`;
281
151
  try { fs.writeSync(logFd, msg); } catch {}
282
152
  writeOutput(msg);
@@ -305,15 +175,7 @@ function startProcess({ name, command, args, cleanupPort, phaseDetail = {}, onRe
305
175
 
306
176
  // Port cleanup fallback
307
177
  if (cleanupPort) {
308
- const portCleanup = killOrphansByPort(cleanupPort);
309
- const orphans = portCleanup.pids;
310
- if (portCleanup.error) {
311
- logEvent(
312
- 'ERROR',
313
- name,
314
- `Port ${cleanupPort} cleanup failed: ${portCleanup.error}`
315
- );
316
- }
178
+ const orphans = killOrphansByPort(cleanupPort);
317
179
  if (orphans.length > 0) {
318
180
  logEvent('WARN', name, `Killed orphan processes on port ${cleanupPort}: ${orphans.join(' ')}`);
319
181
  await sleep(500);
@@ -375,12 +237,8 @@ async function cleanup() {
375
237
  }
376
238
 
377
239
  // Port cleanup fallback
378
- for (const port of [SERVER_PORT, CLIENT_DEV_PORT]) {
379
- const portCleanup = killOrphansByPort(port);
380
- if (portCleanup.error) {
381
- logEvent('ERROR', 'main', `Port ${port} cleanup failed: ${portCleanup.error}`);
382
- }
383
- }
240
+ killOrphansByPort(SERVER_PORT);
241
+ killOrphansByPort(CLIENT_DEV_PORT);
384
242
 
385
243
  logEvent('INFO', 'main', 'All processes stopped');
386
244
 
@@ -406,102 +264,24 @@ function cleanStaleDist() {
406
264
  // ── Main ──────────────────────────────────────────────────────────────────────
407
265
  async function main() {
408
266
  logEvent('INFO', 'main', '========== Dev session started ==========');
409
- previewPhaseReporter.emit('dev_orchestrator_start', 'success', { exact: true });
410
267
 
411
268
  cleanStaleDist();
412
269
 
413
270
  // Initialize action plugins
414
271
  writeOutput('\n🔌 Initializing action plugins...\n');
415
- const actionPluginStartedAt = Date.now();
416
- previewPhaseReporter.emit('action_plugin_init', 'start', {
417
- at_ms: actionPluginStartedAt,
418
- exact: true,
419
- });
420
272
  try {
421
273
  execSync('fullstack-cli action-plugin init', { cwd: PROJECT_ROOT, stdio: 'inherit' });
422
- previewPhaseReporter.emit('action_plugin_init', 'success', {
423
- duration_ms: Date.now() - actionPluginStartedAt,
424
- exact: true,
425
- });
426
274
  writeOutput('✅ Action plugins initialized\n\n');
427
- } catch (error) {
428
- previewPhaseReporter.emit('action_plugin_init', 'error', {
429
- duration_ms: Date.now() - actionPluginStartedAt,
430
- exact: true,
431
- error: error instanceof Error ? error.message : String(error),
432
- });
275
+ } catch {
433
276
  writeOutput('⚠️ Action plugin initialization failed, continuing anyway...\n\n');
434
277
  }
435
278
 
436
279
  // Start server and client
437
- const serverCommand = resolvePreviewServerCommand({
438
- project_root: PROJECT_ROOT,
439
- });
440
- logEvent(
441
- 'INFO',
442
- 'server',
443
- `Selected ${serverCommand.compiler} compiler (${serverCommand.reason})`
444
- );
445
- previewPhaseReporter.emit('backend_compiler_selected', 'success', {
446
- exact: true,
447
- compiler: serverCommand.compiler,
448
- type_check: serverCommand.type_check,
449
- type_check_mode: serverCommand.type_check_mode,
450
- reason: serverCommand.reason,
451
- missing_modules: serverCommand.missing_modules,
452
- });
453
- let serverTypecheckStarted = false;
454
- const startServerTypecheck = () => {
455
- if (serverTypecheckStarted || !serverCommand.type_check_command) return;
456
- try {
457
- logEvent(
458
- 'INFO',
459
- 'server-typecheck',
460
- 'Starting deferred server type checker after backend TCP ready'
461
- );
462
- previewPhaseReporter.emit('backend_typecheck_deferred_start', 'success', {
463
- exact: true,
464
- type_check_mode: serverCommand.type_check_mode,
465
- });
466
- const typecheckPromise = startProcess({
467
- name: 'server-typecheck',
468
- command: serverCommand.type_check_command.command,
469
- args: serverCommand.type_check_command.args,
470
- phaseDetail: { type_check_mode: serverCommand.type_check_mode },
471
- });
472
- serverTypecheckStarted = true;
473
- void typecheckPromise.catch(error => {
474
- logEvent(
475
- 'ERROR',
476
- 'server-typecheck',
477
- `Deferred server type checker failed: ${error instanceof Error ? error.message : String(error)}`
478
- );
479
- });
480
- } catch (error) {
481
- previewPhaseReporter.emit('backend_typecheck_deferred_start', 'error', {
482
- exact: true,
483
- type_check_mode: serverCommand.type_check_mode,
484
- error: error instanceof Error ? error.message : String(error),
485
- });
486
- logEvent(
487
- 'ERROR',
488
- 'server-typecheck',
489
- `Failed to start deferred server type checker: ${error instanceof Error ? error.message : String(error)}`
490
- );
491
- }
492
- };
493
280
  const serverPromise = startProcess({
494
281
  name: 'server',
495
- command: serverCommand.command,
496
- args: serverCommand.args,
282
+ command: 'npm',
283
+ args: ['run', 'dev:server'],
497
284
  cleanupPort: SERVER_PORT,
498
- phaseDetail: {
499
- compiler: serverCommand.compiler,
500
- type_check: serverCommand.type_check,
501
- type_check_mode: serverCommand.type_check_mode,
502
- compiler_selection_reason: serverCommand.reason,
503
- },
504
- onReady: startServerTypecheck,
505
285
  });
506
286
 
507
287
  const clientPromise = startProcess({
@@ -1,2 +1,24 @@
1
1
  #!/usr/bin/env bash
2
- exec node "$(dirname "$0")/dev.js" "$@"
2
+ # `npm run dev` 入口;按 SANDBOX_ID 是否非空判断运行环境:
3
+ # - SANDBOX_ID 非空(沙箱平台注入应用所属沙箱 ID)→ 直接跑 dev.js
4
+ # (保活 / restart loop / 文件日志 —— 沙箱生产形态)。脚本同步由平台 pod 启动阶段做过,
5
+ # dev 入口不再额外 `npm run upgrade`。
6
+ # - 否则(本地)→ 走 miaoda app sync 兜底 + 跑 dev-local.js:纯 stdout、崩了就崩、Agent 友好。
7
+ # 显式想跑本地路径可用 `npm run dev:local`(绕过 SANDBOX_ID 判断)。
8
+ set -euo pipefail
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+
11
+ if [ -n "${SANDBOX_ID:-}" ]; then
12
+ exec node "$SCRIPT_DIR/dev.js" "$@"
13
+ fi
14
+
15
+ if [ ! -f "$SCRIPT_DIR/dev-local.js" ]; then
16
+ echo "[dev] scripts/dev-local.js 缺失;先跑 \`npx -y @lark-apaas/miaoda-cli@latest app sync\` 同步平台脚本" >&2
17
+ exit 1
18
+ fi
19
+
20
+ # 本地启动前先跑一次 miaoda app sync:同步 platform-controlled 内容 + 升 @lark-apaas/* 到
21
+ # latest + 迁移老 npm scripts。沙箱不走这里(SANDBOX_ID 分支已经 exec return)。
22
+ npx -y @lark-apaas/miaoda-cli@latest app sync || echo "[dev] miaoda app sync 失败,按现状继续" >&2
23
+
24
+ exec node "$SCRIPT_DIR/dev-local.js" "$@"
@@ -23,6 +23,19 @@ function runCommand(command, args) {
23
23
  });
24
24
  }
25
25
 
26
+ // 串行依次执行每个任务,全部跑完后再聚合退出码:
27
+ // 降低并发资源占用、让各任务输出按顺序清晰可读,同时保留“一次暴露所有 lint 问题”的行为。
28
+ async function runTasksSerially(taskSpecs) {
29
+ let exitCode = 0;
30
+ for (const [command, args] of taskSpecs) {
31
+ const code = await runCommand(command, args);
32
+ if (code !== 0) {
33
+ exitCode = 1;
34
+ }
35
+ }
36
+ return exitCode;
37
+ }
38
+
26
39
  function normalizeProjectFile(filePath) {
27
40
  const absolutePath = path.isAbsolute(filePath)
28
41
  ? filePath
@@ -63,14 +76,35 @@ function isStylelintTarget(filePath) {
63
76
  return filePath.endsWith('.css');
64
77
  }
65
78
 
79
+ const STYLELINT_CONFIG_FILES = [
80
+ '.stylelintrc',
81
+ '.stylelintrc.js',
82
+ '.stylelintrc.cjs',
83
+ '.stylelintrc.json',
84
+ 'stylelint.config.js',
85
+ ];
86
+
87
+ /**
88
+ * 老模板(fullstack-nestjs-template 1.x 时代)的应用没有 stylelint 配置,跑 stylelint 会以
89
+ * "ConfigurationError: No configuration provided" 挂掉、把 pre-commit 卡死。没配置就跳过。
90
+ */
91
+ function hasStylelintConfig() {
92
+ return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
93
+ }
94
+
66
95
  async function runDefaultLint() {
67
- const code = await runCommand(getBinName('npx'), [
68
- 'concurrently',
69
- 'npm run eslint',
70
- 'npm run type:check',
71
- 'npm run stylelint',
72
- ]);
73
- process.exit(code);
96
+ const taskSpecs = [
97
+ [getBinName('npm'), ['run', 'eslint']],
98
+ [getBinName('npm'), ['run', 'type:check']],
99
+ ];
100
+
101
+ if (hasStylelintConfig()) {
102
+ taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
103
+ } else {
104
+ console.warn('[lint] Skip stylelint: no stylelint config found');
105
+ }
106
+
107
+ process.exit(await runTasksSerially(taskSpecs));
74
108
  }
75
109
 
76
110
  async function runSelectiveLint(inputFiles) {
@@ -101,31 +135,32 @@ async function runSelectiveLint(inputFiles) {
101
135
  }
102
136
  }
103
137
 
104
- const tasks = [];
138
+ const taskSpecs = [];
105
139
 
106
140
  if (eslintFiles.length > 0) {
107
- tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
141
+ taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
108
142
  }
109
143
 
110
- if (stylelintFiles.length > 0) {
111
- tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
144
+ if (stylelintFiles.length > 0 && !hasStylelintConfig()) {
145
+ console.warn('[lint] Skip stylelint: no stylelint config found');
146
+ } else if (stylelintFiles.length > 0) {
147
+ taskSpecs.push([getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]]);
112
148
  }
113
149
 
114
150
  if (clientTypeFiles.length > 0) {
115
- tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
151
+ taskSpecs.push([getBinName('npm'), ['run', 'type:check:client']]);
116
152
  }
117
153
 
118
154
  if (serverTypeFiles.length > 0) {
119
- tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
155
+ taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
120
156
  }
121
157
 
122
- if (tasks.length === 0) {
158
+ if (taskSpecs.length === 0) {
123
159
  console.log('[lint] No supported files matched for lint');
124
160
  process.exit(0);
125
161
  }
126
162
 
127
- const results = await Promise.all(tasks);
128
- process.exit(results.some(code => code !== 0) ? 1 : 0);
163
+ process.exit(await runTasksSerially(taskSpecs));
129
164
  }
130
165
 
131
166
  async function main() {
@@ -6,6 +6,13 @@ const fs = require('fs');
6
6
  const path = require('path');
7
7
 
8
8
  const ROOT_DIR = path.resolve(__dirname, '..');
9
+
10
+ // 加载提交的 .env,供读取构建期配置(如 MIAODA_RUNTIME_ENTRIES)。dotenv 不一定安装 → 静默降级。
11
+ try {
12
+ require('dotenv').config({ path: path.join(ROOT_DIR, '.env') });
13
+ } catch {
14
+ // dotenv 未安装:跳过;MIAODA_RUNTIME_ENTRIES 仍可来自真实环境变量
15
+ }
9
16
  const DIST_DIR = path.join(ROOT_DIR, 'dist');
10
17
  const DIST_SERVER_DIR = path.join(DIST_DIR, 'server');
11
18
  const ROOT_PACKAGE_JSON = path.join(ROOT_DIR, 'package.json');
@@ -16,6 +23,36 @@ const OUT_PACKAGE_JSON = path.join(DIST_DIR, 'package.json');
16
23
  // Server 入口文件
17
24
  const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
18
25
 
26
+ // nest sourceRoot(源码根,默认 server):把 .env 里声明的源码路径映射到构建产物位置
27
+ let SOURCE_ROOT = 'server';
28
+ try {
29
+ const nestCli = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'nest-cli.json'), 'utf8'));
30
+ if (nestCli.sourceRoot) SOURCE_ROOT = nestCli.sourceRoot;
31
+ } catch {
32
+ // 读不到 nest-cli.json 则用默认 server
33
+ }
34
+ const SRC_TO_OUT_EXT = { '.mts': '.mjs', '.cts': '.cjs', '.ts': '.js' };
35
+
36
+ // 额外 trace 入口:经 new Function/动态 import 加载、nft 无法从 main.js 静态发现的 ESM runtime。
37
+ // 应用在提交的 .env 里用 MIAODA_RUNTIME_ENTRIES 声明【源码路径】(相对项目根,逗号分隔),例如:
38
+ // MIAODA_RUNTIME_ENTRIES=server/modules/ethics/runtime/review-run.mjs,server/modules/ethics/runtime/parse-core.mjs
39
+ // 脚本按 nest sourceRoot 自动映射到 dist/server 下的构建产物(并重写 .mts→.mjs / .ts→.js)。
40
+ const RUNTIME_ENTRIES = (process.env.MIAODA_RUNTIME_ENTRIES || '')
41
+ .split(',')
42
+ .map((s) => s.trim())
43
+ .filter(Boolean)
44
+ .map((srcRel) => {
45
+ const fromSrc = path.relative(SOURCE_ROOT, srcRel); // 去掉 sourceRoot 前缀
46
+ const ext = path.extname(fromSrc);
47
+ const outRel = SRC_TO_OUT_EXT[ext] ? fromSrc.slice(0, -ext.length) + SRC_TO_OUT_EXT[ext] : fromSrc;
48
+ return path.join(DIST_SERVER_DIR, outRel);
49
+ })
50
+ .filter((p) => {
51
+ if (fs.existsSync(p)) return true;
52
+ console.warn(`⚠️ MIAODA_RUNTIME_ENTRIES 入口在构建产物中不存在,已忽略: ${path.relative(ROOT_DIR, p)}`);
53
+ return false;
54
+ });
55
+
19
56
  // Node.js 内置模块列表
20
57
  const BUILTIN_MODULES = new Set([
21
58
  'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
@@ -145,12 +182,15 @@ async function smartPrune() {
145
182
  }
146
183
 
147
184
  console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
185
+ if (RUNTIME_ENTRIES.length) {
186
+ console.log(`📎 额外 trace 入口 (${RUNTIME_ENTRIES.length}): ${RUNTIME_ENTRIES.map((p) => path.relative(DIST_SERVER_DIR, p)).join(', ')}`);
187
+ }
148
188
 
149
189
  // 2. 使用 @vercel/nft 追踪依赖
150
190
  console.log('🔎 追踪实际依赖...');
151
191
  const analyzeStart = Date.now();
152
192
 
153
- const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
193
+ const { fileList } = await nodeFileTrace([SERVER_ENTRY, ...RUNTIME_ENTRIES], {
154
194
  base: ROOT_DIR,
155
195
  processCwd: ROOT_DIR,
156
196
  ts: false, // 禁用 TS 解析