import { execSync } from 'node:child_process'; export const TaskCommandType = ['npm-install'] as const; export type TaskCommand = { key?: any; /** * 任务描述 */ description?: string; /** * 命令, 执行的任务 */ command: string; type?: (typeof TaskCommandType)[number] | string; /** * 任务执行完成后,执行判断的命令 */ after?: string; /** * 任务执行前,执行判断的命令 */ before?: string; /** * 任务执行完成后, 检测输出的文本内容,如果有这个文本,表示任务执行成功 * 如果没有这个文本,表示任务执行失败 */ afterCheck?: string; /** * 任务执行前, 检测输出的文本内容,如果有这个文本,表示任务已经安装 * 如果没有这个文本,表示任务没有安装 */ beforeCheck?: string; tags?: string[]; meta?: any; }; type RunTaskResult = { code?: number; message?: string; data?: any; output?: string; task?: TaskCommand; }; type TaskCommandOptions = { isDebug?: boolean; isLog?: boolean; }; export class TasksCommand { tasks: Map = new Map(); isDebug: boolean = false; isLog: boolean = false; constructor(opts?: TaskCommandOptions) { this.isDebug = opts?.isDebug ?? false; this.isLog = opts?.isLog ?? false; } log(...args: any[]) { if (this.isLog) { console.log(...args); } } debug(...args: any[]) { if (this.isDebug) { console.log(...args); } } 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 | number) { return this.tasks.get(name); } runTask(taskName: string | number | TaskCommand): RunTaskResult { let task: TaskCommand | undefined; if (typeof taskName === 'string' || typeof taskName === 'number') { task = this.getTask(taskName); } else if (typeof taskName === 'object') { task = taskName; } const end = (data?: RunTaskResult) => { return { ...data, task, }; }; if (!task) { return { code: 500, message: `没有找到这个任务`, task: task, }; } let { command, before, after, afterCheck, beforeCheck, type } = task; if (type === 'npm-install' && !afterCheck) { afterCheck = 'added'; } if (before) { const res = this.runCommand(before); const checkForContainue = this.checkForContainue(res, beforeCheck); this.debug('运行前检测', { runCommandResult: res, beforeCheck, checkForContainue }); if (!checkForContainue) { return end({ code: 200, message: `当前任务不需要执行, ${command}`, }); } } const res = this.runCommand(command); this.debug(`执行任务:${command}`, { runCommandResult: res }); if (res.code !== 200) { this.debug('当前任务执行失败:', { runCommandResult: res }); return end(res); } let checkText = res.data || ''; if (after) { this.debug('执行后检测是否成功:', { after }); const res = this.runCommand(after); if (res.code !== 200) { return end(res); } checkText = res.data || ''; } if (afterCheck) { const isSuccess = checkText?.includes?.(afterCheck); this.debug('执行后检测是否成功:', { afterCheck, checkText, isSuccess }); return end({ code: isSuccess ? 200 : 500, output: res.data, data: 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(), }; } } getTasksArray() { const tasks = Array.from(this.tasks.values()); return tasks; } /** * 根据标签获取任务 * @param tag */ getTasksArrayByTag(tag?: string) { const tasks = Array.from(this.tasks.values()); return tasks.filter((task) => !tag || task.tags?.includes(tag) || task.meta?.tags?.includes(tag) || task.key === tag); } }