@lark-apaas/fullstack-cli 1.1.59-alpha.3 → 1.1.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +162 -37
- package/package.json +1 -1
- package/templates/.spark_project +2 -2
- package/templates/nest-cli.json +1 -5
- package/templates/scripts/dev-local.js +113 -0
- package/templates/scripts/dev.js +18 -238
- package/templates/scripts/dev.sh +23 -1
- package/templates/scripts/lint.js +51 -16
- package/templates/scripts/prune-smart.js +41 -1
- package/templates/scripts/preview-startup-timing.cjs +0 -377
package/templates/scripts/dev.js
CHANGED
|
@@ -3,16 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { spawn,
|
|
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 =
|
|
48
|
-
const CLIENT_DEV_PORT =
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
379
|
-
|
|
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
|
|
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:
|
|
496
|
-
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({
|
package/templates/scripts/dev.sh
CHANGED
|
@@ -1,2 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
|
|
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
|
|
68
|
-
'
|
|
69
|
-
'npm run
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
138
|
+
const taskSpecs = [];
|
|
105
139
|
|
|
106
140
|
if (eslintFiles.length > 0) {
|
|
107
|
-
|
|
141
|
+
taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
|
|
108
142
|
}
|
|
109
143
|
|
|
110
|
-
if (stylelintFiles.length > 0) {
|
|
111
|
-
|
|
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
|
-
|
|
151
|
+
taskSpecs.push([getBinName('npm'), ['run', 'type:check:client']]);
|
|
116
152
|
}
|
|
117
153
|
|
|
118
154
|
if (serverTypeFiles.length > 0) {
|
|
119
|
-
|
|
155
|
+
taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
|
|
120
156
|
}
|
|
121
157
|
|
|
122
|
-
if (
|
|
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
|
-
|
|
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 解析
|