Skip to content

Commit

Permalink
feat(unpublish): add workspace/dry-run support
Browse files Browse the repository at this point in the history
PR-URL: #3251
Credit: @wraithgar
Close: #3251
Reviewed-by: @ruyadorno, @isaacs
  • Loading branch information
wraithgar authored and isaacs committed May 20, 2021
1 parent faa12cc commit fde3546
Show file tree
Hide file tree
Showing 9 changed files with 269 additions and 98 deletions.
45 changes: 45 additions & 0 deletions docs/content/commands/npm-unpublish.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ passed.

<!-- AUTOGENERATED CONFIG DESCRIPTIONS START -->
<!-- automatically generated, do not edit manually -->
#### `dry-run`

* Default: false
* Type: Boolean

Indicates that you don't want npm to make any changes and that it should
only report what it would have done. This can be passed into any of the
commands that modify your local installation, eg, `install`, `update`,
`dedupe`, `uninstall`, as well as `pack` and `publish`.

Note: This is NOT honored by other network related commands, eg `dist-tags`,
`owner`, etc.

#### `force`

* Default: false
Expand All @@ -73,6 +86,38 @@ mistakes, unnecessary performance degradation, and malicious input.
If you don't have a clear idea of what you want to do, it is strongly
recommended that you do not use this option!

#### `workspace`

* Default:
* Type: String (can be set multiple times)

Enable running a command in the context of the configured workspaces of the
current project while filtering by running only the workspaces defined by
this configuration option.

Valid values for the `workspace` config are either:

* Workspace names
* Path to a workspace directory
* Path to a parent workspace directory (will result to selecting all of the
nested workspaces)

When set for the `npm init` command, this may be set to the folder of a
workspace which does not yet exist, to create the folder and set it up as a
brand new workspace within the project.

This value is not exported to the environment for child processes.

#### `workspaces`

* Default: false
* Type: Boolean

Enable running a command in the context of **all** the configured
workspaces.

This value is not exported to the environment for child processes.

<!-- AUTOGENERATED CONFIG DESCRIPTIONS END -->

