115 lines
4.2 KiB
JavaScript
Raw Normal View History

2026-05-20 21:39:12 +08:00
const fs = require('node:fs');
const path = require('node:path');
const cp = require('node:child_process');
const backendDir = path.resolve(__dirname, '..');
const repoDir = path.resolve(backendDir, '..', '..');
const frontendDir = path.join(repoDir, 'packages', 'frontend');
const stageDir = path.join(backendDir, 'build', 'pkg-stage');
const outputDir = path.join(backendDir, 'build', 'pkg-output');
function run(command, cwd, env) {
cp.execFileSync(
process.platform === 'win32' ? 'cmd.exe' : 'bash',
process.platform === 'win32' ? ['/c', command] : ['-lc', command],
{ cwd, stdio: 'inherit', env: { ...process.env, ...env } }
);
}
function assertStage(dir) {
const required = [
path.join(dir, 'public', 'index.html'),
path.join(dir, 'dist', 'index.js'),
];
for (const file of required) {
if (!fs.existsSync(file)) {
throw new Error(`缺少 staging 文件: ${file}`);
}
}
}
// Clean
fs.rmSync(stageDir, { recursive: true, force: true });
fs.rmSync(outputDir, { recursive: true, force: true });
fs.mkdirSync(stageDir, { recursive: true });
// Build frontend (override API base URL to use same-origin for installer)
console.log('[1/6] Building frontend...');
run('pnpm build', frontendDir, { VITE_API_BASE_URL: '', VITE_COOL_EPS_ENABLE: 'true' });
// Build backend
console.log('[2/6] Building backend...');
run('npm run build', backendDir);
// Stage files
console.log('[3/6] Staging files...');
fs.cpSync(path.join(backendDir, 'bootstrap.js'), path.join(stageDir, 'bootstrap.js'));
fs.cpSync(path.join(backendDir, 'dist'), path.join(stageDir, 'dist'), { recursive: true });
fs.cpSync(path.join(backendDir, 'public'), path.join(stageDir, 'public'), { recursive: true });
// Overlay frontend dist onto public (frontend index.html replaces backend welcome page)
fs.cpSync(path.join(frontendDir, 'dist'), path.join(stageDir, 'public'), { recursive: true, force: true });
if (fs.existsSync(path.join(backendDir, 'typings'))) {
fs.cpSync(path.join(backendDir, 'typings'), path.join(stageDir, 'typings'), { recursive: true });
}
if (fs.existsSync(path.join(backendDir, 'src', 'locales'))) {
fs.mkdirSync(path.join(stageDir, 'src'), { recursive: true });
fs.cpSync(path.join(backendDir, 'src', 'locales'), path.join(stageDir, 'src', 'locales'), { recursive: true });
}
// Generate staging package.json
console.log('[4/6] Generating staging package.json...');
const backendPkg = require(path.join(backendDir, 'package.json'));
const stagePkg = {
name: backendPkg.name,
version: backendPkg.version,
private: true,
overrides: backendPkg.overrides || {},
dependencies: backendPkg.dependencies,
bin: './bootstrap.js',
pkg: {
scripts: ['dist/**/*.js', 'node_modules/**/*.mjs'],
assets: [
'public/**/*',
'typings/**/*',
'src/locales/**/*',
'node_modules/@img/sharp-win32-x64/**/*',
'node_modules/@img/colour/**/*',
'node_modules/better-sqlite3/build/Release/*.node',
'node_modules/@msgpackr-extract/msgpackr-extract-win32-x64/*.node',
'node_modules/@napi-rs/canvas-win32-x64-msvc/*.node',
],
targets: ['node20-win-x64'],
outputPath: outputDir,
},
};
fs.writeFileSync(path.join(stageDir, 'package.json'), JSON.stringify(stagePkg, null, 2));
// Install deps
console.log('[5/6] Installing production dependencies...');
run('npm install --production --install-strategy=hoisted --legacy-peer-deps', stageDir);
// Patch generator-function
const gfPkg = path.join(stageDir, 'node_modules', 'generator-function', 'package.json');
if (fs.existsSync(gfPkg)) {
const gf = JSON.parse(fs.readFileSync(gfPkg, 'utf8'));
if (gf.exports && gf.exports['.']) {
gf.exports['.'] = './index.js';
fs.writeFileSync(gfPkg, JSON.stringify(gf, null, 2));
console.log(' Patched generator-function exports');
}
}
// Run pkg
console.log('[6/6] Running pkg...');
assertStage(stageDir);
fs.mkdirSync(outputDir, { recursive: true });
run('npx @yao-pkg/pkg@6.14.1 .', stageDir);
// Validate
console.log('\n=== Build complete ===');
const exePath = path.join(outputDir, 'backend.exe');
if (fs.existsSync(exePath)) {
const stat = fs.statSync(exePath);
console.log(`Output: ${exePath} (${(stat.size / 1024 / 1024).toFixed(1)} MB)`);
}