launcher.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #!/usr/bin/env node
  2. /**
  3. * Claude AI Installer - Portable 启动器
  4. *
  5. * 功能:
  6. * 1. 检查 update 目录是否有新版本
  7. * 2. 如果有,替换 app 目录中的旧版本
  8. * 3. 启动主程序
  9. */
  10. const fs = require('fs')
  11. const path = require('path')
  12. const { spawn } = require('child_process')
  13. // 获取启动器所在目录
  14. const launcherDir = path.dirname(process.execPath)
  15. const appDir = path.join(launcherDir, 'app')
  16. const updateDir = path.join(launcherDir, 'update')
  17. const logFile = path.join(launcherDir, 'launcher.log')
  18. /**
  19. * 写入日志
  20. */
  21. function log(message) {
  22. const timestamp = new Date().toISOString()
  23. const logMessage = `[${timestamp}] ${message}\n`
  24. console.log(message)
  25. try {
  26. fs.appendFileSync(logFile, logMessage)
  27. } catch (e) {
  28. // 忽略日志写入错误
  29. }
  30. }
  31. /**
  32. * 查找目录中的 exe 文件
  33. */
  34. function findExeFile(dir) {
  35. try {
  36. const files = fs.readdirSync(dir)
  37. const exeFile = files.find(f => f.endsWith('.exe') && f.includes('Claude-AI-Installer'))
  38. return exeFile ? path.join(dir, exeFile) : null
  39. } catch (e) {
  40. return null
  41. }
  42. }
  43. /**
  44. * 删除目录及其内容
  45. */
  46. function removeDir(dir) {
  47. if (fs.existsSync(dir)) {
  48. fs.rmSync(dir, { recursive: true, force: true })
  49. }
  50. }
  51. /**
  52. * 复制文件
  53. */
  54. function copyFile(src, dest) {
  55. fs.copyFileSync(src, dest)
  56. }
  57. /**
  58. * 应用更新
  59. */
  60. function applyUpdate() {
  61. const updateExe = findExeFile(updateDir)
  62. if (!updateExe) {
  63. log('没有找到更新文件')
  64. return false
  65. }
  66. log(`发现更新文件: ${updateExe}`)
  67. try {
  68. // 确保 app 目录存在
  69. if (!fs.existsSync(appDir)) {
  70. fs.mkdirSync(appDir, { recursive: true })
  71. }
  72. // 删除旧版本
  73. const oldExe = findExeFile(appDir)
  74. if (oldExe) {
  75. log(`删除旧版本: ${oldExe}`)
  76. fs.unlinkSync(oldExe)
  77. }
  78. // 移动新版本到 app 目录
  79. const newExeName = path.basename(updateExe)
  80. const destPath = path.join(appDir, newExeName)
  81. log(`安装新版本: ${destPath}`)
  82. // 复制文件(而不是移动,以便保留更新目录结构)
  83. copyFile(updateExe, destPath)
  84. // 清理更新目录
  85. removeDir(updateDir)
  86. log('更新完成')
  87. return true
  88. } catch (error) {
  89. log(`更新失败: ${error.message}`)
  90. return false
  91. }
  92. }
  93. /**
  94. * 启动主程序
  95. */
  96. function launchApp() {
  97. const appExe = findExeFile(appDir)
  98. if (!appExe) {
  99. log('错误: 找不到主程序')
  100. // 尝试在启动器目录查找
  101. const fallbackExe = findExeFile(launcherDir)
  102. if (fallbackExe && !fallbackExe.includes('launcher')) {
  103. log(`使用备用程序: ${fallbackExe}`)
  104. spawn(fallbackExe, [], { detached: true, stdio: 'ignore' }).unref()
  105. return
  106. }
  107. process.exit(1)
  108. }
  109. log(`启动主程序: ${appExe}`)
  110. // 启动主程序并退出启动器
  111. const child = spawn(appExe, [], {
  112. detached: true,
  113. stdio: 'ignore',
  114. cwd: appDir
  115. })
  116. child.unref()
  117. }
  118. /**
  119. * 主函数
  120. */
  121. function main() {
  122. log('========== 启动器开始 ==========')
  123. log(`启动器目录: ${launcherDir}`)
  124. log(`应用目录: ${appDir}`)
  125. log(`更新目录: ${updateDir}`)
  126. // 检查并应用更新
  127. if (fs.existsSync(updateDir)) {
  128. log('检测到更新目录,尝试应用更新...')
  129. applyUpdate()
  130. }
  131. // 启动主程序
  132. launchApp()
  133. log('启动器退出')
  134. process.exit(0)
  135. }
  136. // 运行
  137. main()