mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
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:
5
.changeset/hip-lemons-rescue.md
Normal file
5
.changeset/hip-lemons-rescue.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/filter-workspace-packages": minor
|
||||
---
|
||||
|
||||
If a package selector starts with "!", it will be excluded from the selection.
|
||||
@@ -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)',
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'])
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user