feat: add "multi exec -- <command>" command

close #1227
This commit is contained in:
Zoltan Kochan
2018-06-23 22:22:28 +03:00
committed by Zoltan Kochan
parent 8f209b3149
commit b8658a7d6c
7 changed files with 134 additions and 78 deletions

View File

@@ -43,6 +43,7 @@
"cross-spawn": "^6.0.3",
"delay": "^3.0.0",
"diable": "^4.0.1",
"execa": "^0.10.0",
"find-packages": "^2.2.0",
"get-port": "^3.2.0",
"graceful-fs": "^4.1.11",
@@ -96,7 +97,6 @@
"byline": "^5.0.0",
"caw": "^2.0.0",
"deep-require-cwd": "^1.0.0",
"execa": "^0.10.0",
"exists-link": "^2.0.0",
"mkdirp": "^0.5.1",
"normalize-newline": "^3.0.0",

View File

@@ -236,78 +236,33 @@ function getHelpText (command: string) {
case 'recursive':
return stripIndent`
Usage: pnpm recursive [command] [flags]
pnpm recursive [command] [flags]
pnpm multi [command] [flags]
pnpm m [command] [flags]
Aliases: multi, m
Concurrently performs some actions in all subdirectories with a \`package.json\` (excluding node_modules).
A \`pnpm-workspace.yaml\` file may be used to control what directories are searched for packages.
pnpm recursive install
Commands:
Concurrently runs installation in all subdirectories with a \`package.json\` (excluding node_modules).
install
update
link runs installation in each package. If a package is available locally, the local version is linked.
unlink removes links to local packages and reinstalls them from the registry.
list [[<@scope>/]<pkg> ...] list dependencies in each package.
outdated [[<@scope>/]<pkg> ...] check for outdated dependencies in every package.
Options: same as for \`pnpm install\`
run <command> [-- <args>...] this runs an arbitrary command from each package's "scripts" object.
If a package doesn't have the command, it is skipped.
If none of the packages have the command, the command fails.
* * *
test [-- <args>...] this runs each package's "test" script, if one was provided.
pnpm recursive update
rebuild [[<@scope>/<name>]...] this command runs the "npm build" command on each package.
This is useful when you install a new version of node,
and must recompile all your C++ addons with the new binary.
Concurrently runs update in all subdirectories with a \`package.json\` (excluding node_modules).
Options: same as for \`pnpm update\`
* * *
pnpm recursive link
Concurrently runs installation in all subdirectories with a \`package.json\` (excluding node_modules).
If a package is available locally, the local version is linked.
Options: same as for \`pnpm install\`
* * *
pnpm recursive unlink
Removes links to local packages and reinstalls them from the registry.
* * *
pnpm recursive list [[<@scope>/]<pkg> ...]
List packages in each project of the multi-package repo.
Accepts the same arguments and flags as the regular \`pnpm list\` command.
* * *
pnpm recursive outdated [[<@scope>/]<pkg> ...]
Check for outdated packages in every project of the multi-package repo.
* * *
pnpm recursive run <command> [-- <args>...]
alias: pnpm recursive run-script
This runs an arbitrary command from each package's "scripts" object.
If a package doesn't have the command, it is skipped.
If none of the packages have the command, the command fails.
* * *
pnpm recursive test <command> [-- <args>...]
alias: pnpm recursive t, pnpm recursive tst
This runs each package's "test" script, if one was provided.
* * *
pnpm recursive rebuild [[<@scope>/<name>]...]
alias: pnpm recursive rb
This command runs the "npm build" command on each package.
This is useful when you install a new version of node, and must recompile all your C++ addons with the new binary.
exec -- <command> [args...] run a command in each package.
Options:
@@ -343,6 +298,7 @@ function getHelpText (command: string) {
- recursive run
- recursive test
- recursive rebuild
- recursive exec
- server start
- server stop

View File

@@ -0,0 +1,18 @@
import {PackageJson} from '@pnpm/types'
import graphSequencer = require('graph-sequencer')
import createPkgGraph from 'pkgs-graph'
export default (pkgs: Array<{path: string, manifest: PackageJson}>) => {
const pkgGraphResult = createPkgGraph(pkgs)
const graph = new Map(
Object.keys(pkgGraphResult.graph).map((pkgPath) => [pkgPath, pkgGraphResult.graph[pkgPath].dependencies]) as Array<[string, string[]]>,
)
const graphSequencerResult = graphSequencer({
graph,
groups: [Object.keys(pkgGraphResult.graph)],
})
return {
chunks: graphSequencerResult.chunks,
graph: pkgGraphResult.graph,
}
}

View File

@@ -0,0 +1,34 @@
import logger from '@pnpm/logger'
import {PackageJson} from '@pnpm/types'
import execa = require('execa')
import pLimit = require('p-limit')
import dividePackagesToChunks from './dividePackagesToChunks'
export default async (
pkgs: Array<{path: string, manifest: PackageJson}>,
args: string[],
cmd: string,
opts: {
concurrency: number,
unsafePerm: boolean,
rawNpmConfig: object,
},
) => {
const {chunks} = dividePackagesToChunks(pkgs)
const limitRun = pLimit(opts.concurrency)
for (const chunk of chunks) {
await Promise.all(chunk.map((prefix: string) =>
limitRun(async () => {
try {
await execa(args[0], args.slice(1), {cwd: prefix, stdio: 'inherit'})
} catch (err) {
logger.info(err)
err['prefix'] = prefix // tslint:disable-line:no-string-literal
throw err
}
},
)))
}
}

View File

@@ -26,6 +26,7 @@ import getCommandFullName from '../../getCommandFullName'
import requireHooks from '../../requireHooks'
import {PnpmOptions} from '../../types'
import help from '../help'
import exec from './exec'
import list from './list'
import outdated from './outdated'
import run from './run'
@@ -40,6 +41,7 @@ const supportedRecursiveCommands = new Set([
'rebuild',
'run',
'test',
'exec',
])
export default async (
@@ -99,6 +101,8 @@ export default async (
case 'update':
opts = {...opts, update: true, allowNew: false, concurrency} as any // tslint:disable-line:no-any
break
case 'exec':
return exec(pkgs, input, cmd, {...opts, concurrency} as any) // tslint:disable-line:no-any
}
const store = await createStoreController(opts)

View File

@@ -2,9 +2,8 @@ import runLifecycleHooks from '@pnpm/lifecycle'
import logger from '@pnpm/logger'
import {PackageJson} from '@pnpm/types'
import {realNodeModulesDir} from '@pnpm/utils'
import graphSequencer = require('graph-sequencer')
import pLimit = require('p-limit')
import createPkgGraph from 'pkgs-graph'
import dividePackagesToChunks from './dividePackagesToChunks'
export default async (
pkgs: Array<{path: string, manifest: PackageJson}>,
@@ -17,15 +16,7 @@ export default async (
},
) => {
const scriptName = args[0]
const pkgGraphResult = createPkgGraph(pkgs)
const graph = new Map(
Object.keys(pkgGraphResult.graph).map((pkgPath) => [pkgPath, pkgGraphResult.graph[pkgPath].dependencies]) as Array<[string, string[]]>,
)
const graphSequencerResult = graphSequencer({
graph,
groups: [Object.keys(pkgGraphResult.graph)],
})
const chunks = graphSequencerResult.chunks
const {chunks, graph} = dividePackagesToChunks(pkgs)
let hasCommand = 0
const limitRun = pLimit(opts.concurrency)
@@ -33,7 +24,7 @@ export default async (
for (const chunk of chunks) {
await Promise.all(chunk.map((prefix: string) =>
limitRun(async () => {
const pkg = pkgGraphResult.graph[prefix] as {manifest: PackageJson, path: string}
const pkg = graph[prefix] as {manifest: PackageJson, path: string}
if (!pkg.manifest.scripts || !pkg.manifest.scripts[scriptName]) {
return
}

View File

@@ -887,3 +887,56 @@ test('recursive --scope ignore excluded packages', async (t: tape.Test) => {
projects['project-2'].hasNot('is-negative')
projects['project-3'].hasNot('minimatch')
})
test('pnpm recursive exec', async (t: tape.Test) => {
const projects = prepare(t, [
{
name: 'project-1',
version: '1.0.0',
dependencies: {
'json-append': '1',
},
scripts: {
build: `node -e "process.stdout.write('project-1')" | json-append ../output.json`,
},
},
{
name: 'project-2',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1'
},
scripts: {
prebuild: `node -e "process.stdout.write('project-2-prebuild')" | json-append ../output.json`,
build: `node -e "process.stdout.write('project-2')" | json-append ../output.json`,
postbuild: `node -e "process.stdout.write('project-2-postbuild')" | json-append ../output.json`,
},
},
{
name: 'project-3',
version: '1.0.0',
dependencies: {
'json-append': '1',
'project-1': '1'
},
scripts: {
build: `node -e "process.stdout.write('project-3')" | json-append ../output.json`,
},
},
])
await execPnpm('recursive', 'link')
await execPnpm('recursive', 'exec', 'npm', 'run', 'build')
const outputs = await import(path.resolve('output.json')) as string[]
const p1 = outputs.indexOf('project-1')
const p2 = outputs.indexOf('project-2')
const p2pre = outputs.indexOf('project-2-prebuild')
const p2post = outputs.indexOf('project-2-postbuild')
const p3 = outputs.indexOf('project-3')
t.ok(p1 < p2 && p1 < p3)
t.ok(p1 < p2pre && p1 < p2post)
t.ok(p2 < p2post && p2 > p2pre)
})