@lark-apaas/miaoda-cli 0.1.39-alpha.4ae5653 → 0.1.39-alpha.508c930

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 CHANGED
@@ -54,11 +54,11 @@ miaoda file ls --output json
54
54
 
55
55
  ## 全栈应用的 MCP UI 检查
56
56
 
57
- MCP UI 使用平台预设提供的浏览器检查默认值。`npm run lint` 和 `npm run type:check` 自动覆盖 `server/mcp/ui`,模板统一提供 UI 子命令与类型配置;`lint -- --files` 保留按文件选择检查范围。没有 UI 源码时跳过 UI 检查。
57
+ 创建 MCP UI 时,Agent 在 `server/mcp/ui` 中同时生成 `tsconfig.json` 和 `eslint.config.cjs`,修改 UI 或其 shared 依赖后主动运行独立检查。模板和 CLI 不生成 UI 配置,也不将 UI 检查接入主工程 lint/type:check。
58
58
 
59
- 应用增加 `type:check:mcp-ui` 子命令,与 `type:check:client`、`type:check:server` 一起由 `type:check` 聚合。`scripts/typecheck-mcp-ui.js` 只在 UI 源码存在时调用应用安装的 `tsc --noEmit --project tsconfig.mcp-ui.json`;无 UI 或目录为空时成功跳过。根目录 `tsconfig.mcp-ui.json` 提供浏览器、React JSX 和严格类型检查配置,可按应用需要修改;sync 仅在该文件不存在时创建。已有 `server/mcp/ui/tsconfig.json` 的应用优先沿用原配置,保留自定义 paths/types。
59
+ 主工程保留原 client/server 检查。Agent 从工程根执行 `npx --no-install tsc --noEmit --project server/mcp/ui/tsconfig.json`,再执行 `npx --no-install eslint --config server/mcp/ui/eslint.config.cjs "server/mcp/ui/**/*.{ts,tsx,js,jsx}" --max-warnings 0`;必须检查退出码并修复错误。无需辅助脚本或新增 npm 命令。
60
60
 
61
- UI ESLint 默认配置由平台预设提供,也可用根目录 `eslint.mcp-ui.config.js` / `.cjs` / `.mjs` 定制。`npm run lint` 包含 UI 检查;`--files` 按影响范围检查,shared 变更触发三个环境的类型检查。应用脚本不使用 `node -e` 或 TypeScript 编译器 API。
61
+ CLI 不注入 MCP UI 检查命令;主工程保留原有 client/server 检查,UI 由 Agent 按独立配置执行标准 tsc/eslint。
62
62
 
63
63
  存量 sync 不改写 ESLint 源码;UI 全局排除项由平台 ESLint 预设提供。TS 仅向已有显式 `exclude` 数组追加目录,保留注释和原项;依赖继承且未显式声明 `exclude` 的自定义配置保持不变并提示人工调整。自定义检查命令保留。lint.js 和 build.sh 仍由平台脚本模板维护,不依赖预设包中的脚本执行入口。
64
64
 
