Skip to content

Commit 2ac540b

Browse files
ebsaralnlf
authored andcommitted
fix(unpublish): Show warning on unpublish command when last version (#4191)
1 parent 1f0d137 commit 2ac540b

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

lib/commands/unpublish.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,22 @@ const log = require('../utils/log-shim')
99
const otplease = require('../utils/otplease.js')
1010
const getIdentity = require('../utils/get-identity.js')
1111

12+
const LAST_REMAINING_VERSION_ERROR = 'Refusing to delete the last version of the package. ' +
13+
'It will block from republishing a new version for 24 hours.\n' +
14+
'Run with --force to do this.'
15+
1216
const BaseCommand = require('../base-command.js')
1317
class Unpublish extends BaseCommand {
1418
static description = 'Remove a package from the registry'
1519
static name = 'unpublish'
1620
static params = ['dry-run', 'force', 'workspace', 'workspaces']
1721
static usage = ['[<@scope>/]<pkg>[@<version>]']
1822

23+
async getKeysOfVersions (name, opts) {
24+
const json = await npmFetch.json(npa(name).escapedName, opts)
25+
return Object.keys(json.versions)
26+
}
27+
1928
async completion (args) {
2029
const { partialWord, conf } = args
2130

@@ -44,8 +53,7 @@ class Unpublish extends BaseCommand {
4453
return pkgs
4554
}
4655

47-
const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts)
48-
const versions = Object.keys(json.versions)
56+
const versions = await this.getKeysOfVersions(pkgs[0], opts)
4957
if (!versions.length) {
5058
return pkgs
5159
} else {
@@ -97,12 +105,26 @@ class Unpublish extends BaseCommand {
97105
const { name, version, publishConfig } = manifest
98106
const pkgJsonSpec = npa.resolve(name, version)
99107
const optsWithPub = { ...opts, publishConfig }
108+
109+
const versions = await this.getKeysOfVersions(name, optsWithPub)
110+
if (versions.length === 1 && !force) {
111+
throw this.usageError(
112+
LAST_REMAINING_VERSION_ERROR
113+
)
114+
}
115+
100116
if (!dryRun) {
101117
await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub))
102118
}
103119
pkgName = name
104120
pkgVersion = version ? `@${version}` : ''
105121
} else {
122+
const versions = await this.getKeysOfVersions(spec.name, opts)
123+
if (versions.length === 1 && !force) {
124+
throw this.usageError(
125+
LAST_REMAINING_VERSION_ERROR
126+
)
127+
}
106128
if (!dryRun) {
107129
await otplease(opts, opts => libunpub(spec, opts))
108130
}

test/lib/commands/unpublish.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@ const { fake: mockNpm } = require('../../fixtures/mock-npm')
33

44
let result = ''
55
const noop = () => null
6+
const versions = async () => {
7+
return {
8+
versions: {
9+
'1.0.0': {},
10+
'1.0.1': {},
11+
},
12+
}
13+
}
14+
15+
const singleVersion = async () => {
16+
return {
17+
versions: {
18+
'1.0.0': {},
19+
},
20+
}
21+
}
22+
623
const config = {
724
force: false,
825
loglevel: 'silly',
@@ -26,7 +43,7 @@ const npm = mockNpm({
2643
const mocks = {
2744
libnpmaccess: { lsPackages: noop },
2845
libnpmpublish: { unpublish: noop },
29-
'npm-registry-fetch': { json: noop },
46+
'npm-registry-fetch': { json: versions },
3047
'../../../lib/utils/otplease.js': async (opts, fn) => fn(opts),
3148
'../../../lib/utils/get-identity.js': async () => 'foo',
3249
'proc-log': { silly () {}, verbose () {} },
@@ -530,3 +547,32 @@ t.test('completion', async t => {
530547
})
531548
})
532549
})
550+
551+
t.test('show error on unpublish <pkg>@version with package.json and the last version', async t => {
552+
const Unpublish = t.mock('../../../lib/commands/unpublish.js', {
553+
...mocks,
554+
'npm-registry-fetch': { json: singleVersion },
555+
path: { resolve: () => testDir, join: () => testDir + '/package.json' },
556+
})
557+
const unpublish = new Unpublish(npm)
558+
await t.rejects(
559+
unpublish.exec(['[email protected]']),
560+
'Refusing to delete the last version of the package. ' +
561+
'It will block from republishing a new version for 24 hours.\n' +
562+
'Run with --force to do this.'
563+
)
564+
})
565+
566+
t.test('show error on unpublish <pkg>@version when the last version', async t => {
567+
const Unpublish = t.mock('../../../lib/commands/unpublish.js', {
568+
...mocks,
569+
'npm-registry-fetch': { json: singleVersion },
570+
})
571+
const unpublish = new Unpublish(npm)
572+
await t.rejects(
573+
unpublish.exec(['[email protected]']),
574+
'Refusing to delete the last version of the package. ' +
575+
'It will block from republishing a new version for 24 hours.\n' +
576+
'Run with --force to do this.'
577+
)
578+
})

0 commit comments

Comments
 (0)