1
0

build.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. #!/usr/bin/env node
  2. /**
  3. * Tauri 构建脚本
  4. * Build script for Claude AI Installer (Tauri)
  5. *
  6. * 用法 / Usage:
  7. * node scripts/build.js [options]
  8. *
  9. * 选项 / Options:
  10. * --debug, -d 调试模式构建
  11. * --skip-frontend 跳过前端构建
  12. * --help, -h 显示帮助信息
  13. */
  14. import { execSync } from 'child_process';
  15. import path from 'path';
  16. import fs from 'fs';
  17. import { fileURLToPath } from 'url';
  18. const __filename = fileURLToPath(import.meta.url);
  19. const __dirname = path.dirname(__filename);
  20. // 颜色输出
  21. const colors = {
  22. reset: '\x1b[0m',
  23. bright: '\x1b[1m',
  24. red: '\x1b[31m',
  25. green: '\x1b[32m',
  26. yellow: '\x1b[33m',
  27. blue: '\x1b[34m',
  28. cyan: '\x1b[36m',
  29. };
  30. const log = {
  31. info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
  32. success: (msg) => console.log(`${colors.green}✔${colors.reset} ${msg}`),
  33. warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
  34. error: (msg) => console.log(`${colors.red}✖${colors.reset} ${msg}`),
  35. step: (msg) => console.log(`\n${colors.cyan}${colors.bright}▶ ${msg}${colors.reset}`),
  36. };
  37. // 项目路径
  38. const PROJECT_ROOT = path.resolve(__dirname, '..');
  39. const TAURI_DIR = path.join(PROJECT_ROOT, 'src-tauri');
  40. // 解析命令行参数
  41. function parseArgs() {
  42. const args = process.argv.slice(2);
  43. const options = {
  44. debug: false,
  45. skipFrontend: false,
  46. help: false,
  47. };
  48. for (let i = 0; i < args.length; i++) {
  49. const arg = args[i];
  50. switch (arg) {
  51. case '--debug':
  52. case '-d':
  53. options.debug = true;
  54. break;
  55. case '--skip-frontend':
  56. case '-s':
  57. options.skipFrontend = true;
  58. break;
  59. case '--help':
  60. case '-h':
  61. options.help = true;
  62. break;
  63. }
  64. }
  65. return options;
  66. }
  67. // 显示帮助信息
  68. function showHelp() {
  69. console.log(`
  70. ${colors.bright}Claude AI Installer 构建脚本 (Tauri)${colors.reset}
  71. ${colors.cyan}用法:${colors.reset}
  72. node scripts/build.js [options]
  73. ${colors.cyan}选项:${colors.reset}
  74. --debug, -d 以调试模式构建
  75. --skip-frontend, -s 跳过前端构建
  76. --help, -h 显示此帮助信息
  77. ${colors.cyan}示例:${colors.reset}
  78. node scripts/build.js # 完整发布构建
  79. node scripts/build.js --debug # 调试构建
  80. node scripts/build.js -s # 跳过前端,仅构建 Tauri
  81. ${colors.cyan}输出 (发布模式):${colors.reset}
  82. src-tauri/target/release/bundle/
  83. ├── nsis/*.exe # NSIS 安装程序
  84. └── msi/*.msi # MSI 安装程序
  85. src-tauri/target/release/
  86. └── claude-ai-installer.exe # 可执行文件
  87. ${colors.cyan}输出 (调试模式):${colors.reset}
  88. src-tauri/target/debug/
  89. └── claude-ai-installer.exe # 调试版可执行文件
  90. `);
  91. }
  92. // 执行命令
  93. function exec(command, options = {}) {
  94. log.info(`执行: ${command}`);
  95. try {
  96. execSync(command, {
  97. stdio: 'inherit',
  98. cwd: PROJECT_ROOT,
  99. ...options,
  100. });
  101. return true;
  102. } catch (error) {
  103. log.error(`命令执行失败: ${command}`);
  104. return false;
  105. }
  106. }
  107. // 获取版本号
  108. function getVersion() {
  109. const tauriConfPath = path.join(TAURI_DIR, 'tauri.conf.json');
  110. const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8'));
  111. return tauriConf.version;
  112. }
  113. // 获取产品名称
  114. function getProductName() {
  115. const tauriConfPath = path.join(TAURI_DIR, 'tauri.conf.json');
  116. const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, 'utf8'));
  117. return tauriConf.productName || 'Claude AI Installer';
  118. }
  119. // 构建 Tauri 应用
  120. function buildTauri(options) {
  121. log.step(`构建 Tauri 应用 (${options.debug ? '调试' : '发布'}模式)...`);
  122. let command = 'npm run tauri build';
  123. if (options.debug) {
  124. command += ' -- --debug';
  125. }
  126. if (!exec(command)) {
  127. throw new Error('Tauri 构建失败');
  128. }
  129. log.success('Tauri 构建完成');
  130. }
  131. // 重命名构建产物,使用与 Electron 版本一致的命名格式
  132. function renameArtifacts(options) {
  133. log.step('重命名构建产物...');
  134. const mode = options.debug ? 'debug' : 'release';
  135. const targetDir = path.join(TAURI_DIR, 'target', mode);
  136. const bundleDir = path.join(targetDir, 'bundle');
  137. const version = getVersion();
  138. // 创建 portable 版本(复制可执行文件到 bundle 目录)
  139. const exeName = 'claude-ai-installer.exe';
  140. const exePath = path.join(targetDir, exeName);
  141. if (fs.existsSync(exePath)) {
  142. // 确保 bundle 目录存在
  143. if (!fs.existsSync(bundleDir)) {
  144. fs.mkdirSync(bundleDir, { recursive: true });
  145. }
  146. // 新文件名格式: Claude-AI-Installer-{version}-win-x64-portable.exe
  147. const portableName = `Claude-AI-Installer-${version}-win-x64-portable.exe`;
  148. const portablePath = path.join(bundleDir, portableName);
  149. // 如果目标文件已存在,先删除
  150. if (fs.existsSync(portablePath)) {
  151. fs.unlinkSync(portablePath);
  152. }
  153. fs.copyFileSync(exePath, portablePath);
  154. log.success(`创建便携版: ${portableName}`);
  155. }
  156. // 重命名 NSIS 安装程序
  157. const nsisDir = path.join(bundleDir, 'nsis');
  158. if (fs.existsSync(nsisDir)) {
  159. const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe'));
  160. for (const file of files) {
  161. const oldPath = path.join(nsisDir, file);
  162. // 新文件名格式: Claude-AI-Installer-{version}-win-x64-setup.exe
  163. const newName = `Claude-AI-Installer-${version}-win-x64-setup.exe`;
  164. const newPath = path.join(nsisDir, newName);
  165. if (file !== newName) {
  166. // 如果目标文件已存在,先删除
  167. if (fs.existsSync(newPath)) {
  168. fs.unlinkSync(newPath);
  169. }
  170. fs.renameSync(oldPath, newPath);
  171. log.success(`重命名: ${file} -> ${newName}`);
  172. }
  173. }
  174. }
  175. // 重命名 MSI 安装程序
  176. const msiDir = path.join(bundleDir, 'msi');
  177. if (fs.existsSync(msiDir)) {
  178. const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi'));
  179. for (const file of files) {
  180. const oldPath = path.join(msiDir, file);
  181. // 新文件名格式: Claude-AI-Installer-{version}-win-x64.msi
  182. const newName = `Claude-AI-Installer-${version}-win-x64.msi`;
  183. const newPath = path.join(msiDir, newName);
  184. if (file !== newName) {
  185. // 如果目标文件已存在,先删除
  186. if (fs.existsSync(newPath)) {
  187. fs.unlinkSync(newPath);
  188. }
  189. fs.renameSync(oldPath, newPath);
  190. log.success(`重命名: ${file} -> ${newName}`);
  191. }
  192. }
  193. }
  194. }
  195. // 显示构建结果
  196. function showResults(options) {
  197. log.step('构建结果:');
  198. const mode = options.debug ? 'debug' : 'release';
  199. const targetDir = path.join(TAURI_DIR, 'target', mode);
  200. const bundleDir = path.join(targetDir, 'bundle');
  201. console.log('');
  202. // 显示便携版
  203. const portableFiles = fs.existsSync(bundleDir)
  204. ? fs.readdirSync(bundleDir).filter(f => f.endsWith('-portable.exe'))
  205. : [];
  206. for (const file of portableFiles) {
  207. const filePath = path.join(bundleDir, file);
  208. const stats = fs.statSync(filePath);
  209. const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
  210. console.log(` ${colors.green}•${colors.reset} ${file} (${sizeMB} MB) [便携版]`);
  211. }
  212. // 显示 NSIS 安装程序
  213. const nsisDir = path.join(bundleDir, 'nsis');
  214. if (fs.existsSync(nsisDir)) {
  215. const files = fs.readdirSync(nsisDir).filter(f => f.endsWith('.exe'));
  216. for (const file of files) {
  217. const filePath = path.join(nsisDir, file);
  218. const stats = fs.statSync(filePath);
  219. const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
  220. console.log(` ${colors.green}•${colors.reset} nsis/${file} (${sizeMB} MB)`);
  221. }
  222. }
  223. // 显示 MSI 安装程序
  224. const msiDir = path.join(bundleDir, 'msi');
  225. if (fs.existsSync(msiDir)) {
  226. const files = fs.readdirSync(msiDir).filter(f => f.endsWith('.msi'));
  227. for (const file of files) {
  228. const filePath = path.join(msiDir, file);
  229. const stats = fs.statSync(filePath);
  230. const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
  231. console.log(` ${colors.green}•${colors.reset} msi/${file} (${sizeMB} MB)`);
  232. }
  233. }
  234. console.log('');
  235. log.info(`输出目录: ${targetDir}`);
  236. if (fs.existsSync(bundleDir)) {
  237. log.info(`安装包目录: ${bundleDir}`);
  238. }
  239. }
  240. // 主函数
  241. async function main() {
  242. const options = parseArgs();
  243. if (options.help) {
  244. showHelp();
  245. process.exit(0);
  246. }
  247. console.log(`
  248. ${colors.bright}╔════════════════════════════════════════════╗
  249. ║ Claude AI Installer 构建脚本 ║
  250. ║ (Tauri 2.0) ║
  251. ╚════════════════════════════════════════════╝${colors.reset}
  252. `);
  253. log.info(`构建模式: ${options.debug ? '调试' : '发布'}`);
  254. log.info(`版本: ${getVersion()}`);
  255. log.info(`产品名称: ${getProductName()}`);
  256. const startTime = Date.now();
  257. try {
  258. // 构建 Tauri 应用
  259. buildTauri(options);
  260. // 重命名构建产物(仅发布模式)
  261. if (!options.debug) {
  262. renameArtifacts(options);
  263. }
  264. // 显示结果
  265. showResults(options);
  266. const duration = ((Date.now() - startTime) / 1000).toFixed(1);
  267. log.success(`构建完成! 耗时: ${duration}s`);
  268. } catch (error) {
  269. log.error(error.message);
  270. process.exit(1);
  271. }
  272. }
  273. main();