@daiyu-5577/quickbuild
Version:
front-end build service
233 lines (232 loc) • 11.1 kB
JavaScript
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;
}
}