feat(core): use custom resolution to resolve from source local plugins with artifacts pointing to the outputs (#29222)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

Local Nx plugins in the new TS setup can't be resolved properly if they
aren't built first. Graph plugins can't be built either because the
graph is needed to run a task, but the plugin must be built to construct
the graph.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

Local Nx plugins should work in the new TS setup. A custom resolution is
added to resolve the local plugin artifacts from the source.

It will try to use a `development` condition from the `exports` entry in
`package.json` if it exists. If it doesn't, it will fall back to guess
the source based on the artifact path and some commonly known/used
source dirs: `.`, `./src`, `./src/lib`.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Leosvel Pérez Espinosa 2024-12-13 16:49:54 +01:00 committed by GitHub
parent 5bdda1daac
commit 48cd50a550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 542 additions and 45 deletions

View File

@ -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): "app" | "e2e" | "lib"
- [type](../../devkit/documents/ProjectGraphProjectNode#type): "lib" | "app" | "e2e"
## Properties
@ -28,4 +28,4 @@ Additional metadata about a project
### type
**type**: `"app"` \| `"e2e"` \| `"lib"`
**type**: `"lib"` \| `"app"` \| `"e2e"`

View File

@ -0,0 +1,193 @@
import {
checkFilesExist,
cleanupProject,
createFile,
newProject,
renameFile,
runCLI,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import {
ASYNC_GENERATOR_EXECUTOR_CONTENTS,
NX_PLUGIN_V2_CONTENTS,
} from './nx-plugin.fixtures';
describe('Nx Plugin (TS solution)', () => {
let workspaceName: string;
beforeAll(() => {
workspaceName = newProject({ preset: 'ts', packages: ['@nx/plugin'] });
});
afterAll(() => cleanupProject());
it('should be able to infer projects and targets', async () => {
const plugin = uniq('plugin');
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
// Setup project inference + target inference
updateFile(`packages/${plugin}/src/index.ts`, NX_PLUGIN_V2_CONTENTS);
// Register plugin in nx.json (required for inference)
updateJson(`nx.json`, (nxJson) => {
nxJson.plugins = [
{
plugin: `@${workspaceName}/${plugin}`,
options: { inferredTags: ['my-tag'] },
},
];
return nxJson;
});
// Create project that should be inferred by Nx
const inferredProject = uniq('inferred');
createFile(
`packages/${inferredProject}/package.json`,
JSON.stringify({
name: inferredProject,
version: '0.0.1',
})
);
createFile(`packages/${inferredProject}/my-project-file`);
// Attempt to use inferred project w/ Nx
expect(runCLI(`build ${inferredProject}`)).toContain(
'custom registered target'
);
const configuration = JSON.parse(
runCLI(`show project ${inferredProject} --json`)
);
expect(configuration.tags).toContain('my-tag');
expect(configuration.metadata.technologies).toEqual(['my-plugin']);
});
it('should be able to use local generators and executors', async () => {
const plugin = uniq('plugin');
const generator = uniq('generator');
const executor = uniq('executor');
const generatedProject = uniq('project');
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
runCLI(
`generate @nx/plugin:generator --name ${generator} --path packages/${plugin}/src/generators/${generator}/generator`
);
runCLI(
`generate @nx/plugin:executor --name ${executor} --path packages/${plugin}/src/executors/${executor}/executor`
);
updateFile(
`packages/${plugin}/src/executors/${executor}/executor.ts`,
ASYNC_GENERATOR_EXECUTOR_CONTENTS
);
runCLI(
`generate @${workspaceName}/${plugin}:${generator} --name ${generatedProject}`
);
updateJson(`libs/${generatedProject}/project.json`, (project) => {
project.targets['execute'] = {
executor: `@${workspaceName}/${plugin}:${executor}`,
};
return project;
});
expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow();
expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow();
});
it('should be able to resolve local generators and executors using package.json development condition export', async () => {
const plugin = uniq('plugin');
const generator = uniq('generator');
const executor = uniq('executor');
const generatedProject = uniq('project');
runCLI(`generate @nx/plugin:plugin packages/${plugin}`);
// move/generate everything in the "code" folder, which is not a standard location and wouldn't
// be considered by the fall back resolution logic, so the only way it could be resolved is if
// the development condition export is used
renameFile(
`packages/${plugin}/src/index.ts`,
`packages/${plugin}/code/index.ts`
);
runCLI(
`generate @nx/plugin:generator --name ${generator} --path packages/${plugin}/code/generators/${generator}/generator`
);
runCLI(
`generate @nx/plugin:executor --name ${executor} --path packages/${plugin}/code/executors/${executor}/executor`
);
updateJson(`packages/${plugin}/package.json`, (pkg) => {
pkg.nx.sourceRoot = `packages/${plugin}/code`;
pkg.nx.targets.build.options.main = `packages/${plugin}/code/index.ts`;
pkg.nx.targets.build.options.rootDir = `packages/${plugin}/code`;
pkg.nx.targets.build.options.assets.forEach(
(asset: { input: string }) => {
asset.input = `./packages/${plugin}/code`;
}
);
pkg.exports = {
'.': {
types: './dist/index.d.ts',
development: './code/index.ts',
default: './dist/index.js',
},
'./package.json': './package.json',
'./generators.json': {
development: './generators.json',
default: './generators.json',
},
'./executors.json': './executors.json',
'./dist/generators/*/schema.json': {
development: './code/generators/*/schema.json',
default: './dist/generators/*/schema.json',
},
'./dist/generators/*/generator': {
types: './dist/generators/*/generator.d.ts',
development: './code/generators/*/generator.ts',
default: './dist/generators/*/generator.js',
},
'./dist/executors/*/schema.json': {
development: './code/executors/*/schema.json',
default: './dist/executors/*/schema.json',
},
'./dist/executors/*/executor': {
types: './dist/executors/*/executor.d.ts',
development: './code/executors/*/executor.ts',
default: './dist/executors/*/executor.js',
},
};
return pkg;
});
updateJson(`packages/${plugin}/tsconfig.lib.json`, (tsconfig) => {
tsconfig.compilerOptions.rootDir = 'code';
tsconfig.include = ['code/**/*.ts'];
return tsconfig;
});
updateFile(
`packages/${plugin}/code/executors/${executor}/executor.ts`,
ASYNC_GENERATOR_EXECUTOR_CONTENTS
);
runCLI(
`generate @${workspaceName}/${plugin}:${generator} --name ${generatedProject}`
);
updateJson(`libs/${generatedProject}/project.json`, (project) => {
project.targets['execute'] = {
executor: `@${workspaceName}/${plugin}:${executor}`,
};
return project;
});
expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow();
expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow();
});
});

View File

@ -272,7 +272,7 @@
"react-router-dom": "^6.23.1",
"react-textarea-autosize": "^8.5.3",
"regenerator-runtime": "0.13.7",
"resolve.exports": "1.1.0",
"resolve.exports": "2.0.3",
"rollup": "^4.14.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-postcss": "^4.0.2",

View File

@ -46,7 +46,7 @@
"jest-resolve": "^29.4.1",
"jest-util": "^29.4.1",
"minimatch": "9.0.3",
"resolve.exports": "1.1.0",
"resolve.exports": "2.0.3",
"semver": "^7.5.3",
"tslib": "^2.3.0",
"yargs-parser": "21.1.1"

View File

@ -50,7 +50,7 @@ module.exports = function (path: string, options: ResolverOptions) {
return path;
}
return resolveExports(pkg, path) || path;
return resolveExports(pkg, path)?.[0] || path;
},
});
}

