Skip to content

Commit

Permalink
Unify dependency graph generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Nemanja Tesic committed Oct 15, 2024
1 parent 85643be commit 98ae967
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/__tests__/monorepo/getPackageGraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,6 @@ describe('getPackageGraph', () => {

await expect(async () => {
await getPackageGraphPackageNames(['foo', 'bar'], packageInfos)
}).rejects.toThrow(`Failed to create the package graph. Affected packages size (2) is different from the created graph size (0). Affected packages: foo, bar, created graph packages:`);
}).rejects.toThrow(`Package info is missing for foo.`);
});
});
32 changes: 32 additions & 0 deletions src/monorepo/getPackageDependencyGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { PackageInfos } from '../types/PackageInfo';

/**
* @returns Each element is a tuple of [dependency, dependent] where `dependent` depends on `dependency`.
* These are the edges of the dependency graph.
*/
export function getPackageDependencyGraph(packages: string[], packageInfos: PackageInfos): [string | undefined, string][] {
const packageSet = new Set(packages);
const dependencyGraph: [string | undefined, string][] = [];

for (const pkgName of packageSet) {
const info = packageInfos[pkgName];
if (!info) {
throw new Error(`Package info is missing for ${pkgName}.`);
}

const allDeps = new Set(
[info.dependencies, info.devDependencies, info.peerDependencies, info.optionalDependencies]
.flatMap(deps => Object.keys(deps || {}))
.filter(pkg => packageSet.has(pkg))
);
if (allDeps.size) {
for (const depPkgName of allDeps) {
dependencyGraph.push([depPkgName, pkgName]);
}
} else {
dependencyGraph.push([undefined, pkgName]);
}
}

return dependencyGraph;
}
42 changes: 8 additions & 34 deletions src/monorepo/getPackageGraph.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,24 @@
import { createPackageGraph, PackageDependency, PackageGraph } from 'workspace-tools';
import { PackageInfo, PackageInfos } from '../types/PackageInfo';
import pGraph, { DependencyList, PGraphNodeMap } from 'p-graph';
import pGraph, { PGraphNodeMap } from 'p-graph';
import { getPackageDependencyGraph } from './getPackageDependencyGraph';

export function getPackageGraph(
affectedPackages: Iterable<string>,
packageInfos: PackageInfos,
runHook: (packageInfo: PackageInfo) => Promise<void>
) {
const packageGraph: PackageGraph = createPackageGraphInternal(packageInfos, Array.from(affectedPackages));

const nodeMap: PGraphNodeMap = new Map();
for (const packageToBump of affectedPackages) {
nodeMap.set(packageToBump, {
run: async () => await runHook(packageInfos[packageToBump]),
});
}

return pGraph(nodeMap, createDependencyList(packageGraph.dependencies));
}

function createDependencyList(dependencies: PackageDependency[]): DependencyList {
return dependencies.map(dependency => [dependency.dependency, dependency.name]);
const dependencyGraph: [string | undefined, string][] = getPackageDependencyGraph(Array.from(affectedPackages), packageInfos);
const filteredDependencyGraph = filterDependencyGraph(dependencyGraph);
return pGraph(nodeMap, filteredDependencyGraph);
}

/**
* @returns A package graph that only contains the affected packages and their dependencies. This is done by filtering the
* original package graph, which could contain more packages than the affected packages. This can happen if some scope is
* provided to the command which filters some package.
*/
function createPackageGraphInternal(packageInfos: PackageInfos, affectedPackages: string[]): PackageGraph {
const packageGraph: PackageGraph = createPackageGraph(packageInfos, {
namePatterns: affectedPackages,
includeDependents: true,
includeDependencies: true,
withDevDependencies: true,
withPeerDependencies: true,
});

const filteredGraph: PackageGraph = {
packages: packageGraph.packages.filter(pkg => affectedPackages.includes(pkg)),
dependencies: packageGraph.dependencies.filter(dep => affectedPackages.includes(dep.name) && affectedPackages.includes(dep.dependency)),
};

if (filteredGraph.packages.length !== affectedPackages.length) {
throw new Error(`Failed to create the package graph. Affected packages size (${affectedPackages.length}) is different from the created graph size (${filteredGraph.packages.length}). Affected packages: ${affectedPackages.join(', ')}, created graph packages: ${filteredGraph.packages.join(', ')}`);
}

return filteredGraph;
}
function filterDependencyGraph(dependencyGraph: [string | undefined, string][]): [string, string][] {
return dependencyGraph.filter(([dep, _]) => dep !== undefined) as [string, string][];
}
26 changes: 2 additions & 24 deletions src/publish/toposortPackages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import toposort from 'toposort';
import { PackageInfos } from '../types/PackageInfo';
import { getPackageDependencyGraph } from '../monorepo/getPackageDependencyGraph';

/**
* Topologically sort the packages based on their dependency graph.
Expand All @@ -8,31 +9,8 @@ import { PackageInfos } from '../types/PackageInfo';
* @param packageInfos PackagesInfos for the sorted packages.
*/
export function toposortPackages(packages: string[], packageInfos: PackageInfos): string[] {
const packageSet = new Set(packages);
const dependencyGraph: [string | undefined, string][] = [];

for (const pkgName of packageSet) {
const info = packageInfos[pkgName];
if (!info) {
throw new Error(`Package info is missing for ${pkgName}.`);
}

const allDeps = new Set(
[info.dependencies, info.devDependencies, info.peerDependencies, info.optionalDependencies]
.flatMap(deps => Object.keys(deps || {}))
.filter(pkg => packageSet.has(pkg))
);
if (allDeps.size) {
for (const depPkgName of allDeps) {
dependencyGraph.push([depPkgName, pkgName]);
}
} else {
dependencyGraph.push([undefined, pkgName]);
}
}

try {
return toposort(dependencyGraph).filter((pkg): pkg is string => !!pkg);
return toposort(getPackageDependencyGraph(packages, packageInfos)).filter((pkg): pkg is string => !!pkg);
} catch (err) {
throw new Error(`Failed to topologically sort packages: ${(err as Error)?.message}`);
}
Expand Down

0 comments on commit 98ae967

Please sign in to comment.