import { execSync } from 'node:child_process'; export const TaskCommandType = ['npm-install'] as const; export type TaskCommand = { key?: string; /** * 任务描述 */ description?: string; /** * 命令, 执行的任务 */ command: string; type?: (typeof TaskCommandType)[number] | string; /** * 任务执行完成后,执行判断的命令 */ after?: string; /** * 任务执行前,执行判断的命令 */ before?: string; /** * 任务执行完成后, 检测输出的文本内容,如果有这个文本,表示任务执行成功 * 如果没有这个文本,表示任务执行失败 */ afterCheck?: string; /** * 任务执行前, 检测输出的文本内容,如果有这个文本,表示任务已经安装 * 如果没有这个文本,表示任务没有安装 */ beforeCheck?: string; }; export class TasksCommand { tasks: Map = new Map(); constructor() {} addTask(task: TaskCommand, run?: boolean) { const key = task?.key || task?.description; if (!key) { throw new Error('当前的任务没有key'); } this.tasks.set(key, task); if (run) { this.runTask(key); } return this; } getTask(name: string) { return this.tasks.get(name); } runTask(name: string) { const task = this.getTask(name); const end = (data?: { code: number; [key: string]: any }) => { return { ...data, task, }; }; if (!task) { return { code: 500, message: `没有找到 ${name} 这个任务`, }; } let { command, before, after, afterCheck, beforeCheck, type } = task; if (type === 'npm-install' && !afterCheck) { afterCheck = 'added'; } if (before) { const res = this.runCommand(before); console.log('before', res, beforeCheck, this.checkForContainue(res, beforeCheck)); if (!this.checkForContainue(res, beforeCheck)) { return end({ code: 200, message: `当前任务不需要执行, ${command}`, }); } } const res = this.runCommand(command); console.log('runCommand', res); if (res.code !== 200) { return end(res); } let checkText = res.data || ''; if (after) { const res = this.runCommand(after); if (res.code !== 200) { return end(res); } checkText = res.data || ''; } if (afterCheck) { const isSuccess = checkText?.includes?.(afterCheck); return end({ code: isSuccess ? 200 : 500, output: res.data, check: afterCheck, message: isSuccess ? `当前任务执行成功, ${command}` : `当前任务执行失败, ${command}`, }); } return end(res); } /** * 检测是否需要继续执行。 * 1. res.code === 500 代表执行失败, 需要继续执行 * 2. res.code === 200 代表执行成功,但是需要检测输出内容 * 2.1 如果有 check 的内容,代表不需要继续执行 * 2.2 如果没有 check 的内容,代表需要继续执行 * @param res * @param check * @returns */ private checkForContainue(res: { data: string; code: number; [key: string]: any }, check?: string, isBefore = true) { if (res.code !== 200) { return true; } if (!check) { return true; } const hasIncludes = res.data?.includes?.(check); if (isBefore) { // 代表已经安装, 不需要继续执行 return !hasIncludes; } return hasIncludes; } runCommand(command: string) { try { const res = execSync(command, { encoding: 'utf-8' }); return { code: 200, data: res.toString(), }; } catch (error) { return { code: 500, data: '', message: error.toString(), }; } } }