unpublish.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. const path = require('path')
  2. const util = require('util')
  3. const npa = require('npm-package-arg')
  4. const libaccess = require('libnpmaccess')
  5. const npmFetch = require('npm-registry-fetch')
  6. const libunpub = require('libnpmpublish').unpublish
  7. const readJson = util.promisify(require('read-package-json'))
  8. const otplease = require('./utils/otplease.js')
  9. const getIdentity = require('./utils/get-identity.js')
  10. const BaseCommand = require('./base-command.js')
  11. class Unpublish extends BaseCommand {
  12. static get description () {
  13. return 'Remove a package from the registry'
  14. }
  15. /* istanbul ignore next - see test/lib/load-all-commands.js */
  16. static get name () {
  17. return 'unpublish'
  18. }
  19. /* istanbul ignore next - see test/lib/load-all-commands.js */
  20. static get params () {
  21. return ['dry-run', 'force', 'workspace', 'workspaces']
  22. }
  23. /* istanbul ignore next - see test/lib/load-all-commands.js */
  24. static get usage () {
  25. return ['[<@scope>/]<pkg>[@<version>]']
  26. }
  27. async completion (args) {
  28. const { partialWord, conf } = args
  29. if (conf.argv.remain.length >= 3)
  30. return []
  31. const opts = this.npm.flatOptions
  32. const username = await getIdentity(this.npm, { ...opts }).catch(() => null)
  33. if (!username)
  34. return []
  35. const access = await libaccess.lsPackages(username, opts)
  36. // do a bit of filtering at this point, so that we don't need
  37. // to fetch versions for more than one thing, but also don't
  38. // accidentally unpublish a whole project
  39. let pkgs = Object.keys(access || {})
  40. if (!partialWord || !pkgs.length)
  41. return pkgs
  42. const pp = npa(partialWord).name
  43. pkgs = pkgs.filter(p => !p.indexOf(pp))
  44. if (pkgs.length > 1)
  45. return pkgs
  46. const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts)
  47. const versions = Object.keys(json.versions)
  48. if (!versions.length)
  49. return pkgs
  50. else
  51. return versions.map(v => `${pkgs[0]}@${v}`)
  52. }
  53. exec (args, cb) {
  54. this.unpublish(args).then(() => cb()).catch(cb)
  55. }
  56. execWorkspaces (args, filters, cb) {
  57. this.unpublishWorkspaces(args, filters).then(() => cb()).catch(cb)
  58. }
  59. async unpublish (args) {
  60. if (args.length > 1)
  61. throw this.usageError()
  62. const spec = args.length && npa(args[0])
  63. const force = this.npm.config.get('force')
  64. const loglevel = this.npm.config.get('loglevel')
  65. const silent = loglevel === 'silent'
  66. const dryRun = this.npm.config.get('dry-run')
  67. let pkgName
  68. let pkgVersion
  69. this.npm.log.silly('unpublish', 'args[0]', args[0])
  70. this.npm.log.silly('unpublish', 'spec', spec)
  71. if ((!spec || !spec.rawSpec) && !force) {
  72. throw this.usageError(
  73. 'Refusing to delete entire project.\n' +
  74. 'Run with --force to do this.'
  75. )
  76. }
  77. const opts = this.npm.flatOptions
  78. if (!spec || path.resolve(spec.name) === this.npm.localPrefix) {
  79. // if there's a package.json in the current folder, then
  80. // read the package name and version out of that.
  81. const pkgJson = path.join(this.npm.localPrefix, 'package.json')
  82. let manifest
  83. try {
  84. manifest = await readJson(pkgJson)
  85. } catch (err) {
  86. if (err && err.code !== 'ENOENT' && err.code !== 'ENOTDIR')
  87. throw err
  88. else
  89. throw this.usageError()
  90. }
  91. this.npm.log.verbose('unpublish', manifest)
  92. const { name, version, publishConfig } = manifest
  93. const pkgJsonSpec = npa.resolve(name, version)
  94. const optsWithPub = { ...opts, publishConfig }
  95. if (!dryRun)
  96. await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub))
  97. pkgName = name
  98. pkgVersion = version ? `@${version}` : ''
  99. } else {
  100. if (!dryRun)
  101. await otplease(opts, opts => libunpub(spec, opts))
  102. pkgName = spec.name
  103. pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : ''
  104. }
  105. if (!silent)
  106. this.npm.output(`- ${pkgName}${pkgVersion}`)
  107. }
  108. async unpublishWorkspaces (args, filters) {
  109. await this.setWorkspaces(filters)
  110. const force = this.npm.config.get('force')
  111. if (!force) {
  112. throw this.usageError(
  113. 'Refusing to delete entire project(s).\n' +
  114. 'Run with --force to do this.'
  115. )
  116. }
  117. for (const name of this.workspaceNames)
  118. await this.unpublish([name])
  119. }
  120. }
  121. module.exports = Unpublish