View File

@ -61,6 +61,7 @@
"npm-run-path": "^4.0.1",
"open": "^8.4.0",
"ora": "5.3.0",
"resolve.exports": "2.0.3",
"semver": "^7.5.3",
"string-width": "^4.2.3",
"tar-stream": "~2.2.0",

View File

@ -1183,7 +1183,9 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
optionSchema: builderInfo.schema,
import: resolveImplementation(
executorConfig.implementation,
dirname(executorsFilePath)
dirname(executorsFilePath),
packageName,
this.projects
),
};
}
@ -1240,25 +1242,33 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
const { executorsFilePath, executorConfig, isNgCompat } =
this.readExecutorsJson(nodeModule, executor);
const executorsDir = dirname(executorsFilePath);
const schemaPath = resolveSchema(executorConfig.schema, executorsDir);
const schemaPath = resolveSchema(
executorConfig.schema,
executorsDir,
nodeModule,
this.projects
);
const schema = normalizeExecutorSchema(readJsonFile(schemaPath));
const implementationFactory = this.getImplementationFactory<Executor>(
executorConfig.implementation,
executorsDir
executorsDir,
nodeModule
);
const batchImplementationFactory = executorConfig.batchImplementation
? this.getImplementationFactory<TaskGraphExecutor>(
executorConfig.batchImplementation,
executorsDir
executorsDir,
nodeModule
)
: null;
const hasherFactory = executorConfig.hasher
? this.getImplementationFactory<CustomHasher>(
executorConfig.hasher,
executorsDir
executorsDir,
nodeModule
)
: null;
@ -1278,9 +1288,15 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
private getImplementationFactory<T>(
implementation: string,
executorsDir: string
executorsDir: string,
packageName: string
): () => T {
return getImplementationFactory(implementation, executorsDir);
return getImplementationFactory(
implementation,
executorsDir,
packageName,
this.projects
);
}
}

