fix(core): improve resolution of packages in package manager workspaces when constructing the project graph (#29795)

Main fixes:

- Identify dependencies from packages that only expose named exports (no
`.` export)
- Identify dependencies from exports containing wildcards (e.g.
`"./utils/*": "./src/utils/*.js`)
- Disallow identifying dependencies from restricted exports (e.g.
`"./foo": null`)
- Handle conditional exports (e.g. `"exports": { "import":
"./dist/index.js", "default": "./dist/index.js" }`
- Handle invalid `"exports": {}` (by not falling back to `main`)
- Handle projects included or not in package manager workspaces

## Current Behavior

## Expected Behavior

## Related Issue(s)

Fixes #29486
This commit is contained in:
Leosvel Pérez Espinosa 2025-02-03 14:03:49 +01:00 committed by GitHub
parent d655e667a1
commit 4235cf35e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 926 additions and 108 deletions

View File

@ -8,7 +8,7 @@ A node describing a project in a workspace
- [data](../../devkit/documents/ProjectGraphProjectNode#data): ProjectConfiguration & Object - [data](../../devkit/documents/ProjectGraphProjectNode#data): ProjectConfiguration & Object
- [name](../../devkit/documents/ProjectGraphProjectNode#name): string - [name](../../devkit/documents/ProjectGraphProjectNode#name): string
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "lib" | "app" | "e2e" - [type](../../devkit/documents/ProjectGraphProjectNode#type): "app" | "e2e" | "lib"
## Properties ## Properties
@ -28,4 +28,4 @@ Additional metadata about a project
### type ### type
**type**: `"lib"` \| `"app"` \| `"e2e"` **type**: `"app"` \| `"e2e"` \| `"lib"`

View File

@ -0,0 +1,343 @@
import {
checkFilesExist,
cleanupProject,
createFile,
getPackageManagerCommand,
getSelectedPackageManager,
newProject,
readJson,
runCLI,
runCommand,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { basename } from 'node:path';
describe('Graph - TS solution setup', () => {
beforeAll(() => {
newProject({
packages: ['@nx/js'],
preset: 'ts',
});
});
afterAll(() => {
cleanupProject();
});
it('should detect dependencies from local packages included in the package manager workspaces', () => {
const pm = getSelectedPackageManager();
const pmc = getPackageManagerCommand({ packageManager: pm });
createPackage('pkg-parent', { sourceFilePaths: ['index.ts'] });
// invalid definition with no entry fields in package.json
createPackage('pkg1');
// only `main`
createPackage('pkg2', {
packageJsonEntryFields: { main: './dist/src/index.js' },
});
// invalid empty exports, no code is accessible
createPackage('pkg3', {
packageJsonEntryFields: {
main: './dist/src/index.js',
types: './dist/src/index.d.ts',
exports: {},
},
});
// '.' entry point
createPackage('pkg4', {
packageJsonEntryFields: {
exports: {
'.': {
types: './dist/src/index.d.ts',
default: './dist/src/index.js',
},
},
},
});
// conditional exports
createPackage('pkg5', {
packageJsonEntryFields: {
exports: {
types: './dist/src/index.d.ts',
default: './dist/src/index.js',
},
},
});
// exports set to a string
createPackage('pkg6', {
packageJsonEntryFields: {
exports: './dist/src/index.js',
},
});
// '.' entry point set to source (non buildable library)
createPackage('pkg7', {
packageJsonEntryFields: {
exports: {
'.': './src/index.ts',
},
},
});
// matches a path alias that resolves correctly
createPackage('pkg8', {
packageJsonEntryFields: {
exports: {
'.': {
types: './dist/src/index.d.ts',
default: './dist/src/index.js',
},
},
},
});
// matches a path alias that doesn't resolve correctly, should still be
// picked up by the package manager workspaces fallback resolution
createPackage('pkg9', {
packageJsonEntryFields: {
exports: {
'.': {
types: './src/index.ts',
default: './src/index.ts',
},
},
},
});
// only named exports, no '.' entry point
createPackage('pkg10', {
packageJsonEntryFields: {
exports: {
'./feature1': {
types: './dist/src/index.d.ts',
default: './dist/src/index.js',
},
},
},
});
// wildcard exports
createPackage('pkg11', {
sourceFilePaths: ['src/utils/util1.ts'],
packageJsonEntryFields: {
exports: {
'./utils/*': {
types: './dist/src/utils/*.d.ts',
default: './dist/src/utils/*.js',
},
},
},
});
// restricted exports, should not be picked up as a dependency
createPackage('pkg12', {
packageJsonEntryFields: {
exports: { './feature1': null },
},
});
// valid package that will be imported as @proj/pkg14 due to a TS path alias
createPackage('pkg13', {
packageJsonEntryFields: {
exports: {
'.': {
types: './src/index.ts',
default: './src/index.ts',
},
},
},
});
// valid package that we'll be foreshadowed by a TS path alias pointing to
// pkg13, so should not be picked up as a dependency
createPackage('pkg14', {
packageJsonEntryFields: {
exports: {
'.': {
types: './src/index.ts',
default: './src/index.ts',
},
},
},
});
// project outside of the package manager workspaces
createPackage('lib1', { root: 'libs/lib1' });
if (pm === 'pnpm') {
// for pnpm we need to add the local packages as dependencies to each consumer package.json
// we keep out the ones we want to validate won't be picked up as dependencies, otherwise
// they would be included because the package.json depends on them
updateJson('packages/pkg-parent/package.json', (json) => {
json.dependencies ??= {};
json.dependencies['@proj/pkg2'] = 'workspace:*';
json.dependencies['@proj/pkg4'] = 'workspace:*';
json.dependencies['@proj/pkg5'] = 'workspace:*';
json.dependencies['@proj/pkg6'] = 'workspace:*';
json.dependencies['@proj/pkg7'] = 'workspace:*';
json.dependencies['@proj/pkg8'] = 'workspace:*';
json.dependencies['@proj/pkg9'] = 'workspace:*';
json.dependencies['@proj/pkg10'] = 'workspace:*';
json.dependencies['@proj/pkg11'] = 'workspace:*';
json.dependencies['@proj/pkg13'] = 'workspace:*';
return json;
});
}
runCommand(pmc.install);
updateJson('tsconfig.base.json', (json) => {
json.compilerOptions.baseUrl = '.';
json.compilerOptions.paths = {
'@proj/pkg8': ['packages/pkg8/src/index.ts'],
'@proj/pkg9': ['dist/packages/pkg9'],
'@proj/pkg14': ['packages/pkg13/src/index.ts'],
};
return json;
});
// add TS project references to all packages, including the invalid ones
// so they are all built ahead of pkg-parent and we can assert the test
// correctly sets them up as invalid imports
updateJson('packages/pkg-parent/tsconfig.json', (json) => {
json.references = [
{ path: '../pkg1' },
{ path: '../pkg2' },
{ path: '../pkg3' },
{ path: '../pkg4' },
{ path: '../pkg5' },
{ path: '../pkg6' },
{ path: '../pkg7' },
{ path: '../pkg8' },
{ path: '../pkg9' },
{ path: '../pkg10' },
{ path: '../pkg11' },
{ path: '../pkg12' },
{ path: '../pkg13' },
{ path: '../pkg14' },
];
return json;
});
updateFile(
'packages/pkg-parent/index.ts',
() => `
import { pkg1 } from '@proj/pkg1';
import { pkg2 } from '@proj/pkg2';
import { pkg3 } from '@proj/pkg3';
import { pkg4 } from '@proj/pkg4';
import { pkg5 } from '@proj/pkg5';
import { pkg6 } from '@proj/pkg6';
import { pkg7 } from '@proj/pkg7';
import { pkg8 } from '@proj/pkg8';
import { pkg9 } from '@proj/pkg9';
import { pkg10 } from '@proj/pkg10/feature1';
import { util1 } from '@proj/pkg11/utils/util1';
import { pkg12 } from '@proj/pkg12/feature1';
import { pkg13 } from '@proj/pkg14';
// this is an invalid import that doesn't match any TS path alias and
// it's not included in the package manager workspaces, it should not
// be picked up as a dependency
import { lib1 } from '@proj/lib1';
// use the correct imports, leave out the invalid ones so it's easier to remove them later
export const pkgParent = pkg2 + pkg4 + pkg5 + pkg6 + pkg7 + pkg8 + pkg9 + pkg10 + util1 + pkg13;
`
);
runCLI(`graph --file graph.json`);
const { graph } = readJson('graph.json');
// pkg1, pkg3, pkg12, pkg14, and lib1 are not detected as dependencies
expect(
graph.dependencies['@proj/pkg-parent'].map((d) => d.target)
).toStrictEqual([
'@proj/pkg2',
'@proj/pkg4',
'@proj/pkg5',
'@proj/pkg6',
'@proj/pkg7',
'@proj/pkg8',
'@proj/pkg9',
'@proj/pkg10',
'@proj/pkg11',
'@proj/pkg13',
]);
// assert build fails due to the invalid imports
const output = runCommand(`${pmc.exec} tsc -b packages/pkg-parent`);
expect(output).toContain(
`error TS2307: Cannot find module '@proj/pkg1' or its corresponding type declarations.`
);
expect(output).toContain(
`error TS2307: Cannot find module '@proj/pkg3' or its corresponding type declarations.`
);
expect(output).toContain(
`error TS2307: Cannot find module '@proj/pkg12/feature1' or its corresponding type declarations.`
);
expect(output).toContain(
`error TS2307: Cannot find module '@proj/lib1' or its corresponding type declarations.`
);
// remove the invalid imports
updateFile('packages/pkg-parent/index.ts', (content) =>
content
.replace(`import { pkg1 } from '@proj/pkg1';`, '')
.replace(`import { pkg3 } from '@proj/pkg3';`, '')
.replace(`import { pkg12 } from '@proj/pkg12/feature1';`, '')
.replace(`import { lib1 } from '@proj/lib1';`, '')
);
// assert build succeeds, tsc outputs nothing when successful
expect(runCommand(`${pmc.exec} tsc -b packages/pkg-parent`)).toBe('');
checkFilesExist(
'packages/pkg-parent/dist/index.js',
'packages/pkg-parent/dist/index.d.ts'
);
});
function createPackage(
name: string,
options?: {
root?: string;
sourceFilePaths?: string[];
packageJsonEntryFields?: {
main?: string;
types?: string;
exports?: string | Record<string, any>;
};
}
): void {
const root = options?.root ?? `packages/${name}`;
createFile(
`${root}/package.json`,
JSON.stringify(
{
name: `@proj/${name}`,
version: '1.0.0',
...options?.packageJsonEntryFields,
},
null,
2
)
);
createFile(
`${root}/tsconfig.json`,
JSON.stringify(
{
extends: '../../tsconfig.base.json',
compilerOptions: {
outDir: './dist',
emitDeclarationOnly: false,
},
include: ['**/*.ts'],
},
null,
2
)
);
const sourceFilePaths = options?.sourceFilePaths ?? ['src/index.ts'];
for (const sourceFilePath of sourceFilePaths) {
const fileName = basename(sourceFilePath, '.ts');
createFile(
`${root}/${sourceFilePath}`,
`export const ${
fileName !== 'index' ? fileName : name
} = '${name} - ${fileName}';`
);
}
}
});

View File

@ -5,6 +5,7 @@ import {
ensureCypressInstallation, ensureCypressInstallation,
ensurePlaywrightBrowsersInstallation, ensurePlaywrightBrowsersInstallation,
getNpmMajorVersion, getNpmMajorVersion,
getPnpmVersion,
getPublishedVersion, getPublishedVersion,
getStrippedEnvironmentVariables, getStrippedEnvironmentVariables,
getYarnMajorVersion, getYarnMajorVersion,
@ -17,6 +18,7 @@ import * as isCI from 'is-ci';
import { fileExists, readJson, updateJson } from './file-utils'; import { fileExists, readJson, updateJson } from './file-utils';
import { logError, stripConsoleColors } from './log-utils'; import { logError, stripConsoleColors } from './log-utils';
import { existsSync } from 'fs-extra'; import { existsSync } from 'fs-extra';
import { gte } from 'semver';
export interface RunCmdOpts { export interface RunCmdOpts {
silenceError?: boolean; silenceError?: boolean;
@ -111,9 +113,11 @@ export function getPackageManagerCommand({
addDev: string; addDev: string;
list: string; list: string;
runLerna: string; runLerna: string;
exec: string;
} { } {
const npmMajorVersion = getNpmMajorVersion(); const npmMajorVersion = getNpmMajorVersion();
const yarnMajorVersion = getYarnMajorVersion(path); const yarnMajorVersion = getYarnMajorVersion(path);
const pnpmVersion = getPnpmVersion();
const publishedVersion = getPublishedVersion(); const publishedVersion = getPublishedVersion();
const isYarnWorkspace = fileExists(join(path, 'package.json')) const isYarnWorkspace = fileExists(join(path, 'package.json'))
? readJson('package.json').workspaces ? readJson('package.json').workspaces
@ -135,6 +139,7 @@ export function getPackageManagerCommand({
addDev: `npm install --legacy-peer-deps -D`, addDev: `npm install --legacy-peer-deps -D`,
list: 'npm ls --depth 10', list: 'npm ls --depth 10',
runLerna: `npx lerna`, runLerna: `npx lerna`,
exec: 'npx',
}, },
yarn: { yarn: {
createWorkspace: `npx ${ createWorkspace: `npx ${
@ -156,6 +161,7 @@ export function getPackageManagerCommand({
yarnMajorVersion && +yarnMajorVersion >= 2 yarnMajorVersion && +yarnMajorVersion >= 2
? 'yarn lerna' ? 'yarn lerna'
: `yarn --silent lerna`, : `yarn --silent lerna`,
exec: 'yarn',
}, },
// Pnpm 3.5+ adds nx to // Pnpm 3.5+ adds nx to
pnpm: { pnpm: {
@ -170,6 +176,7 @@ export function getPackageManagerCommand({
addDev: isPnpmWorkspace ? 'pnpm add -Dw' : 'pnpm add -D', addDev: isPnpmWorkspace ? 'pnpm add -Dw' : 'pnpm add -D',
list: 'pnpm ls --depth 10', list: 'pnpm ls --depth 10',
runLerna: `pnpm exec lerna`, runLerna: `pnpm exec lerna`,
exec: pnpmVersion && gte(pnpmVersion, '6.13.0') ? 'pnpm exec' : 'pnpx',
}, },
bun: { bun: {
createWorkspace: `bunx create-nx-workspace@${publishedVersion}`, createWorkspace: `bunx create-nx-workspace@${publishedVersion}`,
@ -183,6 +190,7 @@ export function getPackageManagerCommand({
addDev: 'bun install -D', addDev: 'bun install -D',
list: 'bun pm ls', list: 'bun pm ls',
runLerna: `bunx lerna`, runLerna: `bunx lerna`,
exec: 'bun',
}, },
}[packageManager.trim() as PackageManager]; }[packageManager.trim() as PackageManager];
} }

View File

@ -99,6 +99,17 @@ export function getYarnMajorVersion(path: string): string | undefined {
} }
} }
export function getPnpmVersion(): string | undefined {
try {
const pnpmVersion = execSync(`pnpm -v`, {
encoding: 'utf-8',
}).trim();
return pnpmVersion;
} catch {
return undefined;
}
}
export function getLatestLernaVersion(): string { export function getLatestLernaVersion(): string {
const lernaVersion = execSync(`npm view lerna version`, { const lernaVersion = execSync(`npm view lerna version`, {
encoding: 'utf-8', encoding: 'utf-8',

View File

@ -1,6 +1,9 @@
import { createNodesFromFiles, NxPluginV2 } from '../src/project-graph/plugins'; import { createNodesFromFiles, NxPluginV2 } from '../src/project-graph/plugins';
import { workspaceRoot } from '../src/utils/workspace-root'; import { workspaceRoot } from '../src/utils/workspace-root';
import { createNodeFromPackageJson } from '../src/plugins/package-json'; import {
buildPackageJsonWorkspacesMatcher,
createNodeFromPackageJson,
} from '../src/plugins/package-json';
import { workspaceDataDirectory } from '../src/utils/cache-directory'; import { workspaceDataDirectory } from '../src/utils/cache-directory';
import { join } from 'path'; import { join } from 'path';
import { ProjectConfiguration } from '../src/config/workspace-json-project-json'; import { ProjectConfiguration } from '../src/config/workspace-json-project-json';
@ -31,8 +34,19 @@ const plugin: NxPluginV2 = {
(configFiles, options, context) => { (configFiles, options, context) => {
const cache = readPackageJsonConfigurationCache(); const cache = readPackageJsonConfigurationCache();
const isInPackageJsonWorkspaces = buildPackageJsonWorkspacesMatcher(
context.workspaceRoot,
(f) => readJsonFile(join(context.workspaceRoot, f))
);
const result = createNodesFromFiles( const result = createNodesFromFiles(
(f) => createNodeFromPackageJson(f, workspaceRoot, cache), (packageJsonPath) =>
createNodeFromPackageJson(
packageJsonPath,
workspaceRoot,
cache,
isInPackageJsonWorkspaces(packageJsonPath)
),
configFiles, configFiles,
options, options,
context context

View File

@ -1,7 +1,7 @@
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { extname, join } from 'path'; import { extname, join } from 'path';
import { resolve as resolveExports } from 'resolve.exports'; import { resolve as resolveExports } from 'resolve.exports';
import { getPackageEntryPointsToProjectMap } from '../plugins/js/utils/packages'; import { getWorkspacePackagesMetadata } from '../plugins/js/utils/packages';
import { registerPluginTSTranspiler } from '../project-graph/plugins'; import { registerPluginTSTranspiler } from '../project-graph/plugins';
import { normalizePath } from '../utils/path'; import { normalizePath } from '../utils/path';
import type { ProjectConfiguration } from './workspace-json-project-json'; import type { ProjectConfiguration } from './workspace-json-project-json';
@ -119,16 +119,16 @@ export function resolveSchema(
}); });
} }
let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>; let packageToProjectMap: Record<string, ProjectConfiguration>;
function tryResolveFromSource( function tryResolveFromSource(
path: string, path: string,
directory: string, directory: string,
packageName: string, packageName: string,
projects: Record<string, ProjectConfiguration> projects: Record<string, ProjectConfiguration>
): string | null { ): string | null {
packageEntryPointsToProjectMap ??= packageToProjectMap ??=
getPackageEntryPointsToProjectMap(projects); getWorkspacePackagesMetadata(projects).packageToProjectMap;
const localProject = packageEntryPointsToProjectMap[packageName]; const localProject = packageToProjectMap[packageName];
if (!localProject) { if (!localProject) {
// it doesn't match any of the package names from the local projects // it doesn't match any of the package names from the local projects
return null; return null;

View File

@ -58,6 +58,7 @@ describe('Workspaces', () => {
"metadata": { "metadata": {
"description": "my-package description", "description": "my-package description",
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageName": "my-package", "packageName": "my-package",
}, },
"targetGroups": {}, "targetGroups": {},

View File

@ -140,6 +140,8 @@ export interface ProjectMetadata {
js?: { js?: {
packageName: string; packageName: string;
packageExports?: PackageJson['exports']; packageExports?: PackageJson['exports'];
packageMain?: string;
isInPackageManagerWorkspaces?: boolean;
}; };
} }

View File

@ -74,7 +74,11 @@ describe('explicit package json dependencies', () => {
data: { data: {
root: 'libs/proj', root: 'libs/proj',
metadata: { metadata: {
js: { packageName: 'proj', packageExports: undefined }, js: {
packageName: 'proj',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
}, },
}, },
}, },
@ -84,7 +88,11 @@ describe('explicit package json dependencies', () => {
data: { data: {
root: 'libs/proj2', root: 'libs/proj2',
metadata: { metadata: {
js: { packageName: 'proj2', packageExports: undefined }, js: {
packageName: 'proj2',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
}, },
}, },
}, },
@ -94,7 +102,11 @@ describe('explicit package json dependencies', () => {
data: { data: {
root: 'libs/proj4', root: 'libs/proj4',
metadata: { metadata: {
js: { packageName: 'proj3', packageExports: undefined }, js: {
packageName: 'proj3',
packageExports: undefined,
isInPackageManagerWorkspaces: true,
},
}, },
}, },
}, },

View File

@ -221,6 +221,8 @@ describe('TargetProjectLocator', () => {
js: { js: {
packageName: '@proj/child-pm-workspaces', packageName: '@proj/child-pm-workspaces',
packageExports: undefined, packageExports: undefined,
isInPackageManagerWorkspaces: true,
packageMain: 'index.ts',
}, },
}, },
}, },
@ -1011,6 +1013,197 @@ describe('TargetProjectLocator', () => {
expect(result).toEqual('npm:foo@0.0.1'); expect(result).toEqual('npm:foo@0.0.1');
}); });
}); });
describe('findDependencyInWorkspaceProjects', () => {
it.each`
exports
${undefined}
${'dist/index.js'}
${{}}
${{ '.': 'dist/index.js' }}
${{ './subpath': './dist/subpath.js' }}
${{ import: './dist/index.js', default: './dist/index.js' }}
`(
'should find "@org/pkg1" package as "pkg1" project when exports="$exports"',
({ exports }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};
const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg1');
expect(result).toEqual('pkg1');
}
);
it('should not match "@org/pkg2" when there is no workspace project with that package name', () => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
isInPackageManagerWorkspaces: true,
},
},
},
},
};
const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findDependencyInWorkspaceProjects('@org/pkg2');
expect(result).toBeFalsy();
});
});
describe('findImportInWorkspaceProjects', () => {
it.each`
exports | importPath
${'dist/index.js'} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1'}
`(
'should find "$importPath" as "pkg1" project when exports="$exports"',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};
const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);
expect(result).toEqual('pkg1');
}
);
it.each`
exports | importPath
${'dist/index.js'} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1'}
`(
'should not find "$importPath" as "pkg1" project when exports="$exports" and isInPackageManagerWorkspaces is false',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: false,
},
},
},
},
};
const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);
expect(result).toBeFalsy();
}
);
it.each`
exports | importPath
${undefined} | ${'@org/pkg1'}
${{}} | ${'@org/pkg1'}
${{ '.': 'dist/index.js' }} | ${'@org/pkg1/subpath'}
${{ './subpath': './dist/subpath.js' }} | ${'@org/pkg1/subpath/extra-path'}
${{ './*': './dist/*.js' }} | ${'@org/pkg1/subpath/extra-path'}
${{ './feature': null }} | ${'@org/pkg1/feature'}
${{ import: './dist/index.js', default: './dist/index.js' }} | ${'@org/pkg1/subpath'}
`(
'should not match "$importPath" when exports="$exports"',
({ exports, importPath }) => {
let projects: Record<string, ProjectGraphProjectNode> = {
pkg1: {
name: 'pkg1',
type: 'lib' as const,
data: {
root: 'pkg1',
metadata: {
js: {
packageName: '@org/pkg1',
packageExports: exports,
isInPackageManagerWorkspaces: true,
},
},
},
},
};
const targetProjectLocator = new TargetProjectLocator(
projects,
{},
new Map()
);
const result =
targetProjectLocator.findImportInWorkspaceProjects(importPath);
expect(result).toBeFalsy();
}
);
});
}); });
describe('isBuiltinModuleImport()', () => { describe('isBuiltinModuleImport()', () => {

View File

@ -13,7 +13,10 @@ import { isRelativePath, readJsonFile } from '../../../../utils/fileutils';
import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path'; import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path';
import type { PackageJson } from '../../../../utils/package-json'; import type { PackageJson } from '../../../../utils/package-json';
import { workspaceRoot } from '../../../../utils/workspace-root'; import { workspaceRoot } from '../../../../utils/workspace-root';
import { getPackageEntryPointsToProjectMap } from '../../utils/packages'; import {
getWorkspacePackagesMetadata,
matchImportToWildcardEntryPointsToProjectMap,
} from '../../utils/packages';
import { resolveRelativeToDir } from '../../utils/resolve-relative-to-dir'; import { resolveRelativeToDir } from '../../utils/resolve-relative-to-dir';
import { import {
getRootTsConfigFileName, getRootTsConfigFileName,
@ -45,10 +48,11 @@ export class TargetProjectLocator {
private tsConfig = this.getRootTsConfig(); private tsConfig = this.getRootTsConfig();
private paths = this.tsConfig.config?.compilerOptions?.paths; private paths = this.tsConfig.config?.compilerOptions?.paths;
private typescriptResolutionCache = new Map<string, string | null>(); private typescriptResolutionCache = new Map<string, string | null>();
private packageEntryPointsToProjectMap: Record< private packagesMetadata: {
string, entryPointsToProjectMap: Record<string, ProjectGraphProjectNode>;
ProjectGraphProjectNode wildcardEntryPointsToProjectMap: Record<string, ProjectGraphProjectNode>;
>; packageToProjectMap: Record<string, ProjectGraphProjectNode>;
};
constructor( constructor(
private readonly nodes: Record<string, ProjectGraphProjectNode>, private readonly nodes: Record<string, ProjectGraphProjectNode>,
@ -142,7 +146,7 @@ export class TargetProjectLocator {
// fall back to see if it's a locally linked workspace project where the // fall back to see if it's a locally linked workspace project where the
// output might not exist yet // output might not exist yet
const localProject = this.findDependencyInWorkspaceProjects(importExpr); const localProject = this.findImportInWorkspaceProjects(importExpr);
if (localProject) { if (localProject) {
return localProject; return localProject;
} }
@ -254,12 +258,25 @@ export class TargetProjectLocator {
return undefined; return undefined;
} }
findDependencyInWorkspaceProjects(dep: string): string | null { findImportInWorkspaceProjects(importPath: string): string | null {
this.packageEntryPointsToProjectMap ??= getPackageEntryPointsToProjectMap( this.packagesMetadata ??= getWorkspacePackagesMetadata(this.nodes);
this.nodes
if (this.packagesMetadata.entryPointsToProjectMap[importPath]) {
return this.packagesMetadata.entryPointsToProjectMap[importPath].name;
}
const project = matchImportToWildcardEntryPointsToProjectMap(
this.packagesMetadata.wildcardEntryPointsToProjectMap,
importPath
); );
return this.packageEntryPointsToProjectMap[dep]?.name ?? null; return project?.name;
}
findDependencyInWorkspaceProjects(dep: string): string | null {
this.packagesMetadata ??= getWorkspacePackagesMetadata(this.nodes);
return this.packagesMetadata.packageToProjectMap[dep]?.name;
} }
private resolveImportWithTypescript( private resolveImportWithTypescript(

View File

@ -1,11 +1,20 @@
import { minimatch } from 'minimatch';
import { join } from 'node:path/posix'; import { join } from 'node:path/posix';
import type { ProjectGraphProjectNode } from '../../../config/project-graph'; import type { ProjectGraphProjectNode } from '../../../config/project-graph';
import type { ProjectConfiguration } from '../../../config/workspace-json-project-json'; import type { ProjectConfiguration } from '../../../config/workspace-json-project-json';
export function getPackageEntryPointsToProjectMap< export function getWorkspacePackagesMetadata<
T extends ProjectGraphProjectNode | ProjectConfiguration T extends ProjectGraphProjectNode | ProjectConfiguration
>(projects: Record<string, T>): Record<string, T> { >(
const result: Record<string, T> = {}; projects: Record<string, T>
): {
entryPointsToProjectMap: Record<string, T>;
wildcardEntryPointsToProjectMap: Record<string, T>;
packageToProjectMap: Record<string, T>;
} {
const entryPointsToProjectMap: Record<string, T> = {};
const wildcardEntryPointsToProjectMap: Record<string, T> = {};
const packageToProjectMap: Record<string, T> = {};
for (const project of Object.values(projects)) { for (const project of Object.values(projects)) {
const metadata = const metadata =
'data' in project ? project.data.metadata : project.metadata; 'data' in project ? project.data.metadata : project.metadata;
@ -14,17 +23,75 @@ export function getPackageEntryPointsToProjectMap<
continue; continue;
} }
const { packageName, packageExports } = metadata.js; const {
if (!packageExports || typeof packageExports === 'string') { packageName,
// no `exports` or it points to a file, which would be the equivalent of packageExports,
// an '.' export, in which case the package name is the entry point packageMain,
result[packageName] = project; isInPackageManagerWorkspaces,
} else { } = metadata.js;
for (const entryPoint of Object.keys(packageExports)) { packageToProjectMap[packageName] = project;
result[join(packageName, entryPoint)] = project;
if (!isInPackageManagerWorkspaces) {
// it is not included in the package manager workspaces config, so we
// skip it since the exports information wouldn't be used by the Node.js
// resolution
continue;
}
if (packageExports) {
if (typeof packageExports === 'string') {
// it points to a file, which would be the equivalent of an '.' export,
// in which case the package name is the entry point
entryPointsToProjectMap[packageName] = project;
} else {
for (const entryPoint of Object.keys(packageExports)) {
if (packageExports[entryPoint] === null) {
// if the entry point is restricted, we skip it
continue;
}
if (entryPoint.startsWith('.')) {
// it is a relative subpath export
if (entryPoint.includes('*')) {
wildcardEntryPointsToProjectMap[join(packageName, entryPoint)] =
project;
} else {
entryPointsToProjectMap[join(packageName, entryPoint)] = project;
}
} else {
// it's a conditional export, so we use the package name as the entry point
// https://nodejs.org/api/packages.html#conditional-exports
entryPointsToProjectMap[packageName] = project;
}
}
} }
} else if (packageMain) {
// if there is no exports, but there is a main, the package name is the
// entry point
entryPointsToProjectMap[packageName] = project;
} }
} }
return result; return {
entryPointsToProjectMap,
wildcardEntryPointsToProjectMap,
packageToProjectMap,
};
}
export function matchImportToWildcardEntryPointsToProjectMap<
T extends ProjectGraphProjectNode | ProjectConfiguration
>(
wildcardEntryPointsToProjectMap: Record<string, T>,
importPath: string
): T | null {
if (!Object.keys(wildcardEntryPointsToProjectMap).length) {
return null;
}
const matchingPair = Object.entries(wildcardEntryPointsToProjectMap).find(
([key]) => minimatch(importPath, key)
);
return matchingPair?.[1];
} }

View File

@ -48,7 +48,7 @@ describe('nx package.json workspaces plugin', () => {
'/root' '/root'
); );
expect(createNodeFromPackageJson('package.json', '/root', {})) expect(createNodeFromPackageJson('package.json', '/root', {}, false))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
{ {
"projects": { "projects": {
@ -56,7 +56,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": false,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "root", "packageName": "root",
}, },
"targetGroups": { "targetGroups": {
@ -95,7 +97,12 @@ describe('nx package.json workspaces plugin', () => {
} }
`); `);
expect( expect(
createNodeFromPackageJson('packages/lib-a/package.json', '/root', {}) createNodeFromPackageJson(
'packages/lib-a/package.json',
'/root',
{},
false
)
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
{ {
"projects": { "projects": {
@ -103,7 +110,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": "lib-a description", "description": "lib-a description",
"js": { "js": {
"isInPackageManagerWorkspaces": false,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "lib-a", "packageName": "lib-a",
}, },
"targetGroups": { "targetGroups": {
@ -142,7 +151,12 @@ describe('nx package.json workspaces plugin', () => {
} }
`); `);
expect( expect(
createNodeFromPackageJson('packages/lib-b/package.json', '/root', {}) createNodeFromPackageJson(
'packages/lib-b/package.json',
'/root',
{},
false
)
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
{ {
"projects": { "projects": {
@ -157,7 +171,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": "lib-b description", "description": "lib-b description",
"js": { "js": {
"isInPackageManagerWorkspaces": false,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "lib-b", "packageName": "lib-b",
}, },
"targetGroups": { "targetGroups": {
@ -265,7 +281,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "vite", "packageName": "vite",
}, },
"targetGroups": {}, "targetGroups": {},
@ -367,7 +385,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "vite", "packageName": "vite",
}, },
"targetGroups": {}, "targetGroups": {},
@ -465,7 +485,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "vite", "packageName": "vite",
}, },
"targetGroups": {}, "targetGroups": {},
@ -547,7 +569,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "root", "packageName": "root",
}, },
"targetGroups": { "targetGroups": {
@ -629,7 +653,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "root", "packageName": "root",
}, },
"targetGroups": { "targetGroups": {
@ -718,7 +744,9 @@ describe('nx package.json workspaces plugin', () => {
"metadata": { "metadata": {
"description": undefined, "description": undefined,
"js": { "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": undefined, "packageExports": undefined,
"packageMain": undefined,
"packageName": "root", "packageName": "root",
}, },
"targetGroups": {}, "targetGroups": {},
@ -769,13 +797,17 @@ describe('nx package.json workspaces plugin', () => {
); );
expect( expect(
createNodeFromPackageJson('apps/myapp/package.json', '/root', {}) createNodeFromPackageJson('apps/myapp/package.json', '/root', {}, false)
.projects['apps/myapp'].projectType .projects['apps/myapp'].projectType
).toEqual('application'); ).toEqual('application');
expect( expect(
createNodeFromPackageJson('packages/mylib/package.json', '/root', {}) createNodeFromPackageJson(
.projects['packages/mylib'].projectType 'packages/mylib/package.json',
'/root',
{},
false
).projects['packages/mylib'].projectType
).toEqual('library'); ).toEqual('library');
}); });
@ -797,8 +829,9 @@ describe('nx package.json workspaces plugin', () => {
); );
expect( expect(
createNodeFromPackageJson('package.json', '/root', {}).projects['.'] createNodeFromPackageJson('package.json', '/root', {}, false).projects[
.projectType '.'
].projectType
).toEqual('library'); ).toEqual('library');
}); });
@ -823,19 +856,26 @@ describe('nx package.json workspaces plugin', () => {
); );
expect( expect(
createNodeFromPackageJson('packages/mylib/package.json', '/root', {}) createNodeFromPackageJson(
.projects['packages/mylib'].projectType 'packages/mylib/package.json',
'/root',
{},
false
).projects['packages/mylib'].projectType
).toEqual('library'); ).toEqual('library');
expect( expect(
createNodeFromPackageJson('example/package.json', '/root', {}).projects[ createNodeFromPackageJson('example/package.json', '/root', {}, false)
'example' .projects['example'].projectType
].projectType
).toBeUndefined(); ).toBeUndefined();
}); });
it('should store package name and exports in the project metadata', () => { it('should store js package metadata', async () => {
vol.fromJSON( vol.fromJSON(
{ {
'package.json': JSON.stringify({
name: 'repo',
workspaces: ['packages/*'],
}),
'packages/lib-a/package.json': JSON.stringify({ 'packages/lib-a/package.json': JSON.stringify({
name: 'lib-a', name: 'lib-a',
description: 'lib-a description', description: 'lib-a description',
@ -845,59 +885,139 @@ describe('nx package.json workspaces plugin', () => {
'.': './dist/index.js', '.': './dist/index.js',
}, },
}), }),
// not in package manager workspaces
'libs/lib-b/package.json': JSON.stringify({
name: 'lib-b',
description: 'lib-b description',
scripts: { test: 'jest' },
exports: {
'./package.json': './package.json',
'.': './dist/index.js',
},
}),
// project.json so it's identified as a project
'libs/lib-b/project.json': '{}',
}, },
'/root' '/root'
); );
expect( expect(
createNodeFromPackageJson('packages/lib-a/package.json', '/root', {}) await createNodesV2[1](
[
'package.json',
'packages/lib-a/package.json',
'libs/lib-b/package.json',
'libs/lib-b/project.json',
],
undefined,
context
)
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
{ [
"projects": { [
"packages/lib-a": { "packages/lib-a/package.json",
"metadata": { {
"description": "lib-a description", "projects": {
"js": { "packages/lib-a": {
"packageExports": {
".": "./dist/index.js",
"./package.json": "./package.json",
},
"packageName": "lib-a",
},
"targetGroups": {
"NPM Scripts": [
"test",
],
},
},
"name": "lib-a",
"root": "packages/lib-a",
"sourceRoot": "packages/lib-a",
"tags": [
"npm:public",
],
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
"test": {
"executor": "nx:run-script",
"metadata": { "metadata": {
"runCommand": "npm run test", "description": "lib-a description",
"scriptContent": "jest", "js": {
"isInPackageManagerWorkspaces": true,
"packageExports": {
".": "./dist/index.js",
"./package.json": "./package.json",
},
"packageMain": undefined,
"packageName": "lib-a",
},
"targetGroups": {
"NPM Scripts": [
"test",
],
},
}, },
"options": { "name": "lib-a",
"script": "test", "root": "packages/lib-a",
"sourceRoot": "packages/lib-a",
"tags": [
"npm:public",
],
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
"test": {
"executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": {
"script": "test",
},
},
}, },
}, },
}, },
}, },
}, ],
} [
"libs/lib-b/package.json",
{
"projects": {
"libs/lib-b": {
"metadata": {
"description": "lib-b description",
"js": {
"isInPackageManagerWorkspaces": false,
"packageExports": {
".": "./dist/index.js",
"./package.json": "./package.json",
},
"packageMain": undefined,
"packageName": "lib-b",
},
"targetGroups": {
"NPM Scripts": [
"test",
],
},
},
"name": "lib-b",
"root": "libs/lib-b",
"sourceRoot": "libs/lib-b",
"tags": [
"npm:public",
],
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
"test": {
"executor": "nx:run-script",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": {
"script": "test",
},
},
},
},
},
},
],
]
`); `);
}); });
}); });

View File

@ -50,8 +50,10 @@ export const createNodesV2: CreateNodesV2 = [
return createNodesFromFiles( return createNodesFromFiles(
(packageJsonPath, options, context) => { (packageJsonPath, options, context) => {
const isInPackageManagerWorkspaces =
isInPackageJsonWorkspaces(packageJsonPath);
if ( if (
!isInPackageJsonWorkspaces(packageJsonPath) && !isInPackageManagerWorkspaces &&
!isNextToProjectJson(packageJsonPath) !isNextToProjectJson(packageJsonPath)
) { ) {
// Skip if package.json is not part of the package.json workspaces and not next to a project.json. // Skip if package.json is not part of the package.json workspaces and not next to a project.json.
@ -61,7 +63,8 @@ export const createNodesV2: CreateNodesV2 = [
return createNodeFromPackageJson( return createNodeFromPackageJson(
packageJsonPath, packageJsonPath,
context.workspaceRoot, context.workspaceRoot,
cache cache,
isInPackageManagerWorkspaces
); );
}, },
packageJsons, packageJsons,
@ -91,7 +94,7 @@ function splitConfigFiles(configFiles: readonly string[]): {
export function buildPackageJsonWorkspacesMatcher( export function buildPackageJsonWorkspacesMatcher(
workspaceRoot: string, workspaceRoot: string,
readJson: (string) => any readJson: (path: string) => any
) { ) {
const patterns = getGlobPatternsFromPackageManagerWorkspaces( const patterns = getGlobPatternsFromPackageManagerWorkspaces(
workspaceRoot, workspaceRoot,
@ -129,7 +132,8 @@ export function buildPackageJsonWorkspacesMatcher(
export function createNodeFromPackageJson( export function createNodeFromPackageJson(
pkgJsonPath: string, pkgJsonPath: string,
workspaceRoot: string, workspaceRoot: string,
cache: PackageJsonConfigurationCache cache: PackageJsonConfigurationCache,
isInPackageManagerWorkspaces: boolean
) { ) {
const json: PackageJson = readJsonFile(join(workspaceRoot, pkgJsonPath)); const json: PackageJson = readJsonFile(join(workspaceRoot, pkgJsonPath));
@ -138,12 +142,7 @@ export function createNodeFromPackageJson(
const hash = hashObject({ const hash = hashObject({
...json, ...json,
root: projectRoot, root: projectRoot,
/** isInPackageManagerWorkspaces,
* Increment this number to force processing the package.json again. Do it
* when the implementation of this plugin is changed and results in different
* results for the same package.json contents.
*/
bust: 1,
}); });
const cached = cache[hash]; const cached = cache[hash];
@ -159,7 +158,8 @@ export function createNodeFromPackageJson(
json, json,
workspaceRoot, workspaceRoot,
pkgJsonPath, pkgJsonPath,
readNxJson(workspaceRoot) readNxJson(workspaceRoot),
isInPackageManagerWorkspaces
); );
cache[hash] = project; cache[hash] = project;
@ -174,7 +174,8 @@ export function buildProjectConfigurationFromPackageJson(
packageJson: PackageJson, packageJson: PackageJson,
workspaceRoot: string, workspaceRoot: string,
packageJsonPath: string, packageJsonPath: string,
nxJson: NxJsonConfiguration nxJson: NxJsonConfiguration,
isInPackageManagerWorkspaces: boolean
): ProjectConfiguration & { name: string } { ): ProjectConfiguration & { name: string } {
const normalizedPath = packageJsonPath.split('\\').join('/'); const normalizedPath = packageJsonPath.split('\\').join('/');
const projectRoot = dirname(normalizedPath); const projectRoot = dirname(normalizedPath);
@ -213,7 +214,10 @@ export function buildProjectConfigurationFromPackageJson(
...packageJson.nx, ...packageJson.nx,
targets: readTargetsFromPackageJson(packageJson, nxJson), targets: readTargetsFromPackageJson(packageJson, nxJson),
tags: getTagsFromPackageJson(packageJson), tags: getTagsFromPackageJson(packageJson),
metadata: getMetadataFromPackageJson(packageJson), metadata: getMetadataFromPackageJson(
packageJson,
isInPackageManagerWorkspaces
),
}; };
if ( if (

View File

@ -22,6 +22,7 @@ import {
readProjectConfigurationsFromRootMap, readProjectConfigurationsFromRootMap,
} from './utils/project-configuration-utils'; } from './utils/project-configuration-utils';
import { import {
buildPackageJsonWorkspacesMatcher,
buildProjectConfigurationFromPackageJson, buildProjectConfigurationFromPackageJson,
getGlobPatternsFromPackageManagerWorkspaces, getGlobPatternsFromPackageManagerWorkspaces,
} from '../plugins/package-json'; } from '../plugins/package-json';
@ -200,6 +201,11 @@ function getProjectsSync(
]; ];
const projectFiles = globWithWorkspaceContextSync(root, patterns); const projectFiles = globWithWorkspaceContextSync(root, patterns);
const isInPackageJsonWorkspaces = buildPackageJsonWorkspacesMatcher(
root,
(f) => readJsonFile(join(root, f))
);
const rootMap: Record<string, ProjectConfiguration> = {}; const rootMap: Record<string, ProjectConfiguration> = {};
for (const projectFile of projectFiles) { for (const projectFile of projectFiles) {
if (basename(projectFile) === 'project.json') { if (basename(projectFile) === 'project.json') {
@ -218,7 +224,8 @@ function getProjectsSync(
packageJson, packageJson,
root, root,
projectFile, projectFile,
nxJson nxJson,
isInPackageJsonWorkspaces(projectFile)
); );
if (!rootMap[config.root]) { if (!rootMap[config.root]) {
mergeProjectConfigurationIntoRootMap( mergeProjectConfigurationIntoRootMap(

View File

@ -1,7 +1,10 @@
import * as path from 'node:path'; import * as path from 'node:path';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages'; import {
getWorkspacePackagesMetadata,
matchImportToWildcardEntryPointsToProjectMap,
} from '../../plugins/js/utils/packages';
import { readJsonFile } from '../../utils/fileutils'; import { readJsonFile } from '../../utils/fileutils';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
import { normalizePath } from '../../utils/path'; import { normalizePath } from '../../utils/path';
@ -119,6 +122,7 @@ function lookupLocalPlugin(
} }
let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>; let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
let wildcardEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
function findNxProjectForImportPath( function findNxProjectForImportPath(
importPath: string, importPath: string,
projects: Record<string, ProjectConfiguration>, projects: Record<string, ProjectConfiguration>,
@ -146,12 +150,24 @@ function findNxProjectForImportPath(
} }
} }
packageEntryPointsToProjectMap ??= if (!packageEntryPointsToProjectMap && !wildcardEntryPointsToProjectMap) {
getPackageEntryPointsToProjectMap(projects); ({
entryPointsToProjectMap: packageEntryPointsToProjectMap,
wildcardEntryPointsToProjectMap,
} = getWorkspacePackagesMetadata(projects));
}
if (packageEntryPointsToProjectMap[importPath]) { if (packageEntryPointsToProjectMap[importPath]) {
return packageEntryPointsToProjectMap[importPath]; return packageEntryPointsToProjectMap[importPath];
} }
const project = matchImportToWildcardEntryPointsToProjectMap(
wildcardEntryPointsToProjectMap,
importPath
);
if (project) {
return project;
}
logger.verbose( logger.verbose(
'Unable to find local plugin', 'Unable to find local plugin',
possibleTsPaths, possibleTsPaths,

View File

@ -154,9 +154,10 @@ export function buildTargetFromScript(
let packageManagerCommand: PackageManagerCommands | undefined; let packageManagerCommand: PackageManagerCommands | undefined;
export function getMetadataFromPackageJson( export function getMetadataFromPackageJson(
packageJson: PackageJson packageJson: PackageJson,
isInPackageManagerWorkspaces: boolean
): ProjectMetadata { ): ProjectMetadata {
const { scripts, nx, description, name, exports } = packageJson; const { scripts, nx, description, name, exports, main } = packageJson;
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {}); const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
return { return {
targetGroups: { targetGroups: {
@ -166,6 +167,8 @@ export function getMetadataFromPackageJson(
js: { js: {
packageName: name, packageName: name,
packageExports: exports, packageExports: exports,
packageMain: main,
isInPackageManagerWorkspaces,
}, },
}; };
} }