fix(misc): handle outputs with globs when normalizing tsconfig path mappings for buidable libraries (#30506)

## Current Behavior

If a buildable library depends on another buildable library that has
`outputs` using globs the build fails when using an executor that remaps
the dependency TypeScript path mappings to the build outputs.

## Expected Behavior

Building a buildable library using an executor that remaps the
dependency TypeScript path mappings to the build outputs should succeed.
The remapping logic should identify and replace the glob patterns and
keep the fixed part of the pattern (no segment with wildcards).

Note: additionally, an obsolete check was removed from the
`@nx/angular:package` and `@nx/angular:delegate-build`.

## Related Issue(s)

Fixes #30041
This commit is contained in:
Leosvel Pérez Espinosa 2025-03-27 10:31:53 +01:00 committed by GitHub
parent e4e9973db3
commit bae3acd48e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 85 deletions

View File

@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
import { joinPathFragments, parseTargetString, runExecutor } from '@nx/devkit'; import { joinPathFragments, parseTargetString, runExecutor } from '@nx/devkit';
import { import {
calculateProjectBuildableDependencies, calculateProjectBuildableDependencies,
checkDependentProjectsHaveBeenBuilt,
createTmpTsConfig, createTmpTsConfig,
} from '@nx/js/src/utils/buildable-libs-utils'; } from '@nx/js/src/utils/buildable-libs-utils';
import type { DelegateBuildExecutorSchema } from './schema'; import type { DelegateBuildExecutorSchema } from './schema';
@ -27,17 +26,6 @@ export async function* delegateBuildExecutor(
dependencies dependencies
); );
if (
!checkDependentProjectsHaveBeenBuilt(
context.root,
context.projectName,
context.targetName,
dependencies
)
) {
return { success: false };
}
const { buildTarget, ...targetOptions } = options; const { buildTarget, ...targetOptions } = options;
const delegateTarget = parseTargetString(buildTarget, context); const delegateTarget = parseTargetString(buildTarget, context);

View File

