diff --git a/packages/nx/src/config/nx-json.ts b/packages/nx/src/config/nx-json.ts index 546e18093a405..e6980f9395847 100644 --- a/packages/nx/src/config/nx-json.ts +++ b/packages/nx/src/config/nx-json.ts @@ -40,6 +40,7 @@ export interface NrwlJsPluginConfig { analyzeSourceFiles?: boolean; analyzePackageJson?: boolean; analyzeLockfile?: boolean; + projectsAffectedByDependencyUpdates?: 'all' | 'auto' | string[]; } interface NxInstallationConfiguration { diff --git a/packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.ts b/packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.ts index f6c8c8be72146..8f4c4e393ae3c 100644 --- a/packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.ts +++ b/packages/nx/src/plugins/js/project-graph/affected/lock-file-changes.ts @@ -1,10 +1,25 @@ +import { readNxJson } from '../../../../config/configuration'; import { TouchedProjectLocator } from '../../../../project-graph/affected/affected-project-graph-models'; import { WholeFileChange } from '../../../../project-graph/file-utils'; import { JsonChange } from '../../../../utils/json-diff'; +import { jsPluginConfig as readJsPluginConfig } from '../../utils/config'; +import { findMatchingProjects } from '../../../../utils/find-matching-projects'; export const getTouchedProjectsFromLockFile: TouchedProjectLocator< WholeFileChange | JsonChange > = (fileChanges, projectGraphNodes): string[] => { + const nxJson = readNxJson(); + const { projectsAffectedByDependencyUpdates } = readJsPluginConfig(nxJson); + + if (projectsAffectedByDependencyUpdates === 'auto') { + return []; + } else if (Array.isArray(projectsAffectedByDependencyUpdates)) { + return findMatchingProjects( + projectsAffectedByDependencyUpdates, + projectGraphNodes + ); + } + const lockFiles = [ 'package-lock.json', 'yarn.lock', diff --git a/packages/nx/src/plugins/js/project-graph/affected/npm-packages.ts b/packages/nx/src/plugins/js/project-graph/affected/npm-packages.ts index 33b02fb1b4f27..b3d62460c8a1f 100644 --- a/packages/nx/src/plugins/js/project-graph/affected/npm-packages.ts +++ b/packages/nx/src/plugins/js/project-graph/affected/npm-packages.ts @@ -13,6 +13,8 @@ import { ProjectGraphExternalNode, ProjectGraphProjectNode, } from '../../../../config/project-graph'; +import { NxJsonConfiguration } from '../../../../config/nx-json'; +import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path'; export const getTouchedNpmPackages: TouchedProjectLocator< WholeFileChange | JsonChange @@ -20,6 +22,8 @@ export const getTouchedNpmPackages: TouchedProjectLocator< const packageJsonChange = touchedFiles.find((f) => f.file === 'package.json'); if (!packageJsonChange) return []; + const globalPackages = new Set(getGlobalPackages(nxJson.plugins)); + let touched = []; const changes = packageJsonChange.getChanges(); @@ -59,6 +63,12 @@ export const getTouchedNpmPackages: TouchedProjectLocator< touched.push(implementationNpmPackage.name); } } + + if ('packageName' in npmPackage.data) { + if (globalPackages.has(npmPackage.data.packageName)) { + return Object.keys(projectGraph.nodes); + } + } } } else if (isWholeFileChange(c)) { // Whole file was touched, so all npm packages are touched. @@ -76,3 +86,11 @@ export const getTouchedNpmPackages: TouchedProjectLocator< } return touched; }; + +function getGlobalPackages(plugins: NxJsonConfiguration['plugins']) { + return (plugins ?? []) + .map((p) => + getPackageNameFromImportPath(typeof p === 'string' ? p : p.plugin) + ) + .concat('nx'); +} diff --git a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts index cdd2d8203d953..ba46ea4bf7c48 100644 --- a/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts +++ b/packages/nx/src/plugins/js/project-graph/build-dependencies/target-project-locator.ts @@ -17,7 +17,7 @@ import { getRootTsConfigFileName, resolveModuleByImport, } from '../../utils/typescript'; - +import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path'; /** * The key is a combination of the package name and the workspace relative directory * containing the file importing it e.g. `lodash__packages/my-lib`, the value is the @@ -36,7 +36,7 @@ const builtInModuleSet = new Set([ ]); export function isBuiltinModuleImport(importExpr: string): boolean { - const packageName = parsePackageNameFromImportExpression(importExpr); + const packageName = getPackageNameFromImportPath(importExpr); return builtInModuleSet.has(packageName); } @@ -153,7 +153,7 @@ export class TargetProjectLocator { importExpr: string, fromFilePath: string ): string | null { - const packageName = parsePackageNameFromImportExpression(importExpr); + const packageName = getPackageNameFromImportPath(importExpr); let fullFilePath = fromFilePath; let workspaceRelativeFilePath = fromFilePath; @@ -362,15 +362,3 @@ export class TargetProjectLocator { } } } - -function parsePackageNameFromImportExpression( - importExpression: string -): string { - // Check if the package is scoped - if (importExpression.startsWith('@')) { - // For scoped packages, the package name is up to the second '/' - return importExpression.split('/').slice(0, 2).join('/'); - } - // For unscoped packages, the package name is up to the first '/' - return importExpression.split('/')[0]; -} diff --git a/packages/nx/src/plugins/js/utils/config.ts b/packages/nx/src/plugins/js/utils/config.ts index baef4c7b48cfe..4f681aa6e7f0f 100644 --- a/packages/nx/src/plugins/js/utils/config.ts +++ b/packages/nx/src/plugins/js/utils/config.ts @@ -29,6 +29,7 @@ export function jsPluginConfig( analyzePackageJson: true, analyzeSourceFiles: true, analyzeLockfile, + projectsAffectedByDependencyUpdates: 'all', ...nxJsonConfig, }; } @@ -38,6 +39,7 @@ export function jsPluginConfig( analyzeLockfile: false, analyzePackageJson: false, analyzeSourceFiles: false, + projectsAffectedByDependencyUpdates: 'all', }; } @@ -69,12 +71,14 @@ export function jsPluginConfig( analyzePackageJson: true, analyzeLockfile, analyzeSourceFiles: true, + projectsAffectedByDependencyUpdates: 'all', }; } else { return { analyzePackageJson: true, analyzeLockfile, analyzeSourceFiles: false, + projectsAffectedByDependencyUpdates: 'all', }; } } diff --git a/packages/nx/src/project-graph/affected/affected-project-graph.ts b/packages/nx/src/project-graph/affected/affected-project-graph.ts index 7bbfd3c731a1d..52cc5c8f0f218 100644 --- a/packages/nx/src/project-graph/affected/affected-project-graph.ts +++ b/packages/nx/src/project-graph/affected/affected-project-graph.ts @@ -60,10 +60,10 @@ function filterAffectedProjects( }; const reversed = reverse(graph); ctx.touchedProjects.forEach((p) => { - addAffectedNodes(p, reversed, result, []); + addAffectedNodes(p, reversed, result, new Set()); }); ctx.touchedProjects.forEach((p) => { - addAffectedDependencies(p, reversed, result, []); + addAffectedDependencies(p, reversed, result, new Set()); }); return result; } @@ -72,15 +72,15 @@ function addAffectedNodes( startingProject: string, reversed: ProjectGraph, result: ProjectGraph, - visited: string[] + visited: Set ): void { - if (visited.indexOf(startingProject) > -1) return; + if (visited.has(startingProject)) return; const reversedNode = reversed.nodes[startingProject]; const reversedExternalNode = reversed.externalNodes[startingProject]; if (!reversedNode && !reversedExternalNode) { throw new Error(`Invalid project name is detected: "${startingProject}"`); } - visited.push(startingProject); + visited.add(startingProject); if (reversedNode) { result.nodes[startingProject] = reversedNode; result.dependencies[startingProject] = []; @@ -96,10 +96,10 @@ function addAffectedDependencies( startingProject: string, reversed: ProjectGraph, result: ProjectGraph, - visited: string[] + visited: Set ): void { - if (visited.indexOf(startingProject) > -1) return; - visited.push(startingProject); + if (visited.has(startingProject)) return; + visited.add(startingProject); if (reversed.dependencies[startingProject]) { reversed.dependencies[startingProject].forEach(({ target }) => addAffectedDependencies(target, reversed, result, visited) diff --git a/packages/nx/src/utils/get-package-name-from-import-path.spec.ts b/packages/nx/src/utils/get-package-name-from-import-path.spec.ts new file mode 100644 index 0000000000000..d0d7bec5fc13e --- /dev/null +++ b/packages/nx/src/utils/get-package-name-from-import-path.spec.ts @@ -0,0 +1,13 @@ +import { getPackageNameFromImportPath } from './get-package-name-from-import-path'; + +describe('getPackageNameFromImportPath', () => { + it.each([ + ['@nx/workspace', '@nx/workspace'], + ['@nx/workspace/plugin', '@nx/workspace'], + ['@nx/workspace/other', '@nx/workspace'], + ['nx/plugin', 'nx'], + ['nx', 'nx'], + ])('should return %s for %s', (input, expected) => { + expect(getPackageNameFromImportPath(input)).toEqual(expected); + }); +}); diff --git a/packages/nx/src/utils/get-package-name-from-import-path.ts b/packages/nx/src/utils/get-package-name-from-import-path.ts new file mode 100644 index 0000000000000..9269c07b27e30 --- /dev/null +++ b/packages/nx/src/utils/get-package-name-from-import-path.ts @@ -0,0 +1,14 @@ +//# Converts import paths to package names. +//# e.g. - `@nx/workspace` -> `@nx/workspace` +//# - `@nx/workspace/plugin` -> `@nx/workspace` +//# - `@nx/workspace/other` -> `@nx/workspace` +//# - `nx/plugin` -> `nx` +export function getPackageNameFromImportPath(importExpression: string) { + // Check if the package is scoped + if (importExpression.startsWith('@')) { + // For scoped packages, the package name is up to the second '/' + return importExpression.split('/').slice(0, 2).join('/'); + } + // For unscoped packages, the package name is up to the first '/' + return importExpression.split('/')[0]; +}