File size: 9,667 Bytes
1560bc2 91281d5 1560bc2 77a26e9 1560bc2 eab61c2 1560bc2 c0eeb39 1560bc2 c0eeb39 1560bc2 c0eeb39 1560bc2 91281d5 1560bc2 91281d5 1560bc2 e6c8f63 1560bc2 e6c8f63 1560bc2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
import esbuild from 'esbuild';
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, '..');
const distDir = path.join(rootDir, 'dist');
const bundleDir = path.join(distDir, 'bundle');
// 转换为正斜杠路径(跨平台兼容)
const toSlash = (p) => p.replace(/\\/g, '/');
// 确保目录存在
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
if (!fs.existsSync(bundleDir)) {
fs.mkdirSync(bundleDir, { recursive: true });
}
// 获取命令行参数
const args = process.argv.slice(2);
const targetArg = args.find(arg => arg.startsWith('--target='));
const target = targetArg ? targetArg.split('=')[1] : 'node18-win-x64';
// 解析目标平台
const targetMap = {
'win': 'node18-win-x64',
'win-x64': 'node18-win-x64',
'linux': 'node18-linux-x64',
'linux-x64': 'node18-linux-x64',
'linux-arm64': 'node18-linux-arm64',
'macos': 'node18-macos-x64',
'macos-x64': 'node18-macos-x64',
'macos-arm64': 'node18-macos-arm64',
'all': 'node18-win-x64,node18-linux-x64,node18-linux-arm64,node18-macos-x64,node18-macos-arm64'
};
const resolvedTarget = targetMap[target] || target;
// 输出文件名映射
const outputNameMap = {
'node18-win-x64': 'antigravity-win-x64.exe',
'node18-linux-x64': 'antigravity-linux-x64',
'node18-linux-arm64': 'antigravity-linux-arm64',
'node18-macos-x64': 'antigravity-macos-x64',
'node18-macos-arm64': 'antigravity-macos-arm64'
};
// 平台对应的 bin 文件映射
const binFileMap = {
'node18-win-x64': 'antigravity_requester_windows_amd64.exe',
'node18-linux-x64': 'antigravity_requester_linux_amd64',
'node18-linux-arm64': 'antigravity_requester_android_arm64', // ARM64 使用 Android 版本
'node18-macos-x64': 'antigravity_requester_linux_amd64', // macOS x64 暂用 Linux 版本
'node18-macos-arm64': 'antigravity_requester_android_arm64' // macOS ARM64 暂用 Android 版本
};
console.log('📦 Step 1: Bundling with esbuild...');
// 使用 esbuild 打包成 CommonJS
await esbuild.build({
entryPoints: ['src/server/index.js'],
bundle: true,
platform: 'node',
target: 'node18',
format: 'cjs',
outfile: path.join(bundleDir, 'server.cjs'),
external: [],
minify: false,
sourcemap: false,
// 处理 __dirname 和 __filename
define: {
'import.meta.url': 'importMetaUrl'
},
banner: {
js: `
const importMetaUrl = require('url').pathToFileURL(__filename).href;
const __importMetaDirname = __dirname;
`
},
// 复制静态资源
loader: {
'.node': 'copy'
}
});
console.log('✅ Bundle created: dist/bundle/server.cjs');
// 创建临时 package.json 用于 pkg
// 使用绝对路径引用资源文件
const pkgJson = {
name: 'antigravity-to-openai',
version: '1.0.0',
bin: 'server.cjs',
pkg: {
assets: [
toSlash(path.join(rootDir, 'public', '**/*')),
toSlash(path.join(rootDir, 'public', '*.html')),
toSlash(path.join(rootDir, 'public', '*.css')),
toSlash(path.join(rootDir, 'public', 'js', '*.js')),
toSlash(path.join(rootDir, 'public', 'assets', '*')),
toSlash(path.join(rootDir, 'src', 'bin', '*'))
]
}
};
fs.writeFileSync(
path.join(bundleDir, 'package.json'),
JSON.stringify(pkgJson, null, 2)
);
console.log('📦 Step 2: Building executable with pkg...');
// 执行 pkg 命令的辅助函数
function runPkg(args) {
// 将参数中的路径转换为正斜杠格式
const quotedArgs = args.map(arg => {
if (arg.includes(' ') || arg.includes('\\')) {
return `"${arg.replace(/\\/g, '/')}"`;
}
return arg;
});
const cmd = `npx pkg ${quotedArgs.join(' ')}`;
console.log(`Running: ${cmd}`);
try {
execSync(cmd, {
cwd: rootDir,
stdio: 'inherit',
shell: true
});
} catch (error) {
throw new Error(`pkg failed: ${error.message}`);
}
}
// 构建 pkg 命令
const targets = resolvedTarget.split(',');
const isMultiTarget = targets.length > 1;
try {
const pkgJsonPath = path.join(bundleDir, 'package.json');
// 删除旧的可执行文件(避免 EPERM 错误)
if (isMultiTarget) {
for (const t of targets) {
const oldFile = path.join(distDir, outputNameMap[t] || 'antigravity');
if (fs.existsSync(oldFile)) {
console.log(`🗑️ Removing old file: ${oldFile}`);
fs.unlinkSync(oldFile);
}
}
} else {
const outputName = outputNameMap[resolvedTarget] || 'antigravity';
const oldFile = path.join(distDir, outputName);
if (fs.existsSync(oldFile)) {
console.log(`🗑️ Removing old file: ${oldFile}`);
fs.unlinkSync(oldFile);
}
}
if (isMultiTarget) {
// 多目标构建
runPkg([pkgJsonPath, '--target', resolvedTarget, '--compress', 'GZip', '--out-path', distDir]);
} else {
// 单目标构建
const outputName = outputNameMap[resolvedTarget] || 'antigravity';
const outputPath = path.join(distDir, outputName);
// ARM64 在 Windows 上交叉编译时禁用压缩(避免 spawn UNKNOWN 错误)
const isArm64 = resolvedTarget.includes('arm64');
const isWindows = process.platform === 'win32';
const compressArgs = (isArm64 && isWindows) ? [] : ['--compress', 'GZip'];
runPkg([pkgJsonPath, '--target', resolvedTarget, ...compressArgs, '--output', outputPath]);
}
console.log('✅ Build complete!');
// 复制运行时需要的文件到 dist 目录
console.log('📁 Copying runtime files...');
// 复制 public 目录(排除 images)
const publicSrcDir = path.join(rootDir, 'public');
const publicDestDir = path.join(distDir, 'public');
console.log(` Source: ${publicSrcDir}`);
console.log(` Dest: ${publicDestDir}`);
console.log(` Source exists: ${fs.existsSync(publicSrcDir)}`);
if (fs.existsSync(publicSrcDir)) {
try {
if (fs.existsSync(publicDestDir)) {
console.log(' Removing existing public directory...');
fs.rmSync(publicDestDir, { recursive: true, force: true });
}
// 使用系统命令复制目录(更可靠)
console.log(' Copying public directory...');
if (process.platform === 'win32') {
execSync(`xcopy /E /I /Y /Q "${publicSrcDir}" "${publicDestDir}"`, { stdio: 'pipe', shell: true });
} else {
fs.mkdirSync(publicDestDir, { recursive: true });
execSync(`cp -r "${publicSrcDir}"/* "${publicDestDir}/"`, { stdio: 'pipe', shell: true });
}
// 删除 images 目录(运行时生成,不需要打包)
const imagesDir = path.join(publicDestDir, 'images');
if (fs.existsSync(imagesDir)) {
fs.rmSync(imagesDir, { recursive: true, force: true });
}
console.log(' ✓ Copied public directory');
} catch (err) {
console.error(' ❌ Failed to copy public directory:', err.message);
throw err;
}
} else {
console.error(' ❌ Source public directory not found!');
}
// 复制 bin 目录(只复制对应平台的文件)
const binSrcDir = path.join(rootDir, 'src', 'bin');
const binDestDir = path.join(distDir, 'bin');
if (fs.existsSync(binSrcDir)) {
if (fs.existsSync(binDestDir)) {
fs.rmSync(binDestDir, { recursive: true, force: true });
}
fs.mkdirSync(binDestDir, { recursive: true });
// 只复制对应平台的 bin 文件
const targetBinFiles = isMultiTarget
? [...new Set(targets.map(t => binFileMap[t]).filter(Boolean))] // 多目标:去重后的所有文件
: [binFileMap[resolvedTarget]].filter(Boolean); // 单目标:只复制一个文件
if (targetBinFiles.length > 0) {
for (const binFile of targetBinFiles) {
const srcPath = path.join(binSrcDir, binFile);
const destPath = path.join(binDestDir, binFile);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
console.log(` ✓ Copied bin/${binFile}`);
} else {
console.warn(` ⚠ Warning: bin/${binFile} not found`);
}
}
} else {
// 如果没有映射,复制所有文件(兼容旧行为)
try {
if (process.platform === 'win32') {
execSync(`xcopy /E /I /Y "${binSrcDir}" "${binDestDir}"`, { stdio: 'pipe', shell: true });
} else {
execSync(`cp -r "${binSrcDir}"/* "${binDestDir}/"`, { stdio: 'pipe', shell: true });
}
console.log(' ✓ Copied all bin files');
} catch (err) {
console.error(' ⚠ Warning: Failed to copy bin directory:', err.message);
}
}
}
// 复制配置文件模板(只复制 config.json)
const configSrcPath = path.join(rootDir, 'config.json');
const configDestPath = path.join(distDir, 'config.json');
if (fs.existsSync(configSrcPath)) {
fs.copyFileSync(configSrcPath, configDestPath);
console.log(' ✓ Copied config.json');
}
console.log('');
console.log('🎉 Build successful!');
console.log('');
console.log('📋 Usage:');
console.log(' 1. Copy the dist folder to your target machine');
console.log(' 2. Run the executable (will auto-generate random credentials if not configured)');
console.log(' 3. Optionally create .env file to customize settings');
console.log('');
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
} finally {
// 清理临时文件
if (fs.existsSync(bundleDir)) {
fs.rmSync(bundleDir, { recursive: true, force: true });
console.log('🧹 Cleaned up temporary files');
}
} |