Skip to content

Commit e14ceda

Browse files
Blaszlukebatchelor
authored andcommitted
Allow exclusion of certain dependency types from dependency graph (#244)
* Add excludedTypes args to Package.getAllDependencies and Project.getDependen(cy|ts)Graph Allows the dependency and dependents graphs to be filtered based on dependency type. * Change `Project.runPackageTasksGraphParallel` to exclude dev deps in dep graph * Add --excludeFromGraph flag to exclude certain dependency types from `bolt workspaces` commands * Add tests for runPackageTasks with excludeFromGraph option * Add tests for excludeFromGraph * Fix flow
1 parent a02f6b7 commit e14ceda

File tree

13 files changed

+279
-18
lines changed

13 files changed

+279
-18
lines changed

src/Package.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as logger from './utils/logger';
88
import * as messages from './utils/messages';
99
import sortObject from 'sort-object';
1010
import { BoltError } from './utils/errors';
11+
import type { configDependencyType } from './types';
1112

1213
export default class Package {
1314
filePath: string;
@@ -46,10 +47,23 @@ export default class Package {
4647
return this.config.getWorkspaces() || [];
4748
}
4849

49-
getAllDependencies() {
50+
getAllDependencies(excludedTypes?: configDependencyType[] = []) {
5051
let allDependencies = new Map();
52+
if (excludedTypes.length > 0) {
53+
let invalidTypes = excludedTypes.filter(
54+
t => DEPENDENCY_TYPES.indexOf(t) === -1
55+
);
56+
if (invalidTypes.length > 0) {
57+
throw new BoltError(
58+
`Invalid dependency types to exclude: "${invalidTypes.join(',')}"`
59+
);
60+
}
61+
}
62+
let dependencyTypes = DEPENDENCY_TYPES.filter(
63+
t => excludedTypes.indexOf(t) === -1
64+
);
5165

52-
for (let type of DEPENDENCY_TYPES) {
66+
for (let type of dependencyTypes) {
5367
let deps = this.config.getDeps(type);
5468
if (!deps) continue;
5569

src/Project.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import includes from 'array-includes';
44
import semver from 'semver';
55
import Package from './Package';
66
import Config from './Config';
7-
import type { SpawnOpts, FilterOpts } from './types';
7+
import type { configDependencyType, SpawnOpts, FilterOpts } from './types';
88
import * as fs from './utils/fs';
99
import * as logger from './utils/logger';
1010
import {
@@ -86,7 +86,10 @@ export default class Project {
8686
return packages;
8787
}
8888

89-
async getDependencyGraph(packages: Array<Package>) {
89+
async getDependencyGraph(
90+
packages: Array<Package>,
91+
excludedDependencyTypes?: Array<configDependencyType> | void
92+
) {
9093
let graph: Map<
9194
string,
9295
{ pkg: Package, dependencies: Array<string> }
@@ -103,7 +106,7 @@ export default class Project {
103106
for (let pkg of queue) {
104107
let name = pkg.config.getName();
105108
let dependencies = [];
106-
let allDependencies = pkg.getAllDependencies();
109+
let allDependencies = pkg.getAllDependencies(excludedDependencyTypes);
107110

108111
for (let [depName, depVersion] of allDependencies) {
109112
let match = packagesByName[depName];
@@ -135,10 +138,14 @@ export default class Project {
135138
return { graph, valid };
136139
}
137140

138-
async getDependentsGraph(packages: Array<Package>) {
141+
async getDependentsGraph(
142+
packages: Array<Package>,
143+
excludedDepTypes?: Array<configDependencyType>
144+
) {
139145
let graph = new Map();
140146
let { valid, graph: dependencyGraph } = await this.getDependencyGraph(
141-
packages
147+
packages,
148+
excludedDepTypes
142149
);
143150

144151
let dependentsLookup: {
@@ -184,7 +191,11 @@ export default class Project {
184191
} else if (spawnOpts.orderMode === 'parallel-nodes') {
185192
results = await this.runPackageTasksParallelNodes(packages, wrappedTask);
186193
} else {
187-
results = await this.runPackageTasksGraphParallel(packages, wrappedTask);
194+
results = await this.runPackageTasksGraphParallel(
195+
packages,
196+
wrappedTask,
197+
spawnOpts.excludeFromGraph
198+
);
188199
}
189200

190201
results.forEach(r => {
@@ -236,10 +247,12 @@ export default class Project {
236247

237248
async runPackageTasksGraphParallel<T>(
238249
packages: Array<Package>,
239-
task: GenericTask<T>
250+
task: GenericTask<T>,
251+
excludeDepTypesFromGraph: Array<configDependencyType> | void
240252
): Promise<Array<T>> {
241253
let { graph: dependentsGraph, valid } = await this.getDependencyGraph(
242-
packages
254+
packages,
255+
excludeDepTypesFromGraph
243256
);
244257

245258
let graph = new Map();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"private": true,
3+
"description": "Contains two workspaces that are only dependent as a devDependency.",
4+
"name": "dev-dependent-workspaces-only",
5+
"version": "1.0.0",
6+
"bolt": {
7+
"workspaces": [
8+
"packages/*"
9+
]
10+
}
11+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "bar",
3+
"version": "1.0.0"
4+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "foo",
3+
"version": "1.0.0",
4+
"devDependencies": {
5+
"bar": "^1.0.0"
6+
}
7+
}

src/__fixtures__/simple-project-with-multiple-depTypes/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
"private": true,
33
"name": "simple-project-with-multiple-dep-types",
44
"version": "1.0.0",
5+
"dependencies": {
6+
"depOnly": "^1.0.0"
7+
},
58
"devDependencies": {
9+
"devDepOnly": "^1.0.0",
610
"react": "^16.0.0"
711
},
812
"peerDependencies": {
13+
"peerDepOnly": "^1.0.0",
914
"react": "^16.0.0"
1015
},
1116
"bolt": {

src/__tests__/Package.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,37 @@ describe('Package', () => {
5555
expect(depTypes).toEqual([]);
5656
});
5757
});
58+
59+
describe('getAllDependencies', () => {
60+
it('should return all dependencies of the package', async () => {
61+
let filePath = path.join(
62+
f.find('simple-project-with-multiple-depTypes'),
63+
'package.json'
64+
);
65+
let pkg = await Package.init(filePath);
66+
let dependencies = pkg.getAllDependencies();
67+
let expected = new Map([
68+
['depOnly', '^1.0.0'],
69+
['devDepOnly', '^1.0.0'],
70+
['react', '^16.0.0'],
71+
['peerDepOnly', '^1.0.0']
72+
]);
73+
expect(dependencies).toEqual(expected);
74+
});
75+
76+
it('should filter out dependencies of a certain type when passed the excludedTypes arg', async () => {
77+
let filePath = path.join(
78+
f.find('simple-project-with-multiple-depTypes'),
79+
'package.json'
80+
);
81+
let pkg = await Package.init(filePath);
82+
let dependencies = pkg.getAllDependencies(['devDependencies']);
83+
let expected = new Map([
84+
['depOnly', '^1.0.0'],
85+
['peerDepOnly', '^1.0.0'],
86+
['react', '^16.0.0']
87+
]);
88+
expect(dependencies).toEqual(expected);
89+
});
90+
});
5891
});

src/__tests__/Project.test.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,46 @@ describe('Project', () => {
109109
});
110110
});
111111

112+
test('getDependencyGraph() without excluded dependency types', async () => {
113+
let project = await Project.init(f.find('dev-dependent-workspaces-only'));
114+
let packages = await project.getPackages();
115+
let { valid, graph } = await project.getDependencyGraph(packages);
116+
let expectedDependencies = {
117+
'dev-dependent-workspaces-only': [],
118+
bar: [],
119+
foo: ['bar']
120+
};
121+
122+
expect(valid).toEqual(true);
123+
expect(graph).toBeInstanceOf(Map);
124+
expect(graph.size).toBe(Object.keys(expectedDependencies).length);
125+
126+
Object.entries(expectedDependencies).forEach(([pkg, dependencies]) => {
127+
assertDependencies(graph, pkg, dependencies);
128+
});
129+
});
130+
131+
test('getDependencyGraph() with excluded dependency types', async () => {
132+
let project = await Project.init(f.find('dev-dependent-workspaces-only'));
133+
let packages = await project.getPackages();
134+
let { valid, graph } = await project.getDependencyGraph(packages, [
135+
'devDependencies'
136+
]);
137+
let expectedDependencies = {
138+
'dev-dependent-workspaces-only': [],
139+
bar: [],
140+
foo: []
141+
};
142+
143+
expect(valid).toEqual(true);
144+
expect(graph).toBeInstanceOf(Map);
145+
expect(graph.size).toBe(Object.keys(expectedDependencies).length);
146+
147+
Object.entries(expectedDependencies).forEach(([pkg, dependencies]) => {
148+
assertDependencies(graph, pkg, dependencies);
149+
});
150+
});
151+
112152
test('getDependentsGraph() with nested workspaces', async () => {
113153
let project = await Project.init(f.find('nested-workspaces'));
114154
let packages = await project.getPackages();
@@ -150,6 +190,44 @@ describe('Project', () => {
150190
});
151191
});
152192

193+
test('getDependentsGraph() without excluded dependency types', async () => {
194+
let project = await Project.init(f.find('dev-dependent-workspaces-only'));
195+
let packages = await project.getPackages();
196+
let { valid, graph } = await project.getDependentsGraph(packages);
197+
let expectedDependents = {
198+
bar: ['foo'],
199+
foo: []
200+
};
201+
202+
expect(valid).toEqual(true);
203+
expect(graph).toBeInstanceOf(Map);
204+
expect(graph.size).toBe(Object.keys(expectedDependents).length);
205+
206+
Object.entries(expectedDependents).forEach(([pkg, dependents]) => {
207+
assertDependents(graph, pkg, dependents);
208+
});
209+
});
210+
211+
test('getDependentsGraph() with excluded dependency types', async () => {
212+
let project = await Project.init(f.find('dev-dependent-workspaces-only'));
213+
let packages = await project.getPackages();
214+
let { valid, graph } = await project.getDependentsGraph(packages, [
215+
'devDependencies'
216+
]);
217+
let expectedDependents = {
218+
bar: [],
219+
foo: []
220+
};
221+
222+
expect(valid).toEqual(true);
223+
expect(graph).toBeInstanceOf(Map);
224+
expect(graph.size).toBe(Object.keys(expectedDependents).length);
225+
226+
Object.entries(expectedDependents).forEach(([pkg, dependents]) => {
227+
assertDependents(graph, pkg, dependents);
228+
});
229+
});
230+
153231
test('filterPackages() with no flags', async () => {
154232
let project = await Project.init(f.find('nested-workspaces'));
155233
let packages = await project.getPackages();
@@ -431,4 +509,42 @@ describe('Project', () => {
431509
}
432510
done();
433511
});
512+
513+
test('runPackageTasks() excludeFromGraph: devDependencies', async () => {
514+
let project = await Project.init(f.find('dev-dependent-workspaces-only'));
515+
let packages = await project.getPackages();
516+
let ops = [];
517+
518+
await project.runPackageTasks(
519+
packages,
520+
{ excludeFromGraph: ['devDependencies'] },
521+
async pkg => {
522+
ops.push('start:' + pkg.getName());
523+
await Promise.resolve();
524+
ops.push('end:' + pkg.getName());
525+
}
526+
);
527+
528+
expect(ops).toEqual(['start:bar', 'start:foo', 'end:bar', 'end:foo']);
529+
});
530+
531+
test('runPackageTasks() excludeFromGraph: devDependencies cycle', async () => {
532+
let project = await Project.init(f.find('dependent-workspaces-with-cycle'));
533+
let packages = await project.getPackages();
534+
let ops = [];
535+
536+
await project.runPackageTasks(
537+
packages,
538+
{ excludeFromGraph: ['devDependencies'] },
539+
async pkg => {
540+
ops.push('start:' + pkg.getName());
541+
await Promise.resolve();
542+
ops.push('end:' + pkg.getName());
543+
}
544+
);
545+
546+
expect(ops).toEqual(['start:bar', 'end:bar', 'start:foo', 'end:foo']);
547+
// There is not a cycle anymore now that we have excluded devDependencies
548+
expect(logger.warn).not.toHaveBeenCalled();
549+
});
434550
});

src/functions/getDependencyGraph.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// @flow
22
import Project from '../Project';
33
import * as yarn from '../utils/yarn';
4+
import type { configDependencyType } from '../types';
45

56
type Options = {
6-
cwd?: string
7+
cwd?: string,
8+
excludedTypes?: Array<configDependencyType>
79
};
810

911
type DependencyGraph = Map<string, Array<string>>;
@@ -18,7 +20,7 @@ export default async function getDependencyGraph(
1820
let {
1921
graph: dependencyGraph,
2022
valid: graphIsValid
21-
} = await project.getDependencyGraph(packages);
23+
} = await project.getDependencyGraph(packages, opts.excludedTypes);
2224

2325
if (!graphIsValid) {
2426
throw new Error('Dependency graph is not valid');

src/functions/getDependentsGraph.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// @flow
22
import Project from '../Project';
33
import * as yarn from '../utils/yarn';
4+
import type { configDependencyType } from '../types';
45

56
type Options = {
6-
cwd?: string
7+
cwd?: string,
8+
excludedTypes?: Array<configDependencyType>
79
};
810

911
type DependentsGraph = Map<string, Array<string>>;
@@ -18,7 +20,7 @@ export default async function getDependentsGraph(
1820
let {
1921
graph: dependentsGraph,
2022
valid: graphIsValid
21-
} = await project.getDependentsGraph(packages);
23+
} = await project.getDependentsGraph(packages, opts.excludedTypes);
2224

2325
if (!graphIsValid) {
2426
throw new Error('Dependents graph is not valid');

0 commit comments

Comments
 (0)