next.js/scripts/upload-preview-tarballs.js
upload-preview-tarballs.js89 lines2.8 KB
// @ts-check
const { put } = require('@vercel/blob')
const fs = require('node:fs/promises')
const path = require('node:path')

/**
 * Yields one entry per package tarball under `tarballDirectory`. Scoped
 * packages are laid out one level deeper (e.g. `@next/env/<name>.tgz`), so the
 * walk descends into any directory whose name starts with `@`.
 *
 * @param {string} tarballDirectory
 * @returns {AsyncGenerator<{ packageName: string, tarballPath: string }>}
 */
async function* findTarballs(tarballDirectory) {
  const entries = await fs.readdir(tarballDirectory, { withFileTypes: true })
  for (const entry of entries) {
    if (!entry.isDirectory()) continue
    const entryPath = path.join(tarballDirectory, entry.name)
    if (entry.name.startsWith('@')) {
      const scopeEntries = await fs.readdir(entryPath, { withFileTypes: true })
      for (const scopeEntry of scopeEntries) {
        if (!scopeEntry.isDirectory()) continue
        const tarballPath = await findTarballInDir(
          path.join(entryPath, scopeEntry.name)
        )
        if (tarballPath === null) continue
        yield {
          packageName: `${entry.name}/${scopeEntry.name}`,
          tarballPath,
        }
      }
    } else {
      const tarballPath = await findTarballInDir(entryPath)
      if (tarballPath === null) continue
      yield { packageName: entry.name, tarballPath }
    }
  }
}

/**
 * @param {string} dir
 * @returns {Promise<string | null>}
 */
async function findTarballInDir(dir) {
  const files = await fs.readdir(dir)
  const tgzFile = files.find((f) => f.endsWith('.tgz'))
  return tgzFile ? path.join(dir, tgzFile) : null
}

async function main() {
  const [githubHeadSha, tarballDirectory] = process.argv.slice(2)
  if (!githubHeadSha || !tarballDirectory) {
    throw new Error(
      'Usage: node scripts/upload-preview-tarballs.js <commitSha> <tarballDirectory>'
    )
  }

  if (!process.env.BLOB_READ_WRITE_TOKEN) {
    throw new Error('BLOB_READ_WRITE_TOKEN environment variable is required')
  }
  const blobAccess = process.env.BLOB_ACCESS
  if (blobAccess !== 'private' && blobAccess !== 'public') {
    throw new Error(
      `BLOB_ACCESS environment variable can only be "private" or "public" but got "${blobAccess}".`
    )
  }

  for await (const { packageName, tarballPath } of findTarballs(
    tarballDirectory
  )) {
    const blobPathname = `next/commits/${githubHeadSha}/${packageName}.tgz`

    const fileBuffer = await fs.readFile(tarballPath)
    const { url } = await put(blobPathname, fileBuffer, {
      access: blobAccess,
      addRandomSuffix: false,
      contentType: 'application/gzip',
    })
    console.info(`Uploaded ${packageName} -> ${url}`)
  }

  console.info('All tarballs uploaded to Vercel Blob')
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})
Quest for Codev2.0.0
/
SIGN IN