### See Also
Expand Down
2 changes: 1 addition & 1 deletion lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Publish extends BaseCommand {
if (args.length === 0)
args = ['.']
if (args.length !== 1)
throw this.usage
throw this.usageError()

log.verbose('publish', args)

Expand Down
58 changes: 40 additions & 18 deletions lib/unpublish.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const path = require('path')
const util = require('util')
const log = require('npmlog')
const npa = require('npm-package-arg')
const libaccess = require('libnpmaccess')
const npmFetch = require('npm-registry-fetch')
const libunpub = require('libnpmpublish').unpublish
const readJson = util.promisify(require('read-package-json'))

const getWorkspaces = require('./workspaces/get-workspaces.js')
const otplease = require('./utils/otplease.js')
const getIdentity = require('./utils/get-identity.js')

Expand All @@ -22,13 +22,13 @@ class Unpublish extends BaseCommand {
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get usage () {
return ['[<@scope>/]<pkg>[@<version>]']
static get params () {
return ['dry-run', 'force', 'workspace', 'workspaces']
}

/* istanbul ignore next - see test/lib/load-all-commands.js */
static get params () {
return ['force']
static get usage () {
return ['[<@scope>/]<pkg>[@<version>]']
}

async completion (args) {
Expand Down Expand Up @@ -67,25 +67,29 @@ class Unpublish extends BaseCommand {
this.unpublish(args).then(() => cb()).catch(cb)
}

execWorkspaces (args, filters, cb) {
this.unpublishWorkspaces(args, filters).then(() => cb()).catch(cb)
}

async unpublish (args) {
if (args.length > 1)
throw new Error(this.usage)
throw this.usageError()

const spec = args.length && npa(args[0])
const force = this.npm.config.get('force')
const silent = this.npm.config.get('silent')
const loglevel = this.npm.config.get('loglevel')
const silent = loglevel === 'silent'
const dryRun = this.npm.config.get('dry-run')
let pkgName
let pkgVersion

log.silly('unpublish', 'args[0]', args[0])
log.silly('unpublish', 'spec', spec)
this.npm.log.silly('unpublish', 'args[0]', args[0])
this.npm.log.silly('unpublish', 'spec', spec)

if (!spec.rawSpec && !force) {
throw new Error(
if ((!spec || !spec.rawSpec) && !force) {
throw this.usageError(
'Refusing to delete entire project.\n' +
'Run with --force to do this.\n' +
this.usage
'Run with --force to do this.'
)
}

Expand All @@ -101,25 +105,43 @@ class Unpublish extends BaseCommand {
if (err && err.code !== 'ENOENT' && err.code !== 'ENOTDIR')
throw err
else
throw new Error(`Usage: ${this.usage}`)
throw this.usageError()
}

log.verbose('unpublish', manifest)
this.npm.log.verbose('unpublish', manifest)

const { name, version, publishConfig } = manifest
const pkgJsonSpec = npa.resolve(name, version)
const optsWithPub = { ...opts, publishConfig }
await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub))
if (!dryRun)
await otplease(opts, opts => libunpub(pkgJsonSpec, optsWithPub))
pkgName = name
pkgVersion = version ? `@${version}` : ''
} else {
await otplease(opts, opts => libunpub(spec, opts))
if (!dryRun)
await otplease(opts, opts => libunpub(spec, opts))
pkgName = spec.name
pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : ''
}

if (!silent && loglevel !== 'silent')
if (!silent)
this.npm.output(`- ${pkgName}${pkgVersion}`)
}

async unpublishWorkspaces (args, filters) {
const workspaces =
await getWorkspaces(filters, { path: this.npm.localPrefix })

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 workspaces.entries())
await this.unpublish([name])
}
}
module.exports = Unpublish
4 changes: 3 additions & 1 deletion tap-snapshots/test/lib/load-all-commands.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,9 @@ Usage:
npm unpublish [<@scope>/]<pkg>[@<version>]
Options:
[-f|--force]
[--dry-run] [-f|--force]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]
Run "npm help unpublish" for more info
`
Expand Down
23 changes: 21 additions & 2 deletions tap-snapshots/test/lib/publish.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/
'use strict'
exports[`test/lib/publish.js TAP shows usage with wrong set of arguments > should print usage 1`] = `
npm publish
Error:
Usage: npm publish
Publish a package
Expand All @@ -18,13 +19,16 @@ Options:
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]
Run "npm help publish" for more info
Run "npm help publish" for more info {
"code": "EUSAGE",
}
`

exports[`test/lib/publish.js TAP workspaces all workspaces > should output all publishes 1`] = `
Array [
"+ workspace-a@1.2.3-a",
"+ workspace-b@1.2.3-n",
"+ workspace-n@1.2.3-n",
]
`

Expand Down Expand Up @@ -54,6 +58,12 @@ Array [
},
"version": "1.2.3-n",
},
Object {
"_id": "workspace-n@1.2.3-n",
"name": "workspace-n",
"readme": "ERROR: No README data found!",
"version": "1.2.3-n",
},
]
`

Expand All @@ -66,6 +76,9 @@ Array [
},
"workspace-b": {
"id": "workspace-b@1.2.3-n"
},
"workspace-n": {
"id": "workspace-n@1.2.3-n"
}
}
),
Expand Down Expand Up @@ -98,6 +111,12 @@ Array [
},
"version": "1.2.3-n",
},
Object {
"_id": "workspace-n@1.2.3-n",
"name": "workspace-n",
"readme": "ERROR: No README data found!",
"version": "1.2.3-n",
},
]
`

Expand Down
14 changes: 14 additions & 0 deletions tap-snapshots/test/lib/unpublish.js.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* IMPORTANT
* This snapshot file is auto-generated, but designed for humans.
* It should be checked into source control and tracked carefully.
* Re-generate by setting TAP_SNAPSHOT=1 and running tests.
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/lib/unpublish.js TAP workspaces all workspaces --force > should output all workspaces 1`] = `
- workspace-a- workspace-b- workspace-n
`

exports[`test/lib/unpublish.js TAP workspaces one workspace --force > should output one workspaces 1`] = `
- workspace-a
`
4 changes: 3 additions & 1 deletion tap-snapshots/test/lib/utils/npm-usage.js.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,9 @@ All commands:
npm unpublish [<@scope>/]<pkg>[@<version>]
Options:
[-f|--force]
[--dry-run] [-f|--force]
[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]]
[-ws|--workspaces]
Run "npm help unpublish" for more info
Expand Down
8 changes: 4 additions & 4 deletions test/lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,12 +538,12 @@ t.test('workspaces', (t) => {
repository: 'https://github.com/npm/workspace-b',
}),
},
'workspace-c': JSON.stringify({
'package.json': {
'workspace-c': {
'package.json': JSON.stringify({
name: 'workspace-n',
version: '1.2.3-n',
},
}),
}),
},
})

const publishes = []
Expand Down
Loading

0 comments on commit fde3546

Please sign in to comment.