unpublish.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use strict'
  2. const npa = require('npm-package-arg')
  3. const npmFetch = require('npm-registry-fetch')
  4. const semver = require('semver')
  5. const { URL } = require('url')
  6. const unpublish = async (spec, opts) => {
  7. spec = npa(spec)
  8. // spec is used to pick the appropriate registry/auth combo.
  9. opts = {
  10. force: false,
  11. ...opts,
  12. spec,
  13. }
  14. try {
  15. const pkgUri = spec.escapedName
  16. const pkg = await npmFetch.json(pkgUri, {
  17. ...opts,
  18. query: { write: true },
  19. })
  20. const version = spec.rawSpec
  21. const allVersions = pkg.versions || {}
  22. const versionData = allVersions[version]
  23. const rawSpecs = (!spec.rawSpec || spec.rawSpec === '*')
  24. const onlyVersion = Object.keys(allVersions).length === 1
  25. const noVersions = !Object.keys(allVersions).length
  26. // if missing specific version,
  27. // assumed unpublished
  28. if (!versionData && !rawSpecs && !noVersions)
  29. return true
  30. // unpublish all versions of a package:
  31. // - no specs supplied "npm unpublish foo"
  32. // - all specs ("*") "npm unpublish foo@*"
  33. // - there was only one version
  34. // - has no versions field on packument
  35. if (rawSpecs || onlyVersion || noVersions) {
  36. await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
  37. ...opts,
  38. method: 'DELETE',
  39. ignoreBody: true,
  40. })
  41. return true
  42. } else {
  43. const dist = allVersions[version].dist
  44. delete allVersions[version]
  45. const latestVer = pkg['dist-tags'].latest
  46. // deleting dist tags associated to version
  47. Object.keys(pkg['dist-tags']).forEach(tag => {
  48. if (pkg['dist-tags'][tag] === version)
  49. delete pkg['dist-tags'][tag]
  50. })
  51. if (latestVer === version) {
  52. pkg['dist-tags'].latest = Object.keys(
  53. allVersions
  54. ).sort(semver.compareLoose).pop()
  55. }
  56. delete pkg._revisions
  57. delete pkg._attachments
  58. // Update packument with removed versions
  59. await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
  60. ...opts,
  61. method: 'PUT',
  62. body: pkg,
  63. ignoreBody: true,
  64. })
  65. // Remove the tarball itself
  66. const { _rev } = await npmFetch.json(pkgUri, {
  67. ...opts,
  68. query: { write: true },
  69. })
  70. const tarballUrl = new URL(dist.tarball).pathname.substr(1)
  71. await npmFetch(`${tarballUrl}/-rev/${_rev}`, {
  72. ...opts,
  73. method: 'DELETE',
  74. ignoreBody: true,
  75. })
  76. return true
  77. }
  78. } catch (err) {
  79. if (err.code !== 'E404')
  80. throw err
  81. return true
  82. }
  83. }
  84. module.exports = unpublish