#!/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();
|