fix(angular): handle inferred projects without project configuration files in migrations (#31633)

## Current Behavior

Some Angular migrations collect the Angular projects from the project
graph using the dependencies information. When reading the project
configuration for those projects, it can throw an error if trying to do
it for a completely inferred project (it doesn't have a project
configuration file).

## Expected Behavior

The Angular migrations collecting Angular projects from the project
graph using the dependencies information should gracefully handle
projects that were completely inferred when trying to read the project
configuration.

In fact, the current migrations didn't need to read the project
configuration and could use the project graph information directly, so
the call to read the project configuration was removed.

## Related Issue(s)

Fixes #31607
This commit is contained in:
Leosvel Pérez Espinosa 2025-06-18 12:31:48 +02:00 committed by GitHub
parent 9406d2bfdb
commit e1dfe6ea09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 29 additions and 46 deletions

View File

@ -25,7 +25,6 @@ const newImportPath = '@ngrx/router-store/data-persistence';
export default async function (tree: Tree): Promise<void> {
const projects = await getProjectsFilteredByDependencies(
tree,
angularPluginTargetNames
);
@ -38,7 +37,7 @@ export default async function (tree: Tree): Promise<void> {
const cachedFileMap = readFileMapCache().fileMap.projectFileMap;
const filesWithNxAngularImports: FileData[] = [];
for (const { graphNode } of projects) {
for (const graphNode of projects) {
const files = filterFilesWithNxAngularDep(
cachedFileMap[graphNode.name] || []
);

View File

@ -43,11 +43,11 @@ export default async function (tree: Tree) {
return;
}
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@nguniversal/common',
'npm:@nguniversal/express-engine',
]);
for (const { project } of projects) {
for (const { data: project } of projects) {
if (project.projectType !== 'application') {
continue;
}

View File

@ -2,7 +2,7 @@ import { formatFiles, visitNotIgnoredFiles, type Tree } from '@nx/devkit';
import { getProjectsFilteredByDependencies } from '../utils/projects';
export default async function (tree: Tree) {
const angularProjects = await getProjectsFilteredByDependencies(tree, [
const angularProjects = await getProjectsFilteredByDependencies([
'npm:@angular/core',
]);
@ -13,8 +13,8 @@ export default async function (tree: Tree) {
const zoneJsImportRegex = /(['"`])zone\.js\/dist\/zone(['"`])/g;
const zoneJsTestingImportRegex =
/(['"`])zone\.js\/dist\/zone-testing(['"`])/g;
for (const { project } of angularProjects) {
visitNotIgnoredFiles(tree, project.root, (file) => {
for (const graphNode of angularProjects) {
visitNotIgnoredFiles(tree, graphNode.data.root, (file) => {
// we are only interested in .ts files
if (!file.endsWith('.ts')) {
return;

View File

@ -10,13 +10,12 @@ import { getProjectsFilteredByDependencies } from '../utils/projects';
const preferStandaloneRule = '@angular-eslint/prefer-standalone';
export default async function (tree: Tree) {
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/core',
]);
for (const {
project: { root },
} of projects) {
for (const graphNode of projects) {
const root = graphNode.data.root;
if (!isEslintConfigSupported(tree, root)) {
// ESLint config is not supported, skip
continue;

View File

@ -14,14 +14,13 @@ export const rulesToRemove = [
];
export default async function (tree: Tree) {
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/core',
]);
let hasRootProject = false;
for (const {
project: { root },
} of projects) {
for (const graphNode of projects) {
const root = graphNode.data.root;
if (!isEslintConfigSupported(tree, root)) {
// ESLint config is not supported, skip
continue;

View File

@ -4,12 +4,12 @@ import { FileChangeRecorder } from '../../utils/file-change-recorder';
import { getProjectsFilteredByDependencies } from '../utils/projects';
export default async function (tree: Tree) {
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/ssr',
]);
for (const { project } of projects) {
visitNotIgnoredFiles(tree, project.root, (path) => {
for (const graphNode of projects) {
visitNotIgnoredFiles(tree, graphNode.data.root, (path) => {
if (!path.endsWith('.ts') || path.endsWith('.d.ts')) {
return;
}

View File

@ -26,7 +26,6 @@ const newImportPath = '@ngrx/router-store/data-persistence';
export default async function (tree: Tree): Promise<void> {
const projects = await getProjectsFilteredByDependencies(
tree,
angularPluginTargetNames
);
@ -39,7 +38,7 @@ export default async function (tree: Tree): Promise<void> {
const cachedFileMap = readFileMapCache().fileMap.projectFileMap;
const filesWithNxAngularImports: FileData[] = [];
for (const { graphNode } of projects) {
for (const graphNode of projects) {
const files = filterFilesWithNxAngularDep(
cachedFileMap[graphNode.name] || []
);

View File

@ -10,7 +10,7 @@ import { angularDevkitVersion } from '../../utils/versions';
import { getProjectsFilteredByDependencies } from '../utils/projects';
export default async function (tree: Tree) {
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/platform-server',
]);
@ -19,8 +19,8 @@ export default async function (tree: Tree) {
}
let isSsrInstalled = false;
for (const { project } of projects) {
visitNotIgnoredFiles(tree, project.root, (file) => {
for (const graphNode of projects) {
visitNotIgnoredFiles(tree, graphNode.data.root, (file) => {
if (!file.endsWith('.ts') || file.endsWith('.d.ts')) {
return;
}

View File

@ -5,7 +5,7 @@ import { FileChangeRecorder } from '../../utils/file-change-recorder';
import { getProjectsFilteredByDependencies } from '../utils/projects';
export default async function (tree: Tree) {
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/ssr',
]);
@ -13,8 +13,8 @@ export default async function (tree: Tree) {
return;
}
for (const { project } of projects) {
visitNotIgnoredFiles(tree, project.root, (file) => {
for (const graphNode of projects) {
visitNotIgnoredFiles(tree, graphNode.data.root, (file) => {
if (!file.endsWith('.ts') || file.endsWith('.d.ts')) {
return;
}

View File

@ -12,12 +12,12 @@ type TsConfig = {
export default async function (tree: Tree) {
const uniqueTsConfigs = new Set<string>();
const projects = await getProjectsFilteredByDependencies(tree, [
const projects = await getProjectsFilteredByDependencies([
'npm:@angular/core',
]);
for (const { project } of projects) {
for (const [, target] of allProjectTargets(project)) {
for (const graphNode of projects) {
for (const [, target] of allProjectTargets(graphNode.data)) {
for (const [, options] of allTargetOptions<{ tsConfig?: string }>(
target
)) {

View File

@ -1,19 +1,9 @@
import type {
ProjectConfiguration,
ProjectGraphProjectNode,
Tree,
} from '@nx/devkit';
import { createProjectGraphAsync, readProjectConfiguration } from '@nx/devkit';
import type { ProjectGraphProjectNode } from '@nx/devkit';
import { createProjectGraphAsync } from '@nx/devkit';
export async function getProjectsFilteredByDependencies(
tree: Tree,
dependencies: string[]
): Promise<
Array<{
project: ProjectConfiguration;
graphNode: ProjectGraphProjectNode;
}>
> {
): Promise<ProjectGraphProjectNode[]> {
const projectGraph = await createProjectGraphAsync();
return Object.entries(projectGraph.dependencies)
@ -22,8 +12,5 @@ export async function getProjectsFilteredByDependencies(
!projectGraph.externalNodes?.[node] &&
deps.some(({ target }) => dependencies.includes(target))
)
.map(([projectName]) => ({
project: readProjectConfiguration(tree, projectName),
graphNode: projectGraph.nodes[projectName],
}));
.map(([projectName]) => projectGraph.nodes[projectName]);
}