#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); const rootDir = path.join(__dirname, '..'); const buildDir = path.join(rootDir, 'build'); const distDir = path.join(rootDir, 'dist'); const bundleDir = path.join(buildDir, 'ncc'); const pkgProjectDir = path.join(buildDir, 'pkg'); const deployDir = path.join(rootDir, 'deploy'); const deploymentDocName = '\u751f\u4ea7\u5b9e\u65bd\u90e8\u7f72\u6587\u6863.md'; const runtimeAssets = [ 'config.json', 'alModel.json', deploymentDocName, ]; const targets = { 'win-x64': { pkgTarget: 'node18-win-x64', outputName: 'jhm-service.exe', deploySourceDir: path.join(deployDir, 'windows'), executableMode: null, }, 'linux-x64': { pkgTarget: 'node18-linux-x64', outputName: 'jhm-service', deploySourceDir: path.join(deployDir, 'linux'), executableMode: 0o755, }, }; function parseArgs(argv) { const selectedTargets = new Set(Object.keys(targets)); for (let index = 2; index < argv.length; index += 1) { const arg = argv[index]; const value = argv[index + 1]; if (arg === '--target' && value) { selectedTargets.clear(); for (const item of value.split(',').map((entry) => entry.trim()).filter(Boolean)) { if (!targets[item]) { throw new Error(`Unsupported build target: ${item}`); } selectedTargets.add(item); } index += 1; } } return Array.from(selectedTargets); } function runNode(scriptPath, args) { console.log(`[BUILD] node ${path.relative(rootDir, scriptPath)} ${args.join(' ')}`); const result = spawnSync(process.execPath, [scriptPath, ...args], { cwd: rootDir, stdio: 'inherit', }); if (result.status !== 0) { throw new Error(`Command failed: node ${scriptPath} ${args.join(' ')}`); } } function ensureCleanWorkspace() { fs.rmSync(buildDir, { recursive: true, force: true }); fs.rmSync(distDir, { recursive: true, force: true }); fs.mkdirSync(buildDir, { recursive: true }); fs.mkdirSync(distDir, { recursive: true }); } function buildBundle() { const nccCli = require.resolve('@vercel/ncc/dist/ncc/cli'); runNode(nccCli, ['build', 'app.js', '-o', bundleDir, '--target', 'es2019']); } function preparePkgProject() { const pkgBundleDir = path.join(pkgProjectDir, 'bundle'); const pkgEntryPath = path.join(pkgProjectDir, 'entry.js'); const pkgConfigPath = path.join(pkgProjectDir, 'package.json'); const bundleSourcePath = path.join(bundleDir, 'index.js'); const bundleTargetPath = path.join(pkgBundleDir, 'index.cjs'); const bootstrapSource = `#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const Module = require('module'); const bundlePath = path.join(__dirname, 'bundle', 'index.cjs'); const source = fs.readFileSync(bundlePath, 'utf8'); const bundledModule = new Module(bundlePath); bundledModule.filename = bundlePath; bundledModule.id = '.'; bundledModule.paths = Module._nodeModulePaths(path.dirname(bundlePath)); Module._cache[bundlePath] = bundledModule; process.mainModule = bundledModule; require.main = bundledModule; bundledModule._compile(source, bundlePath); `; const pkgConfig = { name: 'jhm-pkg-bootstrap', version: '1.0.0', private: true, type: 'commonjs', bin: 'entry.js', pkg: { assets: [ 'bundle/index.cjs', ], }, }; fs.mkdirSync(pkgBundleDir, { recursive: true }); fs.copyFileSync(bundleSourcePath, bundleTargetPath); fs.writeFileSync(pkgEntryPath, bootstrapSource, 'utf8'); fs.writeFileSync(pkgConfigPath, `${JSON.stringify(pkgConfig, null, 2)}\n`, 'utf8'); } function buildExecutable(targetKey, targetConfig) { const pkgCli = require.resolve('pkg/lib-es5/bin.js'); const targetDir = path.join(distDir, targetKey); const outputPath = path.join(targetDir, targetConfig.outputName); fs.mkdirSync(targetDir, { recursive: true }); runNode(pkgCli, [ pkgProjectDir, '--targets', targetConfig.pkgTarget, '--output', outputPath, '--public', '--public-packages', '*', '--no-bytecode', ]); if (!fs.existsSync(outputPath)) { throw new Error(`Expected executable was not created: ${outputPath}`); } if (targetConfig.executableMode) { fs.chmodSync(outputPath, targetConfig.executableMode); } } function copyRuntimeAssets(targetDir) { const runtimeDir = path.join(targetDir, 'runtime'); const logsDir = path.join(targetDir, 'logs'); fs.mkdirSync(runtimeDir, { recursive: true }); fs.mkdirSync(logsDir, { recursive: true }); for (const assetName of runtimeAssets) { const sourcePath = path.join(rootDir, assetName); if (!fs.existsSync(sourcePath)) { console.warn(`[BUILD] Skip missing asset: ${assetName}`); continue; } fs.copyFileSync(sourcePath, path.join(runtimeDir, assetName)); } } function copyDeploymentTemplates(targetKey, targetConfig) { const sourceDir = targetConfig.deploySourceDir; if (!fs.existsSync(sourceDir)) { return; } const targetDir = path.join(distDir, targetKey, 'service'); fs.mkdirSync(targetDir, { recursive: true }); fs.cpSync(sourceDir, targetDir, { recursive: true }); for (const entry of fs.readdirSync(targetDir)) { const fullPath = path.join(targetDir, entry); const extension = path.extname(entry); if (extension === '.sh') { fs.chmodSync(fullPath, 0o755); } } } function copySharedDocs(selectedTargets) { const sourcePath = path.join(rootDir, deploymentDocName); if (!fs.existsSync(sourcePath)) { return; } fs.copyFileSync(sourcePath, path.join(distDir, deploymentDocName)); for (const targetKey of selectedTargets) { fs.copyFileSync(sourcePath, path.join(distDir, targetKey, deploymentDocName)); } } function main() { const selectedTargets = parseArgs(process.argv); ensureCleanWorkspace(); buildBundle(); preparePkgProject(); for (const targetKey of selectedTargets) { console.log(`[BUILD] Packaging target: ${targetKey}`); buildExecutable(targetKey, targets[targetKey]); copyRuntimeAssets(path.join(distDir, targetKey)); copyDeploymentTemplates(targetKey, targets[targetKey]); } copySharedDocs(selectedTargets); console.log('[BUILD] Build completed.'); for (const targetKey of selectedTargets) { console.log(`[BUILD] Output: dist/${targetKey}`); } } main();