upload-target.ts 2.8 KB

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