Skip to content

Commit a0d5ba7

Browse files
feat: add new functionality to create a graph from unchanged packages
This adds a function that creates a subgraph of only new and changed package nodes in a graph.
1 parent eb98b7a commit a0d5ba7

File tree

50 files changed

+2899
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2899
-1
lines changed
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { DepGraph, DepGraphInternal, NodeInfo, PkgInfo } from './types';
2+
import { DepGraphImpl } from './dep-graph';
3+
import { DepGraphBuilder } from './builder';
4+
import { eventLoopSpinner } from 'event-loop-spinner';
5+
6+
type NodeId = string;
7+
8+
/**
9+
* Creates an induced subgraph of {@param graphB} with only packages
10+
* that are not present in {@param graphA} or have a different version.
11+
*
12+
* @param graphA
13+
* @param graphB
14+
*/
15+
export async function createChangedPackagesGraph(
16+
graphA: DepGraph,
17+
graphB: DepGraph,
18+
): Promise<DepGraph> {
19+
const depGraph = graphB as DepGraphInternal;
20+
21+
const graphAPackageIds = new Set(
22+
graphA.getDepPkgs().map(DepGraphImpl.getPkgId),
23+
);
24+
25+
const addedOrUpdatedPackages: PkgInfo[] = depGraph
26+
.getDepPkgs()
27+
.filter((pkg) => !graphAPackageIds.has(DepGraphImpl.getPkgId(pkg)));
28+
29+
const depGraphBuilder = new DepGraphBuilder(
30+
depGraph.pkgManager,
31+
depGraph.rootPkg,
32+
);
33+
34+
const parentQueue: [parentId: NodeId, nodeId: NodeId][] = [];
35+
for (const changedPackage of addedOrUpdatedPackages) {
36+
for (const changedNodeId of depGraph.getPkgNodeIds(changedPackage)) {
37+
//we add all nodes with new and changed packages to the new graph.
38+
//a newly added node will also have its dependencies added here, since they are "new".
39+
depGraphBuilder.addPkgNode(
40+
depGraph.getNodePkg(changedNodeId),
41+
changedNodeId,
42+
getNodeInfo(depGraph, changedNodeId),
43+
);
44+
45+
//Push all direct parents of the changed nodes to a queue to later build up a path to root from that node
46+
for (const parentId of depGraph.getNodeParentsNodeIds(changedNodeId)) {
47+
parentQueue.push([parentId, changedNodeId]);
48+
49+
if (eventLoopSpinner.isStarving()) {
50+
await eventLoopSpinner.spin();
51+
}
52+
}
53+
}
54+
}
55+
56+
//add direct and transitive parents for the changed nodes
57+
const visited = new Set([depGraph.rootNodeId]);
58+
59+
while (parentQueue.length > 0) {
60+
const [nodeId, dependencyNodeId] = parentQueue.pop()!;
61+
if (visited.has(nodeId)) {
62+
//ensure we link parents even if visited through another path
63+
depGraphBuilder.connectDep(nodeId, dependencyNodeId);
64+
continue;
65+
}
66+
67+
visited.add(nodeId);
68+
69+
depGraphBuilder.addPkgNode(
70+
depGraph.getNodePkg(nodeId),
71+
nodeId,
72+
getNodeInfo(depGraph, nodeId),
73+
);
74+
depGraphBuilder.connectDep(nodeId, dependencyNodeId);
75+
76+
for (const parentId of depGraph.getNodeParentsNodeIds(nodeId)) {
77+
parentQueue.push([parentId, nodeId]);
78+
79+
if (eventLoopSpinner.isStarving()) {
80+
await eventLoopSpinner.spin();
81+
}
82+
}
83+
}
84+
85+
return depGraphBuilder.build();
86+
}
87+
88+
function getNodeInfo(
89+
depGraph: DepGraphInternal,
90+
nodeId: string,
91+
): NodeInfo | undefined {
92+
const nodeInfo: NodeInfo = depGraph.getNode(nodeId);
93+
if (!nodeInfo || Object.keys(nodeInfo).length === 0) {
94+
return undefined;
95+
}
96+
return nodeInfo;
97+
}

