verify-npm-registry-release.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { existsSync } from 'node:fs'
  2. import { mkdtemp, rm, stat } from 'node:fs/promises'
  3. import { dirname, join, resolve } from 'node:path'
  4. import { spawnSync } from 'node:child_process'
  5. import { tmpdir } from 'node:os'
  6. import { fileURLToPath } from 'node:url'
  7. import { loadEcosystemReleaseContext } from './lib/ecosystem-packages.mjs'
  8. const scriptDir = dirname(fileURLToPath(import.meta.url))
  9. const sourceRoot = resolve(scriptDir, '..')
  10. const args = process.argv.slice(2)
  11. function readArg(name, fallback) {
  12. const index = args.indexOf(name)
  13. return index >= 0 ? args[index + 1] : fallback
  14. }
  15. const explicitPackDir = readArg('--pack-dir', '')
  16. const packDir = explicitPackDir
  17. ? resolve(sourceRoot, explicitPackDir)
  18. : await mkdtemp(join(tmpdir(), 'file-viewer-npm-registry-'))
  19. const registry = readArg(
  20. '--registry',
  21. process.env.FILE_VIEWER_NPM_REGISTRY ||
  22. process.env.NPM_CONFIG_REGISTRY ||
  23. process.env.npm_config_registry ||
  24. 'https://registry.npmjs.org/'
  25. )
  26. const keep = args.includes('--keep')
  27. const { entries } = await loadEcosystemReleaseContext(sourceRoot)
  28. function run(command, commandArgs, options = {}) {
  29. const result = spawnSync(command, commandArgs, {
  30. cwd: options.cwd || sourceRoot,
  31. encoding: 'utf8',
  32. stdio: options.capture ? 'pipe' : 'inherit',
  33. timeout: options.timeout ?? 60_000,
  34. env: {
  35. ...process.env,
  36. npm_config_loglevel: 'error'
  37. }
  38. })
  39. if (result.status !== 0) {
  40. throw new Error(
  41. `Command failed: ${command} ${commandArgs.join(' ')}\n${result.stderr || result.stdout || result.error?.message || ''}`
  42. )
  43. }
  44. return result.stdout || ''
  45. }
  46. async function assertFile(path, label = path) {
  47. if (!existsSync(path)) {
  48. throw new Error(`Missing file: ${label}`)
  49. }
  50. const info = await stat(path)
  51. if (!info.isFile()) {
  52. throw new Error(`Not a file: ${label}`)
  53. }
  54. }
  55. function verifyNpmMetadata(entry) {
  56. const spec = `${entry.packageName}@${entry.version}`
  57. const output = run('npm', ['view', spec, 'name', 'version', 'dist.tarball', '--json', '--registry', registry], {
  58. capture: true
  59. })
  60. const metadata = JSON.parse(output)
  61. if (metadata.name !== entry.packageName) {
  62. throw new Error(`${spec} npm name ${metadata.name} !== ${entry.packageName}`)
  63. }
  64. if (metadata.version !== entry.version) {
  65. throw new Error(`${spec} npm version ${metadata.version} !== ${entry.version}`)
  66. }
  67. const tarball = metadata.dist?.tarball || metadata['dist.tarball']
  68. if (!tarball) {
  69. throw new Error(`${spec} is missing npm dist.tarball`)
  70. }
  71. }
  72. function downloadNpmTarball(entry) {
  73. const spec = `${entry.packageName}@${entry.version}`
  74. verifyNpmMetadata(entry)
  75. run('npm', ['pack', spec, '--pack-destination', packDir, '--registry', registry, '--json'], {
  76. capture: true,
  77. timeout: 120_000
  78. })
  79. }
  80. function readPackedPackageJson(entry) {
  81. const tarballPath = join(packDir, entry.tarballName)
  82. const source = run('tar', ['-xOf', tarballPath, 'package/package.json'], {
  83. capture: true
  84. })
  85. return JSON.parse(source)
  86. }
  87. try {
  88. if (!explicitPackDir) {
  89. for (const entry of entries) {
  90. downloadNpmTarball(entry)
  91. }
  92. }
  93. for (const entry of entries) {
  94. const tarballPath = join(packDir, entry.tarballName)
  95. await assertFile(tarballPath, `${entry.packageName} npm tarball`)
  96. const packageJson = readPackedPackageJson(entry)
  97. if (packageJson.name !== entry.packageName) {
  98. throw new Error(`${entry.tarballName} package name ${packageJson.name} !== ${entry.packageName}`)
  99. }
  100. if (packageJson.version !== entry.version) {
  101. throw new Error(`${entry.tarballName} package version ${packageJson.version} !== ${entry.version}`)
  102. }
  103. }
  104. run('node', ['scripts/verify-ecosystem-tarballs.mjs', '--pack-dir', packDir])
  105. console.log(
  106. `Verified ${entries.length} npm release tarball${entries.length === 1 ? '' : 's'} from ${explicitPackDir ? packDir : registry}.`
  107. )
  108. } finally {
  109. if (!explicitPackDir && !keep) {
  110. await rm(packDir, { recursive: true, force: true })
  111. }
  112. }