diff --git a/docs/generated/devkit/CreateDependencies.md b/docs/generated/devkit/CreateDependencies.md index 3c9938169f..6027067f41 100644 --- a/docs/generated/devkit/CreateDependencies.md +++ b/docs/generated/devkit/CreateDependencies.md @@ -1,10 +1,16 @@ -# Type alias: CreateDependencies +# Type alias: CreateDependencies -Ƭ **CreateDependencies**: (`context`: [`CreateDependenciesContext`](../../devkit/documents/CreateDependenciesContext)) => [`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[] \| `Promise`<[`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[]\> +Ƭ **CreateDependencies**<`T`\>: (`options`: `T` \| `undefined`, `context`: [`CreateDependenciesContext`](../../devkit/documents/CreateDependenciesContext)) => [`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[] \| `Promise`<[`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[]\> + +#### Type parameters + +| Name | Type | +| :--- | :---------------------------------------------------------------------- | +| `T` | extends `Record`<`string`, `unknown`\> = `Record`<`string`, `unknown`\> | #### Type declaration -▸ (`context`): [`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[] \| `Promise`<[`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[]\> +▸ (`options`, `context`): [`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[] \| `Promise`<[`RawProjectGraphDependency`](../../devkit/documents/RawProjectGraphDependency)[]\> A function which parses files in the workspace to create dependencies in the [ProjectGraph](../../devkit/documents/ProjectGraph) Use [validateDependency](../../devkit/documents/validateDependency) to validate dependencies @@ -13,6 +19,7 @@ Use [validateDependency](../../devkit/documents/validateDependency) to validate | Name | Type | | :-------- | :------------------------------------------------------------------------------ | +| `options` | `T` \| `undefined` | | `context` | [`CreateDependenciesContext`](../../devkit/documents/CreateDependenciesContext) | ##### Returns diff --git a/docs/generated/devkit/CreateNodes.md b/docs/generated/devkit/CreateNodes.md index 0264eb5535..fc3af3fd4b 100644 --- a/docs/generated/devkit/CreateNodes.md +++ b/docs/generated/devkit/CreateNodes.md @@ -1,5 +1,11 @@ -# Type alias: CreateNodes +# Type alias: CreateNodes -Ƭ **CreateNodes**: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunction] +Ƭ **CreateNodes**<`T`\>: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunction] A pair of file patterns and [CreateNodesFunction](../../devkit/documents/CreateNodesFunction) + +#### Type parameters + +| Name | Type | +| :--- | :---------------------------------------------------------------------- | +| `T` | extends `Record`<`string`, `unknown`\> = `Record`<`string`, `unknown`\> | diff --git a/docs/generated/devkit/CreateNodesFunction.md b/docs/generated/devkit/CreateNodesFunction.md index 6aac1650e8..4cc65910da 100644 --- a/docs/generated/devkit/CreateNodesFunction.md +++ b/docs/generated/devkit/CreateNodesFunction.md @@ -1,10 +1,16 @@ -# Type alias: CreateNodesFunction +# Type alias: CreateNodesFunction -Ƭ **CreateNodesFunction**: (`projectConfigurationFile`: `string`, `context`: [`CreateNodesContext`](../../devkit/documents/CreateNodesContext)) => { `externalNodes?`: `Record`<`string`, [`ProjectGraphExternalNode`](../../devkit/documents/ProjectGraphExternalNode)\> ; `projects?`: `Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\> } +Ƭ **CreateNodesFunction**<`T`\>: (`projectConfigurationFile`: `string`, `options`: `T` \| `undefined`, `context`: [`CreateNodesContext`](../../devkit/documents/CreateNodesContext)) => { `externalNodes?`: `Record`<`string`, [`ProjectGraphExternalNode`](../../devkit/documents/ProjectGraphExternalNode)\> ; `projects?`: `Record`<`string`, [`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration)\> } + +#### Type parameters + +| Name | Type | +| :--- | :---------------------------------------------------------------------- | +| `T` | extends `Record`<`string`, `unknown`\> = `Record`<`string`, `unknown`\> | #### Type declaration -▸ (`projectConfigurationFile`, `context`): `Object` +▸ (`projectConfigurationFile`, `options`, `context`): `Object` A function which parses a configuration file into a set of nodes. Used for creating nodes for the [ProjectGraph](../../devkit/documents/ProjectGraph) @@ -14,6 +20,7 @@ Used for creating nodes for the [ProjectGraph](../../devkit/documents/ProjectGra | Name | Type | | :------------------------- | :---------------------------------------------------------------- | | `projectConfigurationFile` | `string` | +| `options` | `T` \| `undefined` | | `context` | [`CreateNodesContext`](../../devkit/documents/CreateNodesContext) | ##### Returns diff --git a/docs/generated/devkit/NxJsonConfiguration.md b/docs/generated/devkit/NxJsonConfiguration.md index 60248bc792..630091b22a 100644 --- a/docs/generated/devkit/NxJsonConfiguration.md +++ b/docs/generated/devkit/NxJsonConfiguration.md @@ -32,8 +32,8 @@ Nx.json configuration - [nxCloudEncryptionKey](../../devkit/documents/NxJsonConfiguration#nxcloudencryptionkey): string - [nxCloudUrl](../../devkit/documents/NxJsonConfiguration#nxcloudurl): string - [parallel](../../devkit/documents/NxJsonConfiguration#parallel): number -- [plugins](../../devkit/documents/NxJsonConfiguration#plugins): string[] -- [pluginsConfig](../../devkit/documents/NxJsonConfiguration#pluginsconfig): Record<string, unknown> +- [plugins](../../devkit/documents/NxJsonConfiguration#plugins): PluginDefinition[] +- [pluginsConfig](../../devkit/documents/NxJsonConfiguration#pluginsconfig): Record<string, Record<string, unknown>> - [release](../../devkit/documents/NxJsonConfiguration#release): NxReleaseConfiguration - [targetDefaults](../../devkit/documents/NxJsonConfiguration#targetdefaults): TargetDefaults - [tasksRunnerOptions](../../devkit/documents/NxJsonConfiguration#tasksrunneroptions): Object @@ -198,7 +198,7 @@ Specifies how many tasks can be run in parallel. ### plugins -• `Optional` **plugins**: `string`[] +• `Optional` **plugins**: `PluginDefinition`[] Plugins for extending the project graph @@ -206,7 +206,7 @@ Plugins for extending the project graph ### pluginsConfig -• `Optional` **pluginsConfig**: `Record`<`string`, `unknown`\> +• `Optional` **pluginsConfig**: `Record`<`string`, `Record`<`string`, `unknown`\>\> Configuration for Nx Plugins diff --git a/docs/generated/devkit/NxPluginV2.md b/docs/generated/devkit/NxPluginV2.md index 0ba5699853..a4b774f0a8 100644 --- a/docs/generated/devkit/NxPluginV2.md +++ b/docs/generated/devkit/NxPluginV2.md @@ -1,13 +1,19 @@ -# Type alias: NxPluginV2 +# Type alias: NxPluginV2 -Ƭ **NxPluginV2**: `Object` +Ƭ **NxPluginV2**<`T`\>: `Object` A plugin for Nx which creates nodes and dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) +#### Type parameters + +| Name | Type | +| :--- | :---------------------------------------------------------------------- | +| `T` | extends `Record`<`string`, `unknown`\> = `Record`<`string`, `unknown`\> | + #### Type declaration -| Name | Type | Description | -| :-------------------- | :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | -| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies) | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) | -| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes) | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } | -| `name` | `string` | - | +| Name | Type | Description | +| :-------------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)<`T`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) | +| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes)<`T`\> | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } | +| `name` | `string` | - | diff --git a/docs/generated/devkit/Workspace.md b/docs/generated/devkit/Workspace.md index f6d94f801b..b10eb82517 100644 --- a/docs/generated/devkit/Workspace.md +++ b/docs/generated/devkit/Workspace.md @@ -30,8 +30,8 @@ use ProjectsConfigurations or NxJsonConfiguration - [nxCloudEncryptionKey](../../devkit/documents/Workspace#nxcloudencryptionkey): string - [nxCloudUrl](../../devkit/documents/Workspace#nxcloudurl): string - [parallel](../../devkit/documents/Workspace#parallel): number -- [plugins](../../devkit/documents/Workspace#plugins): string[] -- [pluginsConfig](../../devkit/documents/Workspace#pluginsconfig): Record<string, unknown> +- [plugins](../../devkit/documents/Workspace#plugins): PluginDefinition[] +- [pluginsConfig](../../devkit/documents/Workspace#pluginsconfig): Record<string, Record<string, unknown>> - [projects](../../devkit/documents/Workspace#projects): Record<string, ProjectConfiguration> - [release](../../devkit/documents/Workspace#release): NxReleaseConfiguration - [targetDefaults](../../devkit/documents/Workspace#targetdefaults): TargetDefaults @@ -254,7 +254,7 @@ Specifies how many tasks can be run in parallel. ### plugins -• `Optional` **plugins**: `string`[] +• `Optional` **plugins**: `PluginDefinition`[] Plugins for extending the project graph @@ -266,7 +266,7 @@ Plugins for extending the project graph ### pluginsConfig -• `Optional` **pluginsConfig**: `Record`<`string`, `unknown`\> +• `Optional` **pluginsConfig**: `Record`<`string`, `Record`<`string`, `unknown`\>\> Configuration for Nx Plugins diff --git a/e2e/plugin/src/nx-plugin.fixtures.ts b/e2e/plugin/src/nx-plugin.fixtures.ts index d4d67cad6e..0bf42cc270 100644 --- a/e2e/plugin/src/nx-plugin.fixtures.ts +++ b/e2e/plugin/src/nx-plugin.fixtures.ts @@ -18,3 +18,39 @@ export default async function* execute( yield* asyncGenerator(); } `; + +export const NX_PLUGIN_V2_CONTENTS = `import { basename, dirname } from "path"; +import { CreateNodes } from "@nx/devkit"; + +type PluginOptions = { + inferredTags: string[] +} + +export const createNodes: CreateNodes = [ + "**/my-project-file", + (f, options, ctx) => { + // f = path/to/my/file/my-project-file + const root = dirname(f); + // root = path/to/my/file + const name = basename(root); + // name = file + + return { + projects: { + [name]: { + root, + targets: { + build: { + executor: "nx:run-commands", + options: { + command: "echo 'custom registered target'", + }, + }, + }, + tags: options.inferredTags + }, + }, + }; + }, +]; +`; diff --git a/e2e/plugin/src/nx-plugin.test.ts b/e2e/plugin/src/nx-plugin.test.ts index 058e18e058..8f00ccd053 100644 --- a/e2e/plugin/src/nx-plugin.test.ts +++ b/e2e/plugin/src/nx-plugin.test.ts @@ -16,7 +16,10 @@ import { } from '@nx/e2e/utils'; import type { PackageJson } from 'nx/src/utils/package-json'; -import { ASYNC_GENERATOR_EXECUTOR_CONTENTS } from './nx-plugin.fixtures'; +import { + ASYNC_GENERATOR_EXECUTOR_CONTENTS, + NX_PLUGIN_V2_CONTENTS, +} from './nx-plugin.fixtures'; import { join } from 'path'; describe('Nx Plugin', () => { @@ -263,7 +266,7 @@ describe('Nx Plugin', () => { runCLI(`generate @nx/plugin:plugin ${plugin} --linter=eslint`); }); - it('should be able to infer projects and targets', async () => { + it('should be able to infer projects and targets (v1)', async () => { // Setup project inference + target inference updateFile( `libs/${plugin}/src/index.ts`, @@ -303,6 +306,36 @@ describe('Nx Plugin', () => { ); }); + it('should be able to infer projects and targets (v2)', async () => { + // Setup project inference + target inference + updateFile(`libs/${plugin}/src/index.ts`, NX_PLUGIN_V2_CONTENTS); + + // Register plugin in nx.json (required for inference) + updateFile(`nx.json`, (nxJson) => { + const nx = JSON.parse(nxJson); + nx.plugins = [ + { + plugin: `@${npmScope}/${plugin}`, + options: { inferredTags: ['my-tag'] }, + }, + ]; + return JSON.stringify(nx, null, 2); + }); + + // Create project that should be inferred by Nx + const inferredProject = uniq('inferred'); + createFile(`libs/${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).toEqual(['my-tag']); + }); + it('should be able to use local generators and executors', async () => { const generator = uniq('generator'); const executor = uniq('executor'); diff --git a/packages/nx/schemas/nx-schema.json b/packages/nx/schemas/nx-schema.json index eadfbc5747..14b80116fb 100644 --- a/packages/nx/schemas/nx-schema.json +++ b/packages/nx/schemas/nx-schema.json @@ -70,7 +70,7 @@ "type": "array", "description": "Plugins for extending the project graph.", "items": { - "type": "string" + "$ref": "#/definitions/plugins" } }, "defaultProject": { @@ -377,6 +377,27 @@ } }, "additionalProperties": false + }, + "plugins": { + "oneOf": [ + { + "type": "string", + "description": "A plugin module to load with default options" + }, + { + "type": "object", + "properties": { + "plugin": { + "type": "string", + "description": "The plugin module to load" + }, + "options": { + "type": "object", + "description": "The options passed to the plugin when creating nodes and dependencies" + } + } + } + ] } } } diff --git a/packages/nx/src/adapter/angular-json.ts b/packages/nx/src/adapter/angular-json.ts index 59a95003ec..64a158441a 100644 --- a/packages/nx/src/adapter/angular-json.ts +++ b/packages/nx/src/adapter/angular-json.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import * as path from 'path'; import { readJsonFile } from '../utils/fileutils'; import { ProjectsConfigurations } from '../config/workspace-json-project-json'; -import { NxPluginV2 } from '../devkit-exports'; +import { NxPluginV2 } from '../utils/nx-plugin'; export const NX_ANGULAR_JSON_PLUGIN_NAME = 'nx-angular-json-plugin'; @@ -10,7 +10,7 @@ export const NxAngularJsonPlugin: NxPluginV2 = { name: NX_ANGULAR_JSON_PLUGIN_NAME, createNodes: [ 'angular.json', - (f, ctx) => ({ + (f, _, ctx) => ({ projects: readAngularJson(ctx.workspaceRoot), }), ], diff --git a/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts b/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts index aa647d07cb..041453c4c0 100644 --- a/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts +++ b/packages/nx/src/command-line/init/implementation/add-nx-to-nest.ts @@ -367,7 +367,7 @@ function addNrwlJsPluginsConfig(repoRoot: string) { json.pluginsConfig = { '@nx/js': { analyzeSourceFiles: true, - } as NrwlJsPluginConfig, + }, }; } diff --git a/packages/nx/src/config/nx-json.ts b/packages/nx/src/config/nx-json.ts index 0a76c7d078..c1c1504afb 100644 --- a/packages/nx/src/config/nx-json.ts +++ b/packages/nx/src/config/nx-json.ts @@ -176,12 +176,12 @@ export interface NxJsonConfiguration { /** * Plugins for extending the project graph */ - plugins?: string[]; + plugins?: PluginDefinition[]; /** * Configuration for Nx Plugins */ - pluginsConfig?: Record; + pluginsConfig?: Record>; /** * Default project. When project isn't provided, the default project @@ -234,6 +234,10 @@ export interface NxJsonConfiguration { useDaemonProcess?: boolean; } +export type PluginDefinition = + | string + | { plugin: string; options?: Record }; + export function readNxJson(root: string = workspaceRoot): NxJsonConfiguration { const nxJson = join(root, 'nx.json'); if (existsSync(nxJson)) { diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 07817c9e34..985d02b940 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -213,7 +213,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): { if (basename(projectFile) === 'project.json') { const json = readJson(tree, projectFile); const config = buildProjectFromProjectJson(json, projectFile); - mergeProjectConfigurationIntoRootMap(rootMap, config, projectFile); + mergeProjectConfigurationIntoRootMap(rootMap, config); } else { const packageJson = readJson(tree, projectFile); const config = buildProjectConfigurationFromPackageJson( @@ -228,8 +228,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): { { name: config.name, root: config.root, - }, - projectFile + } ); } } diff --git a/packages/nx/src/plugins/js/index.ts b/packages/nx/src/plugins/js/index.ts index 05c1848856..d9d26a47be 100644 --- a/packages/nx/src/plugins/js/index.ts +++ b/packages/nx/src/plugins/js/index.ts @@ -36,7 +36,7 @@ let parsedLockFile: ParsedLockFile = {}; export const createNodes: CreateNodes = [ // Look for all lockfiles combineGlobPatterns(LOCKFILES), - (lockFile, context) => { + (lockFile, _, context) => { const pluginConfig = jsPluginConfig(context.nxJsonConfiguration); if (!pluginConfig.analyzeLockfile) { return {}; @@ -74,6 +74,7 @@ export const createNodes: CreateNodes = [ ]; export const createDependencies: CreateDependencies = ( + _, ctx: CreateDependenciesContext ) => { const pluginConfig = jsPluginConfig(ctx.nxJsonConfiguration); diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts index 51b8922858..6ba798ca13 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.spec.ts @@ -55,7 +55,8 @@ describe('nx project.json plugin', () => { '/root' ); - expect(createNodes[1]('project.json', context)).toMatchInlineSnapshot(` + expect(createNodes[1]('project.json', undefined, context)) + .toMatchInlineSnapshot(` { "projects": { "root": { @@ -68,7 +69,7 @@ describe('nx project.json plugin', () => { }, } `); - expect(createNodes[1]('packages/lib-a/project.json', context)) + expect(createNodes[1]('packages/lib-a/project.json', undefined, context)) .toMatchInlineSnapshot(` { "projects": { diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts index 3afb86d2e7..3d3596e3d0 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts @@ -17,7 +17,7 @@ export const CreateProjectJsonProjectsPlugin: NxPluginV2 = { name: 'nx-core-build-project-json-nodes', createNodes: [ '{project.json,**/project.json}', - (file, context) => { + (file, _, context) => { const root = context.workspaceRoot; const json = readJsonFile(join(root, file)); const project = buildProjectFromProjectJson(json, file); diff --git a/packages/nx/src/project-graph/build-project-graph.ts b/packages/nx/src/project-graph/build-project-graph.ts index 059ab723ab..31b36cab6b 100644 --- a/packages/nx/src/project-graph/build-project-graph.ts +++ b/packages/nx/src/project-graph/build-project-graph.ts @@ -15,7 +15,6 @@ import { applyImplicitDependencies } from './utils/implicit-project-dependencies import { normalizeProjectNodes } from './utils/normalize-project-nodes'; import { CreateDependenciesContext, - CreateNodesContext, isNxPluginV1, isNxPluginV2, loadNxPlugins, @@ -234,7 +233,7 @@ async function updateProjectGraphWithPlugins( ) { const plugins = await loadNxPlugins(context.nxJsonConfiguration?.plugins); let graph = initProjectGraph; - for (const plugin of plugins) { + for (const { plugin } of plugins) { try { if ( isNxPluginV1(plugin) && @@ -280,12 +279,12 @@ async function updateProjectGraphWithPlugins( ); const createDependencyPlugins = plugins.filter( - (plugin) => isNxPluginV2(plugin) && plugin.createDependencies + ({ plugin }) => isNxPluginV2(plugin) && plugin.createDependencies ); await Promise.all( - createDependencyPlugins.map(async (plugin) => { + createDependencyPlugins.map(async ({ plugin, options }) => { try { - const dependencies = await plugin.createDependencies({ + const dependencies = await plugin.createDependencies(options, { ...context, }); diff --git a/packages/nx/src/project-graph/nx-deps-cache.spec.ts b/packages/nx/src/project-graph/nx-deps-cache.spec.ts index 8bfd3246c7..e0960c3cdb 100644 --- a/packages/nx/src/project-graph/nx-deps-cache.spec.ts +++ b/packages/nx/src/project-graph/nx-deps-cache.spec.ts @@ -111,7 +111,7 @@ describe('nx deps utils', () => { createCache({}), createPackageJsonDeps({ plugin: '2.0.0' }), createProjectsConfiguration({}), - createNxJson({ pluginsConfig: { one: 1 } }), + createNxJson({ pluginsConfig: { somePlugin: { one: 1 } } }), createTsConfigJson() ) ).toEqual(true); diff --git a/packages/nx/src/project-graph/nx-deps-cache.ts b/packages/nx/src/project-graph/nx-deps-cache.ts index bf5d2ac7a0..93a8aa54ac 100644 --- a/packages/nx/src/project-graph/nx-deps-cache.ts +++ b/packages/nx/src/project-graph/nx-deps-cache.ts @@ -2,7 +2,7 @@ import { existsSync } from 'fs'; import { ensureDirSync, renameSync } from 'fs-extra'; import { join } from 'path'; import { performance } from 'perf_hooks'; -import { NxJsonConfiguration } from '../config/nx-json'; +import { NxJsonConfiguration, PluginDefinition } from '../config/nx-json'; import { FileData, FileMap, @@ -17,6 +17,7 @@ import { readJsonFile, writeJsonFile, } from '../utils/fileutils'; +import { PackageJson } from '../utils/package-json'; import { nxVersion } from '../utils/versions'; export interface FileMapCache { @@ -24,7 +25,7 @@ export interface FileMapCache { nxVersion: string; deps: Record; pathMappings: Record; - nxJsonPlugins: { name: string; version: string }[]; + nxJsonPlugins: PluginData[]; pluginsConfig?: any; fileMap: FileMap; } @@ -111,10 +112,7 @@ export function createProjectFileMapCache( fileMap: FileMap, tsConfig: { compilerOptions?: { paths?: { [p: string]: any } } } ) { - const nxJsonPlugins = (nxJson?.plugins || []).map((p) => ({ - name: p, - version: packageJsonDeps[p], - })); + const nxJsonPlugins = getNxJsonPluginsData(nxJson, packageJsonDeps); const newValue: FileMapCache = { version: '6.0', nxVersion: nxVersion, @@ -207,16 +205,9 @@ export function shouldRecomputeWholeGraph( } // a new plugin has been added - if ((nxJson?.plugins || []).length !== cache.nxJsonPlugins.length) - return true; - - // a plugin has changed if ( - (nxJson?.plugins || []).some((t) => { - const matchingPlugin = cache.nxJsonPlugins.find((p) => p.name === t); - if (!matchingPlugin) return true; - return matchingPlugin.version !== packageJsonDeps[t]; - }) + JSON.stringify(getNxJsonPluginsData(nxJson, packageJsonDeps)) !== + JSON.stringify(cache.nxJsonPlugins) ) { return true; } @@ -333,3 +324,24 @@ function processProjectNode( } } } + +type PluginData = { + name: string; + version: string; + options?: Record; +}; + +function getNxJsonPluginsData( + nxJson: NxJsonConfiguration, + packageJsonDeps: Record +): PluginData[] { + return (nxJson?.plugins || []).map((p) => { + const [plugin, options] = + typeof p === 'string' ? [p] : [p.plugin, p.options]; + return { + name: plugin, + version: packageJsonDeps[plugin], + options, + }; + }); +} diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts index 6893da7120..cebad9c86e 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.spec.ts @@ -371,19 +371,15 @@ describe('mergeProjectConfigurationIntoRootMap', () => { }, }) .getRootMap(); - mergeProjectConfigurationIntoRootMap( - rootMap, - { - root: 'libs/lib-a', - name: 'lib-a', - targets: { - build: { - command: 'tsc', - }, + mergeProjectConfigurationIntoRootMap(rootMap, { + root: 'libs/lib-a', + name: 'lib-a', + targets: { + build: { + command: 'tsc', }, }, - 'inferred-project-config-file.ts' - ); + }); expect(rootMap.get('libs/lib-a')).toMatchInlineSnapshot(` { "name": "lib-a", @@ -399,33 +395,6 @@ describe('mergeProjectConfigurationIntoRootMap', () => { } `); }); - - it("shouldn't overwrite project name, unless merging project from project.json", () => { - const rootMap = new RootMapBuilder() - .addProject({ - name: 'bad-name', - root: 'libs/lib-a', - }) - .getRootMap(); - mergeProjectConfigurationIntoRootMap( - rootMap, - { - name: 'other-bad-name', - root: 'libs/lib-a', - }, - 'libs/lib-a/package.json' - ); - expect(rootMap.get('libs/lib-a').name).toEqual('bad-name'); - mergeProjectConfigurationIntoRootMap( - rootMap, - { - name: 'lib-a', - root: 'libs/lib-a', - }, - 'libs/lib-a/project.json' - ); - expect(rootMap.get('libs/lib-a').name).toEqual('lib-a'); - }); }); describe('readProjectsConfigurationsFromRootMap', () => { diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index 5c1a6996ac..8d92c689ce 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -1,5 +1,3 @@ -import { basename } from 'node:path'; - import { NxJsonConfiguration, TargetDefaults } from '../../config/nx-json'; import { ProjectGraphExternalNode } from '../../config/project-graph'; import { @@ -7,30 +5,20 @@ import { TargetConfiguration, } from '../../config/workspace-json-project-json'; import { NX_PREFIX } from '../../utils/logger'; -import { NxPluginV2 } from '../../utils/nx-plugin'; +import { LoadedNxPlugin } from '../../utils/nx-plugin'; import { workspaceRoot } from '../../utils/workspace-root'; import minimatch = require('minimatch'); export function mergeProjectConfigurationIntoRootMap( projectRootMap: Map, - project: ProjectConfiguration, - // project.json is a special case, so we need to detect it. - file: string + project: ProjectConfiguration ): void { const matchingProject = projectRootMap.get(project.root); if (!matchingProject) { projectRootMap.set(project.root, project); return; - } else if ( - project.name && - project.name !== matchingProject.name && - basename(file) === 'project.json' - ) { - // `name` inside project.json overrides any names from - // inference plugins - matchingProject.name = project.name; } // This handles top level properties that are overwritten. @@ -41,7 +29,6 @@ export function mergeProjectConfigurationIntoRootMap( const updatedProjectConfiguration = { ...matchingProject, ...project, - name: matchingProject.name, }; // The next blocks handle properties that should be themselves merged (e.g. targets, tags, and implicit dependencies) @@ -79,7 +66,7 @@ export function mergeProjectConfigurationIntoRootMap( export function buildProjectsConfigurationsFromProjectPathsAndPlugins( nxJson: NxJsonConfiguration, projectFiles: string[], // making this parameter allows devkit to pick up newly created projects - plugins: NxPluginV2[], + plugins: LoadedNxPlugin[], root: string = workspaceRoot ): { projects: Record; @@ -89,7 +76,7 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins( const externalNodes: Record = {}; // We iterate over plugins first - this ensures that plugins specified first take precedence. - for (const plugin of plugins) { + for (const { plugin, options } of plugins) { const [pattern, createNodes] = plugin.createNodes ?? []; if (!pattern) { continue; @@ -97,7 +84,7 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins( for (const file of projectFiles) { if (minimatch(file, pattern, { dot: true })) { const { projects: projectNodes, externalNodes: pluginExternalNodes } = - createNodes(file, { + createNodes(file, options, { nxJsonConfiguration: nxJson, workspaceRoot: root, }); @@ -105,8 +92,7 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins( projectNodes[node].name ??= node; mergeProjectConfigurationIntoRootMap( projectRootMap, - projectNodes[node], - file + projectNodes[node] ); } Object.assign(externalNodes, pluginExternalNodes); diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index 7461fb7018..37ac9fd744 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -22,6 +22,7 @@ import { } from '../../../plugins/package-json-workspaces'; import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project-configuration-utils'; import { + LoadedNxPlugin, loadNxPlugins, loadNxPluginsSync, NxPluginV2, @@ -137,9 +138,9 @@ export async function retrieveProjectConfigurationsWithAngularProjects( if ( shouldMergeAngularProjects(workspaceRoot, true) && - !plugins.some((p) => p.name === NX_ANGULAR_JSON_PLUGIN_NAME) + !plugins.some((p) => p.plugin.name === NX_ANGULAR_JSON_PLUGIN_NAME) ) { - plugins.push(NxAngularJsonPlugin); + plugins.push({ plugin: NxAngularJsonPlugin }); } const globs = configurationGlobs(workspaceRoot, plugins); @@ -169,7 +170,7 @@ export function retrieveProjectConfigurationsSync( function _retrieveProjectConfigurations( workspaceRoot: string, nxJson: NxJsonConfiguration, - plugins: NxPluginV2[], + plugins: LoadedNxPlugin[], globs: string[] ): { externalNodes: Record; @@ -236,8 +237,8 @@ export function retrieveProjectConfigurationsWithoutPluginInference( projectGlobPatterns, (configs: string[]) => { const { projects } = createProjectConfigurations(root, nxJson, configs, [ - getNxPackageJsonWorkspacesPlugin(root), - CreateProjectJsonProjectsPlugin, + { plugin: getNxPackageJsonWorkspacesPlugin(root) }, + { plugin: CreateProjectJsonProjectsPlugin }, ]); return { projectNodes: projects, @@ -273,7 +274,7 @@ export function createProjectConfigurations( workspaceRoot: string, nxJson: NxJsonConfiguration, configFiles: string[], - plugins: NxPluginV2[] + plugins: LoadedNxPlugin[] ): { projects: Record; externalNodes: Record; @@ -305,11 +306,11 @@ export function createProjectConfigurations( export function configurationGlobs( workspaceRoot: string, - plugins: NxPluginV2[] + plugins: LoadedNxPlugin[] ): string[] { const globPatterns: string[] = configurationGlobsWithoutPlugins(workspaceRoot); - for (const plugin of plugins) { + for (const { plugin } of plugins) { if (plugin.createNodes) { globPatterns.push(plugin.createNodes[0]); } diff --git a/packages/nx/src/utils/nx-plugin.ts b/packages/nx/src/utils/nx-plugin.ts index 211d381e6c..ebc49216b7 100644 --- a/packages/nx/src/utils/nx-plugin.ts +++ b/packages/nx/src/utils/nx-plugin.ts @@ -1,9 +1,7 @@ import { existsSync } from 'fs'; import * as path from 'path'; import { - FileData, FileMap, - ProjectFileMap, ProjectGraph, ProjectGraphExternalNode, } from '../config/project-graph'; @@ -19,10 +17,7 @@ import { registerTranspiler, registerTsConfigPaths, } from '../plugins/js/utils/register'; -import { - ProjectConfiguration, - ProjectsConfigurations, -} from '../config/workspace-json-project-json'; +import { ProjectConfiguration } from '../config/workspace-json-project-json'; import { logger } from './logger'; import { createProjectRootMappingsFromProjectConfigurations, @@ -32,7 +27,7 @@ import { normalizePath } from './path'; import { dirname, join } from 'path'; import { getNxRequirePaths } from './installation-directory'; import { readTsConfig } from '../plugins/js/utils/typescript'; -import { NxJsonConfiguration } from '../config/nx-json'; +import { NxJsonConfiguration, PluginDefinition } from '../config/nx-json'; import type * as ts from 'typescript'; import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files'; @@ -45,6 +40,7 @@ import { } from '../adapter/angular-json'; import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +import { FileMapCache } from '../project-graph/nx-deps-cache'; /** * Context for {@link CreateNodesFunction} @@ -58,8 +54,11 @@ export interface CreateNodesContext { * A function which parses a configuration file into a set of nodes. * Used for creating nodes for the {@link ProjectGraph} */ -export type CreateNodesFunction = ( +export type CreateNodesFunction< + T extends Record = Record +> = ( projectConfigurationFile: string, + options: T | undefined, context: CreateNodesContext ) => { projects?: Record; @@ -69,9 +68,11 @@ export type CreateNodesFunction = ( /** * A pair of file patterns and {@link CreateNodesFunction} */ -export type CreateNodes = readonly [ +export type CreateNodes< + T extends Record = Record +> = readonly [ projectFilePattern: string, - createNodesFunction: CreateNodesFunction + createNodesFunction: CreateNodesFunction ]; /** @@ -110,27 +111,32 @@ export interface CreateDependenciesContext { * A function which parses files in the workspace to create dependencies in the {@link ProjectGraph} * Use {@link validateDependency} to validate dependencies */ -export type CreateDependencies = ( +export type CreateDependencies< + T extends Record = Record +> = ( + options: T | undefined, context: CreateDependenciesContext ) => RawProjectGraphDependency[] | Promise; /** * A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph} */ -export type NxPluginV2 = { +export type NxPluginV2< + T extends Record = Record +> = { name: string; /** * Provides a file pattern and function that retrieves configuration info from * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile } */ - createNodes?: CreateNodes; + createNodes?: CreateNodes; // Todo(@AgentEnder): This shouldn't be a full processor, since its only responsible for defining edges between projects. What do we want the API to be? /** * Provides a function to analyze files to create dependencies for the {@link ProjectGraph} */ - createDependencies?: CreateDependencies; + createDependencies?: CreateDependencies; }; export * from './nx-plugin.deprecated'; @@ -140,11 +146,16 @@ export * from './nx-plugin.deprecated'; */ export type NxPlugin = NxPluginV1 | NxPluginV2; +export type LoadedNxPlugin = { + plugin: NxPluginV2 & Pick; + options?: Record; +}; + // Short lived cache (cleared between cmd runs) // holding resolved nx plugin objects. // Allows loadNxPlugins to be called multiple times w/o // executing resolution mulitple times. -let nxPluginCache: Map = new Map(); +let nxPluginCache: Map = new Map(); function getPluginPathAndName( moduleName: string, @@ -191,50 +202,66 @@ function getPluginPathAndName( } export async function loadNxPluginAsync( - moduleName: string, + pluginDefinition: PluginDefinition, paths: string[], root: string -) { +): Promise { + const { plugin: moduleName, options } = + typeof pluginDefinition === 'object' + ? pluginDefinition + : { plugin: pluginDefinition, options: undefined }; let pluginModule = nxPluginCache.get(moduleName); if (pluginModule) { - return pluginModule; + return { plugin: pluginModule, options }; } let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root); - const plugin = (await import(pluginPath)) as NxPlugin; + const plugin = ensurePluginIsV2( + (await import(pluginPath)) as LoadedNxPlugin['plugin'] + ); plugin.name ??= name; nxPluginCache.set(moduleName, plugin); - return plugin; + return { plugin, options }; } -function loadNxPluginSync(moduleName: string, paths: string[], root: string) { +function loadNxPluginSync( + pluginDefinition: PluginDefinition, + paths: string[], + root: string +): LoadedNxPlugin { + const { plugin: moduleName, options } = + typeof pluginDefinition === 'object' + ? pluginDefinition + : { plugin: pluginDefinition, options: undefined }; let pluginModule = nxPluginCache.get(moduleName); if (pluginModule) { - return pluginModule; + return { plugin: pluginModule, options }; } let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root); - const plugin = require(pluginPath) as NxPlugin; + const plugin = ensurePluginIsV2( + require(pluginPath) + ) as LoadedNxPlugin['plugin']; plugin.name ??= name; nxPluginCache.set(moduleName, plugin); - return plugin; + return { plugin, options }; } /** * @deprecated Use loadNxPlugins instead. */ export function loadNxPluginsSync( - plugins: string[], + plugins: NxJsonConfiguration['plugins'], paths = getNxRequirePaths(), root = workspaceRoot -): (NxPluginV2 & Pick)[] { +): LoadedNxPlugin[] { // TODO: This should be specified in nx.json // Temporarily load js as if it were a plugin which is built into nx // In the future, this will be optional and need to be specified in nx.json - const result: NxPlugin[] = [...getDefaultPluginsSync(root)]; + const result: LoadedNxPlugin[] = [...getDefaultPluginsSync(root)]; if (shouldMergeAngularProjects(root, false)) { - result.push(NxAngularJsonPlugin); + result.push({ plugin: NxAngularJsonPlugin, options: undefined }); } plugins ??= []; @@ -253,19 +280,19 @@ export function loadNxPluginsSync( // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins result.push( - getNxPackageJsonWorkspacesPlugin(root), - CreateProjectJsonProjectsPlugin + { plugin: getNxPackageJsonWorkspacesPlugin(root) }, + { plugin: CreateProjectJsonProjectsPlugin } ); - return result.map(ensurePluginIsV2); + return result; } export async function loadNxPlugins( - plugins: string[], + plugins: PluginDefinition[], paths = getNxRequirePaths(), root = workspaceRoot -): Promise<(NxPluginV2 & Pick)[]> { - const result: NxPlugin[] = [...(await getDefaultPlugins(root))]; +): Promise { + const result: LoadedNxPlugin[] = [...(await getDefaultPlugins(root))]; // TODO: These should be specified in nx.json // Temporarily load js as if it were a plugin which is built into nx @@ -279,11 +306,11 @@ export async function loadNxPlugins( // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins result.push( - getNxPackageJsonWorkspacesPlugin(root), - CreateProjectJsonProjectsPlugin + { plugin: getNxPackageJsonWorkspacesPlugin(root) }, + { plugin: CreateProjectJsonProjectsPlugin } ); - return result.map(ensurePluginIsV2); + return result; } function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 { @@ -494,22 +521,26 @@ function readPluginMainFromProjectConfiguration( return main; } -async function getDefaultPlugins(root: string) { - const plugins: NxPlugin[] = [await import('../plugins/js')]; +async function getDefaultPlugins(root: string): Promise { + const plugins: NxPluginV2[] = [await import('../plugins/js')]; if (shouldMergeAngularProjects(root, false)) { plugins.push( await import('../adapter/angular-json').then((m) => m.NxAngularJsonPlugin) ); } - return plugins; + return plugins.map((p) => ({ + plugin: p, + })); } -function getDefaultPluginsSync(root: string) { - const plugins: NxPlugin[] = [require('../plugins/js')]; +function getDefaultPluginsSync(root: string): LoadedNxPlugin[] { + const plugins: NxPluginV2[] = [require('../plugins/js')]; if (shouldMergeAngularProjects(root, false)) { plugins.push(require('../adapter/angular-json').NxAngularJsonPlugin); } - return plugins; + return plugins.map((p) => ({ + plugin: p, + })); } diff --git a/packages/nx/src/utils/plugins/local-plugins.ts b/packages/nx/src/utils/plugins/local-plugins.ts index 4ea842b884..ad601873a0 100644 --- a/packages/nx/src/utils/plugins/local-plugins.ts +++ b/packages/nx/src/utils/plugins/local-plugins.ts @@ -21,7 +21,7 @@ export async function getLocalWorkspacePlugins( if (existsSync(packageJsonPath)) { const packageJson: PackageJson = readJsonFile(packageJsonPath); const includeRuntimeCapabilities = nxJson?.plugins?.some((p) => - p.startsWith(packageJson.name) + (typeof p === 'string' ? p : p.plugin).startsWith(packageJson.name) ); const capabilities = await getPluginCapabilities( workspaceRoot, diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index b14071597d..08252297ec 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -103,11 +103,13 @@ async function tryGetModule( packageJson['nx-migrations'] ?? packageJson['schematics'] ?? packageJson['builders'] - ? await loadNxPluginAsync( - packageJson.name, - getNxRequirePaths(workspaceRoot), - workspaceRoot - ) + ? ( + await loadNxPluginAsync( + packageJson.name, + getNxRequirePaths(workspaceRoot), + workspaceRoot + ) + ).plugin : ({ name: packageJson.name, } as NxPlugin);