1/* eslint-disable standard/no-callback-literal */ 2'use strict' 3 4module.exports = unpublish 5 6const BB = require('bluebird') 7 8const figgyPudding = require('figgy-pudding') 9const libaccess = require('libnpm/access') 10const libunpub = require('libnpm/unpublish') 11const log = require('npmlog') 12const npa = require('npm-package-arg') 13const npm = require('./npm.js') 14const npmConfig = require('./config/figgy-config.js') 15const npmFetch = require('npm-registry-fetch') 16const otplease = require('./utils/otplease.js') 17const output = require('./utils/output.js') 18const path = require('path') 19const readJson = BB.promisify(require('read-package-json')) 20const usage = require('./utils/usage.js') 21const whoami = BB.promisify(require('./whoami.js')) 22 23unpublish.usage = usage( 24 'unpublish', 25 '\nnpm unpublish [<@scope>/]<pkg>@<version>' + 26 '\nnpm unpublish [<@scope>/]<pkg> --force' 27) 28 29function UsageError () { 30 throw Object.assign(new Error(`Usage: ${unpublish.usage}`), { 31 code: 'EUSAGE' 32 }) 33} 34 35const UnpublishConfig = figgyPudding({ 36 force: {}, 37 loglevel: {}, 38 silent: {} 39}) 40 41unpublish.completion = function (cliOpts, cb) { 42 if (cliOpts.conf.argv.remain.length >= 3) return cb() 43 44 whoami([], true).then(username => { 45 if (!username) { return [] } 46 const opts = UnpublishConfig(npmConfig()) 47 return libaccess.lsPackages(username, opts).then(access => { 48 // do a bit of filtering at this point, so that we don't need 49 // to fetch versions for more than one thing, but also don't 50 // accidentally a whole project. 51 let pkgs = Object.keys(access) 52 if (!cliOpts.partialWord || !pkgs.length) { return pkgs } 53 const pp = npa(cliOpts.partialWord).name 54 pkgs = pkgs.filter(p => !p.indexOf(pp)) 55 if (pkgs.length > 1) return pkgs 56 return npmFetch.json(npa(pkgs[0]).escapedName, opts).then(doc => { 57 const vers = Object.keys(doc.versions) 58 if (!vers.length) { 59 return pkgs 60 } else { 61 return vers.map(v => `${pkgs[0]}@${v}`) 62 } 63 }) 64 }) 65 }).nodeify(cb) 66} 67 68function unpublish (args, cb) { 69 if (args.length > 1) return cb(unpublish.usage) 70 71 const spec = args.length && npa(args[0]) 72 const opts = UnpublishConfig(npmConfig()) 73 const version = spec.rawSpec 74 BB.try(() => { 75 log.silly('unpublish', 'args[0]', args[0]) 76 log.silly('unpublish', 'spec', spec) 77 if (!version && !opts.force) { 78 throw Object.assign(new Error( 79 'Refusing to delete entire project.\n' + 80 'Run with --force to do this.\n' + 81 unpublish.usage 82 ), { code: 'EUSAGE' }) 83 } 84 if (!spec || path.resolve(spec.name) === npm.localPrefix) { 85 // if there's a package.json in the current folder, then 86 // read the package name and version out of that. 87 const cwdJson = path.join(npm.localPrefix, 'package.json') 88 return readJson(cwdJson).then(data => { 89 log.verbose('unpublish', data) 90 return otplease(opts, opts => { 91 return libunpub(npa.resolve(data.name, data.version), opts.concat(data.publishConfig)) 92 }) 93 }, err => { 94 if (err && err.code !== 'ENOENT' && err.code !== 'ENOTDIR') { 95 throw err 96 } else { 97 UsageError() 98 } 99 }) 100 } else { 101 return otplease(opts, opts => libunpub(spec, opts)) 102 } 103 }).then( 104 ret => { 105 if (!opts.silent && opts.loglevel !== 'silent') { 106 output(`- ${spec.name}${ 107 spec.type === 'version' ? `@${spec.rawSpec}` : '' 108 }`) 109 } 110 cb(null, ret) 111 }, 112 err => err.code === 'EUSAGE' ? cb(err.message) : cb(err) 113 ) 114} 115