mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
feat: --workspace-root
`--workspace-root`, `-w`: a new option that allows to focus on the root workspace project. E.g., the following command runs the `lint` script of the root `package.json` from anywhere in the monorepo: ``` pnpm -w lint ``` PR #2866
This commit is contained in:
5
.changeset/beige-masks-brush.md
Normal file
5
.changeset/beige-masks-brush.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/parse-cli-args": minor
|
||||
---
|
||||
|
||||
When --workspace-root is used, the working directory is changed to the root of the workspace.
|
||||
5
.changeset/clean-parents-perform.md
Normal file
5
.changeset/clean-parents-perform.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
---
|
||||
|
||||
When the --workspace-root option is used, it is allowed to add a new dependency to the root workspace project. Because this way the intention is clear.
|
||||
5
.changeset/friendly-yaks-battle.md
Normal file
5
.changeset/friendly-yaks-battle.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-script-runners": minor
|
||||
---
|
||||
|
||||
When a script is not found but is present in the workspace root, suggest to use `pnpm -w run`.
|
||||
5
.changeset/ninety-jars-arrive.md
Normal file
5
.changeset/ninety-jars-arrive.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-script-runners": minor
|
||||
---
|
||||
|
||||
`pnpm run` prints all scripts from the root of the workspace. They may be executed using `pnpm -w run`.
|
||||
6
.changeset/poor-fireants-complain.md
Normal file
6
.changeset/poor-fireants-complain.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/common-cli-options-help": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
New universal option added: -w, --workspace-root.
|
||||
5
.changeset/tidy-islands-look.md
Normal file
5
.changeset/tidy-islands-look.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
---
|
||||
|
||||
New setting added: workspace-root.
|
||||
@@ -40,6 +40,11 @@ export const UNIVERSAL_OPTIONS = [
|
||||
name: '--dir <dir>',
|
||||
shortAlias: '-C',
|
||||
},
|
||||
{
|
||||
description: 'Run the command on the root workspace project',
|
||||
name: '--workspace-root',
|
||||
shortAlias: '-w',
|
||||
},
|
||||
{
|
||||
description: 'What level of logs to report. Any logs at or higher than the given level will be shown. Levels (lowest to highest): debug, info, warn, error. Or use "--silent" to turn off all logging.',
|
||||
name: '--loglevel <level>',
|
||||
|
||||
@@ -115,6 +115,7 @@ export interface Config {
|
||||
|
||||
registries: Registries
|
||||
ignoreWorkspaceRootCheck: boolean
|
||||
workspaceRoot: boolean
|
||||
}
|
||||
|
||||
export interface ConfigWithDeprecatedSettings extends Config {
|
||||
|
||||
@@ -85,6 +85,7 @@ export const types = Object.assign({
|
||||
'virtual-store-dir': String,
|
||||
'workspace-concurrency': Number,
|
||||
'workspace-packages': [String, Array],
|
||||
'workspace-root': Boolean,
|
||||
}, npmTypes.types)
|
||||
|
||||
export type CliOptions = Record<string, unknown> & { dir?: string }
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/parse-cli-args#readme",
|
||||
"devDependencies": {
|
||||
"@pnpm/parse-cli-args": "link:",
|
||||
"@types/nopt": "^3.0.29"
|
||||
"@types/nopt": "^3.0.29",
|
||||
"tempy": "^0.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/error": "workspace:^1.3.1",
|
||||
"@pnpm/find-workspace-dir": "workspace:1.0.1",
|
||||
"didyoumean2": "^4.1.0",
|
||||
"nopt": "^5.0.0"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import PnpmError from '@pnpm/error'
|
||||
import findWorkspaceDir from '@pnpm/find-workspace-dir'
|
||||
import didYouMean, { ReturnTypeEnums } from 'didyoumean2'
|
||||
import nopt = require('nopt')
|
||||
@@ -117,6 +118,15 @@ export default async function parseCliArgs (
|
||||
const workspaceDir = options['global'] // eslint-disable-line
|
||||
? undefined
|
||||
: await findWorkspaceDir(dir)
|
||||
if (options['workspace-root']) {
|
||||
if (options['global']) {
|
||||
throw new PnpmError('OPTIONS_CONFLICT', '--workspace-root may not be used with --global')
|
||||
}
|
||||
if (!workspaceDir) {
|
||||
throw new PnpmError('NOT_IN_WORKSPACE', '--workspace-root may only be used inside a workspace')
|
||||
}
|
||||
options['dir'] = workspaceDir
|
||||
}
|
||||
|
||||
if (cmd === 'install' && params.length > 0) {
|
||||
cmd = 'add'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import PnpmError from '@pnpm/error'
|
||||
import parseCliArgs from '@pnpm/parse-cli-args'
|
||||
import os = require('os')
|
||||
import test = require('tape')
|
||||
import tempy = require('tempy')
|
||||
|
||||
const DEFAULT_OPTS = {
|
||||
getCommandLongName: (commandName: string) => commandName,
|
||||
@@ -213,3 +215,35 @@ test("don't use the fallback command if no command is present", async (t) => {
|
||||
t.deepEqual(params, [])
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('--workspace-root changes the directory to the workspace root', async (t) => {
|
||||
const { options, workspaceDir } = await parseCliArgs({ ...DEFAULT_OPTS }, ['--workspace-root'])
|
||||
t.ok(workspaceDir)
|
||||
t.equal(options.dir, workspaceDir)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('--workspace-root fails if used with --global', async (t) => {
|
||||
let err!: PnpmError
|
||||
try {
|
||||
await parseCliArgs({ ...DEFAULT_OPTS }, ['--workspace-root', '--global'])
|
||||
} catch (_err) {
|
||||
err = _err
|
||||
}
|
||||
t.ok(err)
|
||||
t.equal(err.code, 'ERR_PNPM_OPTIONS_CONFLICT')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('--workspace-root fails if used outside of a workspace', async (t) => {
|
||||
process.chdir(tempy.directory())
|
||||
let err!: PnpmError
|
||||
try {
|
||||
await parseCliArgs({ ...DEFAULT_OPTS }, ['--workspace-root'])
|
||||
} catch (_err) {
|
||||
err = _err
|
||||
}
|
||||
t.ok(err)
|
||||
t.equal(err.code, 'ERR_PNPM_NOT_IN_WORKSPACE')
|
||||
t.end()
|
||||
})
|
||||
|
||||
@@ -160,6 +160,7 @@ export function handler (
|
||||
save?: boolean
|
||||
update?: boolean
|
||||
useBetaCli?: boolean
|
||||
workspaceRoot?: boolean
|
||||
},
|
||||
params: string[]
|
||||
) {
|
||||
@@ -172,12 +173,14 @@ export function handler (
|
||||
if (
|
||||
!opts.recursive &&
|
||||
opts.workspaceDir === opts.dir &&
|
||||
!opts.ignoreWorkspaceRootCheck
|
||||
!opts.ignoreWorkspaceRootCheck &&
|
||||
!opts.workspaceRoot
|
||||
) {
|
||||
throw new PnpmError('ADDING_TO_ROOT',
|
||||
'Running this command will add the dependency to the workspace root, ' +
|
||||
'which might not be what you want - if you really meant it, ' +
|
||||
'make it explicit by running this command again with the -W flag (or --ignore-workspace-root-check).'
|
||||
'make it explicit by running this command again with the -w flag (or --workspace-root). ' +
|
||||
'If you don\'t want to see this warning anymore, you may set the ignore-workspace-root-check setting to false.'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ export async function handler (
|
||||
if (rootManifest?.scripts?.[scriptName]) {
|
||||
throw new PnpmError('NO_SCRIPT', `Missing script: ${scriptName}`, {
|
||||
hint: `But ${scriptName} is present in the root of the workspace,
|
||||
so you may run this command in: ${opts.workspaceDir}`,
|
||||
so you may run "pnpm -w ${scriptName}"`,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -225,15 +225,16 @@ function printProjectCommands (
|
||||
if (output !== '') output += '\n\n'
|
||||
output += `Commands available via "pnpm run":\n${renderCommands(otherScripts)}`
|
||||
}
|
||||
if (rootManifest?.scripts) {
|
||||
const rootScripts = Object.entries(rootManifest.scripts)
|
||||
.filter(([scriptName]) => !manifest.scripts?.[scriptName])
|
||||
if (rootScripts.length) {
|
||||
if (output !== '') output += '\n\n'
|
||||
output += `Commands of the root workspace project (to run them, go to the root of the workspace):
|
||||
${renderCommands(rootScripts)}`
|
||||
}
|
||||
if (!rootManifest?.scripts) {
|
||||
return output
|
||||
}
|
||||
const rootScripts = Object.entries(rootManifest.scripts)
|
||||
if (!rootScripts.length) {
|
||||
return output
|
||||
}
|
||||
if (output !== '') output += '\n\n'
|
||||
output += `Commands of the root workspace project (to run them, use "pnpm -w run"):
|
||||
${renderCommands(rootScripts)}`
|
||||
return output
|
||||
}
|
||||
|
||||
|
||||
@@ -298,9 +298,11 @@ Commands available via "pnpm run":
|
||||
foo
|
||||
echo hi
|
||||
|
||||
Commands of the root workspace project (to run them, go to the root of the workspace):
|
||||
Commands of the root workspace project (to run them, use "pnpm -w run"):
|
||||
build
|
||||
echo root`)
|
||||
echo root
|
||||
test
|
||||
test-all`)
|
||||
t.end()
|
||||
})
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export const GLOBAL_OPTIONS = R.pick([
|
||||
'reporter',
|
||||
'stream',
|
||||
'workspace-packages',
|
||||
'workspace-root',
|
||||
], allTypes)
|
||||
|
||||
export type CommandResponse = string | { output: string, exitCode: number } | undefined
|
||||
|
||||
@@ -34,6 +34,7 @@ export default {
|
||||
'frozen-shrinkwrap': '--frozen-lockfile',
|
||||
'prefer-frozen-shrinkwrap': '--prefer-frozen-lockfile',
|
||||
W: '--ignore-workspace-root-check',
|
||||
w: '--workspace-root',
|
||||
i: '--interactive',
|
||||
}
|
||||
// eslint-enable
|
||||
|
||||
@@ -364,7 +364,7 @@ test('non-recursive install ignores filter from config', async (t: tape.Test) =>
|
||||
await projects['project-3'].hasNot('minimatch')
|
||||
})
|
||||
|
||||
test('adding new dependency in the root should fail if --ignore-workspace-root-check is not used', async (t: tape.Test) => {
|
||||
test('adding new dependency in the root should fail if neither --workspace-root nor --ignore-workspace-root-check are used', async (t: tape.Test) => {
|
||||
const project = prepare(t)
|
||||
|
||||
await fs.writeFile('pnpm-workspace.yaml', '', 'utf8')
|
||||
@@ -377,8 +377,7 @@ test('adding new dependency in the root should fail if --ignore-workspace-root-c
|
||||
t.ok(
|
||||
stdout.toString().includes( // eslint-disable-line
|
||||
'Running this command will add the dependency to the workspace root, ' +
|
||||
'which might not be what you want - if you really meant it, ' +
|
||||
'make it explicit by running this command again with the -W flag (or --ignore-workspace-root-check).'
|
||||
'which might not be what you want - if you really meant it, '
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -396,6 +395,20 @@ test('adding new dependency in the root should fail if --ignore-workspace-root-c
|
||||
t.equal(status, 0)
|
||||
await project.has('is-negative')
|
||||
}
|
||||
|
||||
{
|
||||
const { status } = execPnpmSync(['add', 'is-odd', '--workspace-root'])
|
||||
|
||||
t.equal(status, 0)
|
||||
await project.has('is-odd')
|
||||
}
|
||||
|
||||
{
|
||||
const { status } = execPnpmSync(['add', 'is-even', '-w'])
|
||||
|
||||
t.equal(status, 0)
|
||||
await project.has('is-even')
|
||||
}
|
||||
})
|
||||
|
||||
test('--workspace-packages', async (t: tape.Test) => {
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -1447,18 +1447,22 @@ importers:
|
||||
write-json-file: 4.0.0
|
||||
packages/parse-cli-args:
|
||||
dependencies:
|
||||
'@pnpm/error': 'link:../error'
|
||||
'@pnpm/find-workspace-dir': 'link:../find-workspace-dir'
|
||||
didyoumean2: 4.1.0
|
||||
nopt: 5.0.0
|
||||
devDependencies:
|
||||
'@pnpm/parse-cli-args': 'link:'
|
||||
'@types/nopt': 3.0.29
|
||||
tempy: 0.7.0
|
||||
specifiers:
|
||||
'@pnpm/error': 'workspace:^1.3.1'
|
||||
'@pnpm/find-workspace-dir': 'workspace:1.0.1'
|
||||
'@pnpm/parse-cli-args': 'link:'
|
||||
'@types/nopt': ^3.0.29
|
||||
didyoumean2: ^4.1.0
|
||||
nopt: ^5.0.0
|
||||
tempy: ^0.7.0
|
||||
packages/parse-wanted-dependency:
|
||||
dependencies:
|
||||
validate-npm-package-name: 3.0.0
|
||||
|
||||
Reference in New Issue
Block a user