diff --git a/build-all.mjs b/build-all.mjs new file mode 100644 index 0000000..feadc9a --- /dev/null +++ b/build-all.mjs @@ -0,0 +1,103 @@ +/** + * 前端并行构建脚本 + * + * 并行执行 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); +});