From e15479b6916f43c185a2085d17bfc912f3e0b1e5 Mon Sep 17 00:00:00 2001 From: Naymi Date: Fri, 5 Jul 2024 21:21:06 +0300 Subject: [PATCH] fix(core): recursive resolve deps on create command graph (#22989) ## introductory image ## Current Behavior ```shell nx exec --projects=my-node-app -- pwd TypeError: graph.dependencies[id] is not iterable at _findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:8:39) at _findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:11:23) at findCycle (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/tasks-runner/task-graph-utils.js:23:23) at createCommandGraph (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/commands-runner/create-command-graph.js:25:52) at getCommandProjects (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/commands-runner/get-command-projects.js:7:72) at runScriptAsNxTarget (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/exec.js:60:73) at Object.nxExecCommand (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/exec.js:44:16) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.handler (/Users/anatolijfedorov/prjcts/tmp/nx-nest-workspace/node_modules/nx/src/command-line/exec/command-object.js:11:13) ``` ## Expected Behavior ```shell nx exec --projects=my-node-app -- pwd /tmp/nx-nest-workspace/my-node-lib-2 /tmp/nx-nest-workspace/my-node-lib /tmp/nx-nest-workspace/apps/my-node-app ``` Fixes https://github.com/nrwl/nx/issues/22994 Added function that resolves dependencies recursively Co-authored-by: afedorov --- .../create-command-graph.spec.ts | 97 +++++++++++++++++++ .../commands-runner/create-command-graph.ts | 52 +++++++--- .../nx/src/tasks-runner/task-graph-utils.ts | 8 +- 3 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 packages/nx/src/commands-runner/create-command-graph.spec.ts diff --git a/packages/nx/src/commands-runner/create-command-graph.spec.ts b/packages/nx/src/commands-runner/create-command-graph.spec.ts new file mode 100644 index 0000000000000..66f0fc6668cb5 --- /dev/null +++ b/packages/nx/src/commands-runner/create-command-graph.spec.ts @@ -0,0 +1,97 @@ +import { source } from '@angular-devkit/schematics'; +import { createCommandGraph } from './create-command-graph'; +import { CommandGraph } from './command-graph'; + +export { createCommandGraph } from './create-command-graph'; + +describe('createCommandGraph', () => { + it('should create command graph with empty dependencies', () => { + const projectGraph = { + dependencies: {}, + nodes: {}, + }; + const projectNames = []; + const nxArgs = {}; + const result = createCommandGraph(projectGraph, projectNames, nxArgs); + expect(result).toEqual({ dependencies: {}, roots: [] }); + }); + + it('should create command graph with dependencies', () => { + const projectGraph = { + dependencies: { + dep1: [{ target: 'dep2', type: 'static', source: 'dep1' }], + dep2: [], + }, + nodes: { + dep1: { + type: 'lib' as 'lib', + name: 'dep1', + data: { + files: [], + root: 'dep1', + }, + }, + dep2: { + type: 'lib' as 'lib', + name: 'dep2', + data: { + files: [], + root: 'dep2', + }, + }, + }, + }; + const projectNames = ['dep1']; + const nxArgs = {}; + const result = createCommandGraph(projectGraph, projectNames, nxArgs); + expect(result).toEqual({ + dependencies: { dep1: ['dep2'], dep2: [] }, + roots: ['dep2'], + }); + }); + + it('should create command graph with nested dependencies', () => { + const projectGraph = { + dependencies: { + dep1: [{ target: 'dep2', type: 'static', source: 'dep1' }], + dep2: [], + dep3: [{ target: 'dep1', type: 'static', source: 'dep3' }], + }, + nodes: { + dep1: { + type: 'lib' as 'lib', + name: 'dep1', + data: { + files: [], + root: 'dep1', + }, + }, + dep2: { + type: 'lib' as 'lib', + name: 'dep2', + data: { + files: [], + root: 'dep2', + }, + }, + dep3: { + type: 'lib' as 'lib', + name: 'dep3', + data: { + files: [], + root: 'dep3', + }, + }, + }, + }; + const result = createCommandGraph( + projectGraph, + ['dep1', 'dep2', 'dep3'], + {} + ); + expect(result).toEqual({ + dependencies: { dep1: ['dep2'], dep2: [], dep3: ['dep1'] }, + roots: ['dep2'], + }); + }); +}); diff --git a/packages/nx/src/commands-runner/create-command-graph.ts b/packages/nx/src/commands-runner/create-command-graph.ts index 59459321de68f..e46f893438b35 100644 --- a/packages/nx/src/commands-runner/create-command-graph.ts +++ b/packages/nx/src/commands-runner/create-command-graph.ts @@ -4,6 +4,46 @@ import { NxArgs } from '../utils/command-line-utils'; import { output } from '../utils/output'; import { CommandGraph } from './command-graph'; +/** + * Make structure { lib: [dep], dep: [dep1], dep1: [] } from projectName lib and projectGraph + * @param projectGraph + * @param projectName + * @param resolved reference to an object that will contain resolved dependencies + * @returns + */ +const recursiveResolveDeps = ( + projectGraph: ProjectGraph, + projectName: string, + resolved: Record +) => { + if (projectGraph.dependencies[projectName].length === 0) { + // no deps - no resolve + resolved[projectName] = []; + return; + } + // if already resolved - just skip + if (resolved[projectName]) { + return resolved[projectName]; + } + + // deps string list + const projectDeps = [ + ...new Set( + projectGraph.dependencies[projectName] + .map((projectDep) => projectDep.target) + .filter((projectDep) => projectGraph.nodes[projectDep]) + ).values(), + ]; + + // define + resolved[projectName] = projectDeps; + if (projectDeps.length > 0) { + for (const dep of projectDeps) { + recursiveResolveDeps(projectGraph, dep, resolved); + } + } +}; + export function createCommandGraph( projectGraph: ProjectGraph, projectNames: string[], @@ -11,17 +51,7 @@ export function createCommandGraph( ): CommandGraph { const dependencies: Record = {}; for (const projectName of projectNames) { - if (projectGraph.dependencies[projectName].length >= 1) { - dependencies[projectName] = [ - ...new Set( - projectGraph.dependencies[projectName] - .map((projectDep) => projectDep.target) - .filter((projectDep) => projectGraph.nodes[projectDep]) - ).values(), - ]; - } else { - dependencies[projectName] = []; - } + recursiveResolveDeps(projectGraph, projectName, dependencies); } const roots = Object.keys(dependencies).filter( (d) => dependencies[d].length === 0 diff --git a/packages/nx/src/tasks-runner/task-graph-utils.ts b/packages/nx/src/tasks-runner/task-graph-utils.ts index 770f98d79dee9..c2f6fe4f43c86 100644 --- a/packages/nx/src/tasks-runner/task-graph-utils.ts +++ b/packages/nx/src/tasks-runner/task-graph-utils.ts @@ -15,16 +15,16 @@ function _findCycle( return null; } -export function findCycle(taskGraph: { +export function findCycle(graph: { dependencies: Record; }): string[] | null { const visited = {}; - for (const t of Object.keys(taskGraph.dependencies)) { + for (const t of Object.keys(graph.dependencies)) { visited[t] = false; } - for (const t of Object.keys(taskGraph.dependencies)) { - const cycle = _findCycle(taskGraph, t, visited, [t]); + for (const t of Object.keys(graph.dependencies)) { + const cycle = _findCycle(graph, t, visited, [t]); if (cycle) return cycle; }