@lark-apaas/miaoda-cli 0.1.39-alpha.44e04b8 → 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 +7 -14
- package/dist/config/sync-configs/nestjs-react-fullstack.js +1 -45
- package/dist/utils/sync-rule.js +1 -18
- package/dist/utils/typescript-exclude.js +16 -42
- package/package.json +3 -3
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +0 -25
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/lint.js +35 -39
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/prune-smart.js +41 -1
- package/dist/utils/eslint-ignore.js +0 -169
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/check.cjs +0 -123
package/README.md
CHANGED
|
@@ -54,20 +54,13 @@ miaoda file ls --output json
|
|
|
54
54
|
|
|
55
55
|
## 全栈应用的 MCP UI 检查
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- `npm run type:check`:检查服务端、客户端及 MCP UI。
|
|
65
|
-
- `npm run type:check:mcp-ui` / `npm run eslint:mcp-ui`:单独检查 MCP UI。
|
|
66
|
-
- `npm run lint -- --files <files...>`:按改动文件选择检查范围。
|
|
67
|
-
|
|
68
|
-
同步不会生成 MCP UI 目录。开发 MCP UI 时,由 Agent 按 MCP 指南创建
|
|
69
|
-
`server/mcp/ui/tsconfig.json` 和根目录 `eslint.mcp-ui.config.cjs`。
|
|
70
|
-
没有 UI 源码时跳过独立检查;存在 UI 源码但缺少配置时明确报错。
|
|
57
|
+
创建 MCP UI 时,Agent 在 `server/mcp/ui` 中同时生成 `tsconfig.json` 和 `eslint.config.cjs`,修改 UI 或其 shared 依赖后主动运行独立检查。模板和 CLI 不生成 UI 配置,也不将 UI 检查接入主工程 lint/type:check。
|
|
58
|
+
|
|
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
|
+
|
|
61
|
+
CLI 不注入 MCP UI 检查命令;主工程保留原有 client/server 检查,UI 由 Agent 按独立配置执行标准 tsc/eslint。
|
|
62
|
+
|
|
63
|
+
存量 sync 不改写 ESLint 源码;UI 全局排除项由平台 ESLint 预设提供。TS 仅向已有显式 `exclude` 数组追加目录,保留注释和原项;依赖继承且未显式声明 `exclude` 的自定义配置保持不变并提示人工调整。自定义检查命令保留。lint.js 和 build.sh 仍由平台脚本模板维护,不依赖预设包中的脚本执行入口。
|
|
71
64
|
|
|
72
65
|
## 打包可本地访问的静态产物
|
|
73
66
|
|
|
@@ -159,56 +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
|
-
|
|
163
|
-
{ type: 'eslint-ignore', to: 'eslint.config.mjs', pattern: 'server/mcp/ui/**' },
|
|
164
|
-
{ type: 'eslint-ignore', to: 'eslint.config.cjs', pattern: 'server/mcp/ui/**' },
|
|
165
|
-
// MCP UI 使用独立浏览器配置;仅迁移已知模板命令,不生成 UI 目录或配置。
|
|
162
|
+
// MCP UI 使用独立浏览器配置;只补服务端排除项,不生成 UI 目录和配置。
|
|
166
163
|
{
|
|
167
164
|
type: 'typescript-exclude',
|
|
168
165
|
to: 'tsconfig.node.json',
|
|
169
166
|
path: 'server/mcp/ui',
|
|
170
167
|
},
|
|
171
|
-
{
|
|
172
|
-
type: 'add-script',
|
|
173
|
-
name: 'type:check:mcp-ui',
|
|
174
|
-
command: 'node ./scripts/check.cjs --typecheck-mcp-ui',
|
|
175
|
-
overwrite: false,
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
type: 'add-script',
|
|
179
|
-
name: 'eslint:mcp-ui',
|
|
180
|
-
command: 'node ./scripts/check.cjs --eslint-mcp-ui',
|
|
181
|
-
overwrite: false,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
type: 'patch-script',
|
|
185
|
-
name: 'type:check:mcp-ui',
|
|
186
|
-
to: 'node ./scripts/check.cjs --typecheck-mcp-ui',
|
|
187
|
-
ifEquals: ['node ./scripts/lint.js --typecheck-mcp-ui'],
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
type: 'patch-script',
|
|
191
|
-
name: 'eslint:mcp-ui',
|
|
192
|
-
to: 'node ./scripts/check.cjs --eslint-mcp-ui',
|
|
193
|
-
ifEquals: ['node ./scripts/lint.js --eslint-mcp-ui'],
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
type: 'patch-script',
|
|
197
|
-
name: 'type:check',
|
|
198
|
-
to: 'node ./scripts/check.cjs --typecheck',
|
|
199
|
-
ifEquals: [
|
|
200
|
-
'node ./scripts/lint.js --typecheck',
|
|
201
|
-
'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
|
|
202
|
-
'concurrently "npm run type:check:server" "npm run type:check:client"',
|
|
203
|
-
'concurrently "npm run type:check:client" "npm run type:check:server"',
|
|
204
|
-
],
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
type: 'patch-script',
|
|
208
|
-
name: 'eslint',
|
|
209
|
-
to: 'eslint . --quiet --ignore-pattern "server/mcp/ui/**"',
|
|
210
|
-
ifEquals: ['eslint . --quiet', 'eslint .'],
|
|
211
|
-
},
|
|
212
168
|
// ===== miaoda-cli 本地开发新增规则(fullstack-cli sync 不会执行) =====
|
|
213
169
|
// M1. scripts.dev:local —— 本地用户绕过沙箱判定直接跑本地链路(npm run dev:local)。
|
|
214
170
|
// dev.sh 在 MIAODA_DEP_CACHE_DIR 非空时跑 dev.js(沙箱保活)、否则 exec dev-local.js;显式 dev:local
|
package/dist/utils/sync-rule.js
CHANGED
|
@@ -44,7 +44,6 @@ const file_ops_1 = require("./file-ops");
|
|
|
44
44
|
const merge_json_1 = require("./merge-json");
|
|
45
45
|
const logger_1 = require("./logger");
|
|
46
46
|
const typescript_exclude_1 = require("./typescript-exclude");
|
|
47
|
-
const eslint_ignore_1 = require("./eslint-ignore");
|
|
48
47
|
/**
|
|
49
48
|
* 顺序执行 SyncRule[],逐条 apply。一条 rule 报错不影响后续 rule(catch + 记录),最终
|
|
50
49
|
* 在 handler 层决定是否整体 fail。
|
|
@@ -108,19 +107,6 @@ function applyOne(rule, opts) {
|
|
|
108
107
|
case 'patch-script': {
|
|
109
108
|
return applyPatchScript(rule, targetDir, logPrefix);
|
|
110
109
|
}
|
|
111
|
-
case 'eslint-ignore': {
|
|
112
|
-
const result = (0, eslint_ignore_1.addEslintGlobalIgnore)(node_path_1.default.join(targetDir, rule.to), rule.pattern);
|
|
113
|
-
if (result.warning)
|
|
114
|
-
(0, logger_1.log)(logPrefix, ` ⚠ ${result.warning}`);
|
|
115
|
-
if (result.changed)
|
|
116
|
-
(0, logger_1.log)(logPrefix, ` ✓ ${rule.to} (global ignore ${rule.pattern})`);
|
|
117
|
-
return {
|
|
118
|
-
rule,
|
|
119
|
-
action: result.changed ? 'patched' : result.warning ? 'skipped' : 'noop',
|
|
120
|
-
path: rule.to,
|
|
121
|
-
detail: result.warning,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
110
|
case 'typescript-exclude': {
|
|
125
111
|
const changed = (0, typescript_exclude_1.excludeTypeScriptPath)(node_path_1.default.join(targetDir, rule.to), rule.path);
|
|
126
112
|
(0, logger_1.log)(logPrefix, ` ${changed ? '✓' : '○'} ${rule.to} (exclude ${rule.path})`);
|
|
@@ -235,10 +221,7 @@ function applyPatchScript(rule, targetDir, logPrefix) {
|
|
|
235
221
|
(0, logger_1.log)(logPrefix, ` ○ scripts.${rule.name} (already patched)`);
|
|
236
222
|
return { rule, action: 'noop', path: 'package.json' };
|
|
237
223
|
}
|
|
238
|
-
|
|
239
|
-
? rule.ifEquals.includes(current)
|
|
240
|
-
: rule.ifStartsWith !== undefined && current.startsWith(rule.ifStartsWith);
|
|
241
|
-
if (!matches) {
|
|
224
|
+
if (!current.startsWith(rule.ifStartsWith)) {
|
|
242
225
|
(0, logger_1.log)(logPrefix, ` ⚠ scripts.${rule.name} customized, skip patch`);
|
|
243
226
|
return { rule, action: 'skipped', path: 'package.json' };
|
|
244
227
|
}
|
|
@@ -5,58 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.excludeTypeScriptPath = excludeTypeScriptPath;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
10
8
|
const jsonc_parser_1 = require("jsonc-parser");
|
|
11
9
|
const error_1 = require("./error");
|
|
12
|
-
/**
|
|
10
|
+
/** Only append to explicit template excludes; never rewrite inherited user configuration. */
|
|
13
11
|
function excludeTypeScriptPath(filename, excludedPath) {
|
|
14
12
|
if (!node_fs_1.default.existsSync(filename))
|
|
15
13
|
return false;
|
|
16
14
|
const source = node_fs_1.default.readFileSync(filename, 'utf8');
|
|
17
15
|
const errors = [];
|
|
18
16
|
const raw = (0, jsonc_parser_1.parse)(source, errors, { allowTrailingComma: true });
|
|
19
|
-
if (errors.length || !raw || typeof raw !== 'object'
|
|
20
|
-
throw new error_1.AppError('INVALID_CONFIG', `Invalid TypeScript configuration: ${filename}`);
|
|
21
|
-
}
|
|
22
|
-
const parsed = typescript_1.default.getParsedCommandLineOfConfigFile(filename, {}, {
|
|
23
|
-
...typescript_1.default.sys,
|
|
24
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
25
|
-
throw new error_1.AppError('INVALID_CONFIG', typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, '\n'));
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
// 初始化中的空工程允许迁移,其他错误不能被静默忽略。
|
|
29
|
-
const diagnostics = parsed?.errors.filter((d) => d.code !== 18003 && d.code !== 18002) ?? [];
|
|
30
|
-
if (!parsed || diagnostics.length) {
|
|
31
|
-
throw new error_1.AppError('INVALID_CONFIG', `Cannot migrate ${filename}: ${diagnostics.map((d) => typescript_1.default.flattenDiagnosticMessageText(d.messageText, '\n')).join('; ')}`);
|
|
32
|
-
}
|
|
33
|
-
const effective = parsed.raw;
|
|
34
|
-
const excludes = effective.exclude ?? [
|
|
35
|
-
'node_modules',
|
|
36
|
-
'bower_components',
|
|
37
|
-
'jspm_packages',
|
|
38
|
-
...[parsed.options.outDir, parsed.options.declarationDir]
|
|
39
|
-
.filter((p) => Boolean(p))
|
|
40
|
-
.map((p) => node_path_1.default.relative(node_path_1.default.dirname(filename), p).split(node_path_1.default.sep).join('/')),
|
|
41
|
-
];
|
|
42
|
-
if (!Array.isArray(excludes) || excludes.some((p) => typeof p !== 'string')) {
|
|
17
|
+
if (errors.length || !raw || typeof raw !== 'object')
|
|
43
18
|
throw new error_1.AppError('INVALID_CONFIG', `Invalid exclude array: ${filename}`);
|
|
19
|
+
if (raw.exclude === undefined) {
|
|
20
|
+
console.warn(`Cannot safely update inherited excludes in ${filename}; add ${excludedPath} to the effective TypeScript exclude configuration.`);
|
|
21
|
+
return false;
|
|
44
22
|
}
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
if (!Array.isArray(raw.exclude) || raw.exclude.some((p) => typeof p !== 'string'))
|
|
24
|
+
throw new error_1.AppError('INVALID_CONFIG', `Invalid exclude array: ${filename}`);
|
|
25
|
+
if (raw.exclude.includes(excludedPath))
|
|
47
26
|
return false;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
formattingOptions,
|
|
57
|
-
isArrayInsertion: true,
|
|
58
|
-
})
|
|
59
|
-
: (0, jsonc_parser_1.modify)(source, ['exclude'], [...paths, excludedPath], { formattingOptions });
|
|
60
|
-
node_fs_1.default.writeFileSync(filename, (0, jsonc_parser_1.applyEdits)(source, edits));
|
|
27
|
+
node_fs_1.default.writeFileSync(filename, (0, jsonc_parser_1.applyEdits)(source, (0, jsonc_parser_1.modify)(source, ['exclude', raw.exclude.length], excludedPath, {
|
|
28
|
+
isArrayInsertion: true,
|
|
29
|
+
formattingOptions: {
|
|
30
|
+
insertSpaces: true,
|
|
31
|
+
tabSize: 2,
|
|
32
|
+
eol: source.includes('\r\n') ? '\r\n' : '\n',
|
|
33
|
+
},
|
|
34
|
+
})));
|
|
61
35
|
return true;
|
|
62
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.39-alpha.
|
|
3
|
+
"version": "0.1.39-alpha.508c930",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"jsonc-parser": "^3.3.1",
|
|
34
34
|
"ora": "^5.4.1",
|
|
35
35
|
"picocolors": "^1.1.1",
|
|
36
|
-
"which": "^7.0.0"
|
|
37
|
-
"typescript": "^5.8.3"
|
|
36
|
+
"which": "^7.0.0"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
39
|
"@types/cross-spawn": "^6.0.6",
|
|
@@ -52,6 +51,7 @@
|
|
|
52
51
|
"prettier": "^3.8.3",
|
|
53
52
|
"tsc-alias": "^1.8.11",
|
|
54
53
|
"tsx": "^4.19.4",
|
|
54
|
+
"typescript": "^5.8.3",
|
|
55
55
|
"vitest": "^4.1.4",
|
|
56
56
|
"xml2js": "^0.6.2"
|
|
57
57
|
},
|
|
@@ -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,17 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
+
const { spawn } = require('node:child_process');
|
|
4
5
|
const fs = require('node:fs');
|
|
5
6
|
|
|
6
7
|
const cwd = process.cwd();
|
|
7
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
|
+
|
|
8
26
|
function normalizeProjectFile(filePath) {
|
|
9
27
|
const absolutePath = path.isAbsolute(filePath)
|
|
10
28
|
? filePath
|
|
11
29
|
: path.resolve(cwd, filePath);
|
|
12
30
|
|
|
13
31
|
if (!fs.existsSync(absolutePath)) {
|
|
14
|
-
console.
|
|
32
|
+
console.warn(`[lint] Skip missing file: ${filePath}`);
|
|
33
|
+
return null;
|
|
15
34
|
}
|
|
16
35
|
|
|
17
36
|
const relativePath = path.relative(cwd, absolutePath);
|
|
@@ -75,26 +94,17 @@ function canRunStylelint() {
|
|
|
75
94
|
return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
const { getBinName, runTasksSerially, uiTasks, packageScripts, main: runChecks } = require('./check.cjs');
|
|
79
|
-
const UI_TYPECHECKS = ['node ./scripts/check.cjs --typecheck', 'node ./scripts/lint.js --typecheck'];
|
|
80
|
-
function isUiFile(file) { return file.startsWith('server/mcp/ui/'); }
|
|
81
|
-
|
|
82
97
|
async function runDefaultLint() {
|
|
83
|
-
const
|
|
84
|
-
[getBinName('npm'), ['run', 'eslint']],
|
|
85
|
-
[getBinName('npm'), ['run', 'type:check']],
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
// Customized type:check remains untouched; ensure full lint still checks UI.
|
|
89
|
-
taskSpecs.push(...uiTasks({ types: !UI_TYPECHECKS.includes(packageScripts()['type:check']) }));
|
|
98
|
+
const commands = ['npm run eslint', 'npm run type:check'];
|
|
90
99
|
|
|
91
100
|
if (canRunStylelint()) {
|
|
92
|
-
|
|
101
|
+
commands.push('npm run stylelint');
|
|
93
102
|
} else {
|
|
94
103
|
console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
|
|
106
|
+
const code = await runCommand(getBinName('npx'), ['concurrently', ...commands]);
|
|
107
|
+
process.exit(code);
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
async function runSelectiveLint(inputFiles) {
|
|
@@ -107,20 +117,15 @@ async function runSelectiveLint(inputFiles) {
|
|
|
107
117
|
process.exit(0);
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
const eslintFiles = normalizedFiles.filter(
|
|
111
|
-
const stylelintFiles = normalizedFiles.filter(
|
|
120
|
+
const eslintFiles = normalizedFiles.filter(isEslintTarget);
|
|
121
|
+
const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
|
|
112
122
|
const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
|
|
113
123
|
|
|
114
124
|
const clientTypeFiles = [];
|
|
115
125
|
const serverTypeFiles = [];
|
|
116
|
-
const rootConfigChanged = normalizedFiles.some(file => /^tsconfig(?:\.[^/]*)?\.json$/.test(file));
|
|
117
|
-
if (rootConfigChanged) { clientTypeFiles.push('config'); serverTypeFiles.push('config'); }
|
|
118
|
-
let checkUi = rootConfigChanged || normalizedFiles.some(file => isUiFile(file) || file.startsWith('shared/') || file.startsWith('eslint.mcp-ui.config.'));
|
|
119
126
|
|
|
120
127
|
for (const filePath of typeCheckFiles) {
|
|
121
|
-
if (
|
|
122
|
-
checkUi = true;
|
|
123
|
-
} else if (filePath.startsWith('client/')) {
|
|
128
|
+
if (filePath.startsWith('client/')) {
|
|
124
129
|
clientTypeFiles.push(filePath);
|
|
125
130
|
} else if (filePath.startsWith('server/')) {
|
|
126
131
|
serverTypeFiles.push(filePath);
|
|
@@ -130,45 +135,36 @@ async function runSelectiveLint(inputFiles) {
|
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
const
|
|
138
|
+
const tasks = [];
|
|
134
139
|
|
|
135
140
|
if (eslintFiles.length > 0) {
|
|
136
|
-
|
|
141
|
+
tasks.push(runCommand(getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]));
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
if (stylelintFiles.length > 0 && !canRunStylelint()) {
|
|
140
145
|
console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
|
|
141
146
|
} else if (stylelintFiles.length > 0) {
|
|
142
|
-
|
|
147
|
+
tasks.push(runCommand(getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]));
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
if (clientTypeFiles.length > 0) {
|
|
146
|
-
|
|
151
|
+
tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:client']));
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
if (serverTypeFiles.length > 0) {
|
|
150
|
-
|
|
155
|
+
tasks.push(runCommand(getBinName('npm'), ['run', 'type:check:server']));
|
|
151
156
|
}
|
|
152
157
|
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
if (taskSpecs.length === 0) {
|
|
158
|
+
if (tasks.length === 0) {
|
|
156
159
|
console.log('[lint] No supported files matched for lint');
|
|
157
160
|
process.exit(0);
|
|
158
161
|
}
|
|
159
162
|
|
|
160
|
-
|
|
163
|
+
const results = await Promise.all(tasks);
|
|
164
|
+
process.exit(results.some(code => code !== 0) ? 1 : 0);
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
async function main() {
|
|
164
|
-
const args = process.argv.slice(2);
|
|
165
|
-
const modes = ['--typecheck', '--typecheck-mcp-ui', '--eslint-mcp-ui'];
|
|
166
|
-
if (args.length && !(args[0] === '--files' && args.length > 1) && !(args.length === 1 && modes.includes(args[0]))) {
|
|
167
|
-
throw new Error('用法: lint.js [--files <paths...> | --typecheck | --typecheck-mcp-ui | --eslint-mcp-ui]');
|
|
168
|
-
}
|
|
169
|
-
if (args[0] === '--files' && args.slice(1).some(arg => arg.startsWith('--'))) throw new Error('--files 不可与其他检查模式混用');
|
|
170
|
-
// 兼容旧入口,只向独立检查器转发;新的 npm scripts 不再指向 lint.js。
|
|
171
|
-
if (modes.includes(args[0])) return runChecks(args);
|
|
172
168
|
const files = parseFilesArg(process.argv.slice(2));
|
|
173
169
|
if (files === null) {
|
|
174
170
|
await runDefaultLint();
|
|
@@ -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,169 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.addEslintGlobalIgnore = addEslintGlobalIgnore;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
-
const error_1 = require("./error");
|
|
10
|
-
/** 只解析源码,不加载用户配置;用局部插入保留自定义规则和注释。 */
|
|
11
|
-
function addEslintGlobalIgnore(filename, pattern) {
|
|
12
|
-
if (!node_fs_1.default.existsSync(filename))
|
|
13
|
-
return { changed: false };
|
|
14
|
-
const source = node_fs_1.default.readFileSync(filename, 'utf8');
|
|
15
|
-
const file = typescript_1.default.createSourceFile(filename, source, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.JS);
|
|
16
|
-
const diagnostics = file
|
|
17
|
-
.parseDiagnostics;
|
|
18
|
-
if (diagnostics.length)
|
|
19
|
-
throw new error_1.AppError('INVALID_CONFIG', `Invalid ESLint configuration: ${filename}`);
|
|
20
|
-
const variables = new Map();
|
|
21
|
-
const declarations = new Map();
|
|
22
|
-
const aliasReferences = new Set();
|
|
23
|
-
const resolvedAliases = new Set();
|
|
24
|
-
const exportTargets = new Set();
|
|
25
|
-
const helpers = new Set();
|
|
26
|
-
const namespaces = new Set();
|
|
27
|
-
const exports = [];
|
|
28
|
-
const moduleName = (node) => {
|
|
29
|
-
if (!typescript_1.default.isCallExpression(node) ||
|
|
30
|
-
!typescript_1.default.isIdentifier(node.expression) ||
|
|
31
|
-
node.expression.text !== 'require')
|
|
32
|
-
return;
|
|
33
|
-
const arg = node.arguments.at(0);
|
|
34
|
-
return arg && typescript_1.default.isStringLiteral(arg) ? arg.text : undefined;
|
|
35
|
-
};
|
|
36
|
-
for (const statement of file.statements) {
|
|
37
|
-
if (typescript_1.default.isImportDeclaration(statement) && typescript_1.default.isStringLiteral(statement.moduleSpecifier)) {
|
|
38
|
-
const module = statement.moduleSpecifier.text;
|
|
39
|
-
const clause = statement.importClause;
|
|
40
|
-
if (module === 'typescript-eslint' && clause?.name)
|
|
41
|
-
namespaces.add(clause.name.text);
|
|
42
|
-
const bindings = clause?.namedBindings;
|
|
43
|
-
if (bindings && typescript_1.default.isNamespaceImport(bindings) && module === 'typescript-eslint')
|
|
44
|
-
namespaces.add(bindings.name.text);
|
|
45
|
-
if (bindings && typescript_1.default.isNamedImports(bindings)) {
|
|
46
|
-
for (const element of bindings.elements) {
|
|
47
|
-
const name = element.propertyName?.text ?? element.name.text;
|
|
48
|
-
if ((module === 'eslint/config' && name === 'defineConfig') ||
|
|
49
|
-
(module === 'typescript-eslint' && name === 'config'))
|
|
50
|
-
helpers.add(element.name.text);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (typescript_1.default.isVariableStatement(statement) && statement.declarationList.flags & typescript_1.default.NodeFlags.Const) {
|
|
55
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
56
|
-
if (!declaration.initializer)
|
|
57
|
-
continue;
|
|
58
|
-
const module = moduleName(declaration.initializer);
|
|
59
|
-
if (typescript_1.default.isIdentifier(declaration.name)) {
|
|
60
|
-
variables.set(declaration.name.text, declaration.initializer);
|
|
61
|
-
declarations.set(declaration.name.text, declaration.name);
|
|
62
|
-
if (module === 'typescript-eslint')
|
|
63
|
-
namespaces.add(declaration.name.text);
|
|
64
|
-
}
|
|
65
|
-
else if (typescript_1.default.isObjectBindingPattern(declaration.name)) {
|
|
66
|
-
for (const element of declaration.name.elements) {
|
|
67
|
-
const name = element.propertyName ?? element.name;
|
|
68
|
-
if (!typescript_1.default.isIdentifier(name) || !typescript_1.default.isIdentifier(element.name))
|
|
69
|
-
continue;
|
|
70
|
-
if ((module === 'eslint/config' && name.text === 'defineConfig') ||
|
|
71
|
-
(module === 'typescript-eslint' && name.text === 'config'))
|
|
72
|
-
helpers.add(element.name.text);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (typescript_1.default.isExportAssignment(statement) && !statement.isExportEquals)
|
|
78
|
-
exports.push(statement.expression);
|
|
79
|
-
if (typescript_1.default.isExpressionStatement(statement) && typescript_1.default.isBinaryExpression(statement.expression)) {
|
|
80
|
-
const { left, right, operatorToken } = statement.expression;
|
|
81
|
-
if (operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken &&
|
|
82
|
-
typescript_1.default.isPropertyAccessExpression(left) &&
|
|
83
|
-
typescript_1.default.isIdentifier(left.expression) &&
|
|
84
|
-
left.expression.text === 'module' &&
|
|
85
|
-
left.name.text === 'exports') {
|
|
86
|
-
exports.push(right);
|
|
87
|
-
exportTargets.add(left);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function unwrap(node, seen = new Set()) {
|
|
92
|
-
if (typescript_1.default.isParenthesizedExpression(node))
|
|
93
|
-
return unwrap(node.expression, seen);
|
|
94
|
-
if (typescript_1.default.isIdentifier(node) && !seen.has(node.text)) {
|
|
95
|
-
const value = variables.get(node.text);
|
|
96
|
-
if (value) {
|
|
97
|
-
resolvedAliases.add(node.text);
|
|
98
|
-
aliasReferences.add(node);
|
|
99
|
-
seen.add(node.text);
|
|
100
|
-
return unwrap(value, seen);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return node;
|
|
104
|
-
}
|
|
105
|
-
const root = exports.length === 1 ? unwrap(exports[0]) : undefined;
|
|
106
|
-
// const 只固定变量绑定,不能保证数组不被 pop/push 或传给其他函数修改。
|
|
107
|
-
// 有额外引用时保留原配置,避免改变初始化数组影响用户后续操作。
|
|
108
|
-
function checkReferences(node) {
|
|
109
|
-
if (typescript_1.default.isIdentifier(node) &&
|
|
110
|
-
resolvedAliases.has(node.text) &&
|
|
111
|
-
declarations.get(node.text) !== node &&
|
|
112
|
-
!aliasReferences.has(node))
|
|
113
|
-
return true;
|
|
114
|
-
// module["exports"]、(module).exports、module 别名也可能修改导出数组。
|
|
115
|
-
if (typescript_1.default.isIdentifier(node) &&
|
|
116
|
-
node.text === 'module' &&
|
|
117
|
-
!(typescript_1.default.isPropertyAccessExpression(node.parent) &&
|
|
118
|
-
node.parent.expression === node &&
|
|
119
|
-
exportTargets.has(node.parent)))
|
|
120
|
-
return true;
|
|
121
|
-
return typescript_1.default.forEachChild(node, (child) => checkReferences(child) || undefined) ?? false;
|
|
122
|
-
}
|
|
123
|
-
const unsafeReference = checkReferences(file);
|
|
124
|
-
const isHelper = (node) => {
|
|
125
|
-
const callee = node.expression;
|
|
126
|
-
return typescript_1.default.isIdentifier(callee)
|
|
127
|
-
? helpers.has(callee.text)
|
|
128
|
-
: typescript_1.default.isPropertyAccessExpression(callee) &&
|
|
129
|
-
typescript_1.default.isIdentifier(callee.expression) &&
|
|
130
|
-
namespaces.has(callee.expression.text) &&
|
|
131
|
-
callee.name.text === 'config';
|
|
132
|
-
};
|
|
133
|
-
const entries = root && typescript_1.default.isArrayLiteralExpression(root)
|
|
134
|
-
? root.elements
|
|
135
|
-
: root && typescript_1.default.isCallExpression(root) && isHelper(root)
|
|
136
|
-
? root.arguments
|
|
137
|
-
: undefined;
|
|
138
|
-
if (!entries || unsafeReference) {
|
|
139
|
-
return {
|
|
140
|
-
changed: false,
|
|
141
|
-
warning: `Cannot safely migrate ${filename}; add ${JSON.stringify(pattern)} to its global ignores manually.`,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
// 只认真正的全局 ignores;带 files/rules 的局部忽略不能阻止服务端配置匹配 UI。
|
|
145
|
-
// 仅检查最后一个配置,确保此前的否定规则或动态配置无法撤销新增忽略。
|
|
146
|
-
const last = entries.at(-1);
|
|
147
|
-
if (last && typescript_1.default.isObjectLiteralExpression(last)) {
|
|
148
|
-
const props = last.properties;
|
|
149
|
-
const nameOf = (p) => p.name && (typescript_1.default.isIdentifier(p.name) || typescript_1.default.isStringLiteral(p.name)) ? p.name.text : undefined;
|
|
150
|
-
if (new Set(props.map(nameOf)).size === props.length &&
|
|
151
|
-
props.every((p) => typescript_1.default.isPropertyAssignment(p) && ['ignores', 'name'].includes(nameOf(p) ?? ''))) {
|
|
152
|
-
const ignores = props.find((p) => nameOf(p) === 'ignores');
|
|
153
|
-
if (ignores &&
|
|
154
|
-
typescript_1.default.isPropertyAssignment(ignores) &&
|
|
155
|
-
typescript_1.default.isArrayLiteralExpression(ignores.initializer)) {
|
|
156
|
-
const patterns = ignores.initializer.elements;
|
|
157
|
-
if (patterns.length &&
|
|
158
|
-
patterns.every(typescript_1.default.isStringLiteral) &&
|
|
159
|
-
patterns[patterns.length - 1].text === pattern)
|
|
160
|
-
return { changed: false };
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const eol = source.includes('\r\n') ? '\r\n' : '\n';
|
|
165
|
-
const position = last ? last.end : entries.pos;
|
|
166
|
-
const addition = `${last ? ',' : ''}${eol} { ignores: [${JSON.stringify(pattern)}] }`;
|
|
167
|
-
node_fs_1.default.writeFileSync(filename, source.slice(0, position) + addition + source.slice(position));
|
|
168
|
-
return { changed: true };
|
|
169
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// 叶子检查入口:不得调用 lint 或聚合 type:check npm script。
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawn } = require('node:child_process');
|
|
6
|
-
const fs = require('node:fs');
|
|
7
|
-
const cwd = process.cwd();
|
|
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 ?? 1));
|
|
22
|
-
child.on('error', () => resolve(1));
|
|
23
|
-
});
|
|
24
|
-
}
|
|
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
|
-
|
|
39
|
-
function isEslintTarget(filePath) { return /\.(c|m)?(j|t)sx?$/.test(filePath); }
|
|
40
|
-
|
|
41
|
-
const UI_DIR = 'server/mcp/ui';
|
|
42
|
-
|
|
43
|
-
function uiSourceFiles() {
|
|
44
|
-
const files = [];
|
|
45
|
-
function visit(relative) {
|
|
46
|
-
const absolute = path.join(cwd, relative);
|
|
47
|
-
if (!fs.existsSync(absolute)) return;
|
|
48
|
-
if (fs.lstatSync(absolute).isSymbolicLink()) {
|
|
49
|
-
throw new Error(`MCP UI 检查不支持符号链接: ${relative}`);
|
|
50
|
-
}
|
|
51
|
-
for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
|
|
52
|
-
if (['node_modules', 'dist', '.git'].includes(entry.name)) continue;
|
|
53
|
-
const file = `${relative}/${entry.name}`;
|
|
54
|
-
if (entry.isSymbolicLink()) throw new Error(`MCP UI 检查不支持符号链接: ${file}`);
|
|
55
|
-
if (entry.isDirectory()) visit(file);
|
|
56
|
-
else if (isEslintTarget(file) && !/\.d\.(?:ts|mts|cts)$/.test(file)) files.push(file);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
visit(UI_DIR);
|
|
60
|
-
return files.sort();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function uiTasks({ types = true, eslint = true } = {}) {
|
|
64
|
-
const sources = uiSourceFiles();
|
|
65
|
-
if (!sources.length) return [];
|
|
66
|
-
const tasks = [];
|
|
67
|
-
if (types) {
|
|
68
|
-
const config = `${UI_DIR}/tsconfig.json`;
|
|
69
|
-
if (!fs.existsSync(path.join(cwd, config))) {
|
|
70
|
-
throw new Error(`MCP UI 缺少 ${config},请按 mcp-guide 补齐独立浏览器 TypeScript 配置。`);
|
|
71
|
-
}
|
|
72
|
-
const ts = require('typescript');
|
|
73
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(path.join(cwd, config), {}, {
|
|
74
|
-
...ts.sys,
|
|
75
|
-
onUnRecoverableConfigFileDiagnostic: d => { throw new Error(ts.flattenDiagnosticMessageText(d.messageText, '\n')); },
|
|
76
|
-
});
|
|
77
|
-
if (!parsed || parsed.errors.length) throw new Error(`MCP UI TypeScript 配置无效: ${config}`);
|
|
78
|
-
const included = new Set(parsed.fileNames.map(file => path.resolve(file)));
|
|
79
|
-
const missing = sources.filter(file => !included.has(path.resolve(cwd, file)));
|
|
80
|
-
if (missing.length) throw new Error(`MCP UI TypeScript 配置未覆盖源码: ${missing.join(', ')};请检查 include/exclude,JS 源码需开启 allowJs。`);
|
|
81
|
-
tasks.push([getBinName('npx'), ['--no-install', 'tsc', '--noEmit', '-p', config]]);
|
|
82
|
-
}
|
|
83
|
-
if (eslint) {
|
|
84
|
-
const config = ['eslint.mcp-ui.config.js', 'eslint.mcp-ui.config.cjs', 'eslint.mcp-ui.config.mjs']
|
|
85
|
-
.find(file => fs.existsSync(path.join(cwd, file)));
|
|
86
|
-
if (!config) throw new Error('MCP UI 缺少独立 ESLint 配置,请按 mcp-guide 创建 eslint.mcp-ui.config.cjs。');
|
|
87
|
-
tasks.push([getBinName('npx'), ['--no-install', 'eslint', '--config', config, '--quiet', '--max-warnings', '0', ...sources]]);
|
|
88
|
-
}
|
|
89
|
-
return tasks;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function packageScripts() {
|
|
93
|
-
return JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')).scripts || {};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function runTypeCheck() {
|
|
97
|
-
const scripts = packageScripts();
|
|
98
|
-
const tasks = ['type:check:server', 'type:check:client'].map(name => {
|
|
99
|
-
if (!scripts[name]) throw new Error(`缺少 scripts.${name},请保留应用原有类型检查命令。`);
|
|
100
|
-
return [getBinName('npm'), ['run', name]];
|
|
101
|
-
});
|
|
102
|
-
tasks.push(...uiTasks({ eslint: false }));
|
|
103
|
-
process.exit(await runTasksSerially(tasks));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function main(args = process.argv.slice(2)) {
|
|
107
|
-
const modes = ['--typecheck', '--typecheck-mcp-ui', '--eslint-mcp-ui'];
|
|
108
|
-
if (args.length !== 1 || !modes.includes(args[0])) {
|
|
109
|
-
throw new Error('用法: check.cjs --typecheck | --typecheck-mcp-ui | --eslint-mcp-ui');
|
|
110
|
-
}
|
|
111
|
-
// 自定义 leaf script 意外回到聚合入口时立即失败,避免生成递归进程树。
|
|
112
|
-
if (process.env.MIAODA_TYPECHECK_ACTIVE === '1') {
|
|
113
|
-
throw new Error('检测到检查命令递归,请检查 type:check:server/client,不能回调 lint 或 type:check。');
|
|
114
|
-
}
|
|
115
|
-
process.env.MIAODA_TYPECHECK_ACTIVE = '1';
|
|
116
|
-
if (args[0] === '--typecheck') return runTypeCheck();
|
|
117
|
-
process.exit(await runTasksSerially(uiTasks(args[0] === '--typecheck-mcp-ui' ? { eslint: false } : { types: false })));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
module.exports = { getBinName, runTasksSerially, uiTasks, packageScripts, main };
|
|
121
|
-
if (require.main === module) {
|
|
122
|
-
main().catch(error => { console.error(`[check] ${error.message}`); process.exit(1); });
|
|
123
|
-
}
|