electron-builder-config.mjs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { join } from 'node:path'
  2. import { fileURLToPath } from 'node:url'
  3. import { execFile } from 'node:child_process'
  4. import { promisify } from 'node:util'
  5. import {
  6. resolveDesktopAppId,
  7. resolveMacOSNotarizationEnvironment,
  8. resolveMacOSSigningEnvironment,
  9. } from './desktop-release-environment.mjs'
  10. import { notarizeMacOSDiskImageArtifact } from './notarize-macos-disk-images.mjs'
  11. import { verifyMacOSSignatureAfterSign } from './verify-macos-signature.mjs'
  12. import {
  13. createWindowsTokenSigner,
  14. installWindowsNsisBootstrapSigner,
  15. resolveWindowsUpdatePublisher,
  16. scrubWindowsSigningEnvironment,
  17. } from './windows-sign.mjs'
  18. import { resolveDesktopAutoUpdateConfig } from './desktop-auto-update-environment.mjs'
  19. import { resolveDesktopPolicyEnvironment } from './desktop-policy-environment.mjs'
  20. import { desktopTargetBuildPaths, resolveDesktopBuildTarget } from './desktop-build-paths.mjs'
  21. import { installWindowsDirectoryInstaller } from './windows-directory-installer.mjs'
  22. import { preserveWindowsRuntimeSignature } from './windows-runtime-signature.mjs'
  23. import {
  24. resolveMacOSAppUpdateFeed,
  25. verifyMacOSAppUpdateConfig,
  26. writeMacOSAppUpdateConfig,
  27. } from './macos-app-update-config.mjs'
  28. /**
  29. * Create electron-builder configuration from one release environment.
  30. * @param {NodeJS.ProcessEnv} env - Packaging environment.
  31. * @param {NodeJS.Platform} hostPlatform - Build-host platform used when no explicit target is present.
  32. * @param {string} hostArch - Build-host architecture used when no explicit target is present.
  33. * @param {string | undefined} preparedRuntime - Verified private dsh tree for installed-update qualification; ordinary releases use the target tree.
  34. * @returns {object} electron-builder configuration.
  35. */
  36. export function createElectronBuilderConfig(
  37. env = process.env,
  38. hostPlatform = process.platform,
  39. hostArch = process.arch,
  40. preparedRuntime = undefined,
  41. ) {
  42. const appId = resolveDesktopAppId(env)
  43. const policy = resolveDesktopPolicyEnvironment(env)
  44. const targetPlatform = env.DSH_DESKTOP_TARGET_PLATFORM
  45. const resolvedPlatform = targetPlatform ?? hostPlatform
  46. const resolvedArch = env.DSH_DESKTOP_TARGET_ARCH ?? hostArch
  47. if (env.DSH_DESKTOP_UNSIGNED !== undefined && !['0', '1'].includes(env.DSH_DESKTOP_UNSIGNED)) {
  48. throw new Error('desktop package: DSH_DESKTOP_UNSIGNED must be 0 or 1')
  49. }
  50. const unsigned = env.DSH_DESKTOP_UNSIGNED === '1'
  51. if (unsigned && resolvedPlatform !== 'win32') throw new Error('desktop package: unsigned builds require Windows')
  52. const packagesMacOS = targetPlatform === 'darwin' || (targetPlatform === undefined && hostPlatform === 'darwin')
  53. const packagesWindows = resolvedPlatform === 'win32'
  54. if (resolvedPlatform === 'win32') installWindowsDirectoryInstaller()
  55. const macOSSigning = packagesMacOS ? resolveMacOSSigningEnvironment(env) : undefined
  56. if (packagesMacOS) resolveMacOSNotarizationEnvironment(env)
  57. const buildPaths = desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
  58. let primaryRuntimeDestination
  59. const windowsSigner = packagesWindows && !unsigned
  60. ? createWindowsTokenSigner({
  61. certificateFile: env.DSH_DESKTOP_WINDOWS_CER_FILE,
  62. signTool: env.DSH_DESKTOP_WINDOWS_SIGNTOOL,
  63. tokenPin: env.DSH_DESKTOP_WINDOWS_TOKEN_PIN,
  64. keyContainer: env.DSH_DESKTOP_WINDOWS_KEY_CONTAINER,
  65. preserveSignature: async path => primaryRuntimeDestination === undefined ? false : preserveWindowsRuntimeSignature(path, {
  66. sourceRoot: join(buildPaths.runtime, 'primary-runtime'),
  67. destinationRoot: primaryRuntimeDestination,
  68. runDirectory: env.DSH_DESKTOP_PACKAGING_RUN_DIR,
  69. }),
  70. })
  71. : undefined
  72. if (windowsSigner !== undefined) {
  73. installWindowsNsisBootstrapSigner({ sign: windowsSigner })
  74. }
  75. const update = unsigned ? undefined : resolveDesktopAutoUpdateConfig(env, resolvedPlatform, resolvedArch)
  76. if (preparedRuntime !== undefined) buildPaths.dsh = preparedRuntime
  77. return {
  78. appId,
  79. extraMetadata: { dshDesktopAppId: appId, dshMandatoryUpdatePolicy: policy },
  80. productName: 'DeepSeek Harness',
  81. artifactName: 'deepseek-harness-${version}-${os}-${arch}.${ext}',
  82. directories: { output: unsigned ? join(buildPaths.root, 'unsigned-artifacts') : buildPaths.artifacts },
  83. asar: true,
  84. electronDist: buildPaths.electron,
  85. electronFuses: { runAsNode: true },
  86. beforeBuild: async () => {
  87. if (resolvedPlatform !== 'win32') return true
  88. await promisify(execFile)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
  89. fileURLToPath(new URL('./prepare-windows-installer.ps1', import.meta.url)),
  90. '-OutputDirectory', join(buildPaths.root, 'installer-ui')], {
  91. env: scrubWindowsSigningEnvironment(env), windowsHide: true,
  92. })
  93. if (windowsSigner !== undefined) {
  94. await windowsSigner({ path: join(buildPaths.root, 'installer-ui', 'window-frame.dll'), hash: 'sha256', isNest: false })
  95. }
  96. // A falsy result tells electron-builder to omit its production node_modules collection.
  97. return true
  98. },
  99. files: [
  100. 'lib/*.js',
  101. 'lib/*.cjs',
  102. 'renderer/**/*',
  103. 'package.json',
  104. { from: buildPaths.dsh, to: 'dsh', filter: ['**/*'] },
  105. // electron-builder excludes a source directory's root node_modules.
  106. { from: join(buildPaths.dsh, 'node_modules'), to: 'dsh/node_modules', filter: ['**/*'] },
  107. ],
  108. asarUnpack: [
  109. '**/*.{node,dylib,dll,so,exe}',
  110. '**/*.so.*',
  111. '**/spawn-helper',
  112. '**/@vscode/ripgrep/bin/rg',
  113. ],
  114. extraResources: [
  115. { from: buildPaths.runtime, to: 'runtime' },
  116. ],
  117. mac: {
  118. icon: fileURLToPath(new URL('../resources/icon-macos.png', import.meta.url)),
  119. category: 'public.app-category.developer-tools',
  120. identity: macOSSigning?.signingIdentity,
  121. forceCodeSigning: true,
  122. hardenedRuntime: true,
  123. // ASAR-unpacked native runtime files are pre-signed; PAK resources are sealed by their enclosing bundle.
  124. signIgnore: ['/Contents/Resources/app\\.asar\\.unpacked/dsh(?:/|$)', '/Contents/Resources/runtime/primary-runtime(?:/|$)', '\\.pak$'],
  125. notarize: true,
  126. target: ['dmg', 'zip'],
  127. },
  128. dmg: {
  129. sign: true,
  130. writeUpdateInfo: false,
  131. },
  132. beforePack: async context => {
  133. if (windowsSigner !== undefined) primaryRuntimeDestination = join(context.appOutDir, 'resources', 'runtime', 'primary-runtime')
  134. if (policy === undefined) return
  135. const { resolveDesktopPolicyConfig } = await import('../lib/types/mandatory-update-policy.js')
  136. resolveDesktopPolicyConfig(policy)
  137. },
  138. afterPack: async context => {
  139. const { verifyDesktopRuntime, writeDesktopRuntime } = await import('../lib/types/runtime-tree.js')
  140. const resourcesDir = context.packager.getResourcesDir(context.appOutDir)
  141. if (resolvedPlatform === 'darwin' && update !== undefined) {
  142. await writeMacOSAppUpdateConfig(resourcesDir, resolveMacOSAppUpdateFeed(context.packager.config.publish),
  143. context.packager.appInfo.updaterCacheDirName)
  144. }
  145. if (resolvedPlatform === 'win32' && !unsigned) {
  146. // Windows signs copied executable resources before afterPack runs.
  147. const prepared = await verifyDesktopRuntime(buildPaths.dsh,
  148. context.packager.appInfo.version, { platform: resolvedPlatform, arch: resolvedArch })
  149. writeDesktopRuntime(buildPaths.dsh, prepared.release, prepared.sharedPackages.map(entry => entry.name),
  150. { platform: resolvedPlatform, arch: resolvedArch })
  151. }
  152. await verifyDesktopRuntime(buildPaths.dsh,
  153. context.packager.appInfo.version, { platform: resolvedPlatform, arch: resolvedArch })
  154. },
  155. afterSign: async context => {
  156. if (context.electronPlatformName !== 'darwin') return
  157. const appPath = join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`)
  158. if (update !== undefined) {
  159. await verifyMacOSAppUpdateConfig(appPath, resolveMacOSAppUpdateFeed(context.packager.config.publish),
  160. context.packager.appInfo.updaterCacheDirName)
  161. }
  162. verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env))
  163. },
  164. artifactBuildCompleted: artifact => {
  165. if (!artifact.file.endsWith('.dmg')) return
  166. return notarizeMacOSDiskImageArtifact(
  167. artifact,
  168. env,
  169. macOSSigning ?? resolveMacOSSigningEnvironment(env),
  170. )
  171. },
  172. win: {
  173. icon: fileURLToPath(new URL('../resources/icon-windows.png', import.meta.url)),
  174. forceCodeSigning: !unsigned,
  175. signtoolOptions: {
  176. sign: windowsSigner,
  177. publisherName: windowsSigner === undefined ? undefined : resolveWindowsUpdatePublisher(env.DSH_DESKTOP_WINDOWS_CER_FILE),
  178. signingHashAlgorithms: ['sha256'],
  179. },
  180. target: ['nsis'],
  181. },
  182. linux: {
  183. category: 'Development',
  184. target: ['AppImage'],
  185. },
  186. nsis: {
  187. installerSidebar: join(buildPaths.root, 'installer-ui', 'uninstaller-sidebar.bmp'),
  188. uninstallerSidebar: join(buildPaths.root, 'installer-ui', 'uninstaller-sidebar.bmp'),
  189. include: fileURLToPath(new URL('./installer.nsh', import.meta.url)),
  190. oneClick: false,
  191. perMachine: false,
  192. allowElevation: false,
  193. allowToChangeInstallationDirectory: false,
  194. installerLanguages: ['en_US', 'zh_CN'],
  195. differentialPackage: true,
  196. },
  197. detectUpdateChannel: false,
  198. publish: update === undefined ? null : [{ provider: 'generic', url: update.publicUrl, channel: 'nightly' }],
  199. }
  200. }