diff options
author | Gar <gar+gh@danger.computer> | 2021-04-08 20:08:23 +0300 |
---|---|---|
committer | Gar <gar+gh@danger.computer> | 2021-04-15 20:30:37 +0300 |
commit | 8c9e24778db867cb3148bc247c7e321639aa9f58 (patch) | |
tree | 70b5db93056e1dc0b0542d88d348c5a8107213fd | |
parent | 3b476a24cf0b2823fdf92505b84bddde4fcc8b14 (diff) |
feat(version): add workspace support
PR-URL: https://github.com/npm/cli/pull/3055
Credit: @wraithgar
Close: #3055
Reviewed-by: @darcyclarke
-rw-r--r-- | docs/content/commands/npm-version.md | 83 | ||||
-rw-r--r-- | lib/base-command.js | 20 | ||||
-rw-r--r-- | lib/utils/config/definitions.js | 1 | ||||
-rw-r--r-- | lib/version.js | 78 | ||||
-rw-r--r-- | lib/workspaces/get-workspaces.js | 3 | ||||
-rw-r--r-- | tap-snapshots/test/lib/dist-tag.js.test.cjs | 15 | ||||
-rw-r--r-- | tap-snapshots/test/lib/utils/npm-usage.js.test.cjs | 47 | ||||
-rw-r--r-- | test/lib/version.js | 172 |
8 files changed, 358 insertions, 61 deletions
diff --git a/docs/content/commands/npm-version.md b/docs/content/commands/npm-version.md index 0eb814b98..cdf0f977f 100644 --- a/docs/content/commands/npm-version.md +++ b/docs/content/commands/npm-version.md @@ -14,6 +14,56 @@ npm version [<newversion> | major | minor | patch | premajor | preminor | prepat 'npm ls' to inspect current package/dependency versions ``` +### Configuration + +#### `allow-same-version` + +* Default: `false` +* Type: Boolean + +Prevents throwing an error when `npm version` is used to set the new version +to the same value as the current version. + +#### `git-tag-version` + +* Default: `true` +* Type: Boolean + +Commit and tag the version change. + +#### `commit-hooks` + +* Default: `true` +* Type: Boolean + +Run git commit hooks when committing the version change. + +#### `sign-git-tag` + +* Default: `false` +* Type: Boolean + +Pass the `-s` flag to git to sign the tag. + +Note that you must have a default GPG key set up in your git config for this to work properly. + +#### workspaces + +* Default: `false` +* Type: Boolean + +Enables workspaces context and includes workspaces in reported output +when getting versions. When setting a new version *only the workspaces +will be changed*. + +#### workspace + +* Default: [] +* Type: Array + +Enables workspaces context and limits results to only those specified by +this config item. + ### Description Run this in a package directory to bump the version and write the new @@ -87,39 +137,6 @@ This runs all your tests and proceeds only if they pass. Then runs your `build` adds everything in the `dist` directory to the commit. After the commit, it pushes the new commit and tag up to the server, and deletes the `build/temp` directory. -### Configuration - -#### `allow-same-version` - -* Default: `false` -* Type: Boolean - -Prevents throwing an error when `npm version` is used to set the new version -to the same value as the current version. - -#### `git-tag-version` - -* Default: `true` -* Type: Boolean - -Commit and tag the version change. - -#### `commit-hooks` - -* Default: `true` -* Type: Boolean - -Run git commit hooks when committing the version change. - -#### `sign-git-tag` - -* Default: `false` -* Type: Boolean - -Pass the `-s` flag to git to sign the tag. - -Note that you must have a default GPG key set up in your git config for this to work properly. - ### See Also * [npm init](/commands/npm-init) diff --git a/lib/base-command.js b/lib/base-command.js index 91c7c5357..322fd8963 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -4,6 +4,7 @@ const ConfigDefinitions = require('./utils/config/definitions.js') class BaseCommand { constructor (npm) { + this.wrapWidth = 80 this.npm = npm } @@ -27,8 +28,7 @@ class BaseCommand { usage = `${usage}${this.constructor.usage.map(u => `npm ${this.constructor.name} ${u}`).join('\n')}` if (this.constructor.params) - // TODO word wrap this along params boundaries - usage = `${usage}\n\nOptions:\n[${this.constructor.params.map(p => ConfigDefinitions[p].usage).join('] [')}]` + usage = `${usage}\n\nOptions:\n${this.wrappedParams}` // Mostly this just appends aliases, this could be more clear usage = usageUtil(this.constructor.name, usage) @@ -36,6 +36,22 @@ class BaseCommand { return usage } + get wrappedParams () { + let results = '' + let line = '' + + for (const param of this.constructor.params) { + const usage = `[${ConfigDefinitions[param].usage}]` + if (line.length && (line.length + usage.length) > this.wrapWidth) { + results = [results, line].filter(Boolean).join('\n') + line = '' + } + line = [line, usage].filter(Boolean).join(' ') + } + results = [results, line].filter(Boolean).join('\n') + return results + } + usageError (msg) { if (!msg) { return Object.assign(new Error(`\nUsage: ${this.usage}`), { diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js index d87c43dda..db1f25e95 100644 --- a/lib/utils/config/definitions.js +++ b/lib/utils/config/definitions.js @@ -1389,6 +1389,7 @@ define('prefix', { define('preid', { default: '', + hint: 'prerelease-id', type: String, description: ` The "prerelease identifier" to use as a prefix for the "prerelease" part diff --git a/lib/version.js b/lib/version.js index 18b7d7d6c..3ef3801e7 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,6 +1,11 @@ -const libversion = require('libnpmversion') +const libnpmversion = require('libnpmversion') +const { resolve } = require('path') +const { promisify } = require('util') +const readFile = promisify(require('fs').readFile) +const getWorkspaces = require('./workspaces/get-workspaces.js') const BaseCommand = require('./base-command.js') + class Version extends BaseCommand { static get description () { return 'Bump a package version' @@ -12,8 +17,22 @@ class Version extends BaseCommand { } /* istanbul ignore next - see test/lib/load-all-commands.js */ + static get params () { + return [ + 'allow-same-version', + 'commit-hooks', + 'git-tag-version', + 'json', + 'preid', + 'sign-git-tag', + 'workspace', + 'workspaces', + ] + } + + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['[<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]'] + return ['[<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git]'] } async completion (opts) { @@ -37,6 +56,10 @@ class Version extends BaseCommand { return this.version(args).then(() => cb()).catch(cb) } + execWorkspaces (args, filters, cb) { + this.versionWorkspaces(args, filters).then(() => cb()).catch(cb) + } + async version (args) { switch (args.length) { case 0: @@ -48,20 +71,42 @@ class Version extends BaseCommand { } } + async versionWorkspaces (args, filters) { + switch (args.length) { + case 0: + return this.listWorkspaces(filters) + case 1: + return this.changeWorkspaces(args, filters) + default: + throw this.usage + } + } + async change (args) { const prefix = this.npm.config.get('tag-version-prefix') - const version = await libversion(args[0], { + const version = await libnpmversion(args[0], { ...this.npm.flatOptions, path: this.npm.prefix, }) return this.npm.output(`${prefix}${version}`) } - async list () { - const results = {} - const { promisify } = require('util') - const { resolve } = require('path') - const readFile = promisify(require('fs').readFile) + async changeWorkspaces (args, filters) { + const prefix = this.npm.config.get('tag-version-prefix') + const workspaces = + await getWorkspaces(filters, { path: this.npm.localPrefix }) + for (const [name, path] of workspaces) { + this.npm.output(name) + const version = await libnpmversion(args[0], { + ...this.npm.flatOptions, + 'git-tag-version': false, + path, + }) + this.npm.output(`${prefix}${version}`) + } + } + + async list (results = {}) { const pj = resolve(this.npm.prefix, 'package.json') const pkg = await readFile(pj, 'utf8') @@ -80,5 +125,22 @@ class Version extends BaseCommand { else this.npm.output(results) } + + async listWorkspaces (filters) { + const results = {} + const workspaces = + await getWorkspaces(filters, { path: this.npm.localPrefix }) + for (const [, path] of workspaces) { + const pj = resolve(path, 'package.json') + // getWorkspaces has already parsed this so we know it won't error + const pkg = await readFile(pj, 'utf8') + .then(data => JSON.parse(data)) + + if (pkg.name && pkg.version) + results[pkg.name] = pkg.version + } + return this.list(results) + } } + module.exports = Version diff --git a/lib/workspaces/get-workspaces.js b/lib/workspaces/get-workspaces.js index 64812d540..91b007455 100644 --- a/lib/workspaces/get-workspaces.js +++ b/lib/workspaces/get-workspaces.js @@ -3,7 +3,10 @@ const mapWorkspaces = require('@npmcli/map-workspaces') const minimatch = require('minimatch') const rpj = require('read-package-json-fast') +// Returns an Map of paths to workspaces indexed by workspace name +// { foo => '/path/to/foo' } const getWorkspaces = async (filters, { path }) => { + // TODO we need a better error to be bubbled up here if this rpj call fails const pkg = await rpj(resolve(path, 'package.json')) const workspaces = await mapWorkspaces({ cwd: path, pkg }) const res = filters.length ? new Map() : workspaces diff --git a/tap-snapshots/test/lib/dist-tag.js.test.cjs b/tap-snapshots/test/lib/dist-tag.js.test.cjs index ea25b568b..86a9c84eb 100644 --- a/tap-snapshots/test/lib/dist-tag.js.test.cjs +++ b/tap-snapshots/test/lib/dist-tag.js.test.cjs @@ -16,7 +16,8 @@ npm dist-tag rm <pkg> <tag> npm dist-tag ls [<pkg>] Options: -[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] +[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] +[-ws|--workspaces] alias: dist-tags @@ -34,7 +35,8 @@ npm dist-tag rm <pkg> <tag> npm dist-tag ls [<pkg>] Options: -[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] +[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] +[-ws|--workspaces] alias: dist-tags @@ -61,7 +63,8 @@ npm dist-tag rm <pkg> <tag> npm dist-tag ls [<pkg>] Options: -[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] +[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] +[-ws|--workspaces] alias: dist-tags @@ -85,7 +88,8 @@ npm dist-tag rm <pkg> <tag> npm dist-tag ls [<pkg>] Options: -[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] +[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] +[-ws|--workspaces] alias: dist-tags @@ -139,7 +143,8 @@ npm dist-tag rm <pkg> <tag> npm dist-tag ls [<pkg>] Options: -[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] +[-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] +[-ws|--workspaces] alias: dist-tags diff --git a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs index 6bd38772e..e32d5e9f4 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -204,7 +204,8 @@ All commands: npm audit [fix] Options: - [--audit-level <info|low|moderate|high|critical|none>] [--dry-run] [-f|--force] [--json] [--package-lock-only] [--production] + [--audit-level <info|low|moderate|high|critical|none>] [--dry-run] [-f|--force] + [--json] [--package-lock-only] [--production] Run "npm help audit" for more info @@ -324,7 +325,8 @@ All commands: npm dist-tag ls [<pkg>] Options: - [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] alias: dist-tags @@ -338,7 +340,9 @@ All commands: npm docs [<pkgname> [<pkgname> ...]] Options: - [--browser|--browser <browser>] [--registry <registry>] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [--browser|--browser <browser>] [--registry <registry>] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] alias: home @@ -373,7 +377,8 @@ All commands: npm exec --package=foo -c '<cmd> [args...]' Options: - [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] alias: x @@ -416,7 +421,8 @@ All commands: npm fund [[<@scope>/]<pkg>] Options: - [--json] [--browser|--browser <browser>] [--unicode] [--which <fundingSourceNumber>] + [--json] [--browser|--browser <browser>] [--unicode] + [--which <fundingSourceNumber>] Run "npm help fund" for more info @@ -482,7 +488,8 @@ All commands: npm install <github username>/<github project> Options: - [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] [-E|--save-exact] + [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] + [-E|--save-exact] aliases: i, in, ins, inst, insta, instal, isnt, isnta, isntal, add @@ -516,7 +523,8 @@ All commands: npm install-test <github username>/<github project> Options: - [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] [-E|--save-exact] + [-S|--save|--no-save|--save-prod|--save-dev|--save-optional|--save-peer] + [-E|--save-exact] alias: it @@ -625,7 +633,9 @@ All commands: npm pack [[<@scope>/]<pkg>...] Options: - [--dry-run] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [--dry-run] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] Run "npm help pack" for more info @@ -705,7 +715,9 @@ All commands: npm repo [<pkgname> [<pkgname> ...]] Options: - [--browser|--browser <browser>] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [--browser|--browser <browser>] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] Run "npm help repo" for more info @@ -738,7 +750,8 @@ All commands: npm run-script <command> [-- <args>] Options: - [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] aliases: run, rum, urn @@ -775,7 +788,8 @@ All commands: npm set-script [<script>] [<command>] Options: - [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] Run "npm help set-script" for more info @@ -910,7 +924,13 @@ All commands: Bump a package version Usage: - npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git] + npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease | from-git] + + Options: + [--allow-same-version] [--commit-hooks] [--git-tag-version] [--json] + [--preid prerelease-id] [--sign-git-tag] + [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] alias: verison @@ -924,7 +944,8 @@ All commands: npm view [<@scope>/]<pkg>[@<version>] [<field>[.subfield]...] Options: - [--json] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] [-ws|--workspaces] + [--json] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] + [-ws|--workspaces] aliases: v, info, show diff --git a/test/lib/version.js b/test/lib/version.js index 9cd5f47a0..6a83375b0 100644 --- a/test/lib/version.js +++ b/test/lib/version.js @@ -5,6 +5,7 @@ let result = [] const noop = () => null const config = { + 'git-tag-version': true, 'tag-version-prefix': 'v', json: false, } @@ -156,3 +157,174 @@ t.test('with one arg', t => { t.end() }) }) + +t.test('workspaces', t => { + t.teardown(() => { + npm.localPrefix = '' + npm.prefix = '' + }) + + t.test('no args, all workspaces', t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + version.execWorkspaces([], [], err => { + if (err) + throw err + t.same(result, [{ + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + 'workspace-b': '1.0.0', + npm: '1.0.0', + }], 'outputs includes main package and workspace versions') + t.end() + }) + }) + + t.test('no args, single workspaces', t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + version.execWorkspaces([], ['workspace-a'], err => { + if (err) + throw err + t.same(result, [{ + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + npm: '1.0.0', + }], 'outputs includes main package and requested workspace versions') + t.end() + }) + }) + + t.test('no args, all workspaces, workspace with missing name or version', t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b', 'workspace-c'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + }), + }, + 'workspace-c': { + 'package.json': JSON.stringify({ + version: '1.0.0', + }), + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + version.execWorkspaces([], [], err => { + if (err) + throw err + t.same(result, [{ + 'workspaces-test': '1.0.0', + 'workspace-a': '1.0.0', + npm: '1.0.0', + }], 'outputs includes main package and valid workspace versions') + t.end() + }) + }) + + t.test('with one arg, all workspaces', t => { + const libNpmVersionArgs = [] + const testDir = t.testdir({ + 'package.json': JSON.stringify({ + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + }, null, 2), + 'workspace-a': { + 'package.json': JSON.stringify({ + name: 'workspace-a', + version: '1.0.0', + }), + }, + 'workspace-b': { + 'package.json': JSON.stringify({ + name: 'workspace-b', + version: '1.0.0', + }), + }, + }) + const Version = t.mock('../../lib/version.js', { + ...mocks, + libnpmversion: (arg, opts) => { + libNpmVersionArgs.push([arg, opts]) + return '2.0.0' + }, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + + version.execWorkspaces(['major'], [], err => { + if (err) + throw err + t.same(result, ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'], 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix') + t.end() + }) + }) + + t.test('too many args', t => { + version.execWorkspaces(['foo', 'bar'], [], err => { + t.match( + err, + 'npm version', + 'should throw usage instructions error' + ) + + t.end() + }) + }) + + t.end() +}) |