diff options
author | Ruy Adorno <ruyadorno@hotmail.com> | 2022-03-24 18:46:54 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-24 18:46:54 +0300 |
commit | 723a0918a5a9d9f795584f85d04506fafda9ca42 (patch) | |
tree | 37f0f659d7b81abd9b6cb6d2038ce83571e2c2b4 | |
parent | 362831c4eba2554b44feec60fdff197d92eac0c1 (diff) |
feat(version): reify on workspace version change (#4588)
Adds a minimalistic reify step that updates the installed tree after a
version change within one of the configured workspaces when using any
of the workspaces config options.
It's also possible to use the `--save` config option in order to
auto update semver ranges of dependencies declarations accross dependent
`package.json` files.
Fixes: https://github.com/npm/cli/issues/3403
Relates to: https://github.com/npm/rfcs/issues/556
Relates to: https://github.com/npm/cli/issues/3757
Relates to: https://github.com/npm/cli/issues/4193
-rw-r--r-- | docs/content/commands/npm-version.md | 11 | ||||
-rw-r--r-- | docs/content/using-npm/config.md | 11 | ||||
-rw-r--r-- | lib/commands/version.js | 35 | ||||
-rw-r--r-- | lib/utils/config/definitions.js | 10 | ||||
-rw-r--r-- | tap-snapshots/test/lib/commands/config.js.test.cjs | 2 | ||||
-rw-r--r-- | tap-snapshots/test/lib/commands/version.js.test.cjs | 94 | ||||
-rw-r--r-- | tap-snapshots/test/lib/load-all-commands.js.test.cjs | 2 | ||||
-rw-r--r-- | tap-snapshots/test/lib/utils/config/definitions.js.test.cjs | 11 | ||||
-rw-r--r-- | tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs | 11 | ||||
-rw-r--r-- | tap-snapshots/test/lib/utils/npm-usage.js.test.cjs | 2 | ||||
-rw-r--r-- | test/lib/commands/version.js | 117 |
11 files changed, 297 insertions, 9 deletions
diff --git a/docs/content/commands/npm-version.md b/docs/content/commands/npm-version.md index 86e2ce90e..713e5ae41 100644 --- a/docs/content/commands/npm-version.md +++ b/docs/content/commands/npm-version.md @@ -144,6 +144,17 @@ This value is not exported to the environment for child processes. <!-- automatically generated, do not edit manually --> <!-- see lib/utils/config/definitions.js --> +#### `workspaces-update` + +* Default: true +* Type: Boolean + +If set to true, the npm cli will run an update after operations that may +possibly change the workspaces installed to the `node_modules` folder. + +<!-- automatically generated, do not edit manually --> +<!-- see lib/utils/config/definitions.js --> + #### `include-workspace-root` * Default: false diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md index df259715f..76e5e35e6 100644 --- a/docs/content/using-npm/config.md +++ b/docs/content/using-npm/config.md @@ -1823,6 +1823,17 @@ This value is not exported to the environment for child processes. <!-- automatically generated, do not edit manually --> <!-- see lib/utils/config/definitions.js --> +#### `workspaces-update` + +* Default: true +* Type: Boolean + +If set to true, the npm cli will run an update after operations that may +possibly change the workspaces installed to the `node_modules` folder. + +<!-- automatically generated, do not edit manually --> +<!-- see lib/utils/config/definitions.js --> + #### `yes` * Default: null diff --git a/lib/commands/version.js b/lib/commands/version.js index f49a309a7..ed506f663 100644 --- a/lib/commands/version.js +++ b/lib/commands/version.js @@ -3,6 +3,9 @@ const { resolve } = require('path') const { promisify } = require('util') const readFile = promisify(require('fs').readFile) +const Arborist = require('@npmcli/arborist') +const reifyFinish = require('../utils/reify-finish.js') + const BaseCommand = require('../base-command.js') class Version extends BaseCommand { @@ -17,6 +20,7 @@ class Version extends BaseCommand { 'sign-git-tag', 'workspace', 'workspaces', + 'workspaces-update', 'include-workspace-root', ] @@ -81,6 +85,7 @@ class Version extends BaseCommand { async changeWorkspaces (args, filters) { const prefix = this.npm.config.get('tag-version-prefix') await this.setWorkspaces(filters) + const updatedWorkspaces = [] for (const [name, path] of this.workspaces) { this.npm.output(name) const version = await libnpmversion(args[0], { @@ -88,8 +93,10 @@ class Version extends BaseCommand { 'git-tag-version': false, path, }) + updatedWorkspaces.push(name) this.npm.output(`${prefix}${version}`) } + return this.update(updatedWorkspaces) } async list (results = {}) { @@ -129,6 +136,34 @@ class Version extends BaseCommand { } return this.list(results) } + + async update (args) { + if (!this.npm.flatOptions.workspacesUpdate || !args.length) { + return + } + + // default behavior is to not save by default in order to avoid + // race condition problems when publishing multiple workspaces + // that have dependencies on one another, it might still be useful + // in some cases, which then need to set --save + const save = this.npm.config.isDefault('save') + ? false + : this.npm.config.get('save') + + // runs a minimalistic reify update, targetting only the workspaces + // that had version updates and skipping fund/audit/save + const opts = { + ...this.npm.flatOptions, + audit: false, + fund: false, + path: this.npm.localPrefix, + save, + } + const arb = new Arborist(opts) + + await arb.reify({ ...opts, update: args }) + await reifyFinish(this.npm, arb) + } } module.exports = Version diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js index ddccb1475..abc989d0e 100644 --- a/lib/utils/config/definitions.js +++ b/lib/utils/config/definitions.js @@ -2270,6 +2270,16 @@ define('workspaces', { }, }) +define('workspaces-update', { + default: true, + type: Boolean, + description: ` + If set to true, the npm cli will run an update after operations that may + possibly change the workspaces installed to the \`node_modules\` folder. + `, + flatten, +}) + define('yes', { default: null, type: [null, Boolean], diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index 8e9791523..0806c68ca 100644 --- a/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -155,6 +155,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "which": null, "workspace": [], "workspaces": null, + "workspaces-update": true, "yes": null, "metrics-registry": "https://registry.npmjs.org/" } @@ -308,6 +309,7 @@ viewer = "{VIEWER}" which = null workspace = [] workspaces = null +workspaces-update = true yes = null ; "global" config from {GLOBALPREFIX}/npmrc diff --git a/tap-snapshots/test/lib/commands/version.js.test.cjs b/tap-snapshots/test/lib/commands/version.js.test.cjs new file mode 100644 index 000000000..e19f9b8ee --- /dev/null +++ b/tap-snapshots/test/lib/commands/version.js.test.cjs @@ -0,0 +1,94 @@ +/* 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/commands/version.js TAP empty versions workspaces with one arg, all workspaces > must match snapshot 1`] = ` +{ + "name": "workspaces-test", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "workspaces-test", + "version": "1.0.0", + "workspaces": [ + "workspace-a", + "workspace-b" + ] + }, + "node_modules/workspace-a": { + "resolved": "workspace-a", + "link": true + }, + "node_modules/workspace-b": { + "resolved": "workspace-b", + "link": true + }, + "workspace-a": { + "version": "2.0.0" + }, + "workspace-b": { + "version": "2.0.0" + } + }, + "dependencies": { + "workspace-a": { + "version": "file:workspace-a" + }, + "workspace-b": { + "version": "file:workspace-b" + } + } +} + +` + +exports[`test/lib/commands/version.js TAP empty versions workspaces with one arg, all workspaces, saves package.json > must match snapshot 1`] = ` +{ + "name": "workspaces-test", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "workspaces-test", + "version": "1.0.0", + "workspaces": [ + "workspace-a", + "workspace-b" + ], + "dependencies": { + "workspace-a": "^2.0.0", + "workspace-b": "^2.0.0" + } + }, + "node_modules/workspace-a": { + "resolved": "workspace-a", + "link": true + }, + "node_modules/workspace-b": { + "resolved": "workspace-b", + "link": true + }, + "workspace-a": { + "version": "2.0.0" + }, + "workspace-b": { + "version": "2.0.0" + } + }, + "dependencies": { + "workspace-a": { + "version": "file:workspace-a" + }, + "workspace-b": { + "version": "file:workspace-b" + } + } +} + +` diff --git a/tap-snapshots/test/lib/load-all-commands.js.test.cjs b/tap-snapshots/test/lib/load-all-commands.js.test.cjs index 1ad8aee29..9fe2a5491 100644 --- a/tap-snapshots/test/lib/load-all-commands.js.test.cjs +++ b/tap-snapshots/test/lib/load-all-commands.js.test.cjs @@ -1092,7 +1092,7 @@ Options: [--allow-same-version] [--no-commit-hooks] [--no-git-tag-version] [--json] [--preid prerelease-id] [--sign-git-tag] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] -[-ws|--workspaces] [--include-workspace-root] +[-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root] alias: verison diff --git a/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs b/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs index 373f094a5..4d3a6f150 100644 --- a/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs +++ b/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs @@ -151,6 +151,7 @@ Array [ "which", "workspace", "workspaces", + "workspaces-update", "yes", ] ` @@ -1915,6 +1916,16 @@ _unless_ one or more workspaces are specified in the \`workspace\` config. This value is not exported to the environment for child processes. ` +exports[`test/lib/utils/config/definitions.js TAP > config description for workspaces-update 1`] = ` +#### \`workspaces-update\` + +* Default: true +* Type: Boolean + +If set to true, the npm cli will run an update after operations that may +possibly change the workspaces installed to the \`node_modules\` folder. +` + exports[`test/lib/utils/config/definitions.js TAP > config description for yes 1`] = ` #### \`yes\` diff --git a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs index 3a7d90db0..94ddbe2b1 100644 --- a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs +++ b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs @@ -1697,6 +1697,17 @@ This value is not exported to the environment for child processes. <!-- automatically generated, do not edit manually --> <!-- see lib/utils/config/definitions.js --> +#### \`workspaces-update\` + +* Default: true +* Type: Boolean + +If set to true, the npm cli will run an update after operations that may +possibly change the workspaces installed to the \`node_modules\` folder. + +<!-- automatically generated, do not edit manually --> +<!-- see lib/utils/config/definitions.js --> + #### \`yes\` * Default: null 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 8f73b93f3..181e47da7 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -1125,7 +1125,7 @@ All commands: [--allow-same-version] [--no-commit-hooks] [--no-git-tag-version] [--json] [--preid prerelease-id] [--sign-git-tag] [-w|--workspace <workspace-name> [-w|--workspace <workspace-name> ...]] - [-ws|--workspaces] [--include-workspace-root] + [-ws|--workspaces] [--no-workspaces-update] [--include-workspace-root] alias: verison diff --git a/test/lib/commands/version.js b/test/lib/commands/version.js index 980353897..154f6a6f8 100644 --- a/test/lib/commands/version.js +++ b/test/lib/commands/version.js @@ -1,3 +1,5 @@ +const { readFileSync, statSync } = require('fs') +const { resolve } = require('path') const t = require('tap') const { fake: mockNpm } = require('../../fixtures/mock-npm') const mockGlobals = require('../../fixtures/mock-globals.js') @@ -10,8 +12,13 @@ const config = { 'tag-version-prefix': 'v', json: false, } +const flatOptions = { + workspacesUpdate: true, +} const npm = mockNpm({ config, + flatOptions, + localPrefix: '', prefix: '', version: '1.0.0', output: (...msg) => { @@ -21,14 +28,16 @@ const npm = mockNpm({ }, }) const mocks = { - libnpmversion: noop, + '../../../lib/utils/reify-finish.js': noop, } const Version = t.mock('../../../lib/commands/version.js', mocks) const version = new Version(npm) t.afterEach(() => { + flatOptions.workspacesUpdate = true config.json = false + npm.localPrefix = '' npm.prefix = '' result = [] }) @@ -120,7 +129,7 @@ t.test('empty versions', t => { ...mocks, libnpmversion: (arg, opts) => { t.equal(arg, 'major', 'should forward expected value') - t.same( + t.match( opts, { path: '', @@ -271,7 +280,6 @@ t.test('empty versions', t => { }) t.test('with one arg, all workspaces', async t => { - const libNpmVersionArgs = [] const testDir = t.testdir({ 'package.json': JSON.stringify( { @@ -296,12 +304,54 @@ t.test('empty versions', t => { }, }) const Version = t.mock('../../../lib/commands/version.js', { - ...mocks, - libnpmversion: (arg, opts) => { - libNpmVersionArgs.push([arg, opts]) - return '2.0.0' + '../../../lib/utils/reify-finish.js': noop, + }) + npm.localPrefix = testDir + npm.prefix = testDir + const version = new Version(npm) + + await version.execWorkspaces(['major'], []) + 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.matchSnapshot(readFileSync(resolve(testDir, 'package-lock.json'), 'utf8')) + }) + + t.test('with one arg, all workspaces, saves package.json', async t => { + const testDir = t.testdir({ + 'package.json': JSON.stringify( + { + name: 'workspaces-test', + version: '1.0.0', + workspaces: ['workspace-a', 'workspace-b'], + dependencies: { + 'workspace-a': '^1.0.0', + 'workspace-b': '^1.0.0', + }, + }, + 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/commands/version.js', { + '../../../lib/utils/reify-finish.js': noop, }) + config.save = true npm.localPrefix = testDir npm.prefix = testDir const version = new Version(npm) @@ -312,6 +362,8 @@ t.test('empty versions', t => { ['workspace-a', 'v2.0.0', 'workspace-b', 'v2.0.0'], 'outputs the new version for only the workspaces prefixed by the tagVersionPrefix' ) + + t.matchSnapshot(readFileSync(resolve(testDir, 'package-lock.json'), 'utf8')) }) t.test('too many args', async t => { @@ -321,6 +373,57 @@ t.test('empty versions', t => { 'should throw usage instructions error' ) }) + + t.test('no workspaces-update', async t => { + flatOptions.workspacesUpdate = false + + 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/commands/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) + + await version.execWorkspaces(['major'], []) + 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.throws( + () => statSync(resolve(testDir, 'package-lock.json')), + 'should not have a lockfile since have not reified' + ) + }) }) t.end() |