From 9c9a3849d9dcd9ab91832e010d7f069f4bd60435 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sun, 29 Dec 2019 18:45:20 +0200 Subject: [PATCH] refactor: destroying the plugin-commands-recursive package PR #2237 --- packages/cli-utils/package.json | 1 + packages/cli-utils/src/index.ts | 1 + .../src/recursiveSummary.ts | 4 +- packages/config/src/Config.ts | 2 +- .../filter-workspace-packages/package.json | 1 + .../filter-workspace-packages/src/index.ts | 16 + packages/find-workspace-packages/package.json | 1 + packages/find-workspace-packages/src/index.ts | 12 +- packages/parse-cli-args/src/index.ts | 53 +- packages/parse-cli-args/test/index.ts | 59 +- .../plugin-commands-installation/package.json | 31 +- .../plugin-commands-installation/src/add.ts | 18 +- .../src/install.ts | 21 +- .../plugin-commands-installation/src/link.ts | 1 + .../src/recursive.ts | 406 +++++++++ .../src/remove.ts | 24 +- .../src/unlink.ts | 24 +- .../src/update.ts | 2 +- .../src/updateWorkspaceDependencies.ts | 0 .../test/index.ts | 4 + .../test/linkRecursive.ts} | 25 +- .../test/miscRecursive.ts} | 80 +- .../test/updateRecursive.ts} | 43 +- .../test/updateWorkspaceDependencies.spec.ts | 2 +- .../test/utils.ts | 46 + packages/plugin-commands-listing/package.json | 14 +- packages/plugin-commands-listing/src/list.ts | 8 +- .../src/recursive.ts} | 13 +- .../plugin-commands-listing/test/index.ts | 1 + .../test/recursive.ts} | 44 +- .../test/utils.ts | 17 - .../plugin-commands-outdated/package.json | 2 + .../plugin-commands-outdated/src/outdated.ts | 44 +- .../src/recursive.ts} | 18 +- .../plugin-commands-outdated/src/utils.ts | 27 + .../plugin-commands-outdated/test/index.ts | 1 + .../test/recursive.ts} | 42 +- .../plugin-commands-outdated/test/utils.ts | 49 ++ .../plugin-commands-publishing/package.json | 11 +- .../plugin-commands-publishing/src/publish.ts | 18 +- .../src/recursivePublish.ts} | 73 +- .../plugin-commands-publishing/test/index.ts | 1 + .../test/publish.ts | 64 +- .../test/recursivePublish.ts} | 11 +- .../plugin-commands-publishing/test/utils.ts | 46 + packages/plugin-commands-rebuild/package.json | 9 +- .../src/implementation/index.ts | 2 + .../plugin-commands-rebuild/src/rebuild.ts | 18 +- .../plugin-commands-rebuild/src/recursive.ts | 187 ++++ .../plugin-commands-rebuild/test/index.ts | 10 +- .../test/recursive.ts} | 46 +- .../plugin-commands-rebuild/test/utils.ts | 46 + packages/plugin-commands-recursive/README.md | 15 - .../plugin-commands-recursive/package.json | 98 --- .../plugin-commands-recursive/src/exec.ts | 62 -- .../plugin-commands-recursive/src/index.ts | 3 - .../src/recursive.ts | 811 ------------------ .../plugin-commands-recursive/test/index.ts | 13 - .../package.json | 11 +- .../src/exec.ts | 86 ++ .../src/index.ts | 3 +- .../src/restart.ts | 13 +- .../plugin-commands-script-runners/src/run.ts | 26 +- .../src/runRecursive.ts} | 38 +- .../src/start.ts | 13 +- .../src/stop.ts | 13 +- .../src/test.ts | 8 +- .../test/exec.ts | 75 +- .../test/index.ts | 3 + .../test/runRecursive.ts} | 147 ++-- .../test/testRecursive.ts} | 63 +- .../test/utils.ts | 44 + packages/pnpm/package.json | 2 +- packages/pnpm/src/cmd/index.ts | 4 +- packages/pnpm/src/cmd/installTest.ts | 2 +- packages/pnpm/src/cmd/recursive.ts | 123 +++ packages/pnpm/src/main.ts | 20 +- packages/pnpm/test/install/misc.ts | 2 +- packages/pnpm/test/recursive/misc.ts | 4 +- packages/sort-packages/README.md | 15 + packages/sort-packages/package.json | 37 + packages/sort-packages/src/index.ts | 68 ++ .../tsconfig.json | 0 .../tslint.json | 0 pnpm-lock.yaml | 252 +++--- 85 files changed, 2123 insertions(+), 1650 deletions(-) rename packages/{plugin-commands-recursive => cli-utils}/src/recursiveSummary.ts (92%) create mode 100755 packages/plugin-commands-installation/src/recursive.ts rename packages/{plugin-commands-recursive => plugin-commands-installation}/src/updateWorkspaceDependencies.ts (100%) rename packages/{plugin-commands-recursive/test/link.ts => plugin-commands-installation/test/linkRecursive.ts} (86%) rename packages/{plugin-commands-recursive/test/misc.ts => plugin-commands-installation/test/miscRecursive.ts} (85%) rename packages/{plugin-commands-recursive/test/update.ts => plugin-commands-installation/test/updateRecursive.ts} (82%) rename packages/{plugin-commands-recursive => plugin-commands-installation}/test/updateWorkspaceDependencies.spec.ts (95%) create mode 100644 packages/plugin-commands-installation/test/utils.ts rename packages/{plugin-commands-recursive/src/list.ts => plugin-commands-listing/src/recursive.ts} (72%) rename packages/{plugin-commands-recursive/test/list.ts => plugin-commands-listing/test/recursive.ts} (84%) rename packages/{plugin-commands-recursive => plugin-commands-listing}/test/utils.ts (67%) rename packages/{plugin-commands-recursive/src/outdated.ts => plugin-commands-outdated/src/recursive.ts} (95%) create mode 100644 packages/plugin-commands-outdated/src/utils.ts rename packages/{plugin-commands-recursive/test/outdated.ts => plugin-commands-outdated/test/recursive.ts} (91%) create mode 100644 packages/plugin-commands-outdated/test/utils.ts rename packages/{plugin-commands-recursive/src/publish.ts => plugin-commands-publishing/src/recursivePublish.ts} (70%) rename packages/{plugin-commands-recursive/test/publish.ts => plugin-commands-publishing/test/recursivePublish.ts} (90%) create mode 100644 packages/plugin-commands-publishing/test/utils.ts create mode 100755 packages/plugin-commands-rebuild/src/recursive.ts rename packages/{plugin-commands-recursive/test/rebuild.ts => plugin-commands-rebuild/test/recursive.ts} (82%) create mode 100644 packages/plugin-commands-rebuild/test/utils.ts delete mode 100644 packages/plugin-commands-recursive/README.md delete mode 100644 packages/plugin-commands-recursive/package.json delete mode 100644 packages/plugin-commands-recursive/src/exec.ts delete mode 100644 packages/plugin-commands-recursive/src/index.ts delete mode 100755 packages/plugin-commands-recursive/src/recursive.ts delete mode 100644 packages/plugin-commands-recursive/test/index.ts create mode 100644 packages/plugin-commands-script-runners/src/exec.ts rename packages/{plugin-commands-recursive/src/run.ts => plugin-commands-script-runners/src/runRecursive.ts} (72%) rename packages/{plugin-commands-recursive => plugin-commands-script-runners}/test/exec.ts (77%) rename packages/{plugin-commands-recursive/test/run.ts => plugin-commands-script-runners/test/runRecursive.ts} (79%) rename packages/{plugin-commands-recursive/test/test.ts => plugin-commands-script-runners/test/testRecursive.ts} (76%) create mode 100644 packages/plugin-commands-script-runners/test/utils.ts create mode 100644 packages/pnpm/src/cmd/recursive.ts create mode 100644 packages/sort-packages/README.md create mode 100644 packages/sort-packages/package.json create mode 100644 packages/sort-packages/src/index.ts rename packages/{plugin-commands-recursive => sort-packages}/tsconfig.json (100%) rename packages/{plugin-commands-recursive => sort-packages}/tslint.json (100%) diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json index e81df98a86..05fb68f810 100644 --- a/packages/cli-utils/package.json +++ b/packages/cli-utils/package.json @@ -40,6 +40,7 @@ "dependencies": { "@pnpm/config": "workspace:6.0.0", "@pnpm/default-resolver": "workspace:6.0.3", + "@pnpm/error": "workspace:1.0.0", "@pnpm/package-is-installable": "workspace:4.0.1", "@pnpm/read-importer-manifest": "workspace:2.0.1", "@pnpm/utils": "workspace:0.12.2", diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index 7fca6d8047..f80eec751b 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -8,6 +8,7 @@ export * from './createLatestManifestGetter' export * from './getPinnedVersion' export * from './packageIsInstallable' export * from './readImporterManifest' +export * from './recursiveSummary' export * from './style' export * from './updateToLatestSpecsFromManifest' diff --git a/packages/plugin-commands-recursive/src/recursiveSummary.ts b/packages/cli-utils/src/recursiveSummary.ts similarity index 92% rename from packages/plugin-commands-recursive/src/recursiveSummary.ts rename to packages/cli-utils/src/recursiveSummary.ts index 6d942b76c1..8cd8d7c5cd 100644 --- a/packages/plugin-commands-recursive/src/recursiveSummary.ts +++ b/packages/cli-utils/src/recursiveSummary.ts @@ -6,13 +6,11 @@ interface ActionFailure { error: Error, } -interface RecursiveSummary { +export interface RecursiveSummary { fails: ActionFailure[], passes: number, } -export default RecursiveSummary - class RecursiveFailError extends PnpmError { public readonly fails: ActionFailure[] public readonly passes: number diff --git a/packages/config/src/Config.ts b/packages/config/src/Config.ts index 84cc30ca55..3d205ac552 100644 --- a/packages/config/src/Config.ts +++ b/packages/config/src/Config.ts @@ -5,7 +5,7 @@ export type UniversalOptions = Pick Promise, + writeImporterManifest (manifest: ImporterManifest, force?: boolean | undefined): Promise } export type WsPkgsGraph = Record diff --git a/packages/filter-workspace-packages/package.json b/packages/filter-workspace-packages/package.json index 6431de4f5c..7ad355a797 100644 --- a/packages/filter-workspace-packages/package.json +++ b/packages/filter-workspace-packages/package.json @@ -28,6 +28,7 @@ "homepage": "https://pnpm.js.org", "dependencies": { "@pnpm/error": "workspace:1.0.0", + "@pnpm/find-workspace-packages": "workspace:2.0.8", "@pnpm/matcher": "workspace:1.0.0", "execa": "4.0.0", "find-up": "4.1.0", diff --git a/packages/filter-workspace-packages/src/index.ts b/packages/filter-workspace-packages/src/index.ts index 5e8c66327b..c3020854be 100644 --- a/packages/filter-workspace-packages/src/index.ts +++ b/packages/filter-workspace-packages/src/index.ts @@ -1,3 +1,4 @@ +import findWorkspacePackages, { WsPkg } from '@pnpm/find-workspace-packages' import matcher from '@pnpm/matcher' import isSubdir = require('is-subdir') import createPkgGraph, { Package, PackageNode } from 'pkgs-graph' @@ -15,6 +16,21 @@ interface Graph { [nodeId: string]: string[], } +export async function readWsPkgs ( + workspaceDir: string, + pkgSelectors: PackageSelector[], +) { + const allWsPkgs = await findWorkspacePackages(workspaceDir, {}) + const selectedWsPkgsGraph = await filterPkgsBySelectorObjects( + allWsPkgs, + pkgSelectors, + { + workspaceDir, + }, + ) + return { allWsPkgs, selectedWsPkgsGraph } +} + export async function filterPackages ( pkgs: Array, filter: string[], diff --git a/packages/find-workspace-packages/package.json b/packages/find-workspace-packages/package.json index f088bc1cd9..f061b0b617 100644 --- a/packages/find-workspace-packages/package.json +++ b/packages/find-workspace-packages/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@pnpm/cli-utils": "workspace:0.2.5", + "@pnpm/config": "workspace:6.0.0", "@pnpm/constants": "workspace:3.0.0", "@pnpm/types": "workspace:4.0.0", "find-packages": "workspace:7.0.1", diff --git a/packages/find-workspace-packages/src/index.ts b/packages/find-workspace-packages/src/index.ts index 432627cce0..c7a7795418 100644 --- a/packages/find-workspace-packages/src/index.ts +++ b/packages/find-workspace-packages/src/index.ts @@ -1,15 +1,11 @@ import { packageIsInstallable } from '@pnpm/cli-utils' +import { WsPkg } from '@pnpm/config' import { WORKSPACE_MANIFEST_FILENAME } from '@pnpm/constants' -import { ImporterManifest } from '@pnpm/types' import findPackages from 'find-packages' import path = require('path') import readYamlFile from 'read-yaml-file' -interface WorkspaceDependencyPackage { - dir: string - manifest: ImporterManifest - writeImporterManifest (manifest: ImporterManifest, force?: boolean | undefined): Promise -} +export { WsPkg } export default async ( workspaceRoot: string, @@ -29,7 +25,7 @@ export default async ( packageIsInstallable(pkg.dir, pkg.manifest, opts) } - return pkgs as WorkspaceDependencyPackage[] + return pkgs as WsPkg[] } async function requirePackagesManifest (dir: string): Promise<{packages?: string[]} | null> { @@ -44,7 +40,7 @@ async function requirePackagesManifest (dir: string): Promise<{packages?: string } export function arrayOfWorkspacePackagesToMap ( - pkgs: WorkspaceDependencyPackage[], + pkgs: WsPkg[], ) { return pkgs.reduce((acc, pkg) => { if (!pkg.manifest.name || !pkg.manifest.version) return acc diff --git a/packages/parse-cli-args/src/index.ts b/packages/parse-cli-args/src/index.ts index 99d026eae0..29a004ca40 100644 --- a/packages/parse-cli-args/src/index.ts +++ b/packages/parse-cli-args/src/index.ts @@ -1,6 +1,8 @@ import findWorkspaceDir from '@pnpm/find-workspace-dir' import nopt = require('nopt') +const RECURSIVE_CMDS = new Set(['recursive', 'multi', 'm']) + export default async function parseCliArgs ( opts: { getCommandLongName: (commandName: string) => string, @@ -40,28 +42,16 @@ export default async function parseCliArgs ( } } - const types = (() => { - if (opts.getCommandLongName(noptExploratoryResults.argv.remain[0]) === 'recursive') { - return { - ...opts.globalOptionsTypes, - ...opts.getTypesByCommandName('recursive'), - ...opts.getTypesByCommandName(getCommandName(noptExploratoryResults.argv.remain.slice(1))), - } - } - if (noptExploratoryResults['filter'] || noptExploratoryResults['recursive'] === true) { - return { - ...opts.globalOptionsTypes, - ...opts.getTypesByCommandName('recursive'), - ...opts.getTypesByCommandName(getCommandName(noptExploratoryResults.argv.remain)), - } - } - return { - ...opts.globalOptionsTypes, - ...opts.getTypesByCommandName(getCommandName(noptExploratoryResults.argv.remain)), - } - })() as any // tslint:disable-line:no-any + const types = { + 'recursive': Boolean, + ...opts.globalOptionsTypes, + ...opts.getTypesByCommandName(getCommandName(noptExploratoryResults.argv.remain)), + } as any // tslint:disable-line:no-any function getCommandName (cliArgs: string[]) { + if (RECURSIVE_CMDS.has(cliArgs[0])) { + cliArgs = cliArgs.slice(1) + } if (opts.getCommandLongName(cliArgs[0]) !== 'install' || cliArgs.length === 1) return cliArgs[0] return 'add' } @@ -77,9 +67,8 @@ export default async function parseCliArgs ( } } - let cmd = opts.getCommandLongName(argv.remain[0]) - || 'help' - if (!opts.isKnownCommand(cmd)) { + let cmd = opts.getCommandLongName(argv.remain[0]) ?? 'help' + if (!opts.isKnownCommand(cmd) && !RECURSIVE_CMDS.has(cmd)) { cmd = 'help' } @@ -88,10 +77,14 @@ export default async function parseCliArgs ( // `pnpm install ""` is going to be just `pnpm install` const cliArgs = argv.remain.slice(1).filter(Boolean) - if (cmd !== 'recursive' && (cliConf['filter'] || cliConf['recursive'] === true)) { - subCmd = cmd - cmd = 'recursive' - cliArgs.unshift(subCmd) + if (cliConf['recursive'] !== true && (cliConf['filter'] || RECURSIVE_CMDS.has(cmd))) { + cliConf['recursive'] = true + if (subCmd && RECURSIVE_CMDS.has(cmd)) { + cliArgs.shift() + argv.remain.shift() + cmd = subCmd + subCmd = null + } } else if (subCmd && !opts.isKnownCommand(subCmd)) { subCmd = null } @@ -105,15 +98,13 @@ export default async function parseCliArgs ( typeof workspaceDir === 'string' && cliArgs.length === 0 ) { - subCmd = cmd - cmd = 'recursive' - cliArgs.unshift(subCmd) + cliConf['recursive'] = true } if (cmd === 'install' && cliArgs.length > 0) { cmd = 'add' } else if (subCmd === 'install' && cliArgs.length > 1) { - subCmd = 'add' + cmd = 'add' } const allowedOptions = new Set(Object.keys(types)) diff --git a/packages/parse-cli-args/test/index.ts b/packages/parse-cli-args/test/index.ts index 47350a4135..727ae24332 100644 --- a/packages/parse-cli-args/test/index.ts +++ b/packages/parse-cli-args/test/index.ts @@ -13,59 +13,69 @@ const DEFAULT_OPTS = { } test('a command is recursive if it has a --filter option', async (t) => { - const { cmd, subCmd } = await parseCliArgs({ + const { cliConf, cmd } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: { filter: [String, Array] }, }, ['--filter', 'foo', 'update']) - t.equal(cmd, 'recursive') - t.equal(subCmd, 'update') + t.equal(cmd, 'update') + t.ok(cliConf['recursive']) t.end() }) test('a command is recursive if -r option is used', async (t) => { - const { cmd, subCmd } = await parseCliArgs({ + const { cliConf, cmd } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: { recursive: Boolean }, shortHands: { 'r': ['--recursive'] }, }, ['-r', 'update']) - t.equal(cmd, 'recursive') - t.equal(subCmd, 'update') + t.equal(cmd, 'update') + t.ok(cliConf['recursive']) t.end() }) test('a command is recursive if --recursive option is used', async (t) => { - const { cmd, subCmd } = await parseCliArgs({ + const { cliConf, cmd } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: { recursive: Boolean }, }, ['-r', 'update']) - t.equal(cmd, 'recursive') - t.equal(subCmd, 'update') + t.equal(cmd, 'update') + t.ok(cliConf['recursive']) t.end() }) test('the install command is recursive when executed in a subdir of a workspace', async (t) => { - const { cmd, subCmd, workspaceDir } = await parseCliArgs({ + const { cliConf, cmd, workspaceDir } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: { dir: String }, }, ['--dir', __dirname, 'install']) - t.equal(cmd, 'recursive') - t.equal(subCmd, 'install') + t.equal(cmd, 'install') + t.ok(cliConf['recursive']) t.equal(workspaceDir, path.join(__dirname, '../../..')) t.end() }) test('the install command is recursive when executed in the root of a workspace', async (t) => { const expectedWorkspaceDir = path.join(__dirname, '../../..') - const { cmd, subCmd, workspaceDir } = await parseCliArgs({ + const { cliConf, cmd, workspaceDir } = await parseCliArgs({ ...DEFAULT_OPTS, globalOptionsTypes: { dir: String }, }, ['--dir', expectedWorkspaceDir, 'install']) - t.equal(cmd, 'recursive') - t.equal(subCmd, 'install') + t.equal(cmd, 'install') + t.ok(cliConf['recursive']) t.equal(workspaceDir, expectedWorkspaceDir) t.end() }) +test('recursive is returned as the command name if no subcommand passed', async (t) => { + const { cliConf, cmd } = await parseCliArgs({ + ...DEFAULT_OPTS, + globalOptionsTypes: { filter: [String, Array] }, + }, ['recursive']) + t.equal(cmd, 'recursive') + t.ok(cliConf['recursive']) + t.end() +}) + test('when runnning a global command inside a workspace, the workspace should be ignored', async (t) => { const { workspaceDir } = await parseCliArgs({ ...DEFAULT_OPTS, @@ -112,6 +122,7 @@ test('detect unknown options', async (t) => { getTypesByCommandName: (commandName: string) => { if (commandName === 'install') { return { + recursive: Boolean, registry: String, } } @@ -124,24 +135,6 @@ test('detect unknown options', async (t) => { t.end() }) -test('merge option types of recursive and subcommand', async (t) => { - const { unknownOptions } = await parseCliArgs({ - ...DEFAULT_OPTS, - getTypesByCommandName: (commandName: string) => { - switch (commandName) { - case 'install': return { recursive: Boolean, registry: String } - case 'recursive': return { sort: Boolean } - default: return {} - } - }, - globalOptionsTypes: { filter: [String, Array] }, - isKnownCommand: (commandName) => commandName === 'install', - shortHands: { 'r': ['--recursive'] }, - }, ['-r', 'install', '--registry=https://example.com', '--sort']) - t.deepEqual(unknownOptions, []) - t.end() -}) - test('do not incorrectly change "install" command to "add"', async (t) => { const { cmd } = await parseCliArgs({ ...DEFAULT_OPTS, diff --git a/packages/plugin-commands-installation/package.json b/packages/plugin-commands-installation/package.json index 4e614fcfdb..9f0880cdfc 100644 --- a/packages/plugin-commands-installation/package.json +++ b/packages/plugin-commands-installation/package.json @@ -13,7 +13,11 @@ "scripts": { "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", "tsc": "rimraf lib && tsc", - "test": "pnpm run tsc && pnpm run lint && ts-node test", + "registry-mock": "registry-mock", + "test:tap": "ts-node test --type-check", + "pretest:e2e": "registry-mock prepare", + "test:e2e": "run-p -r registry-mock test:tap", + "test": "pnpm run tsc && pnpm run lint && cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", "prepublishOnly": "pnpm run tsc" }, "repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-installation", @@ -27,33 +31,54 @@ }, "homepage": "https://pnpm.js.org", "devDependencies": { + "@pnpm/lockfile-types": "workspace:1.1.0", + "@pnpm/logger": "^3.1.0", "@pnpm/plugin-commands-installation": "link:", "@pnpm/prepare": "workspace:0.0.0", "@pnpm/registry-mock": "1.11.1", "@pnpm/test-fixtures": "workspace:0.0.0", "@types/common-tags": "^1.8.0", + "@types/mz": "^2.7.0", "@types/ramda": "^0.26.38", - "rimraf": "3.0.0" + "make-dir": "3.0.0", + "read-yaml-file": "1.1.0", + "rimraf": "3.0.0", + "write-json-file": "4.2.1", + "write-yaml-file": "3.0.1" }, "dependencies": { "@pnpm/cli-utils": "workspace:0.2.5", "@pnpm/common-cli-options-help": "workspace:0.1.2", "@pnpm/config": "workspace:6.0.0", "@pnpm/constants": "workspace:3.0.0", + "@pnpm/core-loggers": "workspace:4.0.0", "@pnpm/error": "workspace:1.0.0", "@pnpm/filter-workspace-packages": "workspace:1.0.1", "@pnpm/find-workspace-dir": "workspace:1.0.0", "@pnpm/find-workspace-packages": "workspace:2.0.8", "@pnpm/package-store": "workspace:7.0.2", "@pnpm/plugin-commands-rebuild": "workspace:0.0.0", - "@pnpm/plugin-commands-recursive": "workspace:0.1.10", "@pnpm/pnpmfile": "workspace:0.1.0", + "@pnpm/resolver-base": "workspace:6.0.0", + "@pnpm/sort-packages": "workspace:0.0.0", "@pnpm/store-connection-manager": "workspace:0.2.5", + "@pnpm/types": "workspace:4.0.0", + "@pnpm/utils": "workspace:0.12.2", + "camelcase-keys": "6.1.1", "common-tags": "1.8.0", + "is-subdir": "1.1.1", + "mem": "6.0.1", + "mz": "2.7.0", + "p-filter": "2.1.0", "p-limit": "2.2.1", "path-absolute": "1.0.1", + "path-exists": "4.0.0", "ramda": "0.26.1", + "read-ini-file": "2.0.0", "render-help": "0.0.0", "supi": "workspace:0.37.6" + }, + "peerDependencies": { + "@pnpm/logger": "^3.1.0" } } diff --git a/packages/plugin-commands-installation/src/add.ts b/packages/plugin-commands-installation/src/add.ts index 4edd31fc11..7c72adbaac 100644 --- a/packages/plugin-commands-installation/src/add.ts +++ b/packages/plugin-commands-installation/src/add.ts @@ -151,6 +151,7 @@ export async function handler ( input: string[], opts: InstallCommandOptions & { allowNew?: boolean, + ignoreWorkspaceRootCheck?: boolean, save?: boolean, update?: boolean, useBetaCli?: boolean, @@ -160,5 +161,20 @@ export async function handler ( if (opts.cliOptions['save'] === false) { throw new PnpmError('OPTION_NOT_SUPPORTED', 'The "add" command currently does not support the no-save option') } - return install(input, opts, invocation) + if (!input || !input.length) { + throw new PnpmError('MISSING_PACKAGE_NAME', '`pnpm add` requires the package name') + } + if ( + !opts.recursive && + opts.workspaceDir === opts.dir && + !opts.ignoreWorkspaceRootCheck + ) { + 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).', + ) + } + + return install(input, opts, invocation ?? 'add') } diff --git a/packages/plugin-commands-installation/src/install.ts b/packages/plugin-commands-installation/src/install.ts index 0eda639fd5..e266b77f86 100644 --- a/packages/plugin-commands-installation/src/install.ts +++ b/packages/plugin-commands-installation/src/install.ts @@ -14,8 +14,6 @@ import PnpmError from '@pnpm/error' import { filterPkgsBySelectorObjects } from '@pnpm/filter-workspace-packages' import findWorkspacePackages, { arrayOfWorkspacePackagesToMap } from '@pnpm/find-workspace-packages' import { rebuild } from '@pnpm/plugin-commands-rebuild/lib/implementation' -import { recursive } from '@pnpm/plugin-commands-recursive/lib/recursive' -import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from '@pnpm/plugin-commands-recursive/lib/updateWorkspaceDependencies' import { requireHooks } from '@pnpm/pnpmfile' import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' import { oneLine } from 'common-tags' @@ -25,6 +23,8 @@ import { install, mutateModules, } from 'supi' +import recursive from './recursive' +import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies' const OVERWRITE_UPDATE_OPTIONS = { allowNew: true, @@ -256,6 +256,7 @@ export function help () { } export type InstallCommandOptions = Pick & CreateStoreControllerOptions & { @@ -288,13 +291,14 @@ export type InstallCommandOptions = Pick & { + latest?: boolean, + pending?: boolean, + workspace?: boolean, +} & Partial> + +export default async function recursive ( + allWsPkgs: WsPkg[], + input: string[], + opts: RecursiveOptions & { + allowNew?: boolean, + ignoredPackages?: Set, + update?: boolean, + useBetaCli?: boolean, + selectedWsPkgsGraph: WsPkgsGraph, + } & Required>, + cmdFullName: string, +): Promise { + if (allWsPkgs.length === 0) { + // It might make sense to throw an exception in this case + return false + } + + const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) + + if (pkgs.length === 0) { + return false + } + const manifestsByPath: { [dir: string]: Omit } = {} + for (const { dir, manifest, writeImporterManifest } of pkgs) { + manifestsByPath[dir] = { manifest, writeImporterManifest } + } + + scopeLogger.debug({ + selected: pkgs.length, + total: allWsPkgs.length, + workspacePrefix: opts.workspaceDir, + }) + + const throwOnFail = throwOnCommandFail.bind(null, `pnpm recursive ${cmdFullName}`) + + const chunks = opts.sort !== false + ? sortPackages(opts.selectedWsPkgsGraph) + : [Object.keys(opts.selectedWsPkgsGraph).sort()] + + const store = await createOrConnectStoreController(opts) + + // It is enough to save the store.json file once, + // once all installations are done. + // That's why saveState that is passed to the install engine + // does nothing. + const saveState = store.ctrl.saveState + const storeController = { + ...store.ctrl, + saveState: async () => undefined, + } + + const workspacePackages = cmdFullName !== 'unlink' + ? arrayOfWorkspacePackagesToMap(allWsPkgs) + : {} + const installOpts = Object.assign(opts, { + ownLifecycleHooksStdio: 'pipe', + peer: opts.savePeer, + pruneLockfileImporters: (!opts.ignoredPackages || opts.ignoredPackages.size === 0) + && pkgs.length === allWsPkgs.length, + storeController, + storeDir: store.dir, + workspacePackages, + + forceHoistPattern: typeof opts.rawLocalConfig['hoist-pattern'] !== 'undefined' || typeof opts.rawLocalConfig['hoist'] !== 'undefined', + forceIndependentLeaves: typeof opts.rawLocalConfig['independent-leaves'] !== 'undefined', + forceShamefullyHoist: typeof opts.rawLocalConfig['shamefully-hoist'] !== 'undefined', + }) as InstallOptions + + const result = { + fails: [], + passes: 0, + } as RecursiveSummary + + const memReadLocalConfig = mem(readLocalConfig) + + async function getImporters () { + const importers = [] as Array<{ buildIndex: number, manifest: ImporterManifest, rootDir: string }> + await Promise.all(chunks.map((prefixes: string[], buildIndex) => { + if (opts.ignoredPackages) { + prefixes = prefixes.filter((prefix) => !opts.ignoredPackages!.has(prefix)) + } + return Promise.all( + prefixes.map(async (prefix) => { + importers.push({ + buildIndex, + manifest: manifestsByPath[prefix].manifest, + rootDir: prefix, + }) + }) + ) + })) + return importers + } + + const updateToLatest = opts.update && opts.latest + const include = opts.include + if (updateToLatest) { + delete opts.include + } + + // For a workspace with shared lockfile + if (opts.lockfileDir && ['add', 'install', 'remove', 'update'].includes(cmdFullName)) { + if (opts.hoistPattern) { + logger.info({ message: 'Only the root workspace package is going to have hoisted dependencies in node_modules', prefix: opts.lockfileDir }) + } + let importers = await getImporters() + const isFromWorkspace = isSubdir.bind(null, opts.lockfileDir) + importers = await pFilter(importers, async ({ rootDir }: { rootDir: string }) => isFromWorkspace(await fs.realpath(rootDir))) + if (importers.length === 0) return true + const hooks = opts.ignorePnpmfile ? {} : requireHooks(opts.lockfileDir, opts) + const mutation = cmdFullName === 'remove' ? 'uninstallSome' : (input.length === 0 && !updateToLatest ? 'install' : 'installSome') + const writeImporterManifests = [] as Array<(manifest: ImporterManifest) => Promise> + const mutatedImporters = [] as MutatedImporter[] + await Promise.all(importers.map(async ({ buildIndex, rootDir }) => { + const localConfig = await memReadLocalConfig(rootDir) + const { manifest, writeImporterManifest } = manifestsByPath[rootDir] + let currentInput = [...input] + if (updateToLatest) { + if (!currentInput || !currentInput.length) { + currentInput = updateToLatestSpecsFromManifest(manifest, include) + } else { + currentInput = createLatestSpecs(currentInput, manifest) + if (!currentInput.length) { + installOpts.pruneLockfileImporters = false + return + } + } + } + if (opts.workspace) { + if (!currentInput || !currentInput.length) { + currentInput = updateToWorkspacePackagesFromManifest(manifest, opts.include, workspacePackages!) + } else { + currentInput = createWorkspaceSpecs(currentInput, workspacePackages!) + } + } + writeImporterManifests.push(writeImporterManifest) + switch (mutation) { + case 'uninstallSome': + mutatedImporters.push({ + dependencyNames: currentInput, + manifest, + mutation, + rootDir, + targetDependenciesField: getSaveType(opts), + } as MutatedImporter) + return + case 'installSome': + mutatedImporters.push({ + allowNew: cmdFullName === 'install' || cmdFullName === 'add', + dependencySelectors: currentInput, + manifest, + mutation, + peer: opts.savePeer, + pinnedVersion: getPinnedVersion({ + saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact, + savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix, + }), + rootDir, + targetDependenciesField: getSaveType(opts), + } as MutatedImporter) + return + case 'install': + mutatedImporters.push({ + buildIndex, + manifest, + mutation, + rootDir, + } as MutatedImporter) + return + } + })) + const mutatedPkgs = await mutateModules(mutatedImporters, { + ...installOpts, + hooks, + storeController: store.ctrl, + }) + if (opts.save !== false) { + await Promise.all( + mutatedPkgs + .map(({ manifest }, index) => writeImporterManifests[index](manifest)) + ) + } + return true + } + + let pkgPaths = chunks.length === 0 + ? chunks[0] + : Object.keys(opts.selectedWsPkgsGraph).sort() + + const limitInstallation = pLimit(opts.workspaceConcurrency ?? 4) + await Promise.all(pkgPaths.map((rootDir: string) => + limitInstallation(async () => { + const hooks = opts.ignorePnpmfile ? {} : requireHooks(rootDir, opts) + try { + if (opts.ignoredPackages && opts.ignoredPackages.has(rootDir)) { + return + } + + const { manifest, writeImporterManifest } = manifestsByPath[rootDir] + let currentInput = [...input] + if (updateToLatest) { + if (!currentInput || !currentInput.length) { + currentInput = updateToLatestSpecsFromManifest(manifest, include) + } else { + currentInput = createLatestSpecs(currentInput, manifest) + if (!currentInput.length) return + } + } + + let action!: any // tslint:disable-line:no-any + switch (cmdFullName) { + case 'unlink': + action = (currentInput.length === 0 ? unlink : unlinkPkgs.bind(null, currentInput)) + break + case 'remove': + action = (manifest: PackageManifest, opts: any) => mutateModules([ // tslint:disable-line:no-any + { + dependencyNames: currentInput, + manifest, + mutation: 'uninstallSome', + rootDir, + }, + ], opts) + break + default: + action = currentInput.length === 0 + ? install + : (manifest: PackageManifest, opts: any) => addDependenciesToPackage(manifest, currentInput, opts) // tslint:disable-line:no-any + break + } + + const localConfig = await memReadLocalConfig(rootDir) + const newManifest = await action( + manifest, + { + ...installOpts, + ...localConfig, + bin: path.join(rootDir, 'node_modules', '.bin'), + dir: rootDir, + hooks, + ignoreScripts: true, + pinnedVersion: getPinnedVersion({ + saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact, + savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix, + }), + rawConfig: { + ...installOpts.rawConfig, + ...localConfig, + }, + storeController, + }, + ) + if (opts.save !== false) { + await writeImporterManifest(newManifest) + } + result.passes++ + } catch (err) { + logger.info(err) + + if (!opts.bail) { + result.fails.push({ + error: err, + message: err.message, + prefix: rootDir, + }) + return + } + + err['prefix'] = rootDir // tslint:disable-line:no-string-literal + throw err + } + }), + )) + + await saveState() + // The store should be unlocked because otherwise rebuild will not be able + // to access it + await storeController.close() + + if ( + !opts.lockfileOnly && !opts.ignoreScripts && ( + cmdFullName === 'add' || + cmdFullName === 'install' || + cmdFullName === 'update' || + cmdFullName === 'unlink' + ) + ) { + await rebuild.handler([], { + ...opts, + pending: opts.pending === true, + }) + } + + throwOnFail(result) + + return true +} + +async function unlink (manifest: ImporterManifest, opts: any) { // tslint:disable-line:no-any + return mutateModules( + [ + { + manifest, + mutation: 'unlink', + rootDir: opts.dir, + }, + ], + opts, + ) +} + +async function unlinkPkgs (dependencyNames: string[], manifest: ImporterManifest, opts: any) { // tslint:disable-line:no-any + return mutateModules( + [ + { + dependencyNames, + manifest, + mutation: 'unlinkSome', + rootDir: opts.dir, + }, + ], + opts, + ) +} + +async function readLocalConfig (prefix: string) { + try { + const ini = await readIniFile(path.join(prefix, '.npmrc')) as { [key: string]: string } + const config = camelcaseKeys(ini) as ({ [key: string]: string } & { hoist?: boolean }) + if (config.shamefullyFlatten) { + config.hoistPattern = '*' + // TODO: print a warning + } + if (config.hoist === false) { + config.hoistPattern = '' + } + return config + } catch (err) { + if (err.code !== 'ENOENT') throw err + return {} + } +} diff --git a/packages/plugin-commands-installation/src/remove.ts b/packages/plugin-commands-installation/src/remove.ts index f14870f9fe..f8b4e6b1f8 100644 --- a/packages/plugin-commands-installation/src/remove.ts +++ b/packages/plugin-commands-installation/src/remove.ts @@ -10,6 +10,7 @@ import renderHelp = require('render-help') import { mutateModules, } from 'supi' +import recursive from './recursive' export const rcOptionsTypes = cliOptionsTypes @@ -68,8 +69,29 @@ export const commandNames = ['remove', 'uninstall', 'r', 'rm', 'un'] export async function handler ( input: string[], - opts: CreateStoreControllerOptions & Pick, + opts: CreateStoreControllerOptions & Pick & { + recursive?: boolean, + }, ) { + if (opts.recursive && opts.allWsPkgs && opts.selectedWsPkgsGraph && opts.workspaceDir) { + await recursive(opts.allWsPkgs, input, { ...opts, selectedWsPkgsGraph: opts.selectedWsPkgsGraph!, workspaceDir: opts.workspaceDir! }, 'remove') + return + } const store = await createOrConnectStoreController(opts) const removeOpts = Object.assign(opts, { storeController: store.ctrl, diff --git a/packages/plugin-commands-installation/src/unlink.ts b/packages/plugin-commands-installation/src/unlink.ts index dedeec2e28..fbf0293c2f 100644 --- a/packages/plugin-commands-installation/src/unlink.ts +++ b/packages/plugin-commands-installation/src/unlink.ts @@ -6,6 +6,7 @@ import { oneLine } from 'common-tags' import renderHelp = require('render-help') import { mutateModules } from 'supi' import { cliOptionsTypes, rcOptionsTypes } from './install' +import recursive from './recursive' export { cliOptionsTypes, rcOptionsTypes } @@ -40,7 +41,28 @@ export function help () { }) } -export async function handler (input: string[], opts: CreateStoreControllerOptions & Pick) { +export async function handler ( + input: string[], + opts: CreateStoreControllerOptions & + Pick & { + recursive?: boolean, + }, +) { + if (opts.recursive && opts.allWsPkgs && opts.selectedWsPkgsGraph && opts.workspaceDir) { + await recursive(opts.allWsPkgs, input, { ...opts, selectedWsPkgsGraph: opts.selectedWsPkgsGraph!, workspaceDir: opts.workspaceDir! }, 'unlink') + return + } const store = await createOrConnectStoreController(opts) const unlinkOpts = Object.assign(opts, { storeController: store.ctrl, diff --git a/packages/plugin-commands-installation/src/update.ts b/packages/plugin-commands-installation/src/update.ts index c5bc00305a..8d48e137d7 100644 --- a/packages/plugin-commands-installation/src/update.ts +++ b/packages/plugin-commands-installation/src/update.ts @@ -106,5 +106,5 @@ export async function handler ( input: string[], opts: InstallCommandOptions, ) { - return install(input, { ...opts, update: true, allowNew: false }) + return install(input, { ...opts, update: true, allowNew: false }, 'update') } diff --git a/packages/plugin-commands-recursive/src/updateWorkspaceDependencies.ts b/packages/plugin-commands-installation/src/updateWorkspaceDependencies.ts similarity index 100% rename from packages/plugin-commands-recursive/src/updateWorkspaceDependencies.ts rename to packages/plugin-commands-installation/src/updateWorkspaceDependencies.ts diff --git a/packages/plugin-commands-installation/test/index.ts b/packages/plugin-commands-installation/test/index.ts index 44d8616bae..c9ca019d34 100644 --- a/packages/plugin-commands-installation/test/index.ts +++ b/packages/plugin-commands-installation/test/index.ts @@ -1,3 +1,7 @@ /// import './add' +import './linkRecursive' +import './miscRecursive' import './prune' +import './updateRecursive' +import './updateWorkspaceDependencies.spec' diff --git a/packages/plugin-commands-recursive/test/link.ts b/packages/plugin-commands-installation/test/linkRecursive.ts similarity index 86% rename from packages/plugin-commands-recursive/test/link.ts rename to packages/plugin-commands-installation/test/linkRecursive.ts index 01d70f2384..0daa37ba13 100644 --- a/packages/plugin-commands-recursive/test/link.ts +++ b/packages/plugin-commands-installation/test/linkRecursive.ts @@ -1,10 +1,11 @@ import { WANTED_LOCKFILE } from '@pnpm/constants' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { install, link, unlink } from '@pnpm/plugin-commands-installation' import { preparePackages } from '@pnpm/prepare' import path = require('path') import exists = require('path-exists') import test = require('tape') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' test('recursive linking/unlinking', async (t) => { const projects = preparePackages(t, [ @@ -27,12 +28,14 @@ test('recursive linking/unlinking', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') t.ok(projects['is-positive'].requireModule('is-negative')) t.notOk(projects['project-1'].requireModule('is-positive/package.json').author, 'local package is linked') @@ -42,11 +45,13 @@ test('recursive linking/unlinking', async (t) => { t.equal(project1Lockfile.devDependencies['is-positive'], 'link:../is-positive') } - await recursive.handler(['unlink'], { + await unlink.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) process.chdir('project-1') @@ -86,12 +91,14 @@ test('recursive unlink specific package', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') t.ok(projects['is-positive'].requireModule('is-negative')) t.notOk(projects['project-1'].requireModule('is-positive/package.json').author, 'local package is linked') @@ -101,11 +108,13 @@ test('recursive unlink specific package', async (t) => { t.equal(project1Lockfile.devDependencies['is-positive'], 'link:../is-positive') } - await recursive.handler(['unlink', 'is-positive'], { + await unlink.handler(['is-positive'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) process.chdir('project-1') diff --git a/packages/plugin-commands-recursive/test/misc.ts b/packages/plugin-commands-installation/test/miscRecursive.ts similarity index 85% rename from packages/plugin-commands-recursive/test/misc.ts rename to packages/plugin-commands-installation/test/miscRecursive.ts index 92883308e1..d0ea47c4fe 100644 --- a/packages/plugin-commands-recursive/test/misc.ts +++ b/packages/plugin-commands-installation/test/miscRecursive.ts @@ -1,5 +1,6 @@ import PnpmError from '@pnpm/error' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { add, install, remove, update } from '@pnpm/plugin-commands-installation' import { preparePackages } from '@pnpm/prepare' import makeDir = require('make-dir') import fs = require('mz/fs') @@ -7,7 +8,7 @@ import path = require('path') import test = require('tape') import writeJsonFile = require('write-json-file') import writeYamlFile = require('write-yaml-file') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' test('recursive install/uninstall', async (t) => { const projects = preparePackages(t, [ @@ -30,32 +31,38 @@ test('recursive install/uninstall', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') t.ok(projects['project-1'].requireModule('is-positive')) t.ok(projects['project-2'].requireModule('is-negative')) await projects['project-2'].has('is-negative') - await recursive.handler(['add', 'noop'], { + await add.handler(['noop'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'add') t.ok(projects['project-1'].requireModule('noop')) t.ok(projects['project-2'].requireModule('noop')) - await recursive.handler(['remove', 'is-negative'], { + await remove.handler(['is-negative'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) await projects['project-2'].hasNot('is-negative') @@ -85,11 +92,13 @@ test('recursive install with package that has link', async (t) => { }, ]) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') t.ok(projects['project-1'].requireModule('is-positive')) t.ok(projects['project-1'].requireModule('project-2/package.json')) @@ -119,11 +128,13 @@ test('running `pnpm recursive` on a subset of packages', async t => { await writeYamlFile('pnpm-workspace.yaml', { packages: ['project-1'] }) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') await projects['project-1'].has('is-positive') await projects['project-2'].hasNot('is-negative') @@ -170,11 +181,13 @@ test('running `pnpm recursive` only for packages in subdirectories of cwd', asyn await makeDir('node_modules') process.chdir('packages') - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') await projects['project-1'].has('is-positive') await projects['project-2'].has('is-negative') @@ -204,15 +217,17 @@ test('recursive installation fails when installation in one of the packages fail let err!: PnpmError try { - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') } catch (_err) { err = _err } - t.equal(err.code, 'ERR_PNPM_RECURSIVE_FAIL') + t.equal(err.code, 'ERR_PNPM_REGISTRY_META_RESPONSE_404') t.end() }) @@ -233,12 +248,14 @@ test('second run of `recursive install` after package.json has been edited manua ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') await writeJsonFile('is-negative/package.json', { name: 'is-negative', @@ -249,12 +266,14 @@ test('second run of `recursive install` after package.json has been edited manua }, }) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') t.ok(projects['is-negative'].requireModule('is-positive/package.json')) t.end() @@ -296,13 +315,15 @@ test('recursive --filter ignore excluded packages', async (t) => { ], }) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), [ { includeDependencies: true, namePattern: 'project-1' }, ]), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') projects['project-1'].hasNot('is-positive') projects['project-2'].hasNot('is-negative') @@ -339,14 +360,16 @@ test('recursive filter multiple times', async (t) => { }, ]) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), [ { namePattern: 'project-1' }, { namePattern: 'project-2' }, ]), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') projects['project-1'].has('is-positive') projects['project-2'].has('is-negative') @@ -376,12 +399,14 @@ test('recursive install --no-bail', async (t) => { let err!: PnpmError try { - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), bail: false, dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') } catch (_err) { err = _err } @@ -408,12 +433,13 @@ test('installing with "workspace=true" should work even if link-workspace-packag }, ]) - await recursive.handler(['update', 'project-2'], { + await update.handler(['project-2'], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), linkWorkspacePackages: false, lockfileDir: process.cwd(), + recursive: true, saveWorkspaceProtocol: false, sharedWorkspaceLockfile: true, workspace: true, diff --git a/packages/plugin-commands-recursive/test/update.ts b/packages/plugin-commands-installation/test/updateRecursive.ts similarity index 82% rename from packages/plugin-commands-recursive/test/update.ts rename to packages/plugin-commands-installation/test/updateRecursive.ts index 83201dd5fe..6af31c290c 100644 --- a/packages/plugin-commands-recursive/test/update.ts +++ b/packages/plugin-commands-installation/test/updateRecursive.ts @@ -1,10 +1,11 @@ +import { readWsPkgs } from '@pnpm/filter-workspace-packages' import { Lockfile } from '@pnpm/lockfile-types' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { install, update } from '@pnpm/plugin-commands-installation' import { preparePackages } from '@pnpm/prepare' import { addDistTag } from '@pnpm/registry-mock' import readYamlFile from 'read-yaml-file' import test = require('tape') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' test('recursive update', async (t) => { const projects = preparePackages(t, [ @@ -27,18 +28,22 @@ test('recursive update', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') - await recursive.handler(['update', 'is-positive@2.0.0'], { + await update.handler(['is-positive@2.0.0'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) t.equal(projects['project-1'].requireModule('is-positive/package.json').version, '2.0.0') @@ -75,24 +80,28 @@ test('recursive update --latest foo should only update workspace packages that h const lockfileDir = process.cwd() const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), lockfileDir, + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') await addDistTag({ package: 'foo', version: '100.1.0', distTag: 'latest' }) await addDistTag({ package: 'bar', version: '100.1.0', distTag: 'latest' }) - await recursive.handler(['update', '@zkochan/async-regex-replace', 'foo', 'qar@100.1.0'], { + await update.handler(['@zkochan/async-regex-replace', 'foo', 'qar@100.1.0'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), latest: true, lockfileDir, + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) const lockfile = await readYamlFile('./pnpm-lock.yaml') @@ -127,22 +136,26 @@ test('recursive update --latest foo should only update packages that have foo', ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') await addDistTag({ package: 'foo', version: '100.1.0', distTag: 'latest' }) await addDistTag({ package: 'bar', version: '100.1.0', distTag: 'latest' }) - await recursive.handler(['update', 'foo', 'qar@100.1.0'], { + await update.handler(['foo', 'qar@100.1.0'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), latest: true, + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) { @@ -171,10 +184,12 @@ test('recursive update in workspace should not add new dependencies', async (t) }, ]) - await recursive.handler(['update', 'is-positive'], { + await update.handler(['is-positive'], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), + recursive: true, + workspaceDir: process.cwd(), }) projects['project-1'].hasNot('is-positive') @@ -194,10 +209,12 @@ test('recursive update should not add new dependencies', async (t) => { }, ]) - await recursive.handler(['update', 'is-positive'], { + await update.handler(['is-positive'], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), + recursive: true, + workspaceDir: process.cwd(), }) projects['project-1'].hasNot('is-positive') diff --git a/packages/plugin-commands-recursive/test/updateWorkspaceDependencies.spec.ts b/packages/plugin-commands-installation/test/updateWorkspaceDependencies.spec.ts similarity index 95% rename from packages/plugin-commands-recursive/test/updateWorkspaceDependencies.spec.ts rename to packages/plugin-commands-installation/test/updateWorkspaceDependencies.spec.ts index 0c36b1391e..428eba6c80 100644 --- a/packages/plugin-commands-recursive/test/updateWorkspaceDependencies.spec.ts +++ b/packages/plugin-commands-installation/test/updateWorkspaceDependencies.spec.ts @@ -2,7 +2,7 @@ import PnpmError from '@pnpm/error' import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest, -} from '@pnpm/plugin-commands-recursive/lib/updateWorkspaceDependencies' +} from '@pnpm/plugin-commands-installation/lib/updateWorkspaceDependencies' import test = require('tape') const INCLUDE_ALL = { diff --git a/packages/plugin-commands-installation/test/utils.ts b/packages/plugin-commands-installation/test/utils.ts new file mode 100644 index 0000000000..da9f9ff78d --- /dev/null +++ b/packages/plugin-commands-installation/test/utils.ts @@ -0,0 +1,46 @@ +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: true, + ca: undefined, + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './pnpmfile.js', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + userAgent: 'pnpm', + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/plugin-commands-listing/package.json b/packages/plugin-commands-listing/package.json index 890024ed25..f2ca887b95 100644 --- a/packages/plugin-commands-listing/package.json +++ b/packages/plugin-commands-listing/package.json @@ -16,15 +16,13 @@ "registry-mock": "registry-mock", "test:tap": "ts-node test --type-check", "pretest:e2e": "registry-mock prepare", - "test:e2e": "cross-env PNPM_REGISTRY_MOCK_PORT=7775 run-p -r registry-mock test:tap", - "test": "pnpm run tsc && pnpm run lint && pnpm run test:e2e", + "test:e2e": "run-p -r registry-mock test:tap", + "test": "pnpm run tsc && pnpm run lint && cross-env PNPM_REGISTRY_MOCK_PORT=7773 pnpm run test:e2e", "prepublishOnly": "pnpm run tsc" }, "repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-listing", "keywords": [ - "pnpm", - "pack", - "publish" + "pnpm" ], "author": "Zoltan Kochan (https://www.kochan.io/)", "license": "MIT", @@ -34,6 +32,9 @@ "homepage": "https://pnpm.js.org", "devDependencies": { "@pnpm/constants": "workspace:3.0.0", + "@pnpm/filter-workspace-packages": "workspace:1.0.1", + "@pnpm/logger": "^3.1.0", + "@pnpm/plugin-commands-installation": "workspace:*", "@pnpm/plugin-commands-listing": "link:", "@pnpm/prepare": "workspace:0.0.0", "@types/common-tags": "1.8.0", @@ -54,5 +55,8 @@ "common-tags": "1.8.0", "ramda": "0.26.1", "render-help": "0.0.0" + }, + "peerDependencies": { + "@pnpm/logger": "^3.1.0" } } diff --git a/packages/plugin-commands-listing/src/list.ts b/packages/plugin-commands-listing/src/list.ts index c5764be7c9..4c6d038788 100644 --- a/packages/plugin-commands-listing/src/list.ts +++ b/packages/plugin-commands-listing/src/list.ts @@ -6,6 +6,7 @@ import list, { forPackages as listForPackages } from '@pnpm/list' import { oneLine } from 'common-tags' import R = require('ramda') import renderHelp = require('render-help') +import listRecursive from './recursive' export const rcOptionsTypes = cliOptionsTypes @@ -97,15 +98,20 @@ export function help () { export function handler ( args: string[], - opts: Pick & { + opts: Pick & { alwaysPrintRootPackage?: boolean, depth?: number, lockfileDir?: string, long?: boolean, parseable?: boolean, + recursive?: boolean, }, command: string, ) { + if (opts.recursive && opts.selectedWsPkgsGraph) { + const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) + return listRecursive(pkgs, args, command, opts) + } return render([opts.dir], args, { ...opts, lockfileDir: opts.lockfileDir || opts.dir, diff --git a/packages/plugin-commands-recursive/src/list.ts b/packages/plugin-commands-listing/src/recursive.ts similarity index 72% rename from packages/plugin-commands-recursive/src/list.ts rename to packages/plugin-commands-listing/src/recursive.ts index 73caddc80c..74d801b1fe 100644 --- a/packages/plugin-commands-recursive/src/list.ts +++ b/packages/plugin-commands-listing/src/recursive.ts @@ -1,13 +1,12 @@ -import { Config } from '@pnpm/config' +import { Config, WsPkg } from '@pnpm/config' import logger from '@pnpm/logger' -import { list } from '@pnpm/plugin-commands-listing' -import { ImporterManifest } from '@pnpm/types' +import { render } from './list' export default async ( - pkgs: Array<{ dir: string, manifest: ImporterManifest }>, + pkgs: WsPkg[], args: string[], cmd: string, - opts: Config & { + opts: Pick & { depth?: number, long?: boolean, parseable?: boolean, @@ -16,7 +15,7 @@ export default async ( ) => { const depth = opts.depth ?? 0 if (opts.lockfileDir) { - return list.render(pkgs.map((pkg) => pkg.dir), args, { + return render(pkgs.map((pkg) => pkg.dir), args, { ...opts, alwaysPrintRootPackage: depth === -1, lockfileDir: opts.lockfileDir, @@ -25,7 +24,7 @@ export default async ( const outputs = [] for (const { dir } of pkgs) { try { - const output = await list.render([dir], args, { + const output = await render([dir], args, { ...opts, alwaysPrintRootPackage: depth === -1, lockfileDir: opts.lockfileDir || dir, diff --git a/packages/plugin-commands-listing/test/index.ts b/packages/plugin-commands-listing/test/index.ts index 05834391e3..7af3ff3aab 100644 --- a/packages/plugin-commands-listing/test/index.ts +++ b/packages/plugin-commands-listing/test/index.ts @@ -10,6 +10,7 @@ import path = require('path') import stripAnsi = require('strip-ansi') import test = require('tape') import writeYamlFile = require('write-yaml-file') +import './recursive' test('listing packages', async (t) => { prepare(t, { diff --git a/packages/plugin-commands-recursive/test/list.ts b/packages/plugin-commands-listing/test/recursive.ts similarity index 84% rename from packages/plugin-commands-recursive/test/list.ts rename to packages/plugin-commands-listing/test/recursive.ts index 4f59a16495..5982c32935 100644 --- a/packages/plugin-commands-recursive/test/list.ts +++ b/packages/plugin-commands-listing/test/recursive.ts @@ -1,5 +1,7 @@ import PnpmError from '@pnpm/error' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { install } from '@pnpm/plugin-commands-installation' +import { list, why } from '@pnpm/plugin-commands-listing' import prepare, { preparePackages } from '@pnpm/prepare' import { addDistTag } from '@pnpm/registry-mock' import { stripIndent } from 'common-tags' @@ -8,7 +10,7 @@ import path = require('path') import stripAnsi = require('strip-ansi') import test = require('tape') import writeYamlFile = require('write-yaml-file') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' test('recursive list', async (t) => { const projects = preparePackages(t, [ @@ -35,19 +37,22 @@ test('recursive list', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') - const output = await recursive.handler(['list'], { + const output = await list.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + }, 'list') t.equal(stripAnsi(output as unknown as string), stripIndent` Legend: production dependency, optional only, dev only @@ -97,20 +102,23 @@ test('recursive list with shared-workspace-lockfile', async (t) => { await fs.writeFile('.npmrc', 'shared-workspace-lockfile = true', 'utf8') const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') - const output = await recursive.handler(['list'], { + const output = await list.handler([], { ...DEFAULT_OPTS, allWsPkgs, depth: 2, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + }, 'list') t.equal(stripAnsi(output as unknown as string), stripIndent` Legend: production dependency, optional only, dev only @@ -161,19 +169,22 @@ test('recursive list --filter', async (t) => { }, ]) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + workspaceDir: process.cwd(), + }, 'install') - const output = await recursive.handler(['list'], { + const output = await list.handler([], { ...DEFAULT_OPTS, dir: process.cwd(), + recursive: true, ...await readWsPkgs(process.cwd(), [ { includeDependencies: true, namePattern: 'project-1' }, ]), - }) + }, 'list') t.equal(stripAnsi(output as unknown as string), stripIndent` Legend: production dependency, optional only, dev only @@ -199,11 +210,12 @@ test('`pnpm recursive why` should fail if no package name was provided', async ( let err!: PnpmError try { - const output = await recursive.handler(['why'], { + await why.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), - }) + recursive: true, + }, 'why') } catch (_err) { err = _err } diff --git a/packages/plugin-commands-recursive/test/utils.ts b/packages/plugin-commands-listing/test/utils.ts similarity index 67% rename from packages/plugin-commands-recursive/test/utils.ts rename to packages/plugin-commands-listing/test/utils.ts index 7a3b8cdf86..4916e73764 100644 --- a/packages/plugin-commands-recursive/test/utils.ts +++ b/packages/plugin-commands-listing/test/utils.ts @@ -1,5 +1,3 @@ -import { filterPkgsBySelectorObjects, PackageSelector } from '@pnpm/filter-workspace-packages' -import findWorkspacePackages from '@pnpm/find-workspace-packages' import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` @@ -46,18 +44,3 @@ export const DEFAULT_OPTS = { useStoreServer: false, workspaceConcurrency: 4, } - -export async function readWsPkgs ( - workspaceDir: string, - pkgSelectors: PackageSelector[], -) { - const allWsPkgs = await findWorkspacePackages(workspaceDir, {}) - const selectedWsPkgsGraph = await filterPkgsBySelectorObjects( - allWsPkgs, - pkgSelectors, - { - workspaceDir, - }, - ) - return { allWsPkgs, selectedWsPkgsGraph } -} diff --git a/packages/plugin-commands-outdated/package.json b/packages/plugin-commands-outdated/package.json index 82e57b148a..8c1bfe7cff 100644 --- a/packages/plugin-commands-outdated/package.json +++ b/packages/plugin-commands-outdated/package.json @@ -33,6 +33,8 @@ "homepage": "https://pnpm.js.org", "devDependencies": { "@pnpm/constants": "workspace:3.0.0", + "@pnpm/filter-workspace-packages": "workspace:1.0.1", + "@pnpm/plugin-commands-installation": "workspace:*", "@pnpm/plugin-commands-outdated": "link:", "@pnpm/prepare": "workspace:0.0.0", "@pnpm/types": "workspace:4.0.0", diff --git a/packages/plugin-commands-outdated/src/outdated.ts b/packages/plugin-commands-outdated/src/outdated.ts index da9a64574e..47851018c7 100644 --- a/packages/plugin-commands-outdated/src/outdated.ts +++ b/packages/plugin-commands-outdated/src/outdated.ts @@ -1,6 +1,6 @@ import { createLatestManifestGetter, docsUrl, readImporterManifestOnly, TABLE_OPTIONS } from '@pnpm/cli-utils' import { FILTERING, OPTIONS, UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help' -import { types as allTypes } from '@pnpm/config' +import { Config, types as allTypes } from '@pnpm/config' import PnpmError from '@pnpm/error' import { getLockfileImporterId, @@ -10,7 +10,7 @@ import { import matcher from '@pnpm/matcher' import { read as readModulesManifest } from '@pnpm/modules-yaml' import outdated, { OutdatedPackage } from '@pnpm/outdated' -import semverDiff, { SEMVER_CHANGE } from '@pnpm/semver-diff' +import semverDiff from '@pnpm/semver-diff' import storePath from '@pnpm/store-path' import { ImporterManifest, Registries } from '@pnpm/types' import chalk = require('chalk') @@ -21,6 +21,11 @@ import renderHelp = require('render-help') import stripAnsi = require('strip-ansi') import { table } from 'table' import wrapAnsi = require('wrap-ansi') +import outdatedRecursive from './recursive' +import { + DEFAULT_COMPARATORS, + OutdatedWithVersionDiff, +} from './utils' export const rcOptionsTypes = cliOptionsTypes @@ -82,17 +87,7 @@ export function help () { }) } -export type OutdatedWithVersionDiff = OutdatedPackage & { change: SEMVER_CHANGE | null, diff?: [string[], string[]] } - -/** - * Default comparators used as the argument to `ramda.sortWith()`. - */ -export const DEFAULT_COMPARATORS = [ - sortBySemverChange, - (o1: OutdatedWithVersionDiff, o2: OutdatedWithVersionDiff) => o1.packageName.localeCompare(o2.packageName), -] - -export interface OutdatedOptions { +export type OutdatedOptions = { alwaysAuth: boolean ca?: string cert?: string @@ -112,6 +107,7 @@ export interface OutdatedOptions { dir: string proxy?: string rawConfig: object + recursive?: boolean, registries: Registries lockfileDir?: string store?: string @@ -119,13 +115,17 @@ export interface OutdatedOptions { table?: boolean tag: string userAgent: string -} +} & Pick export async function handler ( args: string[], opts: OutdatedOptions, - command: string, + command?: string, ) { + if (opts.recursive && opts.selectedWsPkgsGraph) { + const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) + return outdatedRecursive(pkgs, args, opts) + } const packages = [ { dir: opts.dir, @@ -274,20 +274,6 @@ function joinVersionTuples (versionTuples: string[], startIndex: number) { }` } -export function sortBySemverChange (outdated1: OutdatedWithVersionDiff, outdated2: OutdatedWithVersionDiff) { - return pkgPriority(outdated1) - pkgPriority(outdated2) -} - -function pkgPriority (pkg: OutdatedWithVersionDiff) { - switch (pkg.change) { - case null: return 0 - case 'fix': return 1 - case 'feature': return 2 - case 'breaking': return 3 - default: return 4 - } -} - export function renderDetails ({ latestManifest }: OutdatedPackage) { if (!latestManifest) return '' const outputs = [] diff --git a/packages/plugin-commands-recursive/src/outdated.ts b/packages/plugin-commands-outdated/src/recursive.ts similarity index 95% rename from packages/plugin-commands-recursive/src/outdated.ts rename to packages/plugin-commands-outdated/src/recursive.ts index d648913ad1..afc7d98b77 100644 --- a/packages/plugin-commands-recursive/src/outdated.ts +++ b/packages/plugin-commands-outdated/src/recursive.ts @@ -1,8 +1,12 @@ import { TABLE_OPTIONS } from '@pnpm/cli-utils' import { getLockfileImporterId } from '@pnpm/lockfile-file' import { OutdatedPackage } from '@pnpm/outdated' +import { DependenciesField, ImporterManifest } from '@pnpm/types' +import chalk = require('chalk') +import { stripIndent } from 'common-tags' +import R = require('ramda') +import { table } from 'table' import { - DEFAULT_COMPARATORS, getCellWidth, outdatedDependenciesOfWorkspacePackages, OutdatedOptions, @@ -11,12 +15,8 @@ import { renderLatest, renderPackageName, toOutdatedWithVersionDiff, -} from '@pnpm/plugin-commands-outdated/lib/outdated' -import { DependenciesField, ImporterManifest } from '@pnpm/types' -import chalk = require('chalk') -import { stripIndent } from 'common-tags' -import R = require('ramda') -import { table } from 'table' +} from './outdated' +import { DEFAULT_COMPARATORS } from './utils' const DEP_PRIORITY: Record = { dependencies: 1, @@ -26,7 +26,8 @@ const DEP_PRIORITY: Record = { const COMPARATORS = [ ...DEFAULT_COMPARATORS, - (o1: OutdatedInWorkspace, o2: OutdatedInWorkspace) => DEP_PRIORITY[o1.belongsTo] - DEP_PRIORITY[o2.belongsTo], + (o1: OutdatedInWorkspace, o2: OutdatedInWorkspace) => + DEP_PRIORITY[o1.belongsTo] - DEP_PRIORITY[o2.belongsTo], ] interface OutdatedInWorkspace extends OutdatedPackage { @@ -41,7 +42,6 @@ interface OutdatedInWorkspace extends OutdatedPackage { export default async ( pkgs: Array<{ dir: string, manifest: ImporterManifest }>, args: string[], - cmd: string, opts: OutdatedOptions, ) => { const outdatedByNameAndType = {} as Record diff --git a/packages/plugin-commands-outdated/src/utils.ts b/packages/plugin-commands-outdated/src/utils.ts new file mode 100644 index 0000000000..06a3725bb6 --- /dev/null +++ b/packages/plugin-commands-outdated/src/utils.ts @@ -0,0 +1,27 @@ +import { OutdatedPackage } from '@pnpm/outdated' +import { SEMVER_CHANGE } from '@pnpm/semver-diff' + +export type OutdatedWithVersionDiff = OutdatedPackage & { change: SEMVER_CHANGE | null, diff?: [string[], string[]] } + +/** + * Default comparators used as the argument to `ramda.sortWith()`. + */ +export const DEFAULT_COMPARATORS = [ + sortBySemverChange, + (o1: OutdatedWithVersionDiff, o2: OutdatedWithVersionDiff) => + o1.packageName.localeCompare(o2.packageName), +] + +export function sortBySemverChange (outdated1: OutdatedWithVersionDiff, outdated2: OutdatedWithVersionDiff) { + return pkgPriority(outdated1) - pkgPriority(outdated2) +} + +function pkgPriority (pkg: OutdatedWithVersionDiff) { + switch (pkg.change) { + case null: return 0 + case 'fix': return 1 + case 'feature': return 2 + case 'breaking': return 3 + default: return 4 + } +} diff --git a/packages/plugin-commands-outdated/test/index.ts b/packages/plugin-commands-outdated/test/index.ts index 0a220b69e3..86368a2f4e 100644 --- a/packages/plugin-commands-outdated/test/index.ts +++ b/packages/plugin-commands-outdated/test/index.ts @@ -11,6 +11,7 @@ import path = require('path') import stripAnsi = require('strip-ansi') import test = require('tape') import { promisify } from 'util' +import './recursive' const copyFile = promisify(fs.copyFile) const fixtures = path.join(__dirname, '../../../fixtures') diff --git a/packages/plugin-commands-recursive/test/outdated.ts b/packages/plugin-commands-outdated/test/recursive.ts similarity index 91% rename from packages/plugin-commands-recursive/test/outdated.ts rename to packages/plugin-commands-outdated/test/recursive.ts index 10722d8e7f..3d00d31af6 100644 --- a/packages/plugin-commands-recursive/test/outdated.ts +++ b/packages/plugin-commands-outdated/test/recursive.ts @@ -1,10 +1,11 @@ -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { install } from '@pnpm/plugin-commands-installation' +import { outdated } from '@pnpm/plugin-commands-outdated' import { preparePackages } from '@pnpm/prepare' import { stripIndent } from 'common-tags' import stripAnsi = require('strip-ansi') import test = require('tape') -import writeYamlFile = require('write-yaml-file') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' test('pnpm recursive outdated', async (t) => { preparePackages(t, [ @@ -38,18 +39,21 @@ test('pnpm recursive outdated', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') { - const output = await recursive.handler(['outdated'], { + const output = await outdated.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, }) @@ -67,11 +71,12 @@ test('pnpm recursive outdated', async (t) => { } { - const output = await recursive.handler(['outdated'], { + const output = await outdated.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), long: true, + recursive: true, selectedWsPkgsGraph, }) @@ -89,10 +94,11 @@ test('pnpm recursive outdated', async (t) => { } { - const output = await recursive.handler(['outdated'], { + const output = await outdated.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, table: false, }) @@ -113,11 +119,12 @@ test('pnpm recursive outdated', async (t) => { } { - const output = await recursive.handler(['outdated'], { + const output = await outdated.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), long: true, + recursive: true, selectedWsPkgsGraph, table: false, }) @@ -141,10 +148,11 @@ test('pnpm recursive outdated', async (t) => { } { - const output = await recursive.handler(['outdated', 'is-positive'], { + const output = await outdated.handler(['is-positive'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, }) @@ -190,21 +198,22 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => { }, ]) - await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] }) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + await install.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, - }) + workspaceDir: process.cwd(), + }, 'install') { - const output = await recursive.handler(['outdated'], { + const output = await outdated.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, }) @@ -222,10 +231,11 @@ test('pnpm recursive outdated in workspace with shared lockfile', async (t) => { } { - const output = await recursive.handler(['outdated', 'is-positive'], { + const output = await outdated.handler(['is-positive'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, }) diff --git a/packages/plugin-commands-outdated/test/utils.ts b/packages/plugin-commands-outdated/test/utils.ts new file mode 100644 index 0000000000..31b495c561 --- /dev/null +++ b/packages/plugin-commands-outdated/test/utils.ts @@ -0,0 +1,49 @@ +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: false, + ca: undefined, + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + global: false, + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + independentLeaves: false, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './pnpmfile.js', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + tag: 'latest', + userAgent: 'pnpm', + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/plugin-commands-publishing/package.json b/packages/plugin-commands-publishing/package.json index 4835d6617d..b7795dcac7 100644 --- a/packages/plugin-commands-publishing/package.json +++ b/packages/plugin-commands-publishing/package.json @@ -17,7 +17,7 @@ "test:tap": "ts-node test --type-check", "pretest:e2e": "registry-mock prepare", "test:e2e": "run-p -r registry-mock test:tap", - "test": "pnpm run tsc && pnpm run lint && cross-env PNPM_REGISTRY_MOCK_PORT=7775 pnpm run test:e2e", + "test": "pnpm run tsc && pnpm run lint && cross-env PNPM_REGISTRY_MOCK_PORT=7771 pnpm run test:e2e", "prepublishOnly": "pnpm run tsc" }, "repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-publishing", @@ -33,12 +33,15 @@ }, "homepage": "https://pnpm.js.org", "devDependencies": { + "@pnpm/filter-workspace-packages": "workspace:1.0.1", "@pnpm/plugin-commands-publishing": "link:", "@pnpm/prepare": "workspace:0.0.0", "@types/cross-spawn": "^6.0.1", + "@types/lru-cache": "^5.1.0", "@types/mz": "^2.7.0", "@types/ramda": "^0.26.38", "cross-spawn": "7.0.1", + "execa": "4.0.0", "path-exists": "4.0.0", "rimraf": "3.0.0", "write-yaml-file": "3.0.1" @@ -47,13 +50,19 @@ "@pnpm/cli-utils": "workspace:0.2.5", "@pnpm/config": "workspace:6.0.0", "@pnpm/error": "workspace:1.0.0", + "@pnpm/npm-resolver": "workspace:6.0.2", "@pnpm/read-importer-manifest": "workspace:2.0.1", + "@pnpm/resolver-base": "workspace:6.0.0", "@pnpm/run-npm": "workspace:1.0.0", + "@pnpm/store-path": "2.1.1", "@pnpm/types": "workspace:4.0.0", + "@pnpm/utils": "workspace:0.12.2", "@zkochan/rimraf": "1.0.0", "cp-file": "7.0.0", "fast-glob": "3.1.1", + "lru-cache": "5.1.1", "mz": "2.7.0", + "p-filter": "2.1.0", "ramda": "0.26.1", "render-help": "0.0.0", "write-json-file": "4.2.1" diff --git a/packages/plugin-commands-publishing/src/publish.ts b/packages/plugin-commands-publishing/src/publish.ts index d39cf0e26c..582491d9e5 100644 --- a/packages/plugin-commands-publishing/src/publish.ts +++ b/packages/plugin-commands-publishing/src/publish.ts @@ -1,5 +1,5 @@ import { docsUrl, readImporterManifest } from '@pnpm/cli-utils' -import { types as allTypes } from '@pnpm/config' +import { Config, types as allTypes } from '@pnpm/config' import PnpmError from '@pnpm/error' import { tryReadImporterManifest } from '@pnpm/read-importer-manifest' import runNpm from '@pnpm/run-npm' @@ -12,6 +12,7 @@ import path = require('path') import R = require('ramda') import renderHelp = require('render-help') import writeJsonFile = require('write-json-file') +import recursivePublish, { PublishRecursiveOpts } from './recursivePublish' export const rcOptionsTypes = cliOptionsTypes @@ -35,15 +36,24 @@ export function help () { export async function handler ( args: string[], - opts: { + opts: Omit & { argv: { original: string[], }, engineStrict?: boolean, + recursive?: boolean, workspaceDir?: string, - }, - command: string, + } & Pick, + command?: string, ) { + if (opts.recursive && opts.selectedWsPkgsGraph) { + const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) + await recursivePublish(pkgs, { + ...opts, + workspaceDir: opts.workspaceDir ?? process.cwd(), + }) + return + } if (args.length && args[0].endsWith('.tgz')) { await runNpm(['publish', ...args]) return diff --git a/packages/plugin-commands-recursive/src/publish.ts b/packages/plugin-commands-publishing/src/recursivePublish.ts similarity index 70% rename from packages/plugin-commands-recursive/src/publish.ts rename to packages/plugin-commands-publishing/src/recursivePublish.ts index d7342c7bfe..83a78e941a 100644 --- a/packages/plugin-commands-recursive/src/publish.ts +++ b/packages/plugin-commands-publishing/src/recursivePublish.ts @@ -1,43 +1,49 @@ -import { Config } from '@pnpm/config' +import { Config, WsPkg } from '@pnpm/config' import createResolver from '@pnpm/npm-resolver' -import { publish } from '@pnpm/plugin-commands-publishing' import { ResolveFunction } from '@pnpm/resolver-base' import runNpm from '@pnpm/run-npm' import storePath from '@pnpm/store-path' -import { ImporterManifest, Registries } from '@pnpm/types' +import { Registries } from '@pnpm/types' import { pickRegistryForPackage } from '@pnpm/utils' import LRU = require('lru-cache') import pFilter = require('p-filter') +import { handler as publish } from './publish' + +export type PublishRecursiveOpts = Required> & +Partial> & { + access?: 'public' | 'restricted', + argv: { + original: string[], + }, +} export default async function ( - pkgs: Array<{ dir: string, manifest: ImporterManifest }>, - opts: Pick & { - access?: 'public' | 'restricted', - argv: { - original: string[], - }, - tag?: string, - ca?: string, - cert?: string, - fetchRetries?: number, - fetchRetryFactor?: number, - fetchRetryMaxtimeout?: number, - fetchRetryMintimeout?: number, - httpsProxy?: string, - key?: string, - localAddress?: string, - lockfileDir?: string, - offline?: boolean, - dir: string, - proxy?: string, - rawConfig: object, - registries: Registries, - storeDir?: string, - strictSsl?: boolean, - userAgent?: string, - verifyStoreIntegrity?: boolean, - workspaceDir: string, - }, + pkgs: WsPkg[], + opts: PublishRecursiveOpts, ) { const storeDir = await storePath(opts.workspaceDir, opts.storeDir) const resolve = createResolver(Object.assign(opts, { @@ -59,7 +65,8 @@ export default async function ( }) const access = opts.cliOptions['access'] ? ['--access', opts.cliOptions['access']] : [] for (const pkg of pkgsToPublish) { - await publish.handler([pkg.dir], { + await publish([pkg.dir], { + ...opts, argv: { original: [ 'publish', @@ -71,7 +78,7 @@ export default async function ( ...access, ], }, - workspaceDir: opts.workspaceDir, + recursive: false, }, 'publish') } const tag = opts.tag || 'latest' diff --git a/packages/plugin-commands-publishing/test/index.ts b/packages/plugin-commands-publishing/test/index.ts index 7a276797d1..51b3d3a3f5 100644 --- a/packages/plugin-commands-publishing/test/index.ts +++ b/packages/plugin-commands-publishing/test/index.ts @@ -1,3 +1,4 @@ /// import './pack' import './publish' +import './recursivePublish' diff --git a/packages/plugin-commands-publishing/test/publish.ts b/packages/plugin-commands-publishing/test/publish.ts index c7672a220a..0d90d64a8c 100644 --- a/packages/plugin-commands-publishing/test/publish.ts +++ b/packages/plugin-commands-publishing/test/publish.ts @@ -8,6 +8,7 @@ import path = require('path') import exists = require('path-exists') import test = require('tape') import writeYamlFile = require('write-yaml-file') +import { DEFAULT_OPTS } from './utils' const CREDENTIALS = [ `--registry=http://localhost:${REGISTRY_MOCK_PORT}/`, @@ -22,7 +23,11 @@ test('publish: package with package.json', async (t) => { version: '0.0.0', }) - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') t.end() }) @@ -32,7 +37,11 @@ test('publish: package with package.yaml', async (t) => { version: '0.0.0', }, { manifestFormat: 'YAML' }) - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') t.ok(await exists('package.yaml')) t.notOk(await exists('package.json')) @@ -45,7 +54,11 @@ test('publish: package with package.json5', async (t) => { version: '0.0.0', }, { manifestFormat: 'JSON5' }) - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') t.ok(await exists('package.json5')) t.notOk(await exists('package.json')) @@ -60,7 +73,11 @@ test('publish: package with package.json5 running publish from different folder' process.chdir('..') - await publish.handler(['project'], { argv: { original: ['publish', ...CREDENTIALS, 'project'] } }, 'publish') + await publish.handler(['project'], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS, 'project'] }, + dir: process.cwd(), + }, 'publish') t.ok(await exists('project/package.json5')) t.notOk(await exists('project/package.json')) @@ -129,10 +146,20 @@ test('publish packages with workspace LICENSE if no own LICENSE is present', asy await fs.writeFile('project-200/LICENSE', 'project-200 license', 'utf8') process.chdir('project-100') - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] }, workspaceDir }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + workspaceDir, + }, 'publish') process.chdir('../project-200') - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] }, workspaceDir }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + workspaceDir, + }, 'publish') process.chdir('../target') @@ -181,7 +208,11 @@ test('publish: package with all possible fields in publishConfig', async (t) => process.chdir('test-publish-config') await fs.writeFile('published-bin.js', `#!/usr/bin/env node`, 'utf8') - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') const originalManifests = await import(path.resolve('package.json')) t.deepEqual(originalManifests, { @@ -286,7 +317,12 @@ test.skip('publish package that calls executable from the workspace .bin folder await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] }) process.chdir('test-publish-scripts') - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] }, workspaceDir }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + workspaceDir, + }, 'publish') t.deepEqual( await import(path.resolve('output.json')), @@ -353,7 +389,11 @@ test('convert specs with workspace protocols to regular version ranges', async ( let err!: PnpmError try { - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') } catch (_err) { err = _err } @@ -368,7 +408,11 @@ test('convert specs with workspace protocols to regular version ranges', async ( crossSpawn.sync('pnpm', ['multi', 'install', '--store-dir=store', `--registry=http://localhost:${REGISTRY_MOCK_PORT}`]) process.chdir('workspace-protocol-package') - await publish.handler([], { argv: { original: ['publish', ...CREDENTIALS] } }, 'publish') + await publish.handler([], { + ...DEFAULT_OPTS, + argv: { original: ['publish', ...CREDENTIALS] }, + dir: process.cwd(), + }, 'publish') process.chdir('../target') diff --git a/packages/plugin-commands-recursive/test/publish.ts b/packages/plugin-commands-publishing/test/recursivePublish.ts similarity index 90% rename from packages/plugin-commands-recursive/test/publish.ts rename to packages/plugin-commands-publishing/test/recursivePublish.ts index 0b3c588905..bf1b512a4e 100644 --- a/packages/plugin-commands-recursive/test/publish.ts +++ b/packages/plugin-commands-publishing/test/recursivePublish.ts @@ -1,10 +1,11 @@ -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { publish } from '@pnpm/plugin-commands-publishing' import { preparePackages } from '@pnpm/prepare' import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' import execa = require('execa') import fs = require('mz/fs') import test = require('tape') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS } from './utils' const CREDENTIALS = [ `--registry=http://localhost:${REGISTRY_MOCK_PORT}/`, @@ -65,10 +66,11 @@ test('recursive publish', async (t) => { await fs.writeFile('.npmrc', CREDENTIALS, 'utf8') - await recursive.handler(['publish'], { + await publish.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), + recursive: true, }) { @@ -82,10 +84,11 @@ test('recursive publish', async (t) => { await projects[pkg1.name].writePackageJson({ ...pkg1, version: '2.0.0' }) - await recursive.handler(['publish'], { + await publish.handler([], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), []), dir: process.cwd(), + recursive: true, tag: 'next', }) diff --git a/packages/plugin-commands-publishing/test/utils.ts b/packages/plugin-commands-publishing/test/utils.ts new file mode 100644 index 0000000000..4916e73764 --- /dev/null +++ b/packages/plugin-commands-publishing/test/utils.ts @@ -0,0 +1,46 @@ +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: false, + ca: undefined, + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './pnpmfile.js', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + userAgent: 'pnpm', + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/plugin-commands-rebuild/package.json b/packages/plugin-commands-rebuild/package.json index b83d740ff8..1821d7cd0d 100644 --- a/packages/plugin-commands-rebuild/package.json +++ b/packages/plugin-commands-rebuild/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://pnpm.js.org", "devDependencies": { + "@pnpm/filter-workspace-packages": "workspace:1.0.1", "@pnpm/logger": "^3.1.0", "@pnpm/plugin-commands-rebuild": "link:", "@pnpm/prepare": "workspace:0.0.0", @@ -44,7 +45,8 @@ "execa": "4.0.0", "path-exists": "4.0.0", "rimraf": "3.0.0", - "sinon": "8.0.0" + "sinon": "8.0.0", + "write-yaml-file": "3.0.1" }, "dependencies": { "@pnpm/cli-utils": "workspace:0.2.5", @@ -52,6 +54,7 @@ "@pnpm/config": "workspace:6.0.0", "@pnpm/constants": "workspace:3.0.0", "@pnpm/core-loggers": "workspace:4.0.0", + "@pnpm/find-workspace-packages": "workspace:2.0.8", "@pnpm/get-context": "workspace:0.0.0", "@pnpm/lifecycle": "workspace:8.0.1", "@pnpm/link-bins": "5.0.1", @@ -59,17 +62,21 @@ "@pnpm/lockfile-walker": "workspace:1.0.1", "@pnpm/modules-yaml": "workspace:5.0.0", "@pnpm/pkgid-to-filename": "2.0.0", + "@pnpm/sort-packages": "workspace:0.0.0", "@pnpm/store-connection-manager": "workspace:0.2.5", "@pnpm/store-controller-types": "workspace:6.0.0", "@pnpm/types": "workspace:4.0.0", "@pnpm/utils": "workspace:0.12.2", "@zkochan/npm-package-arg": "1.0.2", + "camelcase-keys": "6.1.1", "common-tags": "1.8.0", "dependency-path": "workspace:4.0.2", "graph-sequencer": "2.0.0", "load-json-file": "6.2.0", + "mem": "6.0.1", "p-limit": "2.2.1", "ramda": "0.26.1", + "read-ini-file": "2.0.0", "render-help": "0.0.0", "run-groups": "2.0.1", "semver": "7.1.1" diff --git a/packages/plugin-commands-rebuild/src/implementation/index.ts b/packages/plugin-commands-rebuild/src/implementation/index.ts index 7bccafea1b..4c09818e58 100644 --- a/packages/plugin-commands-rebuild/src/implementation/index.ts +++ b/packages/plugin-commands-rebuild/src/implementation/index.ts @@ -34,6 +34,8 @@ import extendOptions, { StrictRebuildOptions, } from './extendRebuildOptions' +export { RebuildOptions } + function findPackages ( packages: PackageSnapshots, searched: PackageSelector[], diff --git a/packages/plugin-commands-rebuild/src/rebuild.ts b/packages/plugin-commands-rebuild/src/rebuild.ts index 529651b8a9..81124d02ef 100644 --- a/packages/plugin-commands-rebuild/src/rebuild.ts +++ b/packages/plugin-commands-rebuild/src/rebuild.ts @@ -2,7 +2,10 @@ import { docsUrl, readImporterManifestOnly } from '@pnpm/cli-utils' import { FILTERING, UNIVERSAL_OPTIONS } from '@pnpm/common-cli-options-help' import { Config, types as allTypes } from '@pnpm/config' import { LogBase } from '@pnpm/logger' -import { CreateStoreControllerOptions, createOrConnectStoreController } from '@pnpm/store-connection-manager' +import { + createOrConnectStoreController, + CreateStoreControllerOptions, +} from '@pnpm/store-connection-manager' import { oneLine } from 'common-tags' import R = require('ramda') import renderHelp = require('render-help') @@ -10,6 +13,7 @@ import { rebuild, rebuildPkgs, } from './implementation' +import recursive from './recursive' export function rcOptionsTypes () { return {} @@ -58,10 +62,18 @@ export function help () { export async function handler ( args: string[], - opts: Pick & + opts: Pick & CreateStoreControllerOptions & - { reporter?: (logObj: LogBase) => void, pending: boolean }, + { + recursive?: boolean, + reporter?: (logObj: LogBase) => void, + pending: boolean, + }, ) { + if (opts.recursive && opts.allWsPkgs && opts.selectedWsPkgsGraph && opts.workspaceDir) { + await recursive(opts.allWsPkgs, args, { ...opts, selectedWsPkgsGraph: opts.selectedWsPkgsGraph!, workspaceDir: opts.workspaceDir! }) + return + } const store = await createOrConnectStoreController(opts) const rebuildOpts = Object.assign(opts, { storeController: store.ctrl, diff --git a/packages/plugin-commands-rebuild/src/recursive.ts b/packages/plugin-commands-rebuild/src/recursive.ts new file mode 100755 index 0000000000..82f74235d7 --- /dev/null +++ b/packages/plugin-commands-rebuild/src/recursive.ts @@ -0,0 +1,187 @@ +import { + RecursiveSummary, + throwOnCommandFail, +} from '@pnpm/cli-utils' +import { Config, types as allTypes, WsPkg, WsPkgsGraph } from '@pnpm/config' +import { scopeLogger } from '@pnpm/core-loggers' +import { arrayOfWorkspacePackagesToMap } from '@pnpm/find-workspace-packages' +import logger from '@pnpm/logger' +import sortPackages from '@pnpm/sort-packages' +import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' +import { ImporterManifest, PackageManifest } from '@pnpm/types' +import camelcaseKeys = require('camelcase-keys') +import mem = require('mem') +import pLimit from 'p-limit' +import path = require('path') +import readIniFile = require('read-ini-file') +import { rebuild as rebuildAll, RebuildOptions, rebuildPkgs } from './implementation' + +type RecursiveRebuildOpts = CreateStoreControllerOptions & Pick & { + pending?: boolean, +} & Partial> + +export default async function recursive ( + allWsPkgs: WsPkg[], + input: string[], + opts: RecursiveRebuildOpts & { + ignoredPackages?: Set, + } & Required>, +) { + if (allWsPkgs.length === 0) { + // It might make sense to throw an exception in this case + return + } + + const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) + + if (pkgs.length === 0) { + return + } + const manifestsByPath: { [dir: string]: Omit } = {} + for (const { dir, manifest, writeImporterManifest } of pkgs) { + manifestsByPath[dir] = { manifest, writeImporterManifest } + } + + scopeLogger.debug({ + selected: pkgs.length, + total: allWsPkgs.length, + workspacePrefix: opts.workspaceDir, + }) + + const throwOnFail = throwOnCommandFail.bind(null, `pnpm recursive rebuild`) + + const chunks = opts.sort !== false + ? sortPackages(opts.selectedWsPkgsGraph) + : [Object.keys(opts.selectedWsPkgsGraph).sort()] + + const store = await createOrConnectStoreController(opts) + + const workspacePackages = arrayOfWorkspacePackagesToMap(allWsPkgs) + const rebuildOpts = Object.assign(opts, { + ownLifecycleHooksStdio: 'pipe', + pruneLockfileImporters: (!opts.ignoredPackages || opts.ignoredPackages.size === 0) + && pkgs.length === allWsPkgs.length, + storeController: store.ctrl, + storeDir: store.dir, + workspacePackages, + }) as RebuildOptions + + const result = { + fails: [], + passes: 0, + } as RecursiveSummary + + const memReadLocalConfig = mem(readLocalConfig) + + async function getImporters () { + const importers = [] as Array<{ buildIndex: number, manifest: ImporterManifest, rootDir: string }> + await Promise.all(chunks.map((prefixes: string[], buildIndex) => { + if (opts.ignoredPackages) { + prefixes = prefixes.filter((prefix) => !opts.ignoredPackages!.has(prefix)) + } + return Promise.all( + prefixes.map(async (prefix) => { + importers.push({ + buildIndex, + manifest: manifestsByPath[prefix].manifest, + rootDir: prefix, + }) + }) + ) + })) + return importers + } + + const rebuild = ( + input.length === 0 + ? rebuildAll + : (importers: any, opts: any) => rebuildPkgs(importers, input, opts) // tslint:disable-line + ) + if (opts.lockfileDir) { + const importers = await getImporters() + await rebuild( + importers, + { + ...rebuildOpts, + pending: opts.pending === true, + }, + ) + return + } + const limitRebuild = pLimit(opts.workspaceConcurrency ?? 4) + for (const chunk of chunks) { + await Promise.all(chunk.map((rootDir: string) => + limitRebuild(async () => { + try { + if (opts.ignoredPackages && opts.ignoredPackages.has(rootDir)) { + return + } + const localConfig = await memReadLocalConfig(rootDir) + await rebuild( + [ + { + buildIndex: 0, + manifest: manifestsByPath[rootDir].manifest, + rootDir, + }, + ], + { + ...rebuildOpts, + ...localConfig, + dir: rootDir, + pending: opts.pending === true, + rawConfig: { + ...rebuildOpts.rawConfig, + ...localConfig, + }, + }, + ) + result.passes++ + } catch (err) { + logger.info(err) + + if (!opts.bail) { + result.fails.push({ + error: err, + message: err.message, + prefix: rootDir, + }) + return + } + + err['prefix'] = rootDir // tslint:disable-line:no-string-literal + throw err + } + }), + )) + } + + throwOnFail(result) +} + +async function readLocalConfig (prefix: string) { + try { + const ini = await readIniFile(path.join(prefix, '.npmrc')) as { [key: string]: string } + const config = camelcaseKeys(ini) as ({ [key: string]: string } & { hoist?: boolean }) + if (config.shamefullyFlatten) { + config.hoistPattern = '*' + // TODO: print a warning + } + if (config.hoist === false) { + config.hoistPattern = '' + } + return config + } catch (err) { + if (err.code !== 'ENOENT') throw err + return {} + } +} diff --git a/packages/plugin-commands-rebuild/test/index.ts b/packages/plugin-commands-rebuild/test/index.ts index 99a82b2ec7..3bc92a998e 100644 --- a/packages/plugin-commands-rebuild/test/index.ts +++ b/packages/plugin-commands-rebuild/test/index.ts @@ -11,16 +11,10 @@ import exists = require('path-exists') import sinon = require('sinon') import test = require('tape') import { promisify } from 'util' +import './recursive' +import { DEFAULT_OPTS } from './utils' const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}/` -const DEFAULT_OPTS = { - independentLeaves: false, - lock: false, - rawConfig: { - registry: `http://localhost:${REGISTRY_MOCK_PORT}/`, - }, - registries: { default: `http://localhost:${REGISTRY_MOCK_PORT}/` }, -} test('rebuilds dependencies', async (t) => { const project = prepareEmpty(t) diff --git a/packages/plugin-commands-recursive/test/rebuild.ts b/packages/plugin-commands-rebuild/test/recursive.ts similarity index 82% rename from packages/plugin-commands-recursive/test/rebuild.ts rename to packages/plugin-commands-rebuild/test/recursive.ts index 3535b0cf20..2b5df3ee8e 100644 --- a/packages/plugin-commands-recursive/test/rebuild.ts +++ b/packages/plugin-commands-rebuild/test/recursive.ts @@ -1,10 +1,12 @@ -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { rebuild } from '@pnpm/plugin-commands-rebuild' import { preparePackages } from '@pnpm/prepare' import { PackageManifest } from '@pnpm/types' +import execa = require('execa') import path = require('path') import test = require('tape') import writeYamlFile = require('write-yaml-file') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS, REGISTRY } from './utils' test('pnpm recursive rebuild', async (t) => { const projects = preparePackages(t, [ @@ -27,24 +29,28 @@ test('pnpm recursive rebuild', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - ignoreScripts: true, - selectedWsPkgsGraph, - }) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + '--ignore-scripts', + ]) await projects['project-1'].hasNot('pre-and-postinstall-scripts-example/generated-by-preinstall.js') await projects['project-1'].hasNot('pre-and-postinstall-scripts-example/generated-by-postinstall.js') await projects['project-2'].hasNot('pre-and-postinstall-scripts-example/generated-by-preinstall.js') await projects['project-2'].hasNot('pre-and-postinstall-scripts-example/generated-by-postinstall.js') - await recursive.handler(['rebuild'], { + await rebuild.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) await projects['project-1'].has('pre-and-postinstall-scripts-example/generated-by-preinstall.js') @@ -103,19 +109,23 @@ test.skip('rebuild multiple packages in correct order', async (t) => { await writeYamlFile('pnpm-workspace.yaml', { packages: ['project-1'] }) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - ignoreScripts: true, - selectedWsPkgsGraph, - }) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + '--ignore-scripts', + ]) - await recursive.handler(['rebuild'], { + await rebuild.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) const outputs1 = await import(path.resolve('output1.json')) as string[] diff --git a/packages/plugin-commands-rebuild/test/utils.ts b/packages/plugin-commands-rebuild/test/utils.ts new file mode 100644 index 0000000000..c42aada6bb --- /dev/null +++ b/packages/plugin-commands-rebuild/test/utils.ts @@ -0,0 +1,46 @@ +import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock' + +export const REGISTRY = `http://localhost:${REGISTRY_MOCK_PORT}` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: false, + ca: undefined, + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './pnpmfile.js', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + userAgent: 'pnpm', + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/plugin-commands-recursive/README.md b/packages/plugin-commands-recursive/README.md deleted file mode 100644 index b8063e9c8d..0000000000 --- a/packages/plugin-commands-recursive/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# @pnpm/plugin-commands-recursive - -> Recursive commands - -[![npm version](https://img.shields.io/npm/v/@pnpm/plugin-commands-recursive.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-recursive) - -## Installation - -```sh - add @pnpm/plugin-commands-recursive -``` - -## License - -MIT © [Zoltan Kochan](https://www.kochan.io/) diff --git a/packages/plugin-commands-recursive/package.json b/packages/plugin-commands-recursive/package.json deleted file mode 100644 index 22ad866990..0000000000 --- a/packages/plugin-commands-recursive/package.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "name": "@pnpm/plugin-commands-recursive", - "version": "0.1.10", - "description": "Recursive commands", - "main": "lib/index.js", - "typings": "lib/index.d.ts", - "files": [ - "lib" - ], - "engines": { - "node": ">=10" - }, - "scripts": { - "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", - "tsc": "rimraf lib && tsc", - "registry-mock": "registry-mock", - "test:tap": "ts-node test --type-check", - "pretest:e2e": "registry-mock prepare", - "test:e2e": "run-p -r registry-mock test:tap", - "test": "pnpm run tsc && pnpm run lint && cross-env PNPM_REGISTRY_MOCK_PORT=7777 pnpm run test:e2e", - "prepublishOnly": "pnpm run tsc" - }, - "repository": "https://github.com/pnpm/pnpm/blob/master/packages/plugin-commands-recursive", - "keywords": [ - "pnpm" - ], - "author": "Zoltan Kochan (https://www.kochan.io/)", - "license": "MIT", - "bugs": { - "url": "https://github.com/pnpm/pnpm/issues" - }, - "homepage": "https://pnpm.js.org", - "devDependencies": { - "@pnpm/lockfile-types": "workspace:1.1.0", - "@pnpm/logger": "3.1.0", - "@pnpm/plugin-commands-recursive": "link:", - "@pnpm/prepare": "workspace:0.0.0", - "@types/common-tags": "^1.8.0", - "@types/lru-cache": "^5.1.0", - "@types/mz": "^2.7.0", - "@types/ramda": "^0.26.38", - "@types/table": "^4.0.7", - "@zkochan/rimraf": "1.0.0", - "make-dir": "3.0.0", - "path-exists": "4.0.0", - "read-yaml-file": "1.1.0", - "rimraf": "3.0.0", - "strip-ansi": "6.0.0", - "write-json-file": "4.2.1", - "write-yaml-file": "3.0.1" - }, - "dependencies": { - "@pnpm/cli-utils": "workspace:0.2.5", - "@pnpm/common-cli-options-help": "workspace:0.1.2", - "@pnpm/config": "workspace:6.0.0", - "@pnpm/constants": "workspace:3.0.0", - "@pnpm/core-loggers": "workspace:4.0.0", - "@pnpm/error": "workspace:1.0.0", - "@pnpm/filter-workspace-packages": "workspace:1.0.1", - "@pnpm/find-workspace-packages": "workspace:2.0.8", - "@pnpm/lifecycle": "workspace:8.0.1", - "@pnpm/lockfile-file": "workspace:3.0.1", - "@pnpm/matcher": "workspace:1.0.0", - "@pnpm/npm-resolver": "workspace:6.0.2", - "@pnpm/outdated": "workspace:6.0.5", - "@pnpm/plugin-commands-listing": "workspace:0.1.9", - "@pnpm/plugin-commands-outdated": "workspace:0.1.9", - "@pnpm/plugin-commands-publishing": "workspace:0.1.9", - "@pnpm/plugin-commands-rebuild": "workspace:0.0.0", - "@pnpm/pnpmfile": "workspace:0.1.0", - "@pnpm/resolver-base": "workspace:6.0.0", - "@pnpm/run-npm": "workspace:1.0.0", - "@pnpm/store-connection-manager": "workspace:0.2.5", - "@pnpm/store-path": "2.1.1", - "@pnpm/types": "workspace:4.0.0", - "@pnpm/utils": "workspace:0.12.2", - "camelcase-keys": "6.1.1", - "chalk": "3.0.0", - "common-tags": "1.8.0", - "execa": "4.0.0", - "graph-sequencer": "2.0.0", - "is-subdir": "1.1.1", - "lru-cache": "5.1.1", - "mem": "6.0.1", - "mz": "2.7.0", - "p-filter": "2.1.0", - "p-limit": "2.2.1", - "pkgs-graph": "workspace:5.0.1", - "ramda": "0.26.1", - "read-ini-file": "2.0.0", - "render-help": "0.0.0", - "supi": "workspace:0.37.6", - "table": "5.4.6" - }, - "peerDependencies": { - "@pnpm/logger": "^3.1.0" - } -} diff --git a/packages/plugin-commands-recursive/src/exec.ts b/packages/plugin-commands-recursive/src/exec.ts deleted file mode 100644 index bbed357029..0000000000 --- a/packages/plugin-commands-recursive/src/exec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { WsPkgsGraph } from '@pnpm/config' -import logger from '@pnpm/logger' -import execa = require('execa') -import pLimit from 'p-limit' -import RecursiveSummary from './recursiveSummary' - -export default async ( - packageChunks: string[][], - graph: WsPkgsGraph, - args: string[], - cmd: string, - opts: { - bail: boolean, - workspaceConcurrency: number, - unsafePerm: boolean, - rawConfig: object, - }, -): Promise => { - const limitRun = pLimit(opts.workspaceConcurrency) - - const result = { - fails: [], - passes: 0, - } as RecursiveSummary - - for (const chunk of packageChunks) { - await Promise.all(chunk.map((prefix: string) => - limitRun(async () => { - try { - await execa(args[0], args.slice(1), { - cwd: prefix, - env: { - ...process.env, - PNPM_PACKAGE_NAME: graph[prefix].package.manifest.name, - }, - stdio: 'inherit', - }) - result.passes++ - } catch (err) { - logger.info(err) - - if (!opts.bail) { - result.fails.push({ - error: err, - message: err.message, - prefix, - }) - return - } - - // tslint:disable:no-string-literal - err['code'] = 'ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL' - err['prefix'] = prefix - // tslint:enable:no-string-literal - throw err - } - }, - ))) - } - - return result -} diff --git a/packages/plugin-commands-recursive/src/index.ts b/packages/plugin-commands-recursive/src/index.ts deleted file mode 100644 index c0504f2e45..0000000000 --- a/packages/plugin-commands-recursive/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as recursive from './recursive' - -export { recursive } diff --git a/packages/plugin-commands-recursive/src/recursive.ts b/packages/plugin-commands-recursive/src/recursive.ts deleted file mode 100755 index 3165ddc169..0000000000 --- a/packages/plugin-commands-recursive/src/recursive.ts +++ /dev/null @@ -1,811 +0,0 @@ -import { - createLatestSpecs, - docsUrl, - getPinnedVersion, - getSaveType, - updateToLatestSpecsFromManifest, -} from '@pnpm/cli-utils' -import { FILTERING } from '@pnpm/common-cli-options-help' -import { Config, types as allTypes, WsPkg, WsPkgsGraph } from '@pnpm/config' -import { WANTED_LOCKFILE } from '@pnpm/constants' -import { scopeLogger } from '@pnpm/core-loggers' -import PnpmError from '@pnpm/error' -import filterGraph, { PackageSelector, parsePackageSelector } from '@pnpm/filter-workspace-packages' -import findWorkspacePackages, { arrayOfWorkspacePackagesToMap } from '@pnpm/find-workspace-packages' -import logger from '@pnpm/logger' -import { rebuild, rebuildPkgs } from '@pnpm/plugin-commands-rebuild/lib/implementation' -import { requireHooks } from '@pnpm/pnpmfile' -import { createOrConnectStoreController, CreateStoreControllerOptions } from '@pnpm/store-connection-manager' -import { DependencyManifest, ImporterManifest, PackageManifest } from '@pnpm/types' -import camelcaseKeys = require('camelcase-keys') -import { oneLine } from 'common-tags' -import graphSequencer = require('graph-sequencer') -import isSubdir = require('is-subdir') -import mem = require('mem') -import fs = require('mz/fs') -import pFilter = require('p-filter') -import pLimit from 'p-limit' -import path = require('path') -import createPkgGraph, { PackageNode } from 'pkgs-graph' -import R = require('ramda') -import readIniFile = require('read-ini-file') -import renderHelp = require('render-help') -import { - addDependenciesToPackage, - install, - InstallOptions, - MutatedImporter, - mutateModules, -} from 'supi' -import exec from './exec' -import list from './list' -import outdated from './outdated' -import publish from './publish' -import RecursiveSummary, { throwOnCommandFail } from './recursiveSummary' -import run from './run' -import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies' - -const supportedRecursiveCommands = new Set([ - 'add', - 'install', - 'remove', - 'update', - 'unlink', - 'list', - 'why', - 'outdated', - 'rebuild', - 'run', - 'test', - 'exec', - 'publish', -]) - -function getCommandFullName (commandName: string) { - switch (commandName) { - case 'i': - return 'install' - case 'r': - case 'rm': - case 'un': - case 'uninstall': - return 'remove' - case 'up': - case 'upgrade': - return 'update' - case 'dislink': - return 'unlink' - case 'ls': - case 'la': - case 'll': - return 'list' - case 'rb': - return 'rebuild' - case 'run-script': - return 'run' - case 't': - case 'tst': - return 'test' - } - return commandName -} - -export const rcOptionsTypes = cliOptionsTypes - -export function cliOptionsTypes () { - return { - access: ['public', 'restricted'], - recursive: Boolean, - table: Boolean, - ...R.pick([ - 'bail', - 'link-workspace-packages', - 'reporter', - 'shared-workspace-lockfile', - 'sort', - 'tag', - 'workspace-concurrency', - ], allTypes), - } -} - -export const commandNames = ['recursive', 'multi', 'm'] - -export function help () { - return renderHelp({ - description: oneLine` - 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.`, - descriptionLists: [ - { - title: 'Commands', - - list: [ - { - name: 'install', - }, - { - name: 'add', - }, - { - name: 'update', - }, - { - description: 'Uninstall a dependency from each package', - name: 'remove ...', - }, - { - description: 'Removes links to local packages and reinstalls them from the registry.', - name: 'unlink', - }, - { - description: 'List dependencies in each package.', - name: 'list [...]', - }, - { - description: 'List packages that depend on .', - name: 'why ...', - }, - { - description: 'Check for outdated dependencies in every package.', - name: 'outdated [...]', - }, - { - description: oneLine` - 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.`, - name: 'run [-- ...]', - }, - { - description: `This runs each package's "test" script, if one was provided.`, - name: 'test [-- ...]', - }, - { - description: oneLine` - 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.`, - name: 'rebuild [[<@scope>/]...]', - }, - { - description: `Run a command in each package.`, - name: 'exec -- [args...]', - }, - { - description: 'Publishes packages to the npm registry. Only publishes a package if its version is not taken in the registry.', - name: 'publish [--tag ] [--access ]', - }, - ], - }, - { - title: 'Options', - - list: [ - { - description: 'Continues executing other tasks even if a task threw an error.', - name: '--no-bail', - }, - { - description: 'Set the maximum number of concurrency. Default is 4. For unlimited concurrency use Infinity.', - name: '--workspace-concurrency ', - }, - { - description: oneLine` - Locally available packages are linked to node_modules instead of being downloaded from the registry. - Convenient to use in a multi-package repository.`, - name: '--link-workspace-packages', - }, - { - description: 'Sort packages topologically (dependencies before dependents). Pass --no-sort to disable.', - name: '--sort', - }, - { - description: oneLine` - Creates a single ${WANTED_LOCKFILE} file in the root of the workspace. - A shared lockfile also means that all dependencies of all workspace packages will be in a single node_modules.`, - name: '--shared-workspace-lockfile', - }, - ], - }, - FILTERING, - ], - url: docsUrl('recursive'), - usages: [ - 'pnpm recursive [command] [flags] [--filter ]', - 'pnpm multi [command] [flags] [--filter ]', - 'pnpm m [command] [flags] [--filter ]' - ], - }) -} - -export async function handler ( - input: string[], - opts: RecursiveOptions & Pick & { long?: boolean, table?: boolean } & Required>, -) { - if (opts.workspaceConcurrency < 1) { - throw new PnpmError('INVALID_WORKSPACE_CONCURRENCY', 'Workspace concurrency should be at least 1') - } - - const cmd = input.shift() - if (!cmd) { - help() - return undefined - } - const cmdFullName = getCommandFullName(cmd) - if (!supportedRecursiveCommands.has(cmdFullName)) { - help() - throw new PnpmError('INVALID_RECURSIVE_COMMAND', - `"recursive ${cmdFullName}" is not a pnpm command. See "pnpm help recursive".`) - } - - const workspaceDir = opts.workspaceDir ?? process.cwd() - - const atLeastOnePackageMatched = await recursive(opts.allWsPkgs, input, { ...opts, workspaceDir }, cmdFullName, cmd) - - if (typeof atLeastOnePackageMatched === 'string') { - return atLeastOnePackageMatched - } - - if (atLeastOnePackageMatched === false) { - logger.info({ message: `No packages matched the filters in "${workspaceDir}"`, prefix: workspaceDir }) - } - return undefined -} - -type RecursiveOptions = CreateStoreControllerOptions & Pick & { - access?: 'public' | 'restricted', - argv: { - original: string[], - }, - latest?: boolean, - pending?: boolean, - workspace?: boolean, -} - -export async function recursive ( - allWsPkgs: WsPkg[], - input: string[], - opts: RecursiveOptions & { - allowNew?: boolean, - ignoredPackages?: Set, - update?: boolean, - useBetaCli?: boolean, - selectedWsPkgsGraph: WsPkgsGraph, - } & Required>, - cmdFullName: string, - cmd: string, -): Promise { - if (allWsPkgs.length === 0) { - // It might make sense to throw an exception in this case - return false - } - - const pkgs = Object.values(opts.selectedWsPkgsGraph).map((wsPkg) => wsPkg.package) - const allPackagesAreSelected = pkgs.length === allWsPkgs.length - - if (pkgs.length === 0) { - return false - } - const manifestsByPath: { [dir: string]: { manifest: ImporterManifest, writeImporterManifest: (manifest: ImporterManifest) => Promise } } = {} - for (const { dir, manifest, writeImporterManifest } of pkgs) { - manifestsByPath[dir] = { manifest, writeImporterManifest } - } - - scopeLogger.debug({ - selected: pkgs.length, - total: allWsPkgs.length, - workspacePrefix: opts.workspaceDir, - }) - - const throwOnFail = throwOnCommandFail.bind(null, `pnpm recursive ${cmd}`) - - switch (cmdFullName) { - case 'why': - case 'list': - return list(pkgs, input, cmd, opts as any) // tslint:disable-line:no-any - case 'outdated': - return outdated(pkgs, input, cmd, opts as any) // tslint:disable-line:no-any - case 'add': - if (!input || !input.length) { - throw new PnpmError('MISSING_PACKAGE_NAME', '`pnpm recursive add` requires the package name') - } - break - case 'publish': { - await publish(pkgs, opts) - return true - } - } - - const chunks = opts.sort - ? sortPackages(opts.selectedWsPkgsGraph) - : [Object.keys(opts.selectedWsPkgsGraph).sort()] - - switch (cmdFullName) { - case 'test': - throwOnFail(await run(chunks, opts.selectedWsPkgsGraph, ['test', ...input], cmd, opts as any)) // tslint:disable-line:no-any - return true - case 'run': - throwOnFail(await run(chunks, opts.selectedWsPkgsGraph, input, cmd, { ...opts, allPackagesAreSelected } as any)) // tslint:disable-line:no-any - return true - case 'update': - opts = { ...opts, update: true, allowNew: false } as any // tslint:disable-line:no-any - break - case 'exec': - throwOnFail(await exec(chunks, opts.selectedWsPkgsGraph, input, cmd, opts as any)) // tslint:disable-line:no-any - return true - } - - const store = await createOrConnectStoreController(opts) - - // It is enough to save the store.json file once, - // once all installations are done. - // That's why saveState that is passed to the install engine - // does nothing. - const saveState = store.ctrl.saveState - const storeController = { - ...store.ctrl, - saveState: async () => undefined, - } - - const workspacePackages = cmdFullName !== 'unlink' - ? arrayOfWorkspacePackagesToMap(allWsPkgs) - : {} - const installOpts = Object.assign(opts, { - ownLifecycleHooksStdio: 'pipe', - peer: opts.savePeer, - pruneLockfileImporters: (!opts.ignoredPackages || opts.ignoredPackages.size === 0) - && pkgs.length === allWsPkgs.length, - storeController, - storeDir: store.dir, - workspacePackages, - - forceHoistPattern: typeof opts.rawLocalConfig['hoist-pattern'] !== 'undefined' || typeof opts.rawLocalConfig['hoist'] !== 'undefined', - forceIndependentLeaves: typeof opts.rawLocalConfig['independent-leaves'] !== 'undefined', - forceShamefullyHoist: typeof opts.rawLocalConfig['shamefully-hoist'] !== 'undefined', - }) as InstallOptions - - const result = { - fails: [], - passes: 0, - } as RecursiveSummary - - const memReadLocalConfig = mem(readLocalConfig) - - async function getImporters () { - const importers = [] as Array<{ buildIndex: number, manifest: ImporterManifest, rootDir: string }> - await Promise.all(chunks.map((prefixes: string[], buildIndex) => { - if (opts.ignoredPackages) { - prefixes = prefixes.filter((prefix) => !opts.ignoredPackages!.has(prefix)) - } - return Promise.all( - prefixes.map(async (prefix) => { - importers.push({ - buildIndex, - manifest: manifestsByPath[prefix].manifest, - rootDir: prefix, - }) - }) - ) - })) - return importers - } - - const updateToLatest = opts.update && opts.latest - const include = opts.include - if (updateToLatest) { - delete opts.include - } - if (opts.workspace && (cmdFullName === 'install' || cmdFullName === 'add')) { - if (opts.latest) { - throw new PnpmError('BAD_OPTIONS', 'Cannot use --latest with --workspace simultaneously') - } - if (!opts.workspaceDir) { - throw new PnpmError('WORKSPACE_OPTION_OUTSIDE_WORKSPACE', '--workspace can only be used inside a workspace') - } - if (!opts.linkWorkspacePackages && !opts.saveWorkspaceProtocol) { - if (opts.rawLocalConfig['save-workspace-protocol'] === false) { - throw new PnpmError('BAD_OPTIONS', oneLine`This workspace has link-workspace-packages turned off, - so dependencies are linked from the workspace only when the workspace protocol is used. - Either set link-workspace-packages to true or don't use the --no-save-workspace-protocol option - when running add/update with the --workspace option`) - } else { - opts.saveWorkspaceProtocol = true - } - } - opts['preserveWorkspaceProtocol'] = !opts.linkWorkspacePackages - } - - if (cmdFullName !== 'rebuild') { - // For a workspace with shared lockfile - if (opts.lockfileDir && ['add', 'install', 'remove', 'update'].includes(cmdFullName)) { - if (opts.hoistPattern) { - logger.info({ message: 'Only the root workspace package is going to have hoisted dependencies in node_modules', prefix: opts.lockfileDir }) - } - let importers = await getImporters() - const isFromWorkspace = isSubdir.bind(null, opts.lockfileDir) - importers = await pFilter(importers, async ({ rootDir }: { rootDir: string }) => isFromWorkspace(await fs.realpath(rootDir))) - if (importers.length === 0) return true - const hooks = opts.ignorePnpmfile ? {} : requireHooks(opts.lockfileDir, opts) - const mutation = cmdFullName === 'remove' ? 'uninstallSome' : (input.length === 0 && !updateToLatest ? 'install' : 'installSome') - const writeImporterManifests = [] as Array<(manifest: ImporterManifest) => Promise> - const mutatedImporters = [] as MutatedImporter[] - await Promise.all(importers.map(async ({ buildIndex, rootDir }) => { - const localConfig = await memReadLocalConfig(rootDir) - const { manifest, writeImporterManifest } = manifestsByPath[rootDir] - let currentInput = [...input] - if (updateToLatest) { - if (!currentInput || !currentInput.length) { - currentInput = updateToLatestSpecsFromManifest(manifest, include) - } else { - currentInput = createLatestSpecs(currentInput, manifest) - if (!currentInput.length) { - installOpts.pruneLockfileImporters = false - return - } - } - } - if (opts.workspace) { - if (!currentInput || !currentInput.length) { - currentInput = updateToWorkspacePackagesFromManifest(manifest, opts.include, workspacePackages!) - } else { - currentInput = createWorkspaceSpecs(currentInput, workspacePackages!) - } - } - writeImporterManifests.push(writeImporterManifest) - switch (mutation) { - case 'uninstallSome': - mutatedImporters.push({ - dependencyNames: currentInput, - manifest, - mutation, - rootDir, - targetDependenciesField: getSaveType(opts), - } as MutatedImporter) - return - case 'installSome': - mutatedImporters.push({ - allowNew: cmdFullName === 'install' || cmdFullName === 'add', - dependencySelectors: currentInput, - manifest, - mutation, - peer: opts.savePeer, - pinnedVersion: getPinnedVersion({ - saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact, - savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix, - }), - rootDir, - targetDependenciesField: getSaveType(opts), - } as MutatedImporter) - return - case 'install': - mutatedImporters.push({ - buildIndex, - manifest, - mutation, - rootDir, - } as MutatedImporter) - return - } - })) - const mutatedPkgs = await mutateModules(mutatedImporters, { - ...installOpts, - hooks, - storeController: store.ctrl, - }) - if (opts.save !== false) { - await Promise.all( - mutatedPkgs - .map(({ manifest }, index) => writeImporterManifests[index](manifest)) - ) - } - return true - } - - let pkgPaths = chunks.length === 0 - ? chunks[0] - : Object.keys(opts.selectedWsPkgsGraph).sort() - - const limitInstallation = pLimit(opts.workspaceConcurrency) - await Promise.all(pkgPaths.map((rootDir: string) => - limitInstallation(async () => { - const hooks = opts.ignorePnpmfile ? {} : requireHooks(rootDir, opts) - try { - if (opts.ignoredPackages && opts.ignoredPackages.has(rootDir)) { - return - } - - const { manifest, writeImporterManifest } = manifestsByPath[rootDir] - let currentInput = [...input] - if (updateToLatest) { - if (!currentInput || !currentInput.length) { - currentInput = updateToLatestSpecsFromManifest(manifest, include) - } else { - currentInput = createLatestSpecs(currentInput, manifest) - if (!currentInput.length) return - } - } - - let action!: any // tslint:disable-line:no-any - switch (cmdFullName) { - case 'unlink': - action = (currentInput.length === 0 ? unlink : unlinkPkgs.bind(null, currentInput)) - break - case 'remove': - action = (manifest: PackageManifest, opts: any) => mutateModules([ // tslint:disable-line:no-any - { - dependencyNames: currentInput, - manifest, - mutation: 'uninstallSome', - rootDir, - }, - ], opts) - break - default: - action = currentInput.length === 0 - ? install - : (manifest: PackageManifest, opts: any) => addDependenciesToPackage(manifest, currentInput, opts) // tslint:disable-line:no-any - break - } - - const localConfig = await memReadLocalConfig(rootDir) - const newManifest = await action( - manifest, - { - ...installOpts, - ...localConfig, - bin: path.join(rootDir, 'node_modules', '.bin'), - dir: rootDir, - hooks, - ignoreScripts: true, - pinnedVersion: getPinnedVersion({ - saveExact: typeof localConfig.saveExact === 'boolean' ? localConfig.saveExact : opts.saveExact, - savePrefix: typeof localConfig.savePrefix === 'string' ? localConfig.savePrefix : opts.savePrefix, - }), - rawConfig: { - ...installOpts.rawConfig, - ...localConfig, - }, - storeController, - }, - ) - if (opts.save !== false) { - await writeImporterManifest(newManifest) - } - result.passes++ - } catch (err) { - logger.info(err) - - if (!opts.bail) { - result.fails.push({ - error: err, - message: err.message, - prefix: rootDir, - }) - return - } - - err['prefix'] = rootDir // tslint:disable-line:no-string-literal - throw err - } - }), - )) - - await saveState() - } - - if ( - cmdFullName === 'rebuild' || - !opts.lockfileOnly && !opts.ignoreScripts && ( - cmdFullName === 'add' || - cmdFullName === 'install' || - cmdFullName === 'update' || - cmdFullName === 'unlink' - ) - ) { - const action = ( - cmdFullName !== 'rebuild' || input.length === 0 - ? rebuild - : (importers: any, opts: any) => rebuildPkgs(importers, input, opts) // tslint:disable-line - ) - if (opts.lockfileDir) { - const importers = await getImporters() - await action( - importers, - { - ...installOpts, - pending: cmdFullName !== 'rebuild' || opts.pending === true, - }, - ) - return true - } - const limitRebuild = pLimit(opts.workspaceConcurrency) - for (const chunk of chunks) { - await Promise.all(chunk.map((rootDir: string) => - limitRebuild(async () => { - try { - if (opts.ignoredPackages && opts.ignoredPackages.has(rootDir)) { - return - } - const localConfig = await memReadLocalConfig(rootDir) - await action( - [ - { - buildIndex: 0, - manifest: manifestsByPath[rootDir].manifest, - rootDir, - }, - ], - { - ...installOpts, - ...localConfig, - dir: rootDir, - pending: cmdFullName !== 'rebuild' || opts.pending === true, - rawConfig: { - ...installOpts.rawConfig, - ...localConfig, - }, - }, - ) - result.passes++ - } catch (err) { - logger.info(err) - - if (!opts.bail) { - result.fails.push({ - error: err, - message: err.message, - prefix: rootDir, - }) - return - } - - err['prefix'] = rootDir // tslint:disable-line:no-string-literal - throw err - } - }), - )) - } - } - - throwOnFail(result) - - return true -} - -async function unlink (manifest: ImporterManifest, opts: any) { // tslint:disable-line:no-any - return mutateModules( - [ - { - manifest, - mutation: 'unlink', - rootDir: opts.dir, - }, - ], - opts, - ) -} - -async function unlinkPkgs (dependencyNames: string[], manifest: ImporterManifest, opts: any) { // tslint:disable-line:no-any - return mutateModules( - [ - { - dependencyNames, - manifest, - mutation: 'unlinkSome', - rootDir: opts.dir, - }, - ], - opts, - ) -} - -function sortPackages (pkgGraph: WsPkgsGraph): string[][] { - const keys = Object.keys(pkgGraph) - const setOfKeys = new Set(keys) - const graph = new Map( - keys.map((pkgPath) => [ - pkgPath, - pkgGraph[pkgPath].dependencies.filter( - /* remove cycles of length 1 (ie., package 'a' depends on 'a'). They - confuse the graph-sequencer, but can be ignored when ordering packages - topologically. - - See the following example where 'b' and 'c' depend on themselves: - - graphSequencer({graph: new Map([ - ['a', ['b', 'c']], - ['b', ['b']], - ['c', ['b', 'c']]] - ), - groups: [['a', 'b', 'c']]}) - - returns chunks: - - [['b'],['a'],['c']] - - But both 'b' and 'c' should be executed _before_ 'a', because 'a' depends on - them. It works (and is considered 'safe' if we run:) - - graphSequencer({graph: new Map([ - ['a', ['b', 'c']], - ['b', []], - ['c', ['b']]] - ), groups: [['a', 'b', 'c']]}) - - returning: - - [['b'], ['c'], ['a']] - - */ - d => d !== pkgPath && - /* remove unused dependencies that we can ignore due to a filter expression. - - Again, the graph sequencer used to behave weirdly in the following edge case: - - graphSequencer({graph: new Map([ - ['a', ['b', 'c']], - ['d', ['a']], - ['e', ['a', 'b', 'c']]] - ), - groups: [['a', 'e', 'e']]}) - - returns chunks: - - [['d'],['a'],['e']] - - But we really want 'a' to be executed first. - */ - setOfKeys.has(d))] - ) as Array<[string, string[]]>, - ) - const graphSequencerResult = graphSequencer({ - graph, - groups: [keys], - }) - return graphSequencerResult.chunks -} - -async function readLocalConfig (prefix: string) { - try { - const ini = await readIniFile(path.join(prefix, '.npmrc')) as { [key: string]: string } - const config = camelcaseKeys(ini) as ({ [key: string]: string } & { hoist?: boolean }) - if (config.shamefullyFlatten) { - config.hoistPattern = '*' - // TODO: print a warning - } - if (config.hoist === false) { - config.hoistPattern = '' - } - return config - } catch (err) { - if (err.code !== 'ENOENT') throw err - return {} - } -} diff --git a/packages/plugin-commands-recursive/test/index.ts b/packages/plugin-commands-recursive/test/index.ts deleted file mode 100644 index 2219243aba..0000000000 --- a/packages/plugin-commands-recursive/test/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// -import './updateWorkspaceDependencies.spec' - -import './exec' -import './link' -import './list' -import './misc' -import './outdated' -import './publish' -import './rebuild' -import './run' -import './test' -import './update' diff --git a/packages/plugin-commands-script-runners/package.json b/packages/plugin-commands-script-runners/package.json index 692d62a2ea..0c42099020 100644 --- a/packages/plugin-commands-script-runners/package.json +++ b/packages/plugin-commands-script-runners/package.json @@ -32,14 +32,18 @@ }, "homepage": "https://pnpm.js.org", "devDependencies": { + "@pnpm/filter-workspace-packages": "workspace:1.0.1", + "@pnpm/logger": "^3.1.0", "@pnpm/plugin-commands-script-runners": "link:", "@pnpm/prepare": "workspace:0.0.0", "@types/common-tags": "^1.8.0", "@types/mz": "^2.7.0", "@types/ramda": "^0.26.38", + "@zkochan/rimraf": "1.0.0", "execa": "4.0.0", "mz": "2.7.0", - "rimraf": "3.0.0" + "rimraf": "3.0.0", + "write-yaml-file": "3.0.1" }, "dependencies": { "@pnpm/cli-utils": "workspace:0.2.5", @@ -47,10 +51,15 @@ "@pnpm/config": "workspace:6.0.0", "@pnpm/error": "workspace:1.0.0", "@pnpm/lifecycle": "workspace:8.0.1", + "@pnpm/sort-packages": "workspace:0.0.0", "@pnpm/types": "workspace:4.0.0", "@pnpm/utils": "workspace:0.12.2", "common-tags": "1.8.0", + "p-limit": "2.2.1", "ramda": "0.26.1", "render-help": "0.0.0" + }, + "peerDependencies": { + "@pnpm/logger": "^3.1.0" } } diff --git a/packages/plugin-commands-script-runners/src/exec.ts b/packages/plugin-commands-script-runners/src/exec.ts new file mode 100644 index 0000000000..c3e19f3e36 --- /dev/null +++ b/packages/plugin-commands-script-runners/src/exec.ts @@ -0,0 +1,86 @@ +import { RecursiveSummary, throwOnCommandFail } from '@pnpm/cli-utils' +import { Config, types, WsPkgsGraph } from '@pnpm/config' +import logger from '@pnpm/logger' +import sortPackages from '@pnpm/sort-packages' +import execa = require('execa') +import pLimit from 'p-limit' +import R = require('ramda') +import renderHelp = require('render-help') + +export const commandNames = ['exec'] + +export const rcOptionsTypes = cliOptionsTypes + +export function cliOptionsTypes () { + return R.pick([ + 'bail', + 'unsafe-perm', + 'workspace-concurrency', + ], types) +} + +export function help () { + return renderHelp({ + description: 'Run a command in each package.', + usages: ['-r exec -- [args...]'], + }) +} + +export async function handler ( + args: string[], + opts: Required> & { + bail?: boolean, + unsafePerm?: boolean, + rawConfig: object, + sort?: boolean, + workspaceConcurrency?: number, + }, +) { + const limitRun = pLimit(opts.workspaceConcurrency ?? 4) + + const result = { + fails: [], + passes: 0, + } as RecursiveSummary + + const chunks = opts.sort + ? sortPackages(opts.selectedWsPkgsGraph) + : [Object.keys(opts.selectedWsPkgsGraph).sort()] + + for (const chunk of chunks) { + await Promise.all(chunk.map((prefix: string) => + limitRun(async () => { + try { + await execa(args[0], args.slice(1), { + cwd: prefix, + env: { + ...process.env, + PNPM_PACKAGE_NAME: opts.selectedWsPkgsGraph[prefix].package.manifest.name, + }, + stdio: 'inherit', + }) + result.passes++ + } catch (err) { + logger.info(err) + + if (!opts.bail) { + result.fails.push({ + error: err, + message: err.message, + prefix, + }) + return + } + + // tslint:disable:no-string-literal + err['code'] = 'ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL' + err['prefix'] = prefix + // tslint:enable:no-string-literal + throw err + } + }, + ))) + } + + throwOnCommandFail('pnpm recursive exec', result) +} diff --git a/packages/plugin-commands-script-runners/src/index.ts b/packages/plugin-commands-script-runners/src/index.ts index 55eee22f2f..f43f93938a 100644 --- a/packages/plugin-commands-script-runners/src/index.ts +++ b/packages/plugin-commands-script-runners/src/index.ts @@ -1,7 +1,8 @@ +import * as exec from './exec' import * as restart from './restart' import * as run from './run' import * as start from './start' import * as stop from './stop' import * as test from './test' -export { restart, run, start, stop, test } +export { exec, restart, run, start, stop, test } diff --git a/packages/plugin-commands-script-runners/src/restart.ts b/packages/plugin-commands-script-runners/src/restart.ts index 43b5bf6d17..aef07fbff4 100644 --- a/packages/plugin-commands-script-runners/src/restart.ts +++ b/packages/plugin-commands-script-runners/src/restart.ts @@ -1,5 +1,10 @@ import renderHelp = require('render-help') -import { handler as run, IF_PRESENT_OPTION, IF_PRESENT_OPTION_HELP } from './run' +import { + handler as run, + IF_PRESENT_OPTION, + IF_PRESENT_OPTION_HELP, + RunOpts, +} from './run' import { handler as start } from './start' import { handler as stop } from './stop' @@ -31,11 +36,7 @@ export function help () { export async function handler ( args: string[], - opts: { - extraBinPaths: string[], - dir: string, - rawConfig: object, - }, + opts: RunOpts, ) { await stop(args, opts) await run(['restart', ...args], opts) diff --git a/packages/plugin-commands-script-runners/src/run.ts b/packages/plugin-commands-script-runners/src/run.ts index b9c56c4238..32523119e9 100644 --- a/packages/plugin-commands-script-runners/src/run.ts +++ b/packages/plugin-commands-script-runners/src/run.ts @@ -1,6 +1,6 @@ import { docsUrl, readImporterManifestOnly } from '@pnpm/cli-utils' import { FILTERING } from '@pnpm/common-cli-options-help' -import { types as allTypes } from '@pnpm/config' +import { Config, types as allTypes } from '@pnpm/config' import PnpmError from '@pnpm/error' import runLifecycleHooks from '@pnpm/lifecycle' import { ImporterManifest } from '@pnpm/types' @@ -8,6 +8,7 @@ import { realNodeModulesDir } from '@pnpm/utils' import { oneLine } from 'common-tags' import R = require('ramda') import renderHelp = require('render-help') +import runRecursive, { RecursiveRunOpts } from './runRecursive' export const IF_PRESENT_OPTION = { 'if-present': Boolean, @@ -54,16 +55,25 @@ export function help () { }) } +export type RunOpts = Omit & { + ifPresent?: boolean, + recursive?: boolean, +} & Pick & ( + { recursive?: false } & + Partial> + | + { recursive: true } & + Required> +) + export async function handler ( args: string[], - opts: { - engineStrict?: boolean, - extraBinPaths: string[], - dir: string, - ifPresent?: boolean, - rawConfig: object, - }, + opts: RunOpts, ) { + if (opts.recursive) { + await runRecursive(args, opts) + return + } const dir = opts.dir const manifest = await readImporterManifestOnly(dir, opts) const scriptName = args[0] diff --git a/packages/plugin-commands-recursive/src/run.ts b/packages/plugin-commands-script-runners/src/runRecursive.ts similarity index 72% rename from packages/plugin-commands-recursive/src/run.ts rename to packages/plugin-commands-script-runners/src/runRecursive.ts index 26efab7a3e..66834db598 100644 --- a/packages/plugin-commands-recursive/src/run.ts +++ b/packages/plugin-commands-script-runners/src/runRecursive.ts @@ -1,36 +1,35 @@ -import { WsPkgsGraph } from '@pnpm/config' +import { RecursiveSummary, throwOnCommandFail } from '@pnpm/cli-utils' +import { Config, WsPkgsGraph } from '@pnpm/config' import PnpmError from '@pnpm/error' import runLifecycleHooks from '@pnpm/lifecycle' import logger from '@pnpm/logger' +import sortPackages from '@pnpm/sort-packages' import { PackageManifest } from '@pnpm/types' import { realNodeModulesDir } from '@pnpm/utils' import pLimit from 'p-limit' -import RecursiveSummary from './recursiveSummary' -export default async ( - packageChunks: string[][], - graph: WsPkgsGraph, +export type RecursiveRunOpts = Pick & Required> & +Partial> + +export default async ( args: string[], - cmd: string, - opts: { - bail: boolean, - extraBinPaths: string[], - workspaceConcurrency: number, - unsafePerm: boolean, - rawConfig: object, - workspaceDir: string, - allPackagesAreSelected: boolean, - }, + opts: RecursiveRunOpts, ) => { const scriptName = args[0] let hasCommand = 0 + const packageChunks = opts.sort + ? sortPackages(opts.selectedWsPkgsGraph) + : [Object.keys(opts.selectedWsPkgsGraph).sort()] const result = { fails: [], passes: 0, } as RecursiveSummary - const limitRun = pLimit(opts.workspaceConcurrency) + const limitRun = pLimit(opts.workspaceConcurrency ?? 4) const stdio = ( opts.workspaceConcurrency === 1 || packageChunks.length === 1 && packageChunks[0].length === 1 @@ -40,7 +39,7 @@ export default async ( for (const chunk of packageChunks) { await Promise.all(chunk.map((prefix: string) => limitRun(async () => { - const pkg = graph[prefix] as {package: {dir: string, manifest: PackageManifest}} + const pkg = opts.selectedWsPkgsGraph[prefix] as {package: {dir: string, manifest: PackageManifest}} if (!pkg.package.manifest.scripts || !pkg.package.manifest.scripts[scriptName]) { return } @@ -86,7 +85,8 @@ export default async ( } if (scriptName !== 'test' && !hasCommand) { - if (opts.allPackagesAreSelected) { + const allPackagesAreSelected = Object.keys(opts.selectedWsPkgsGraph).length === opts.allWsPkgs.length + if (allPackagesAreSelected) { throw new PnpmError('RECURSIVE_RUN_NO_SCRIPT', `None of the packages has a "${scriptName}" script`) } else { logger.info({ @@ -96,5 +96,5 @@ export default async ( } } - return result + throwOnCommandFail('pnpm recursive run', result) } diff --git a/packages/plugin-commands-script-runners/src/start.ts b/packages/plugin-commands-script-runners/src/start.ts index f625b24bd7..2ea3630897 100644 --- a/packages/plugin-commands-script-runners/src/start.ts +++ b/packages/plugin-commands-script-runners/src/start.ts @@ -1,7 +1,12 @@ import { docsUrl } from '@pnpm/cli-utils' import { oneLine } from 'common-tags' import renderHelp = require('render-help') -import { handler as run, IF_PRESENT_OPTION, IF_PRESENT_OPTION_HELP } from './run' +import { + handler as run, + IF_PRESENT_OPTION, + IF_PRESENT_OPTION_HELP, + RunOpts, +} from './run' export function rcOptionsTypes () { return {} @@ -34,11 +39,7 @@ export function help () { export async function handler ( args: string[], - opts: { - extraBinPaths: string[], - dir: string, - rawConfig: object, - }, + opts: RunOpts, ) { return run(['start', ...args], opts) } diff --git a/packages/plugin-commands-script-runners/src/stop.ts b/packages/plugin-commands-script-runners/src/stop.ts index d383baac19..fd59159b0b 100644 --- a/packages/plugin-commands-script-runners/src/stop.ts +++ b/packages/plugin-commands-script-runners/src/stop.ts @@ -1,6 +1,11 @@ import { docsUrl } from '@pnpm/cli-utils' import renderHelp = require('render-help') -import { handler as run, IF_PRESENT_OPTION, IF_PRESENT_OPTION_HELP } from './run' +import { + handler as run, + IF_PRESENT_OPTION, + IF_PRESENT_OPTION_HELP, + RunOpts, +} from './run' export function rcOptionsTypes () { return {} @@ -31,11 +36,7 @@ export function help () { export async function handler ( args: string[], - opts: { - extraBinPaths: string[], - dir: string, - rawConfig: object, - }, + opts: RunOpts, ) { return run(['stop', ...args], opts) } diff --git a/packages/plugin-commands-script-runners/src/test.ts b/packages/plugin-commands-script-runners/src/test.ts index 59684cdd41..702428022c 100644 --- a/packages/plugin-commands-script-runners/src/test.ts +++ b/packages/plugin-commands-script-runners/src/test.ts @@ -4,7 +4,7 @@ import { types as allTypes } from '@pnpm/config' import { oneLine } from 'common-tags' import R = require('ramda') import renderHelp = require('render-help') -import { handler as run } from './run' +import { handler as run, RunOpts } from './run' export function rcOptionsTypes () { return {} @@ -46,11 +46,7 @@ export function help () { export async function handler ( args: string[], - opts: { - extraBinPaths: string[], - dir: string, - rawConfig: object, - }, + opts: RunOpts, ) { return run(['test', ...args], opts) } diff --git a/packages/plugin-commands-recursive/test/exec.ts b/packages/plugin-commands-script-runners/test/exec.ts similarity index 77% rename from packages/plugin-commands-recursive/test/exec.ts rename to packages/plugin-commands-script-runners/test/exec.ts index bfd43238e6..d923476521 100644 --- a/packages/plugin-commands-recursive/test/exec.ts +++ b/packages/plugin-commands-script-runners/test/exec.ts @@ -1,11 +1,13 @@ import PnpmError from '@pnpm/error' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { readWsPkgs } from '@pnpm/filter-workspace-packages' +import { exec } from '@pnpm/plugin-commands-script-runners' import { preparePackages } from '@pnpm/prepare' import rimraf = require('@zkochan/rimraf') +import execa = require('execa') import fs = require('mz/fs') import path = require('path') import test = require('tape') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS, REGISTRY } from './utils' test('pnpm recursive exec', async (t) => { const projects = preparePackages(t, [ @@ -48,17 +50,17 @@ test('pnpm recursive exec', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + const { selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await exec.handler(['npm', 'run', 'build'], { ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['exec', 'npm', 'run', 'build'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), selectedWsPkgsGraph, }) @@ -80,10 +82,8 @@ test('pnpm recursive exec sets PNPM_PACKAGE_NAME env var', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['exec', 'node', '-e', `require('fs').writeFileSync('pkgname', process.env.PNPM_PACKAGE_NAME, 'utf8')`], { + await exec.handler(['node', '-e', `require('fs').writeFileSync('pkgname', process.env.PNPM_PACKAGE_NAME, 'utf8')`], { ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), selectedWsPkgsGraph, }) @@ -130,21 +130,21 @@ test('testing the bail config with "pnpm recursive exec"', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) + const { selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) let failed = false let err1!: PnpmError try { - await recursive.handler(['exec', 'npm', 'run', 'build', '--no-bail'], { + await exec.handler(['npm', 'run', 'build', '--no-bail'], { ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), selectedWsPkgsGraph, }) } catch (_err) { @@ -162,10 +162,8 @@ test('testing the bail config with "pnpm recursive exec"', async (t) => { failed = false let err2!: PnpmError try { - await recursive.handler(['exec', 'npm', 'run', 'build'], { + await exec.handler(['npm', 'run', 'build'], { ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), selectedWsPkgsGraph, }) } catch (_err) { @@ -205,18 +203,17 @@ test('pnpm recursive exec --no-sort', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { + const { selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await exec.handler(['npm', 'run', 'build'], { ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - linkWorkspacePackages: true, - selectedWsPkgsGraph, - }) - await recursive.handler(['exec', 'npm', 'run', 'build'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), selectedWsPkgsGraph, sort: false, workspaceConcurrency: 1, diff --git a/packages/plugin-commands-script-runners/test/index.ts b/packages/plugin-commands-script-runners/test/index.ts index 48dfbcc2e0..83693a4694 100644 --- a/packages/plugin-commands-script-runners/test/index.ts +++ b/packages/plugin-commands-script-runners/test/index.ts @@ -11,6 +11,9 @@ import execa = require('execa') import fs = require('mz/fs') import path = require('path') import test = require('tape') +import './exec' +import './runRecursive' +import './testRecursive' test('pnpm run: returns correct exit code', async (t) => { prepare(t, { diff --git a/packages/plugin-commands-recursive/test/run.ts b/packages/plugin-commands-script-runners/test/runRecursive.ts similarity index 79% rename from packages/plugin-commands-recursive/test/run.ts rename to packages/plugin-commands-script-runners/test/runRecursive.ts index 47e6daf11c..346a07a05e 100644 --- a/packages/plugin-commands-recursive/test/run.ts +++ b/packages/plugin-commands-script-runners/test/runRecursive.ts @@ -1,13 +1,14 @@ import PnpmError from '@pnpm/error' -import { filterPkgsBySelectorObjects } from '@pnpm/filter-workspace-packages' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { filterPkgsBySelectorObjects, readWsPkgs } from '@pnpm/filter-workspace-packages' +import { run } from '@pnpm/plugin-commands-script-runners' import { preparePackages } from '@pnpm/prepare' import rimraf = require('@zkochan/rimraf') +import execa = require('execa') import fs = require('mz/fs') import path = require('path') import test = require('tape') import writeYamlFile = require('write-yaml-file') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS, REGISTRY } from './utils' test('pnpm recursive run', async (t) => { const projects = preparePackages(t, [ @@ -57,17 +58,21 @@ test('pnpm recursive run', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['run', 'build'], { + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await run.handler(['build'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) const outputs1 = await import(path.resolve('output1.json')) as string[] @@ -105,17 +110,21 @@ test('pnpm recursive run concurrently', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['run', 'build'], { + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await run.handler(['build'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) const outputs1 = await import(path.resolve('output1.json')) as number[] @@ -154,20 +163,24 @@ test('`pnpm recursive run` fails when run without filters and no package has the ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) let err!: PnpmError try { - await recursive.handler(['run', 'this-command-does-not-exist'], { + await run.handler(['this-command-does-not-exist'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) } catch (_err) { err = _err @@ -206,10 +219,12 @@ test('`pnpm recursive run` fails when run with a filter that includes all packag let err!: PnpmError try { - await recursive.handler(['run', 'this-command-does-not-exist'], { + await run.handler(['this-command-does-not-exist'], { ...DEFAULT_OPTS, ...await readWsPkgs(process.cwd(), [{ namePattern: '*' }]), dir: process.cwd(), + recursive: true, + workspaceDir: process.cwd(), }) } catch (_err) { err = _err @@ -246,22 +261,26 @@ test('`pnpm recursive run` succeeds when run against a subset of packages and no }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['run', 'this-command-does-not-exist'], { + const { allWsPkgs } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await run.handler(['this-command-does-not-exist'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph: await filterPkgsBySelectorObjects( allWsPkgs, [{ namePattern: 'project-1' }], { workspaceDir: process.cwd() }, ), + workspaceDir: process.cwd(), }) t.end() }) @@ -306,20 +325,24 @@ test('testing the bail config with "pnpm recursive run"', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) let err1!: PnpmError try { - await recursive.handler(['run', 'build', '--no-bail'], { + await run.handler(['build', '--no-bail'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) } catch (_err) { err1 = _err @@ -333,11 +356,13 @@ test('testing the bail config with "pnpm recursive run"', async (t) => { let err2!: PnpmError try { - await recursive.handler(['run', 'build'], { + await run.handler(['build'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) } catch (_err) { err2 = _err @@ -376,22 +401,26 @@ test('pnpm recursive run with filtering', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['run', 'build'], { + const { allWsPkgs } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await run.handler(['build'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph: await filterPkgsBySelectorObjects( allWsPkgs, [{ namePattern: 'project-1' }], { workspaceDir: process.cwd() }, ), + workspaceDir: process.cwd(), }) const outputs = await import(path.resolve('output.json')) as string[] @@ -415,19 +444,23 @@ test('`pnpm recursive run` should always trust the scripts', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) + const { allWsPkgs } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) process.env['npm_config_unsafe_perm'] = 'false' - await recursive.handler(['run', 'build'], { + await run.handler(['build'], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, + workspaceDir: process.cwd(), ...await readWsPkgs(process.cwd(), []), }) delete process.env['npm_config_unsafe_perm'] diff --git a/packages/plugin-commands-recursive/test/test.ts b/packages/plugin-commands-script-runners/test/testRecursive.ts similarity index 76% rename from packages/plugin-commands-recursive/test/test.ts rename to packages/plugin-commands-script-runners/test/testRecursive.ts index 82104241b7..5ac3d825e9 100644 --- a/packages/plugin-commands-recursive/test/test.ts +++ b/packages/plugin-commands-script-runners/test/testRecursive.ts @@ -1,9 +1,10 @@ -import { filterPkgsBySelectorObjects } from '@pnpm/filter-workspace-packages' -import { recursive } from '@pnpm/plugin-commands-recursive' +import { filterPkgsBySelectorObjects, readWsPkgs } from '@pnpm/filter-workspace-packages' +import { test as testCommand } from '@pnpm/plugin-commands-script-runners' import { preparePackages } from '@pnpm/prepare' +import execa = require('execa') import path = require('path') import test = require('tape') -import { DEFAULT_OPTS, readWsPkgs } from './utils' +import { DEFAULT_OPTS, REGISTRY } from './utils' test('pnpm recursive test', async (t) => { const projects = preparePackages(t, [ @@ -51,17 +52,21 @@ test('pnpm recursive test', async (t) => { ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['test'], { + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await testCommand.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) const outputs1 = await import(path.resolve('output1.json')) as string[] @@ -103,18 +108,22 @@ test('`pnpm recursive test` does not fail if none of the packaegs has a test com ]) const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) - await recursive.handler(['test'], { + await testCommand.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph, + workspaceDir: process.cwd(), }) t.pass('command did not fail') @@ -148,22 +157,26 @@ test('pnpm recursive test with filtering', async (t) => { }, ]) - const { allWsPkgs, selectedWsPkgsGraph } = await readWsPkgs(process.cwd(), []) - await recursive.handler(['install'], { - ...DEFAULT_OPTS, - allWsPkgs, - dir: process.cwd(), - selectedWsPkgsGraph, - }) - await recursive.handler(['test'], { + const { allWsPkgs } = await readWsPkgs(process.cwd(), []) + await execa('pnpm', [ + 'install', + '-r', + '--registry', + REGISTRY, + '--store-dir', + path.resolve(DEFAULT_OPTS.storeDir), + ]) + await testCommand.handler([], { ...DEFAULT_OPTS, allWsPkgs, dir: process.cwd(), + recursive: true, selectedWsPkgsGraph: await filterPkgsBySelectorObjects( allWsPkgs, [{ namePattern: 'project-1' }], { workspaceDir: process.cwd() }, ), + workspaceDir: process.cwd(), }) const outputs = await import(path.resolve('output.json')) as string[] diff --git a/packages/plugin-commands-script-runners/test/utils.ts b/packages/plugin-commands-script-runners/test/utils.ts new file mode 100644 index 0000000000..5aef81ac2a --- /dev/null +++ b/packages/plugin-commands-script-runners/test/utils.ts @@ -0,0 +1,44 @@ +export const REGISTRY = `https://registry.npmjs.org/` + +export const DEFAULT_OPTS = { + alwaysAuth: false, + argv: { + original: [], + }, + bail: false, + ca: undefined, + cert: undefined, + cliOptions: {}, + fetchRetries: 2, + fetchRetryFactor: 90, + fetchRetryMaxtimeout: 90, + fetchRetryMintimeout: 10, + filter: [] as string[], + httpsProxy: undefined, + include: { + dependencies: true, + devDependencies: true, + optionalDependencies: true, + }, + key: undefined, + linkWorkspacePackages: true, + localAddress: undefined, + lock: false, + lockStaleDuration: 90, + networkConcurrency: 16, + offline: false, + pending: false, + pnpmfile: './pnpmfile.js', + proxy: undefined, + rawConfig: { registry: REGISTRY }, + rawLocalConfig: {}, + registries: { default: REGISTRY }, + registry: REGISTRY, + sort: true, + storeDir: '../store', + strictSsl: false, + userAgent: 'pnpm', + useRunningStoreServer: false, + useStoreServer: false, + workspaceConcurrency: 4, +} diff --git a/packages/pnpm/package.json b/packages/pnpm/package.json index ab0a9c5079..3fbb1b39b6 100644 --- a/packages/pnpm/package.json +++ b/packages/pnpm/package.json @@ -22,6 +22,7 @@ ], "dependencies": { "@pnpm/cli-utils": "workspace:0.2.5", + "@pnpm/common-cli-options-help": "workspace:0.1.2", "@pnpm/config": "workspace:6.0.0", "@pnpm/core-loggers": "workspace:4.0.0", "@pnpm/default-reporter": "workspace:5.0.5", @@ -37,7 +38,6 @@ "@pnpm/plugin-commands-outdated": "workspace:0.1.9", "@pnpm/plugin-commands-publishing": "workspace:0.1.9", "@pnpm/plugin-commands-rebuild": "workspace:0.0.0", - "@pnpm/plugin-commands-recursive": "workspace:0.1.10", "@pnpm/plugin-commands-script-runners": "workspace:0.1.9", "@pnpm/plugin-commands-server": "workspace:0.0.0", "@pnpm/plugin-commands-store": "workspace:0.0.0", diff --git a/packages/pnpm/src/cmd/index.ts b/packages/pnpm/src/cmd/index.ts index 5d8e0adc79..43020977eb 100644 --- a/packages/pnpm/src/cmd/index.ts +++ b/packages/pnpm/src/cmd/index.ts @@ -5,8 +5,8 @@ import { list, why } from '@pnpm/plugin-commands-listing' import { outdated } from '@pnpm/plugin-commands-outdated' import { pack, publish } from '@pnpm/plugin-commands-publishing' import { rebuild } from '@pnpm/plugin-commands-rebuild' -import { recursive } from '@pnpm/plugin-commands-recursive' import { + exec, restart, run, start, @@ -18,6 +18,7 @@ import { store } from '@pnpm/plugin-commands-store' import { PnpmOptions } from '../types' import createHelp from './help' import * as installTest from './installTest' +import * as recursive from './recursive' import * as root from './root' export type Command = ( @@ -35,6 +36,7 @@ const commands: Array<{ }> = [ add, audit, + exec, importCommand, install, installTest, diff --git a/packages/pnpm/src/cmd/installTest.ts b/packages/pnpm/src/cmd/installTest.ts index 5d81cca78f..6faba575bb 100644 --- a/packages/pnpm/src/cmd/installTest.ts +++ b/packages/pnpm/src/cmd/installTest.ts @@ -20,6 +20,6 @@ export function help () { } export async function handler (input: string[], opts: PnpmOptions) { - await install.handler(input, opts) + await install.handler(input, opts, 'install') await test.handler(input, opts) } diff --git a/packages/pnpm/src/cmd/recursive.ts b/packages/pnpm/src/cmd/recursive.ts new file mode 100644 index 0000000000..7d6b9cd96f --- /dev/null +++ b/packages/pnpm/src/cmd/recursive.ts @@ -0,0 +1,123 @@ +import { docsUrl } from '@pnpm/cli-utils' +import { FILTERING } from '@pnpm/common-cli-options-help' +import { WANTED_LOCKFILE } from '@pnpm/constants' +import { oneLine } from 'common-tags' +import renderHelp = require('render-help') + +export const rcOptionsTypes = () => ({}) +export const cliOptionsTypes = () => ({}) + +export const commandNames = ['recursive', 'multi', 'm'] + +export function help () { + return renderHelp({ + description: oneLine` + 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.`, + descriptionLists: [ + { + title: 'Commands', + + list: [ + { + name: 'install', + }, + { + name: 'add', + }, + { + name: 'update', + }, + { + description: 'Uninstall a dependency from each package', + name: 'remove ...', + }, + { + description: 'Removes links to local packages and reinstalls them from the registry.', + name: 'unlink', + }, + { + description: 'List dependencies in each package.', + name: 'list [...]', + }, + { + description: 'List packages that depend on .', + name: 'why ...', + }, + { + description: 'Check for outdated dependencies in every package.', + name: 'outdated [...]', + }, + { + description: oneLine` + 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.`, + name: 'run [-- ...]', + }, + { + description: `This runs each package's "test" script, if one was provided.`, + name: 'test [-- ...]', + }, + { + description: oneLine` + 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.`, + name: 'rebuild [[<@scope>/]...]', + }, + { + description: `Run a command in each package.`, + name: 'exec -- [args...]', + }, + { + description: 'Publishes packages to the npm registry. Only publishes a package if its version is not taken in the registry.', + name: 'publish [--tag ] [--access ]', + }, + ], + }, + { + title: 'Options', + + list: [ + { + description: 'Continues executing other tasks even if a task threw an error.', + name: '--no-bail', + }, + { + description: 'Set the maximum number of concurrency. Default is 4. For unlimited concurrency use Infinity.', + name: '--workspace-concurrency ', + }, + { + description: oneLine` + Locally available packages are linked to node_modules instead of being downloaded from the registry. + Convenient to use in a multi-package repository.`, + name: '--link-workspace-packages', + }, + { + description: 'Sort packages topologically (dependencies before dependents). Pass --no-sort to disable.', + name: '--sort', + }, + { + description: oneLine` + Creates a single ${WANTED_LOCKFILE} file in the root of the workspace. + A shared lockfile also means that all dependencies of all workspace packages will be in a single node_modules.`, + name: '--shared-workspace-lockfile', + }, + ], + }, + FILTERING, + ], + url: docsUrl('recursive'), + usages: [ + 'pnpm recursive [command] [flags] [--filter ]', + 'pnpm multi [command] [flags] [--filter ]', + 'pnpm m [command] [flags] [--filter ]' + ], + }) +} + +export function handler () { + console.log(help()) + process.exit(1) +} diff --git a/packages/pnpm/src/main.ts b/packages/pnpm/src/main.ts index 0688c28ce9..c822c0f0aa 100644 --- a/packages/pnpm/src/main.ts +++ b/packages/pnpm/src/main.ts @@ -130,21 +130,6 @@ export default async function run (inputArgv: string[]) { process.env['FORCE_COLOR'] = '0' } - if ( - cmd === 'add' && - workspaceDir === dir && - !config.ignoreWorkspaceRootCheck - ) { - // Reporting is not initialized at this point, so just printing the error - console.error(`${chalk.bgRed.black('\u2009ERROR\u2009')} ${ - chalk.red('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).')}`) - console.log(`For help, run: pnpm help ${cmd}`) - process.exit(1) - return - } - const selfUpdate = config.global && (cmd === 'add' || cmd === 'update') && argv.remain.includes(packageManager.name) // Don't check for updates @@ -172,7 +157,7 @@ export default async function run (inputArgv: string[]) { await pnpmCmds.server(['stop'], config as any) // tslint:disable-line:no-any } - if (cmd === 'recursive') { + if (cliConf['recursive']) { const wsDir = workspaceDir ?? process.cwd() const allWsPkgs = await findWorkspacePackages(wsDir, config) @@ -186,6 +171,7 @@ export default async function run (inputArgv: string[]) { workspaceDir: wsDir, }) config.allWsPkgs = allWsPkgs + config.workspaceDir = wsDir } // NOTE: we defer the next stage, otherwise reporter might not catch all the logs @@ -198,7 +184,7 @@ export default async function run (inputArgv: string[]) { }) } - if (cmd !== 'recursive') { + if (!cliConf['recursive']) { scopeLogger.debug(workspaceDir ? { selected: 1, workspacePrefix: workspaceDir } : { selected: 1 }) diff --git a/packages/pnpm/test/install/misc.ts b/packages/pnpm/test/install/misc.ts index 96ae1219ba..87a922a38e 100644 --- a/packages/pnpm/test/install/misc.ts +++ b/packages/pnpm/test/install/misc.ts @@ -323,7 +323,7 @@ test('`pnpm recursive add` should fail if no package name was provided', (t: tap const { status, stdout } = execPnpmSync('recursive', 'add') t.equal(status, 1) - t.ok(stdout.toString().includes('`pnpm recursive add` requires the package name')) + t.ok(stdout.toString().includes('`pnpm add` requires the package name')) t.end() }) diff --git a/packages/pnpm/test/recursive/misc.ts b/packages/pnpm/test/recursive/misc.ts index de7322366a..6ecb8e6c1c 100644 --- a/packages/pnpm/test/recursive/misc.ts +++ b/packages/pnpm/test/recursive/misc.ts @@ -371,12 +371,12 @@ test('adding new dependency in the root should fail if --ignore-workspace-root-c await fs.writeFile('pnpm-workspace.yaml', '', 'utf8') { - const { status, stderr } = execPnpmSync('add', 'is-positive') + const { status, stdout } = execPnpmSync('add', 'is-positive') t.equal(status, 1) t.ok( - stderr.toString().includes( + stdout.toString().includes( '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).' diff --git a/packages/sort-packages/README.md b/packages/sort-packages/README.md new file mode 100644 index 0000000000..7c33ca2195 --- /dev/null +++ b/packages/sort-packages/README.md @@ -0,0 +1,15 @@ +# @pnpm/sort-packages + +> Sort packages + +[![npm version](https://img.shields.io/npm/v/@pnpm/sort-packages.svg)](https://www.npmjs.com/package/@pnpm/plugin-commands-recursive) + +## Installation + +```sh + add @pnpm/sort-packages +``` + +## License + +MIT © [Zoltan Kochan](https://www.kochan.io/) diff --git a/packages/sort-packages/package.json b/packages/sort-packages/package.json new file mode 100644 index 0000000000..d88dbbc904 --- /dev/null +++ b/packages/sort-packages/package.json @@ -0,0 +1,37 @@ +{ + "name": "@pnpm/sort-packages", + "version": "0.0.0", + "description": "Sort packages", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "files": [ + "lib" + ], + "engines": { + "node": ">=10" + }, + "scripts": { + "lint": "tslint -c tslint.json src/**/*.ts test/**/*.ts", + "tsc": "rimraf lib && tsc", + "test": "pnpm run tsc && pnpm run lint", + "prepublishOnly": "pnpm run tsc" + }, + "repository": "https://github.com/pnpm/pnpm/blob/master/packages/sort-packages", + "keywords": [ + "pnpm" + ], + "author": "Zoltan Kochan (https://www.kochan.io/)", + "license": "MIT", + "bugs": { + "url": "https://github.com/pnpm/pnpm/issues" + }, + "homepage": "https://pnpm.js.org", + "devDependencies": { + "@pnpm/sort-packages": "link:", + "rimraf": "3.0.0" + }, + "dependencies": { + "@pnpm/config": "workspace:6.0.0", + "graph-sequencer": "2.0.0" + } +} diff --git a/packages/sort-packages/src/index.ts b/packages/sort-packages/src/index.ts new file mode 100644 index 0000000000..9e86e24b18 --- /dev/null +++ b/packages/sort-packages/src/index.ts @@ -0,0 +1,68 @@ +import { WsPkgsGraph } from '@pnpm/config' +import graphSequencer = require('graph-sequencer') + +export default function sortPackages (pkgGraph: WsPkgsGraph): string[][] { + const keys = Object.keys(pkgGraph) + const setOfKeys = new Set(keys) + const graph = new Map( + keys.map((pkgPath) => [ + pkgPath, + pkgGraph[pkgPath].dependencies.filter( + /* remove cycles of length 1 (ie., package 'a' depends on 'a'). They + confuse the graph-sequencer, but can be ignored when ordering packages + topologically. + + See the following example where 'b' and 'c' depend on themselves: + + graphSequencer({graph: new Map([ + ['a', ['b', 'c']], + ['b', ['b']], + ['c', ['b', 'c']]] + ), + groups: [['a', 'b', 'c']]}) + + returns chunks: + + [['b'],['a'],['c']] + + But both 'b' and 'c' should be executed _before_ 'a', because 'a' depends on + them. It works (and is considered 'safe' if we run:) + + graphSequencer({graph: new Map([ + ['a', ['b', 'c']], + ['b', []], + ['c', ['b']]] + ), groups: [['a', 'b', 'c']]}) + + returning: + + [['b'], ['c'], ['a']] + + */ + d => d !== pkgPath && + /* remove unused dependencies that we can ignore due to a filter expression. + + Again, the graph sequencer used to behave weirdly in the following edge case: + + graphSequencer({graph: new Map([ + ['a', ['b', 'c']], + ['d', ['a']], + ['e', ['a', 'b', 'c']]] + ), + groups: [['a', 'e', 'e']]}) + + returns chunks: + + [['d'],['a'],['e']] + + But we really want 'a' to be executed first. + */ + setOfKeys.has(d))] + ) as Array<[string, string[]]>, + ) + const graphSequencerResult = graphSequencer({ + graph, + groups: [keys], + }) + return graphSequencerResult.chunks +} diff --git a/packages/plugin-commands-recursive/tsconfig.json b/packages/sort-packages/tsconfig.json similarity index 100% rename from packages/plugin-commands-recursive/tsconfig.json rename to packages/sort-packages/tsconfig.json diff --git a/packages/plugin-commands-recursive/tslint.json b/packages/sort-packages/tslint.json similarity index 100% rename from packages/plugin-commands-recursive/tslint.json rename to packages/sort-packages/tslint.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 105e2210e3..197085403d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,7 @@ importers: dependencies: '@pnpm/config': 'link:../config' '@pnpm/default-resolver': 'link:../default-resolver' + '@pnpm/error': 'link:../error' '@pnpm/package-is-installable': 'link:../package-is-installable' '@pnpm/read-importer-manifest': 'link:../read-importer-manifest' '@pnpm/utils': 'link:../utils' @@ -107,6 +108,7 @@ importers: '@pnpm/cli-utils': 'link:' '@pnpm/config': 'workspace:6.0.0' '@pnpm/default-resolver': 'workspace:6.0.3' + '@pnpm/error': 'workspace:1.0.0' '@pnpm/logger': ^3.1.0 '@pnpm/package-is-installable': 'workspace:4.0.1' '@pnpm/read-importer-manifest': 'workspace:2.0.1' @@ -404,6 +406,7 @@ importers: packages/filter-workspace-packages: dependencies: '@pnpm/error': 'link:../error' + '@pnpm/find-workspace-packages': 'link:../find-workspace-packages' '@pnpm/matcher': 'link:../matcher' execa: 4.0.0 find-up: 4.1.0 @@ -424,6 +427,7 @@ importers: specifiers: '@pnpm/error': 'workspace:1.0.0' '@pnpm/filter-workspace-packages': 'link:' + '@pnpm/find-workspace-packages': 'workspace:2.0.8' '@pnpm/matcher': 'workspace:1.0.0' '@types/is-ci': ^2.0.0 '@types/is-windows': ^1.0.0 @@ -464,6 +468,7 @@ importers: packages/find-workspace-packages: dependencies: '@pnpm/cli-utils': 'link:../cli-utils' + '@pnpm/config': 'link:../config' '@pnpm/constants': 'link:../constants' '@pnpm/types': 'link:../types' find-packages: 'link:../find-packages' @@ -473,6 +478,7 @@ importers: rimraf: 3.0.0 specifiers: '@pnpm/cli-utils': 'workspace:0.2.5' + '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' '@pnpm/find-workspace-packages': 'link:' '@pnpm/types': 'workspace:4.0.0' @@ -1337,56 +1343,92 @@ importers: '@pnpm/common-cli-options-help': 'link:../common-cli-options-help' '@pnpm/config': 'link:../config' '@pnpm/constants': 'link:../constants' + '@pnpm/core-loggers': 'link:../core-loggers' '@pnpm/error': 'link:../error' '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' '@pnpm/find-workspace-dir': 'link:../find-workspace-dir' '@pnpm/find-workspace-packages': 'link:../find-workspace-packages' '@pnpm/package-store': 'link:../package-store' '@pnpm/plugin-commands-rebuild': 'link:../plugin-commands-rebuild' - '@pnpm/plugin-commands-recursive': 'link:../plugin-commands-recursive' '@pnpm/pnpmfile': 'link:../pnpmfile' + '@pnpm/resolver-base': 'link:../resolver-base' + '@pnpm/sort-packages': 'link:../sort-packages' '@pnpm/store-connection-manager': 'link:../store-connection-manager' + '@pnpm/types': 'link:../types' + '@pnpm/utils': 'link:../utils' + camelcase-keys: 6.1.1 common-tags: 1.8.0 + is-subdir: 1.1.1 + mem: 6.0.1 + mz: 2.7.0 + p-filter: 2.1.0 p-limit: 2.2.1 path-absolute: 1.0.1 + path-exists: 4.0.0 ramda: 0.26.1 + read-ini-file: 2.0.0 render-help: 0.0.0 supi: 'link:../supi' devDependencies: + '@pnpm/lockfile-types': 'link:../lockfile-types' + '@pnpm/logger': 3.1.0 '@pnpm/plugin-commands-installation': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' '@pnpm/registry-mock': 1.11.1 '@pnpm/test-fixtures': 'link:../../privatePackages/test-fixtures' '@types/common-tags': 1.8.0 + '@types/mz': 2.7.0 '@types/ramda': 0.26.38 + make-dir: 3.0.0 + read-yaml-file: 1.1.0 rimraf: 3.0.0 + write-json-file: 4.2.1 + write-yaml-file: 3.0.1 specifiers: '@pnpm/cli-utils': 'workspace:0.2.5' '@pnpm/common-cli-options-help': 'workspace:0.1.2' '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' + '@pnpm/core-loggers': 'workspace:4.0.0' '@pnpm/error': 'workspace:1.0.0' '@pnpm/filter-workspace-packages': 'workspace:1.0.1' '@pnpm/find-workspace-dir': 'workspace:1.0.0' '@pnpm/find-workspace-packages': 'workspace:2.0.8' + '@pnpm/lockfile-types': 'workspace:1.1.0' + '@pnpm/logger': ^3.1.0 '@pnpm/package-store': 'workspace:7.0.2' '@pnpm/plugin-commands-installation': 'link:' '@pnpm/plugin-commands-rebuild': 'workspace:0.0.0' - '@pnpm/plugin-commands-recursive': 'workspace:0.1.10' '@pnpm/pnpmfile': 'workspace:0.1.0' '@pnpm/prepare': 'workspace:0.0.0' '@pnpm/registry-mock': 1.11.1 + '@pnpm/resolver-base': 'workspace:6.0.0' + '@pnpm/sort-packages': 'workspace:0.0.0' '@pnpm/store-connection-manager': 'workspace:0.2.5' '@pnpm/test-fixtures': 'workspace:0.0.0' + '@pnpm/types': 'workspace:4.0.0' + '@pnpm/utils': 'workspace:0.12.2' '@types/common-tags': ^1.8.0 + '@types/mz': ^2.7.0 '@types/ramda': ^0.26.38 + camelcase-keys: 6.1.1 common-tags: 1.8.0 + is-subdir: 1.1.1 + make-dir: 3.0.0 + mem: 6.0.1 + mz: 2.7.0 + p-filter: 2.1.0 p-limit: 2.2.1 path-absolute: 1.0.1 + path-exists: 4.0.0 ramda: 0.26.1 + read-ini-file: 2.0.0 + read-yaml-file: 1.1.0 render-help: 0.0.0 rimraf: 3.0.0 supi: 'workspace:0.37.6' + write-json-file: 4.2.1 + write-yaml-file: 3.0.1 packages/plugin-commands-listing: dependencies: '@pnpm/cli-utils': 'link:../cli-utils' @@ -1399,6 +1441,9 @@ importers: render-help: 0.0.0 devDependencies: '@pnpm/constants': 'link:../constants' + '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' + '@pnpm/logger': 3.1.0 + '@pnpm/plugin-commands-installation': 'link:../plugin-commands-installation' '@pnpm/plugin-commands-listing': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' '@types/common-tags': 1.8.0 @@ -1415,7 +1460,10 @@ importers: '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' '@pnpm/error': 'workspace:1.0.0' + '@pnpm/filter-workspace-packages': 'workspace:1.0.1' '@pnpm/list': 'workspace:4.0.12' + '@pnpm/logger': ^3.1.0 + '@pnpm/plugin-commands-installation': 'workspace:*' '@pnpm/plugin-commands-listing': 'link:' '@pnpm/prepare': 'workspace:0.0.0' '@types/common-tags': 1.8.0 @@ -1449,6 +1497,8 @@ importers: wrap-ansi: 6.2.0 devDependencies: '@pnpm/constants': 'link:../constants' + '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' + '@pnpm/plugin-commands-installation': 'link:../plugin-commands-installation' '@pnpm/plugin-commands-outdated': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' '@pnpm/types': 'link:../types' @@ -1467,10 +1517,12 @@ importers: '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' '@pnpm/error': 'workspace:1.0.0' + '@pnpm/filter-workspace-packages': 'workspace:1.0.1' '@pnpm/lockfile-file': 'workspace:3.0.1' '@pnpm/matcher': 'workspace:1.0.0' '@pnpm/modules-yaml': 'workspace:5.0.0' '@pnpm/outdated': 'workspace:6.0.5' + '@pnpm/plugin-commands-installation': 'workspace:*' '@pnpm/plugin-commands-outdated': 'link:' '@pnpm/prepare': 'workspace:0.0.0' '@pnpm/semver-diff': 1.0.2 @@ -1496,23 +1548,32 @@ importers: '@pnpm/cli-utils': 'link:../cli-utils' '@pnpm/config': 'link:../config' '@pnpm/error': 'link:../error' + '@pnpm/npm-resolver': 'link:../npm-resolver' '@pnpm/read-importer-manifest': 'link:../read-importer-manifest' + '@pnpm/resolver-base': 'link:../resolver-base' '@pnpm/run-npm': 'link:../run-npm' + '@pnpm/store-path': 2.1.1 '@pnpm/types': 'link:../types' + '@pnpm/utils': 'link:../utils' '@zkochan/rimraf': 1.0.0 cp-file: 7.0.0 fast-glob: 3.1.1 + lru-cache: 5.1.1 mz: 2.7.0 + p-filter: 2.1.0 ramda: 0.26.1 render-help: 0.0.0 write-json-file: 4.2.1 devDependencies: + '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' '@pnpm/plugin-commands-publishing': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' '@types/cross-spawn': 6.0.1 + '@types/lru-cache': 5.1.0 '@types/mz': 2.7.0 '@types/ramda': 0.26.38 cross-spawn: 7.0.1 + execa: 4.0.0 path-exists: 4.0.0 rimraf: 3.0.0 write-yaml-file: 3.0.1 @@ -1520,19 +1581,28 @@ importers: '@pnpm/cli-utils': 'workspace:0.2.5' '@pnpm/config': 'workspace:6.0.0' '@pnpm/error': 'workspace:1.0.0' + '@pnpm/filter-workspace-packages': 'workspace:1.0.1' + '@pnpm/npm-resolver': 'workspace:6.0.2' '@pnpm/plugin-commands-publishing': 'link:' '@pnpm/prepare': 'workspace:0.0.0' '@pnpm/read-importer-manifest': 'workspace:2.0.1' + '@pnpm/resolver-base': 'workspace:6.0.0' '@pnpm/run-npm': 'workspace:1.0.0' + '@pnpm/store-path': 2.1.1 '@pnpm/types': 'workspace:4.0.0' + '@pnpm/utils': 'workspace:0.12.2' '@types/cross-spawn': ^6.0.1 + '@types/lru-cache': ^5.1.0 '@types/mz': ^2.7.0 '@types/ramda': ^0.26.38 '@zkochan/rimraf': 1.0.0 cp-file: 7.0.0 cross-spawn: 7.0.1 + execa: 4.0.0 fast-glob: 3.1.1 + lru-cache: 5.1.1 mz: 2.7.0 + p-filter: 2.1.0 path-exists: 4.0.0 ramda: 0.26.1 render-help: 0.0.0 @@ -1546,6 +1616,7 @@ importers: '@pnpm/config': 'link:../config' '@pnpm/constants': 'link:../constants' '@pnpm/core-loggers': 'link:../core-loggers' + '@pnpm/find-workspace-packages': 'link:../find-workspace-packages' '@pnpm/get-context': 'link:../get-context' '@pnpm/lifecycle': 'link:../lifecycle' '@pnpm/link-bins': 5.0.1 @@ -1553,21 +1624,26 @@ importers: '@pnpm/lockfile-walker': 'link:../lockfile-walker' '@pnpm/modules-yaml': 'link:../modules-yaml' '@pnpm/pkgid-to-filename': 2.0.0 + '@pnpm/sort-packages': 'link:../sort-packages' '@pnpm/store-connection-manager': 'link:../store-connection-manager' '@pnpm/store-controller-types': 'link:../store-controller-types' '@pnpm/types': 'link:../types' '@pnpm/utils': 'link:../utils' '@zkochan/npm-package-arg': 1.0.2 + camelcase-keys: 6.1.1 common-tags: 1.8.0 dependency-path: 'link:../dependency-path' graph-sequencer: 2.0.0 load-json-file: 6.2.0 + mem: 6.0.1 p-limit: 2.2.1 ramda: 0.26.1 + read-ini-file: 2.0.0 render-help: 0.0.0 run-groups: 2.0.1 semver: 7.1.1 devDependencies: + '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' '@pnpm/logger': 3.1.0 '@pnpm/plugin-commands-rebuild': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' @@ -1581,12 +1657,15 @@ importers: path-exists: 4.0.0 rimraf: 3.0.0 sinon: 8.0.0 + write-yaml-file: 3.0.1 specifiers: '@pnpm/cli-utils': 'workspace:0.2.5' '@pnpm/common-cli-options-help': 'workspace:0.1.2' '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' '@pnpm/core-loggers': 'workspace:4.0.0' + '@pnpm/filter-workspace-packages': 'workspace:1.0.1' + '@pnpm/find-workspace-packages': 'workspace:2.0.8' '@pnpm/get-context': 'workspace:0.0.0' '@pnpm/lifecycle': 'workspace:8.0.1' '@pnpm/link-bins': 5.0.1 @@ -1598,6 +1677,7 @@ importers: '@pnpm/plugin-commands-rebuild': 'link:' '@pnpm/prepare': 'workspace:0.0.0' '@pnpm/registry-mock': 1.11.1 + '@pnpm/sort-packages': 'workspace:0.0.0' '@pnpm/store-connection-manager': 'workspace:0.2.5' '@pnpm/store-controller-types': 'workspace:6.0.0' '@pnpm/test-fixtures': 'workspace:0.0.0' @@ -1608,138 +1688,22 @@ importers: '@types/semver': ^6.2.0 '@types/sinon': ^7.5.1 '@zkochan/npm-package-arg': 1.0.2 + camelcase-keys: 6.1.1 common-tags: 1.8.0 dependency-path: 'workspace:4.0.2' execa: 4.0.0 graph-sequencer: 2.0.0 load-json-file: 6.2.0 + mem: 6.0.1 p-limit: 2.2.1 path-exists: 4.0.0 ramda: 0.26.1 + read-ini-file: 2.0.0 render-help: 0.0.0 rimraf: 3.0.0 run-groups: 2.0.1 semver: 7.1.1 sinon: 8.0.0 - packages/plugin-commands-recursive: - dependencies: - '@pnpm/cli-utils': 'link:../cli-utils' - '@pnpm/common-cli-options-help': 'link:../common-cli-options-help' - '@pnpm/config': 'link:../config' - '@pnpm/constants': 'link:../constants' - '@pnpm/core-loggers': 'link:../core-loggers' - '@pnpm/error': 'link:../error' - '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' - '@pnpm/find-workspace-packages': 'link:../find-workspace-packages' - '@pnpm/lifecycle': 'link:../lifecycle' - '@pnpm/lockfile-file': 'link:../lockfile-file' - '@pnpm/matcher': 'link:../matcher' - '@pnpm/npm-resolver': 'link:../npm-resolver' - '@pnpm/outdated': 'link:../outdated' - '@pnpm/plugin-commands-listing': 'link:../plugin-commands-listing' - '@pnpm/plugin-commands-outdated': 'link:../plugin-commands-outdated' - '@pnpm/plugin-commands-publishing': 'link:../plugin-commands-publishing' - '@pnpm/plugin-commands-rebuild': 'link:../plugin-commands-rebuild' - '@pnpm/pnpmfile': 'link:../pnpmfile' - '@pnpm/resolver-base': 'link:../resolver-base' - '@pnpm/run-npm': 'link:../run-npm' - '@pnpm/store-connection-manager': 'link:../store-connection-manager' - '@pnpm/store-path': 2.1.1 - '@pnpm/types': 'link:../types' - '@pnpm/utils': 'link:../utils' - camelcase-keys: 6.1.1 - chalk: 3.0.0 - common-tags: 1.8.0 - execa: 4.0.0 - graph-sequencer: 2.0.0 - is-subdir: 1.1.1 - lru-cache: 5.1.1 - mem: 6.0.1 - mz: 2.7.0 - p-filter: 2.1.0 - p-limit: 2.2.1 - pkgs-graph: 'link:../pkgs-graph' - ramda: 0.26.1 - read-ini-file: 2.0.0 - render-help: 0.0.0 - supi: 'link:../supi' - table: 5.4.6 - devDependencies: - '@pnpm/lockfile-types': 'link:../lockfile-types' - '@pnpm/logger': 3.1.0 - '@pnpm/plugin-commands-recursive': 'link:' - '@pnpm/prepare': 'link:../../privatePackages/prepare' - '@types/common-tags': 1.8.0 - '@types/lru-cache': 5.1.0 - '@types/mz': 2.7.0 - '@types/ramda': 0.26.38 - '@types/table': 4.0.7 - '@zkochan/rimraf': 1.0.0 - make-dir: 3.0.0 - path-exists: 4.0.0 - read-yaml-file: 1.1.0 - rimraf: 3.0.0 - strip-ansi: 6.0.0 - write-json-file: 4.2.1 - write-yaml-file: 3.0.1 - specifiers: - '@pnpm/cli-utils': 'workspace:0.2.5' - '@pnpm/common-cli-options-help': 'workspace:0.1.2' - '@pnpm/config': 'workspace:6.0.0' - '@pnpm/constants': 'workspace:3.0.0' - '@pnpm/core-loggers': 'workspace:4.0.0' - '@pnpm/error': 'workspace:1.0.0' - '@pnpm/filter-workspace-packages': 'workspace:1.0.1' - '@pnpm/find-workspace-packages': 'workspace:2.0.8' - '@pnpm/lifecycle': 'workspace:8.0.1' - '@pnpm/lockfile-file': 'workspace:3.0.1' - '@pnpm/lockfile-types': 'workspace:1.1.0' - '@pnpm/logger': 3.1.0 - '@pnpm/matcher': 'workspace:1.0.0' - '@pnpm/npm-resolver': 'workspace:6.0.2' - '@pnpm/outdated': 'workspace:6.0.5' - '@pnpm/plugin-commands-listing': 'workspace:0.1.9' - '@pnpm/plugin-commands-outdated': 'workspace:0.1.9' - '@pnpm/plugin-commands-publishing': 'workspace:0.1.9' - '@pnpm/plugin-commands-rebuild': 'workspace:0.0.0' - '@pnpm/plugin-commands-recursive': 'link:' - '@pnpm/pnpmfile': 'workspace:0.1.0' - '@pnpm/prepare': 'workspace:0.0.0' - '@pnpm/resolver-base': 'workspace:6.0.0' - '@pnpm/run-npm': 'workspace:1.0.0' - '@pnpm/store-connection-manager': 'workspace:0.2.5' - '@pnpm/store-path': 2.1.1 - '@pnpm/types': 'workspace:4.0.0' - '@pnpm/utils': 'workspace:0.12.2' - '@types/common-tags': ^1.8.0 - '@types/lru-cache': ^5.1.0 - '@types/mz': ^2.7.0 - '@types/ramda': ^0.26.38 - '@types/table': ^4.0.7 - '@zkochan/rimraf': 1.0.0 - camelcase-keys: 6.1.1 - chalk: 3.0.0 - common-tags: 1.8.0 - execa: 4.0.0 - graph-sequencer: 2.0.0 - is-subdir: 1.1.1 - lru-cache: 5.1.1 - make-dir: 3.0.0 - mem: 6.0.1 - mz: 2.7.0 - p-filter: 2.1.0 - p-limit: 2.2.1 - path-exists: 4.0.0 - pkgs-graph: 'workspace:5.0.1' - ramda: 0.26.1 - read-ini-file: 2.0.0 - read-yaml-file: 1.1.0 - render-help: 0.0.0 - rimraf: 3.0.0 - strip-ansi: 6.0.0 - supi: 'workspace:0.37.6' - table: 5.4.6 - write-json-file: 4.2.1 write-yaml-file: 3.0.1 packages/plugin-commands-script-runners: dependencies: @@ -1748,39 +1712,51 @@ importers: '@pnpm/config': 'link:../config' '@pnpm/error': 'link:../error' '@pnpm/lifecycle': 'link:../lifecycle' + '@pnpm/sort-packages': 'link:../sort-packages' '@pnpm/types': 'link:../types' '@pnpm/utils': 'link:../utils' common-tags: 1.8.0 + p-limit: 2.2.1 ramda: 0.26.1 render-help: 0.0.0 devDependencies: + '@pnpm/filter-workspace-packages': 'link:../filter-workspace-packages' + '@pnpm/logger': 3.1.0 '@pnpm/plugin-commands-script-runners': 'link:' '@pnpm/prepare': 'link:../../privatePackages/prepare' '@types/common-tags': 1.8.0 '@types/mz': 2.7.0 '@types/ramda': 0.26.38 + '@zkochan/rimraf': 1.0.0 execa: 4.0.0 mz: 2.7.0 rimraf: 3.0.0 + write-yaml-file: 3.0.1 specifiers: '@pnpm/cli-utils': 'workspace:0.2.5' '@pnpm/common-cli-options-help': 'workspace:0.1.2' '@pnpm/config': 'workspace:6.0.0' '@pnpm/error': 'workspace:1.0.0' + '@pnpm/filter-workspace-packages': 'workspace:1.0.1' '@pnpm/lifecycle': 'workspace:8.0.1' + '@pnpm/logger': ^3.1.0 '@pnpm/plugin-commands-script-runners': 'link:' '@pnpm/prepare': 'workspace:0.0.0' + '@pnpm/sort-packages': 'workspace:0.0.0' '@pnpm/types': 'workspace:4.0.0' '@pnpm/utils': 'workspace:0.12.2' '@types/common-tags': ^1.8.0 '@types/mz': ^2.7.0 '@types/ramda': ^0.26.38 + '@zkochan/rimraf': 1.0.0 common-tags: 1.8.0 execa: 4.0.0 mz: 2.7.0 + p-limit: 2.2.1 ramda: 0.26.1 render-help: 0.0.0 rimraf: 3.0.0 + write-yaml-file: 3.0.1 packages/plugin-commands-server: dependencies: '@pnpm/cli-utils': 'link:../cli-utils' @@ -1908,6 +1884,7 @@ importers: packages/pnpm: dependencies: '@pnpm/cli-utils': 'link:../cli-utils' + '@pnpm/common-cli-options-help': 'link:../common-cli-options-help' '@pnpm/config': 'link:../config' '@pnpm/core-loggers': 'link:../core-loggers' '@pnpm/default-reporter': 'link:../default-reporter' @@ -1923,7 +1900,6 @@ importers: '@pnpm/plugin-commands-outdated': 'link:../plugin-commands-outdated' '@pnpm/plugin-commands-publishing': 'link:../plugin-commands-publishing' '@pnpm/plugin-commands-rebuild': 'link:../plugin-commands-rebuild' - '@pnpm/plugin-commands-recursive': 'link:../plugin-commands-recursive' '@pnpm/plugin-commands-script-runners': 'link:../plugin-commands-script-runners' '@pnpm/plugin-commands-server': 'link:../plugin-commands-server' '@pnpm/plugin-commands-store': 'link:../plugin-commands-store' @@ -1995,6 +1971,7 @@ importers: specifiers: '@pnpm/assert-project': 'workspace:*' '@pnpm/cli-utils': 'workspace:0.2.5' + '@pnpm/common-cli-options-help': 'workspace:0.1.2' '@pnpm/config': 'workspace:6.0.0' '@pnpm/constants': 'workspace:3.0.0' '@pnpm/core-loggers': 'workspace:4.0.0' @@ -2014,7 +1991,6 @@ importers: '@pnpm/plugin-commands-outdated': 'workspace:0.1.9' '@pnpm/plugin-commands-publishing': 'workspace:0.1.9' '@pnpm/plugin-commands-rebuild': 'workspace:0.0.0' - '@pnpm/plugin-commands-recursive': 'workspace:0.1.10' '@pnpm/plugin-commands-script-runners': 'workspace:0.1.9' '@pnpm/plugin-commands-server': 'workspace:0.0.0' '@pnpm/plugin-commands-store': 'workspace:0.0.0' @@ -2309,6 +2285,18 @@ importers: promise-share: 1.0.0 rimraf: 3.0.0 uuid: 3.3.3 + packages/sort-packages: + dependencies: + '@pnpm/config': 'link:../config' + graph-sequencer: 2.0.0 + devDependencies: + '@pnpm/sort-packages': 'link:' + rimraf: 3.0.0 + specifiers: + '@pnpm/config': 'workspace:6.0.0' + '@pnpm/sort-packages': 'link:' + graph-sequencer: 2.0.0 + rimraf: 3.0.0 packages/store-connection-manager: dependencies: '@pnpm/cli-utils': 'link:../cli-utils' @@ -3281,7 +3269,7 @@ packages: dependencies: '@types/events': 3.0.0 '@types/minimatch': 3.0.3 - '@types/node': 12.12.21 + '@types/node': 13.1.0 resolution: integrity: sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== /@types/graceful-fs/4.1.3: @@ -3338,7 +3326,7 @@ packages: integrity: sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg== /@types/mz/2.7.0: dependencies: - '@types/node': 12.12.21 + '@types/node': 13.1.1 resolution: integrity: sha512-Q5TZYMKnH0hdV5fNstmMWL2LLw5eRRtTd73KNtsZxoQ2gtCQyET5X79uERUEwGneuxPglg441I7OSY00+9CkSw== /@types/ncp/2.0.3: @@ -3360,6 +3348,12 @@ packages: /@types/node/12.12.21: resolution: integrity: sha512-8sRGhbpU+ck1n0PGAUgVrWrWdjSW2aqNeyC15W88GRsMpSwzv6RJGlLhE7s2RhVSOdyDmxbqlWSeThq4/7xqlA== + /@types/node/13.1.0: + resolution: + integrity: sha512-zwrxviZS08kRX40nqBrmERElF2vpw4IUTd5khkhBTfFH8AOaeoLVx48EC4+ZzS2/Iga7NevncqnsUSYjM4OWYA== + /@types/node/13.1.1: + resolution: + integrity: sha512-hx6zWtudh3Arsbl3cXay+JnkvVgCKzCWKv42C9J01N2T2np4h8w5X8u6Tpz5mj38kE3M9FM0Pazx8vKFFMnjLQ== /@types/nopt/3.0.29: dev: true resolution: @@ -3623,7 +3617,7 @@ packages: /@zkochan/rimraf/1.0.0: dependencies: '@types/glob': 7.1.1 - '@types/node': 12.12.21 + '@types/node': 13.1.0 rimraf: 3.0.0 engines: node: '>=8.15' @@ -5783,7 +5777,7 @@ packages: human-signals: 1.1.1 is-stream: 2.0.0 merge-stream: 2.0.0 - npm-run-path: 4.0.0 + npm-run-path: 4.0.1 onetime: 5.1.0 signal-exit: 3.0.2 strip-final-newline: 2.0.0 @@ -8353,13 +8347,13 @@ packages: node: '>=4' resolution: integrity: sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - /npm-run-path/4.0.0: + /npm-run-path/4.0.1: dependencies: path-key: 3.1.1 engines: node: '>=8' resolution: - integrity: sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ== + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== /npm/4.6.1: bundledDependencies: - abbrev