#!/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 runtimeAssets = [
|
'config.json',
|
'alModel.json',
|
];
|
const distDocs = [
|
'配置说明.md',
|
'实施部署文档.md',
|
];
|
const targets = {
|
'win-x64': {
|
pkgTarget: 'node18-win-x64',
|
outputName: 'jh2028-service.exe',
|
executableMode: null,
|
},
|
'linux-x64': {
|
pkgTarget: 'node18-linux-x64',
|
outputName: 'jh2028-service',
|
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(`不支持的打包目标: ${item}`);
|
}
|
|
selectedTargets.add(item);
|
}
|
|
index += 1;
|
}
|
}
|
|
return Array.from(selectedTargets);
|
}
|
|
function runNode(scriptPath, args) {
|
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
cwd: rootDir,
|
stdio: 'inherit',
|
});
|
|
if (result.status !== 0) {
|
throw new Error(`命令执行失败: 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']);
|
fs.rmSync(path.join(bundleDir, 'logs'), { recursive: true, force: true });
|
}
|
|
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: 'jh2028-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(`未生成预期的可执行文件: ${outputPath}`);
|
}
|
|
if (targetConfig.executableMode) {
|
fs.chmodSync(outputPath, targetConfig.executableMode);
|
}
|
}
|
|
function copyRuntimeAssets(targetDir) {
|
const runtimeDir = path.join(targetDir, 'runtime');
|
const logsDir = path.join(targetDir, 'logs');
|
const dashboardSourceDir = path.join(rootDir, 'dashboard');
|
const dashboardTargetDir = path.join(runtimeDir, 'dashboard');
|
|
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)) {
|
continue;
|
}
|
|
fs.copyFileSync(sourcePath, path.join(runtimeDir, assetName));
|
}
|
|
if (fs.existsSync(dashboardSourceDir)) {
|
fs.cpSync(dashboardSourceDir, dashboardTargetDir, { recursive: true });
|
}
|
|
for (const docName of distDocs) {
|
const sourcePath = path.join(rootDir, docName);
|
|
if (!fs.existsSync(sourcePath)) {
|
continue;
|
}
|
|
fs.copyFileSync(sourcePath, path.join(targetDir, docName));
|
}
|
}
|
|
function main() {
|
const selectedTargets = parseArgs(process.argv);
|
ensureCleanWorkspace();
|
buildBundle();
|
preparePkgProject();
|
|
for (const targetKey of selectedTargets) {
|
buildExecutable(targetKey, targets[targetKey]);
|
copyRuntimeAssets(path.join(distDir, targetKey));
|
}
|
|
console.log('[BUILD] 打包完成');
|
}
|
|
main();
|