feat: filtering packages in recursive commands (#1219)

* feat: filtering packages in recursive commands

close #1213

* test: recursive --scope ignore excluded packages

* test: recursive list --scope

PR #1219
This commit is contained in:
Zoltan Kochan
2018-06-14 14:44:41 +03:00
committed by GitHub
parent e00f128df7
commit a1ae9016c4
7 changed files with 167 additions and 2 deletions

View File

@@ -35,6 +35,7 @@ export const types = Object.assign(npmTypes.types, {
'production': [null, true],
'protocol': ['auto', 'tcp', 'ipc'],
'reporter': String,
'scope': String,
'shamefully-flatten': Boolean,
'shrinkwrap-only': Boolean,
'side-effects-cache': Boolean,

View File

@@ -33,6 +33,7 @@
"@types/camelcase-keys": "^4.0.0",
"@types/get-port": "^3.2.0",
"@types/lru-cache": "^4.1.1",
"@types/minimatch": "^3.0.3",
"@types/mz": "^0.0.32",
"@zkochan/libnpx": "^9.6.1",
"camelcase": "^4.1.0 || 5",
@@ -52,6 +53,7 @@
"load-yaml-file": "^0.1.0",
"loud-rejection": "^1.6.0",
"lru-cache": "^4.1.3",
"minimatch": "^3.0.4",
"mkdirp-promise": "^5.0.1",
"mz": "^2.7.0",
"nopt": "^4.0.1",

View File

@@ -12,6 +12,7 @@ dependencies:
'@types/camelcase-keys': 4.0.0
'@types/get-port': 3.2.0
'@types/lru-cache': 4.1.1
'@types/minimatch': 3.0.3
'@types/mz': 0.0.32
'@zkochan/libnpx': 9.6.1
camelcase: 5.0.0
@@ -31,6 +32,7 @@ dependencies:
load-yaml-file: 0.1.0
loud-rejection: 1.6.0
lru-cache: 4.1.3
minimatch: 3.0.4
mkdirp-promise: 5.0.1
mz: 2.7.0
nopt: 4.0.1
@@ -6457,6 +6459,7 @@ specifiers:
'@types/get-port': ^3.2.0
'@types/load-json-file': ^2.0.6
'@types/lru-cache': ^4.1.1
'@types/minimatch': ^3.0.3
'@types/mkdirp': ^0.5.0
'@types/mz': ^0.0.32
'@types/node': ^10.0.6
@@ -6492,6 +6495,7 @@ specifiers:
load-yaml-file: ^0.1.0
loud-rejection: ^1.6.0
lru-cache: ^4.1.3
minimatch: ^3.0.4
mkdirp: ^0.5.1
mkdirp-promise: ^5.0.1
mz: ^2.7.0

View File

@@ -304,6 +304,10 @@ function getHelpText (command: string) {
This command runs the "npm build" command on each package.
This is useful when you install a new version of node, and must recompile all your C++ addons with the new binary.
Options:
--scope restricts the scope to package names matching the given glob.
`
default:

View File

@@ -3,10 +3,12 @@ import camelcaseKeys = require('camelcase-keys')
import findPackages from 'find-packages'
import graphSequencer = require('graph-sequencer')
import loadYamlFile = require('load-yaml-file')
import minimatch = require('minimatch')
import pLimit = require('p-limit')
import { StoreController } from 'package-store'
import path = require('path')
import createPkgGraph, {PackageNode} from 'pkgs-graph'
import R = require('ramda')
import readIniFile = require('read-ini-file')
import sortPkgs = require('sort-pkgs')
import {
@@ -64,7 +66,7 @@ export default async (
const cwd = process.cwd()
const packagesManifest = await requirePackagesManifest(cwd)
const pkgs = await findPackages(cwd, {
let pkgs = await findPackages(cwd, {
ignore: [
'**/node_modules/**',
'**/bower_components/**',
@@ -72,6 +74,12 @@ export default async (
patterns: packagesManifest && packagesManifest.packages || undefined,
})
const pkgGraphResult = createPkgGraph(pkgs)
if (opts.scope) {
pkgGraphResult.graph = filterGraph(pkgGraphResult.graph, opts.scope)
pkgs = pkgs.filter((pkg: {path: string}) => pkgGraphResult.graph[pkg.path])
}
switch (cmdFullName) {
case 'list':
await list(pkgs, input, cmd, opts as any) // tslint:disable-line:no-any
@@ -88,7 +96,6 @@ export default async (
break
}
const pkgGraphResult = createPkgGraph(pkgs)
const store = await createStoreController(opts)
// It is enough to save the store.json file once,
@@ -208,3 +215,33 @@ async function requirePackagesManifest (dir: string): Promise<{packages: string[
throw err
}
}
interface PackageGraph {
[id: string]: PackageNode,
}
function filterGraph (
graph: PackageGraph,
scope: string,
): PackageGraph {
const root = R.keys(graph).filter((id) => graph[id].manifest.name && minimatch(graph[id].manifest.name, scope))
if (!root.length) return {}
const subgraphNodeIds = new Set()
pickSubgraph(graph, root, subgraphNodeIds)
return R.pick(Array.from(subgraphNodeIds), graph)
}
function pickSubgraph (
graph: PackageGraph,
nextNodeIds: string[],
walked: Set<string>,
) {
for (const nextNodeId of nextNodeIds) {
if (!walked.has(nextNodeId)) {
walked.add(nextNodeId)
pickSubgraph(graph, graph[nextNodeId].dependencies, walked)
}
}
}

View File

@@ -18,6 +18,7 @@ export interface PnpmOptions {
saveProd?: boolean,
saveDev?: boolean,
saveOptional?: boolean,
scope: string,
production?: boolean,
development?: boolean,
fetchRetries?: number,

View File

@@ -440,6 +440,49 @@ test('recursive list', async (t: tape.Test) => {
` + '\n\n')
})
test('recursive list --scope', async (t: tape.Test) => {
const projects = prepare(t, [
{
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: {
'is-negative': '1.0.0',
'is-positive': '1.0.0',
},
},
])
await execPnpm('recursive', 'link')
const result = execPnpmSync('recursive', 'list', '--scope', 'project-1')
t.equal(result.status, 0)
t.equal(result.stdout.toString(), stripIndent`
project-1@1.0.0 ${path.resolve('project-1')}
├── is-positive@1.0.0
└── project-2@link:../project-2
project-2@1.0.0 ${path.resolve('project-2')}
└── is-negative@1.0.0
` + '\n\n')
})
test('pnpm recursive outdated', async (t: tape.Test) => {
const projects = prepare(t, [
{
@@ -771,3 +814,76 @@ test('`pnpm recursive rebuild` specific dependencies', async (t: tape.Test) => {
t.ok(typeof generatedByPostinstall === 'function', 'generatedByPostinstall() is available')
}
})
test('recursive --scope', async (t: tape.Test) => {
const projects = prepare(t, [
{
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 execPnpm('recursive', 'link', '--scope', 'project-1')
projects['project-1'].has('is-positive')
projects['project-2'].has('is-negative')
projects['project-3'].hasNot('minimatch')
})
test('recursive --scope ignore excluded packages', async (t: tape.Test) => {
const projects = prepare(t, [
{
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 writeYamlFile('pnpm-workspace.yaml', {
packages: [
'**',
'!project-1'
],
})
await execPnpm('recursive', 'link', '--scope', 'project-1')
projects['project-1'].hasNot('is-positive')
projects['project-2'].hasNot('is-negative')
projects['project-3'].hasNot('minimatch')
})