|
|
@@ -43,8 +43,6 @@ const log = {
|
|
|
// 项目路径
|
|
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
|
const TAURI_DIR = path.join(PROJECT_ROOT, 'src-tauri');
|
|
|
-const RELEASE_DIR = path.join(PROJECT_ROOT, 'release');
|
|
|
-const LAUNCHER_DIR = path.join(PROJECT_ROOT, 'launcher');
|
|
|
|
|
|
// 解析命令行参数
|
|
|
function parseArgs() {
|
|
|
@@ -94,12 +92,16 @@ ${colors.cyan}示例:${colors.reset}
|
|
|
node scripts/build.js --debug # 调试构建
|
|
|
node scripts/build.js -s # 跳过前端,仅构建 Tauri
|
|
|
|
|
|
-${colors.cyan}输出:${colors.reset}
|
|
|
- release/ # 最终发布目录
|
|
|
- ├── *-nsis.exe # NSIS 安装程序
|
|
|
- ├── *-portable.exe # 便携版
|
|
|
- └── Claude-AI-Installer-x.x.x-portable/ # 带启动器的便携版
|
|
|
- └── Claude-AI-Installer-x.x.x-portable.zip # 便携版压缩包
|
|
|
+${colors.cyan}输出 (发布模式):${colors.reset}
|
|
|
+ src-tauri/target/release/bundle/
|
|
|
+ ├── nsis/*.exe # NSIS 安装程序
|
|
|
+ └── msi/*.msi # MSI 安装程序
|
|
|
+ src-tauri/target/release/
|
|
|
+ └── claude-ai-installer.exe # 可执行文件
|
|
|
+
|
|
|
+${colors.cyan}输出 (调试模式):${colors.reset}
|
|
|
+ src-tauri/target/debug/
|
|
|
+ └── claude-ai-installer.exe # 调试版可执行文件
|
|
|
`);
|
|
|
}
|
|
|
|
|
|
@@ -133,19 +135,6 @@ function getProductName() {
|
|
|
return tauriConf.productName || 'Claude AI Installer';
|
|
|
}
|
|
|
|
|
|
-// 清理旧的构建文件
|
|
|
-function cleanBuild() {
|
|
|
- log.step('清理旧的构建文件...');
|
|
|
-
|
|
|
- // 清空 release 目录
|
|
|
- if (fs.existsSync(RELEASE_DIR)) {
|
|
|
- fs.rmSync(RELEASE_DIR, { recursive: true, force: true });
|
|
|
- }
|
|
|
- fs.mkdirSync(RELEASE_DIR, { recursive: true });
|
|
|
-
|
|
|
- log.success('清理完成');
|
|
|
-}
|
|
|
-
|
|
|
// 构建 Tauri 应用
|
|
|
function buildTauri(options) {
|
|
|
log.step(`构建 Tauri 应用 (${options.debug ? '调试' : '发布'}模式)...`);
|
|
|
@@ -162,228 +151,55 @@ function buildTauri(options) {
|
|
|
log.success('Tauri 构建完成');
|
|
|
}
|
|
|
|
|
|
-// 获取 Tauri 生成的 exe 文件名(小写,空格转连字符)
|
|
|
-function getTauriExeName() {
|
|
|
- const productName = getProductName();
|
|
|
- // Tauri 将产品名称转换为小写并用连字符替换空格
|
|
|
- return productName.toLowerCase().replace(/\s+/g, '-') + '.exe';
|
|
|
-}
|
|
|
-
|
|
|
-// 复制构建产物到 release 目录
|
|
|
-function copyArtifacts(options) {
|
|
|
- log.step('复制构建产物到 release 目录...');
|
|
|
+// 显示构建结果
|
|
|
+function showResults(options) {
|
|
|
+ log.step('构建结果:');
|
|
|
|
|
|
const mode = options.debug ? 'debug' : 'release';
|
|
|
- const bundleDir = path.join(TAURI_DIR, 'target', mode, 'bundle');
|
|
|
- const nsisDir = path.join(bundleDir, 'nsis');
|
|
|
-
|
|
|
- // 复制 NSIS 安装程序
|
|
|
- if (fs.existsSync(nsisDir)) {
|
|
|
- const files = fs.readdirSync(nsisDir);
|
|
|
- for (const file of files) {
|
|
|
- if (file.endsWith('.exe')) {
|
|
|
- const src = path.join(nsisDir, file);
|
|
|
- const dest = path.join(RELEASE_DIR, file);
|
|
|
- fs.copyFileSync(src, dest);
|
|
|
- log.info(`复制: ${file}`);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 复制独立 exe (portable)
|
|
|
- const exeName = getTauriExeName();
|
|
|
- const portableExe = path.join(TAURI_DIR, 'target', mode, exeName);
|
|
|
- if (fs.existsSync(portableExe)) {
|
|
|
- const version = getVersion();
|
|
|
- const portableName = `Claude-AI-Installer_${version}_x64-portable.exe`;
|
|
|
- const dest = path.join(RELEASE_DIR, portableName);
|
|
|
- fs.copyFileSync(portableExe, dest);
|
|
|
- log.info(`复制便携版: ${portableName}`);
|
|
|
- } else {
|
|
|
- log.warn(`未找到便携版 exe: ${portableExe}`);
|
|
|
- }
|
|
|
-
|
|
|
- log.success('构建产物复制完成');
|
|
|
-}
|
|
|
-
|
|
|
-// ==================== Portable 版本构建(带启动器)====================
|
|
|
-
|
|
|
-/**
|
|
|
- * 查找 portable exe 文件
|
|
|
- */
|
|
|
-function findPortableExe() {
|
|
|
- const files = fs.readdirSync(RELEASE_DIR);
|
|
|
- return files.find(f => f.includes('portable') && f.endsWith('.exe'));
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 使用 pkg 打包启动器
|
|
|
- */
|
|
|
-function buildLauncher() {
|
|
|
- log.step('构建启动器...');
|
|
|
-
|
|
|
- // 检查 launcher.js 是否存在
|
|
|
- const launcherScript = path.join(LAUNCHER_DIR, 'launcher.js');
|
|
|
- if (!fs.existsSync(launcherScript)) {
|
|
|
- log.warn('launcher.js 不存在,跳过启动器构建');
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // 检查是否安装了 pkg
|
|
|
- try {
|
|
|
- execSync('npx pkg --version', { stdio: 'ignore' });
|
|
|
- } catch {
|
|
|
- log.info('安装 pkg...');
|
|
|
- execSync('npm install -g pkg', { stdio: 'inherit' });
|
|
|
- }
|
|
|
-
|
|
|
- const outputPath = path.join(RELEASE_DIR, 'launcher.exe');
|
|
|
-
|
|
|
- // 使用 pkg 打包
|
|
|
- execSync(`npx pkg "${launcherScript}" --target node18-win-x64 --output "${outputPath}"`, {
|
|
|
- stdio: 'inherit',
|
|
|
- cwd: PROJECT_ROOT,
|
|
|
- });
|
|
|
-
|
|
|
- log.success(`启动器构建完成: ${outputPath}`);
|
|
|
- return outputPath;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 创建 Portable 目录结构
|
|
|
- */
|
|
|
-function createPortableStructure(portableExe, launcherExe) {
|
|
|
- const version = getVersion();
|
|
|
- const portableDirName = `Claude-AI-Installer-${version}-portable`;
|
|
|
- const portableDir = path.join(RELEASE_DIR, portableDirName);
|
|
|
- const appDir = path.join(portableDir, 'app');
|
|
|
- const updateDir = path.join(portableDir, 'update');
|
|
|
-
|
|
|
- log.step('创建 Portable 目录结构...');
|
|
|
-
|
|
|
- // 清理旧目录
|
|
|
- if (fs.existsSync(portableDir)) {
|
|
|
- fs.rmSync(portableDir, { recursive: true });
|
|
|
- }
|
|
|
-
|
|
|
- // 创建目录
|
|
|
- fs.mkdirSync(appDir, { recursive: true });
|
|
|
- fs.mkdirSync(updateDir, { recursive: true });
|
|
|
-
|
|
|
- // 复制启动器
|
|
|
- const launcherDest = path.join(portableDir, 'Claude-AI-Installer.exe');
|
|
|
- fs.copyFileSync(launcherExe, launcherDest);
|
|
|
- log.info(`复制启动器: ${launcherDest}`);
|
|
|
+ const targetDir = path.join(TAURI_DIR, 'target', mode);
|
|
|
+ const bundleDir = path.join(targetDir, 'bundle');
|
|
|
|
|
|
- // 复制主程序到 app 目录
|
|
|
- const portableExePath = path.join(RELEASE_DIR, portableExe);
|
|
|
- const appExeName = `Claude-AI-Installer-${version}.exe`;
|
|
|
- const appExeDest = path.join(appDir, appExeName);
|
|
|
- fs.copyFileSync(portableExePath, appExeDest);
|
|
|
- log.info(`复制主程序: ${appExeDest}`);
|
|
|
-
|
|
|
- // 创建 .gitkeep 文件保持 update 目录
|
|
|
- fs.writeFileSync(path.join(updateDir, '.gitkeep'), '');
|
|
|
-
|
|
|
- // 创建 README
|
|
|
- const readme = `Claude AI Installer - Portable 版本
|
|
|
-
|
|
|
-使用方法:
|
|
|
-1. 双击 Claude-AI-Installer.exe 启动程序
|
|
|
-2. 程序会自动检查更新
|
|
|
-3. 更新下载后,重启程序即可完成更新
|
|
|
-
|
|
|
-目录说明:
|
|
|
-- Claude-AI-Installer.exe: 启动器
|
|
|
-- app/: 主程序目录
|
|
|
-- update/: 更新文件临时目录
|
|
|
-
|
|
|
-版本: ${version}
|
|
|
-`;
|
|
|
- fs.writeFileSync(path.join(portableDir, 'README.txt'), readme, 'utf8');
|
|
|
-
|
|
|
- log.success(`Portable 目录创建完成: ${portableDir}`);
|
|
|
-
|
|
|
- // 创建 zip 包
|
|
|
- createPortableZip(portableDir, portableDirName);
|
|
|
-
|
|
|
- return portableDir;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 创建 ZIP 压缩包
|
|
|
- */
|
|
|
-function createPortableZip(sourceDir, name) {
|
|
|
- const zipPath = path.join(RELEASE_DIR, `${name}.zip`);
|
|
|
-
|
|
|
- log.step('创建 ZIP 压缩包...');
|
|
|
-
|
|
|
- // 使用 PowerShell 创建 zip
|
|
|
- const psCommand = `Compress-Archive -Path "${sourceDir}\\*" -DestinationPath "${zipPath}" -Force`;
|
|
|
- execSync(`powershell -Command "${psCommand}"`, { stdio: 'inherit' });
|
|
|
-
|
|
|
- log.success(`ZIP 压缩包创建完成: ${zipPath}`);
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 构建 Portable 版本(带启动器)
|
|
|
- */
|
|
|
-function buildPortableVersion() {
|
|
|
- log.step('构建 Portable 版本(带启动器)...');
|
|
|
-
|
|
|
- // 查找 portable exe
|
|
|
- const portableExe = findPortableExe();
|
|
|
- if (!portableExe) {
|
|
|
- log.warn('未找到 portable exe 文件,跳过带启动器的 Portable 版本构建');
|
|
|
- return;
|
|
|
- }
|
|
|
- log.info(`找到 Portable 文件: ${portableExe}`);
|
|
|
+ console.log('');
|
|
|
|
|
|
- // 构建启动器
|
|
|
- const launcherExe = buildLauncher();
|
|
|
- if (!launcherExe) {
|
|
|
- log.warn('启动器构建失败,跳过带启动器的 Portable 版本构建');
|
|
|
- return;
|
|
|
+ // 显示可执行文件
|
|
|
+ const exeName = 'claude-ai-installer.exe';
|
|
|
+ const exePath = path.join(targetDir, exeName);
|
|
|
+ if (fs.existsSync(exePath)) {
|
|
|
+ const stats = fs.statSync(exePath);
|
|
|
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
|
+ console.log(` ${colors.green}•${colors.reset} ${exeName} (${sizeMB} MB)`);
|
|
|
+ log.info(` 路径: ${exePath}`);
|
|
|
}
|
|
|
|
|
|
- // 创建 Portable 目录结构
|
|
|
- createPortableStructure(portableExe, launcherExe);
|
|
|
-
|
|
|
- // 清理临时的 launcher.exe
|
|
|
- fs.unlinkSync(launcherExe);
|
|
|
-
|
|
|
- log.success('Portable 版本构建完成');
|
|
|
-}
|
|
|
-
|
|
|
-// 显示构建结果
|
|
|
-function showResults() {
|
|
|
- log.step('构建结果:');
|
|
|
-
|
|
|
- if (!fs.existsSync(RELEASE_DIR)) {
|
|
|
- log.warn('release 目录不存在');
|
|
|
- return;
|
|
|
+ // 显示 NSIS 安装程序
|
|
|
+ const nsisDir = path.join(bundleDir, 'nsis');
|
|
|
+ if (fs.existsSync(nsisDir)) {
|
|
|
+ const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe'));
|
|
|
+ for (const file of files) {
|
|
|
+ const filePath = path.join(nsisDir, file);
|
|
|
+ const stats = fs.statSync(filePath);
|
|
|
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
|
+ console.log(` ${colors.green}•${colors.reset} nsis/${file} (${sizeMB} MB)`);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- const files = fs.readdirSync(RELEASE_DIR);
|
|
|
- const packages = files.filter(f => {
|
|
|
- const ext = path.extname(f).toLowerCase();
|
|
|
- return ['.exe', '.zip'].includes(ext);
|
|
|
- });
|
|
|
-
|
|
|
- if (packages.length === 0) {
|
|
|
- log.warn('未找到打包文件');
|
|
|
- return;
|
|
|
+ // 显示 MSI 安装程序
|
|
|
+ const msiDir = path.join(bundleDir, 'msi');
|
|
|
+ if (fs.existsSync(msiDir)) {
|
|
|
+ const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi'));
|
|
|
+ for (const file of files) {
|
|
|
+ const filePath = path.join(msiDir, file);
|
|
|
+ const stats = fs.statSync(filePath);
|
|
|
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
|
+ console.log(` ${colors.green}•${colors.reset} msi/${file} (${sizeMB} MB)`);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
console.log('');
|
|
|
- for (const pkg of packages) {
|
|
|
- const filePath = path.join(RELEASE_DIR, pkg);
|
|
|
- const stats = fs.statSync(filePath);
|
|
|
- const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
|
- console.log(` ${colors.green}•${colors.reset} ${pkg} (${sizeMB} MB)`);
|
|
|
+ log.info(`输出目录: ${targetDir}`);
|
|
|
+ if (fs.existsSync(bundleDir)) {
|
|
|
+ log.info(`安装包目录: ${bundleDir}`);
|
|
|
}
|
|
|
- console.log('');
|
|
|
- log.info(`输出目录: ${RELEASE_DIR}`);
|
|
|
}
|
|
|
|
|
|
// 主函数
|
|
|
@@ -403,25 +219,17 @@ ${colors.bright}╔════════════════════
|
|
|
`);
|
|
|
|
|
|
log.info(`构建模式: ${options.debug ? '调试' : '发布'}`);
|
|
|
- log.info(`跳过前端: ${options.skipFrontend}`);
|
|
|
+ log.info(`版本: ${getVersion()}`);
|
|
|
+ log.info(`产品名称: ${getProductName()}`);
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
try {
|
|
|
- // 清理旧的构建文件
|
|
|
- cleanBuild();
|
|
|
-
|
|
|
// 构建 Tauri 应用
|
|
|
buildTauri(options);
|
|
|
|
|
|
- // 复制构建产物
|
|
|
- copyArtifacts(options);
|
|
|
-
|
|
|
- // 构建带启动器的 Portable 版本
|
|
|
- buildPortableVersion();
|
|
|
-
|
|
|
// 显示结果
|
|
|
- showResults();
|
|
|
+ showResults(options);
|
|
|
|
|
|
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
log.success(`构建完成! 耗时: ${duration}s`);
|