@@ -70,7 +70,6 @@ exports.MIGRATE_CONFIG = {
70
70
  { type: 'file', from: 'tsconfig.json', to: 'tsconfig.json', overwrite: true },
71
71
  { type: 'file', from: 'tsconfig.app.json', to: 'tsconfig.app.json', overwrite: true },
72
72
  { type: 'file', from: 'tsconfig.node.json', to: 'tsconfig.node.json', overwrite: true },
73
- { type: 'file', from: 'tsconfig.mcp-ui.json', to: 'tsconfig.mcp-ui.json', overwrite: false },
74
73
  // ESLint 9 flat config —— vite-react 用 eslint.config.mjs 已删,fullstack 用
75
74
  // eslint.config.js (commonjs) + extends @lark-apaas/fullstack-presets;user 通常不改
76
75
  { type: 'file', from: 'eslint.config.js', to: 'eslint.config.js', overwrite: true },
@@ -159,16 +158,10 @@ exports.MIGRATE_CONFIG = {
159
158
  command: 'tsc --noEmit --project tsconfig.app.json',
160
159
  overwrite: true,
161
160
  },
162
- {
163
- type: 'add-script',
164
- name: 'type:check:mcp-ui',
165
- command: 'node ./scripts/typecheck-mcp-ui.js',
166
- overwrite: true,
167
- },
168
161
  {
169
162
  type: 'add-script',
170
163
  name: 'type:check',
171
- command: 'concurrently -n "server,client,mcp-ui" -c "blue,green,magenta" "npm run type:check:server" "npm run type:check:client" "npm run type:check:mcp-ui"',
164
+ command: 'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
172
165
  overwrite: true,
173
166
  },
174
167
  // ----- 平台脚本调用链 -----
@@ -159,46 +159,12 @@ exports.SYNC_CONFIG = {
159
159
  to: `npx -y ${fullstack_cli_pin_1.FULLSTACK_CLI_PIN_SPEC} sync --disable-gen-openapi`,
160
160
  ifStartsWith: 'npx -y @lark-apaas/fullstack-cli sync',
161
161
  },
162
- // MCP UI 使用独立浏览器配置;仅迁移已知模板命令,不生成 UI 目录;根配置仅在缺失时补齐。
162
+ // MCP UI 使用独立浏览器配置;只补服务端排除项,不生成 UI 目录和配置。
163
163
  {
164
164
  type: 'typescript-exclude',
165
165
  to: 'tsconfig.node.json',
166
166
  path: 'server/mcp/ui',
167
167
  },
168
- { type: 'file', from: 'tsconfig.mcp-ui.json', to: 'tsconfig.mcp-ui.json', overwrite: false },
169
- {
170
- type: 'add-script',
171
- name: 'type:check:mcp-ui',
172
- command: 'node ./scripts/typecheck-mcp-ui.js',
173
- overwrite: false,
174
- },
175
- {
176
- type: 'patch-script',
177
- name: 'type:check:mcp-ui',
178
- to: 'node ./scripts/typecheck-mcp-ui.js',
179
- ifEquals: [
180
- 'node ./scripts/check.cjs --typecheck-mcp-ui',
181
- 'node ./scripts/lint.js --typecheck-mcp-ui',
182
- ],
183
- },
184
- {
185
- type: 'patch-script',
186
- name: 'type:check',
187
- to: 'concurrently -n "server,client,mcp-ui" -c "blue,green,magenta" "npm run type:check:server" "npm run type:check:client" "npm run type:check:mcp-ui"',
188
- ifEquals: [
189
- 'node ./scripts/check.cjs --typecheck',
190
- 'node ./scripts/lint.js --typecheck',
191
- 'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
192
- 'concurrently "npm run type:check:server" "npm run type:check:client"',
193
- 'concurrently "npm run type:check:client" "npm run type:check:server"',
194
- ],
195
- },
196
- {
197
- type: 'patch-script',
198
- name: 'eslint',
199
- to: 'eslint . --quiet',
200
- ifEquals: ['eslint . --quiet --ignore-pattern "server/mcp/ui/**"'],
201
- },
202
168
  // ===== miaoda-cli 本地开发新增规则(fullstack-cli sync 不会执行) =====
203
169
  // M1. scripts.dev:local —— 本地用户绕过沙箱判定直接跑本地链路(npm run dev:local)。
204
170
  // dev.sh 在 MIAODA_DEP_CACHE_DIR 非空时跑 dev.js(沙箱保活)、否则 exec dev-local.js;显式 dev:local
@@ -221,10 +221,7 @@ function applyPatchScript(rule, targetDir, logPrefix) {
221
221
  (0, logger_1.log)(logPrefix, ` ○ scripts.${rule.name} (already patched)`);
222
222
  return { rule, action: 'noop', path: 'package.json' };
223
223
  }
224
- const matches = rule.ifEquals
225
- ? rule.ifEquals.includes(current)
226
- : rule.ifStartsWith !== undefined && current.startsWith(rule.ifStartsWith);
227
- if (!matches) {
224
+ if (!current.startsWith(rule.ifStartsWith)) {
228
225
  (0, logger_1.log)(logPrefix, ` ⚠ scripts.${rule.name} customized, skip patch`);
229
226
  return { rule, action: 'skipped', path: 'package.json' };
230
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.39-alpha.4ae5653",
3
+ "version": "0.1.39-alpha.508c930",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -51,9 +51,9 @@
51
51
  "prettier": "^3.8.3",
52
52
  "tsc-alias": "^1.8.11",
53
53
  "tsx": "^4.19.4",
54
+ "typescript": "^5.8.3",
54
55
  "vitest": "^4.1.4",
55
- "xml2js": "^0.6.2",
56
- "typescript": "^5.8.3"
56
+ "xml2js": "^0.6.2"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "bash scripts/build.sh",
@@ -210,28 +210,3 @@ else
210
210
  echo " node_modules: $NODE_MODULES_SIZE"
211
211
  echo ""
212
212
  fi
213
-
214
- # 仅成功的全构建生成 backendBuildID;必须在构建、产物准备和依赖裁剪之后。
215
- # UI/Skill 内容由 SDK 独立计入总 hash;纯前端发布不替换已部署的 backendBuildID。
216
- if [[ "${only_frontend_change:-false}" != "true" ]]; then
217
- node - "$ROOT_DIR/dist" <<'NODE'
218
- const fs = require('node:fs');
219
- const path = require('node:path');
220
- const { randomUUID } = require('node:crypto');
221
- const dist = process.argv[2];
222
- // 防止 build:server 空跑成功、却没有可交付的服务端入口。
223
- if (!fs.statSync(path.join(dist, 'server/main.js')).isFile()) {
224
- throw new Error('Missing server build output: dist/server/main.js');
225
- }
226
- // runtime cwd 分别为工程根、dist/(或整包解压到 dist-server/)、旧版 dist/server/。
227
- // 与 MCP UI/Skill 的交付布局一致:复制产物时保留同一个 UUID,不在启动时生成。
228
- const files = [
229
- path.join(dist, 'mcp-build-id'),
230
- path.join(dist, 'dist/mcp-build-id'),
231
- path.join(dist, 'server/dist/mcp-build-id'),
232
- ];
233
- for (const file of files) fs.mkdirSync(path.dirname(file), { recursive: true });
234
- const buildID = randomUUID();
235
- for (const file of files) fs.writeFileSync(file, buildID, 'utf8');
236
- NODE
237
- fi
@@ -1,26 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const path = require('node:path');
4
- const fs = require('node:fs');
5
4
  const { spawn } = require('node:child_process');
5
+ const fs = require('node:fs');
6
6
 
7
7
  const cwd = process.cwd();
8
8
 
9
+ function getBinName(name) {
10
+ return process.platform === 'win32' ? `${name}.cmd` : name;
11
+ }
12
+
13
+ function runCommand(command, args) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(command, args, {
16
+ cwd,
17
+ stdio: 'inherit',
18
+ shell: false,
19
+ });
20
+
21
+ child.on('close', (code) => resolve(code || 0));
22
+ child.on('error', () => resolve(1));
23
+ });
24
+ }
25
+
9
26
  function normalizeProjectFile(filePath) {
10
27
  const absolutePath = path.isAbsolute(filePath)
11
28
  ? filePath
12
29
  : path.resolve(cwd, filePath);
13
30
 
14
31
  if (!fs.existsSync(absolutePath)) {
15
- console.log(`[lint] Deleted file: ${filePath}; checking affected projects`);
32
+ console.warn(`[lint] Skip missing file: ${filePath}`);
33
+ return null;
16
34
  }
17
35
 
18
36
  const relativePath = path.relative(cwd, absolutePath);
19
- if (
20
- relativePath === '..' ||
21
- relativePath.startsWith(`..${path.sep}`) ||
22
- path.isAbsolute(relativePath)
23
- ) {
37
+ if (relativePath.startsWith('..')) {
24
38
  console.warn(`[lint] Skip file outside project: ${filePath}`);
25
39
  return null;
26
40
  }
@@ -77,144 +91,25 @@ function canRunStylelint() {
77
91
 
78
92
  if (!pkg.scripts || !pkg.scripts.stylelint) return false;
79
93
 
80
- return STYLELINT_CONFIG_FILES.some(file =>
81
- fs.existsSync(path.join(cwd, file))
82
- );
83
- }
84
-
85
- function getBinName(name) {
86
- return process.platform === 'win32' ? `${name}.cmd` : name;
87
- }
88
-
89
- function runCommand(command, args) {
90
- return new Promise(resolve => {
91
- const child = spawn(command, args, {
92
- cwd,
93
- stdio: 'inherit',
94
- shell: false,
95
- });
96
-
97
- child.on('close', code => resolve(code ?? 1));
98
- child.on('error', () => resolve(1));
99
- });
100
- }
101
-
102
- // 串行依次执行每个任务,全部跑完后再聚合退出码:
103
- // 降低并发资源占用、让各任务输出按顺序清晰可读,同时保留“一次暴露所有 lint 问题”的行为。
104
- async function runTasksSerially(taskSpecs) {
105
- let exitCode = 0;
106
- for (const [command, args] of taskSpecs) {
107
- const code = await runCommand(command, args);
108
- if (code !== 0) {
109
- exitCode = 1;
110
- }
111
- }
112
- return exitCode;
113
- }
114
-
115
- const UI_DIR = 'server/mcp/ui';
116
-
117
- function uiSourceFiles() {
118
- const files = [];
119
- function visit(relative) {
120
- const absolute = path.join(cwd, relative);
121
- if (!fs.existsSync(absolute)) return;
122
- if (fs.lstatSync(absolute).isSymbolicLink()) {
123
- throw new Error(`MCP UI 检查不支持符号链接: ${relative}`);
124
- }
125
- for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
126
- if (['node_modules', 'dist', '.git'].includes(entry.name)) continue;
127
- const file = `${relative}/${entry.name}`;
128
- if (entry.isSymbolicLink())
129
- throw new Error(`MCP UI 检查不支持符号链接: ${file}`);
130
- if (entry.isDirectory()) visit(file);
131
- else if (isEslintTarget(file) && !/\.d\.(?:ts|mts|cts)$/.test(file))
132
- files.push(file);
133
- }
134
- }
135
- visit(UI_DIR);
136
- return files.sort();
137
- }
138
-
139
- function uiTasks({ types = true, eslint = true } = {}) {
140
- const sources = uiSourceFiles();
141
- if (!sources.length) return [];
142
- const tasks = [];
143
- if (types) tasks.push([getBinName('npm'), ['run', 'type:check:mcp-ui']]);
144
- if (eslint) {
145
- const config = [
146
- 'eslint.mcp-ui.config.js',
147
- 'eslint.mcp-ui.config.cjs',
148
- 'eslint.mcp-ui.config.mjs',
149
- ].find(file => fs.existsSync(path.join(cwd, file)));
150
- let resolvedConfig = config;
151
- if (!resolvedConfig) {
152
- try {
153
- resolvedConfig =
154
- require.resolve('@lark-apaas/fullstack-presets/lib/mcp/eslint-ui.js');
155
- } catch {
156
- throw new Error(
157
- '请升级 @lark-apaas/fullstack-presets 以使用 MCP UI 默认 ESLint 配置,或提供 eslint.mcp-ui.config.cjs'
158
- );
159
- }
160
- }
161
- tasks.push([
162
- getBinName('npx'),
163
- [
164
- '--no-install',
165
- 'eslint',
166
- '--config',
167
- resolvedConfig,
168
- '--quiet',
169
- '--max-warnings',
170
- '0',
171
- ...sources,
172
- ],
173
- ]);
174
- }
175
- return tasks;
176
- }
177
-
178
- function packageScripts() {
179
- return (
180
- JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'))
181
- .scripts || {}
182
- );
183
- }
184
-
185
- function isUiFile(file) {
186
- return file.startsWith('server/mcp/ui/');
94
+ return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
187
95
  }
188
96
 
189
97
  async function runDefaultLint() {
190
- const taskSpecs = [
191
- [getBinName('npm'), ['run', 'eslint']],
192
- [getBinName('npm'), ['run', 'type:check']],
193
- ];
194
-
195
- // 保留用户的 type:check;未聚合 UI 时由 lint 补查。
196
- taskSpecs.push(
197
- ...uiTasks({
198
- types: !/\bnpm\s+run\s+type:check:mcp-ui(?=[\s"';&|)]|$)/.test(
199
- packageScripts()['type:check'] || ''
200
- ),
201
- })
202
- );
98
+ const commands = ['npm run eslint', 'npm run type:check'];
203
99
 
204
100
  if (canRunStylelint()) {
205
- taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
101
+ commands.push('npm run stylelint');
206
102
  } else {
207
- console.warn(
208
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
209
- );
103
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
210
104
  }
211
105
 
212
- process.exit(await runTasksSerially(taskSpecs));
106
+ const code = await runCommand(getBinName('npx'), ['concurrently', ...commands]);
107
+ process.exit(code);
213
108
  }
214
109
 
215
110
  async function runSelectiveLint(inputFiles) {
216
111
  const normalizedFiles = Array.from(
217
- new Set(inputFiles.map(normalizeProjectFile).filter(Boolean))
112
+ new Set(inputFiles.map(normalizeProjectFile).filter(Boolean)),
218
113
  );
219
114
 
220
115
  if (normalizedFiles.length === 0) {
@@ -222,41 +117,15 @@ async function runSelectiveLint(inputFiles) {
222
117
  process.exit(0);
223
118
  }
224
119
 
225
- const eslintFiles = normalizedFiles.filter(
226
- file =>
227
- fs.existsSync(path.join(cwd, file)) &&
228
- isEslintTarget(file) &&
229
- !isUiFile(file) &&
230
- !file.startsWith('eslint.mcp-ui.config.')
231
- );
232
- const stylelintFiles = normalizedFiles.filter(
233
- file => fs.existsSync(path.join(cwd, file)) && isStylelintTarget(file)
234
- );
120
+ const eslintFiles = normalizedFiles.filter(isEslintTarget);
121
+ const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
235
122
  const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
236
123
 
237
124
  const clientTypeFiles = [];
238
125
  const serverTypeFiles = [];
239
- const rootConfigChanged = normalizedFiles.some(file =>
240
- /^tsconfig(?:\.[^/]*)?\.json$/.test(file)
241
- );
242
- if (rootConfigChanged) {
243
- clientTypeFiles.push('config');
244
- serverTypeFiles.push('config');
245
- }
246
- let checkUi =
247
- rootConfigChanged ||
248
- normalizedFiles.some(
249
- file =>
250
- isUiFile(file) ||
251
- file.startsWith('shared/') ||
252
- file.startsWith('eslint.mcp-ui.config.') ||
253
- file === 'package.json'
254
- );
255
126
 
256
127
  for (const filePath of typeCheckFiles) {
257
- if (isUiFile(filePath)) {
258
- checkUi = true;
259
- } else if (filePath.startsWith('client/')) {
128
+ if (filePath.startsWith('client/')) {
260
129
  clientTypeFiles.push(filePath);
261
130
  } else if (filePath.startsWith('server/')) {
262
131
  serverTypeFiles.push(filePath);
@@ -266,52 +135,36 @@ async function runSelectiveLint(inputFiles) {
266
135
  }
267
136
  }
268
137
 
269
- const taskSpecs = [];
138
+ const tasks = [];
270
139
 
271
140
  if (eslintFiles.length > 0) {
272
- taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
141
+ tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
273
142
  }
274
143
 
275
144
  if (stylelintFiles.length > 0 && !canRunStylelint()) {
276
- console.warn(
277
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
278
- );
145
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
279
146
  } else if (stylelintFiles.length > 0) {
280
- taskSpecs.push([
281
- getBinName('npx'),
282
- ['stylelint', '--quiet', ...stylelintFiles],
283
- ]);
147
+ tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
284
148
  }
285
149
 
286
150
  if (clientTypeFiles.length > 0) {
287
- taskSpecs.push([getBinName('npm'), ['run', 'type:check:client']]);
151
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
288
152
  }
289
153
 
290
154
  if (serverTypeFiles.length > 0) {
291
- taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
155
+ tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
292
156
  }
293
157
 
294
- if (checkUi) taskSpecs.push(...uiTasks());
295
-
296
- if (taskSpecs.length === 0) {
158
+ if (tasks.length === 0) {
297
159
  console.log('[lint] No supported files matched for lint');
298
160
  process.exit(0);
299
161
  }
300
162
 
301
- process.exit(await runTasksSerially(taskSpecs));
163
+ const results = await Promise.all(tasks);
164
+ process.exit(results.some(code => code !== 0) ? 1 : 0);
302
165
  }
303
166
 
304
167
  async function main() {
305
- const args = process.argv.slice(2);
306
- if (args.length === 1 && args[0] === '--eslint-mcp-ui') {
307
- process.exit(await runTasksSerially(uiTasks({ types: false })));
308
- return;
309
- }
310
- if (args.length && !(args[0] === '--files' && args.length > 1)) {
311
- throw new Error('用法: lint.js [--files <paths...>]');
312
- }
313
- if (args[0] === '--files' && args.slice(1).some(arg => arg.startsWith('--')))
314
- throw new Error('--files 不可与其他检查模式混用');
315
168
  const files = parseFilesArg(process.argv.slice(2));
316
169
  if (files === null) {
317
170
  await runDefaultLint();
@@ -326,7 +179,7 @@ async function main() {
326
179
  await runSelectiveLint(files);
327
180
  }
328
181
 
329
- main().catch(error => {
182
+ main().catch((error) => {
330
183
  const message = error instanceof Error ? error.message : String(error);
331
184
  console.error(`[lint] Failed to run lint: ${message}`);
332
185
  process.exit(1);
@@ -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 解析
@@ -1,32 +0,0 @@
1
- // MCP UI 是可选浏览器工程;没有源码时跳过,否则直接交给 tsc。
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const { spawnSync } = require('node:child_process');
5
-
6
- function hasSource(dir) {
7
- if (!fs.existsSync(dir)) return false;
8
- return fs.readdirSync(dir, { withFileTypes: true }).some(entry => {
9
- if (['node_modules', 'dist'].includes(entry.name)) return false;
10
- if (entry.isDirectory()) return hasSource(path.join(dir, entry.name));
11
- return entry.isFile() && /\.(?:[cm]?[jt]sx?)$/.test(entry.name);
12
- });
13
- }
14
-
15
- if (hasSource('server/mcp/ui')) {
16
- // 已有应用的独立 UI 配置继续生效,避免升级覆盖自定义 paths/types。
17
- const config = fs.existsSync('server/mcp/ui/tsconfig.json')
18
- ? 'server/mcp/ui/tsconfig.json'
19
- : 'tsconfig.mcp-ui.json';
20
- const result = spawnSync(
21
- process.execPath,
22
- [
23
- require.resolve('typescript/bin/tsc'),
24
- '--noEmit',
25
- '--project',
26
- config,
27
- ],
28
- { stdio: 'inherit' }
29
- );
30
- if (result.error) console.error(result.error.message);
31
- process.exitCode = result.status ?? 1;
32
- }
@@ -1,33 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "jsx": "react-jsx",
7
- "types": [],
8
- "strict": true,
9
- "checkJs": true,
10
- "lib": [
11
- "ES2020",
12
- "DOM",
13
- "DOM.Iterable"
14
- ],
15
- "allowJs": true,
16
- "skipLibCheck": true,
17
- "esModuleInterop": true,
18
- "noEmit": true,
19
- "baseUrl": ".",
20
- "paths": {
21
- "@shared/*": [
22
- "shared/*"
23
- ]
24
- }
25
- },
26
- "include": [
27
- "server/mcp/ui/**/*"
28
- ],
29
- "exclude": [
30
- "node_modules",
31
- "dist"
32
- ]
33
- }