@lark-apaas/fullstack-cli 1.1.67-alpha.20260917143103 → 1.1.67-alpha.20260918073527

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.20260917143103",
3
+ "version": "1.1.67-alpha.20260918073527",
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,8 +1,8 @@
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
 
@@ -11,14 +11,14 @@ function getBinName(name) {
11
11
  }
12
12
 
13
13
  function runCommand(command, args) {
14
- return new Promise(resolve => {
14
+ return new Promise((resolve) => {
15
15
  const child = spawn(command, args, {
16
16
  cwd,
17
17
  stdio: 'inherit',
18
18
  shell: false,
19
19
  });
20
20
 
21
- child.on('close', code => resolve(code ?? 1));
21
+ child.on('close', (code) => resolve(code || 0));
22
22
  child.on('error', () => resolve(1));
23
23
  });
24
24
  }
@@ -36,25 +36,18 @@ async function runTasksSerially(taskSpecs) {
36
36
  return exitCode;
37
37
  }
38
38
 
39
- function normalizeProjectFile(filePath, checkDeleted = false) {
39
+ function normalizeProjectFile(filePath) {
40
40
  const absolutePath = path.isAbsolute(filePath)
41
41
  ? filePath
42
42
  : path.resolve(cwd, filePath);
43
43
 
44
44
  if (!fs.existsSync(absolutePath)) {
45
- if (!checkDeleted) {
46
- console.warn(`[lint] Skip missing file: ${filePath}`);
47
- return null;
48
- }
49
- console.log(`[lint] Deleted file: ${filePath}; checking affected projects`);
45
+ console.warn(`[lint] Skip missing file: ${filePath}`);
46
+ return null;
50
47
  }
51
48
 
52
49
  const relativePath = path.relative(cwd, absolutePath);
53
- if (
54
- relativePath === '..' ||
55
- relativePath.startsWith(`..${path.sep}`) ||
56
- path.isAbsolute(relativePath)
57
- ) {
50
+ if (relativePath.startsWith('..')) {
58
51
  console.warn(`[lint] Skip file outside project: ${filePath}`);
59
52
  return null;
60
53
  }
@@ -111,81 +104,7 @@ function canRunStylelint() {
111
104
 
112
105
  if (!pkg.scripts || !pkg.scripts.stylelint) return false;
113
106
 
114
- return STYLELINT_CONFIG_FILES.some(file =>
115
- fs.existsSync(path.join(cwd, file))
116
- );
117
- }
118
-
119
- const UI_DIR = 'server/mcp/ui';
120
-
121
- function uiSourceFiles() {
122
- const files = [];
123
- function visit(relative) {
124
- const absolute = path.join(cwd, relative);
125
- if (!fs.existsSync(absolute)) return;
126
- if (fs.lstatSync(absolute).isSymbolicLink()) {
127
- throw new Error(`MCP UI 检查不支持符号链接: ${relative}`);
128
- }
129
- for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
130
- if (['node_modules', 'dist', '.git'].includes(entry.name)) continue;
131
- const file = `${relative}/${entry.name}`;
132
- if (entry.isSymbolicLink())
133
- throw new Error(`MCP UI 检查不支持符号链接: ${file}`);
134
- if (entry.isDirectory()) visit(file);
135
- else if (isEslintTarget(file) && !/\.d\.(?:ts|mts|cts)$/.test(file))
136
- files.push(file);
137
- }
138
- }
139
- visit(UI_DIR);
140
- return files.sort();
141
- }
142
-
143
- function uiTasks({ types = true, eslint = true, aggregate = false } = {}) {
144
- const sources = uiSourceFiles();
145
- if (!sources.length) return [];
146
- const tasks = [];
147
- // 只在有 UI 源码时读取聚合命令;普通应用不依赖 MCP 配置或脚本。
148
- if (types && aggregate) {
149
- const scripts = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')).scripts || {};
150
- types = !/\bnpm\s+run\s+type:check:mcp-ui(?=[\s"';&|)]|$)/.test(scripts['type:check'] || '');
151
- }
152
- if (types) tasks.push([getBinName('npm'), ['run', 'type:check:mcp-ui']]);
153
- if (eslint) {
154
- const config = [
155
- 'eslint.mcp-ui.config.js',
156
- 'eslint.mcp-ui.config.cjs',
157
- 'eslint.mcp-ui.config.mjs',
158
- ].find(file => fs.existsSync(path.join(cwd, file)));
159
- let resolvedConfig = config;
160
- if (!resolvedConfig) {
161
- try {
162
- resolvedConfig =
163
- require.resolve('@lark-apaas/fullstack-presets/lib/mcp/eslint-ui.js');
164
- } catch {
165
- throw new Error(
166
- '请升级 @lark-apaas/fullstack-presets 以使用 MCP UI 默认 ESLint 配置,或提供 eslint.mcp-ui.config.cjs'
167
- );
168
- }
169
- }
170
- tasks.push([
171
- getBinName('npx'),
172
- [
173
- '--no-install',
174
- 'eslint',
175
- '--config',
176
- resolvedConfig,
177
- '--quiet',
178
- '--max-warnings',
179
- '0',
180
- ...sources,
181
- ],
182
- ]);
183
- }
184
- return tasks;
185
- }
186
-
187
- function isUiFile(file) {
188
- return file.startsWith('server/mcp/ui/');
107
+ return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
189
108
  }
190
109
 
191
110
  async function runDefaultLint() {
@@ -194,23 +113,18 @@ async function runDefaultLint() {
194
113
  [getBinName('npm'), ['run', 'type:check']],
195
114
  ];
196
115
 
197
- taskSpecs.push(...uiTasks({ aggregate: true }));
198
-
199
116
  if (canRunStylelint()) {
200
117
  taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
201
118
  } else {
202
- console.warn(
203
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
204
- );
119
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
205
120
  }
206
121
 
207
122
  process.exit(await runTasksSerially(taskSpecs));
208
123
  }
209
124
 
210
125
  async function runSelectiveLint(inputFiles) {
211
- const hasUiSources = uiSourceFiles().length > 0;
212
126
  const normalizedFiles = Array.from(
213
- new Set(inputFiles.map(file => normalizeProjectFile(file, hasUiSources)).filter(Boolean))
127
+ new Set(inputFiles.map(normalizeProjectFile).filter(Boolean)),
214
128
  );
215
129
 
216
130
  if (normalizedFiles.length === 0) {
@@ -218,40 +132,15 @@ async function runSelectiveLint(inputFiles) {
218
132
  process.exit(0);
219
133
  }
220
134
 
221
- const eslintFiles = normalizedFiles.filter(
222
- file =>
223
- fs.existsSync(path.join(cwd, file)) &&
224
- isEslintTarget(file) &&
225
- (!hasUiSources || (!isUiFile(file) && !file.startsWith('eslint.mcp-ui.config.')))
226
- );
227
- const stylelintFiles = normalizedFiles.filter(
228
- file => fs.existsSync(path.join(cwd, file)) && isStylelintTarget(file)
229
- );
135
+ const eslintFiles = normalizedFiles.filter(isEslintTarget);
136
+ const stylelintFiles = normalizedFiles.filter(isStylelintTarget);
230
137
  const typeCheckFiles = normalizedFiles.filter(isTypeCheckTarget);
231
138
 
232
139
  const clientTypeFiles = [];
233
140
  const serverTypeFiles = [];
234
- const rootConfigChanged = hasUiSources && normalizedFiles.some(file =>
235
- /^tsconfig(?:\.[^/]*)?\.json$/.test(file)
236
- );
237
- if (rootConfigChanged) {
238
- clientTypeFiles.push('config');
239
- serverTypeFiles.push('config');
240
- }
241
- let checkUi =
242
- rootConfigChanged ||
243
- normalizedFiles.some(
244
- file =>
245
- isUiFile(file) ||
246
- file.startsWith('shared/') ||
247
- file.startsWith('eslint.mcp-ui.config.') ||
248
- file === 'package.json'
249
- );
250
141
 
251
142
  for (const filePath of typeCheckFiles) {
252
- if (hasUiSources && isUiFile(filePath)) {
253
- checkUi = true;
254
- } else if (filePath.startsWith('client/')) {
143
+ if (filePath.startsWith('client/')) {
255
144
  clientTypeFiles.push(filePath);
256
145
  } else if (filePath.startsWith('server/')) {
257
146
  serverTypeFiles.push(filePath);
@@ -268,14 +157,9 @@ async function runSelectiveLint(inputFiles) {
268
157
  }
269
158
 
270
159
  if (stylelintFiles.length > 0 && !canRunStylelint()) {
271
- console.warn(
272
- '[lint] Skip stylelint: missing scripts.stylelint or stylelint config'
273
- );
160
+ console.warn('[lint] Skip stylelint: missing scripts.stylelint or stylelint config');
274
161
  } else if (stylelintFiles.length > 0) {
275
- taskSpecs.push([
276
- getBinName('npx'),
277
- ['stylelint', '--quiet', ...stylelintFiles],
278
- ]);
162
+ taskSpecs.push([getBinName('npx'), ['stylelint', '--quiet', ...stylelintFiles]]);
279
163
  }
280
164
 
281
165
  if (clientTypeFiles.length > 0) {
@@ -286,8 +170,6 @@ async function runSelectiveLint(inputFiles) {
286
170
  taskSpecs.push([getBinName('npm'), ['run', 'type:check:server']]);
287
171
  }
288
172
 
289
- if (checkUi) taskSpecs.push(...uiTasks());
290
-
291
173
  if (taskSpecs.length === 0) {
292
174
  console.log('[lint] No supported files matched for lint');
293
175
  process.exit(0);
@@ -297,16 +179,6 @@ async function runSelectiveLint(inputFiles) {
297
179
  }
298
180
 
299
181
  async function main() {
300
- const args = process.argv.slice(2);
301
- if (args.length === 1 && args[0] === '--eslint-mcp-ui') {
302
- process.exit(await runTasksSerially(uiTasks({ types: false })));
303
- return;
304
- }
305
- if (args.length && !(args[0] === '--files' && args.length > 1)) {
306
- throw new Error('用法: lint.js [--files <paths...>]');
307
- }
308
- if (args[0] === '--files' && args.slice(1).some(arg => arg.startsWith('--')))
309
- throw new Error('--files 不可与其他检查模式混用');
310
182
  const files = parseFilesArg(process.argv.slice(2));
311
183
  if (files === null) {
312
184
  await runDefaultLint();
@@ -321,7 +193,7 @@ async function main() {
321
193
  await runSelectiveLint(files);
322
194
  }
323
195
 
324
- main().catch(error => {
196
+ main().catch((error) => {
325
197
  const message = error instanceof Error ? error.message : String(error);
326
198
  console.error(`[lint] Failed to run lint: ${message}`);
327
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
- }