From b8658a7d6cc405741ccefefb4d5f0697e47ce4eb Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sat, 23 Jun 2018 22:22:28 +0300 Subject: [PATCH] feat: add "multi exec -- " command close #1227 --- packages/pnpm/package.json | 2 +- packages/pnpm/src/cmd/help.ts | 86 +++++-------------- .../cmd/recursive/dividePackagesToChunks.ts | 18 ++++ packages/pnpm/src/cmd/recursive/exec.ts | 34 ++++++++ packages/pnpm/src/cmd/recursive/index.ts | 4 + packages/pnpm/src/cmd/recursive/run.ts | 15 +--- packages/pnpm/test/recursive.ts | 53 ++++++++++++ 7 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 packages/pnpm/src/cmd/recursive/dividePackagesToChunks.ts create mode 100644 packages/pnpm/src/cmd/recursive/exec.ts diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index 0056aa2f88..ef17418a11 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -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", diff --git a/packages/pnpm/src/cmd/help.ts b/packages/pnpm/src/cmd/help.ts index 5a911de9dd..58f27a43a7 100644 --- a/packages/pnpm/src/cmd/help.ts +++ b/packages/pnpm/src/cmd/help.ts @@ -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>/] ...] list dependencies in each package. + outdated [[<@scope>/] ...] check for outdated dependencies in every package. - Options: same as for \`pnpm install\` + run [-- ...] 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 [-- ...] this runs each package's "test" script, if one was provided. - pnpm recursive update + rebuild [[<@scope>/]...] 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>/] ...] - - 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>/] ...] - - Check for outdated packages in every project of the multi-package repo. - - * * * - - pnpm recursive run [-- ...] - - 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 [-- ...] - - alias: pnpm recursive t, pnpm recursive tst - - This runs each package's "test" script, if one was provided. - - * * * - - pnpm recursive rebuild [[<@scope>/]...] - - 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 -- [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 diff --git a/packages/pnpm/src/cmd/recursive/dividePackagesToChunks.ts b/packages/pnpm/src/cmd/recursive/dividePackagesToChunks.ts new file mode 100644 index 0000000000..ad5f40c9f7 --- /dev/null +++ b/packages/pnpm/src/cmd/recursive/dividePackagesToChunks.ts @@ -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, + } +} diff --git a/packages/pnpm/src/cmd/recursive/exec.ts b/packages/pnpm/src/cmd/recursive/exec.ts new file mode 100644 index 0000000000..3a8375f269 --- /dev/null +++ b/packages/pnpm/src/cmd/recursive/exec.ts @@ -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 + } + }, + ))) + } +} diff --git a/packages/pnpm/src/cmd/recursive/index.ts b/packages/pnpm/src/cmd/recursive/index.ts index a9cd138a81..11a06ae669 100644 --- a/packages/pnpm/src/cmd/recursive/index.ts +++ b/packages/pnpm/src/cmd/recursive/index.ts @@ -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) diff --git a/packages/pnpm/src/cmd/recursive/run.ts b/packages/pnpm/src/cmd/recursive/run.ts index f20545e1a2..67c1140249 100644 --- a/packages/pnpm/src/cmd/recursive/run.ts +++ b/packages/pnpm/src/cmd/recursive/run.ts @@ -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 } diff --git a/packages/pnpm/test/recursive.ts b/packages/pnpm/test/recursive.ts index 4a79d3fffd..1782c83294 100644 --- a/packages/pnpm/test/recursive.ts +++ b/packages/pnpm/test/recursive.ts @@ -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) +})