From 2ac540b0ccd016a14676ad891758e8d9e903a12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emin=20Bu=C4=9Fra=20Saral?= Date: Fri, 7 Jan 2022 00:11:04 +0300 Subject: [PATCH] fix(unpublish): Show warning on unpublish command when last version (#4191) --- lib/commands/unpublish.js | 26 ++++++++++++++++-- test/lib/commands/unpublish.js | 48 +++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/lib/commands/unpublish.js b/lib/commands/unpublish.js index 578890025d224..85c366381cc7a 100644 --- a/lib/commands/unpublish.js +++ b/lib/commands/unpublish.js @@ -9,6 +9,10 @@ const log = require('../utils/log-shim') const otplease = require('../utils/otplease.js') const getIdentity = require('../utils/get-identity.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' @@ -16,6 +20,11 @@ class Unpublish extends BaseCommand { static params = ['dry-run', 'force', 'workspace', 'workspaces'] static usage = ['[<@scope>/][@]'] + async getKeysOfVersions (name, opts) { + const json = await npmFetch.json(npa(name).escapedName, opts) + return Object.keys(json.versions) + } + async completion (args) { const { partialWord, conf } = args @@ -44,8 +53,7 @@ class Unpublish extends BaseCommand { return pkgs } - const json = await npmFetch.json(npa(pkgs[0]).escapedName, opts) - const versions = Object.keys(json.versions) + const versions = await this.getKeysOfVersions(pkgs[0], opts) if (!versions.length) { return pkgs } else { @@ -97,12 +105,26 @@ class Unpublish extends BaseCommand { const { name, version, publishConfig } = manifest const pkgJsonSpec = npa.resolve(name, version) const optsWithPub = { ...opts, publishConfig } + + const versions = await this.getKeysOfVersions(name, optsWithPub) + if (versions.length === 1 && !force) { + throw this.usageError( + LAST_REMAINING_VERSION_ERROR + ) + } + if (!dryRun) { await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub)) } pkgName = name pkgVersion = version ? `@${version}` : '' } else { + const versions = await this.getKeysOfVersions(spec.name, opts) + if (versions.length === 1 && !force) { + throw this.usageError( + LAST_REMAINING_VERSION_ERROR + ) + } if (!dryRun) { await otplease(opts, opts => libunpub(spec, opts)) } diff --git a/test/lib/commands/unpublish.js b/test/lib/commands/unpublish.js index 1424adf5c9851..b1b148a7c27ec 100644 --- a/test/lib/commands/unpublish.js +++ b/test/lib/commands/unpublish.js @@ -3,6 +3,23 @@ const { fake: mockNpm } = require('../../fixtures/mock-npm') let result = '' const noop = () => null +const versions = async () => { + return { + versions: { + '1.0.0': {}, + '1.0.1': {}, + }, + } +} + +const singleVersion = async () => { + return { + versions: { + '1.0.0': {}, + }, + } +} + const config = { force: false, loglevel: 'silly', @@ -26,7 +43,7 @@ const npm = mockNpm({ const mocks = { libnpmaccess: { lsPackages: noop }, libnpmpublish: { unpublish: noop }, - 'npm-registry-fetch': { json: noop }, + 'npm-registry-fetch': { json: versions }, '../../../lib/utils/otplease.js': async (opts, fn) => fn(opts), '../../../lib/utils/get-identity.js': async () => 'foo', 'proc-log': { silly () {}, verbose () {} }, @@ -530,3 +547,32 @@ t.test('completion', async t => { }) }) }) + +t.test('show error on unpublish @version with package.json and the last version', async t => { + const Unpublish = t.mock('../../../lib/commands/unpublish.js', { + ...mocks, + 'npm-registry-fetch': { json: singleVersion }, + path: { resolve: () => testDir, join: () => testDir + '/package.json' }, + }) + const unpublish = new Unpublish(npm) + await t.rejects( + unpublish.exec(['pkg@1.0.0']), + '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.' + ) +}) + +t.test('show error on unpublish @version when the last version', async t => { + const Unpublish = t.mock('../../../lib/commands/unpublish.js', { + ...mocks, + 'npm-registry-fetch': { json: singleVersion }, + }) + const unpublish = new Unpublish(npm) + await t.rejects( + unpublish.exec(['pkg@1.0.0']), + '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.' + ) +})