Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGar <gar+gh@danger.computer>2021-07-28 01:38:07 +0300
committernlf <quitlahok@gmail.com>2021-10-15 00:41:20 +0300
commit24273a862e54abfd022df9fc4b8c250bfe77817c (patch)
treed23b525c0d2dfc5d7235d679802f5f595910dbc6 /lib
parent0f69d295bd5516f496af75ef29e7ae6304fa2ba5 (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.js10
-rw-r--r--lib/diff.js1
-rw-r--r--lib/dist-tag.js2
-rw-r--r--lib/docs.js8
-rw-r--r--lib/exec.js8
-rw-r--r--lib/explain.js9
-rw-r--r--lib/fund.js1
-rw-r--r--lib/init.js6
-rw-r--r--lib/link.js1
-rw-r--r--lib/ls.js9
-rw-r--r--lib/npm.js12
-rw-r--r--lib/outdated.js9
-rw-r--r--lib/pack.js1
-rw-r--r--lib/publish.js10
-rw-r--r--lib/repo.js10
-rw-r--r--lib/run-script.js1
-rw-r--r--lib/set-script.js2
-rw-r--r--lib/utils/completion/installed-deep.js9
-rw-r--r--lib/utils/config/definitions.js41
-rw-r--r--lib/version.js1
-rw-r--r--lib/view.js1
-rw-r--r--lib/workspaces/arborist-cmd.js7
-rw-r--r--lib/workspaces/get-workspaces.js9
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)
diff --git a/lib/ls.js b/lib/ls.js
index 495b6348a..46cfb2462 100644
--- a/lib/ls.js
+++ b/lib/ls.js
@@ -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()) {