From 8d4855de611b3e3d75b77f7e1286a8db51b7fabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Jona=C5=A1?= Date: Tue, 21 Feb 2023 16:39:44 +0100 Subject: [PATCH] feat(core): add full dependency information to project graph file dependencies (#14893) --- docs/generated/devkit/nrwl_devkit.md | 13 - .../packages/devkit/documents/index.md | 13 - .../packages/devkit/documents/nrwl_devkit.md | 13 - .../recipes/plugins/project-graph-plugins.md | 30 ++- e2e/nx-run/src/affected-graph.test.ts | 2 +- .../src/app/mock-project-graph-service.ts | 9 +- .../src/lib/util-cytoscape/render-graph.ts | 4 +- .../delegate-build.impl.spec.ts | 7 + .../cypress-component-configuration.spec.ts | 8 + .../storybook-configuration.spec.ts | 8 + .../update-cy-mount-usage.spec.ts | 9 + packages/devkit/nx-reexports-pre16.ts | 1 - .../rules/enforce-module-boundaries.spec.ts | 87 +++++- .../eslint-plugin-nx/src/utils/graph-utils.ts | 4 +- packages/nx/src/config/project-graph.ts | 13 +- packages/nx/src/devkit-exports.ts | 1 - packages/nx/src/hasher/hasher.ts | 2 + packages/nx/src/lock-file/npm-parser.ts | 6 +- packages/nx/src/lock-file/pnpm-parser.ts | 2 +- .../nx/src/lock-file/project-graph-pruning.ts | 8 +- packages/nx/src/lock-file/yarn-parser.ts | 2 +- ...ypescript-and-package-json-dependencies.ts | 7 +- .../explicit-package-json-dependencies.ts | 3 +- .../explicit-project-dependencies.spec.ts | 27 +- .../explicit-project-dependencies.ts | 16 +- .../project-graph/build-project-graph.spec.ts | 8 +- .../src/project-graph/build-project-graph.ts | 49 ++-- .../src/project-graph/nx-deps-cache.spec.ts | 4 +- .../nx/src/project-graph/nx-deps-cache.ts | 4 +- .../project-graph-builder.spec.ts | 46 +++- .../project-graph/project-graph-builder.ts | 250 ++++++++++++------ .../nx/src/project-graph/project-graph.ts | 51 ++-- .../configuration.spec.ts | 8 + .../cypress-component-configuration.spec.ts | 8 + .../configuration.spec.ts | 8 + .../configuration-nested.spec.ts | 8 + .../configuration/configuration-v7.spec.ts | 8 + .../configuration/configuration.spec.ts | 8 + .../migrate-defaults-5-to-6.spec.ts | 8 + .../migrate-stories-to-6-2.spec.ts | 8 + .../storybook/src/utils/utilities.spec.ts | 20 +- .../remove/lib/check-targets.spec.ts | 8 + 42 files changed, 562 insertions(+), 237 deletions(-) diff --git a/docs/generated/devkit/nrwl_devkit.md b/docs/generated/devkit/nrwl_devkit.md index eab5a0242e..1f81d44d0f 100644 --- a/docs/generated/devkit/nrwl_devkit.md +++ b/docs/generated/devkit/nrwl_devkit.md @@ -48,7 +48,6 @@ It only uses language primitives and immutable objects - [ProjectGraphExternalNode](../../devkit/documents/nrwl_devkit#projectgraphexternalnode) - [ProjectGraphProcessorContext](../../devkit/documents/nrwl_devkit#projectgraphprocessorcontext) - [ProjectGraphProjectNode](../../devkit/documents/nrwl_devkit#projectgraphprojectnode) -- [ProjectGraphV4](../../devkit/documents/nrwl_devkit#projectgraphv4) ### Tree Interfaces @@ -301,18 +300,6 @@ A plugin for Nx --- -### ProjectGraphV4 - -• **ProjectGraphV4**<`T`\>: `Object` - -#### Type parameters - -| Name | Type | -| :--- | :---- | -| `T` | `any` | - ---- - ## Tree Interfaces ### FileChange diff --git a/docs/generated/packages/devkit/documents/index.md b/docs/generated/packages/devkit/documents/index.md index eab5a0242e..1f81d44d0f 100644 --- a/docs/generated/packages/devkit/documents/index.md +++ b/docs/generated/packages/devkit/documents/index.md @@ -48,7 +48,6 @@ It only uses language primitives and immutable objects - [ProjectGraphExternalNode](../../devkit/documents/nrwl_devkit#projectgraphexternalnode) - [ProjectGraphProcessorContext](../../devkit/documents/nrwl_devkit#projectgraphprocessorcontext) - [ProjectGraphProjectNode](../../devkit/documents/nrwl_devkit#projectgraphprojectnode) -- [ProjectGraphV4](../../devkit/documents/nrwl_devkit#projectgraphv4) ### Tree Interfaces @@ -301,18 +300,6 @@ A plugin for Nx --- -### ProjectGraphV4 - -• **ProjectGraphV4**<`T`\>: `Object` - -#### Type parameters - -| Name | Type | -| :--- | :---- | -| `T` | `any` | - ---- - ## Tree Interfaces ### FileChange diff --git a/docs/generated/packages/devkit/documents/nrwl_devkit.md b/docs/generated/packages/devkit/documents/nrwl_devkit.md index eab5a0242e..1f81d44d0f 100644 --- a/docs/generated/packages/devkit/documents/nrwl_devkit.md +++ b/docs/generated/packages/devkit/documents/nrwl_devkit.md @@ -48,7 +48,6 @@ It only uses language primitives and immutable objects - [ProjectGraphExternalNode](../../devkit/documents/nrwl_devkit#projectgraphexternalnode) - [ProjectGraphProcessorContext](../../devkit/documents/nrwl_devkit#projectgraphprocessorcontext) - [ProjectGraphProjectNode](../../devkit/documents/nrwl_devkit#projectgraphprojectnode) -- [ProjectGraphV4](../../devkit/documents/nrwl_devkit#projectgraphv4) ### Tree Interfaces @@ -301,18 +300,6 @@ A plugin for Nx --- -### ProjectGraphV4 - -• **ProjectGraphV4**<`T`\>: `Object` - -#### Type parameters - -| Name | Type | -| :--- | :---- | -| `T` | `any` | - ---- - ## Tree Interfaces ### FileChange diff --git a/docs/shared/recipes/plugins/project-graph-plugins.md b/docs/shared/recipes/plugins/project-graph-plugins.md index f038fe0b5e..3c9da43883 100644 --- a/docs/shared/recipes/plugins/project-graph-plugins.md +++ b/docs/shared/recipes/plugins/project-graph-plugins.md @@ -86,11 +86,9 @@ You can create 2 types of dependencies. ### Implicit Dependencies -An implicit dependency is not associated with any file, and can be crated as follows: +An implicit dependency is not associated with any file, and can be created as follows: ```typescript -import { DependencyType } from '@nrwl/devkit'; - // Add a new edge builder.addImplicitDependency('existing-project', 'new-project'); ``` @@ -101,23 +99,37 @@ Even though the plugin is written in JavaScript, resolving dependencies of diffe Because an implicit dependency is not associated with any file, Nx doesn't know when it might change, so it will be recomputed every time. -## Explicit Dependencies +## Static Dependencies Nx knows what files have changed since the last invocation. Only those files will be present in the provided `filesToProcess`. You can associate a dependency with a particular file (e.g., if that file contains an import). +```typescript +// Add a new edge +builder.addStaticDependency( + 'existing-project', + 'new-project', + 'libs/existing-project/src/index.ts' +); +``` + +If a file hasn't changed since the last invocation, it doesn't need to be reanalyzed. Nx knows what dependencies are associated with what files, so it will reuse this information for the files that haven't changed. + +## Dynamic Dependencies + +Dynamic dependencies are a special type of explicit dependencies. In contrast to standard `explicit` dependencies, they are only imported in the runtime under specific conditions. +A typical example would be lazy-loaded routes. Having separation between these two allows us to identify situations where static import breaks the lazy-loading. + ```typescript import { DependencyType } from '@nrwl/devkit'; // Add a new edge -builder.addExplicitDependency( +builder.addDynamicDependency( 'existing-project', - 'libs/existing-project/src/index.ts', - 'new-project' + 'lazy-route', + 'libs/existing-project/src/router-setup.ts' ); ``` -If a file hasn't changed since the last invocation, it doesn't need to be reanalyzed. Nx knows what dependencies are associated with what files, so it will reuse this information for the files that haven't changed. - ## Visualizing the Project Graph You can then visualize the project graph as described [here](/core-features/explore-graph). However, there is a cache that Nx uses to avoid recalculating the project graph as much as possible. As you develop your project graph plugin, it might be a good idea to set the following environment variable to disable the project graph cache: `NX_CACHE_PROJECT_GRAPH=false`. diff --git a/e2e/nx-run/src/affected-graph.test.ts b/e2e/nx-run/src/affected-graph.test.ts index afc1194376..c8c7ad47a8 100644 --- a/e2e/nx-run/src/affected-graph.test.ts +++ b/e2e/nx-run/src/affected-graph.test.ts @@ -472,7 +472,7 @@ describe('Nx Affected and Graph Tests', () => { target: mylib, type: 'static', }, - { source: myapp, target: mylib2, type: 'static' }, + { source: myapp, target: mylib2, type: 'dynamic' }, ], [myappE2e]: [ { diff --git a/graph/client/src/app/mock-project-graph-service.ts b/graph/client/src/app/mock-project-graph-service.ts index bd79ddd478..2c7162d43a 100644 --- a/graph/client/src/app/mock-project-graph-service.ts +++ b/graph/client/src/app/mock-project-graph-service.ts @@ -1,5 +1,6 @@ // nx-ignore-next-line import type { + DependencyType, ProjectGraphDependency, ProjectGraphProjectNode, } from '@nrwl/devkit'; @@ -28,7 +29,13 @@ export class MockProjectGraphService implements ProjectGraphService { { file: 'some/file.ts', hash: 'ecccd8481d2e5eae0e59928be1bc4c2d071729d7', - deps: ['existing-lib-1'], + dependencies: [ + { + target: 'existing-lib-1', + source: 'existing-app-1', + type: 'static' as DependencyType, + }, + ], }, ], }, diff --git a/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts b/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts index 1cc3fe1454..d466ed4026 100644 --- a/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts +++ b/graph/ui-graph/src/lib/util-cytoscape/render-graph.ts @@ -281,7 +281,9 @@ export class RenderGraph { .source() .data('files') ?.filter( - (file) => file.deps && file.deps.includes(edge.target().id()) + (file) => + file.dependencies && + file.dependencies.find((d) => d.target === edge.target().id()) ) .map((file) => { return { diff --git a/packages/angular/src/executors/delegate-build/delegate-build.impl.spec.ts b/packages/angular/src/executors/delegate-build/delegate-build.impl.spec.ts index f4771401da..c4eb89ba4e 100644 --- a/packages/angular/src/executors/delegate-build/delegate-build.impl.spec.ts +++ b/packages/angular/src/executors/delegate-build/delegate-build.impl.spec.ts @@ -1,6 +1,13 @@ jest.mock('@nrwl/devkit'); jest.mock('@nrwl/devkit'); jest.mock('@nrwl/workspace/src/utilities/buildable-libs-utils'); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + readCachedProjectGraph: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); import type { ExecutorContext, Target } from '@nrwl/devkit'; import * as devkit from '@nrwl/devkit'; diff --git a/packages/angular/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts b/packages/angular/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts index 0e82b01dff..4f84ec5707 100644 --- a/packages/angular/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts +++ b/packages/angular/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts @@ -22,6 +22,14 @@ jest.mock('@nrwl/devkit', () => ({ .fn() .mockImplementation(async () => projectGraph), })); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + readCachedProjectGraph: jest + .fn() + .mockImplementation(async () => projectGraph), +})); + describe('Cypress Component Testing Configuration', () => { let tree: Tree; let mockedInstalledCypressVersion: jest.Mock< diff --git a/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts b/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts index ddd572c949..542bac6991 100644 --- a/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts +++ b/packages/angular/src/generators/storybook-configuration/storybook-configuration.spec.ts @@ -11,6 +11,14 @@ import { storybookConfigurationGenerator } from './storybook-configuration'; // need to mock cypress otherwise it'll use the nx installed version from package.json // which is v9 while we are testing for the new v10 version jest.mock('@nrwl/cypress/src/utils/cypress-version'); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + function listFiles(tree: Tree): string[] { const files = new Set(); tree.listChanges().forEach((change) => { diff --git a/packages/cypress/src/migrations/update-15-0-0/update-cy-mount-usage.spec.ts b/packages/cypress/src/migrations/update-15-0-0/update-cy-mount-usage.spec.ts index 8682050284..bc5bf7024f 100644 --- a/packages/cypress/src/migrations/update-15-0-0/update-cy-mount-usage.spec.ts +++ b/packages/cypress/src/migrations/update-15-0-0/update-cy-mount-usage.spec.ts @@ -12,7 +12,16 @@ import { } from './update-cy-mount-usage'; import { libraryGenerator } from '@nrwl/workspace'; import { cypressComponentProject } from '../../generators/cypress-component-project/cypress-component-project'; + jest.mock('../../utils/cypress-version'); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('update cy.mount usage', () => { let tree: Tree; let mockedInstalledCypressVersion: jest.Mock< diff --git a/packages/devkit/nx-reexports-pre16.ts b/packages/devkit/nx-reexports-pre16.ts index 67f256ef19..eb230de6f2 100644 --- a/packages/devkit/nx-reexports-pre16.ts +++ b/packages/devkit/nx-reexports-pre16.ts @@ -172,7 +172,6 @@ export type { ProjectFileMap, FileData, ProjectGraph, - ProjectGraphV4, ProjectGraphDependency, ProjectGraphNode, ProjectGraphProjectNode, diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts index 97df03e46d..fc87f3a862 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts @@ -1,6 +1,10 @@ import 'nx/src/utils/testing/mock-fs'; -import type { FileData, ProjectGraph } from '@nrwl/devkit'; +import type { + FileData, + ProjectGraph, + ProjectGraphDependency, +} from '@nrwl/devkit'; import { DependencyType } from '@nrwl/devkit'; import * as parser from '@typescript-eslint/parser'; import { TSESLint } from '@typescript-eslint/utils'; @@ -284,7 +288,13 @@ describe('Enforce Module Boundaries (eslint)', () => { implicitDependencies: [], targets: {}, files: [ - createFile(`libs/dependsOnPrivate/src/index.ts`, ['privateName']), + createFile(`libs/dependsOnPrivate/src/index.ts`, [ + { + source: 'dependsOnPrivateName', + type: 'static', + target: 'privateName', + }, + ]), ], }, }, @@ -298,7 +308,11 @@ describe('Enforce Module Boundaries (eslint)', () => { targets: {}, files: [ createFile(`libs/dependsOnPrivate2/src/index.ts`, [ - 'privateName', + { + source: 'dependsOnPrivateName2', + type: 'static', + target: 'privateName', + }, ]), ], }, @@ -313,8 +327,12 @@ describe('Enforce Module Boundaries (eslint)', () => { targets: {}, files: [ createFile(`libs/private/src/index.ts`, [ - 'untaggedName', - 'taggedName', + { + source: 'privateName', + type: 'static', + target: 'untaggedName', + }, + { source: 'privateName', type: 'static', target: 'taggedName' }, ]), ], }, @@ -1419,7 +1437,15 @@ Violation detected in: tags: [], implicitDependencies: [], targets: {}, - files: [createFile(`libs/mylib/src/main.ts`, ['anotherlibName'])], + files: [ + createFile(`libs/mylib/src/main.ts`, [ + { + source: 'mylibName', + type: 'static', + target: 'anotherlibName', + }, + ]), + ], }, }, anotherlibName: { @@ -1430,7 +1456,15 @@ Violation detected in: tags: [], implicitDependencies: [], targets: {}, - files: [createFile(`libs/anotherlib/src/main.ts`, ['mylibName'])], + files: [ + createFile(`libs/anotherlib/src/main.ts`, [ + { + source: 'anotherlibName', + type: 'static', + target: 'mylibName', + }, + ]), + ], }, }, myappName: { @@ -1486,7 +1520,13 @@ Circular file chain: implicitDependencies: [], targets: {}, files: [ - createFile(`libs/mylib/src/main.ts`, ['badcirclelibName']), + createFile(`libs/mylib/src/main.ts`, [ + { + source: 'badcirclelibName', + type: 'static', + target: 'mylibName', + }, + ]), ], }, }, @@ -1499,8 +1539,20 @@ Circular file chain: implicitDependencies: [], targets: {}, files: [ - createFile(`libs/anotherlib/src/main.ts`, ['mylibName']), - createFile(`libs/anotherlib/src/index.ts`, ['mylibName']), + createFile(`libs/anotherlib/src/main.ts`, [ + { + source: 'anotherlibName', + type: 'static', + target: 'mylibName', + }, + ]), + createFile(`libs/anotherlib/src/index.ts`, [ + { + source: 'anotherlibName', + type: 'static', + target: 'mylibName', + }, + ]), ], }, }, @@ -1513,7 +1565,13 @@ Circular file chain: implicitDependencies: [], targets: {}, files: [ - createFile(`libs/badcirclelib/src/main.ts`, ['anotherlibName']), + createFile(`libs/badcirclelib/src/main.ts`, [ + { + source: 'badcirclelibName', + type: 'static', + target: 'anotherlibName', + }, + ]), ], }, }, @@ -2025,8 +2083,11 @@ const baseConfig = { linter.defineParser('@typescript-eslint/parser', parser); linter.defineRule(enforceModuleBoundariesRuleName, enforceModuleBoundaries); -function createFile(f: string, deps?: string[]): FileData { - return { file: f, hash: '', ...(deps && { deps }) }; +function createFile( + f: string, + dependencies?: ProjectGraphDependency[] +): FileData { + return { file: f, hash: '', ...(dependencies && { dependencies }) }; } function runRule( diff --git a/packages/eslint-plugin-nx/src/utils/graph-utils.ts b/packages/eslint-plugin-nx/src/utils/graph-utils.ts index dbf88e2f35..a750f879b8 100644 --- a/packages/eslint-plugin-nx/src/utils/graph-utils.ts +++ b/packages/eslint-plugin-nx/src/utils/graph-utils.ts @@ -146,7 +146,9 @@ export function findFilesInCircularPath( filePathChain.push( Object.keys(files) .filter( - (key) => files[key].deps && files[key].deps.indexOf(next) !== -1 + (key) => + files[key].dependencies && + files[key].dependencies.find((d) => d.target === next) ) .map((key) => files[key].file) ); diff --git a/packages/nx/src/config/project-graph.ts b/packages/nx/src/config/project-graph.ts index 0a6afd34f5..d755478a13 100644 --- a/packages/nx/src/config/project-graph.ts +++ b/packages/nx/src/config/project-graph.ts @@ -11,7 +11,9 @@ import { NxJsonConfiguration } from './nx-json'; export interface FileData { file: string; hash: string; + /** @deprecated this field will be removed in v17. Use {@link dependencies} instead */ deps?: string[]; + dependencies?: ProjectGraphDependency[]; } /** @@ -33,15 +35,6 @@ export interface ProjectGraph { version?: string; } -/** @deprecated this type will be removed in v16. Use {@link ProjectGraph} instead */ -export interface ProjectGraphV4 { - nodes: Record; - dependencies: Record; - // this is optional otherwise it might break folks who use project graph creation - allWorkspaceFiles?: FileData[]; - version?: string; -} - /** * Type of dependency between projects */ @@ -125,7 +118,7 @@ export interface ProjectGraphDependency { export interface ProjectGraphProcessorContext { /** * Workspace information such as projects and configuration - * @deprecated use projectsConfigurations or nxJsonConfiguration + * @deprecated use {@link projectsConfigurations} or {@link nxJsonConfiguration} instead */ workspace: Workspace; diff --git a/packages/nx/src/devkit-exports.ts b/packages/nx/src/devkit-exports.ts index 6562c83f5b..403dacd019 100644 --- a/packages/nx/src/devkit-exports.ts +++ b/packages/nx/src/devkit-exports.ts @@ -132,7 +132,6 @@ export type { ProjectFileMap, FileData, ProjectGraph, - ProjectGraphV4, ProjectGraphDependency, ProjectGraphNode, ProjectGraphProjectNode, diff --git a/packages/nx/src/hasher/hasher.ts b/packages/nx/src/hasher/hasher.ts index 77f94ac682..6eed88a9ba 100644 --- a/packages/nx/src/hasher/hasher.ts +++ b/packages/nx/src/hasher/hasher.ts @@ -285,6 +285,8 @@ class TaskHasher { visited: string[] ) { const projectGraphDeps = this.projectGraph.dependencies[projectName] ?? []; + // we don't want random order of dependencies to change the hash + projectGraphDeps.sort((a, b) => a.target.localeCompare(b.target)); const self = await this.hashSelfInputs(projectName, selfInputs); const deps = await this.hashDepsInputs( diff --git a/packages/nx/src/lock-file/npm-parser.ts b/packages/nx/src/lock-file/npm-parser.ts index 1bb5e34fe4..831a7ec075 100644 --- a/packages/nx/src/lock-file/npm-parser.ts +++ b/packages/nx/src/lock-file/npm-parser.ts @@ -230,7 +230,7 @@ function addDependencies( Object.entries(section).forEach(([name, versionRange]) => { const target = findTarget(path, keyMap, name, versionRange); if (target) { - builder.addExternalNodeDependency(sourceName, target.name); + builder.addStaticDependency(sourceName, target.name); } }); } @@ -291,7 +291,7 @@ function addV1NodeDependencies( Object.entries(snapshot.requires).forEach(([name, versionRange]) => { const target = findTarget(path, keyMap, name, versionRange); if (target) { - builder.addExternalNodeDependency(source, target.name); + builder.addStaticDependency(source, target.name); } }); } @@ -317,7 +317,7 @@ function addV1NodeDependencies( ) { const target = findTarget(path, keyMap, depName, depSpec); if (target) { - builder.addExternalNodeDependency(node.name, target.name); + builder.addStaticDependency(node.name, target.name); } } }); diff --git a/packages/nx/src/lock-file/pnpm-parser.ts b/packages/nx/src/lock-file/pnpm-parser.ts index 7833cbb7f5..84b4aaef3b 100644 --- a/packages/nx/src/lock-file/pnpm-parser.ts +++ b/packages/nx/src/lock-file/pnpm-parser.ts @@ -122,7 +122,7 @@ function addDependencies( builder.graph.externalNodes[`npm:${name}@${version}`] || builder.graph.externalNodes[`npm:${name}`]; if (target) { - builder.addExternalNodeDependency(node.name, target.name); + builder.addStaticDependency(node.name, target.name); } }); } diff --git a/packages/nx/src/lock-file/project-graph-pruning.ts b/packages/nx/src/lock-file/project-graph-pruning.ts index 2f807750c6..40b0c658bc 100644 --- a/packages/nx/src/lock-file/project-graph-pruning.ts +++ b/packages/nx/src/lock-file/project-graph-pruning.ts @@ -111,7 +111,7 @@ function traverseNode( graph.dependencies[node.name]?.forEach((dep) => { const depNode = graph.externalNodes[dep.target]; traverseNode(graph, builder, depNode); - builder.addExternalNodeDependency(node.name, dep.target); + builder.addStaticDependency(node.name, dep.target); }); } @@ -184,11 +184,9 @@ function switchNodeToHoisted( builder.addExternalNode(node); targets.forEach((target) => { - builder.addExternalNodeDependency(node.name, target); + builder.addStaticDependency(node.name, target); }); - sources.forEach((source) => - builder.addExternalNodeDependency(source, node.name) - ); + sources.forEach((source) => builder.addStaticDependency(source, node.name)); } // BFS to find the shortest path to a dependency specified in package.json diff --git a/packages/nx/src/lock-file/yarn-parser.ts b/packages/nx/src/lock-file/yarn-parser.ts index 381e0ce3d3..75483cefdf 100644 --- a/packages/nx/src/lock-file/yarn-parser.ts +++ b/packages/nx/src/lock-file/yarn-parser.ts @@ -168,7 +168,7 @@ function addDependencies( keyMap.get(`${name}@npm:${versionRange}`) || keyMap.get(`${name}@${versionRange}`); if (target) { - builder.addExternalNodeDependency(node.name, target.name); + builder.addStaticDependency(node.name, target.name); } }); } diff --git a/packages/nx/src/project-graph/build-dependencies/build-explicit-typescript-and-package-json-dependencies.ts b/packages/nx/src/project-graph/build-dependencies/build-explicit-typescript-and-package-json-dependencies.ts index cd5bf97fa0..287d15fc82 100644 --- a/packages/nx/src/project-graph/build-dependencies/build-explicit-typescript-and-package-json-dependencies.ts +++ b/packages/nx/src/project-graph/build-dependencies/build-explicit-typescript-and-package-json-dependencies.ts @@ -1,4 +1,7 @@ -import { buildExplicitTypeScriptDependencies } from './explicit-project-dependencies'; +import { + buildExplicitTypeScriptDependencies, + ExplicitDependency, +} from './explicit-project-dependencies'; import { buildExplicitPackageJsonDependencies } from './explicit-package-json-dependencies'; import { ProjectFileMap, ProjectGraph } from '../../config/project-graph'; import { ProjectsConfigurations } from '../../config/workspace-json-project-json'; @@ -14,7 +17,7 @@ export function buildExplicitTypescriptAndPackageJsonDependencies( projectGraph: ProjectGraph, filesToProcess: ProjectFileMap ) { - let res = []; + let res: ExplicitDependency[] = []; if ( jsPluginConfig.analyzeSourceFiles === undefined || jsPluginConfig.analyzeSourceFiles === true diff --git a/packages/nx/src/project-graph/build-dependencies/explicit-package-json-dependencies.ts b/packages/nx/src/project-graph/build-dependencies/explicit-package-json-dependencies.ts index fda0f4b798..b96e1366cf 100644 --- a/packages/nx/src/project-graph/build-dependencies/explicit-package-json-dependencies.ts +++ b/packages/nx/src/project-graph/build-dependencies/explicit-package-json-dependencies.ts @@ -5,6 +5,7 @@ import { parseJson } from '../../utils/json'; import { getImportPath, joinPathFragments } from '../../utils/path'; import { ProjectsConfigurations } from '../../config/workspace-json-project-json'; import { NxJsonConfiguration } from '../../config/nx-json'; +import { ExplicitDependency } from './explicit-project-dependencies'; class ProjectGraphNodeRecords {} @@ -74,7 +75,7 @@ function processPackageJson( sourceProject: string, fileName: string, graph: ProjectGraph, - collectedDeps: any[], + collectedDeps: ExplicitDependency[], packageNameMap: { [packageName: string]: string } ) { try { diff --git a/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.spec.ts b/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.spec.ts index 4c598cb347..b2c9878ef7 100644 --- a/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.spec.ts +++ b/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.spec.ts @@ -46,21 +46,25 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj2', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj3a', + type: 'dynamic', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj4ab', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'npm:npm-package', + type: 'static', }, ]); }); @@ -91,16 +95,19 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj2', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj3a', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj4ab', + type: 'static', }, ]); }); @@ -131,16 +138,19 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj2', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj3a', + type: 'static', }, { sourceProjectName, sourceProjectFile: 'libs/proj/index.ts', targetProjectName: 'proj4ab', + type: 'static', }, ]); }); @@ -168,7 +178,7 @@ describe('explicit project dependencies', () => { }, { path: 'libs/proj/component.tsx', - content: ` + content: ` export function App() { import('@proj/my-second-proj') return ( @@ -192,16 +202,19 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/component.tsx', targetProjectName: 'proj2', + type: 'dynamic', }, { sourceProjectName, sourceProjectFile: 'libs/proj/nested-dynamic-import.ts', targetProjectName: 'proj3a', + type: 'dynamic', }, { sourceProjectName, sourceProjectFile: 'libs/proj/nested-require.ts', targetProjectName: 'proj4ab', + type: 'static', }, ]); }); @@ -236,11 +249,13 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/absolute-path.ts', targetProjectName: 'proj3a', + type: 'dynamic', }, { sourceProjectName, sourceProjectFile: 'libs/proj/relative-path.ts', targetProjectName: 'proj4ab', + type: 'dynamic', }, ]); }); @@ -313,15 +328,15 @@ describe('explicit project dependencies', () => { { path: 'libs/proj/comments-with-excess-whitespace.ts', content: ` - /* + /* nx-ignore-next-line - + */ require('@proj/proj4ab'); // nx-ignore-next-line import('@proj/proj4ab'); - /* - + /* + nx-ignore-next-line */ import { foo } from '@proj/proj4ab'; `, @@ -464,11 +479,13 @@ describe('explicit project dependencies', () => { sourceProjectName, sourceProjectFile: 'libs/proj/file-1.ts', targetProjectName: 'proj4ab', + type: 'dynamic', }, { sourceProjectName, sourceProjectFile: 'libs/proj/file-2.ts', targetProjectName: 'proj3a', + type: 'dynamic', }, ]); }); diff --git a/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.ts b/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.ts index 5ebb7dc754..bfee751a8b 100644 --- a/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.ts +++ b/packages/nx/src/project-graph/build-dependencies/explicit-project-dependencies.ts @@ -6,6 +6,13 @@ import { ProjectGraph, } from '../../config/project-graph'; +export type ExplicitDependency = { + sourceProjectName: string; + targetProjectName: string; + sourceProjectFile: string; + type?: DependencyType.static | DependencyType.dynamic; +}; + export function buildExplicitTypeScriptDependencies( graph: ProjectGraph, filesToProcess: ProjectFileMap @@ -19,12 +26,16 @@ export function buildExplicitTypeScriptDependencies( graph.nodes as any, graph.externalNodes ); - const res = [] as any; + const res: ExplicitDependency[] = []; Object.keys(filesToProcess).forEach((source) => { Object.values(filesToProcess[source]).forEach((f) => { importLocator.fromFile( f.file, - (importExpr: string, filePath: string, type: DependencyType) => { + ( + importExpr: string, + filePath: string, + type: DependencyType.static | DependencyType.dynamic + ) => { const target = targetProjectLocator.findProjectWithImport( importExpr, f.file @@ -39,6 +50,7 @@ export function buildExplicitTypeScriptDependencies( sourceProjectName: source, targetProjectName: target, sourceProjectFile: f.file, + type, }); } } diff --git a/packages/nx/src/project-graph/build-project-graph.spec.ts b/packages/nx/src/project-graph/build-project-graph.spec.ts index ea63bf98f4..f96233a03a 100644 --- a/packages/nx/src/project-graph/build-project-graph.spec.ts +++ b/packages/nx/src/project-graph/build-project-graph.spec.ts @@ -243,7 +243,6 @@ describe('project graph', () => { expect(graph.dependencies).toEqual({ api: [{ source: 'api', target: 'npm:express', type: 'static' }], demo: [ - { source: 'demo', target: 'api', type: 'implicit' }, { source: 'demo', target: 'ui', @@ -253,8 +252,9 @@ describe('project graph', () => { { source: 'demo', target: 'lazy-lib', - type: 'static', + type: 'dynamic', }, + { source: 'demo', target: 'api', type: 'implicit' }, ], 'demo-e2e': [], 'lazy-lib': [], @@ -267,7 +267,7 @@ describe('project graph', () => { { source: 'ui', target: 'lazy-lib', - type: 'static', + type: 'dynamic', }, ], }); @@ -295,7 +295,7 @@ describe('project graph', () => { target: 'shared-util', }, { - type: DependencyType.static, + type: DependencyType.dynamic, source: 'ui', target: 'lazy-lib', }, diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index 5971726ec0..5a0fe43600 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -11,7 +11,10 @@ import { shouldRecomputeWholeGraph, writeCache, } from './nx-deps-cache'; -import { buildImplicitProjectDependencies } from './build-dependencies'; +import { + buildImplicitProjectDependencies, + ExplicitDependency, +} from './build-dependencies'; import { buildWorkspaceProjectNodes } from './build-nodes'; import * as os from 'os'; import { buildExplicitTypescriptAndPackageJsonDependencies } from './build-dependencies/build-explicit-typescript-and-package-json-dependencies'; @@ -75,7 +78,7 @@ export async function buildProjectGraphUsingProjectFileMap( projectGraphCache: ProjectGraphCache; }> { const nxJson = readNxJson(); - const projectGraphVersion = '5.0'; + const projectGraphVersion = '5.1'; assertWorkspaceValidity(projectsConfigurations, nxJson); const packageJsonDeps = readCombinedDeps(); const rootTsConfig = readRootTsConfig(); @@ -203,8 +206,8 @@ async function buildProjectGraphUsingContext( for (const proj of Object.keys(cachedFileData)) { for (const f of updatedBuilder.graph.nodes[proj].data.files) { const cached = cachedFileData[proj][f.file]; - if (cached && cached.deps) { - f.deps = [...cached.deps]; + if (cached && cached.dependencies) { + f.dependencies = [...cached.dependencies]; } } } @@ -346,11 +349,19 @@ function buildExplicitDependenciesWithoutWorkers( builder.graph, ctx.filesToProcess ).forEach((r) => { - builder.addExplicitDependency( - r.sourceProjectName, - r.sourceProjectFile, - r.targetProjectName - ); + if (r.type === 'static') { + builder.addStaticDependency( + r.sourceProjectName, + r.targetProjectName, + r.sourceProjectFile + ); + } else { + builder.addDynamicDependency( + r.sourceProjectName, + r.targetProjectName, + r.sourceProjectFile + ); + } }); } @@ -378,12 +389,20 @@ function buildExplicitDependenciesUsingWorkers( return new Promise((res, reject) => { for (let w of workers) { w.on('message', (explicitDependencies) => { - explicitDependencies.forEach((r) => { - builder.addExplicitDependency( - r.sourceProjectName, - r.sourceProjectFile, - r.targetProjectName - ); + explicitDependencies.forEach((r: ExplicitDependency) => { + if (r.type === 'static') { + builder.addStaticDependency( + r.sourceProjectName, + r.targetProjectName, + r.sourceProjectFile + ); + } else { + builder.addDynamicDependency( + r.sourceProjectName, + r.targetProjectName, + r.sourceProjectFile + ); + } }); if (bins.length > 0) { w.postMessage({ filesToProcess: bins.shift() }); diff --git a/packages/nx/src/project-graph/nx-deps-cache.spec.ts b/packages/nx/src/project-graph/nx-deps-cache.spec.ts index e151196f5a..7377c09d38 100644 --- a/packages/nx/src/project-graph/nx-deps-cache.spec.ts +++ b/packages/nx/src/project-graph/nx-deps-cache.spec.ts @@ -13,7 +13,7 @@ describe('nx deps utils', () => { it('should be false when nothing changes', () => { expect( shouldRecomputeWholeGraph( - createCache({ version: '5.0' }), + createCache({ version: '5.1' }), createPackageJsonDeps({}), createWorkspaceJson({}), createNxJson({}), @@ -318,7 +318,7 @@ describe('nx deps utils', () => { function createCache(p: Partial): ProjectGraphCache { const defaults: ProjectGraphCache = { - version: '5.0', + version: '5.1', deps: { '@nrwl/workspace': '12.0.0', plugin: '1.0.0', diff --git a/packages/nx/src/project-graph/nx-deps-cache.ts b/packages/nx/src/project-graph/nx-deps-cache.ts index d652e5d17d..cc0f0eeec1 100644 --- a/packages/nx/src/project-graph/nx-deps-cache.ts +++ b/packages/nx/src/project-graph/nx-deps-cache.ts @@ -94,7 +94,7 @@ export function createCache( version: packageJsonDeps[p], })); const newValue: ProjectGraphCache = { - version: projectGraph.version || '5.0', + version: projectGraph.version || '5.1', deps: packageJsonDeps, lockFileHash, // compilerOptions may not exist, especially for repos converted through add-nx-to-monorepo @@ -149,7 +149,7 @@ export function shouldRecomputeWholeGraph( nxJson: NxJsonConfiguration, tsConfig: { compilerOptions: { paths: { [k: string]: any } } } ): boolean { - if (cache.version !== '5.0') { + if (cache.version !== '5.1') { return true; } if (cache.deps['@nrwl/workspace'] !== packageJsonDeps['@nrwl/workspace']) { diff --git a/packages/nx/src/project-graph/project-graph-builder.spec.ts b/packages/nx/src/project-graph/project-graph-builder.spec.ts index 1348b3355c..9dbd23c47c 100644 --- a/packages/nx/src/project-graph/project-graph-builder.spec.ts +++ b/packages/nx/src/project-graph/project-graph-builder.spec.ts @@ -25,7 +25,7 @@ describe('ProjectGraphBuilder', () => { }); }); - it(`should add an implicit dependency`, () => { + it(`should add a dependency`, () => { expect(() => builder.addImplicitDependency('invalid-source', 'target') ).toThrowError(); @@ -34,11 +34,40 @@ describe('ProjectGraphBuilder', () => { ).toThrowError(); // ignore the self deps - builder.addImplicitDependency('source', 'source'); + builder.addDynamicDependency('source', 'source', 'source/index.ts'); - // don't include duplicates + // don't include duplicates of the same type builder.addImplicitDependency('source', 'target'); builder.addImplicitDependency('source', 'target'); + builder.addStaticDependency('source', 'target', 'source/index.ts'); + builder.addDynamicDependency('source', 'target', 'source/index.ts'); + builder.addStaticDependency('source', 'target', 'source/index.ts'); + + const graph = builder.getUpdatedProjectGraph(); + expect(graph.dependencies).toEqual({ + source: [ + { + source: 'source', + target: 'target', + type: 'implicit', + }, + { + source: 'source', + target: 'target', + type: 'static', + }, + { + source: 'source', + target: 'target', + type: 'dynamic', + }, + ], + target: [], + }); + }); + + it(`should add an implicit dependency`, () => { + builder.addImplicitDependency('source', 'target'); const graph = builder.getUpdatedProjectGraph(); expect(graph.dependencies).toEqual({ @@ -96,10 +125,10 @@ describe('ProjectGraphBuilder', () => { }); }); - it(`should use implicit dep when both implicit and explicit deps are available`, () => { + it(`should use both deps when both implicit and explicit deps are available`, () => { // don't include duplicates builder.addImplicitDependency('source', 'target'); - builder.addExplicitDependency('source', 'source/index.ts', 'target'); + builder.addStaticDependency('source', 'target', 'source/index.ts'); const graph = builder.getUpdatedProjectGraph(); expect(graph.dependencies).toEqual({ @@ -109,6 +138,11 @@ describe('ProjectGraphBuilder', () => { target: 'target', type: 'implicit', }, + { + source: 'source', + target: 'target', + type: 'static', + }, ], target: [], }); @@ -121,7 +155,7 @@ describe('ProjectGraphBuilder', () => { data: {} as any, }); builder.addImplicitDependency('source', 'target'); - builder.addExplicitDependency('source', 'source/index.ts', 'target'); + builder.addStaticDependency('source', 'target', 'source/index.ts'); builder.addImplicitDependency('source', 'target2'); builder.removeDependency('source', 'target'); diff --git a/packages/nx/src/project-graph/project-graph-builder.ts b/packages/nx/src/project-graph/project-graph-builder.ts index 75b4c44259..fe428fe67f 100644 --- a/packages/nx/src/project-graph/project-graph-builder.ts +++ b/packages/nx/src/project-graph/project-graph-builder.ts @@ -44,7 +44,6 @@ export class ProjectGraphBuilder { } } this.graph.nodes[node.name] = node; - this.graph.dependencies[node.name] = []; } /** @@ -72,29 +71,61 @@ export class ProjectGraphBuilder { } /** - * Adds a dependency from source project to target project + * Adds static dependency from source project to target project + */ + addStaticDependency( + sourceProjectName: string, + targetProjectName: string, + sourceProjectFile?: string + ): void { + if (this.graph.nodes[sourceProjectName] && !sourceProjectFile) { + throw new Error(`Source project file is required`); + } + this.addDependency( + sourceProjectName, + targetProjectName, + DependencyType.static, + sourceProjectFile + ); + } + + /** + * Adds dynamic dependency from source project to target project + */ + addDynamicDependency( + sourceProjectName: string, + targetProjectName: string, + sourceProjectFile: string + ): void { + if (this.graph.externalNodes[sourceProjectName]) { + throw new Error(`External projects can't have "dynamic" dependencies`); + } + if (!sourceProjectFile) { + throw new Error(`Source project file is required`); + } + this.addDependency( + sourceProjectName, + targetProjectName, + DependencyType.dynamic, + sourceProjectFile + ); + } + + /** + * Adds implicit dependency from source project to target project */ addImplicitDependency( sourceProjectName: string, targetProjectName: string ): void { - if (sourceProjectName === targetProjectName) { - return; + if (this.graph.externalNodes[sourceProjectName]) { + throw new Error(`External projects can't have "implicit" dependencies`); } - if (!this.graph.nodes[sourceProjectName]) { - throw new Error(`Source project does not exist: ${sourceProjectName}`); - } - if ( - !this.graph.nodes[targetProjectName] && - !this.graph.externalNodes[targetProjectName] - ) { - throw new Error(`Target project does not exist: ${targetProjectName}`); - } - this.graph.dependencies[sourceProjectName].push({ - source: sourceProjectName, - target: targetProjectName, - type: DependencyType.implicit, - }); + this.addDependency( + sourceProjectName, + targetProjectName, + DependencyType.implicit + ); } /** @@ -124,6 +155,7 @@ export class ProjectGraphBuilder { /** * Add an explicit dependency from a file in source project to target project + * @deprecated this method will be removed in v17. Use {@link addStaticDependency} or {@link addDynamicDependency} instead */ addExplicitDependency( sourceProjectName: string, @@ -154,46 +186,14 @@ export class ProjectGraphBuilder { ); } - if (!fileData.deps) { - fileData.deps = []; + if (!fileData.dependencies) { + fileData.dependencies = []; } - if (!fileData.deps.find((t) => t === targetProjectName)) { - fileData.deps.push(targetProjectName); - } - } - - /** - * Add an explicit dependency from a file in source project to target project - */ - addExternalNodeDependency( - sourceProjectName: string, - targetProjectName: string - ): void { - if (sourceProjectName === targetProjectName) { - return; - } - const source = this.graph.externalNodes[sourceProjectName]; - if (!source) { - throw new Error(`Source project does not exist: ${sourceProjectName}`); - } - - if (!this.graph.externalNodes[targetProjectName]) { - throw new Error(`Target project does not exist: ${targetProjectName}`); - } - - if (!this.graph.dependencies[sourceProjectName]) { - this.graph.dependencies[sourceProjectName] = []; - } - - if ( - !this.graph.dependencies[sourceProjectName].some( - (d) => d.target === targetProjectName - ) - ) { - this.graph.dependencies[sourceProjectName].push({ - source: sourceProjectName, + if (!fileData.dependencies.find((t) => t.target === targetProjectName)) { + fileData.dependencies.push({ target: targetProjectName, + source: sourceProjectName, type: DependencyType.static, }); } @@ -212,20 +212,25 @@ export class ProjectGraphBuilder { this.calculateAlreadySetTargetDeps(sourceProject); this.graph.dependencies[sourceProject] = [ ...alreadySetTargetProjects.values(), - ]; + ].flatMap((depsMap) => [...depsMap.values()]); const fileDeps = this.calculateTargetDepsFromFiles(sourceProject); - for (const targetProject of fileDeps) { - if (!alreadySetTargetProjects.has(targetProject)) { + for (const [targetProject, types] of fileDeps.entries()) { + for (const type of types.values()) { if ( - !this.removedEdges[sourceProject] || - !this.removedEdges[sourceProject].has(targetProject) + !alreadySetTargetProjects.has(targetProject) || + !alreadySetTargetProjects.get(targetProject).has(type) ) { - this.graph.dependencies[sourceProject].push({ - source: sourceProject, - target: targetProject, - type: DependencyType.static, - }); + if ( + !this.removedEdges[sourceProject] || + !this.removedEdges[sourceProject].has(targetProject) + ) { + this.graph.dependencies[sourceProject].push({ + source: sourceProject, + target: targetProject, + type, + }); + } } } } @@ -233,6 +238,78 @@ export class ProjectGraphBuilder { return this.graph; } + private addDependency( + sourceProjectName: string, + targetProjectName: string, + type: DependencyType, + sourceProjectFile?: string + ): void { + if (sourceProjectName === targetProjectName) { + return; + } + if ( + !this.graph.nodes[sourceProjectName] && + !this.graph.externalNodes[sourceProjectName] + ) { + throw new Error(`Source project does not exist: ${sourceProjectName}`); + } + if ( + !this.graph.nodes[targetProjectName] && + !this.graph.externalNodes[targetProjectName] + ) { + throw new Error(`Target project does not exist: ${targetProjectName}`); + } + if ( + this.graph.externalNodes[sourceProjectName] && + this.graph.nodes[targetProjectName] + ) { + throw new Error(`External projects can't depend on internal projects`); + } + if (!this.graph.dependencies[sourceProjectName]) { + this.graph.dependencies[sourceProjectName] = []; + } + // do not add duplicate + if ( + this.graph.dependencies[sourceProjectName].find( + (d) => d.target === targetProjectName && d.type === type + ) + ) { + return; + } + + const dependency = { + source: sourceProjectName, + target: targetProjectName, + type, + }; + + if (sourceProjectFile) { + const source = this.graph.nodes[sourceProjectName]; + if (!source) { + throw new Error( + `Source project is not a project node: ${sourceProjectName}` + ); + } + 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.dependencies) { + fileData.dependencies = []; + } + if (!fileData.dependencies.find((t) => t.target === targetProjectName)) { + fileData.dependencies.push(dependency); + } + } + + this.graph.dependencies[sourceProjectName].push(dependency); + } + private removeDependenciesWithNode(name: string) { // remove all source dependencies delete this.graph.dependencies[name]; @@ -254,26 +331,45 @@ export class ProjectGraphBuilder { } } - private calculateTargetDepsFromFiles(sourceProject: string) { - const fileDeps = new Set(); + private calculateTargetDepsFromFiles( + sourceProject: string + ): Map> { + const fileDeps = new Map>(); const files = this.graph.nodes[sourceProject].data.files; - if (!files) return fileDeps; + if (!files) { + return fileDeps; + } for (let f of files) { - if (f.deps) { - for (let p of f.deps) { - fileDeps.add(p); + if (f.dependencies) { + for (let d of f.dependencies) { + if (!fileDeps.has(d.target)) { + fileDeps.set(d.target, new Set([d.type])); + } else { + fileDeps.get(d.target).add(d.type); + } } } } return fileDeps; } - private calculateAlreadySetTargetDeps(sourceProject: string) { - const alreadySetTargetProjects = new Map(); - const removed = this.removedEdges[sourceProject]; - for (const d of this.graph.dependencies[sourceProject]) { - if (!removed || !removed.has(d.target)) { - alreadySetTargetProjects.set(d.target, d); + private calculateAlreadySetTargetDeps( + sourceProject: string + ): Map> { + const alreadySetTargetProjects = new Map< + string, + Map + >(); + if (this.graph.dependencies[sourceProject]) { + const removed = this.removedEdges[sourceProject]; + for (const d of this.graph.dependencies[sourceProject]) { + if (!removed || !removed.has(d.target)) { + if (!alreadySetTargetProjects.has(d.target)) { + alreadySetTargetProjects.set(d.target, new Map([[d.type, d]])); + } else { + alreadySetTargetProjects.get(d.target).set(d.type, d); + } + } } } return alreadySetTargetProjects; diff --git a/packages/nx/src/project-graph/project-graph.ts b/packages/nx/src/project-graph/project-graph.ts index 770c09088a..e00091edec 100644 --- a/packages/nx/src/project-graph/project-graph.ts +++ b/packages/nx/src/project-graph/project-graph.ts @@ -3,7 +3,7 @@ import { buildProjectGraph } from './build-project-graph'; import { output } from '../utils/output'; import { defaultFileHasher } from '../hasher/file-hasher'; import { markDaemonAsDisabled, writeDaemonLogs } from '../daemon/tmp-dir'; -import { ProjectGraph, ProjectGraphV4 } from '../config/project-graph'; +import { ProjectGraph } from '../config/project-graph'; import { stripIndents } from '../utils/strip-indents'; import { ProjectConfiguration, @@ -47,7 +47,7 @@ export function readCachedProjectGraph(): ProjectGraph { return projectGraphAdapter( projectGraph.version, - '5.0', + '5.1', projectGraph ) as ProjectGraph; } @@ -179,43 +179,38 @@ export function projectGraphAdapter( sourceVersion: string, targetVersion: string, projectGraph: ProjectGraph -): ProjectGraph | ProjectGraphV4 { +): ProjectGraph { if (sourceVersion === targetVersion) { return projectGraph; } - if (+sourceVersion >= 5 && targetVersion === '4.0') { - return projectGraphCompat5to4(projectGraph as ProjectGraph); + if (+sourceVersion > 5 && +targetVersion === 5) { + return projectGraphCompatFileDependencies(projectGraph as ProjectGraph); } throw new Error( `Invalid source or target versions. Source: ${sourceVersion}, Target: ${targetVersion}. -Only backwards compatibility between "5.0" and "4.0" is supported. +Only backwards compatibility between "5.1" and "5.0" is supported. This error can be caused by "@nrwl/..." packages getting out of sync or outdated project graph cache. Check the versions running "nx report" and/or remove your "nxdeps.json" file (in node_modules/.cache/nx folder). ` ); } -/** - * Backwards compatibility adapter for project Nodes v4 to v5 - * @param {ProjectGraph} projectGraph - * @returns {ProjectGraph} - */ -function projectGraphCompat5to4(projectGraph: ProjectGraph): ProjectGraphV4 { - const { externalNodes, ...rest } = projectGraph; - return { - ...rest, - nodes: { - ...projectGraph.nodes, - ...externalNodes, - }, - dependencies: { - ...projectGraph.dependencies, - ...Object.keys(externalNodes).reduce( - (acc, key) => ({ ...acc, [`npm:${key}`]: [] }), - {} - ), - }, - version: '4.0', - }; +function projectGraphCompatFileDependencies( + projectGraph: ProjectGraph +): ProjectGraph { + Object.values(projectGraph.nodes).forEach(({ data }) => { + if (data.files) { + data.files = data.files.map(({ file, hash, dependencies }) => ({ + file, + hash, + // map dependencies to array of targets + ...(dependencies && + dependencies.length && { + deps: [...new Set(dependencies.map((d) => d.target))], + }), + })); + } + }); + return projectGraph; } diff --git a/packages/react-native/src/generators/storybook-configuration/configuration.spec.ts b/packages/react-native/src/generators/storybook-configuration/configuration.spec.ts index 85aca394da..bf8bf982de 100644 --- a/packages/react-native/src/generators/storybook-configuration/configuration.spec.ts +++ b/packages/react-native/src/generators/storybook-configuration/configuration.spec.ts @@ -8,6 +8,14 @@ import applicationGenerator from '../application/application'; import componentGenerator from '../component/component'; import storybookConfigurationGenerator from './configuration'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('react-native:storybook-configuration', () => { let appTree; diff --git a/packages/react/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts b/packages/react/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts index 7ddb5d4773..071860fee5 100644 --- a/packages/react/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts +++ b/packages/react/src/generators/cypress-component-configuration/cypress-component-configuration.spec.ts @@ -22,6 +22,14 @@ jest.mock('@nrwl/devkit', () => ({ .mockImplementation(async () => projectGraph), })); jest.mock('@nrwl/cypress/src/utils/cypress-version'); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + readCachedProjectGraph: jest + .fn() + .mockImplementation(async () => projectGraph), +})); + describe('React:CypressComponentTestConfiguration', () => { let tree: Tree; let mockedAssertCypressVersion: jest.Mock< diff --git a/packages/react/src/generators/storybook-configuration/configuration.spec.ts b/packages/react/src/generators/storybook-configuration/configuration.spec.ts index 0a03f46555..cdd0a617c8 100644 --- a/packages/react/src/generators/storybook-configuration/configuration.spec.ts +++ b/packages/react/src/generators/storybook-configuration/configuration.spec.ts @@ -9,6 +9,14 @@ import storybookConfigurationGenerator from './configuration'; // need to mock cypress otherwise it'll use the nx installed version from package.json // which is v9 while we are testing for the new v10 version jest.mock('@nrwl/cypress/src/utils/cypress-version'); +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('react:storybook-configuration', () => { let appTree; let mockedInstalledCypressVersion: jest.Mock< diff --git a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts index 2e82044357..25d3541b7a 100644 --- a/packages/storybook/src/generators/configuration/configuration-nested.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration-nested.spec.ts @@ -10,6 +10,14 @@ import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import configurationGenerator from './configuration'; import * as workspaceConfiguration from './test-configs/root-workspace-configuration.json'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('@nrwl/storybook:configuration for workspaces with Root project', () => { describe('basic functionalities', () => { let tree: Tree; diff --git a/packages/storybook/src/generators/configuration/configuration-v7.spec.ts b/packages/storybook/src/generators/configuration/configuration-v7.spec.ts index 90c78d283f..8e951ab1d7 100644 --- a/packages/storybook/src/generators/configuration/configuration-v7.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration-v7.spec.ts @@ -18,6 +18,14 @@ import { storybook7Version } from '../../utils/versions'; import configurationGenerator from './configuration'; import * as variousProjects from './test-configs/various-projects.json'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('@nrwl/storybook:configuration for Storybook v7', () => { describe('basic functionalities', () => { let tree: Tree; diff --git a/packages/storybook/src/generators/configuration/configuration.spec.ts b/packages/storybook/src/generators/configuration/configuration.spec.ts index 3108fb689c..178b1ca50f 100644 --- a/packages/storybook/src/generators/configuration/configuration.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration.spec.ts @@ -15,6 +15,14 @@ import { TsConfig } from '../../utils/utilities'; import configurationGenerator from './configuration'; import * as workspaceConfiguration from './test-configs/workspace-conifiguration.json'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('@nrwl/storybook:configuration', () => { describe('basic functionalities', () => { let tree: Tree; diff --git a/packages/storybook/src/migrations/update-14-0-0/migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts b/packages/storybook/src/migrations/update-14-0-0/migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts index 4b64bfe570..0416b15360 100644 --- a/packages/storybook/src/migrations/update-14-0-0/migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts +++ b/packages/storybook/src/migrations/update-14-0-0/migrate-defaults-5-to-6/migrate-defaults-5-to-6.spec.ts @@ -14,6 +14,14 @@ import { } from '../../../utils/testing'; import { migrateDefaultsGenerator } from './migrate-defaults-5-to-6'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + describe('migrate-defaults-5-to-6 Generator', () => { let appTree: Tree; diff --git a/packages/storybook/src/migrations/update-14-0-0/migrate-stories-to-6-2/migrate-stories-to-6-2.spec.ts b/packages/storybook/src/migrations/update-14-0-0/migrate-stories-to-6-2/migrate-stories-to-6-2.spec.ts index 5af70af01b..73f8f7f508 100644 --- a/packages/storybook/src/migrations/update-14-0-0/migrate-stories-to-6-2/migrate-stories-to-6-2.spec.ts +++ b/packages/storybook/src/migrations/update-14-0-0/migrate-stories-to-6-2/migrate-stories-to-6-2.spec.ts @@ -17,6 +17,14 @@ import { } from '@nrwl/devkit/ngcli-adapter'; import { getTsSourceFile } from '@nrwl/storybook/src/utils/utilities'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + const componentSchematic = wrapAngularDevkitSchematic( '@schematics/angular', 'component' diff --git a/packages/storybook/src/utils/utilities.spec.ts b/packages/storybook/src/utils/utilities.spec.ts index 7c743791dd..8387ce65d3 100644 --- a/packages/storybook/src/utils/utilities.spec.ts +++ b/packages/storybook/src/utils/utilities.spec.ts @@ -11,6 +11,14 @@ import { import { nxVersion, storybookVersion } from './versions'; import * as targetVariations from './test-configs/different-target-variations.json'; +// nested code imports graph from the repo, which might have innacurate graph version +jest.mock('nx/src/project-graph/project-graph', () => ({ + ...jest.requireActual('nx/src/project-graph/project-graph'), + createProjectGraphAsync: jest + .fn() + .mockImplementation(async () => ({ nodes: {}, dependencies: {} })), +})); + const componentSchematic = wrapAngularDevkitSchematic( '@schematics/angular', 'component' @@ -66,14 +74,14 @@ describe('testing utilities', () => { ` import { Story, Meta } from '@storybook/react'; import { Button } from './button'; - + export default { component: Button, title: 'Button', } as Meta; - + const Template: Story = (args) =>