src/core/dep-graph.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class DepGraphImpl implements types.DepGraphInternal {
255255
compareRoot: boolean,
256256
traversedPairs = new Set<string>(),
257257
): boolean {
258-
// Skip root nodes comparision if needed.
258+
// Skip root nodes comparison if needed.
259259
if (
260260
compareRoot ||
261261
(nodeIdA !== graphA.rootNodeId && nodeIdB !== graphB.rootNodeId)

src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ export {
88
} from './core/types';
99
export { createFromJSON } from './core/create-from-json';
1010
export { DepGraphBuilder } from './core/builder';
11+
export { createChangedPackagesGraph } from './core/create-changed-packages-graph';
1112

1213
import * as Errors from './core/errors';
14+
1315
export { Errors };
1416

1517
import * as legacy from './legacy';
18+
1619
export { legacy };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as depGraphLib from '../../src';
2+
import * as helpers from '../helpers';
3+
import { createChangedPackagesGraph } from '../../src';
4+
5+
describe('filter-unchanged-packages', () => {
6+
it.each`
7+
fixture
8+
${'equals/simple.json'}
9+
${'cyclic-complex-dep-graph.json'}
10+
${'goof-graph.json'}
11+
`(
12+
'result and $fixture are equals for empty initial graph',
13+
async ({ fixture }) => {
14+
const graphB = depGraphLib.createFromJSON(helpers.loadFixture(fixture));
15+
16+
const graphA = new depGraphLib.DepGraphBuilder(
17+
graphB.pkgManager,
18+
graphB.rootPkg,
19+
).build();
20+
21+
const result = await createChangedPackagesGraph(graphA, graphB);
22+
expect(graphB.equals(result)).toBe(true);
23+
},
24+
);
25+
26+
it.each`
27+
fixtureA | fixtureB | expected
28+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-added.json'} | ${'changed-packages-graph/graph-direct-dep-added-expected.json'}
29+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-changed-cycle.json'} | ${'changed-packages-graph/graph-direct-dep-changed-cycle-expected.json'}
30+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-changed.json'} | ${'changed-packages-graph/graph-direct-dep-changed-expected.json'}
31+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-removed.json'} | ${'changed-packages-graph/graph-direct-dep-removed-expected.json'}
32+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-direct-dep-with-exiting-transitive-dep-added.json'} | ${'changed-packages-graph/graph-direct-dep-with-exiting-transitive-dep-added-expected.json'}
33+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-root-and-direct-dep-changed.json'} | ${'changed-packages-graph/graph-root-and-direct-dep-changed-expected.json'}
34+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-root-changed-expected.json'} | ${'changed-packages-graph/graph-root-changed-expected.json'}
35+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-as-direct-dep.json'} | ${'changed-packages-graph/graph-transitive-dep-as-direct-dep-expected.json'}
36+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-cycle.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-cycle-expected.json'}
37+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-changed.json'} | ${'changed-packages-graph/graph-transitive-dep-changed-expected.json'}
38+
${'changed-packages-graph/graph.json'} | ${'changed-packages-graph/graph-transitive-dep-removed.json'} | ${'changed-packages-graph/graph-transitive-dep-removed-expected.json'}
39+
`(
40+
'result is $expected for $fixtureA and $fixtureB',
41+
async ({ fixtureA, fixtureB, expected }) => {
42+
const graphA = depGraphLib.createFromJSON(helpers.loadFixture(fixtureA));
43+
44+
const graphB = depGraphLib.createFromJSON(helpers.loadFixture(fixtureB));
45+
46+
const expectedResult = depGraphLib.createFromJSON(
47+
helpers.loadFixture(expected),
48+
);
49+
50+
const result = await createChangedPackagesGraph(graphA, graphB);
51+
expect(expectedResult.equals(result, { compareRoot: true })).toBe(true);
52+
},
53+
);
54+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"schemaVersion": "1.3.0",
3+
"pkgManager": {
4+
"name": "pip"
5+
},
6+
"pkgs": [
7+
{
8+
"id": "a@1",
9+
"info": {
10+
"name": "a",
11+
"version": "1"
12+
}
13+
},
14+
{
15+
16+
"info": {
17+
"name": "l",
18+
"version": "0.1"
19+
}
20+
},
21+
{
22+
23+
"info": {
24+
"name": "m",
25+
"version": "1.2"
26+
}
27+
}
28+
],
29+
"graph": {
30+
"rootNodeId": "root-node",
31+
"nodes": [
32+
{
33+
"nodeId": "12",
34+
"pkgId": "[email protected]",
35+
"deps": [
36+
{
37+
"nodeId": "13"
38+
}
39+
]
40+
},
41+
{
42+
"nodeId": "13",
43+
"pkgId": "[email protected]",
44+
"deps": []
45+
},
46+
{
47+
"nodeId": "root-node",
48+
"pkgId": "a@1",
49+
"deps": [
50+
{
51+
"nodeId": "12"
52+
}
53+
]
54+
}
55+
]
56+
}
57+
}
Loading

0 commit comments

Comments
 (0)