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
This commit is contained in:
Zoltan Kochan
2020-09-26 13:41:49 +03:00
committed by GitHub
parent 52124bf090
commit a11aff2990
6 changed files with 95 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/filter-workspace-packages": minor
---
If a package selector starts with "!", it will be excluded from the selection.

View File

@@ -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 [<since>]',
},
{
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 !<selector>',
},
],
title: 'Filtering options (run the command only on packages that satisfy at least one of the selectors)',
}

View File

@@ -84,6 +84,34 @@ export default async function filterGraph<T> (
selectedProjectsGraph: PackageGraph<T>
unmatchedFilters: string[]
}> {
const [excludeSelectors, includeSelectors] = R.partition<PackageSelector>(
(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<T> (
pkgGraph: PackageGraph<T>,
opts: {
workspaceDir: string
},
packageSelectors: PackageSelector[]
): Promise<{
selected: string[]
unmatchedFilters: string[]
}> {
const cherryPickedPackages = [] as string[]
const walkedDependencies = new Set<string>()
const walkedDependents = new Set<string>()
@@ -135,7 +163,7 @@ export default async function filterGraph<T> (
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,
}
}

View File

@@ -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,

View File

@@ -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'])
})

View File

@@ -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,