#!/usr/bin/env node /** * 从 node_modules 复制 agent-gui-server 到 resources/ * * 前提: * 1. pnpm install 已执行(workspace 链接生效) * 2. agent-gui-server 已构建(npm run build) * * 产物: * resources/agent-gui-server/ * ├── dist/index.js — esbuild CLI bundle(ESM,原生依赖标记为 external) * ├── dist/lib.bundle.cjs — esbuild SDK bundle(CJS,供 Electron runtime require) * ├── node_modules/ — external 原生依赖(sharp, @nut-tree-fork/*, clipboardy) * └── package.json — 精简版(name/version/main/bin) * * 打包时 electron-builder extraResources 会打包到 * .app/Contents/Resources/agent-gui-server/ */ const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); const { getProjectRoot } = require('../utils/project-paths'); const projectRoot = getProjectRoot(); const srcDir = path.join(projectRoot, 'node_modules', 'agent-gui-server'); const destDir = path.join(projectRoot, 'resources', 'agent-gui-server'); // esbuild 中标记为 external 的原生依赖(需要在运行时可用) const NATIVE_EXTERNALS = ['sharp', '@nut-tree-fork/nut-js', 'clipboardy']; function main() { // 1. 验证 node_modules 中存在 agent-gui-server if (!fs.existsSync(path.join(srcDir, 'package.json'))) { console.error('[prepare-gui-server] node_modules 中未找到 agent-gui-server'); console.error('[prepare-gui-server] 请先执行 pnpm install'); process.exit(1); } const srcPkg = JSON.parse(fs.readFileSync(path.join(srcDir, 'package.json'), 'utf8')); console.log(`[prepare-gui-server] 源码版本: ${srcPkg.name}@${srcPkg.version}`); // 2. 验证必要文件存在 const srcIndexJs = path.join(srcDir, 'dist', 'index.js'); if (!fs.existsSync(srcIndexJs)) { console.error(`[prepare-gui-server] CLI 入口不存在: ${srcIndexJs}`); console.error('[prepare-gui-server] 请先在 crates/agent-gui-server 中执行 npm run build'); process.exit(1); } // 3. 检查目标是否已是最新版本 const destPkgPath = path.join(destDir, 'package.json'); const destLibBundle = path.join(destDir, 'dist', 'lib.bundle.cjs'); if (fs.existsSync(destPkgPath) && fs.existsSync(destLibBundle)) { try { const destPkg = JSON.parse(fs.readFileSync(destPkgPath, 'utf8')); if (destPkg.version === srcPkg.version) { const destIndexJs = path.join(destDir, 'dist', 'index.js'); if (fs.existsSync(destIndexJs)) { const srcSize = fs.statSync(srcIndexJs).size; const destSize = fs.statSync(destIndexJs).size; if (srcSize === destSize) { console.log(`[prepare-gui-server] ${srcPkg.version} 已是最新,跳过`); return; } } } } catch { // 目标损坏,重新复制 } } // 4. 清理并创建目标目录 console.log('[prepare-gui-server] 复制到 resources/agent-gui-server/...'); if (fs.existsSync(destDir)) { fs.rmSync(destDir, { recursive: true }); } fs.mkdirSync(path.join(destDir, 'dist'), { recursive: true }); // 5. 复制 dist/index.js (esbuild CLI bundle) const destIndexJs = path.join(destDir, 'dist', 'index.js'); fs.copyFileSync(srcIndexJs, destIndexJs); fs.chmodSync(destIndexJs, 0o755); console.log(` dist/index.js (${(fs.statSync(destIndexJs).size / 1024).toFixed(0)} KB)`); // 5b. 复制 dist/lib.bundle.cjs (esbuild CJS SDK bundle, for Electron runtime require) const srcLibBundle = path.join(srcDir, 'dist', 'lib.bundle.cjs'); if (fs.existsSync(srcLibBundle)) { fs.copyFileSync(srcLibBundle, destLibBundle); console.log(` dist/lib.bundle.cjs (${(fs.statSync(destLibBundle).size / 1024).toFixed(0)} KB)`); } else { console.warn('[prepare-gui-server] dist/lib.bundle.cjs not found — CJS SDK bundle will be unavailable'); } // 6. 安装 external 原生依赖到 resources/agent-gui-server/node_modules/ // 使用 npm install 确保原生模块为当前平台编译 const depsToInstall = []; for (const dep of NATIVE_EXTERNALS) { const ver = srcPkg.dependencies?.[dep]; if (ver) { depsToInstall.push(`${dep}@${ver.replace(/^[\^~]/, '')}`); } } if (depsToInstall.length > 0) { // 先写一个临时 package.json 让 npm install 工作 const tmpPkg = { name: 'agent-gui-server-runtime', version: '0.0.0', private: true }; fs.writeFileSync(destPkgPath, JSON.stringify(tmpPkg, null, 2) + '\n'); const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; // CI 交叉编译支持:TARGET_ARCH 环境变量指定目标架构(如 macOS arm64 runner 构建 x64 包) // npm install 通过 --cpu 标志安装目标架构的原生模块 const targetArch = process.env.TARGET_ARCH; const archFlag = targetArch ? ` --cpu=${targetArch}` : ''; const archLabel = targetArch ? ` (target: ${targetArch})` : ''; console.log(` 安装原生依赖${archLabel}: ${depsToInstall.join(', ')}...`); try { execSync(`${npmCmd} install --no-save${archFlag} ${depsToInstall.join(' ')}`, { cwd: destDir, stdio: 'pipe', }); console.log(' node_modules/ (原生依赖安装完成)'); } catch (e) { console.error('[prepare-gui-server] 原生依赖安装失败:', e.stderr?.toString() || e.message); process.exit(1); } } // 7. 生成最终精简版 package.json const slimPkg = { name: srcPkg.name, version: srcPkg.version, type: 'module', main: './dist/lib.bundle.cjs', bin: { 'agent-gui-server': './dist/index.js' }, }; fs.writeFileSync(destPkgPath, JSON.stringify(slimPkg, null, 2) + '\n'); console.log(' package.json (slim)'); console.log(`[prepare-gui-server] ✓ resources/agent-gui-server/ (${srcPkg.version})`); } main();