UNPKG

@daiyu-5577/quickbuild

Version:

front-end build service

233 lines (232 loc) 11.1 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { globSync } from "glob"; import { URL } from "node:url"; import path from "node:path"; import { generateUid } from '../../database/index.js'; import chalk from 'chalk'; import fse from 'fs-extra'; import CustError from "../../utils/custError.js"; import dayjs from "dayjs"; import axios from 'axios'; import { execPms, spawnPms } from "../../utils/exec.js"; import { getFromatTime } from "../../utils/time.js"; import { checkBaranch, gitClean } from '../../utils/git.js'; import { formatMsg } from './type.js'; import { getWriteStream } from '../../utils/fileLog.js'; import { catchBuildLogPath } from '../config.js'; import { baseRoute } from '../config.js'; import { IdType } from './type.js'; const __dirname = new URL('.', import.meta.url).pathname; let cwd = process.cwd(); if (process.env.NODE_ENV === 'development') { cwd = new URL('../../git', import.meta.url).pathname; } export default class Build { constructor(props) { this.buildServer = null; this.packages = []; this.buildTasks = []; this.curTask = null; this.npmTypes = [ { name: 'npm', lockFile: 'package-lock.json', installScript: 'npm ci', }, { name: 'yarn', lockFile: 'yarn.lock', installScript: 'yarn install --frozen-lockfile', }, { name: 'pnpm', lockFile: 'pnpm-lock.yaml', installScript: 'pnpm install --frozen-lockfile', } ]; const { buildServer } = props; const packages = this.getAllPackage(); if (!packages.length) { console.error(chalk.red(new CustError(`${cwd} 当前路径不存在package.json文件`))); } this.packages = packages; this.buildServer = buildServer || null; } sendChatMsgByTask(curTask, msg) { if (!!curTask.notify) { axios.post(curTask.notify, formatMsg('on:msg-chat', { id: generateUid(IdType.M), socketId: '', name: '系统消息', msg: `branch: ${curTask.branch}\ntaskId: ${curTask.taskId}\npackagePath: ${curTask.packagePath}\ncwd: ${curTask.cwd}\ntime: ${getFromatTime()}\n${msg}`, ctxType: 'ctx:txt', time: getFromatTime(), timestamp: +dayjs(), })); } return msg; } addTask(params) { return __awaiter(this, void 0, void 0, function* () { const { packagePath, branch, commit, notify } = params; const packageCwd = path.join(cwd, packagePath); if (!this.packages.length) { throw new CustError(`${cwd} 当前路径不存在可执行的项目`); } if (!fse.existsSync(packageCwd)) { throw new CustError(`${packageCwd} 路径不存在`); } const checkAddTask = (pkg) => __awaiter(this, void 0, void 0, function* () { const pkgCwd = path.join(cwd, pkg); const packageJsonPath = path.join(pkgCwd, 'package.json'); const packageJson = fse.readJSONSync(packageJsonPath, { encoding: 'utf-8' }); const { scripts } = packageJson; if (!scripts.build) { throw new CustError(`${packageJsonPath} 没有找到当前项目的build脚本`); } let npmType = this.npmTypes[0]; for (const item of this.npmTypes) { if (fse.existsSync(path.join(pkgCwd, item.lockFile))) { npmType = item; break; } } const task = Object.assign(Object.assign({ time: getFromatTime(+dayjs(), 'YYYY-MM-DD_HH:mm:ss'), timestamp: +dayjs(), taskId: generateUid(IdType.T), npmType, cwd: pkgCwd }, params), { packagePath: pkg }); this.buildTasks.push(task); this.sendChatMsgByTask(task, `已加入任务队列`); }); if (!commit) { yield checkAddTask(packagePath); setImmediate(() => { this.build(); }); return; } const diffsRes = yield execPms(`git diff --name-only ${commit}`, { cwd: packageCwd, encoding: 'utf-8' }); const diffs = diffsRes.stdout.trim().split('\n').filter(v => !!v); if (!diffs.length) throw new CustError(`${cwd} 当前路径 git diff 没有任何文件变更`); const changePackages = new Set(); if (this.packages.length === 1 && this.packages[0] === '.') { changePackages.add(this.packages[0]); } else { for (const diff of diffs) { const findPkg = this.packages.find(v => diff.includes(v)); if (!findPkg && this.packages.includes('.')) { changePackages.add('.'); } if (findPkg) { changePackages.add(findPkg); } } } if (changePackages.size === 0) throw new CustError('git diff 没有找到可执行的匹配项目'); for (const pkg of changePackages) { yield checkAddTask(pkg); } setImmediate(() => { this.build(); }); }); } build() { return __awaiter(this, void 0, void 0, function* () { if (!!this.curTask) return; const curTask = this.buildTasks.shift(); if (!curTask) return; this.curTask = curTask; const fileName = `${curTask.time}_${curTask.branch}_${curTask.packagePath}_${curTask.taskId}.log`; const { writeStream, localFileName } = yield getWriteStream(catchBuildLogPath, fileName); writeStream.write(`\n${dayjs().format('YYYY-MM-DD HH:mm:ss')}\n`); const onSendBuildMsg = (msg, status = 1, isCustMsg = false) => { if (this.buildServer) { if ([2, 3].includes(status)) { writeStream.writable && writeStream.end(msg + '\n'); } else { writeStream.writable && writeStream.write(msg + '\n'); } } if (!!curTask.notify && (/err|error/i.test(msg) || status != 1 || isCustMsg)) { axios.post(curTask.notify, formatMsg('on:msg-build', { id: generateUid(IdType.TM), name: '构建消息', taskId: curTask.taskId, branch: curTask.branch, msg: msg + '\n', task: curTask, time: getFromatTime(), timestamp: +dayjs(), status, buildLogPath: this.buildServer ? `${baseRoute}/api/showLog?type=buildLog&name=${localFileName}` : '', }), { maxContentLength: Infinity, maxBodyLength: Infinity }); } return msg; }; try { this.sendChatMsgByTask(curTask, `开始构建`); console.log(chalk.green(onSendBuildMsg(`------ build time ${getFromatTime()} ------`, 1, true))); console.log(chalk.green(onSendBuildMsg(`------ build taskId ${curTask.taskId} ------`, 1, true))); console.log(chalk.green(onSendBuildMsg(`------ build packagePath ${curTask.packagePath} ------`, 1, true))); console.log(chalk.green(onSendBuildMsg(`------ build by ${curTask.npmType.name} ------`, 1, true))); console.log(chalk.green(onSendBuildMsg(`------ check branch ${curTask.branch} by ${curTask.cwd} ------`, 1, true))); yield checkBaranch(curTask.branch, curTask.cwd); console.log(chalk.green(onSendBuildMsg(`------ ${curTask.npmType.installScript} ------`, 1, true))); const [runName, ...args] = curTask.npmType.installScript.split(' '); yield spawnPms(`${runName}`, args, { cwd: path.join(curTask.cwd), onMessage: onSendBuildMsg }); console.log(chalk.green(onSendBuildMsg(`------ npm run build ------`, 1, true))); yield spawnPms(`npm`, [`run`, `build`], { cwd: path.join(curTask.cwd), onMessage: onSendBuildMsg }); const sourcePath = path.join(curTask.cwd, 'dist'); const targetPath = path.join(cwd, 'dist', curTask.packagePath); console.log(chalk.green(onSendBuildMsg(`------ copy ${sourcePath} ${targetPath} ------`, 1, true))); if (sourcePath !== targetPath) { yield fse.copySync(path.join(sourcePath), path.join(targetPath), { overwrite: true }); } console.log(chalk.green(onSendBuildMsg(`------ git clean ------`, 1, true))); yield gitClean(curTask.cwd); console.log(chalk.green(onSendBuildMsg(`------ ${getFromatTime()} 构建成功 packagePath ${curTask.packagePath} build success ------`, 2))); this.sendChatMsgByTask(curTask, `构建成功`); } catch (error) { this.sendChatMsgByTask(curTask, `构建失败 ${error}`); console.log(chalk.red(onSendBuildMsg(`------ ${getFromatTime()} 构建失败 packagePath ${curTask.packagePath} build error: ${error} ------`, 3))); } finally { this.curTask = null; this.build(); } }); } getAllPackage() { const jsonPaths = globSync(['**/package.json'], { cwd: cwd, ignore: ['**/node_modules/**', '**/dist/**'] }); const packages = jsonPaths.map(item => path.dirname(item)).sort((a, b) => (a.length - b.length) * -1); return packages; } }