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:
Aankhen
2019-12-11 02:21:34 +05:30
committed by Zoltan Kochan
parent 703ff93590
commit 758a3e4eef
4 changed files with 145 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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