feat(core): allow skipping lockfile for affected (#23509)

This commit is contained in:
Craigory Coppola 2024-06-04 18:15:00 -04:00 committed by GitHub
parent 43ddd72aa2
commit cda799b8a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 76 additions and 23 deletions

View File

@ -40,6 +40,7 @@ export interface NrwlJsPluginConfig {
analyzeSourceFiles?: boolean; analyzeSourceFiles?: boolean;
analyzePackageJson?: boolean; analyzePackageJson?: boolean;
analyzeLockfile?: boolean; analyzeLockfile?: boolean;
projectsAffectedByDependencyUpdates?: 'all' | 'auto' | string[];
} }
interface NxInstallationConfiguration { interface NxInstallationConfiguration {

View File

@ -1,10 +1,25 @@
import { readNxJson } from '../../../../config/configuration';
import { TouchedProjectLocator } from '../../../../project-graph/affected/affected-project-graph-models'; import { TouchedProjectLocator } from '../../../../project-graph/affected/affected-project-graph-models';
import { WholeFileChange } from '../../../../project-graph/file-utils'; import { WholeFileChange } from '../../../../project-graph/file-utils';
import { JsonChange } from '../../../../utils/json-diff'; import { JsonChange } from '../../../../utils/json-diff';
import { jsPluginConfig as readJsPluginConfig } from '../../utils/config';
import { findMatchingProjects } from '../../../../utils/find-matching-projects';
export const getTouchedProjectsFromLockFile: TouchedProjectLocator< export const getTouchedProjectsFromLockFile: TouchedProjectLocator<
WholeFileChange | JsonChange WholeFileChange | JsonChange
> = (fileChanges, projectGraphNodes): string[] => { > = (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 = [ const lockFiles = [
'package-lock.json', 'package-lock.json',
'yarn.lock', 'yarn.lock',

View File

@ -13,6 +13,8 @@ import {
ProjectGraphExternalNode, ProjectGraphExternalNode,
ProjectGraphProjectNode, ProjectGraphProjectNode,
} from '../../../../config/project-graph'; } from '../../../../config/project-graph';
import { NxJsonConfiguration } from '../../../../config/nx-json';
import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path';
export const getTouchedNpmPackages: TouchedProjectLocator< export const getTouchedNpmPackages: TouchedProjectLocator<
WholeFileChange | JsonChange WholeFileChange | JsonChange
@ -20,6 +22,8 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
const packageJsonChange = touchedFiles.find((f) => f.file === 'package.json'); const packageJsonChange = touchedFiles.find((f) => f.file === 'package.json');
if (!packageJsonChange) return []; if (!packageJsonChange) return [];
const globalPackages = new Set(getGlobalPackages(nxJson.plugins));
let touched = []; let touched = [];
const changes = packageJsonChange.getChanges(); const changes = packageJsonChange.getChanges();
@ -59,6 +63,12 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
touched.push(implementationNpmPackage.name); touched.push(implementationNpmPackage.name);
} }
} }
if ('packageName' in npmPackage.data) {
if (globalPackages.has(npmPackage.data.packageName)) {
return Object.keys(projectGraph.nodes);
}
}
} }
} else if (isWholeFileChange(c)) { } else if (isWholeFileChange(c)) {
// Whole file was touched, so all npm packages are touched. // Whole file was touched, so all npm packages are touched.
@ -76,3 +86,11 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
} }
return touched; return touched;
}; };
function getGlobalPackages(plugins: NxJsonConfiguration['plugins']) {
return (plugins ?? [])
.map((p) =>
getPackageNameFromImportPath(typeof p === 'string' ? p : p.plugin)
)
.concat('nx');
}

View File

@ -17,7 +17,7 @@ import {
getRootTsConfigFileName, getRootTsConfigFileName,
resolveModuleByImport, resolveModuleByImport,
} from '../../utils/typescript'; } 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 * 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 * containing the file importing it e.g. `lodash__packages/my-lib`, the value is the
@ -36,7 +36,7 @@ const builtInModuleSet = new Set<string>([
]); ]);
export function isBuiltinModuleImport(importExpr: string): boolean { export function isBuiltinModuleImport(importExpr: string): boolean {
const packageName = parsePackageNameFromImportExpression(importExpr); const packageName = getPackageNameFromImportPath(importExpr);
return builtInModuleSet.has(packageName); return builtInModuleSet.has(packageName);
} }
@ -153,7 +153,7 @@ export class TargetProjectLocator {
importExpr: string, importExpr: string,
fromFilePath: string fromFilePath: string
): string | null { ): string | null {
const packageName = parsePackageNameFromImportExpression(importExpr); const packageName = getPackageNameFromImportPath(importExpr);
let fullFilePath = fromFilePath; let fullFilePath = fromFilePath;
let workspaceRelativeFilePath = 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];
}

