From a11aff29906ee5d691449e25e68f295592ed0366 Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Sat, 26 Sep 2020 13:41:49 +0300 Subject: [PATCH] feat: excluding packages using --filter Packages may be excluded from a command's scope, using "!" at the beginning of the selector. For instance, this will run tests in all projects except foo: ``` pnpm --filter=!foo test ``` And this one will run tests in all projects that are not under the `lib` directory: ``` pnpm --filter=!./lib test ``` close #2804 PR #2898 --- .changeset/hip-lemons-rescue.md | 5 +++ packages/common-cli-options-help/src/index.ts | 4 +++ .../filter-workspace-packages/src/index.ts | 30 ++++++++++++++++- .../src/parsePackageSelector.ts | 8 +++++ .../filter-workspace-packages/test/index.ts | 32 +++++++++++++++++++ .../test/parsePackageSelector.ts | 17 ++++++++++ 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 .changeset/hip-lemons-rescue.md diff --git a/.changeset/hip-lemons-rescue.md b/.changeset/hip-lemons-rescue.md new file mode 100644 index 0000000000..1fbe857c7d --- /dev/null +++ b/.changeset/hip-lemons-rescue.md @@ -0,0 +1,5 @@ +--- +"@pnpm/filter-workspace-packages": minor +--- + +If a package selector starts with "!", it will be excluded from the selection. diff --git a/packages/common-cli-options-help/src/index.ts b/packages/common-cli-options-help/src/index.ts index 6a68f94594..5967f9c5e2 100644 --- a/packages/common-cli-options-help/src/index.ts +++ b/packages/common-cli-options-help/src/index.ts @@ -92,6 +92,10 @@ export const FILTERING = { description: 'Includes all packages changed since the specified commit/branch. E.g.: [master], [HEAD~2]. It may be used together with "...". So, for instance, ...[HEAD~1] selects all packages changed in the last commit and their dependents', name: '--filter []', }, + { + description: 'If a selector starts with !, it means the packages matching the selector must be excluded. E.g., "pnpm --filter !foo" selects all packages except "foo"', + name: '--filter !', + }, ], title: 'Filtering options (run the command only on packages that satisfy at least one of the selectors)', } diff --git a/packages/filter-workspace-packages/src/index.ts b/packages/filter-workspace-packages/src/index.ts index 6718b6a0ac..676423f633 100644 --- a/packages/filter-workspace-packages/src/index.ts +++ b/packages/filter-workspace-packages/src/index.ts @@ -84,6 +84,34 @@ export default async function filterGraph ( selectedProjectsGraph: PackageGraph unmatchedFilters: string[] }> { + const [excludeSelectors, includeSelectors] = R.partition( + (selector: PackageSelector) => selector.exclude === true, + packageSelectors + ) + const fg = _filterGraph.bind(null, pkgGraph, opts) + const include = includeSelectors.length === 0 + ? { selected: Object.keys(pkgGraph), unmatchedFilters: [] } + : await fg(includeSelectors) + const exclude = await fg(excludeSelectors) + return { + selectedProjectsGraph: R.pick( + R.difference(include.selected, exclude.selected), + pkgGraph + ), + unmatchedFilters: [...include.unmatchedFilters, ...exclude.unmatchedFilters], + } +} + +async function _filterGraph ( + pkgGraph: PackageGraph, + opts: { + workspaceDir: string + }, + packageSelectors: PackageSelector[] +): Promise<{ + selected: string[] + unmatchedFilters: string[] + }> { const cherryPickedPackages = [] as string[] const walkedDependencies = new Set() const walkedDependents = new Set() @@ -135,7 +163,7 @@ export default async function filterGraph ( const walked = new Set([...walkedDependencies, ...walkedDependents]) cherryPickedPackages.forEach((cherryPickedPackage) => walked.add(cherryPickedPackage)) return { - selectedProjectsGraph: R.pick(Array.from(walked), pkgGraph), + selected: Array.from(walked), unmatchedFilters, } } diff --git a/packages/filter-workspace-packages/src/parsePackageSelector.ts b/packages/filter-workspace-packages/src/parsePackageSelector.ts index cfb15757f4..dc031f74c4 100644 --- a/packages/filter-workspace-packages/src/parsePackageSelector.ts +++ b/packages/filter-workspace-packages/src/parsePackageSelector.ts @@ -2,6 +2,7 @@ import path = require('path') export interface PackageSelector { diff?: string + exclude?: boolean excludeSelf?: boolean includeDependencies?: boolean includeDependents?: boolean @@ -10,6 +11,11 @@ export interface PackageSelector { } export default (rawSelector: string, prefix: string): PackageSelector => { + let exclude = false + if (rawSelector[0] === '!') { + exclude = true + rawSelector = rawSelector.substring(1) + } let excludeSelf = false const includeDependencies = rawSelector.endsWith('...') if (includeDependencies) { @@ -31,6 +37,7 @@ export default (rawSelector: string, prefix: string): PackageSelector => { if (matches === null) { if (isSelectorByLocation(rawSelector)) { return { + exclude, excludeSelf: false, parentDir: path.join(prefix, rawSelector), } @@ -43,6 +50,7 @@ export default (rawSelector: string, prefix: string): PackageSelector => { return { diff: matches[3]?.substr(1, matches[3].length - 2), + exclude, excludeSelf, includeDependencies, includeDependents, diff --git a/packages/filter-workspace-packages/test/index.ts b/packages/filter-workspace-packages/test/index.ts index c5f4a6a0d1..3f3c5ce05c 100644 --- a/packages/filter-workspace-packages/test/index.ts +++ b/packages/filter-workspace-packages/test/index.ts @@ -7,6 +7,7 @@ import execa = require('execa') import isCI = require('is-ci') import isWindows = require('is-windows') import path = require('path') +import R = require('ramda') import tempy = require('tempy') import touchCB = require('touch') @@ -307,3 +308,34 @@ test('should return unmatched filters', async () => { expect(unmatchedFilters).toStrictEqual(['project-5']) }) + +test('select all packages except one', async () => { + const { selectedProjectsGraph } = await filterWorkspacePackages(PKGS_GRAPH, [ + { + exclude: true, + excludeSelf: false, + includeDependencies: false, + namePattern: 'project-1', + }, + ], { workspaceDir: process.cwd() }) + + expect(Object.keys(selectedProjectsGraph)) + .toStrictEqual(Object.keys(R.omit(['/packages/project-1'], PKGS_GRAPH))) +}) + +test('select by parentDir and exclude one package by pattern', async () => { + const { selectedProjectsGraph } = await filterWorkspacePackages(PKGS_GRAPH, [ + { + excludeSelf: false, + parentDir: '/packages', + }, + { + exclude: true, + excludeSelf: false, + includeDependents: false, + namePattern: '*-1', + }, + ], { workspaceDir: process.cwd() }) + + expect(Object.keys(selectedProjectsGraph)).toStrictEqual(['/packages/project-0']) +}) diff --git a/packages/filter-workspace-packages/test/parsePackageSelector.ts b/packages/filter-workspace-packages/test/parsePackageSelector.ts index 863b3895b7..89f29c2f1d 100644 --- a/packages/filter-workspace-packages/test/parsePackageSelector.ts +++ b/packages/filter-workspace-packages/test/parsePackageSelector.ts @@ -7,6 +7,7 @@ const fixtures: Array<[string, PackageSelector]> = [ 'foo', { diff: undefined, + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: false, @@ -18,6 +19,7 @@ const fixtures: Array<[string, PackageSelector]> = [ 'foo...', { diff: undefined, + exclude: false, excludeSelf: false, includeDependencies: true, includeDependents: false, @@ -29,6 +31,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...foo', { diff: undefined, + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: true, @@ -40,6 +43,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...foo...', { diff: undefined, + exclude: false, excludeSelf: false, includeDependencies: true, includeDependents: true, @@ -51,6 +55,7 @@ const fixtures: Array<[string, PackageSelector]> = [ 'foo^...', { diff: undefined, + exclude: false, excludeSelf: true, includeDependencies: true, includeDependents: false, @@ -62,6 +67,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...^foo', { diff: undefined, + exclude: false, excludeSelf: true, includeDependencies: false, includeDependents: true, @@ -72,6 +78,7 @@ const fixtures: Array<[string, PackageSelector]> = [ [ './foo', { + exclude: false, excludeSelf: false, parentDir: path.resolve('foo'), }, @@ -79,6 +86,7 @@ const fixtures: Array<[string, PackageSelector]> = [ [ '../foo', { + exclude: false, excludeSelf: false, parentDir: path.resolve('../foo'), }, @@ -87,6 +95,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...{./foo}', { diff: undefined, + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: true, @@ -97,6 +106,7 @@ const fixtures: Array<[string, PackageSelector]> = [ [ '.', { + exclude: false, excludeSelf: false, parentDir: process.cwd(), }, @@ -104,6 +114,7 @@ const fixtures: Array<[string, PackageSelector]> = [ [ '..', { + exclude: false, excludeSelf: false, parentDir: path.resolve('..'), }, @@ -112,6 +123,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '[master]', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: false, @@ -123,6 +135,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '{foo}[master]', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: false, @@ -134,6 +147,7 @@ const fixtures: Array<[string, PackageSelector]> = [ 'pattern{foo}[master]', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: false, @@ -145,6 +159,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '[master]...', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: true, includeDependents: false, @@ -156,6 +171,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...[master]', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: false, includeDependents: true, @@ -167,6 +183,7 @@ const fixtures: Array<[string, PackageSelector]> = [ '...[master]...', { diff: 'master', + exclude: false, excludeSelf: false, includeDependencies: true, includeDependents: true,