build-portable.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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 releasePdfium = resolve(root, "src-tauri/target/release/pdfium/pdfium.dll")
  17. const portableDevPdfium = resolve(root, "src-tauri/target/portable-dev/pdfium/pdfium.dll")
  18. const sourcePdfium = existsSync(portableDevPdfium) ? portableDevPdfium : releasePdfium
  19. const outDir = resolve(root, "release-portable")
  20. const outExe = resolve(outDir, isStorySimulationBranch ? "QMaiWrite-剧情推演版.exe" : "QMaiWrite.exe")
  21. const outPdfium = resolve(outDir, "pdfium/pdfium.dll")
  22. const outSkillDir = resolve(outDir, "skills")
  23. const manifest = resolve(outDir, "version-info.json")
  24. const backupDir = resolve(root, "release-portable-backup")
  25. if (!existsSync(sourceExe)) {
  26. throw new Error(`未找到最新 EXE,请先运行 npm run tauri build:${sourceExe}`)
  27. }
  28. // 尝试安全清理输出目录
  29. try {
  30. if (existsSync(backupDir)) {
  31. try {
  32. rmSync(backupDir, { recursive: true, force: true })
  33. } catch {}
  34. }
  35. if (existsSync(outDir)) {
  36. // 先尝试重命名而不是直接删除
  37. renameSync(outDir, backupDir)
  38. try {
  39. rmSync(backupDir, { recursive: true, force: true })
  40. } catch {}
  41. }
  42. } catch {}
  43. mkdirSync(outDir, { recursive: true })
  44. // 复制 exe,处理被占用的情况
  45. try {
  46. if (existsSync(outExe)) {
  47. const backupExe = resolve(outDir, "QMaiWrite-old.exe")
  48. try {
  49. if (existsSync(backupExe)) {
  50. rmSync(backupExe, { force: true })
  51. }
  52. renameSync(outExe, backupExe)
  53. } catch {}
  54. }
  55. cpSync(sourceExe, outExe)
  56. } catch (e) {
  57. console.warn("警告:无法完全替换正在运行的 exe,保留旧版本,但已更新其他资源")
  58. }
  59. if (existsSync(sourcePdfium)) {
  60. mkdirSync(dirname(outPdfium), { recursive: true })
  61. try {
  62. cpSync(sourcePdfium, outPdfium)
  63. } catch {}
  64. }
  65. // 复制 skills 文件夹到便携版目录
  66. const sourceSkillDir = resolve(root, "skills")
  67. if (existsSync(sourceSkillDir)) {
  68. try {
  69. rmSync(outSkillDir, { recursive: true, force: true })
  70. } catch {}
  71. mkdirSync(outSkillDir, { recursive: true })
  72. cpSync(sourceSkillDir, outSkillDir, { recursive: true })
  73. }
  74. const exeStat = statSync(outExe)
  75. writeFileSync(manifest, JSON.stringify({
  76. productName: isStorySimulationBranch ? "青幕AI写作(剧情推演版)" : "青幕AI写作",
  77. version: pkg.version,
  78. builtAt: new Date().toISOString(),
  79. sourceExe,
  80. portableExe: outExe,
  81. exeBytes: exeStat.size,
  82. includesPdfium: existsSync(outPdfium),
  83. includesSkills: existsSync(outSkillDir),
  84. ...(isStorySimulationBranch ? { variant: "story-simulation", branch: currentBranch } : {}),
  85. }, null, 2), "utf8")
  86. console.log(`便携版已生成:${outExe}`)
  87. console.log(`版本信息:${manifest}`)
  88. if (isStorySimulationBranch) {
  89. console.log("注意:这是测试版,不可上传到 GitHub main 分支")
  90. }