desktop-auto-update-environment.mjs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /** Resolve the Desktop auto-update channel and its Tencent COS destination. */
  2. import { prerelease, valid } from 'semver'
  3. /** Environment variable that selects the Desktop update deployment. */
  4. export const DESKTOP_AUTO_UPDATE_ENV = 'DSH_DESKTOP_AUTO_UPDATE_ENV'
  5. const UPDATE_ENVIRONMENTS = {
  6. test: {
  7. originEnvName: 'DOWNLOAD_TEST_ORIGIN',
  8. fixedOrigin: undefined,
  9. bucketEnvName: 'DOWNLOAD_TEST_COS_BUCKET',
  10. secretIdEnvName: 'DOWNLOAD_TEST_COS_SECRET_ID',
  11. secretKeyEnvName: 'DOWNLOAD_TEST_COS_SECRET_KEY',
  12. },
  13. production: {
  14. originEnvName: undefined,
  15. fixedOrigin: 'https://download.deepseek.com',
  16. bucketEnvName: 'DOWNLOAD_PROD_COS_BUCKET',
  17. secretIdEnvName: 'DOWNLOAD_PROD_COS_SECRET_ID',
  18. secretKeyEnvName: 'DOWNLOAD_PROD_COS_SECRET_KEY',
  19. },
  20. }
  21. const UPDATE_TARGETS = new Set(['mac-arm64', 'mac-x64', 'win-x64'])
  22. /**
  23. * Resolve the update deployment, defaulting local release work to test.
  24. * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
  25. * @returns {'test' | 'production'} Validated deployment name.
  26. */
  27. export function resolveDesktopAutoUpdateEnvironment(env) {
  28. const value = env[DESKTOP_AUTO_UPDATE_ENV]?.trim() || 'test'
  29. if (value !== 'test' && value !== 'production') {
  30. throw new Error(`desktop auto-update: ${DESKTOP_AUTO_UPDATE_ENV} must be "test" or "production"`)
  31. }
  32. return value
  33. }
  34. /**
  35. * Resolve one supported platform and architecture to its update directory.
  36. * @param {NodeJS.Platform} platform - Target Node.js platform.
  37. * @param {string} arch - Target Node.js architecture.
  38. * @returns {'mac-arm64' | 'mac-x64' | 'win-x64'} Update target directory.
  39. */
  40. export function resolveDesktopAutoUpdateTarget(platform, arch) {
  41. const os = platform === 'darwin' ? 'mac' : platform === 'win32' ? 'win' : platform
  42. const target = `${os}-${arch}`
  43. if (!UPDATE_TARGETS.has(target)) {
  44. throw new Error(`desktop auto-update: unsupported target ${target}`)
  45. }
  46. return target
  47. }
  48. /**
  49. * Return the local completion record filename for one packaged target.
  50. * @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported release target.
  51. * @returns {string} Filename stored beside electron-builder artifacts.
  52. */
  53. export function desktopBuildRecordFilename(target) {
  54. if (!UPDATE_TARGETS.has(target)) {
  55. throw new Error(`desktop auto-update: unsupported target ${target}`)
  56. }
  57. return `${target}-release.json`
  58. }
  59. /**
  60. * Return the electron-builder channel metadata filename for an application version.
  61. * @param {string} version - Desktop semantic version.
  62. * @param {NodeJS.Platform} platform - Target platform.
  63. * @returns {string} Channel metadata filename emitted for the target.
  64. */
  65. export function desktopUpdateMetadataFilename(version, platform) {
  66. if (valid(version) === null) {
  67. throw new Error(`desktop auto-update: invalid Desktop version ${JSON.stringify(version)}`)
  68. }
  69. if (platform !== 'darwin' && platform !== 'win32') {
  70. throw new Error(`desktop auto-update: unsupported metadata platform ${platform}`)
  71. }
  72. const release = prerelease(version)
  73. const channel = release === null ? 'latest' : String(release[0])
  74. return `${channel}${platform === 'darwin' ? '-mac' : ''}.yml`
  75. }
  76. /**
  77. * Read one required release setting without accepting whitespace-only values.
  78. * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
  79. * @param {string} name - Environment variable to read.
  80. * @returns {string} Trimmed setting.
  81. */
  82. function requiredEnvironmentValue(env, name) {
  83. const value = env[name]?.trim()
  84. if (value === undefined || value === '') {
  85. throw new Error(`desktop auto-update: ${name} must be set to a non-empty value`)
  86. }
  87. return value
  88. }
  89. /**
  90. * Normalize an HTTPS origin and reject paths or credentials.
  91. * @param {string} value - Candidate origin.
  92. * @param {string} name - Environment variable used in diagnostics.
  93. * @returns {string} Normalized HTTPS origin without a trailing slash.
  94. */
  95. function httpsOrigin(value, name) {
  96. let parsed
  97. try {
  98. parsed = new URL(value)
  99. }
  100. catch {
  101. throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin`)
  102. }
  103. if (parsed.protocol !== 'https:'
  104. || parsed.username !== ''
  105. || parsed.password !== ''
  106. || parsed.pathname !== '/'
  107. || parsed.search !== ''
  108. || parsed.hash !== '') {
  109. throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin without a path, credentials, query, or fragment`)
  110. }
  111. return parsed.origin
  112. }
  113. /**
  114. * Resolve the public updater URL for one release target.
  115. * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
  116. * @param {NodeJS.Platform} platform - Target Node.js platform.
  117. * @param {string} arch - Target Node.js architecture.
  118. * @returns {{ environment: 'test' | 'production', target: 'mac-arm64' | 'mac-x64' | 'win-x64', origin: string, publicUrl: string, keyPrefix: string }} Resolved updater configuration.
  119. * @throws {Error} When the test deployment lacks a valid HTTPS origin.
  120. */
  121. export function resolveDesktopAutoUpdateConfig(env, platform, arch) {
  122. const environment = resolveDesktopAutoUpdateEnvironment(env)
  123. const target = resolveDesktopAutoUpdateTarget(platform, arch)
  124. const deployment = UPDATE_ENVIRONMENTS[environment]
  125. let origin = deployment.fixedOrigin
  126. if (origin === undefined) {
  127. const { originEnvName } = deployment
  128. if (originEnvName === undefined) throw new Error('desktop auto-update: selected deployment has no origin')
  129. origin = httpsOrigin(requiredEnvironmentValue(env, originEnvName), originEnvName)
  130. }
  131. const keyPrefix = `_/harness/desktop/stable/${target}`
  132. return {
  133. environment,
  134. target,
  135. origin,
  136. keyPrefix,
  137. publicUrl: `${origin}/${keyPrefix}/`,
  138. }
  139. }
  140. /**
  141. * Resolve the public updater URL and private COS destination for one upload target.
  142. * @param {NodeJS.ProcessEnv} env - Upload environment.
  143. * @param {NodeJS.Platform} platform - Target Node.js platform.
  144. * @param {string} arch - Target Node.js architecture.
  145. * @returns {{ environment: 'test' | 'production', target: 'mac-arm64' | 'mac-x64' | 'win-x64', origin: string, publicUrl: string, keyPrefix: string, bucket: string, secretIdEnvName: string, secretKeyEnvName: string }} Resolved upload configuration.
  146. * @throws {Error} When the selected deployment lacks a required origin or bucket, or the test origin is not HTTPS.
  147. */
  148. export function resolveDesktopUploadConfig(env, platform, arch) {
  149. const update = resolveDesktopAutoUpdateConfig(env, platform, arch)
  150. const deployment = UPDATE_ENVIRONMENTS[update.environment]
  151. return {
  152. ...update,
  153. bucket: requiredEnvironmentValue(env, deployment.bucketEnvName),
  154. secretIdEnvName: deployment.secretIdEnvName,
  155. secretKeyEnvName: deployment.secretKeyEnvName,
  156. }
  157. }