diff --git a/scripts/vsh/src/index.ts b/scripts/vsh/src/index.ts index ed9eb6c9b..00f711feb 100644 --- a/scripts/vsh/src/index.ts +++ b/scripts/vsh/src/index.ts @@ -6,7 +6,7 @@ import { version } from '../package.json'; import { defineCheckCircularCommand } from './check-circular'; import { defineCheckDepCommand } from './check-dep'; import { defineCodeWorkspaceCommand } from './code-workspace'; -import { defineLintCommand } from './lint'; +import { defineLintCommand, LintError } from './lint'; import { definePubLintCommand } from './publint'; // 命令描述 @@ -57,6 +57,12 @@ async function main(): Promise { await vsh.runMatchedCommand(); } catch (error) { + // lint 检查失败是预期内的结果(格式/规范不达标), + // 直接展示可操作的错误信息,而非笼统的 "unexpected error" + if (error instanceof LintError) { + consola.error(error.message); + process.exit(1); + } consola.error( colors.red('An unexpected error occurred:'), '\n', diff --git a/scripts/vsh/src/lint/index.ts b/scripts/vsh/src/lint/index.ts index e19c8f6fb..6ec9ecf63 100644 --- a/scripts/vsh/src/lint/index.ts +++ b/scripts/vsh/src/lint/index.ts @@ -1,9 +1,8 @@ import type { CAC } from 'cac'; -import { execSync } from 'node:child_process'; import { availableParallelism, freemem } from 'node:os'; -import { execa } from '@vben/node-utils'; +import { colors, execa } from '@vben/node-utils'; interface LintCommandOptions { /** @@ -48,6 +47,21 @@ function formatCommand([file, args]: Command) { return [file, ...args].join(' '); } +/** + * 统一的 lint 失败错误。 + * 相比 execa 的原始错误,会列出所有失败命令并给出修复提示。 + */ +class LintError extends Error { + constructor(failed: Command[]) { + super( + `Lint failed:\n${failed + .map((command) => ` - ${formatCommand(command)}`) + .join('\n')}\n\n运行 ${colors.cyan('vsh lint --format')} 可自动修复。`, + ); + this.name = 'LintError'; + } +} + /** * 串行执行所有命令:一次只运行一个进程。 * 保证一次能看到所有工具的报错(配置低的机器更友好)。 @@ -64,39 +78,29 @@ async function runSerial(commands: Command[]) { } if (failed.length > 0) { - throw new Error( - `Lint failed:\n${failed - .map((command) => ` - ${formatCommand(command)}`) - .join('\n')}`, - ); + throw new LintError(failed); } } /** * 并行执行所有命令:同时启动全部进程。 - * 任一进程失败时,强制结束其余仍在运行的进程,避免产生遗漏进程。 + * 等待全部结束后汇总失败命令,与串行模式保持一致的错误输出。 */ async function runParallel(commands: Command[]) { const subprocesses = commands.map((command) => runCommand(command)); - try { - await Promise.all(subprocesses); - } catch (error) { - for (const subprocess of subprocesses) { - try { - if (process.platform === 'win32' && subprocess.pid) { - execSync(`taskkill /F /T /PID ${subprocess.pid}`, { - stdio: 'ignore', - }); - } else { - subprocess.kill('SIGKILL'); - } - } catch { - // process may have already exited - } + const results = await Promise.allSettled(subprocesses); + + // 汇总所有失败的命令,与串行模式保持一致的错误输出 + const failed: Command[] = []; + results.forEach((result, index) => { + if (result.status === 'rejected') { + failed.push(commands[index]); } - await Promise.allSettled(subprocesses); - throw error; + }); + + if (failed.length > 0) { + throw new LintError(failed); } } @@ -143,4 +147,4 @@ function defineLintCommand(cac: CAC) { .action(runLint); } -export { defineLintCommand }; +export { defineLintCommand, LintError };