193 lines
5.2 KiB
TypeScript
193 lines
5.2 KiB
TypeScript
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<string | number, TaskCommand> = 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);
|
|
}
|
|
}
|