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:
parent
d655e667a1
commit
4235cf35e3
@ -8,7 +8,7 @@ A node describing a project in a workspace
|
||||
|
||||
- [data](../../devkit/documents/ProjectGraphProjectNode#data): ProjectConfiguration & Object
|
||||
- [name](../../devkit/documents/ProjectGraphProjectNode#name): string
|
||||
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "lib" | "app" | "e2e"
|
||||
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "app" | "e2e" | "lib"
|
||||
|
||||
## Properties
|
||||
|
||||
@ -28,4 +28,4 @@ Additional metadata about a project
|
||||
|
||||
### type
|
||||
|
||||
• **type**: `"lib"` \| `"app"` \| `"e2e"`
|
||||
• **type**: `"app"` \| `"e2e"` \| `"lib"`
|
||||
|
||||
343
e2e/nx/src/graph-ts-solution.test.ts
Normal file
343
e2e/nx/src/graph-ts-solution.test.ts
Normal 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}';`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -5,6 +5,7 @@ import {
|
||||
ensureCypressInstallation,
|
||||
ensurePlaywrightBrowsersInstallation,
|
||||
getNpmMajorVersion,
|
||||
getPnpmVersion,
|
||||
getPublishedVersion,
|
||||
getStrippedEnvironmentVariables,
|
||||
getYarnMajorVersion,
|
||||
@ -17,6 +18,7 @@ import * as isCI from 'is-ci';
|
||||
import { fileExists, readJson, updateJson } from './file-utils';
|
||||
import { logError, stripConsoleColors } from './log-utils';
|
||||
import { existsSync } from 'fs-extra';
|
||||
import { gte } from 'semver';
|
||||
|
||||
export interface RunCmdOpts {
|
||||
silenceError?: boolean;
|
||||
@ -111,9 +113,11 @@ export function getPackageManagerCommand({
|
||||
addDev: string;
|
||||
list: string;
|
||||
runLerna: string;
|
||||
exec: string;
|
||||
} {
|
||||
const npmMajorVersion = getNpmMajorVersion();
|
||||
const yarnMajorVersion = getYarnMajorVersion(path);
|
||||
const pnpmVersion = getPnpmVersion();
|
||||
const publishedVersion = getPublishedVersion();
|
||||
const isYarnWorkspace = fileExists(join(path, 'package.json'))
|
||||
? readJson('package.json').workspaces
|
||||
@ -135,6 +139,7 @@ export function getPackageManagerCommand({
|
||||
addDev: `npm install --legacy-peer-deps -D`,
|
||||
list: 'npm ls --depth 10',
|
||||
runLerna: `npx lerna`,
|
||||
exec: 'npx',
|
||||
},
|
||||
yarn: {
|
||||
createWorkspace: `npx ${
|
||||
@ -156,6 +161,7 @@ export function getPackageManagerCommand({
|
||||
yarnMajorVersion && +yarnMajorVersion >= 2
|
||||
? 'yarn lerna'
|
||||
: `yarn --silent lerna`,
|
||||
exec: 'yarn',
|
||||
},
|
||||
// Pnpm 3.5+ adds nx to
|
||||
pnpm: {
|
||||
@ -170,6 +176,7 @@ export function getPackageManagerCommand({
|
||||
addDev: isPnpmWorkspace ? 'pnpm add -Dw' : 'pnpm add -D',
|
||||
list: 'pnpm ls --depth 10',
|
||||
runLerna: `pnpm exec lerna`,
|
||||
exec: pnpmVersion && gte(pnpmVersion, '6.13.0') ? 'pnpm exec' : 'pnpx',
|
||||
},
|
||||
bun: {
|
||||
createWorkspace: `bunx create-nx-workspace@${publishedVersion}`,
|
||||
@ -183,6 +190,7 @@ export function getPackageManagerCommand({
|
||||
addDev: 'bun install -D',
|
||||
list: 'bun pm ls',
|
||||
runLerna: `bunx lerna`,
|
||||
exec: 'bun',
|
||||
},
|
||||
}[packageManager.trim() as PackageManager];
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
const lernaVersion = execSync(`npm view lerna version`, {
|
||||
encoding: 'utf-8',
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { createNodesFromFiles, NxPluginV2 } from '../src/project-graph/plugins';
|
||||
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 { join } from 'path';
|
||||
import { ProjectConfiguration } from '../src/config/workspace-json-project-json';
|
||||
@ -31,8 +34,19 @@ const plugin: NxPluginV2 = {
|
||||
(configFiles, options, context) => {
|
||||
const cache = readPackageJsonConfigurationCache();
|
||||
|
||||
const isInPackageJsonWorkspaces = buildPackageJsonWorkspacesMatcher(
|
||||
context.workspaceRoot,
|
||||
(f) => readJsonFile(join(context.workspaceRoot, f))
|
||||
);
|
||||
|
||||
const result = createNodesFromFiles(
|
||||
(f) => createNodeFromPackageJson(f, workspaceRoot, cache),
|
||||
(packageJsonPath) =>
|
||||
createNodeFromPackageJson(
|
||||
packageJsonPath,
|
||||
workspaceRoot,
|
||||
cache,
|
||||
isInPackageJsonWorkspaces(packageJsonPath)
|
||||
),
|
||||
configFiles,
|
||||
options,
|
||||
context
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { extname, join } from 'path';
|
||||
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 { normalizePath } from '../utils/path';
|
||||
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(
|
||||
path: string,
|
||||
directory: string,
|
||||
packageName: string,
|
||||
projects: Record<string, ProjectConfiguration>
|
||||
): string | null {
|
||||
packageEntryPointsToProjectMap ??=
|
||||
getPackageEntryPointsToProjectMap(projects);
|
||||
const localProject = packageEntryPointsToProjectMap[packageName];
|
||||
packageToProjectMap ??=
|
||||
getWorkspacePackagesMetadata(projects).packageToProjectMap;
|
||||
const localProject = packageToProjectMap[packageName];
|
||||
if (!localProject) {
|
||||
// it doesn't match any of the package names from the local projects
|
||||
return null;
|
||||
|
||||
@ -58,6 +58,7 @@ describe('Workspaces', () => {
|
||||
"metadata": {
|
||||
"description": "my-package description",
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageName": "my-package",
|
||||
},
|
||||
"targetGroups": {},
|
||||
|
||||
@ -140,6 +140,8 @@ export interface ProjectMetadata {
|
||||
js?: {
|
||||
packageName: string;
|
||||
packageExports?: PackageJson['exports'];
|
||||
packageMain?: string;
|
||||
isInPackageManagerWorkspaces?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -74,7 +74,11 @@ describe('explicit package json dependencies', () => {
|
||||
data: {
|
||||
root: 'libs/proj',
|
||||
metadata: {
|
||||
js: { packageName: 'proj', packageExports: undefined },
|
||||
js: {
|
||||
packageName: 'proj',
|
||||
packageExports: undefined,
|
||||
isInPackageManagerWorkspaces: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -84,7 +88,11 @@ describe('explicit package json dependencies', () => {
|
||||
data: {
|
||||
root: 'libs/proj2',
|
||||
metadata: {
|
||||
js: { packageName: 'proj2', packageExports: undefined },
|
||||
js: {
|
||||
packageName: 'proj2',
|
||||
packageExports: undefined,
|
||||
isInPackageManagerWorkspaces: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -94,7 +102,11 @@ describe('explicit package json dependencies', () => {
|
||||
data: {
|
||||
root: 'libs/proj4',
|
||||
metadata: {
|
||||
js: { packageName: 'proj3', packageExports: undefined },
|
||||
js: {
|
||||
packageName: 'proj3',
|
||||
packageExports: undefined,
|
||||
isInPackageManagerWorkspaces: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -221,6 +221,8 @@ describe('TargetProjectLocator', () => {
|
||||
js: {
|
||||
packageName: '@proj/child-pm-workspaces',
|
||||
packageExports: undefined,
|
||||
isInPackageManagerWorkspaces: true,
|
||||
packageMain: 'index.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1011,6 +1013,197 @@ describe('TargetProjectLocator', () => {
|
||||
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()', () => {
|
||||
|
||||
@ -13,7 +13,10 @@ import { isRelativePath, readJsonFile } from '../../../../utils/fileutils';
|
||||
import { getPackageNameFromImportPath } from '../../../../utils/get-package-name-from-import-path';
|
||||
import type { PackageJson } from '../../../../utils/package-json';
|
||||
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 {
|
||||
getRootTsConfigFileName,
|
||||
@ -45,10 +48,11 @@ export class TargetProjectLocator {
|
||||
private tsConfig = this.getRootTsConfig();
|
||||
private paths = this.tsConfig.config?.compilerOptions?.paths;
|
||||
private typescriptResolutionCache = new Map<string, string | null>();
|
||||
private packageEntryPointsToProjectMap: Record<
|
||||
string,
|
||||
ProjectGraphProjectNode
|
||||
>;
|
||||
private packagesMetadata: {
|
||||
entryPointsToProjectMap: Record<string, ProjectGraphProjectNode>;
|
||||
wildcardEntryPointsToProjectMap: Record<string, ProjectGraphProjectNode>;
|
||||
packageToProjectMap: Record<string, ProjectGraphProjectNode>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
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
|
||||
// output might not exist yet
|
||||
const localProject = this.findDependencyInWorkspaceProjects(importExpr);
|
||||
const localProject = this.findImportInWorkspaceProjects(importExpr);
|
||||
if (localProject) {
|
||||
return localProject;
|
||||
}
|
||||
@ -254,12 +258,25 @@ export class TargetProjectLocator {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findDependencyInWorkspaceProjects(dep: string): string | null {
|
||||
this.packageEntryPointsToProjectMap ??= getPackageEntryPointsToProjectMap(
|
||||
this.nodes
|
||||
findImportInWorkspaceProjects(importPath: string): string | null {
|
||||
this.packagesMetadata ??= getWorkspacePackagesMetadata(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(
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
import { minimatch } from 'minimatch';
|
||||
import { join } from 'node:path/posix';
|
||||
import type { ProjectGraphProjectNode } from '../../../config/project-graph';
|
||||
import type { ProjectConfiguration } from '../../../config/workspace-json-project-json';
|
||||
|
||||
export function getPackageEntryPointsToProjectMap<
|
||||
export function getWorkspacePackagesMetadata<
|
||||
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)) {
|
||||
const metadata =
|
||||
'data' in project ? project.data.metadata : project.metadata;
|
||||
@ -14,17 +23,75 @@ export function getPackageEntryPointsToProjectMap<
|
||||
continue;
|
||||
}
|
||||
|
||||
const { packageName, packageExports } = metadata.js;
|
||||
if (!packageExports || typeof packageExports === 'string') {
|
||||
// no `exports` or it points to a file, which would be the equivalent of
|
||||
// an '.' export, in which case the package name is the entry point
|
||||
result[packageName] = project;
|
||||
} else {
|
||||
for (const entryPoint of Object.keys(packageExports)) {
|
||||
result[join(packageName, entryPoint)] = project;
|
||||
const {
|
||||
packageName,
|
||||
packageExports,
|
||||
packageMain,
|
||||
isInPackageManagerWorkspaces,
|
||||
} = metadata.js;
|
||||
packageToProjectMap[packageName] = 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];
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ describe('nx package.json workspaces plugin', () => {
|
||||
'/root'
|
||||
);
|
||||
|
||||
expect(createNodeFromPackageJson('package.json', '/root', {}))
|
||||
expect(createNodeFromPackageJson('package.json', '/root', {}, false))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
@ -56,7 +56,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": false,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "root",
|
||||
},
|
||||
"targetGroups": {
|
||||
@ -95,7 +97,12 @@ describe('nx package.json workspaces plugin', () => {
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
createNodeFromPackageJson('packages/lib-a/package.json', '/root', {})
|
||||
createNodeFromPackageJson(
|
||||
'packages/lib-a/package.json',
|
||||
'/root',
|
||||
{},
|
||||
false
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
@ -103,7 +110,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": "lib-a description",
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": false,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "lib-a",
|
||||
},
|
||||
"targetGroups": {
|
||||
@ -142,7 +151,12 @@ describe('nx package.json workspaces plugin', () => {
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
createNodeFromPackageJson('packages/lib-b/package.json', '/root', {})
|
||||
createNodeFromPackageJson(
|
||||
'packages/lib-b/package.json',
|
||||
'/root',
|
||||
{},
|
||||
false
|
||||
)
|
||||
).toMatchInlineSnapshot(`
|
||||
{
|
||||
"projects": {
|
||||
@ -157,7 +171,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": "lib-b description",
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": false,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "lib-b",
|
||||
},
|
||||
"targetGroups": {
|
||||
@ -265,7 +281,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "vite",
|
||||
},
|
||||
"targetGroups": {},
|
||||
@ -367,7 +385,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "vite",
|
||||
},
|
||||
"targetGroups": {},
|
||||
@ -465,7 +485,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "vite",
|
||||
},
|
||||
"targetGroups": {},
|
||||
@ -547,7 +569,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "root",
|
||||
},
|
||||
"targetGroups": {
|
||||
@ -629,7 +653,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "root",
|
||||
},
|
||||
"targetGroups": {
|
||||
@ -718,7 +744,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
"metadata": {
|
||||
"description": undefined,
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": undefined,
|
||||
"packageMain": undefined,
|
||||
"packageName": "root",
|
||||
},
|
||||
"targetGroups": {},
|
||||
@ -769,13 +797,17 @@ describe('nx package.json workspaces plugin', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
createNodeFromPackageJson('apps/myapp/package.json', '/root', {})
|
||||
createNodeFromPackageJson('apps/myapp/package.json', '/root', {}, false)
|
||||
.projects['apps/myapp'].projectType
|
||||
).toEqual('application');
|
||||
|
||||
expect(
|
||||
createNodeFromPackageJson('packages/mylib/package.json', '/root', {})
|
||||
.projects['packages/mylib'].projectType
|
||||
createNodeFromPackageJson(
|
||||
'packages/mylib/package.json',
|
||||
'/root',
|
||||
{},
|
||||
false
|
||||
).projects['packages/mylib'].projectType
|
||||
).toEqual('library');
|
||||
});
|
||||
|
||||
@ -797,8 +829,9 @@ describe('nx package.json workspaces plugin', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
createNodeFromPackageJson('package.json', '/root', {}).projects['.']
|
||||
.projectType
|
||||
createNodeFromPackageJson('package.json', '/root', {}, false).projects[
|
||||
'.'
|
||||
].projectType
|
||||
).toEqual('library');
|
||||
});
|
||||
|
||||
@ -823,19 +856,26 @@ describe('nx package.json workspaces plugin', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
createNodeFromPackageJson('packages/mylib/package.json', '/root', {})
|
||||
.projects['packages/mylib'].projectType
|
||||
createNodeFromPackageJson(
|
||||
'packages/mylib/package.json',
|
||||
'/root',
|
||||
{},
|
||||
false
|
||||
).projects['packages/mylib'].projectType
|
||||
).toEqual('library');
|
||||
expect(
|
||||
createNodeFromPackageJson('example/package.json', '/root', {}).projects[
|
||||
'example'
|
||||
].projectType
|
||||
createNodeFromPackageJson('example/package.json', '/root', {}, false)
|
||||
.projects['example'].projectType
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should store package name and exports in the project metadata', () => {
|
||||
it('should store js package metadata', async () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'package.json': JSON.stringify({
|
||||
name: 'repo',
|
||||
workspaces: ['packages/*'],
|
||||
}),
|
||||
'packages/lib-a/package.json': JSON.stringify({
|
||||
name: 'lib-a',
|
||||
description: 'lib-a description',
|
||||
@ -845,59 +885,139 @@ describe('nx package.json workspaces plugin', () => {
|
||||
'.': './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'
|
||||
);
|
||||
|
||||
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(`
|
||||
{
|
||||
"projects": {
|
||||
"packages/lib-a": {
|
||||
"metadata": {
|
||||
"description": "lib-a description",
|
||||
"js": {
|
||||
"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",
|
||||
[
|
||||
[
|
||||
"packages/lib-a/package.json",
|
||||
{
|
||||
"projects": {
|
||||
"packages/lib-a": {
|
||||
"metadata": {
|
||||
"runCommand": "npm run test",
|
||||
"scriptContent": "jest",
|
||||
"description": "lib-a description",
|
||||
"js": {
|
||||
"isInPackageManagerWorkspaces": true,
|
||||
"packageExports": {
|
||||
".": "./dist/index.js",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
"packageMain": undefined,
|
||||
"packageName": "lib-a",
|
||||
},
|
||||
"targetGroups": {
|
||||
"NPM Scripts": [
|
||||
"test",
|
||||
],
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
"script": "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": {
|
||||
"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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -50,8 +50,10 @@ export const createNodesV2: CreateNodesV2 = [
|
||||
|
||||
return createNodesFromFiles(
|
||||
(packageJsonPath, options, context) => {
|
||||
const isInPackageManagerWorkspaces =
|
||||
isInPackageJsonWorkspaces(packageJsonPath);
|
||||
if (
|
||||
!isInPackageJsonWorkspaces(packageJsonPath) &&
|
||||
!isInPackageManagerWorkspaces &&
|
||||
!isNextToProjectJson(packageJsonPath)
|
||||
) {
|
||||
// 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(
|
||||
packageJsonPath,
|
||||
context.workspaceRoot,
|
||||
cache
|
||||
cache,
|
||||
isInPackageManagerWorkspaces
|
||||
);
|
||||
},
|
||||
packageJsons,
|
||||
@ -91,7 +94,7 @@ function splitConfigFiles(configFiles: readonly string[]): {
|
||||
|
||||
export function buildPackageJsonWorkspacesMatcher(
|
||||
workspaceRoot: string,
|
||||
readJson: (string) => any
|
||||
readJson: (path: string) => any
|
||||
) {
|
||||
const patterns = getGlobPatternsFromPackageManagerWorkspaces(
|
||||
workspaceRoot,
|
||||
@ -129,7 +132,8 @@ export function buildPackageJsonWorkspacesMatcher(
|
||||
export function createNodeFromPackageJson(
|
||||
pkgJsonPath: string,
|
||||
workspaceRoot: string,
|
||||
cache: PackageJsonConfigurationCache
|
||||
cache: PackageJsonConfigurationCache,
|
||||
isInPackageManagerWorkspaces: boolean
|
||||
) {
|
||||
const json: PackageJson = readJsonFile(join(workspaceRoot, pkgJsonPath));
|
||||
|
||||
@ -138,12 +142,7 @@ export function createNodeFromPackageJson(
|
||||
const hash = hashObject({
|
||||
...json,
|
||||
root: projectRoot,
|
||||
/**
|
||||
* 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,
|
||||
isInPackageManagerWorkspaces,
|
||||
});
|
||||
|
||||
const cached = cache[hash];
|
||||
@ -159,7 +158,8 @@ export function createNodeFromPackageJson(
|
||||
json,
|
||||
workspaceRoot,
|
||||
pkgJsonPath,
|
||||
readNxJson(workspaceRoot)
|
||||
readNxJson(workspaceRoot),
|
||||
isInPackageManagerWorkspaces
|
||||
);
|
||||
|
||||
cache[hash] = project;
|
||||
@ -174,7 +174,8 @@ export function buildProjectConfigurationFromPackageJson(
|
||||
packageJson: PackageJson,
|
||||
workspaceRoot: string,
|
||||
packageJsonPath: string,
|
||||
nxJson: NxJsonConfiguration
|
||||
nxJson: NxJsonConfiguration,
|
||||
isInPackageManagerWorkspaces: boolean
|
||||
): ProjectConfiguration & { name: string } {
|
||||
const normalizedPath = packageJsonPath.split('\\').join('/');
|
||||
const projectRoot = dirname(normalizedPath);
|
||||
@ -213,7 +214,10 @@ export function buildProjectConfigurationFromPackageJson(
|
||||
...packageJson.nx,
|
||||
targets: readTargetsFromPackageJson(packageJson, nxJson),
|
||||
tags: getTagsFromPackageJson(packageJson),
|
||||
metadata: getMetadataFromPackageJson(packageJson),
|
||||
metadata: getMetadataFromPackageJson(
|
||||
packageJson,
|
||||
isInPackageManagerWorkspaces
|
||||
),
|
||||
};
|
||||
|
||||
if (
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
readProjectConfigurationsFromRootMap,
|
||||
} from './utils/project-configuration-utils';
|
||||
import {
|
||||
buildPackageJsonWorkspacesMatcher,
|
||||
buildProjectConfigurationFromPackageJson,
|
||||
getGlobPatternsFromPackageManagerWorkspaces,
|
||||
} from '../plugins/package-json';
|
||||
@ -200,6 +201,11 @@ function getProjectsSync(
|
||||
];
|
||||
const projectFiles = globWithWorkspaceContextSync(root, patterns);
|
||||
|
||||
const isInPackageJsonWorkspaces = buildPackageJsonWorkspacesMatcher(
|
||||
root,
|
||||
(f) => readJsonFile(join(root, f))
|
||||
);
|
||||
|
||||
const rootMap: Record<string, ProjectConfiguration> = {};
|
||||
for (const projectFile of projectFiles) {
|
||||
if (basename(projectFile) === 'project.json') {
|
||||
@ -218,7 +224,8 @@ function getProjectsSync(
|
||||
packageJson,
|
||||
root,
|
||||
projectFile,
|
||||
nxJson
|
||||
nxJson,
|
||||
isInPackageJsonWorkspaces(projectFile)
|
||||
);
|
||||
if (!rootMap[config.root]) {
|
||||
mergeProjectConfigurationIntoRootMap(
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import * as path from 'node:path';
|
||||
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 { logger } from '../../utils/logger';
|
||||
import { normalizePath } from '../../utils/path';
|
||||
@ -119,6 +122,7 @@ function lookupLocalPlugin(
|
||||
}
|
||||
|
||||
let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
|
||||
let wildcardEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
|
||||
function findNxProjectForImportPath(
|
||||
importPath: string,
|
||||
projects: Record<string, ProjectConfiguration>,
|
||||
@ -146,12 +150,24 @@ function findNxProjectForImportPath(
|
||||
}
|
||||
}
|
||||
|
||||
packageEntryPointsToProjectMap ??=
|
||||
getPackageEntryPointsToProjectMap(projects);
|
||||
if (!packageEntryPointsToProjectMap && !wildcardEntryPointsToProjectMap) {
|
||||
({
|
||||
entryPointsToProjectMap: packageEntryPointsToProjectMap,
|
||||
wildcardEntryPointsToProjectMap,
|
||||
} = getWorkspacePackagesMetadata(projects));
|
||||
}
|
||||
if (packageEntryPointsToProjectMap[importPath]) {
|
||||
return packageEntryPointsToProjectMap[importPath];
|
||||
}
|
||||
|
||||
const project = matchImportToWildcardEntryPointsToProjectMap(
|
||||
wildcardEntryPointsToProjectMap,
|
||||
importPath
|
||||
);
|
||||
if (project) {
|
||||
return project;
|
||||
}
|
||||
|
||||
logger.verbose(
|
||||
'Unable to find local plugin',
|
||||
possibleTsPaths,
|
||||
|
||||
@ -154,9 +154,10 @@ export function buildTargetFromScript(
|
||||
let packageManagerCommand: PackageManagerCommands | undefined;
|
||||
|
||||
export function getMetadataFromPackageJson(
|
||||
packageJson: PackageJson
|
||||
packageJson: PackageJson,
|
||||
isInPackageManagerWorkspaces: boolean
|
||||
): ProjectMetadata {
|
||||
const { scripts, nx, description, name, exports } = packageJson;
|
||||
const { scripts, nx, description, name, exports, main } = packageJson;
|
||||
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
|
||||
return {
|
||||
targetGroups: {
|
||||
@ -166,6 +167,8 @@ export function getMetadataFromPackageJson(
|
||||
js: {
|
||||
packageName: name,
|
||||
packageExports: exports,
|
||||
packageMain: main,
|
||||
isInPackageManagerWorkspaces,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user