desktop-auto-update-environment.mjs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /** Resolve the Desktop auto-update channel and its Tencent COS destination. */
  2. import { 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. return `nightly${platform === 'darwin' ? '-mac' : ''}.yml`
  73. }
  74. /**
  75. * Read one required release setting without accepting whitespace-only values.
  76. * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
  77. * @param {string} name - Environment variable to read.
  78. * @returns {string} Trimmed setting.
  79. */
  80. function requiredEnvironmentValue(env, name) {
  81. const value = env[name]?.trim()
  82. if (value === undefined || value === '') {
  83. throw new Error(`desktop auto-update: ${name} must be set to a non-empty value`)
  84. }
  85. return value
  86. }
  87. /**
  88. * Normalize an HTTPS origin and reject paths or credentials.
  89. * @param {string} value - Candidate origin.
  90. * @param {string} name - Environment variable used in diagnostics.
  91. * @returns {string} Normalized HTTPS origin without a trailing slash.
  92. */
  93. function httpsOrigin(value, name) {
  94. let parsed
  95. try {
  96. parsed = new URL(value)
  97. }
  98. catch {
  99. throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin`)
  100. }
  101. if (parsed.protocol !== 'https:'
  102. || parsed.username !== ''
  103. || parsed.password !== ''
  104. || parsed.pathname !== '/'
  105. || parsed.search !== ''
  106. || parsed.hash !== '') {
  107. throw new Error(`desktop auto-update: ${name} must be an absolute HTTPS origin without a path, credentials, query, or fragment`)
  108. }
  109. return parsed.origin
  110. }
  111. /**
  112. * Resolve the public updater URL for one release target.
  113. * @param {NodeJS.ProcessEnv} env - Packaging or upload environment.
  114. * @param {NodeJS.Platform} platform - Target Node.js platform.
  115. * @param {string} arch - Target Node.js architecture.
  116. * @returns {{ environment: 'test' | 'production', target: 'mac-arm64' | 'mac-x64' | 'win-x64', origin: string, publicUrl: string, keyPrefix: string }} Resolved updater configuration.
  117. * @throws {Error} When the test deployment lacks a valid HTTPS origin.
  118. */
  119. export function resolveDesktopAutoUpdateConfig(env, platform, arch) {
  120. const environment = resolveDesktopAutoUpdateEnvironment(env)
  121. const target = resolveDesktopAutoUpdateTarget(platform, arch)
  122. const deployment = UPDATE_ENVIRONMENTS[environment]
  123. let origin = deployment.fixedOrigin
  124. if (origin === undefined) {
  125. const { originEnvName } = deployment
  126. if (originEnvName === undefined) throw new Error('desktop auto-update: selected deployment has no origin')
  127. origin = httpsOrigin(requiredEnvironmentValue(env, originEnvName), originEnvName)
  128. }
  129. const keyPrefix = `dsh-desk/feeds/${target}`
  130. return {
  131. environment,
  132. target,
  133. origin,
  134. keyPrefix,
  135. publicUrl: `${origin}/${keyPrefix}/`,
  136. }
  137. }
  138. /**
  139. * Resolve the public updater URL and private COS destination for one upload target.
  140. * @param {NodeJS.ProcessEnv} env - Upload environment.
  141. * @param {NodeJS.Platform} platform - Target Node.js platform.
  142. * @param {string} arch - Target Node.js architecture.
  143. * @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.
  144. * @throws {Error} When the selected deployment lacks a required origin or bucket, or the test origin is not HTTPS.
  145. */
  146. export function resolveDesktopUploadConfig(env, platform, arch) {
  147. const update = resolveDesktopAutoUpdateConfig(env, platform, arch)
  148. const deployment = UPDATE_ENVIRONMENTS[update.environment]
  149. return {
  150. ...update,
  151. bucket: requiredEnvironmentValue(env, deployment.bucketEnvName),
  152. secretIdEnvName: deployment.secretIdEnvName,
  153. secretKeyEnvName: deployment.secretKeyEnvName,
  154. }
  155. }