@lark-apaas/coding-template-nestjs-react-fullstack 0.1.41 → 0.1.42-alpha.20260917135900
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 +37 -0
- package/package.json +1 -1
- package/template/package-lock.json +740 -98
- package/template/package.json +6 -5
- package/template/scripts/build.sh +81 -43
- package/template/scripts/lint.js +188 -41
- package/template/scripts/typecheck-mcp-ui.js +32 -0
- package/template/tsconfig.mcp-ui.json +33 -0
- package/template/tsconfig.node.json +2 -1
package/template/package.json
CHANGED
|
@@ -20,15 +20,16 @@
|
|
|
20
20
|
"start": "NODE_ENV=production node main.js",
|
|
21
21
|
"eslint": "eslint . --quiet",
|
|
22
22
|
"stylelint": "stylelint client/src/**/*.css --quiet",
|
|
23
|
-
"type:check": "concurrently -n \"server,client\" -c \"blue,green\" \"npm run type:check:server\" \"npm run type:check:client\"",
|
|
23
|
+
"type:check": "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\"",
|
|
24
24
|
"type:check:client": "tsc --noEmit --project tsconfig.app.json",
|
|
25
25
|
"type:check:server": "tsc --noEmit --project tsconfig.node.json",
|
|
26
26
|
"lint": "node ./scripts/lint.js",
|
|
27
27
|
"precommit": "node scripts/hooks/run-precommit.js",
|
|
28
|
-
"upgrade": "npx -y @lark-apaas/fullstack-cli sync --disable-gen-openapi"
|
|
28
|
+
"upgrade": "npx -y @lark-apaas/fullstack-cli sync --disable-gen-openapi",
|
|
29
|
+
"type:check:mcp-ui": "node ./scripts/typecheck-mcp-ui.js"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@lark-apaas/fullstack-nestjs-core": "
|
|
32
|
+
"@lark-apaas/fullstack-nestjs-core": "1.1.62-alpha.20260917134431",
|
|
32
33
|
"@nestjs/axios": "^4.0.1",
|
|
33
34
|
"@nestjs/cache-manager": "^3.1.3",
|
|
34
35
|
"@nestjs/common": "^10.4.20",
|
|
@@ -53,8 +54,8 @@
|
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@hookform/resolvers": "^5.2.2",
|
|
55
56
|
"@lark-apaas/client-toolkit": "^1.2.70",
|
|
56
|
-
"@lark-apaas/coding-preset-vite-react": "
|
|
57
|
-
"@lark-apaas/fullstack-presets": "
|
|
57
|
+
"@lark-apaas/coding-preset-vite-react": "1.0.24-alpha.20260917134427",
|
|
58
|
+
"@lark-apaas/fullstack-presets": "1.1.24-alpha.20260917134434",
|
|
58
59
|
"@nestjs/cli": "^10.4.9",
|
|
59
60
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
60
61
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
@@ -43,37 +43,43 @@ echo ""
|
|
|
43
43
|
echo "🔨 [3/5] 并行构建 server 和 client"
|
|
44
44
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
45
45
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
# server/mcp(含 UI/Skill)变更必须由平台判定为全构建。
|
|
47
|
+
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
48
|
+
echo " ├─ 仅构建 client (only_frontend_change=true)"
|
|
49
|
+
npm run build:client > /tmp/build-client.log 2>&1
|
|
50
|
+
else
|
|
51
|
+
# 并行构建
|
|
52
|
+
echo " ├─ 启动 server 构建..."
|
|
53
|
+
npm run build:server > /tmp/build-server.log 2>&1 &
|
|
54
|
+
SERVER_PID=$!
|
|
55
|
+
|
|
56
|
+
echo " ├─ 启动 client 构建..."
|
|
57
|
+
npm run build:client > /tmp/build-client.log 2>&1 &
|
|
58
|
+
CLIENT_PID=$!
|
|
59
|
+
|
|
60
|
+
# 等待两个构建完成
|
|
61
|
+
SERVER_EXIT=0
|
|
62
|
+
CLIENT_EXIT=0
|
|
63
|
+
|
|
64
|
+
wait $SERVER_PID || SERVER_EXIT=$?
|
|
65
|
+
wait $CLIENT_PID || CLIENT_EXIT=$?
|
|
66
|
+
|
|
67
|
+
# 检查构建结果
|
|
68
|
+
if [ $SERVER_EXIT -ne 0 ]; then
|
|
69
|
+
echo " ❌ Server 构建失败"
|
|
70
|
+
cat /tmp/build-server.log
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
75
|
+
echo " ❌ Client 构建失败"
|
|
76
|
+
cat /tmp/build-client.log
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
echo " ✅ Server 构建完成"
|
|
81
|
+
echo " ✅ Client 构建完成"
|
|
67
82
|
fi
|
|
68
|
-
|
|
69
|
-
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
70
|
-
echo " ❌ Client 构建失败"
|
|
71
|
-
cat /tmp/build-client.log
|
|
72
|
-
exit 1
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
echo " ✅ Server 构建完成"
|
|
76
|
-
echo " ✅ Client 构建完成"
|
|
77
83
|
print_time $STEP_START
|
|
78
84
|
echo ""
|
|
79
85
|
|
|
@@ -81,18 +87,21 @@ echo ""
|
|
|
81
87
|
echo "📦 [4/5] 准备 server 依赖产物"
|
|
82
88
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
if [[ "${only_frontend_change:-false}" != "true" ]]; then
|
|
91
|
+
mkdir -p "$OUT_DIR/dist/client"
|
|
85
92
|
|
|
86
|
-
# public 先进(server setBaseViewsDir + nestjs-core 中间件同源 serve)
|
|
87
|
-
if [ -d "$ROOT_DIR/client/public" ]; then
|
|
88
|
-
|
|
89
|
-
fi
|
|
93
|
+
# public 先进(server setBaseViewsDir + nestjs-core 中间件同源 serve)
|
|
94
|
+
if [ -d "$ROOT_DIR/client/public" ]; then
|
|
95
|
+
cp -R "$ROOT_DIR/client/public/." "$OUT_DIR/dist/client/"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# 构建产物 HTML 随后拷,覆盖 public 里的同名文件(保证入口页是构建版,不被 public 静默覆盖)
|
|
99
|
+
cp "$ROOT_DIR/dist/client/"*.html "$OUT_DIR/dist/client/" || true
|
|
90
100
|
|
|
91
|
-
#
|
|
92
|
-
cp "$ROOT_DIR/
|
|
101
|
+
# 拷贝 run.sh 文件
|
|
102
|
+
cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
|
|
104
|
+
fi
|
|
96
105
|
|
|
97
106
|
# 清理无用文件
|
|
98
107
|
rm -rf "$ROOT_DIR/dist/scripts"
|
|
@@ -106,7 +115,9 @@ echo "✂️ [5/5] 智能依赖裁剪"
|
|
|
106
115
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
107
116
|
|
|
108
117
|
# 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
|
|
109
|
-
|
|
118
|
+
if [[ "${only_frontend_change:-false}" != "true" ]]; then
|
|
119
|
+
node "$ROOT_DIR/scripts/prune-smart.js"
|
|
120
|
+
fi
|
|
110
121
|
|
|
111
122
|
print_time $STEP_START
|
|
112
123
|
echo ""
|
|
@@ -116,10 +127,37 @@ echo "构建完成"
|
|
|
116
127
|
print_time $TOTAL_START
|
|
117
128
|
|
|
118
129
|
# 输出产物信息
|
|
119
|
-
DIST_SIZE=$(du -sh "$
|
|
120
|
-
NODE_MODULES_SIZE=$(du -sh "$OUT_DIR/node_modules" | cut -f1)
|
|
130
|
+
DIST_SIZE=$(du -sh "$ROOT_DIR/dist" | cut -f1)
|
|
121
131
|
echo ""
|
|
122
132
|
echo "📊 构建产物统计:"
|
|
123
133
|
echo " 总大小: $DIST_SIZE"
|
|
124
|
-
|
|
134
|
+
if [[ "${only_frontend_change:-false}" != "true" ]]; then
|
|
135
|
+
NODE_MODULES_SIZE=$(du -sh "$OUT_DIR/node_modules" | cut -f1)
|
|
136
|
+
echo " node_modules: $NODE_MODULES_SIZE"
|
|
137
|
+
fi
|
|
125
138
|
echo ""
|
|
139
|
+
|
|
140
|
+
# 仅成功的全构建生成 backendBuildID;必须在构建、产物准备和依赖裁剪之后。
|
|
141
|
+
# UI/Skill 内容由 SDK 独立计入总 hash;纯前端发布不替换已部署的 backendBuildID。
|
|
142
|
+
if [[ "${only_frontend_change:-false}" != "true" ]]; then
|
|
143
|
+
node - "$ROOT_DIR/dist" <<'NODE'
|
|
144
|
+
const fs = require('node:fs');
|
|
145
|
+
const path = require('node:path');
|
|
146
|
+
const { randomUUID } = require('node:crypto');
|
|
147
|
+
const dist = process.argv[2];
|
|
148
|
+
// 防止 build:server 空跑成功、却没有可交付的服务端入口。
|
|
149
|
+
if (!fs.statSync(path.join(dist, 'server/main.js')).isFile()) {
|
|
150
|
+
throw new Error('Missing server build output: dist/server/main.js');
|
|
151
|
+
}
|
|
152
|
+
// runtime cwd 分别为工程根、dist/(或整包解压到 dist-server/)、旧版 dist/server/。
|
|
153
|
+
// 与 MCP UI/Skill 的交付布局一致:复制产物时保留同一个 UUID,不在启动时生成。
|
|
154
|
+
const files = [
|
|
155
|
+
path.join(dist, 'mcp-build-id'),
|
|
156
|
+
path.join(dist, 'dist/mcp-build-id'),
|
|
157
|
+
path.join(dist, 'server/dist/mcp-build-id'),
|
|
158
|
+
];
|
|
159
|
+
for (const file of files) fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
160
|
+
const buildID = randomUUID();
|
|
161
|
+
for (const file of files) fs.writeFileSync(file, buildID, 'utf8');
|
|
162
|
+
NODE
|
|
163
|
+
fi
|
package/template/scripts/lint.js
CHANGED
|
@@ -1,40 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const { spawn } = require('node:child_process');
|
|
5
4
|
const fs = require('node:fs');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
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
|
-
|
|
26
9
|
function normalizeProjectFile(filePath) {
|
|
27
10
|
const absolutePath = path.isAbsolute(filePath)
|
|
28
11
|
? filePath
|
|
29
12
|
: path.resolve(cwd, filePath);
|
|
30
13
|
|
|
31
14
|
if (!fs.existsSync(absolutePath)) {
|
|
32
|
-
console.
|
|
33
|
-
return null;
|
|
15
|
+
console.log(`[lint] Deleted file: ${filePath}; checking affected projects`);
|
|
34
16
|
}
|
|
35
17
|
|
|
36
18
|
const relativePath = path.relative(cwd, absolutePath);
|
|
37
|
-
if (
|
|
19
|
+
if (
|
|
20
|
+
relativePath === '..' ||
|
|
21
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
22
|
+
path.isAbsolute(relativePath)
|
|
23
|
+
) {
|
|
38
24
|
console.warn(`[lint] Skip file outside project: ${filePath}`);
|
|
39
25
|
return null;
|
|
40
26
|
}
|
|
@@ -91,25 +77,144 @@ function canRunStylelint() {
|
|
|
91
77
|
|
|
92
78
|
if (!pkg.scripts || !pkg.scripts.stylelint) return false;
|
|
93
79
|
|
|
94
|
-
return STYLELINT_CONFIG_FILES.some(file =>
|
|
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/');
|
|
95
187
|
}
|
|
96
188
|
|
|
97
189
|
async function runDefaultLint() {
|
|
98
|
-
const
|
|
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
|
+
);
|
|
99
203
|
|
|
100
204
|
if (canRunStylelint()) {
|
|
101
|
-
|
|
205
|
+
taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
|
|
102
206
|
} else {
|
|
103
|
-
console.warn(
|
|
207
|
+
console.warn(
|
|
208
|
+
'[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
|
|
209
|
+
);
|
|
104
210
|
}
|
|
105
211
|
|
|
106
|
-
|
|
107
|
-
process.exit(code);
|
|
212
|
+
process.exit(await runTasksSerially(taskSpecs));
|
|
108
213
|
}
|
|
109
214
|
|
|
110
215
|
async function runSelectiveLint(inputFiles) {
|
|
111
216
|
const normalizedFiles = Array.from(
|
|
112
|
-
new Set(inputFiles.map(normalizeProjectFile).filter(Boolean))
|
|
217
|
+
new Set(inputFiles.map(normalizeProjectFile).filter(Boolean))
|
|
113
218
|
);
|
|
114
219
|
|
|
115
220
|
if (normalizedFiles.length === 0) {
|
|
@@ -117,15 +222,41 @@ async function runSelectiveLint(inputFiles) {
|
|
|
117
222
|
process.exit(0);
|
|
118
223
|
}
|
|
119
224
|
|
|
120
|
-
const eslintFiles = normalizedFiles.filter(
|
|
121
|
-
|
|
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
|
+
);
|
|
122
235
|
const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
|
|
123
236
|
|
|
124
237
|
const clientTypeFiles = [];
|
|
125
238
|
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
|
+
);
|
|
126
255
|
|
|
127
256
|
for (const filePath of typeCheckFiles) {
|
|
128
|
-
if (filePath
|
|
257
|
+
if (isUiFile(filePath)) {
|
|
258
|
+
checkUi = true;
|
|
259
|
+
} else if (filePath.startsWith('client/')) {
|
|
129
260
|
clientTypeFiles.push(filePath);
|
|
130
261
|
} else if (filePath.startsWith('server/')) {
|
|
131
262
|
serverTypeFiles.push(filePath);
|
|
@@ -135,36 +266,52 @@ async function runSelectiveLint(inputFiles) {
|
|
|
135
266
|
}
|
|
136
267
|
}
|
|
137
268
|
|
|
138
|
-
const
|
|
269
|
+
const taskSpecs = [];
|
|
139
270
|
|
|
140
271
|
if (eslintFiles.length > 0) {
|
|
141
|
-
|
|
272
|
+
taskSpecs.push([getBinName('npx'), ['eslint', '--quiet', ...eslintFiles]]);
|
|
142
273
|
}
|
|
143
274
|
|
|
144
275
|
if (stylelintFiles.length > 0 && !canRunStylelint()) {
|
|
145
|
-
console.warn(
|
|
276
|
+
console.warn(
|
|
277
|
+
'[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
|
|
278
|
+
);
|
|
146
279
|
} else if (stylelintFiles.length > 0) {
|
|
147
|
-
|
|
280
|
+
taskSpecs.push([
|
|
281
|
+
getBinName('npx'),
|
|
282
|
+
['stylelint', '--quiet', ...stylelintFiles],
|
|
283
|
+
]);
|
|
148
284
|
}
|
|
149
285
|
|
|
150
286
|
if (clientTypeFiles.length > 0) {
|
|
151
|
-
|
|
287
|
+
taskSpecs.push([getBinName('npm'), ['run', 'type:check:client']]);
|
|
152
288
|
}
|
|
153
289
|
|
|
154
290
|
if (serverTypeFiles.length > 0) {
|
|
155
|
-
|
|
291
|
+
taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
|
|
156
292
|
}
|
|
157
293
|
|
|
158
|
-
if (
|
|
294
|
+
if (checkUi) taskSpecs.push(...uiTasks());
|
|
295
|
+
|
|
296
|
+
if (taskSpecs.length === 0) {
|
|
159
297
|
console.log('[lint] No supported files matched for lint');
|
|
160
298
|
process.exit(0);
|
|
161
299
|
}
|
|
162
300
|
|
|
163
|
-
|
|
164
|
-
process.exit(results.some(code => code !== 0) ? 1 : 0);
|
|
301
|
+
process.exit(await runTasksSerially(taskSpecs));
|
|
165
302
|
}
|
|
166
303
|
|
|
167
304
|
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 不可与其他检查模式混用');
|
|
168
315
|
const files = parseFilesArg(process.argv.slice(2));
|
|
169
316
|
if (files === null) {
|
|
170
317
|
await runDefaultLint();
|
|
@@ -179,7 +326,7 @@ async function main() {
|
|
|
179
326
|
await runSelectiveLint(files);
|
|
180
327
|
}
|
|
181
328
|
|
|
182
|
-
main().catch(
|
|
329
|
+
main().catch(error => {
|
|
183
330
|
const message = error instanceof Error ? error.message : String(error);
|
|
184
331
|
console.error(`[lint] Failed to run lint: ${message}`);
|
|
185
332
|
process.exit(1);
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
}
|
|
@@ -13,5 +13,6 @@
|
|
|
13
13
|
"excludeDirectories": ["node_modules/**"]
|
|
14
14
|
},
|
|
15
15
|
"include": ["server/**/*", "shared/**/*.ts"],
|
|
16
|
-
|
|
16
|
+
// MCP UI 是独立浏览器工程,不使用服务端的 TypeScript 配置。
|
|
17
|
+
"exclude": ["server/mcp/ui", "node_modules", "dist", "client", "source_package", "**/*.spec.ts", "**/*.e2e-spec.ts"],
|
|
17
18
|
}
|