You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
103 lines
3.1 KiB
103 lines
3.1 KiB
/**
|
|
* 前端并行构建脚本
|
|
*
|
|
* 并行执行 client/ (SDK) 和 frontend/ (管理后台) 的 npm install + build,
|
|
* 替代 pom.xml 中两个串行的 frontend-maven-plugin。
|
|
*
|
|
* 用法: node build-all.mjs [--skip-install]
|
|
* --skip-install 跳过 npm install(仅执行 build),热构建加速
|
|
*/
|
|
|
|
import { spawn } from 'node:child_process';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { existsSync } from 'node:fs';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = resolve(__dirname);
|
|
|
|
// 优先使用 Maven 安装的本地 Node.js/npm,保持版本一致
|
|
const isWindows = process.platform === 'win32';
|
|
const LOCAL_NPM = isWindows
|
|
? resolve(ROOT, 'node', 'npm.cmd')
|
|
: resolve(ROOT, 'node', 'npm');
|
|
|
|
const NPM_EXE = existsSync(LOCAL_NPM) ? LOCAL_NPM : 'npm';
|
|
|
|
const SKIP_INSTALL = process.argv.includes('--skip-install');
|
|
|
|
const PROJECTS = [
|
|
{ name: 'client', dir: resolve(ROOT, 'client') },
|
|
{ name: 'frontend', dir: resolve(ROOT, 'frontend') },
|
|
];
|
|
|
|
/**
|
|
* 在指定目录执行 npm 命令,返回 Promise。
|
|
* 将 stdout/stderr 实时转发到控制台,带项目前缀。
|
|
*/
|
|
function runNpm(cwd, args, label) {
|
|
return new Promise((resolvePromise, reject) => {
|
|
const child = spawn(NPM_EXE, args, {
|
|
cwd,
|
|
shell: isWindows, // Windows 需要 shell 才能找到 npm.cmd
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
env: { ...process.env },
|
|
});
|
|
|
|
child.stdout.on('data', (data) => {
|
|
process.stdout.write(`[${label}] ${data}`);
|
|
});
|
|
child.stderr.on('data', (data) => {
|
|
process.stderr.write(`[${label}] ${data}`);
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
if (code === 0) {
|
|
console.log(`[${label}] ✓ 完成`);
|
|
resolvePromise();
|
|
} else {
|
|
reject(new Error(`[${label}] 失败,退出码: ${code}`));
|
|
}
|
|
});
|
|
|
|
child.on('error', (err) => {
|
|
reject(new Error(`[${label}] 启动失败: ${err.message}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const startTime = Date.now();
|
|
|
|
// ===== Phase 1: npm install(两个项目并行) =====
|
|
if (!SKIP_INSTALL) {
|
|
console.log('\n=== Phase 1: npm install ===');
|
|
const installResults = await Promise.allSettled(
|
|
PROJECTS.map((p) =>
|
|
runNpm(p.dir, ['install', '--prefer-offline', '--no-audit', '--no-fund'], p.name)
|
|
)
|
|
);
|
|
for (const r of installResults) {
|
|
if (r.status === 'rejected') throw r.reason;
|
|
}
|
|
} else {
|
|
console.log('\n=== Phase 1: 跳过 npm install(--skip-install) ===');
|
|
}
|
|
|
|
// ===== Phase 2: npm run build(两个项目并行) =====
|
|
console.log('\n=== Phase 2: npm run build ===');
|
|
const buildResults = await Promise.allSettled(
|
|
PROJECTS.map((p) => runNpm(p.dir, ['run', 'build'], p.name))
|
|
);
|
|
for (const r of buildResults) {
|
|
if (r.status === 'rejected') throw r.reason;
|
|
}
|
|
|
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
console.log(`\n✓ 全部前端构建完成 (${elapsed}s)`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(`\n✗ 前端构建失败: ${err.message}`);
|
|
process.exit(1);
|
|
});
|