mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-10 03:07:30 -04:00
feat: add ^...' and ...^' filters
`package^...` is like `package...` but excludes `package` itself. `...^package` is like `...package` but excludes `package` itself. PR #2201
This commit is contained in:
@@ -51,10 +51,18 @@ export const FILTERING = {
|
||||
description: 'Includes all direct and indirect dependencies of the matched packages. E.g.: foo...',
|
||||
name: '--filter <pattern>...',
|
||||
},
|
||||
{
|
||||
description: 'Includes only the direct and indirect dependencies of the matched packages without including the matched packages themselves. ^ must be doubled at the Windows Command Prompt. E.g.: foo^... (foo^^... in Command Prompt)',
|
||||
name: '--filter <pattern>^...',
|
||||
},
|
||||
{
|
||||
description: 'Includes all direct and indirect dependents of the matched packages. E.g.: ...foo, ...@bar/*',
|
||||
name: '--filter ...<pattern>',
|
||||
},
|
||||
{
|
||||
description: 'Includes only the direct and indirect dependents of the matched packages without including the matched packages themselves. ^ must be doubled at the Windows Command Prompt. E.g.: ...^foo (...^^foo in Command Prompt)',
|
||||
name: '--filter ...^<pattern>',
|
||||
},
|
||||
{
|
||||
description: 'Includes all packages that are inside a given subdirectory. E.g.: ./components',
|
||||
name: '--filter ./<dir>',
|
||||
|
||||
@@ -21,20 +21,20 @@ export function filterGraph<T> (
|
||||
const walkedDependents = new Set<string>()
|
||||
const graph = pkgGraphToGraph(pkgGraph)
|
||||
let reversedGraph: Graph | undefined
|
||||
for (const { pattern, scope, selectBy } of packageSelectors) {
|
||||
for (const { excludeSelf, pattern, scope, selectBy } of packageSelectors) {
|
||||
const entryPackages = selectBy === 'name'
|
||||
? matchPackages(pkgGraph, pattern)
|
||||
: matchPackagesByPath(pkgGraph, pattern)
|
||||
|
||||
switch (scope) {
|
||||
case 'dependencies':
|
||||
pickSubgraph(graph, entryPackages, walkedDependencies)
|
||||
pickSubgraph(graph, entryPackages, walkedDependencies, { includeRoot: !excludeSelf })
|
||||
continue
|
||||
case 'dependents':
|
||||
if (!reversedGraph) {
|
||||
reversedGraph = reverseGraph(graph)
|
||||
}
|
||||
pickSubgraph(reversedGraph, entryPackages, walkedDependents)
|
||||
pickSubgraph(reversedGraph, entryPackages, walkedDependents, { includeRoot: !excludeSelf })
|
||||
continue
|
||||
case 'exact':
|
||||
Array.prototype.push.apply(cherryPickedPackages, entryPackages)
|
||||
@@ -87,11 +87,17 @@ function pickSubgraph (
|
||||
graph: Graph,
|
||||
nextNodeIds: string[],
|
||||
walked: Set<string>,
|
||||
opts: {
|
||||
includeRoot: boolean
|
||||
},
|
||||
) {
|
||||
for (const nextNodeId of nextNodeIds) {
|
||||
if (!walked.has(nextNodeId)) {
|
||||
walked.add(nextNodeId)
|
||||
if (graph[nextNodeId]) pickSubgraph(graph, graph[nextNodeId], walked)
|
||||
if (opts.includeRoot) {
|
||||
walked.add(nextNodeId)
|
||||
}
|
||||
|
||||
if (graph[nextNodeId]) pickSubgraph(graph, graph[nextNodeId], walked, { includeRoot: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
import path = require('path')
|
||||
|
||||
export interface PackageSelector {
|
||||
excludeSelf?: boolean,
|
||||
pattern: string,
|
||||
scope: 'exact' | 'dependencies' | 'dependents',
|
||||
selectBy: 'name' | 'location',
|
||||
}
|
||||
|
||||
export default (rawSelector: string, prefix: string): PackageSelector => {
|
||||
if (rawSelector.endsWith('^...')) {
|
||||
const pattern = rawSelector.substring(0, rawSelector.length - 4)
|
||||
return {
|
||||
excludeSelf: true,
|
||||
pattern,
|
||||
scope: 'dependencies',
|
||||
selectBy: 'name',
|
||||
}
|
||||
}
|
||||
if (rawSelector.startsWith('...^')) {
|
||||
const pattern = rawSelector.substring(4)
|
||||
return {
|
||||
excludeSelf: true,
|
||||
pattern,
|
||||
scope: 'dependents',
|
||||
selectBy: 'name',
|
||||
}
|
||||
}
|
||||
if (rawSelector.endsWith('...')) {
|
||||
const pattern = rawSelector.substring(0, rawSelector.length - 3)
|
||||
return {
|
||||
excludeSelf: false,
|
||||
pattern,
|
||||
scope: 'dependencies',
|
||||
selectBy: 'name',
|
||||
@@ -18,6 +38,7 @@ export default (rawSelector: string, prefix: string): PackageSelector => {
|
||||
if (rawSelector.startsWith('...')) {
|
||||
const pattern = rawSelector.substring(3)
|
||||
return {
|
||||
excludeSelf: false,
|
||||
pattern,
|
||||
scope: 'dependents',
|
||||
selectBy: 'name',
|
||||
@@ -25,12 +46,14 @@ export default (rawSelector: string, prefix: string): PackageSelector => {
|
||||
}
|
||||
if (isSelectorByLocation(rawSelector)) {
|
||||
return {
|
||||
excludeSelf: false,
|
||||
pattern: path.join(prefix, rawSelector),
|
||||
scope: 'exact',
|
||||
selectBy: 'location',
|
||||
}
|
||||
}
|
||||
return {
|
||||
excludeSelf: false,
|
||||
pattern: rawSelector,
|
||||
scope: 'exact',
|
||||
selectBy: 'name',
|
||||
|
||||
@@ -374,6 +374,58 @@ test('recursive filter package with dependencies', async (t) => {
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('recursive filter only package dependencies', async (t) => {
|
||||
const projects = preparePackages(t, [
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'project-2': '1.0.0',
|
||||
'project-4': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
minimatch: '*',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-4',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await recursive.handler(['install'], {
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
filter: ['project-1^...'],
|
||||
})
|
||||
|
||||
projects['project-1'].hasNot('is-positive')
|
||||
projects['project-2'].has('is-negative')
|
||||
projects['project-3'].hasNot('minimatch')
|
||||
projects['project-4'].has('is-positive')
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('recursive filter package with dependents', async (t) => {
|
||||
const projects = preparePackages(t, [
|
||||
{
|
||||
@@ -425,6 +477,57 @@ test('recursive filter package with dependents', async (t) => {
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('recursive filter only package dependents', async (t) => {
|
||||
const projects = preparePackages(t, [
|
||||
{
|
||||
name: 'project-0',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'project-1': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-positive': '1.0.0',
|
||||
'project-2': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'is-negative': '1.0.0',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
minimatch: '*',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await recursive.handler(['install'], {
|
||||
...DEFAULT_OPTS,
|
||||
dir: process.cwd(),
|
||||
filter: ['...^project-2'],
|
||||
})
|
||||
|
||||
projects['project-0'].has('is-positive')
|
||||
projects['project-1'].has('is-positive')
|
||||
projects['project-2'].hasNot('is-negative')
|
||||
projects['project-3'].hasNot('minimatch')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('recursive filter package with dependents and filter with dependencies', async (t) => {
|
||||
const projects = preparePackages(t, [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user