nx/packages/devkit/src/project-graph/project-graph-builder.ts

160 lines
4.3 KiB
TypeScript

import type {
FileData,
ProjectFileMap,
ProjectGraph,
ProjectGraphDependency,
ProjectGraphNode,
} from './interfaces';
import { DependencyType } from './interfaces';
/**
* Builder for adding nodes and dependencies to a {@link ProjectGraph}
*/
export class ProjectGraphBuilder {
readonly graph: ProjectGraph;
constructor(g?: ProjectGraph) {
if (g) {
this.graph = g;
} else {
this.graph = {
nodes: {},
dependencies: {},
};
}
}
/**
* Adds a project node to the project graph
*/
addNode(node: ProjectGraphNode): void {
// Check if project with the same name already exists
if (this.graph.nodes[node.name]) {
// Throw if existing project is of a different type
if (this.graph.nodes[node.name].type !== node.type) {
throw new Error(
`Multiple projects are named "${node.name}". One is of type "${
node.type
}" and the other is of type "${
this.graph.nodes[node.name].type
}". Please resolve the conflicting project names.`
);
}
}
this.graph.nodes[node.name] = node;
this.graph.dependencies[node.name] = [];
}
/**
* Adds a dependency from source project to target project
*/
addImplicitDependency(
sourceProjectName: string,
targetProjectName: string
): void {
if (sourceProjectName === targetProjectName) {
return;
}
if (!this.graph.nodes[sourceProjectName]) {
throw new Error(`Source project does not exist: ${sourceProjectName}`);
}
if (!this.graph.nodes[targetProjectName]) {
throw new Error(`Target project does not exist: ${targetProjectName}`);
}
this.graph.dependencies[sourceProjectName].push({
source: sourceProjectName,
target: targetProjectName,
type: DependencyType.implicit,
});
}
/**
* Add an explicit dependency from a file in source project to target project
*/
addExplicitDependency(
sourceProjectName: string,
sourceProjectFile: string,
targetProjectName: string
): void {
if (sourceProjectName === targetProjectName) {
return;
}
const source = this.graph.nodes[sourceProjectName];
if (!source) {
throw new Error(`Source project does not exist: ${sourceProjectName}`);
}
if (!this.graph.nodes[targetProjectName]) {
throw new Error(`Target project does not exist: ${targetProjectName}`);
}
const fileData = source.data.files.find(
(f) => f.file === sourceProjectFile
);
if (!fileData) {
throw new Error(
`Source project ${sourceProjectName} does not have a file: ${sourceProjectFile}`
);
}
if (!fileData.deps) {
fileData.deps = [];
}
if (!fileData.deps.find((t) => t === targetProjectName)) {
fileData.deps.push(targetProjectName);
}
}
/**
* Set version of the project graph
*/
setVersion(version: string): void {
this.graph.version = version;
}
getUpdatedProjectGraph(): ProjectGraph {
for (const sourceProject of Object.keys(this.graph.nodes)) {
const alreadySetTargetProjects =
this.calculateAlreadySetTargetDeps(sourceProject);
this.graph.dependencies[sourceProject] = [
...alreadySetTargetProjects.values(),
];
const fileDeps = this.calculateTargetDepsFromFiles(sourceProject);
for (const targetProject of fileDeps) {
if (!alreadySetTargetProjects.has(targetProject)) {
this.graph.dependencies[sourceProject].push({
source: sourceProject,
target: targetProject,
type: DependencyType.static,
});
}
}
}
return this.graph;
}
private calculateTargetDepsFromFiles(sourceProject: string) {
const fileDeps = new Set<string>();
const files = this.graph.nodes[sourceProject].data.files;
if (!files) return fileDeps;
for (let f of files) {
if (f.deps) {
for (let p of f.deps) {
fileDeps.add(p);
}
}
}
return fileDeps;
}
private calculateAlreadySetTargetDeps(sourceProject: string) {
const alreadySetTargetProjects = new Map<string, ProjectGraphDependency>();
for (const d of this.graph.dependencies[sourceProject]) {
alreadySetTargetProjects.set(d.target, d);
}
return alreadySetTargetProjects;
}
}