View File

@ -29,6 +29,7 @@ export function jsPluginConfig(
analyzePackageJson: true, analyzePackageJson: true,
analyzeSourceFiles: true, analyzeSourceFiles: true,
analyzeLockfile, analyzeLockfile,
projectsAffectedByDependencyUpdates: 'all',
...nxJsonConfig, ...nxJsonConfig,
}; };
} }
@ -38,6 +39,7 @@ export function jsPluginConfig(
analyzeLockfile: false, analyzeLockfile: false,
analyzePackageJson: false, analyzePackageJson: false,
analyzeSourceFiles: false, analyzeSourceFiles: false,
projectsAffectedByDependencyUpdates: 'all',
}; };
} }
@ -69,12 +71,14 @@ export function jsPluginConfig(
analyzePackageJson: true, analyzePackageJson: true,
analyzeLockfile, analyzeLockfile,
analyzeSourceFiles: true, analyzeSourceFiles: true,
projectsAffectedByDependencyUpdates: 'all',
}; };
} else { } else {
return { return {
analyzePackageJson: true, analyzePackageJson: true,
analyzeLockfile, analyzeLockfile,
analyzeSourceFiles: false, analyzeSourceFiles: false,
projectsAffectedByDependencyUpdates: 'all',
}; };
} }
} }

View File

@ -60,10 +60,10 @@ function filterAffectedProjects(
}; };
const reversed = reverse(graph); const reversed = reverse(graph);
ctx.touchedProjects.forEach((p) => { ctx.touchedProjects.forEach((p) => {
addAffectedNodes(p, reversed, result, []); addAffectedNodes(p, reversed, result, new Set());
}); });
ctx.touchedProjects.forEach((p) => { ctx.touchedProjects.forEach((p) => {
addAffectedDependencies(p, reversed, result, []); addAffectedDependencies(p, reversed, result, new Set());
}); });
return result; return result;
} }
@ -72,15 +72,15 @@ function addAffectedNodes(
startingProject: string, startingProject: string,
reversed: ProjectGraph, reversed: ProjectGraph,
result: ProjectGraph, result: ProjectGraph,
visited: string[] visited: Set<string>
): void { ): void {
if (visited.indexOf(startingProject) > -1) return; if (visited.has(startingProject)) return;
const reversedNode = reversed.nodes[startingProject]; const reversedNode = reversed.nodes[startingProject];
const reversedExternalNode = reversed.externalNodes[startingProject]; const reversedExternalNode = reversed.externalNodes[startingProject];
if (!reversedNode && !reversedExternalNode) { if (!reversedNode && !reversedExternalNode) {
throw new Error(`Invalid project name is detected: "${startingProject}"`); throw new Error(`Invalid project name is detected: "${startingProject}"`);
} }
visited.push(startingProject); visited.add(startingProject);
if (reversedNode) { if (reversedNode) {
result.nodes[startingProject] = reversedNode; result.nodes[startingProject] = reversedNode;
result.dependencies[startingProject] = []; result.dependencies[startingProject] = [];
@ -96,10 +96,10 @@ function addAffectedDependencies(
startingProject: string, startingProject: string,
reversed: ProjectGraph, reversed: ProjectGraph,
result: ProjectGraph, result: ProjectGraph,
visited: string[] visited: Set<string>
): void { ): void {
if (visited.indexOf(startingProject) > -1) return; if (visited.has(startingProject)) return;
visited.push(startingProject); visited.add(startingProject);
if (reversed.dependencies[startingProject]) { if (reversed.dependencies[startingProject]) {
reversed.dependencies[startingProject].forEach(({ target }) => reversed.dependencies[startingProject].forEach(({ target }) =>
addAffectedDependencies(target, reversed, result, visited) addAffectedDependencies(target, reversed, result, visited)

View File

@ -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);
});
});

View File

@ -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];
}