build-portable.mjs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { cpSync, existsSync, mkdirSync, rmSync, statSync, writeFileSync, renameSync } from "node:fs"
  2. import { readFile } from "node:fs/promises"
  3. import { execSync } from "node:child_process"
  4. import { dirname, resolve } from "node:path"
  5. import { fileURLToPath } from "node:url"
  6. const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
  7. const pkg = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"))
  8. let currentBranch = ""
  9. try {
  10. currentBranch = execSync("git rev-parse --abbrev-ref HEAD").toString().trim()
  11. } catch {}
  12. const isStorySimulationBranch = currentBranch === "feature-story-simulation"
  13. const releaseExe = resolve(root, "src-tauri/target/release/qmai.exe")
  14. const portableDevExe = resolve(root, "src-tauri/target/portable-dev/qmai.exe")
  15. const sourceExe = existsSync(portableDevExe) ? portableDevExe : releaseExe
  16. const outDir = resolve(root, "release-portable")
  17. const outExe = resolve(outDir, isStorySimulationBranch ? "QMaiWrite-剧情推演版.exe" : "QMaiWrite.exe")
  18. const outSkillDir = resolve(outDir, "skills")
  19. const manifest = resolve(outDir, "version-info.json")
  20. const backupDir = resolve(root, "release-portable-backup")
  21. if (!existsSync(sourceExe)) {
  22. throw new Error(`未找到最新 EXE,请先运行 npm run tauri build:${sourceExe}`)
  23. }
  24. // 尝试安全清理输出目录
  25. try {
  26. if (existsSync(backupDir)) {
  27. try {
  28. rmSync(backupDir, { recursive: true, force: true })
  29. } catch {}
  30. }
  31. if (existsSync(outDir)) {
  32. // 先尝试重命名而不是直接删除
  33. renameSync(outDir, backupDir)
  34. try {
  35. rmSync(backupDir, { recursive: true, force: true })
  36. } catch {}
  37. }
  38. } catch {}
  39. mkdirSync(outDir, { recursive: true })
  40. // 复制 exe,处理被占用的情况
  41. try {
  42. if (existsSync(outExe)) {
  43. const backupExe = resolve(outDir, "QMaiWrite-old.exe")
  44. try {
  45. if (existsSync(backupExe)) {
  46. rmSync(backupExe, { force: true })
  47. }
  48. renameSync(outExe, backupExe)
  49. } catch {}
  50. }
  51. cpSync(sourceExe, outExe)
  52. } catch (e) {
  53. console.warn("警告:无法完全替换正在运行的 exe,保留旧版本,但已更新其他资源")
  54. }
  55. // 复制 skills 文件夹到便携版目录
  56. const sourceSkillDir = resolve(root, "skills")
  57. if (existsSync(sourceSkillDir)) {
  58. try {
  59. rmSync(outSkillDir, { recursive: true, force: true })
  60. } catch {}
  61. mkdirSync(outSkillDir, { recursive: true })
  62. cpSync(sourceSkillDir, outSkillDir, { recursive: true })
  63. }
  64. const exeStat = statSync(outExe)
  65. writeFileSync(manifest, JSON.stringify({
  66. productName: isStorySimulationBranch ? "青幕AI写作(剧情推演版)" : "青幕AI写作",
  67. version: pkg.version,
  68. builtAt: new Date().toISOString(),
  69. sourceExe,
  70. portableExe: outExe,
  71. exeBytes: exeStat.size,
  72. includesSkills: existsSync(outSkillDir),
  73. ...(isStorySimulationBranch ? { variant: "story-simulation", branch: currentBranch } : {}),
  74. }, null, 2), "utf8")
  75. console.log(`便携版已生成:${outExe}`)
  76. console.log(`版本信息:${manifest}`)
  77. if (isStorySimulationBranch) {
  78. console.log("注意:这是测试版,不可上传到 GitHub main 分支")
  79. }