electron-builder.config.mjs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 './scripts/desktop-release-environment.mjs'
  10. import { notarizeMacOSDiskImageArtifact } from './scripts/notarize-macos-disk-images.mjs'
  11. import { verifyMacOSSignatureAfterSign } from './scripts/verify-macos-signature.mjs'
  12. import {
  13. createWindowsTokenSigner,
  14. installWindowsNsisBootstrapSigner,
  15. scrubWindowsSigningEnvironment,
  16. } from './scripts/windows-sign.mjs'
  17. import { resolveDesktopAutoUpdateConfig } from './scripts/desktop-auto-update-environment.mjs'
  18. import { desktopTargetBuildPaths, resolveDesktopBuildTarget } from './scripts/desktop-build-paths.mjs'
  19. import { installWindowsDirectoryInstaller } from './scripts/windows-directory-installer.mjs'
  20. /**
  21. * Create electron-builder configuration from one release environment.
  22. * @param {NodeJS.ProcessEnv} env - Packaging environment.
  23. * @param {NodeJS.Platform} hostPlatform - Build-host platform used when no explicit target is present.
  24. * @param {string} hostArch - Build-host architecture used when no explicit target is present.
  25. * @returns {object} electron-builder configuration.
  26. */
  27. export function createElectronBuilderConfig(
  28. env = process.env,
  29. hostPlatform = process.platform,
  30. hostArch = process.arch,
  31. ) {
  32. const appId = resolveDesktopAppId(env)
  33. const targetPlatform = env.DSH_DESKTOP_TARGET_PLATFORM
  34. const resolvedPlatform = targetPlatform ?? hostPlatform
  35. const resolvedArch = env.DSH_DESKTOP_TARGET_ARCH ?? hostArch
  36. if (env.DSH_DESKTOP_UNSIGNED !== undefined && !['0', '1'].includes(env.DSH_DESKTOP_UNSIGNED)) {
  37. throw new Error('desktop package: DSH_DESKTOP_UNSIGNED must be 0 or 1')
  38. }
  39. const unsigned = env.DSH_DESKTOP_UNSIGNED === '1'
  40. if (unsigned && resolvedPlatform !== 'win32') throw new Error('desktop package: unsigned builds require Windows')
  41. const packagesMacOS = targetPlatform === 'darwin' || (targetPlatform === undefined && hostPlatform === 'darwin')
  42. const packagesWindows = targetPlatform === 'win32'
  43. if (resolvedPlatform === 'win32') installWindowsDirectoryInstaller()
  44. const macOSSigning = packagesMacOS ? resolveMacOSSigningEnvironment(env) : undefined
  45. if (packagesMacOS) resolveMacOSNotarizationEnvironment(env)
  46. const windowsSigner = packagesWindows && !unsigned
  47. ? createWindowsTokenSigner({
  48. certificateFile: env.DSH_DESKTOP_WINDOWS_CER_FILE,
  49. signTool: env.DSH_DESKTOP_WINDOWS_SIGNTOOL,
  50. tokenPin: env.DSH_DESKTOP_WINDOWS_TOKEN_PIN,
  51. keyContainer: env.DSH_DESKTOP_WINDOWS_KEY_CONTAINER,
  52. })
  53. : undefined
  54. if (windowsSigner !== undefined) {
  55. installWindowsNsisBootstrapSigner({ sign: windowsSigner })
  56. }
  57. const update = unsigned ? undefined : resolveDesktopAutoUpdateConfig(env, resolvedPlatform, resolvedArch)
  58. const buildPaths = desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
  59. return {
  60. appId,
  61. productName: 'DeepSeek Harness',
  62. artifactName: 'deepseek-harness-${version}-${os}-${arch}.${ext}',
  63. directories: { output: unsigned ? join(buildPaths.root, 'unsigned-artifacts') : buildPaths.artifacts },
  64. asar: true,
  65. electronDist: buildPaths.electron,
  66. electronFuses: { runAsNode: true },
  67. beforeBuild: async () => {
  68. if (resolvedPlatform !== 'win32') return true
  69. await promisify(execFile)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
  70. fileURLToPath(new URL('./scripts/prepare-windows-installer.ps1', import.meta.url)),
  71. '-OutputDirectory', join(buildPaths.root, 'installer-ui')], {
  72. env: scrubWindowsSigningEnvironment(env), windowsHide: true,
  73. })
  74. if (windowsSigner !== undefined) {
  75. await windowsSigner({ path: join(buildPaths.root, 'installer-ui', 'window-frame.dll'), hash: 'sha256', isNest: false })
  76. }
  77. // A falsy result tells electron-builder to omit its production node_modules collection.
  78. return true
  79. },
  80. files: [
  81. 'lib/*.js',
  82. 'lib/*.cjs',
  83. 'renderer/**/*',
  84. 'package.json',
  85. { from: buildPaths.dsh, to: 'dsh', filter: ['**/*'] },
  86. // electron-builder excludes a source directory's root node_modules.
  87. { from: join(buildPaths.dsh, 'node_modules'), to: 'dsh/node_modules', filter: ['**/*'] },
  88. ],
  89. asarUnpack: [
  90. '**/*.{node,dylib,dll,so,exe}',
  91. '**/*.so.*',
  92. '**/spawn-helper',
  93. '**/@vscode/ripgrep/bin/rg',
  94. ],
  95. extraResources: [
  96. { from: buildPaths.runtime, to: 'runtime' },
  97. ],
  98. mac: {
  99. icon: fileURLToPath(new URL('./resources/icon-macos.png', import.meta.url)),
  100. category: 'public.app-category.developer-tools',
  101. identity: macOSSigning?.signingIdentity,
  102. forceCodeSigning: true,
  103. hardenedRuntime: true,
  104. // ASAR-unpacked native runtime files are pre-signed; PAK resources are sealed by their enclosing bundle.
  105. signIgnore: ['/Contents/Resources/app\\.asar\\.unpacked/dsh(?:/|$)', '/Contents/Resources/runtime/primary-runtime(?:/|$)', '\\.pak$'],
  106. notarize: true,
  107. target: ['dmg', 'zip'],
  108. },
  109. dmg: {
  110. sign: true,
  111. writeUpdateInfo: false,
  112. },
  113. afterSign: async context => {
  114. if (context.electronPlatformName !== 'darwin') return
  115. verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env))
  116. },
  117. artifactBuildCompleted: artifact => {
  118. if (!artifact.file.endsWith('.dmg')) return
  119. return notarizeMacOSDiskImageArtifact(
  120. artifact,
  121. env,
  122. macOSSigning ?? resolveMacOSSigningEnvironment(env),
  123. )
  124. },
  125. win: {
  126. icon: fileURLToPath(new URL('./resources/icon-windows.png', import.meta.url)),
  127. forceCodeSigning: !unsigned,
  128. signtoolOptions: {
  129. sign: windowsSigner,
  130. signingHashAlgorithms: ['sha256'],
  131. },
  132. target: ['nsis'],
  133. },
  134. linux: {
  135. category: 'Development',
  136. target: ['AppImage'],
  137. },
  138. nsis: {
  139. installerSidebar: join(buildPaths.root, 'installer-ui', 'uninstaller-sidebar.bmp'),
  140. uninstallerSidebar: join(buildPaths.root, 'installer-ui', 'uninstaller-sidebar.bmp'),
  141. include: fileURLToPath(new URL('./scripts/installer.nsh', import.meta.url)),
  142. oneClick: false,
  143. perMachine: false,
  144. allowElevation: false,
  145. allowToChangeInstallationDirectory: false,
  146. installerLanguages: ['en_US', 'zh_CN'],
  147. differentialPackage: true,
  148. },
  149. publish: update === undefined ? null : [{ provider: 'generic', url: update.publicUrl }],
  150. }
  151. }
  152. export default createElectronBuilderConfig()