@lark-apaas/fullstack-cli 1.1.67-alpha.20260916155507 → 1.1.67-alpha.20260917070101

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/dist/index.js CHANGED
@@ -2587,14 +2587,29 @@ function buildDefaultRules(opts) {
2587
2587
  { onlyNestjs: true, type: "eslint-ignore", to: "eslint.config.js", pattern: "server/mcp/ui/**" },
2588
2588
  { onlyNestjs: true, type: "eslint-ignore", to: "eslint.config.mjs", pattern: "server/mcp/ui/**" },
2589
2589
  { onlyNestjs: true, type: "eslint-ignore", to: "eslint.config.cjs", pattern: "server/mcp/ui/**" },
2590
- { onlyNestjs: true, type: "add-script", name: "type:check:mcp-ui", command: "node ./scripts/lint.js --typecheck-mcp-ui", overwrite: false },
2591
- { onlyNestjs: true, type: "add-script", name: "eslint:mcp-ui", command: "node ./scripts/lint.js --eslint-mcp-ui", overwrite: false },
2590
+ { onlyNestjs: true, type: "add-script", name: "type:check:mcp-ui", command: "node ./scripts/check.cjs --typecheck-mcp-ui", overwrite: false },
2591
+ { onlyNestjs: true, type: "add-script", name: "eslint:mcp-ui", command: "node ./scripts/check.cjs --eslint-mcp-ui", overwrite: false },
2592
+ {
2593
+ onlyNestjs: true,
2594
+ type: "patch-script",
2595
+ name: "type:check:mcp-ui",
2596
+ to: "node ./scripts/check.cjs --typecheck-mcp-ui",
2597
+ ifEquals: ["node ./scripts/lint.js --typecheck-mcp-ui"]
2598
+ },
2599
+ {
2600
+ onlyNestjs: true,
2601
+ type: "patch-script",
2602
+ name: "eslint:mcp-ui",
2603
+ to: "node ./scripts/check.cjs --eslint-mcp-ui",
2604
+ ifEquals: ["node ./scripts/lint.js --eslint-mcp-ui"]
2605
+ },
2592
2606
  {
2593
2607
  onlyNestjs: true,
2594
2608
  type: "patch-script",
2595
2609
  name: "type:check",
2596
- to: "node ./scripts/lint.js --typecheck",
2610
+ to: "node ./scripts/check.cjs --typecheck",
2597
2611
  ifEquals: [
2612
+ "node ./scripts/lint.js --typecheck",
2598
2613
  'concurrently -n "server,client" -c "blue,green" "npm run type:check:server" "npm run type:check:client"',
2599
2614
  'concurrently "npm run type:check:server" "npm run type:check:client"',
2600
2615
  'concurrently "npm run type:check:client" "npm run type:check:server"'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.1.67-alpha.20260916155507",
3
+ "version": "1.1.67-alpha.20260917070101",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,123 @@
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
+ }
@@ -1,41 +1,10 @@
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');
6
5
 
7
6
  const cwd = process.cwd();
8
7
 
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
8
  function normalizeProjectFile(filePath) {
40
9
  const absolutePath = path.isAbsolute(filePath)
41
10
  ? filePath
@@ -106,75 +75,9 @@ function canRunStylelint() {
106
75
  return STYLELINT_CONFIG_FILES.some(file => fs.existsSync(path.join(cwd, file)));
107
76
  }
108
77
 
109
- const UI_DIR = 'server/mcp/ui';
110
- const UI_TYPECHECK = 'node ./scripts/lint.js --typecheck';
111
-
112
- function isUiFile(file) {
113
- return file.startsWith(`${UI_DIR}/`);
114
- }
115
-
116
- function uiSourceFiles() {
117
- const files = [];
118
- function visit(relative) {
119
- const absolute = path.join(cwd, relative);
120
- if (!fs.existsSync(absolute)) return;
121
- if (fs.lstatSync(absolute).isSymbolicLink()) {
122
- throw new Error(`MCP UI 检查不支持符号链接: ${relative}`);
123
- }
124
- for (const entry of fs.readdirSync(absolute, { withFileTypes: true })) {
125
- if (['node_modules', 'dist', '.git'].includes(entry.name)) continue;
126
- const file = `${relative}/${entry.name}`;
127
- if (entry.isSymbolicLink()) throw new Error(`MCP UI 检查不支持符号链接: ${file}`);
128
- if (entry.isDirectory()) visit(file);
129
- else if (isEslintTarget(file) && !/\.d\.(?:ts|mts|cts)$/.test(file)) files.push(file);
130
- }
131
- }
132
- visit(UI_DIR);
133
- return files.sort();
134
- }
135
-
136
- function uiTasks({ types = true, eslint = true } = {}) {
137
- const sources = uiSourceFiles();
138
- if (!sources.length) return [];
139
- const tasks = [];
140
- if (types) {
141
- const config = `${UI_DIR}/tsconfig.json`;
142
- if (!fs.existsSync(path.join(cwd, config))) {
143
- throw new Error(`MCP UI 缺少 ${config},请按 mcp-guide 补齐独立浏览器 TypeScript 配置。`);
144
- }
145
- const ts = require('typescript');
146
- const parsed = ts.getParsedCommandLineOfConfigFile(path.join(cwd, config), {}, {
147
- ...ts.sys,
148
- onUnRecoverableConfigFileDiagnostic: d => { throw new Error(ts.flattenDiagnosticMessageText(d.messageText, '\n')); },
149
- });
150
- if (!parsed || parsed.errors.length) throw new Error(`MCP UI TypeScript 配置无效: ${config}`);
151
- const included = new Set(parsed.fileNames.map(file => path.resolve(file)));
152
- const missing = sources.filter(file => !included.has(path.resolve(cwd, file)));
153
- if (missing.length) throw new Error(`MCP UI TypeScript 配置未覆盖源码: ${missing.join(', ')};请检查 include/exclude,JS 源码需开启 allowJs。`);
154
- tasks.push([getBinName('npx'), ['--no-install', 'tsc', '--noEmit', '-p', config]]);
155
- }
156
- if (eslint) {
157
- const config = ['eslint.mcp-ui.config.js', 'eslint.mcp-ui.config.cjs', 'eslint.mcp-ui.config.mjs']
158
- .find(file => fs.existsSync(path.join(cwd, file)));
159
- if (!config) throw new Error('MCP UI 缺少独立 ESLint 配置,请按 mcp-guide 创建 eslint.mcp-ui.config.cjs。');
160
- tasks.push([getBinName('npx'), ['--no-install', 'eslint', '--config', config, '--quiet', '--max-warnings', '0', ...sources]]);
161
- }
162
- return tasks;
163
- }
164
-
165
- function packageScripts() {
166
- return JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8')).scripts || {};
167
- }
168
-
169
- async function runTypeCheck() {
170
- const scripts = packageScripts();
171
- const tasks = ['type:check:server', 'type:check:client'].map(name => {
172
- if (!scripts[name]) throw new Error(`缺少 scripts.${name},请保留应用原有类型检查命令。`);
173
- return [getBinName('npm'), ['run', name]];
174
- });
175
- tasks.push(...uiTasks({ eslint: false }));
176
- process.exit(await runTasksSerially(tasks));
177
- }
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/'); }
178
81
 
179
82
  async function runDefaultLint() {
180
83
  const taskSpecs = [
@@ -183,7 +86,7 @@ async function runDefaultLint() {
183
86
  ];
184
87
 
185
88
  // Customized type:check remains untouched; ensure full lint still checks UI.
186
- taskSpecs.push(...uiTasks({ types: packageScripts()['type:check'] !== UI_TYPECHECK }));
89
+ taskSpecs.push(...uiTasks({ types: !UI_TYPECHECKS.includes(packageScripts()['type:check']) }));
187
90
 
188
91
  if (canRunStylelint()) {
189
92
  taskSpecs.push([getBinName('npm'), ['run', 'stylelint']]);
@@ -264,9 +167,8 @@ async function main() {
264
167
  throw new Error('用法: lint.js [--files <paths...> | --typecheck | --typecheck-mcp-ui | --eslint-mcp-ui]');
265
168
  }
266
169
  if (args[0] === '--files' && args.slice(1).some(arg => arg.startsWith('--'))) throw new Error('--files 不可与其他检查模式混用');
267
- if (args.includes('--typecheck')) return runTypeCheck();
268
- if (args.includes('--typecheck-mcp-ui')) return process.exit(await runTasksSerially(uiTasks({ eslint: false })));
269
- if (args.includes('--eslint-mcp-ui')) return process.exit(await runTasksSerially(uiTasks({ types: false })));
170
+ // 兼容旧入口,只向独立检查器转发;新的 npm scripts 不再指向 lint.js。
171
+ if (modes.includes(args[0])) return runChecks(args);
270
172
  const files = parseFilesArg(process.argv.slice(2));
271
173
  if (files === null) {
272
174
  await runDefaultLint();