@ -2,7 +2,6 @@ import type { ExecutorContext } from '@nx/devkit';
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await'; import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
import { import {
calculateProjectBuildableDependencies, calculateProjectBuildableDependencies,
checkDependentProjectsHaveBeenBuilt,
createTmpTsConfig, createTmpTsConfig,
type DependentBuildableProjectNode, type DependentBuildableProjectNode,
} from '@nx/js/src/utils/buildable-libs-utils'; } from '@nx/js/src/utils/buildable-libs-utils';
@ -65,8 +64,7 @@ export function createLibraryExecutor(
); );
} }
const { target, dependencies, topLevelDependencies } = const { dependencies } = calculateProjectBuildableDependencies(
calculateProjectBuildableDependencies(
context.taskGraph, context.taskGraph,
context.projectGraph, context.projectGraph,
context.root, context.root,
@ -74,16 +72,6 @@ export function createLibraryExecutor(
context.targetName, context.targetName,
context.configurationName context.configurationName
); );
if (
!checkDependentProjectsHaveBeenBuilt(
context.root,
context.projectName,
context.targetName,
dependencies
)
) {
return Promise.resolve({ success: false });
}
if (options.watch) { if (options.watch) {
return yield* eachValueFrom( return yield* eachValueFrom(

View File

@ -45,6 +45,41 @@ describe('updatePaths', () => {
], ],
}); });
}); });
it('should handle outputs with glob patterns', () => {
const paths: Record<string, string[]> = {
'@proj/lib1': ['libs/lib1/src/index.ts'],
'@proj/lib2': ['libs/lib2/src/index.ts'],
'@proj/lib3': ['libs/lib3/src/index.ts'],
};
updatePaths(
[
{
name: '@proj/lib1',
node: { name: 'lib1', type: 'lib', data: { root: 'libs/lib1' } },
outputs: ['dist/libs/lib1/**/*.js'],
},
{
name: '@proj/lib2',
node: { name: 'lib2', type: 'lib', data: { root: 'libs/lib2' } },
outputs: ['dist/libs/lib2/*.js'],
},
{
name: '@proj/lib3',
node: { name: 'lib3', type: 'lib', data: { root: 'libs/lib3' } },
outputs: ['dist/libs/lib3/foo-*/*.js'],
},
],
paths
);
expect(paths).toEqual({
'@proj/lib1': ['dist/libs/lib1'],
'@proj/lib2': ['dist/libs/lib2'],
'@proj/lib3': ['dist/libs/lib3'],
});
});
}); });
describe('calculateProjectDependencies', () => { describe('calculateProjectDependencies', () => {

View File

@ -8,12 +8,11 @@ import {
getOutputsForTargetAndConfiguration, getOutputsForTargetAndConfiguration,
parseTargetString, parseTargetString,
readJsonFile, readJsonFile,
stripIndents,
writeJsonFile, writeJsonFile,
} from '@nx/devkit'; } from '@nx/devkit';
import { unlinkSync } from 'fs'; import { unlinkSync } from 'fs';
import { isNpmProject } from 'nx/src/project-graph/operators'; import { isNpmProject } from 'nx/src/project-graph/operators';
import { directoryExists, fileExists } from 'nx/src/utils/fileutils'; import { fileExists } from 'nx/src/utils/fileutils';
import { output } from 'nx/src/utils/output'; import { output } from 'nx/src/utils/output';
import { dirname, join, relative, extname, resolve } from 'path'; import { dirname, join, relative, extname, resolve } from 'path';
import type * as ts from 'typescript'; import type * as ts from 'typescript';
@ -477,56 +476,6 @@ function cleanupTmpTsConfigFile(tmpTsConfigPath) {
} catch (e) {} } catch (e) {}
} }
export function checkDependentProjectsHaveBeenBuilt(
root: string,
projectName: string,
targetName: string,
projectDependencies: DependentBuildableProjectNode[]
): boolean {
const missing = findMissingBuildDependencies(
root,
projectName,
targetName,
projectDependencies
);
if (missing.length > 0) {
console.error(stripIndents`
It looks like all of ${projectName}'s dependencies have not been built yet:
${missing.map((x) => ` - ${x.node.name}`).join('\n')}
You might be missing a "targetDefaults" configuration in your root nx.json (https://nx.dev/reference/project-configuration#target-defaults),
or "dependsOn" configured in ${projectName}'s project.json (https://nx.dev/reference/project-configuration#dependson)
`);
return false;
} else {
return true;
}
}
export function findMissingBuildDependencies(
root: string,
projectName: string,
targetName: string,
projectDependencies: DependentBuildableProjectNode[]
): DependentBuildableProjectNode[] {
const depLibsToBuildFirst: DependentBuildableProjectNode[] = [];
// verify whether all dependent libraries have been built
projectDependencies.forEach((dep) => {
if (dep.node.type !== 'lib') {
return;
}
const paths = dep.outputs.map((p) => join(root, p));
if (!paths.some(directoryExists)) {
depLibsToBuildFirst.push(dep);
}
});
return depLibsToBuildFirst;
}
export function updatePaths( export function updatePaths(
dependencies: DependentBuildableProjectNode[], dependencies: DependentBuildableProjectNode[],
paths: Record<string, string[]> paths: Record<string, string[]>
@ -539,7 +488,9 @@ export function updatePaths(
// If there are outputs // If there are outputs
if (dep.outputs && dep.outputs.length > 0) { if (dep.outputs && dep.outputs.length > 0) {
// Directly map the dependency name to the output paths (dist/packages/..., etc.) // Directly map the dependency name to the output paths (dist/packages/..., etc.)
paths[dep.name] = dep.outputs; paths[dep.name] = dep.outputs.map((output) =>
output.replace(/(\*|\/[^\/]*\*).*$/, '')
);
// check for secondary entrypoints // check for secondary entrypoints
// For each registered path // For each registered path