electron-builder.config.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { execFile } from "node:child_process"
  2. import path from "node:path"
  3. import { fileURLToPath } from "node:url"
  4. import { promisify } from "node:util"
  5. import type { Configuration } from "electron-builder"
  6. const execFileAsync = promisify(execFile)
  7. const packageDir = path.dirname(fileURLToPath(import.meta.url))
  8. const rootDir = path.resolve(packageDir, "../..")
  9. const signScript = path.join(rootDir, "script", "sign-windows.ps1")
  10. // The Electron 42 packaging update briefly installed Linux launchers/icons under
  11. // "opencode-desktop". Keep that hidden desktop entry around so existing GNOME/KDE
  12. // pins still resolve after the canonical app id changes back to ai.opencode.desktop.
  13. const legacyDesktopEntry = path.join(packageDir, "resources", "linux", "opencode-desktop.desktop")
  14. const legacyDesktopEntryFpm = `${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`
  15. const metainfoFpm = (appId: string) =>
  16. `${path.join(packageDir, "resources", `${appId}.metainfo.xml`)}=/usr/share/metainfo/${appId}.metainfo.xml`
  17. async function signWindows(configuration: { path: string }) {
  18. if (process.platform !== "win32") return
  19. if (process.env.GITHUB_ACTIONS !== "true") return
  20. await execFileAsync(
  21. "pwsh",
  22. ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", signScript, configuration.path],
  23. { cwd: rootDir },
  24. )
  25. }
  26. const channel = (() => {
  27. const raw = process.env.OPENCODE_CHANNEL
  28. if (raw === "dev" || raw === "beta" || raw === "prod") return raw
  29. return "dev"
  30. })()
  31. const APP_IDS = {
  32. dev: "ai.opencode.desktop.dev",
  33. beta: "ai.opencode.desktop.beta",
  34. prod: "ai.opencode.desktop",
  35. } as const
  36. const getBase = (appId: string): Configuration => ({
  37. artifactName: "opencode-desktop-${os}-${arch}.${ext}",
  38. directories: {
  39. output: "dist",
  40. buildResources: "resources",
  41. },
  42. // Linux launchers are .desktop files, so this is the desktop file name,
  43. // not just the app id. For prod, app id "ai.opencode.desktop" becomes
  44. // "ai.opencode.desktop.desktop".
  45. // https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
  46. // https://www.electron.build/docs/linux/
  47. extraMetadata: {
  48. desktopName: `${appId}.desktop`,
  49. },
  50. files: ["out/**/*", "resources/**/*", "!resources/opencode-cli*"],
  51. extraResources: [
  52. ...(channel === "dev"
  53. ? [
  54. {
  55. from: "resources/",
  56. to: "",
  57. filter: ["opencode-cli*"],
  58. },
  59. ]
  60. : []),
  61. {
  62. from: "native/",
  63. to: "native/",
  64. filter: ["index.js", "index.d.ts", "build/Release/mac_window.node", "swift-build/**"],
  65. },
  66. ],
  67. mac: {
  68. category: "public.app-category.developer-tools",
  69. icon: `resources/icons/icon.icns`,
  70. hardenedRuntime: true,
  71. gatekeeperAssess: false,
  72. entitlements: "resources/entitlements.plist",
  73. entitlementsInherit: "resources/entitlements.plist",
  74. notarize: true,
  75. target: ["dmg", "zip"],
  76. },
  77. dmg: {
  78. sign: true,
  79. },
  80. protocols: {
  81. name: "OpenCode",
  82. schemes: ["opencode"],
  83. },
  84. win: {
  85. icon: `resources/icons/icon.ico`,
  86. signtoolOptions: {
  87. sign: signWindows,
  88. },
  89. target: ["nsis"],
  90. verifyUpdateCodeSignature: false,
  91. },
  92. nsis: {
  93. oneClick: true,
  94. perMachine: false,
  95. installerIcon: `resources/icons/icon.ico`,
  96. installerHeaderIcon: `resources/icons/icon.ico`,
  97. },
  98. linux: {
  99. icon: `resources/icons`,
  100. category: "Development",
  101. executableName: appId,
  102. desktop: {
  103. entry: {
  104. // Match the installed .desktop file and hicolor icon basename so
  105. // Linux shells can associate the running Electron window with its launcher.
  106. StartupWMClass: appId,
  107. },
  108. },
  109. target: ["AppImage", "deb", "rpm"],
  110. },
  111. })
  112. function getConfig() {
  113. const appId = APP_IDS[channel]
  114. const base = getBase(appId)
  115. switch (channel) {
  116. case "dev": {
  117. return {
  118. ...base,
  119. appId,
  120. productName: "OpenCode Dev",
  121. deb: { fpm: [metainfoFpm(appId)] },
  122. rpm: { packageName: "opencode-dev", fpm: [metainfoFpm(appId)] },
  123. }
  124. }
  125. case "beta": {
  126. return {
  127. ...base,
  128. appId,
  129. productName: "OpenCode Beta",
  130. protocols: { name: "OpenCode Beta", schemes: ["opencode"] },
  131. publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" },
  132. deb: { fpm: [metainfoFpm(appId)] },
  133. rpm: { packageName: "opencode-beta", fpm: [metainfoFpm(appId)] },
  134. }
  135. }
  136. case "prod": {
  137. return {
  138. ...base,
  139. appId,
  140. productName: "OpenCode",
  141. protocols: { name: "OpenCode", schemes: ["opencode"] },
  142. publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" },
  143. deb: { fpm: [metainfoFpm(appId), legacyDesktopEntryFpm] },
  144. rpm: { packageName: "opencode", fpm: [metainfoFpm(appId), legacyDesktopEntryFpm] },
  145. }
  146. }
  147. }
  148. }
  149. export default getConfig()