mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-19 15:32:05 +08:00
176 lines
5.8 KiB
JavaScript
176 lines
5.8 KiB
JavaScript
#!/usr/bin/env node
|
|
import crypto from 'node:crypto';
|
|
import { createReadStream, readFileSync } from 'node:fs';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { spawn } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const rootDir = path.resolve(__dirname, '..', '..');
|
|
const packageJson = JSON.parse(
|
|
await fs.readFile(path.join(rootDir, 'package.json'), 'utf8'),
|
|
);
|
|
|
|
function getElectronVersion() {
|
|
try {
|
|
return JSON.parse(
|
|
readFileSync(path.join(rootDir, 'node_modules', 'electron', 'package.json'), 'utf8'),
|
|
).version;
|
|
} catch {
|
|
try {
|
|
return JSON.parse(
|
|
readFileSync(path.join(rootDir, 'package-lock.json'), 'utf8'),
|
|
).packages['node_modules/electron'].version;
|
|
} catch {
|
|
throw new Error('Could not resolve an exact Electron version for server native rebuild.');
|
|
}
|
|
}
|
|
}
|
|
|
|
function mapArch(arch = process.arch) {
|
|
return arch === 'arm64' ? 'arm64' : 'x64';
|
|
}
|
|
|
|
function mapPlatform(platform = process.platform) {
|
|
if (platform === 'darwin') return 'mac';
|
|
if (platform === 'win32') return 'win';
|
|
return 'linux';
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, {
|
|
stdio: 'inherit',
|
|
shell: process.platform === 'win32',
|
|
...options,
|
|
});
|
|
child.once('error', reject);
|
|
child.once('exit', (code) => {
|
|
if (code === 0) resolve();
|
|
else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function pathExists(filePath) {
|
|
try {
|
|
await fs.access(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function copyRequired(stageDir, relativePath) {
|
|
const from = path.join(rootDir, relativePath);
|
|
if (!(await pathExists(from))) {
|
|
throw new Error(`Required server bundle input is missing: ${relativePath}`);
|
|
}
|
|
await fs.cp(from, path.join(stageDir, relativePath), { recursive: true });
|
|
}
|
|
|
|
async function copyIfExists(stageDir, relativePath) {
|
|
const from = path.join(rootDir, relativePath);
|
|
if (!(await pathExists(from))) return;
|
|
await fs.cp(from, path.join(stageDir, relativePath), { recursive: true });
|
|
}
|
|
|
|
async function writeServerPackageJson(stageDir) {
|
|
const stagedPackageJson = {
|
|
...packageJson,
|
|
scripts: {
|
|
...(packageJson.scripts || {}),
|
|
},
|
|
};
|
|
// The bundle stage is not a git checkout with dev dependencies, so lifecycle
|
|
// scripts such as Husky prepare must not run there. Dependency install scripts
|
|
// still run; native modules need them before the Electron ABI rebuild below.
|
|
delete stagedPackageJson.scripts.prepare;
|
|
delete stagedPackageJson.scripts.prepublishOnly;
|
|
await fs.writeFile(
|
|
path.join(stageDir, 'package.json'),
|
|
`${JSON.stringify(stagedPackageJson, null, 2)}\n`,
|
|
'utf8',
|
|
);
|
|
}
|
|
|
|
function sha256(filePath) {
|
|
return new Promise((resolve, reject) => {
|
|
const hash = crypto.createHash('sha256');
|
|
const stream = createReadStream(filePath);
|
|
stream.on('data', (chunk) => hash.update(chunk));
|
|
stream.on('end', () => resolve(hash.digest('hex')));
|
|
stream.on('error', reject);
|
|
});
|
|
}
|
|
|
|
const platform = mapPlatform(process.env.CLOUDCLI_BUNDLE_PLATFORM || process.platform);
|
|
const arch = mapArch(process.env.CLOUDCLI_BUNDLE_ARCH || process.arch);
|
|
const version = packageJson.version;
|
|
const bundleName = `cloudcli-server-${version}-${platform}-${arch}.tar.gz`;
|
|
const bundleRoot = path.join(rootDir, 'release', 'server-bundles');
|
|
const stageDir = path.join(bundleRoot, `.stage-${version}-${platform}-${arch}`);
|
|
const archivePath = path.join(bundleRoot, bundleName);
|
|
|
|
await fs.rm(stageDir, { recursive: true, force: true });
|
|
await fs.mkdir(stageDir, { recursive: true });
|
|
await fs.mkdir(bundleRoot, { recursive: true });
|
|
|
|
await copyRequired(stageDir, 'dist');
|
|
await copyRequired(stageDir, 'dist-server');
|
|
await copyRequired(stageDir, 'public');
|
|
await copyRequired(stageDir, 'shared');
|
|
await copyRequired(stageDir, 'package-lock.json');
|
|
await copyIfExists(stageDir, 'scripts/fix-node-pty.js');
|
|
await writeServerPackageJson(stageDir);
|
|
|
|
console.log('Installing production server dependencies into bundle stage...');
|
|
await run('npm', ['ci', '--omit=dev'], {
|
|
cwd: stageDir,
|
|
env: {
|
|
...process.env,
|
|
npm_config_audit: 'false',
|
|
npm_config_fund: 'false',
|
|
},
|
|
});
|
|
|
|
const electronVersion = getElectronVersion();
|
|
const electronRebuild = process.platform === 'win32'
|
|
? path.join(rootDir, 'node_modules', '.bin', 'electron-rebuild.cmd')
|
|
: path.join(rootDir, 'node_modules', '.bin', 'electron-rebuild');
|
|
console.log(`Rebuilding native server dependencies for Electron ${electronVersion} (${arch})...`);
|
|
await run(electronRebuild, ['--version', electronVersion, '--module-dir', stageDir, '--arch', arch, '--force'], {
|
|
cwd: rootDir,
|
|
env: {
|
|
...process.env,
|
|
npm_config_audit: 'false',
|
|
npm_config_fund: 'false',
|
|
},
|
|
});
|
|
|
|
if (await pathExists(path.join(stageDir, 'scripts', 'fix-node-pty.js'))) {
|
|
await run(process.execPath, ['scripts/fix-node-pty.js'], { cwd: stageDir });
|
|
}
|
|
|
|
await fs.writeFile(
|
|
path.join(stageDir, '.installed.json'),
|
|
JSON.stringify({ version, platform, arch, builtAt: new Date().toISOString() }, null, 2),
|
|
'utf8',
|
|
);
|
|
|
|
await fs.rm(archivePath, { force: true });
|
|
const tarArgs = process.platform === 'win32'
|
|
? ['-czf', archivePath, '-C', stageDir, '.']
|
|
: ['-czf', archivePath, '-C', stageDir, '.'];
|
|
await run('tar', tarArgs);
|
|
|
|
const digest = await sha256(archivePath);
|
|
const checksumPath = `${archivePath}.sha256`;
|
|
await fs.writeFile(checksumPath, `${digest} ${bundleName}\n`, 'utf8');
|
|
await fs.rm(stageDir, { recursive: true, force: true });
|
|
|
|
const size = (await fs.stat(archivePath)).size / 1024 / 1024;
|
|
console.log(`Wrote ${path.relative(rootDir, archivePath)} (${size.toFixed(1)} MB)`);
|
|
console.log(`Wrote ${path.relative(rootDir, checksumPath)}`);
|