upload-target.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. /** Upload one validated Desktop release to its Tencent COS update directory. */
  2. import { createReadStream } from 'node:fs'
  3. import { stat } from 'node:fs/promises'
  4. import { resolve } from 'node:path'
  5. import { parseArgs } from 'node:util'
  6. import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
  7. import type { DesktopPackageTargetName } from './package-target.ts'
  8. import { loadDesktopPackageEnvironment } from './desktop-package-environment.mjs'
  9. import {
  10. createDesktopUploadPlan,
  11. type DesktopUploadArtifact,
  12. } from './desktop-upload-plan.ts'
  13. const SUPPORTED_TARGETS = new Set<DesktopPackageTargetName>(['mac-arm64', 'mac-x64', 'win-x64'])
  14. function targetName(value: string): DesktopPackageTargetName {
  15. if (!SUPPORTED_TARGETS.has(value as DesktopPackageTargetName)) {
  16. throw new Error(`desktop upload: unsupported target ${JSON.stringify(value)}; expected ${[...SUPPORTED_TARGETS].join(', ')}`)
  17. }
  18. return value as DesktopPackageTargetName
  19. }
  20. function requiredEnvironmentValue(environment: NodeJS.ProcessEnv, name: string): string {
  21. const value = environment[name]?.trim()
  22. if (value === undefined || value === '') {
  23. throw new Error(`desktop upload: ${name} must be set to a non-empty value`)
  24. }
  25. return value
  26. }
  27. async function putArtifact(
  28. client: S3Client,
  29. bucket: string,
  30. artifact: DesktopUploadArtifact,
  31. ): Promise<void> {
  32. const details = await stat(artifact.path)
  33. const body = createReadStream(artifact.path)
  34. try {
  35. await client.send(new PutObjectCommand({
  36. Bucket: bucket,
  37. Key: artifact.key,
  38. Body: body,
  39. ContentLength: details.size,
  40. ContentType: artifact.contentType,
  41. CacheControl: artifact.cacheControl,
  42. }))
  43. }
  44. finally {
  45. body.destroy()
  46. }
  47. process.stdout.write(`desktop upload: uploaded ${artifact.key}\n`)
  48. }
  49. async function main(): Promise<void> {
  50. const { positionals } = parseArgs({ args: process.argv.slice(2), allowPositionals: true })
  51. const target = positionals[0]
  52. if (target === undefined || positionals.length !== 1) {
  53. throw new Error('desktop upload: expected exactly one target')
  54. }
  55. const name = targetName(target)
  56. const environment = loadDesktopPackageEnvironment(name === 'win-x64' ? 'win32' : 'darwin')
  57. const plan = await createDesktopUploadPlan(name, { environment })
  58. const client = new S3Client({
  59. region: 'Auto',
  60. endpoint: 'https://cos.ap-beijing.myqcloud.com',
  61. credentials: {
  62. accessKeyId: requiredEnvironmentValue(environment, plan.secretIdEnvName),
  63. secretAccessKey: requiredEnvironmentValue(environment, plan.secretKeyEnvName),
  64. },
  65. })
  66. process.stdout.write(`desktop upload: ${plan.target} ${plan.version} -> ${plan.publicUrl}\n`)
  67. try {
  68. for (const artifact of plan.artifacts) await putArtifact(client, plan.bucket, artifact)
  69. }
  70. finally {
  71. client.destroy()
  72. }
  73. }
  74. if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) {
  75. main().catch((error: unknown) => {
  76. process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`)
  77. process.exitCode = 1
  78. })
  79. }