upload-to-cdn.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/usr/bin/env node
  2. // Upload Uppy releases to Edgly.net CDN. Copyright (c) 2018, Transloadit Ltd.
  3. //
  4. // This file:
  5. //
  6. // - Assumes EDGLY_KEY and EDGLY_SECRET are available (e.g. set via Travis secrets)
  7. // - Assumes a fully built uppy is in root dir (unless a specific tag was specified, then it's fetched from npm)
  8. // - Collects dist/ files that would be in an npm package release, and uploads to eg. https://transloadit.edgly.net/releases/uppy/v1.0.1/uppy.css
  9. // - Uses local package by default, if [version] argument was specified, takes package from npm
  10. //
  11. // Run as:
  12. //
  13. // npm run uploadcdn <package-name> [version]
  14. //
  15. // Authors:
  16. //
  17. // - Kevin van Zonneveld <kevin@transloadit.com>
  18. const path = require('path')
  19. const AWS = require('aws-sdk')
  20. const packlist = require('npm-packlist')
  21. const tar = require('tar')
  22. const pacote = require('pacote')
  23. const concat = require('concat-stream')
  24. const mime = require('mime-types')
  25. const { promisify } = require('util')
  26. const readFile = promisify(require('fs').readFile)
  27. const finished = promisify(require('stream').finished)
  28. function delay (ms) {
  29. return new Promise(resolve => setTimeout(resolve, ms))
  30. }
  31. const AWS_REGION = 'us-east-1'
  32. const AWS_BUCKET = 'crates.edgly.net'
  33. const AWS_DIRECTORY = '756b8efaed084669b02cb99d4540d81f/default'
  34. /**
  35. * Get remote dist/ files by fetching the tarball for the given version
  36. * from npm and filtering it down to package/dist/ files.
  37. *
  38. * @param {string} Package name, eg. @uppy/robodog
  39. * @param {string} Package version, eg. "1.2.0"
  40. * @returns a Map<string, Buffer>, filename → content
  41. */
  42. async function getRemoteDistFiles (packageName, version) {
  43. const files = new Map()
  44. const tarball = pacote.tarball.stream(`${packageName}@${version}`)
  45. .pipe(new tar.Parse())
  46. tarball.on('entry', (readEntry) => {
  47. if (readEntry.path.startsWith('package/dist/')) {
  48. readEntry
  49. .pipe(concat((buf) => {
  50. files.set(readEntry.path.replace(/^package\/dist\//, ''), buf)
  51. }))
  52. .on('error', (err) => {
  53. tarball.emit('error', err)
  54. })
  55. } else {
  56. readEntry.resume()
  57. }
  58. })
  59. await finished(tarball)
  60. return files
  61. }
  62. /**
  63. * Get local dist/ files by asking npm-packlist what files would be added
  64. * to an npm package during publish, and filtering those down to just dist/ files.
  65. *
  66. * @param {string} Base file path of the package, eg. ./packages/@uppy/locales
  67. * @returns a Map<string, Buffer>, filename → content
  68. */
  69. async function getLocalDistFiles (packagePath) {
  70. const files = (await packlist({ path: packagePath }))
  71. .filter(f => f.startsWith('dist/'))
  72. .map(f => f.replace(/^dist\//, ''))
  73. const entries = await Promise.all(
  74. files.map(async (f) => [
  75. f,
  76. await readFile(path.join(packagePath, 'dist', f))
  77. ])
  78. )
  79. return new Map(entries)
  80. }
  81. async function main (packageName, version) {
  82. if (!packageName) {
  83. console.error('usage: upload-to-cdn <packagename> [version]')
  84. console.error('Must provide a package name')
  85. process.exit(1)
  86. }
  87. if (!process.env.EDGLY_KEY || !process.env.EDGLY_SECRET) {
  88. console.error('Missing EDGLY_KEY or EDGLY_SECRET env variables, bailing')
  89. process.exit(1)
  90. }
  91. // version should only be a positional arg and semver string
  92. // this deals with usage like `npm run uploadcdn uppy -- --force`
  93. // where we force push a local build
  94. if (version.startsWith('-')) version = undefined
  95. const s3 = new AWS.S3({
  96. credentials: new AWS.Credentials({
  97. accessKeyId: process.env.EDGLY_KEY,
  98. secretAccessKey: process.env.EDGLY_SECRET
  99. }),
  100. region: AWS_REGION
  101. })
  102. const remote = !!version
  103. if (!remote) {
  104. version = require(`../packages/${packageName}/package.json`).version
  105. }
  106. // Warn if uploading a local build not from CI:
  107. // - If we're on CI, this should be a release commit.
  108. // - If we're local, normally we should upload a released version, not a local build.
  109. if (!remote && !process.env.CI) {
  110. console.log('Warning, writing a local build to the CDN, this is usually not what you want. Sleeping 3s. Press CTRL+C!')
  111. await delay(3000)
  112. }
  113. const packagePath = remote
  114. ? `${packageName}@${version}`
  115. : path.join(__dirname, '..', 'packages', packageName)
  116. // uppy → releases/uppy/
  117. // @uppy/robodog → releases/uppy/robodog/
  118. // @uppy/locales → releases/uppy/locales/
  119. const dirName = packageName.startsWith('@uppy/')
  120. ? packageName.replace(/^@/, '')
  121. : 'uppy'
  122. const outputPath = path.posix.join('releases', dirName, `v${version}`)
  123. const { Contents: existing } = await s3.listObjects({
  124. Bucket: AWS_BUCKET,
  125. Prefix: `${AWS_DIRECTORY}/${outputPath}/`
  126. }).promise()
  127. if (existing.length > 0) {
  128. if (process.argv.includes('--force')) {
  129. console.warn(`WARN Release files for ${dirName} v${version} already exist, overwriting...`)
  130. } else {
  131. console.error(`Release files for ${dirName} v${version} already exist, exiting...`)
  132. process.exit(1)
  133. }
  134. }
  135. const files = remote
  136. ? await getRemoteDistFiles(packageName, version)
  137. : await getLocalDistFiles(packagePath)
  138. for (const [filename, buffer] of files.entries()) {
  139. const key = path.posix.join(AWS_DIRECTORY, outputPath, filename)
  140. console.log(`pushing s3://${AWS_BUCKET}/${key}`)
  141. await s3.putObject({
  142. Bucket: AWS_BUCKET,
  143. Key: key,
  144. ContentType: mime.lookup(filename),
  145. Body: buffer
  146. }).promise()
  147. }
  148. }
  149. main(...process.argv.slice(2)).catch((err) => {
  150. console.error(err.stack)
  151. process.exit(1)
  152. })