@lark-apaas/fullstack-cli 1.1.67-alpha.20260917134435 → 1.1.67-alpha.20260917183015

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
@@ -399,10 +399,10 @@ MIT
399
399
 
400
400
  ## MCP UI 检查与存量配置同步
401
401
 
402
- MCP UI 使用平台预设提供的浏览器检查默认值。`npm run lint` 和 `npm run type:check` 自动覆盖 `server/mcp/ui`,模板统一提供 UI 子命令与类型配置;`lint -- --files` 保留按文件选择检查范围。没有 UI 源码时跳过 UI 检查。
402
+ 创建 MCP UI 时,Agent 在 `server/mcp/ui` 中同时生成 `tsconfig.json` 和 `eslint.config.cjs`,修改 UI 或其 shared 依赖后主动运行独立检查。模板和 CLI 不生成 UI 配置,也不将 UI 检查接入主工程 lint/type:check。
403
403
 
404
- 应用增加 `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。
404
+ 主工程保留原 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 命令。
405
405
 
406
- UI ESLint 默认配置由平台预设提供,也可用根目录 `eslint.mcp-ui.config.js` / `.cjs` / `.mjs` 定制。`npm run lint` 包含 UI 检查;`--files` 按影响范围检查,shared 变更触发三个环境的类型检查。应用脚本不使用 `node -e` 或 TypeScript 编译器 API。
406
+ CLI 不注入 MCP UI 检查命令;主工程保留原有 client/server 检查,UI 由 Agent 按独立配置执行标准 tsc/eslint。
407
407
 
408
408
  存量 sync 不改写 ESLint 源码;UI 全局排除项由平台 ESLint 预设提供。TS 仅向已有显式 `exclude` 数组追加目录,保留注释和原项;依赖继承且未显式声明 `exclude` 的自定义配置保持不变并提示人工调整。自定义检查命令保留。lint.js 和 build.sh 仍由平台脚本模板维护,不依赖预设包中的脚本执行入口。
package/dist/index.js CHANGED
@@ -2581,38 +2581,9 @@ function buildDefaultRules(opts) {
2581
2581
  type: "file",
2582
2582
  overwrite: true
2583
2583
  },
2584
- // UI 留在 server/mcp/ui,但使用平台默认浏览器检查配置。
2584
+ // UI 位于 server/mcp/ui,检查配置由 Agent 随 UI 代码创建。
2585
2585
  // 不生成 UI 目录;仅给已有 Nest 工程增量补服务端排除项。
2586
2586
  { onlyNestjs: true, type: "typescript-exclude", to: "tsconfig.node.json", path: "server/mcp/ui" },
2587
- { onlyNestjs: true, type: "file", from: "templates/tsconfig.mcp-ui.json", to: "tsconfig.mcp-ui.json", overwrite: false },
2588
- { onlyNestjs: true, type: "add-script", name: "type:check:mcp-ui", command: "node ./scripts/typecheck-mcp-ui.js", overwrite: false },
2589
- {
2590
- onlyNestjs: true,
2591
- type: "patch-script",
2592
- name: "type:check:mcp-ui",
2593
- to: "node ./scripts/typecheck-mcp-ui.js",
2594
- ifEquals: ["node ./scripts/check.cjs --typecheck-mcp-ui", "node ./scripts/lint.js --typecheck-mcp-ui"]
2595
- },
2596
- {
2597
- onlyNestjs: true,
2598
- type: "patch-script",
2599
- name: "type:check",
2600
- 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"',
2601
- ifEquals: [
2602
- "node ./scripts/check.cjs --typecheck",
2603
- "node ./scripts/lint.js --typecheck",
2604
- 'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
2605
- 'concurrently "npm run type:check:server" "npm run type:check:client"',
2606
- 'concurrently "npm run type:check:client" "npm run type:check:server"'
2607
- ]
2608
- },
2609
- {
2610
- onlyNestjs: true,
2611
- type: "patch-script",
2612
- name: "eslint",
2613
- to: "eslint . --quiet",
2614
- ifEquals: ['eslint . --quiet --ignore-pattern "server/mcp/ui/**"']
2615
- },
2616
2587
  // 9. 把模板版本的 lint 脚本替换为支持 --files 的 runner
2617
2588
  // 只识别平台模板生成的 `concurrently ...` 形态,用户真正改写过的脚本保持原样
2618
2589
  {
@@ -2906,7 +2877,7 @@ async function syncRule(rule, pluginRoot, userProjectRoot) {
2906
2877
  }
2907
2878
  if (rule.type === "patch-script") {
2908
2879
  const packageJsonPath = path6.join(userProjectRoot, "package.json");
2909
- patchScript(packageJsonPath, rule.name, rule.to, rule.ifStartsWith, rule.ifEquals);
2880
+ patchScript(packageJsonPath, rule.name, rule.to, rule.ifStartsWith);
2910
2881
  return;
2911
2882
  }
2912
2883
  if (rule.type === "add-line") {
@@ -3046,7 +3017,7 @@ function addScript(packageJsonPath, name, command, overwrite) {
3046
3017
  fs9.writeFileSync(packageJsonPath, JSON.stringify(pkg2, null, 2) + "\n");
3047
3018
  console.log(`[fullstack-cli] \u2713 scripts.${name}`);
3048
3019
  }
3049
- function patchScript(packageJsonPath, name, to, ifStartsWith, ifEquals) {
3020
+ function patchScript(packageJsonPath, name, to, ifStartsWith) {
3050
3021
  if (!fs9.existsSync(packageJsonPath)) {
3051
3022
  console.log(`[fullstack-cli] \u25CB package.json (not found)`);
3052
3023
  return;
@@ -3063,7 +3034,7 @@ function patchScript(packageJsonPath, name, to, ifStartsWith, ifEquals) {
3063
3034
  console.log(`[fullstack-cli] \u25CB scripts.${name} (already patched)`);
3064
3035
  return;
3065
3036
  }
3066
- if (!(ifEquals ? ifEquals.includes(current) : ifStartsWith && current.startsWith(ifStartsWith))) {
3037
+ if (!current.startsWith(ifStartsWith)) {
3067
3038
  console.warn(
3068
3039
  `[fullstack-cli] \u26A0 Skipped patching scripts.${name} because it has been customized`
3069
3040
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.67-alpha.20260917134435",
3
+ "version": "1.1.67-alpha.20260917183015",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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,53 @@
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
+
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
+
9
39
  function normalizeProjectFile(filePath) {
10
40
  const absolutePath = path.isAbsolute(filePath)
11
41
  ? filePath
12
42
  : path.resolve(cwd, filePath);
13
43
 
14
44
  if (!fs.existsSync(absolutePath)) {
15
- console.log(`[lint] Deleted file: ${filePath}; checking affected projects`);
45
+ console.warn(`[lint] Skip missing file: ${filePath}`);
46
+ return null;
16
47
  }
17
48
 
18
49
  const relativePath = path.relative(cwd, absolutePath);
19
- if (
20
- relativePath === '..' ||
21
- relativePath.startsWith(`..${path.sep}`) ||
22
- path.isAbsolute(relativePath)
23
- ) {
50
+ if (relativePath.startsWith('..')) {
24
51
  console.warn(`[lint] Skip file outside project: ${filePath}`);
25
52
  return null;
26
53
  }
@@ -77,113 +104,7 @@ function canRunStylelint() {
77
104
 
78
105
  if (!pkg.scripts || !pkg.scripts.stylelint) return false;
79
106
 
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/');
107
+ return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
187
108
  }
188
109
 
189
110
  async function runDefaultLint() {
@@ -192,21 +113,10 @@ async function runDefaultLint() {
192
113
  [getBinName('npm'), ['run', 'type:check']],
193
114
  ];
194
115
 
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
- );
203
-
204
116
  if (canRunStylelint()) {
205
117
  taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
206
118
  } else {
207
- console.warn(
208
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
209
- );
119
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
210
120
  }
211
121
 
212
122
  process.exit(await runTasksSerially(taskSpecs));
@@ -214,7 +124,7 @@ async function runDefaultLint() {
214
124
 
215
125
  async function runSelectiveLint(inputFiles) {
216
126
  const normalizedFiles = Array.from(
217
- new Set(inputFiles.map(normalizeProjectFile).filter(Boolean))
127
+ new Set(inputFiles.map(normalizeProjectFile).filter(Boolean)),
218
128
  );
219
129
 
220
130
  if (normalizedFiles.length === 0) {
@@ -222,41 +132,15 @@ async function runSelectiveLint(inputFiles) {
222
132
  process.exit(0);
223
133
  }
224
134
 
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
- );
135
+ const eslintFiles = normalizedFiles.filter(isEslintTarget);
136
+ const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
235
137
  const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
236
138
 
237
139
  const clientTypeFiles = [];
238
140
  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
141
 
256
142
  for (const filePath of typeCheckFiles) {
257
- if (isUiFile(filePath)) {
258
- checkUi = true;
259
- } else if (filePath.startsWith('client/')) {
143
+ if (filePath.startsWith('client/')) {
260
144
  clientTypeFiles.push(filePath);
261
145
  } else if (filePath.startsWith('server/')) {
262
146
  serverTypeFiles.push(filePath);
@@ -273,14 +157,9 @@ async function runSelectiveLint(inputFiles) {
273
157
  }
274
158
 
275
159
  if (stylelintFiles.length > 0 && !canRunStylelint()) {
276
- console.warn(
277
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
278
- );
160
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
279
161
  } else if (stylelintFiles.length > 0) {
280
- taskSpecs.push([
281
- getBinName('npx'),
282
- ['stylelint', '--quiet', ...stylelintFiles],
283
- ]);
162
+ taskSpecs.push([getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]]);
284
163
  }
285
164
 
286
165
  if (clientTypeFiles.length > 0) {
@@ -291,8 +170,6 @@ async function runSelectiveLint(inputFiles) {
291
170
  taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
292
171
  }
293
172
 
294
- if (checkUi) taskSpecs.push(...uiTasks());
295
-
296
173
  if (taskSpecs.length === 0) {
297
174
  console.log('[lint] No supported files matched for lint');
298
175
  process.exit(0);
@@ -302,16 +179,6 @@ async function runSelectiveLint(inputFiles) {
302
179
  }
303
180
 
304
181
  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
182
  const files = parseFilesArg(process.argv.slice(2));
316
183
  if (files === null) {
317
184
  await runDefaultLint();
@@ -326,7 +193,7 @@ async function main() {
326
193
  await runSelectiveLint(files);
327
194
  }
328
195
 
329
- main().catch(error => {
196
+ main().catch((error) => {
330
197
  const message = error instanceof Error ? error.message : String(error);
331
198
  console.error(`[lint] Failed to run lint: ${message}`);
332
199
  process.exit(1);
@@ -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
- }