feat: add CentOS7 single executable build
This commit is contained in:
203
scripts/build-single-exe.js
Normal file
203
scripts/build-single-exe.js
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const { spawnSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DEFAULT_TARGET = 'bun-linux-x64-baseline';
|
||||
const DEFAULT_OUTDIR = 'dist-exe';
|
||||
const DEFAULT_NAME = 'cc-web';
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {
|
||||
target: DEFAULT_TARGET,
|
||||
outdir: DEFAULT_OUTDIR,
|
||||
name: DEFAULT_NAME,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
const readValue = (name) => {
|
||||
const inlinePrefix = `${name}=`;
|
||||
if (arg.startsWith(inlinePrefix)) return arg.slice(inlinePrefix.length);
|
||||
if (arg === name && index + 1 < argv.length) {
|
||||
index += 1;
|
||||
return argv[index];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const target = readValue('--target');
|
||||
if (target !== null) {
|
||||
options.target = target;
|
||||
continue;
|
||||
}
|
||||
|
||||
const outdir = readValue('--outdir');
|
||||
if (outdir !== null) {
|
||||
options.outdir = outdir;
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = readValue('--name');
|
||||
if (name !== null) {
|
||||
options.name = name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === '-h' || arg === '--help') {
|
||||
options.help = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`未知参数: ${arg}`);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`用法:
|
||||
npm run build:single-exe
|
||||
node scripts/build-single-exe.js --target bun-linux-x64-baseline --outdir dist-exe --name cc-web
|
||||
|
||||
说明:
|
||||
默认 target 是 ${DEFAULT_TARGET},用于兼容 CentOS 7 这类老 glibc Linux x64 系统。
|
||||
该命令只打包 cc-web 服务本体;Claude/Codex CLI 仍在运行时从宿主机 PATH 或 CLAUDE_PATH/CODEX_PATH 调用。`);
|
||||
}
|
||||
|
||||
function isWindowsTarget(target) {
|
||||
return /^bun-windows(?:-|$)/.test(String(target || ''));
|
||||
}
|
||||
|
||||
function resolveBinaryName(target, name) {
|
||||
return isWindowsTarget(target) && !name.endsWith('.exe') ? `${name}.exe` : name;
|
||||
}
|
||||
|
||||
function copyFileIfExists(source, destination) {
|
||||
if (!fs.existsSync(source)) return false;
|
||||
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||
fs.copyFileSync(source, destination);
|
||||
return true;
|
||||
}
|
||||
|
||||
function copyDirIfExists(source, destination) {
|
||||
if (!fs.existsSync(source)) return false;
|
||||
fs.cpSync(source, destination, { recursive: true, force: true });
|
||||
return true;
|
||||
}
|
||||
|
||||
function writeRunningGuide(releaseDir, target, binaryName) {
|
||||
const runCommand = isWindowsTarget(target) ? binaryName : `./${binaryName}`;
|
||||
const content = `# cc-web single executable 运行说明
|
||||
|
||||
这个目录是 Bun single executable 发布包,面向 CentOS 7 / 老 glibc Linux 运行。
|
||||
|
||||
## 直接运行
|
||||
|
||||
\`\`\`bash
|
||||
chmod +x ${binaryName}
|
||||
PORT=8002 CC_WEB_PASSWORD='请改成强密码' ${runCommand}
|
||||
\`\`\`
|
||||
|
||||
## 调用宿主机 Claude/Codex CLI
|
||||
|
||||
cc-web 不内置 Claude/Codex CLI。运行时会继续从宿主机 PATH 查找:
|
||||
|
||||
\`\`\`bash
|
||||
export CLAUDE_PATH=/usr/local/bin/claude
|
||||
export CODEX_PATH=/usr/local/bin/codex
|
||||
${runCommand}
|
||||
\`\`\`
|
||||
|
||||
如果不设置上述变量,默认命令名仍是 \`claude\` 和 \`codex\`。
|
||||
|
||||
## 目录说明
|
||||
|
||||
- \`public/\`:前端静态资源
|
||||
- \`config/\`:运行时配置,首次启动会写入登录和通知配置
|
||||
- \`sessions/\`:会话数据
|
||||
- \`logs/\`:运行日志
|
||||
|
||||
不要把已有生产环境的 \`config/\`、\`sessions/\` 误删或覆盖。
|
||||
`;
|
||||
fs.writeFileSync(path.join(releaseDir, 'RUNNING.md'), content, 'utf8');
|
||||
}
|
||||
|
||||
function copyRuntimeAssets(projectRoot, releaseDir, target, binaryName) {
|
||||
copyDirIfExists(path.join(projectRoot, 'public'), path.join(releaseDir, 'public'));
|
||||
|
||||
for (const file of ['.env.example', 'README.md', 'README.en.md', 'CHANGELOG.md', 'package.json']) {
|
||||
copyFileIfExists(path.join(projectRoot, file), path.join(releaseDir, file));
|
||||
}
|
||||
|
||||
for (const dir of ['.codex/skills', '.codex/prompts', '.agents/skills', '.agents/prompts']) {
|
||||
copyDirIfExists(path.join(projectRoot, dir), path.join(releaseDir, dir));
|
||||
}
|
||||
|
||||
for (const dir of ['config', 'sessions', 'logs']) {
|
||||
fs.mkdirSync(path.join(releaseDir, dir), { recursive: true });
|
||||
}
|
||||
|
||||
writeRunningGuide(releaseDir, target, binaryName);
|
||||
}
|
||||
|
||||
function runBunBuild(projectRoot, target, outfile) {
|
||||
const bunCommand = process.versions.bun ? process.execPath : (process.env.BUN_BIN || 'bun');
|
||||
const args = [
|
||||
'build',
|
||||
'--compile',
|
||||
'--no-compile-autoload-dotenv',
|
||||
`--target=${target}`,
|
||||
`--outfile=${outfile}`,
|
||||
path.join(projectRoot, 'server.js'),
|
||||
];
|
||||
|
||||
console.log(`[build:single-exe] ${bunCommand} ${args.join(' ')}`);
|
||||
const result = spawnSync(bunCommand, args, {
|
||||
cwd: projectRoot,
|
||||
env: process.env,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
if (result.error && result.error.code === 'ENOENT') {
|
||||
throw new Error('未找到 bun。请先在构建机安装 Bun,或通过 BUN_BIN 指定 bun 可执行文件。');
|
||||
}
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Bun 编译失败,退出码: ${result.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
if (options.help) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
const target = options.target || DEFAULT_TARGET;
|
||||
const binaryName = resolveBinaryName(target, options.name || DEFAULT_NAME);
|
||||
const outdir = path.resolve(projectRoot, options.outdir || DEFAULT_OUTDIR);
|
||||
const releaseDir = path.join(outdir, target);
|
||||
const outfile = path.join(releaseDir, binaryName);
|
||||
|
||||
fs.rmSync(releaseDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(releaseDir, { recursive: true });
|
||||
|
||||
runBunBuild(projectRoot, target, outfile);
|
||||
if (!isWindowsTarget(target)) fs.chmodSync(outfile, 0o755);
|
||||
copyRuntimeAssets(projectRoot, releaseDir, target, binaryName);
|
||||
|
||||
console.log(`[build:single-exe] 发布目录: ${releaseDir}`);
|
||||
console.log(`[build:single-exe] 可执行文件: ${outfile}`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (err) {
|
||||
console.error(`[build:single-exe] ERROR: ${err.message || err}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user