View File

@ -40,7 +40,12 @@ export function getGeneratorInformation(
generatorsJson.generators?.[normalizedGeneratorName] ||
generatorsJson.schematics?.[normalizedGeneratorName];
const isNgCompat = !generatorsJson.generators?.[normalizedGeneratorName];
const schemaPath = resolveSchema(generatorConfig.schema, generatorsDir);
const schemaPath = resolveSchema(
generatorConfig.schema,
generatorsDir,
collectionName,
projects
);
const schema = readJsonFile(schemaPath);
if (!schema.properties || typeof schema.properties !== 'object') {
schema.properties = {};
@ -49,7 +54,9 @@ export function getGeneratorInformation(
generatorConfig.implementation || generatorConfig.factory;
const implementationFactory = getImplementationFactory<Generator>(
generatorConfig.implementation,
generatorsDir
generatorsDir,
collectionName,
projects
);
const normalizedGeneratorConfiguration: GeneratorsJsonEntry = {
...generatorConfig,

View File

@ -55,25 +55,36 @@ export function getExecutorInformation(
projects
);
const executorsDir = dirname(executorsFilePath);
const schemaPath = resolveSchema(executorConfig.schema, executorsDir);
const schemaPath = resolveSchema(
executorConfig.schema,
executorsDir,
nodeModule,
projects
);
const schema = normalizeExecutorSchema(readJsonFile(schemaPath));
const implementationFactory = getImplementationFactory<Executor>(
executorConfig.implementation,
executorsDir
executorsDir,
nodeModule,
projects
);
const batchImplementationFactory = executorConfig.batchImplementation
? getImplementationFactory<TaskGraphExecutor>(
executorConfig.batchImplementation,
executorsDir
executorsDir,
nodeModule,
projects
)
: null;
const hasherFactory = executorConfig.hasher
? getImplementationFactory<CustomHasher>(
executorConfig.hasher,
executorsDir
executorsDir,
nodeModule,
projects
)
: null;

View File

@ -1,6 +1,10 @@
import { existsSync } from 'fs';
import { extname, join } from 'path';
import { resolve as resolveExports } from 'resolve.exports';
import { getPackageEntryPointsToProjectMap } from '../plugins/js/utils/packages';
import { registerPluginTSTranspiler } from '../project-graph/plugins';
import { normalizePath } from '../utils/path';
import type { ProjectConfiguration } from './workspace-json-project-json';
/**
* This function is used to get the implementation factory of an executor or generator.
@ -10,14 +14,18 @@ import { registerPluginTSTranspiler } from '../project-graph/plugins';
*/
export function getImplementationFactory<T>(
implementation: string,
directory: string
directory: string,
packageName: string,
projects: Record<string, ProjectConfiguration>
): () => T {
const [implementationModulePath, implementationExportName] =
implementation.split('#');
return () => {
const modulePath = resolveImplementation(
implementationModulePath,
directory
directory,
packageName,
projects
);
if (extname(modulePath) === '.ts') {
registerPluginTSTranspiler();
@ -37,12 +45,31 @@ export function getImplementationFactory<T>(
*/
export function resolveImplementation(
implementationModulePath: string,
directory: string
directory: string,
packageName: string,
projects: Record<string, ProjectConfiguration>
): string {
const validImplementations = ['', '.js', '.ts'].map(
(x) => implementationModulePath + x
);
if (!directory.includes('node_modules')) {
// It might be a local plugin where the implementation path points to the
// outputs which might not exist or can be stale. We prioritize finding
// the implementation from the source over the outputs.
for (const maybeImplementation of validImplementations) {
const maybeImplementationFromSource = tryResolveFromSource(
maybeImplementation,
directory,
packageName,
projects
);
if (maybeImplementationFromSource) {
return maybeImplementationFromSource;
}
}
}
for (const maybeImplementation of validImplementations) {
const maybeImplementationPath = join(directory, maybeImplementation);
if (existsSync(maybeImplementationPath)) {
@ -61,7 +88,27 @@ export function resolveImplementation(
);
}
export function resolveSchema(schemaPath: string, directory: string): string {
export function resolveSchema(
schemaPath: string,
directory: string,
packageName: string,
projects: Record<string, ProjectConfiguration>
): string {
if (!directory.includes('node_modules')) {
// It might be a local plugin where the schema path points to the outputs
// which might not exist or can be stale. We prioritize finding the schema
// from the source over the outputs.
const schemaPathFromSource = tryResolveFromSource(
schemaPath,
directory,
packageName,
projects
);
if (schemaPathFromSource) {
return schemaPathFromSource;
}
}
const maybeSchemaPath = join(directory, schemaPath);
if (existsSync(maybeSchemaPath)) {
return maybeSchemaPath;
@ -71,3 +118,60 @@ export function resolveSchema(schemaPath: string, directory: string): string {
paths: [directory],
});
}
let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
function tryResolveFromSource(
path: string,
directory: string,
packageName: string,
projects: Record<string, ProjectConfiguration>
): string | null {
packageEntryPointsToProjectMap ??=
getPackageEntryPointsToProjectMap(projects);
const localProject = packageEntryPointsToProjectMap[packageName];
if (!localProject) {
// it doesn't match any of the package names from the local projects
return null;
}
try {
const fromExports = resolveExports(
{
name: localProject.metadata!.js!.packageName,
exports: localProject.metadata!.js!.packageExports,
},
path,
{ conditions: ['development'] }
);
if (fromExports && fromExports.length) {
for (const exportPath of fromExports) {
if (existsSync(join(directory, exportPath))) {
return join(directory, exportPath);
}
}
}
} catch {}
/**
* Fall back to try to "guess" the source by checking the path in some common directories:
* - the root of the project
* - the src directory
* - the src/lib directory
*/
const segments = normalizePath(path).replace(/^\.\//, '').split('/');
for (let i = 1; i < segments.length; i++) {
const possiblePaths = [
join(directory, ...segments.slice(i)),
join(directory, 'src', ...segments.slice(i)),
join(directory, 'src', 'lib', ...segments.slice(i)),
];
for (const possiblePath of possiblePaths) {
if (existsSync(possiblePath)) {
return possiblePath;
}
}
}
return null;
}

View File

@ -57,6 +57,9 @@ describe('Workspaces', () => {
{
"metadata": {
"description": "my-package description",
"js": {
"packageName": "my-package",
},
"targetGroups": {},
},
"name": "my-package",

View File

@ -1,3 +1,4 @@
import type { PackageJson } from '../utils/package-json';
import type {
NxJsonConfiguration,
NxReleaseVersionConfiguration,
@ -136,6 +137,10 @@ export interface ProjectMetadata {
}[];
};
};
js?: {
packageName: string;
packageExports: undefined | PackageJson['exports'];
};
}
export interface TargetMetadata {

View File

@ -0,0 +1,26 @@
import { join } from 'node:path/posix';
import type { ProjectConfiguration } from '../../../config/workspace-json-project-json';
export function getPackageEntryPointsToProjectMap(
projects: Record<string, ProjectConfiguration>
): Record<string, ProjectConfiguration> {
const result: Record<string, ProjectConfiguration> = {};
for (const project of Object.values(projects)) {
if (!project.metadata?.js) {
continue;
}
const { packageName, packageExports } = project.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;
}
}
}
return result;
}

View File

@ -55,6 +55,10 @@ describe('nx package.json workspaces plugin', () => {
".": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "root",
},
"targetGroups": {
"NPM Scripts": [
"echo",
@ -98,6 +102,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/lib-a": {
"metadata": {
"description": "lib-a description",
"js": {
"packageExports": undefined,
"packageName": "lib-a",
},
"targetGroups": {
"NPM Scripts": [
"test",
@ -148,6 +156,10 @@ describe('nx package.json workspaces plugin', () => {
],
"metadata": {
"description": "lib-b description",
"js": {
"packageExports": undefined,
"packageName": "lib-b",
},
"targetGroups": {
"NPM Scripts": [
"build",
@ -252,6 +264,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/vite": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "vite",
},
"targetGroups": {},
},
"name": "vite",
@ -350,6 +366,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/vite": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "vite",
},
"targetGroups": {},
},
"name": "vite",
@ -444,6 +464,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/vite": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "vite",
},
"targetGroups": {},
},
"name": "vite",
@ -522,6 +546,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/a": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "root",
},
"targetGroups": {
"NPM Scripts": [
"build",
@ -600,6 +628,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/a": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "root",
},
"targetGroups": {
"NPM Scripts": [
"build",
@ -685,6 +717,10 @@ describe('nx package.json workspaces plugin', () => {
"packages/a": {
"metadata": {
"description": undefined,
"js": {
"packageExports": undefined,
"packageName": "root",
},
"targetGroups": {},
},
"name": "root",
@ -796,4 +832,72 @@ describe('nx package.json workspaces plugin', () => {
].projectType
).toBeUndefined();
});
it('should store package name and exports in the project metadata', () => {
vol.fromJSON(
{
'packages/lib-a/package.json': JSON.stringify({
name: 'lib-a',
description: 'lib-a description',
scripts: { test: 'jest' },
exports: {
'./package.json': './package.json',
'.': './dist/index.js',
},
}),
},
'/root'
);
expect(
createNodeFromPackageJson('packages/lib-a/package.json', '/root', {})
).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",
"metadata": {
"runCommand": "npm run test",
"scriptContent": "jest",
},
"options": {
"script": "test",
},
},
},
},
},
}
`);
});
});

View File

@ -138,6 +138,12 @@ 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,
});
const cached = cache[hash];

View File

@ -31,6 +31,7 @@ import { LoadPluginError } from '../error-types';
import path = require('node:path/posix');
import { readTsConfig } from '../../plugins/js/utils/typescript';
import { loadResolvedNxPluginAsync } from './load-resolved-plugin';
import { getPackageEntryPointsToProjectMap } from '../../plugins/js/utils/packages';
export function readPluginPackageJson(
pluginName: string,
@ -124,38 +125,48 @@ function lookupLocalPlugin(
return { path: path.join(root, projectConfig.root), projectConfig };
}
let packageEntryPointsToProjectMap: Record<string, ProjectConfiguration>;
function findNxProjectForImportPath(
importPath: string,
projects: Record<string, ProjectConfiguration>,
root = workspaceRoot
): ProjectConfiguration | null {
const tsConfigPaths: Record<string, string[]> = readTsConfigPaths(root);
const possiblePaths = tsConfigPaths[importPath]?.map((p) =>
normalizePath(path.relative(root, path.join(root, p)))
);
if (possiblePaths?.length) {
const projectRootMappings: ProjectRootMappings = new Map();
const possibleTsPaths =
tsConfigPaths[importPath]?.map((p) =>
normalizePath(path.relative(root, path.join(root, p)))
) ?? [];
const projectRootMappings: ProjectRootMappings = new Map();
if (possibleTsPaths.length) {
const projectNameMap = new Map<string, ProjectConfiguration>();
for (const projectRoot in projects) {
const project = projects[projectRoot];
projectRootMappings.set(project.root, project.name);
projectNameMap.set(project.name, project);
}
for (const tsConfigPath of possiblePaths) {
for (const tsConfigPath of possibleTsPaths) {
const nxProject = findProjectForPath(tsConfigPath, projectRootMappings);
if (nxProject) {
return projectNameMap.get(nxProject);
}
}
logger.verbose(
'Unable to find local plugin',
possiblePaths,
projectRootMappings
);
throw new Error(
'Unable to resolve local plugin with import path ' + importPath
);
}
packageEntryPointsToProjectMap ??=
getPackageEntryPointsToProjectMap(projects);
if (packageEntryPointsToProjectMap[importPath]) {
return packageEntryPointsToProjectMap[importPath];
}
logger.verbose(
'Unable to find local plugin',
possibleTsPaths,
projectRootMappings
);
throw new Error(
'Unable to resolve local plugin with import path ' + importPath
);
}
let tsconfigPaths: Record<string, string[]>;

View File

@ -49,7 +49,13 @@ export interface PackageJson {
| string
| Record<
string,
string | { types?: string; require?: string; import?: string }
| string
| {
types?: string;
require?: string;
import?: string;
development?: string;
}
>;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
@ -149,13 +155,17 @@ let packageManagerCommand: PackageManagerCommands | undefined;
export function getMetadataFromPackageJson(
packageJson: PackageJson
): ProjectMetadata {
const { scripts, nx, description } = packageJson ?? {};
const { scripts, nx, description, name, exports } = packageJson;
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
return {
targetGroups: {
...(includedScripts.length ? { 'NPM Scripts': includedScripts } : {}),
},
description,
js: {
packageName: name,
packageExports: exports,
},
};
}

14
pnpm-lock.yaml generated
View File

@ -902,8 +902,8 @@ importers:
specifier: ^8.5.3
version: 8.5.3(@types/react@18.3.1)(react@18.3.1)
resolve.exports:
specifier: 1.1.0
version: 1.1.0
specifier: 2.0.3
version: 2.0.3
rollup:
specifier: ^4.14.0
version: 4.22.0
@ -15058,8 +15058,8 @@ packages:
resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==}
engines: {node: '>=10'}
resolve.exports@2.0.2:
resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==}
resolve.exports@2.0.3:
resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
engines: {node: '>=10'}
resolve@1.22.8:
@ -28326,7 +28326,7 @@ snapshots:
'@jspm/core': 2.0.1
esbuild: 0.17.6
local-pkg: 0.5.0
resolve.exports: 2.0.2
resolve.exports: 2.0.3
esbuild-register@3.6.0(esbuild@0.19.5):
dependencies:
@ -30895,7 +30895,7 @@ snapshots:
jest-util: 29.7.0
jest-validate: 29.7.0
resolve: 1.22.8
resolve.exports: 2.0.2
resolve.exports: 2.0.3
slash: 3.0.0
jest-runner@29.7.0:
@ -35074,7 +35074,7 @@ snapshots:
resolve.exports@1.1.0: {}
resolve.exports@2.0.2: {}
resolve.exports@2.0.3: {}
resolve@1.22.8:
dependencies: