diff options
author | Gar <gar+gh@danger.computer> | 2021-07-28 01:38:07 +0300 |
---|---|---|
committer | nlf <quitlahok@gmail.com> | 2021-10-15 00:41:20 +0300 |
commit | 24273a862e54abfd022df9fc4b8c250bfe77817c (patch) | |
tree | d23b525c0d2dfc5d7235d679802f5f595910dbc6 /lib | |
parent | 0f69d295bd5516f496af75ef29e7ae6304fa2ba5 (diff) |
feat(workspaces): add --include-workspace-root and explicit --no-workspacesfritzy/workspace-root
Adds a new config item that includes the workspace root. This also changes
--workspaces to a trinary, so that setting it to false will explicitly exclude
workspaces altogether.
PR-URL: https://github.com/npm/cli/pull/3890
Credit: @fritzy
Close: #3890
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 | 6 | ||||
-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, 144 insertions, 24 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..e654793ec 100644 --- a/lib/init.js +++ b/lib/init.js @@ -19,7 +19,7 @@ class Init extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['yes', 'force', 'workspace', 'workspaces'] + return ['yes', 'force', 'workspace', 'workspaces', 'include-workspace-root'] } /* istanbul ignore next - see test/lib/load-all-commands.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 1a7b06a34..63c5ede8a 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 3bb8a4210..c9806b3c2 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, @@ -2164,8 +2177,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 @@ -2177,16 +2190,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()) { |