diff options
author | Gar <gar+gh@danger.computer> | 2021-07-28 01:38:07 +0300 |
---|---|---|
committer | Luke Karrys <luke@lukekarrys.com> | 2021-09-30 23:35:23 +0300 |
commit | f17dfa0ced7d8df9bb7baf378bb20d33175c8e8b (patch) | |
tree | 68ddb44f93c8f139e8cb592340a1612e608c65a6 /lib | |
parent | 8349c3c1557ac52973ad08c10db492e3a5a30204 (diff) |
feat(workspaces): --include-workspace-root
Adds a new config item that includes the workspace root when running
non-arborist commands (i.e. repo, version, publish). Arborist will need
to be udpated to look for this flag to change its behavior to include
the workspace root for its functions.
This also changes --workspaces to a trinary, so that setting it to false
will explicitly exclude workspaces altogether. This is also going to
require an arborist change so that it ignores workspaces altogether.
Co-author: @fritzy
PR-URL: https://github.com/npm/cli/pull/3816
Credit: @isaacs
Close: #3816
Reviewed-by: @wraithgar
Diffstat (limited to 'lib')
-rw-r--r-- | lib/base-command.js | 10 | ||||
-rw-r--r-- | lib/diff.js | 1 | ||||
-rw-r--r-- | lib/dist-tag.js | 2 | ||||
-rw-r--r-- | lib/docs.js | 8 | ||||
-rw-r--r-- | lib/exec.js | 8 | ||||
-rw-r--r-- | lib/explain.js | 9 | ||||
-rw-r--r-- | lib/fund.js | 1 | ||||
-rw-r--r-- | lib/init.js | 4 | ||||
-rw-r--r-- | lib/link.js | 1 | ||||
-rw-r--r-- | lib/ls.js | 9 | ||||
-rw-r--r-- | lib/npm.js | 12 | ||||
-rw-r--r-- | lib/outdated.js | 9 | ||||
-rw-r--r-- | lib/pack.js | 1 | ||||
-rw-r--r-- | lib/publish.js | 10 | ||||
-rw-r--r-- | lib/repo.js | 10 | ||||
-rw-r--r-- | lib/run-script.js | 1 | ||||
-rw-r--r-- | lib/set-script.js | 2 | ||||
-rw-r--r-- | lib/utils/completion/installed-deep.js | 9 | ||||
-rw-r--r-- | lib/utils/config/definitions.js | 41 | ||||
-rw-r--r-- | lib/version.js | 1 | ||||
-rw-r--r-- | lib/view.js | 1 | ||||
-rw-r--r-- | lib/workspaces/arborist-cmd.js | 7 | ||||
-rw-r--r-- | lib/workspaces/get-workspaces.js | 9 |
23 files changed, 143 insertions, 23 deletions
diff --git a/lib/base-command.js b/lib/base-command.js index 870c69acc..c5bd3fd94 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -7,8 +7,6 @@ class BaseCommand { constructor (npm) { this.wrapWidth = 80 this.npm = npm - this.workspaces = null - this.workspacePaths = null } get name () { @@ -75,7 +73,13 @@ class BaseCommand { } async setWorkspaces (filters) { - const ws = await getWorkspaces(filters, { path: this.npm.localPrefix }) + if (this.isArboristCmd) + this.includeWorkspaceRoot = false + + const ws = await getWorkspaces(filters, { + path: this.npm.localPrefix, + includeWorkspaceRoot: this.includeWorkspaceRoot, + }) this.workspaces = ws this.workspaceNames = [...ws.keys()] this.workspacePaths = [...ws.values()] diff --git a/lib/diff.js b/lib/diff.js index 01658c466..b1a32705c 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -43,6 +43,7 @@ class Diff extends BaseCommand { 'tag', 'workspace', 'workspaces', + 'include-workspace-root', ] } diff --git a/lib/dist-tag.js b/lib/dist-tag.js index e32dcf61f..be44f39ff 100644 --- a/lib/dist-tag.js +++ b/lib/dist-tag.js @@ -14,7 +14,7 @@ class DistTag extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['workspace', 'workspaces'] + return ['workspace', 'workspaces', 'include-workspace-root'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/docs.js b/lib/docs.js index 69a19c35c..51f8be388 100644 --- a/lib/docs.js +++ b/lib/docs.js @@ -17,7 +17,13 @@ class Docs extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['browser', 'registry', 'workspace', 'workspaces'] + return [ + 'browser', + 'registry', + 'workspace', + 'workspaces', + 'include-workspace-root', + ] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/exec.js b/lib/exec.js index 8c64c2f24..d11947483 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -35,7 +35,13 @@ class Exec extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['package', 'call', 'workspace', 'workspaces'] + return [ + 'package', + 'call', + 'workspace', + 'workspaces', + 'include-workspace-root', + ] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/explain.js b/lib/explain.js index 7d785d7bf..fc7f57891 100644 --- a/lib/explain.js +++ b/lib/explain.js @@ -46,8 +46,15 @@ class Explain extends ArboristWorkspaceCmd { const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions }) const tree = await arb.loadActual() - if (this.workspaceNames && this.workspaceNames.length) + if (this.npm.flatOptions.workspacesEnabled + && this.workspaceNames + && this.workspaceNames.length + ) this.filterSet = arb.workspaceDependencySet(tree, this.workspaceNames) + else if (!this.npm.flatOptions.workspacesEnabled) { + this.filterSet = + arb.excludeWorkspacesDependencySet(tree) + } const nodes = new Set() for (const arg of args) { diff --git a/lib/fund.js b/lib/fund.js index 1e0fa1ecb..97139f5bb 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -92,6 +92,7 @@ class Fund extends ArboristWorkspaceCmd { return } + // TODO: add !workspacesEnabled option handling to libnpmfund const fundingInfo = getFundingInfo(tree, { ...this.flatOptions, log: this.npm.log, diff --git a/lib/init.js b/lib/init.js index e4bd20b72..30fd7e2f7 100644 --- a/lib/init.js +++ b/lib/init.js @@ -54,6 +54,10 @@ class Init extends BaseCommand { } async initWorkspaces (args, filters) { + // if the root package is uninitiated, take care of it first + if (this.npm.flatOptions.includeWorkspaceRoot) + await this.init(args) + // reads package.json for the top-level folder first, by doing this we // ensure the command throw if no package.json is found before trying // to create a workspace package.json file or its folders diff --git a/lib/link.js b/lib/link.js index febd90871..2437eb12e 100644 --- a/lib/link.js +++ b/lib/link.js @@ -185,6 +185,7 @@ class Link extends ArboristWorkspaceCmd { // atm but should be simple once we have a mocked registry again if (arg.name !== node.name /* istanbul ignore next */ || ( arg.version && + /* istanbul ignore next */ !semver.satisfies(node.version, arg.version) )) { foundNodes.push(node) @@ -82,6 +82,7 @@ class LS extends ArboristWorkspaceCmd { const production = this.npm.config.get('production') const unicode = this.npm.config.get('unicode') const packageLockOnly = this.npm.config.get('package-lock-only') + const workspacesEnabled = this.npm.flatOptions.workspacesEnabled const path = global ? resolve(this.npm.globalDir, '..') : this.npm.prefix @@ -100,12 +101,18 @@ class LS extends ArboristWorkspaceCmd { if (this.workspaceNames && this.workspaceNames.length) wsNodes = arb.workspaceNodes(tree, this.workspaceNames) const filterBySelectedWorkspaces = edge => { + if (!workspacesEnabled + && edge.from.isProjectRoot + && edge.to.isWorkspace + ) + return false + if (!wsNodes || !wsNodes.length) return true if (edge.from.isProjectRoot) { return edge.to && - edge.to.isWorkspace & + edge.to.isWorkspace && wsNodes.includes(edge.to.target) } diff --git a/lib/npm.js b/lib/npm.js index 966d11210..9b9f3b238 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -137,7 +137,19 @@ const npm = module.exports = new class extends EventEmitter { const workspacesEnabled = this.config.get('workspaces') const workspacesFilters = this.config.get('workspace') + if (workspacesEnabled === false && workspacesFilters.length > 0) + return cb(new Error('Can not use --no-workspaces and --workspace at the same time')) + const filterByWorkspaces = workspacesEnabled || workspacesFilters.length > 0 + // normally this would go in the constructor, but our tests don't + // actually use a real npm object so this.npm.config isn't always + // populated. this is the compromise until we can make that a reality + // and then move this into the constructor. + impl.workspaces = this.config.get('workspaces') + impl.workspacePaths = null + // normally this would be evaluated in base-command#setWorkspaces, see + // above for explanation + impl.includeWorkspaceRoot = this.config.get('include-workspace-root') if (this.config.get('usage')) { this.output(impl.usage) diff --git a/lib/outdated.js b/lib/outdated.js index b3b630421..ab46b4536 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -62,7 +62,14 @@ class Outdated extends ArboristWorkspaceCmd { if (this.workspaceNames && this.workspaceNames.length) { this.filterSet = - arb.workspaceDependencySet(this.tree, this.workspaceNames) + arb.workspaceDependencySet( + this.tree, + this.workspaceNames, + this.npm.flatOptions.includeWorkspaceRoot + ) + } else if (!this.npm.flatOptions.workspacesEnabled) { + this.filterSet = + arb.excludeWorkspacesDependencySet(this.tree) } if (args.length !== 0) { diff --git a/lib/pack.js b/lib/pack.js index 8fc89db1a..848f8afd5 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -30,6 +30,7 @@ class Pack extends BaseCommand { 'pack-destination', 'workspace', 'workspaces', + 'include-workspace-root', ] } diff --git a/lib/publish.js b/lib/publish.js index 9c747eb50..32e70129f 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -36,7 +36,15 @@ class Publish extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['tag', 'access', 'dry-run', 'otp', 'workspace', 'workspaces'] + return [ + 'tag', + 'access', + 'dry-run', + 'otp', + 'workspace', + 'workspaces', + 'include-workspace-root', + ] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/repo.js b/lib/repo.js index e0172d01f..bf1d1e7ff 100644 --- a/lib/repo.js +++ b/lib/repo.js @@ -19,7 +19,7 @@ class Repo extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['browser', 'workspace', 'workspaces'] + return ['browser', 'workspace', 'workspaces', 'include-workspace-root'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -48,7 +48,13 @@ class Repo extends BaseCommand { } async get (pkg) { - const opts = { ...this.npm.flatOptions, fullMetadata: true } + // XXX It is very odd that `where` is how pacote knows to look anywhere + // other than the cwd. + const opts = { + ...this.npm.flatOptions, + where: this.npm.localPrefix, + fullMetadata: true, + } const mani = await pacote.manifest(pkg, opts) const r = mani.repository diff --git a/lib/run-script.js b/lib/run-script.js index 1daaeb990..de847ff28 100644 --- a/lib/run-script.js +++ b/lib/run-script.js @@ -38,6 +38,7 @@ class RunScript extends BaseCommand { return [ 'workspace', 'workspaces', + 'include-workspace-root', 'if-present', 'ignore-scripts', 'script-shell', diff --git a/lib/set-script.js b/lib/set-script.js index 24e4d8f20..185c4cc90 100644 --- a/lib/set-script.js +++ b/lib/set-script.js @@ -12,7 +12,7 @@ class SetScript extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['workspace', 'workspaces'] + return ['workspace', 'workspaces', 'include-workspace-root'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ diff --git a/lib/utils/completion/installed-deep.js b/lib/utils/completion/installed-deep.js index 590955a1e..62686f9b2 100644 --- a/lib/utils/completion/installed-deep.js +++ b/lib/utils/completion/installed-deep.js @@ -7,6 +7,7 @@ const installedDeep = async (npm) => { depth, global, prefix, + workspacesEnabled, } = npm.flatOptions const getValues = (tree) => @@ -19,14 +20,18 @@ const installedDeep = async (npm) => { .sort((a, b) => (a.depth - b.depth) || localeCompare(a.name, b.name)) const res = new Set() - const gArb = new Arborist({ global: true, path: resolve(npm.globalDir, '..') }) + const gArb = new Arborist({ + global: true, + path: resolve(npm.globalDir, '..'), + workspacesEnabled, + }) const gTree = await gArb.loadActual({ global: true }) for (const node of getValues(gTree)) res.add(global ? node.name : [node.name, '-g']) if (!global) { - const arb = new Arborist({ global: false, path: prefix }) + const arb = new Arborist({ global: false, path: prefix, workspacesEnabled }) const tree = await arb.loadActual() for (const node of getValues(tree)) res.add(node.name) diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js index 009f60a7b..57a50d1c8 100644 --- a/lib/utils/config/definitions.js +++ b/lib/utils/config/definitions.js @@ -918,6 +918,19 @@ define('include-staged', { flatten, }) +define('include-workspace-root', { + default: false, + type: Boolean, + description: ` + Include the workspace root when workspaces are enabled for a command. + + When false, specifying individual workspaces via the \`workspace\` config, + or all workspaces via the \`workspaces\` flag, will cause npm to operate only + on the specified workspaces, and not on the root project. + `, + flatten, +}) + define('init-author-email', { default: '', type: String, @@ -2137,8 +2150,8 @@ define('workspace', { * Workspace names * Path to a workspace directory - * Path to a parent workspace directory (will result to selecting all of the - nested workspaces) + * Path to a parent workspace directory (will result in selecting all + workspaces within that folder) 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 @@ -2150,16 +2163,34 @@ define('workspace', { }) define('workspaces', { - default: false, - type: Boolean, + default: null, + type: [null, Boolean], short: 'ws', envExport: false, description: ` - Enable running a command in the context of **all** the configured + Set to true to run the command in the context of **all** configured workspaces. + + Explicitly setting this to false will cause commands like \`install\` to + ignore workspaces altogether. + When not set explicitly: + + - Commands that operate on the \`node_modules\` tree (install, update, + etc.) will link workspaces into the \`node_modules\` folder. + - Commands that do other things (test, exec, publish, etc.) will operate + on the root project, _unless_ one or more workspaces are specified in + the \`workspace\` config. `, flatten: (key, obj, flatOptions) => { definitions['user-agent'].flatten('user-agent', obj, flatOptions) + + // TODO: this is a derived value, and should be reworked when we have a + // pattern for derived value + + // workspacesEnabled is true whether workspaces is null or true + // commands contextually work with workspaces or not regardless of + // configuration, so we need an option specifically to disable workspaces + flatOptions.workspacesEnabled = obj[key] !== false }, }) diff --git a/lib/version.js b/lib/version.js index f3680fe8b..917a64747 100644 --- a/lib/version.js +++ b/lib/version.js @@ -26,6 +26,7 @@ class Version extends BaseCommand { 'sign-git-tag', 'workspace', 'workspaces', + 'include-workspace-root', ] } diff --git a/lib/view.js b/lib/view.js index 0124bfb7d..46b1b5edf 100644 --- a/lib/view.js +++ b/lib/view.js @@ -31,6 +31,7 @@ class View extends BaseCommand { 'json', 'workspace', 'workspaces', + 'include-workspace-root', ] } diff --git a/lib/workspaces/arborist-cmd.js b/lib/workspaces/arborist-cmd.js index cb6b66b8c..a75b351be 100644 --- a/lib/workspaces/arborist-cmd.js +++ b/lib/workspaces/arborist-cmd.js @@ -4,16 +4,21 @@ const BaseCommand = require('../base-command.js') class ArboristCmd extends BaseCommand { + get isArboristCmd () { + return true + } + /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { return [ 'workspace', 'workspaces', + 'include-workspace-root', ] } execWorkspaces (args, filters, cb) { - this.setWorkspaces(filters) + this.setWorkspaces(filters, true) .then(() => { this.exec(args, cb) }) diff --git a/lib/workspaces/get-workspaces.js b/lib/workspaces/get-workspaces.js index 91b007455..3eb8e4865 100644 --- a/lib/workspaces/get-workspaces.js +++ b/lib/workspaces/get-workspaces.js @@ -5,11 +5,16 @@ 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 }) => { +const getWorkspaces = async (filters, { path, includeWorkspaceRoot }) => { // 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 + let res = new Map() + if (includeWorkspaceRoot) + res.set(pkg.name, path) + + if (!filters.length) + res = new Map([...res, ...workspaces]) for (const filterArg of filters) { for (const [workspaceName, workspacePath] of workspaces.entries()) { |