Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
424 lines
11 KiB
JavaScript
424 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ZCLAW Runtime Preparation Script
|
|
*
|
|
* Prepares the ZCLAW binary for bundling with Tauri.
|
|
* Supports cross-platform: Windows, Linux, macOS
|
|
*
|
|
* Usage:
|
|
* node scripts/prepare-zclaw-runtime.mjs
|
|
* node scripts/prepare-zclaw-runtime.mjs --dry-run
|
|
* ZCLAW_VERSION=v1.2.3 node scripts/prepare-zclaw-runtime.mjs
|
|
*/
|
|
|
|
import { execSync, execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { arch as osArch, platform as osPlatform, homedir } from 'node:os';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const desktopRoot = path.resolve(__dirname, '..');
|
|
const outputDir = path.join(desktopRoot, 'src-tauri', 'resources', 'zclaw-runtime');
|
|
const dryRun = process.argv.includes('--dry-run');
|
|
const zclawVersion = process.env.ZCLAW_VERSION || 'latest';
|
|
|
|
const PLATFORM = osPlatform();
|
|
const ARCH = osArch();
|
|
|
|
function log(message) {
|
|
console.log(`[prepare-zclaw-runtime] ${message}`);
|
|
}
|
|
|
|
function warn(message) {
|
|
console.warn(`[prepare-zclaw-runtime] WARN: ${message}`);
|
|
}
|
|
|
|
function error(message) {
|
|
console.error(`[prepare-zclaw-runtime] ERROR: ${message}`);
|
|
}
|
|
|
|
/**
|
|
* Get platform-specific binary configuration
|
|
* ZCLAW releases: .zip for Windows, .tar.gz for Unix
|
|
*/
|
|
function getPlatformConfig() {
|
|
const configs = {
|
|
win32: {
|
|
x64: {
|
|
binaryName: 'zclaw.exe',
|
|
downloadName: 'zclaw-x86_64-pc-windows-msvc.zip',
|
|
archiveFormat: 'zip',
|
|
},
|
|
arm64: {
|
|
binaryName: 'zclaw.exe',
|
|
downloadName: 'zclaw-aarch64-pc-windows-msvc.zip',
|
|
archiveFormat: 'zip',
|
|
},
|
|
},
|
|
darwin: {
|
|
x64: {
|
|
binaryName: 'zclaw-x86_64-apple-darwin',
|
|
downloadName: 'zclaw-x86_64-apple-darwin.tar.gz',
|
|
archiveFormat: 'tar.gz',
|
|
},
|
|
arm64: {
|
|
binaryName: 'zclaw-aarch64-apple-darwin',
|
|
downloadName: 'zclaw-aarch64-apple-darwin.tar.gz',
|
|
archiveFormat: 'tar.gz',
|
|
},
|
|
},
|
|
linux: {
|
|
x64: {
|
|
binaryName: 'zclaw-x86_64-unknown-linux-gnu',
|
|
downloadName: 'zclaw-x86_64-unknown-linux-gnu.tar.gz',
|
|
archiveFormat: 'tar.gz',
|
|
},
|
|
arm64: {
|
|
binaryName: 'zclaw-aarch64-unknown-linux-gnu',
|
|
downloadName: 'zclaw-aarch64-unknown-linux-gnu.tar.gz',
|
|
archiveFormat: 'tar.gz',
|
|
},
|
|
},
|
|
};
|
|
|
|
const platformConfig = configs[PLATFORM];
|
|
if (!platformConfig) {
|
|
throw new Error(`Unsupported platform: ${PLATFORM}`);
|
|
}
|
|
|
|
const archConfig = platformConfig[ARCH];
|
|
if (!archConfig) {
|
|
throw new Error(`Unsupported architecture: ${ARCH} on ${PLATFORM}`);
|
|
}
|
|
|
|
return archConfig;
|
|
}
|
|
|
|
/**
|
|
* Find ZCLAW binary in system PATH
|
|
*/
|
|
function findSystemBinary() {
|
|
const override = process.env.ZCLAW_BIN;
|
|
if (override) {
|
|
if (fs.existsSync(override)) {
|
|
return override;
|
|
}
|
|
throw new Error(`ZCLAW_BIN specified but file not found: ${override}`);
|
|
}
|
|
|
|
try {
|
|
let result;
|
|
if (PLATFORM === 'win32') {
|
|
result = execFileSync('where.exe', ['zclaw'], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
});
|
|
} else {
|
|
result = execFileSync('which', ['zclaw'], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
});
|
|
}
|
|
|
|
const binaryPath = result.split(/\r?\n/).map(s => s.trim()).find(Boolean);
|
|
if (binaryPath && fs.existsSync(binaryPath)) {
|
|
return binaryPath;
|
|
}
|
|
} catch {
|
|
// Binary not found in PATH
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if ZCLAW is installed via install script
|
|
*/
|
|
function findInstalledBinary() {
|
|
const config = getPlatformConfig();
|
|
const home = homedir();
|
|
|
|
const possiblePaths = [
|
|
// Default install location
|
|
path.join(home, '.zclaw', 'bin', config.binaryName),
|
|
path.join(home, '.local', 'bin', config.binaryName),
|
|
// macOS
|
|
path.join(home, '.zclaw', 'bin', 'zclaw'),
|
|
'/usr/local/bin/zclaw',
|
|
'/usr/bin/zclaw',
|
|
];
|
|
|
|
for (const p of possiblePaths) {
|
|
if (fs.existsSync(p)) {
|
|
return p;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Download ZCLAW binary from GitHub Releases
|
|
* Handles .zip for Windows, .tar.gz for Unix
|
|
*/
|
|
function downloadBinary(config) {
|
|
const baseUrl = 'https://github.com/RightNow-AI/zclaw/releases';
|
|
const downloadUrl = zclawVersion === 'latest'
|
|
? `${baseUrl}/latest/download/${config.downloadName}`
|
|
: `${baseUrl}/download/${zclawVersion}/${config.downloadName}`;
|
|
|
|
const archivePath = path.join(outputDir, config.downloadName);
|
|
const binaryOutputPath = path.join(outputDir, config.binaryName);
|
|
|
|
log(`Downloading ZCLAW binary...`);
|
|
log(` Platform: ${PLATFORM} (${ARCH})`);
|
|
log(` Version: ${zclawVersion}`);
|
|
log(` Archive: ${config.downloadName}`);
|
|
log(` URL: ${downloadUrl}`);
|
|
|
|
if (dryRun) {
|
|
log('DRY RUN: Would download and extract binary');
|
|
return null;
|
|
}
|
|
|
|
// Ensure output directory exists
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
|
|
try {
|
|
// Download archive using curl (works on all platforms)
|
|
log('Downloading archive...');
|
|
execSync(`curl -fsSL -o "${archivePath}" "${downloadUrl}"`, { stdio: 'inherit' });
|
|
|
|
if (!fs.existsSync(archivePath)) {
|
|
throw new Error('Download failed - archive not created');
|
|
}
|
|
|
|
// Extract archive
|
|
log('Extracting binary...');
|
|
if (config.archiveFormat === 'zip') {
|
|
// Use PowerShell to extract zip on Windows
|
|
execSync(
|
|
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${outputDir}' -Force"`,
|
|
{ stdio: 'inherit' }
|
|
);
|
|
} else {
|
|
// Use tar for .tar.gz on Unix
|
|
execSync(`tar -xzf "${archivePath}" -C "${outputDir}"`, { stdio: 'inherit' });
|
|
}
|
|
|
|
// Find and rename the extracted binary
|
|
// The archive contains a single binary file
|
|
const extractedFiles = fs.readdirSync(outputDir).filter(f =>
|
|
f.startsWith('zclaw') && !f.endsWith('.zip') && !f.endsWith('.tar.gz') && !f.endsWith('.sha256')
|
|
);
|
|
|
|
if (extractedFiles.length === 0) {
|
|
throw new Error('No binary found in archive');
|
|
}
|
|
|
|
// Use the first extracted binary (should be only one)
|
|
const extractedBinary = path.join(outputDir, extractedFiles[0]);
|
|
log(`Found extracted binary: ${extractedFiles[0]}`);
|
|
|
|
// Rename to standard name if needed
|
|
if (extractedFiles[0] !== config.binaryName) {
|
|
if (fs.existsSync(binaryOutputPath)) {
|
|
fs.unlinkSync(binaryOutputPath);
|
|
}
|
|
fs.renameSync(extractedBinary, binaryOutputPath);
|
|
log(`Renamed to: ${config.binaryName}`);
|
|
}
|
|
|
|
// Make executable on Unix
|
|
if (PLATFORM !== 'win32') {
|
|
fs.chmodSync(binaryOutputPath, 0o755);
|
|
}
|
|
|
|
// Clean up archive
|
|
fs.unlinkSync(archivePath);
|
|
log('Cleaned up archive file');
|
|
|
|
log(`Binary ready at: ${binaryOutputPath}`);
|
|
return binaryOutputPath;
|
|
} catch (err) {
|
|
error(`Failed to download/extract: ${err.message}`);
|
|
// Clean up partial files
|
|
if (fs.existsSync(archivePath)) {
|
|
fs.unlinkSync(archivePath);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy binary to output directory
|
|
*/
|
|
function copyBinary(sourcePath, config) {
|
|
if (dryRun) {
|
|
log(`DRY RUN: Would copy binary from ${sourcePath}`);
|
|
return;
|
|
}
|
|
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const destPath = path.join(outputDir, config.binaryName);
|
|
fs.copyFileSync(sourcePath, destPath);
|
|
|
|
// Make executable on Unix
|
|
if (PLATFORM !== 'win32') {
|
|
fs.chmodSync(destPath, 0o755);
|
|
}
|
|
|
|
log(`Copied binary to: ${destPath}`);
|
|
}
|
|
|
|
/**
|
|
* Write runtime manifest
|
|
*/
|
|
function writeManifest(config) {
|
|
if (dryRun) {
|
|
log('DRY RUN: Would write manifest');
|
|
return;
|
|
}
|
|
|
|
const manifest = {
|
|
source: {
|
|
binPath: config.binaryName,
|
|
binPathLinux: 'zclaw-x86_64-unknown-linux-gnu',
|
|
binPathMac: 'zclaw-x86_64-apple-darwin',
|
|
binPathMacArm: 'zclaw-aarch64-apple-darwin',
|
|
},
|
|
stagedAt: new Date().toISOString(),
|
|
version: zclawVersion === 'latest'
|
|
? new Date().toISOString().split('T')[0].replace(/-/g, '.')
|
|
: zclawVersion,
|
|
runtimeType: 'zclaw',
|
|
description: 'ZCLAW Agent OS - Single binary runtime (~32MB)',
|
|
endpoints: {
|
|
websocket: 'ws://127.0.0.1:4200/ws',
|
|
rest: 'http://127.0.0.1:4200/api',
|
|
},
|
|
platform: {
|
|
os: PLATFORM,
|
|
arch: ARCH,
|
|
},
|
|
};
|
|
|
|
const manifestPath = path.join(outputDir, 'runtime-manifest.json');
|
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
log(`Manifest written to: ${manifestPath}`);
|
|
}
|
|
|
|
/**
|
|
* Write launcher scripts for convenience
|
|
*/
|
|
function writeLauncherScripts(config) {
|
|
if (dryRun) {
|
|
log('DRY RUN: Would write launcher scripts');
|
|
return;
|
|
}
|
|
|
|
// Windows launcher
|
|
const cmdLauncher = [
|
|
'@echo off',
|
|
'REM ZCLAW Agent OS - Bundled Binary Launcher',
|
|
`"%~dp0${config.binaryName}" %*`,
|
|
'',
|
|
].join('\r\n');
|
|
fs.writeFileSync(path.join(outputDir, 'zclaw.cmd'), cmdLauncher, 'utf8');
|
|
|
|
// Unix launcher
|
|
const shLauncher = [
|
|
'#!/bin/bash',
|
|
'# ZCLAW Agent OS - Bundled Binary Launcher',
|
|
`SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"`,
|
|
`exec "$SCRIPT_DIR/${config.binaryName}" "$@"`,
|
|
'',
|
|
].join('\n');
|
|
const shPath = path.join(outputDir, 'zclaw.sh');
|
|
fs.writeFileSync(shPath, shLauncher, 'utf8');
|
|
fs.chmodSync(shPath, 0o755);
|
|
|
|
log('Launcher scripts written');
|
|
}
|
|
|
|
/**
|
|
* Clean old OpenClaw runtime files
|
|
*/
|
|
function cleanOldRuntime() {
|
|
const oldPaths = [
|
|
path.join(outputDir, 'node.exe'),
|
|
path.join(outputDir, 'node_modules'),
|
|
path.join(outputDir, 'openclaw.cmd'),
|
|
];
|
|
|
|
for (const p of oldPaths) {
|
|
if (fs.existsSync(p)) {
|
|
if (dryRun) {
|
|
log(`DRY RUN: Would remove ${p}`);
|
|
} else {
|
|
fs.rmSync(p, { recursive: true, force: true });
|
|
log(`Removed old file: ${p}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function
|
|
*/
|
|
function main() {
|
|
log('='.repeat(60));
|
|
log('ZCLAW Runtime Preparation');
|
|
log('='.repeat(60));
|
|
|
|
const config = getPlatformConfig();
|
|
log(`Platform: ${PLATFORM} (${ARCH})`);
|
|
log(`Binary: ${config.binaryName}`);
|
|
log(`Output: ${outputDir}`);
|
|
|
|
// Clean old OpenClaw runtime
|
|
cleanOldRuntime();
|
|
|
|
// Try to find existing binary
|
|
let binaryPath = findSystemBinary();
|
|
|
|
if (binaryPath) {
|
|
log(`Found ZCLAW in PATH: ${binaryPath}`);
|
|
copyBinary(binaryPath, config);
|
|
} else {
|
|
binaryPath = findInstalledBinary();
|
|
if (binaryPath) {
|
|
log(`Found installed ZCLAW: ${binaryPath}`);
|
|
copyBinary(binaryPath, config);
|
|
} else {
|
|
log('ZCLAW not found locally, downloading...');
|
|
const downloaded = downloadBinary(config);
|
|
if (!downloaded && !dryRun) {
|
|
error('Failed to obtain ZCLAW binary!');
|
|
error('');
|
|
error('Please either:');
|
|
error(' 1. Install ZCLAW: curl -fsSL https://zclaw.sh/install | sh');
|
|
error(' 2. Set ZCLAW_BIN environment variable to binary path');
|
|
error(' 3. Manually download from: https://github.com/RightNow-AI/zclaw/releases');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write supporting files
|
|
writeManifest(config);
|
|
writeLauncherScripts(config);
|
|
|
|
log('='.repeat(60));
|
|
if (dryRun) {
|
|
log('DRY RUN complete. No files were written.');
|
|
} else {
|
|
log('ZCLAW runtime ready for build!');
|
|
}
|
|
log('='.repeat(60));
|
|
}
|
|
|
|
main();
|