1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
const libaccess = require('libnpmaccess')
const libunpub = require('libnpmpublish').unpublish
const npa = require('npm-package-arg')
const npmFetch = require('npm-registry-fetch')
const path = require('path')
const util = require('util')
const readJson = util.promisify(require('read-package-json'))
const { flatten } = require('../utils/config/index.js')
const getIdentity = require('../utils/get-identity.js')
const log = require('../utils/log-shim')
const otplease = require('../utils/otplease.js')
const LAST_REMAINING_VERSION_ERROR = 'Refusing to delete the last version of the package. ' +
'It will block from republishing a new version for 24 hours.\n' +
'Run with --force to do this.'
const BaseCommand = require('../base-command.js')
class Unpublish extends BaseCommand {
static description = 'Remove a package from the registry'
static name = 'unpublish'
static params = ['dry-run', 'force', 'workspace', 'workspaces']
static usage = ['[<package-spec>]']
static ignoreImplicitWorkspace = false
async getKeysOfVersions (name, opts) {
const pkgUri = npa(name).escapedName
const json = await npmFetch.json(`${pkgUri}?write=true`, opts)
return Object.keys(json.versions)
}
async completion (args) {
const { partialWord, conf } = args
if (conf.argv.remain.length >= 3) {
return []
}
const opts = { ...this.npm.flatOptions }
const username = await getIdentity(this.npm, { ...opts }).catch(() => null)
if (!username) {
return []
}
const access = await libaccess.getPackages(username, opts)
// do a bit of filtering at this point, so that we don't need
// to fetch versions for more than one thing, but also don't
// accidentally unpublish a whole project
let pkgs = Object.keys(access)
if (!partialWord || !pkgs.length) {
return pkgs
}
const pp = npa(partialWord).name
pkgs = pkgs.filter(p => !p.indexOf(pp))
if (pkgs.length > 1) {
return pkgs
}
const versions = await this.getKeysOfVersions(pkgs[0], opts)
if (!versions.length) {
return pkgs
} else {
return versions.map(v => `${pkgs[0]}@${v}`)
}
}
async exec (args) {
if (args.length > 1) {
throw this.usageError()
}
let spec = args.length && npa(args[0])
const force = this.npm.config.get('force')
const { silent } = this.npm
const dryRun = this.npm.config.get('dry-run')
log.silly('unpublish', 'args[0]', args[0])
log.silly('unpublish', 'spec', spec)
if ((!spec || !spec.rawSpec) && !force) {
throw this.usageError(
'Refusing to delete entire project.\n' +
'Run with --force to do this.'
)
}
const opts = { ...this.npm.flatOptions }
let pkgName
let pkgVersion
let manifest
let manifestErr
try {
const pkgJson = path.join(this.npm.localPrefix, 'package.json')
manifest = await readJson(pkgJson)
} catch (err) {
manifestErr = err
}
if (spec) {
// If cwd has a package.json with a name that matches the package being
// unpublished, load up the publishConfig
if (manifest && manifest.name === spec.name && manifest.publishConfig) {
flatten(manifest.publishConfig, opts)
}
const versions = await this.getKeysOfVersions(spec.name, opts)
if (versions.length === 1 && !force) {
throw this.usageError(LAST_REMAINING_VERSION_ERROR)
}
pkgName = spec.name
pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : ''
} else {
if (manifestErr) {
if (manifestErr.code === 'ENOENT' || manifestErr.code === 'ENOTDIR') {
throw this.usageError()
} else {
throw manifestErr
}
}
log.verbose('unpublish', manifest)
spec = npa.resolve(manifest.name, manifest.version)
if (manifest.publishConfig) {
flatten(manifest.publishConfig, opts)
}
pkgName = manifest.name
pkgVersion = manifest.version ? `@${manifest.version}` : ''
}
if (!dryRun) {
await otplease(this.npm, opts, opts => libunpub(spec, opts))
}
if (!silent) {
this.npm.output(`- ${pkgName}${pkgVersion}`)
}
}
async execWorkspaces (args, filters) {
await this.setWorkspaces(filters)
const force = this.npm.config.get('force')
if (!force) {
throw this.usageError(
'Refusing to delete entire project(s).\n' +
'Run with --force to do this.'
)
}
for (const name of this.workspaceNames) {
await this.exec([name])
}
}
}
module.exports = Unpublish
|