feat(core): make createNodes async (#20195)

Co-authored-by: Jonathan Cammisuli <jon@cammisuli.ca>
This commit is contained in:
Craigory Coppola 2023-11-13 16:57:20 -05:00 committed by GitHub
parent 6475c41ec8
commit 206247f9ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 614 additions and 366 deletions

View File

@ -0,0 +1,9 @@
# Type alias: CreateNodesAsync<T\>
Ƭ **CreateNodesAsync**<`T`\>: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunctionAsync<T\>]
#### Type parameters
| Name | Type |
| :--- | :-------- |
| `T` | `unknown` |

View File

@ -1,6 +1,6 @@
# Type alias: CreateNodesFunction<T\> # Type alias: CreateNodesFunction<T\>
Ƭ **CreateNodesFunction**<`T`\>: (`projectConfigurationFile`: `string`, `options`: `T` \| `undefined`, `context`: [`CreateNodesContext`](../../devkit/documents/CreateNodesContext)) => { `externalNodes?`: `Record`<`string`, [`ProjectGraphExternalNode`](../../devkit/documents/ProjectGraphExternalNode)\> ; `projects?`: `Record`<`string`, `Optional`<[`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration), `"root"`\>\> } Ƭ **CreateNodesFunction**<`T`\>: (`projectConfigurationFile`: `string`, `options`: `T` \| `undefined`, `context`: [`CreateNodesContext`](../../devkit/documents/CreateNodesContext)) => [`CreateNodesResult`](../../devkit/documents/CreateNodesResult)
#### Type parameters #### Type parameters
@ -10,7 +10,7 @@
#### Type declaration #### Type declaration
▸ (`projectConfigurationFile`, `options`, `context`): `Object` ▸ (`projectConfigurationFile`, `options`, `context`): [`CreateNodesResult`](../../devkit/documents/CreateNodesResult)
A function which parses a configuration file into a set of nodes. A function which parses a configuration file into a set of nodes.
Used for creating nodes for the [ProjectGraph](../../devkit/documents/ProjectGraph) Used for creating nodes for the [ProjectGraph](../../devkit/documents/ProjectGraph)
@ -25,9 +25,4 @@ Used for creating nodes for the [ProjectGraph](../../devkit/documents/ProjectGra
##### Returns ##### Returns
`Object` [`CreateNodesResult`](../../devkit/documents/CreateNodesResult)
| Name | Type | Description |
| :--------------- | :---------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
| `externalNodes?` | `Record`<`string`, [`ProjectGraphExternalNode`](../../devkit/documents/ProjectGraphExternalNode)\> | A map of external node name -> external node. External nodes do not have a root, so the key is their name. |
| `projects?` | `Record`<`string`, `Optional`<[`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration), `"root"`\>\> | A map of project root -> project configuration |

View File

@ -0,0 +1,24 @@
# Interface: CreateNodesResult
## Table of contents
### Properties
- [externalNodes](../../devkit/documents/CreateNodesResult#externalnodes): Record&lt;string, ProjectGraphExternalNode&gt;
- [projects](../../devkit/documents/CreateNodesResult#projects): Record&lt;string, Optional&lt;ProjectConfiguration, &quot;root&quot;&gt;&gt;
## Properties
### externalNodes
`Optional` **externalNodes**: `Record`<`string`, [`ProjectGraphExternalNode`](../../devkit/documents/ProjectGraphExternalNode)\>
A map of external node name -> external node. External nodes do not have a root, so the key is their name.
---
### projects
`Optional` **projects**: `Record`<`string`, `Optional`<[`ProjectConfiguration`](../../devkit/documents/ProjectConfiguration), `"root"`\>\>
A map of project root -> project configuration

View File

@ -1,19 +1,20 @@
# Type alias: NxPluginV2<T\> # Type alias: NxPluginV2<TOptions, TCreateNodes\>
Ƭ **NxPluginV2**<`T`\>: `Object` Ƭ **NxPluginV2**<`TOptions`, `TCreateNodes`\>: `Object`
A plugin for Nx which creates nodes and dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) A plugin for Nx which creates nodes and dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph)
#### Type parameters #### Type parameters
| Name | Type | | Name | Type |
| :--- | :-------- | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `T` | `unknown` | | `TOptions` | `unknown` |
| `TCreateNodes` | extends [`CreateNodes`](../../devkit/documents/CreateNodes)<`TOptions`\> \| [`CreateNodesAsync`](../../devkit/documents/CreateNodesAsync)<`TOptions`\> = [`CreateNodes`](../../devkit/documents/CreateNodes)<`TOptions`\> \| [`CreateNodesAsync`](../../devkit/documents/CreateNodesAsync)<`TOptions`\> |
#### Type declaration #### Type declaration
| Name | Type | Description | | Name | Type | Description |
| :-------------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | :-------------------- | :----------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)<`T`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) | | `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)<`TOptions`\> | 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 } | | `createNodes?` | `TCreateNodes` | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } |
| `name` | `string` | - | | `name` | `string` | - |

View File

@ -25,6 +25,7 @@ It only uses language primitives and immutable objects
- [CreateDependenciesContext](../../devkit/documents/CreateDependenciesContext) - [CreateDependenciesContext](../../devkit/documents/CreateDependenciesContext)
- [CreateNodesContext](../../devkit/documents/CreateNodesContext) - [CreateNodesContext](../../devkit/documents/CreateNodesContext)
- [CreateNodesResult](../../devkit/documents/CreateNodesResult)
- [DefaultTasksRunnerOptions](../../devkit/documents/DefaultTasksRunnerOptions) - [DefaultTasksRunnerOptions](../../devkit/documents/DefaultTasksRunnerOptions)
- [ExecutorContext](../../devkit/documents/ExecutorContext) - [ExecutorContext](../../devkit/documents/ExecutorContext)
- [ExecutorsJson](../../devkit/documents/ExecutorsJson) - [ExecutorsJson](../../devkit/documents/ExecutorsJson)
@ -64,6 +65,7 @@ It only uses language primitives and immutable objects
- [CreateDependencies](../../devkit/documents/CreateDependencies) - [CreateDependencies](../../devkit/documents/CreateDependencies)
- [CreateNodes](../../devkit/documents/CreateNodes) - [CreateNodes](../../devkit/documents/CreateNodes)
- [CreateNodesAsync](../../devkit/documents/CreateNodesAsync)
- [CreateNodesFunction](../../devkit/documents/CreateNodesFunction) - [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
- [CustomHasher](../../devkit/documents/CustomHasher) - [CustomHasher](../../devkit/documents/CustomHasher)
- [DynamicDependency](../../devkit/documents/DynamicDependency) - [DynamicDependency](../../devkit/documents/DynamicDependency)

View File

@ -25,6 +25,7 @@ It only uses language primitives and immutable objects
- [CreateDependenciesContext](../../devkit/documents/CreateDependenciesContext) - [CreateDependenciesContext](../../devkit/documents/CreateDependenciesContext)
- [CreateNodesContext](../../devkit/documents/CreateNodesContext) - [CreateNodesContext](../../devkit/documents/CreateNodesContext)
- [CreateNodesResult](../../devkit/documents/CreateNodesResult)
- [DefaultTasksRunnerOptions](../../devkit/documents/DefaultTasksRunnerOptions) - [DefaultTasksRunnerOptions](../../devkit/documents/DefaultTasksRunnerOptions)
- [ExecutorContext](../../devkit/documents/ExecutorContext) - [ExecutorContext](../../devkit/documents/ExecutorContext)
- [ExecutorsJson](../../devkit/documents/ExecutorsJson) - [ExecutorsJson](../../devkit/documents/ExecutorsJson)
@ -64,6 +65,7 @@ It only uses language primitives and immutable objects
- [CreateDependencies](../../devkit/documents/CreateDependencies) - [CreateDependencies](../../devkit/documents/CreateDependencies)
- [CreateNodes](../../devkit/documents/CreateNodes) - [CreateNodes](../../devkit/documents/CreateNodes)
- [CreateNodesAsync](../../devkit/documents/CreateNodesAsync)
- [CreateNodesFunction](../../devkit/documents/CreateNodesFunction) - [CreateNodesFunction](../../devkit/documents/CreateNodesFunction)
- [CustomHasher](../../devkit/documents/CustomHasher) - [CustomHasher](../../devkit/documents/CustomHasher)
- [DynamicDependency](../../devkit/documents/DynamicDependency) - [DynamicDependency](../../devkit/documents/DynamicDependency)

View File

@ -203,7 +203,8 @@ function startDevRemotes(
const { schema } = getExecutorInformation( const { schema } = getExecutorInformation(
collection, collection,
executor, executor,
workspaceRoot workspaceRoot,
workspaceProjects
); );
if ( if (
(options.verbose && schema.additionalProperties) || (options.verbose && schema.additionalProperties) ||
@ -223,6 +224,7 @@ function startDevRemotes(
target: 'serve', target: 'serve',
configuration: context.target.configuration, configuration: context.target.configuration,
runOptions, runOptions,
projects: workspaceProjects,
}, },
options.verbose options.verbose
).then((obs) => { ).then((obs) => {
@ -279,8 +281,7 @@ export function executeModuleFederationDevServerBuilder(
configurationName: context.target.configuration, configurationName: context.target.configuration,
cwd: context.currentDirectory, cwd: context.currentDirectory,
isVerbose: options.verbose, isVerbose: options.verbose,
projectsConfigurations: projectsConfigurations: { projects: workspaceProjects, version: 2 },
readProjectsConfigurationFromProjectGraph(projectGraph),
nxJsonConfiguration: readNxJson(), nxJsonConfiguration: readNxJson(),
} }
) )

View File

@ -98,7 +98,8 @@ export function executeModuleFederationDevSSRBuilder(
const { schema } = getExecutorInformation( const { schema } = getExecutorInformation(
collection, collection,
executor, executor,
workspaceRoot workspaceRoot,
workspaceProjects
); );
if (schema.additionalProperties || 'verbose' in schema.properties) { if (schema.additionalProperties || 'verbose' in schema.properties) {
@ -141,6 +142,7 @@ export function executeModuleFederationDevSSRBuilder(
target, target,
configuration: context.target.configuration, configuration: context.target.configuration,
runOptions, runOptions,
projects: workspaceProjects,
}, },
options.verbose options.verbose
).then((obs) => ).then((obs) =>

View File

@ -194,7 +194,8 @@ ${e.message || e}`);
const { schema } = getExecutorInformation( const { schema } = getExecutorInformation(
collection, collection,
executor, executor,
context.root context.root,
context.projectsConfigurations.projects
); );
// NOTE: schema won't have a default since readTargetOptions would have // NOTE: schema won't have a default since readTargetOptions would have

View File

@ -38,7 +38,12 @@ export function readTargetOptions<T = any>(
const ws = new Workspaces(context.root); const ws = new Workspaces(context.root);
const [nodeModule, executorName] = targetConfiguration.executor.split(':'); const [nodeModule, executorName] = targetConfiguration.executor.split(':');
const { schema } = getExecutorInformation const { schema } = getExecutorInformation
? getExecutorInformation(nodeModule, executorName, context.root) ? getExecutorInformation(
nodeModule,
executorName,
context.root,
context.projectsConfigurations?.projects ?? context.workspace.projects
)
: // TODO(v18): remove readExecutor. This is to be backwards compatible with Nx 16.5 and below. : // TODO(v18): remove readExecutor. This is to be backwards compatible with Nx 16.5 and below.
(ws as any).readExecutor(nodeModule, executorName); (ws as any).readExecutor(nodeModule, executorName);

View File

@ -35,7 +35,10 @@ import {
getProjects, getProjects,
updateProjectConfiguration, updateProjectConfiguration,
} from '../generators/utils/project-configuration'; } from '../generators/utils/project-configuration';
import { createProjectGraphAsync } from '../project-graph/project-graph'; import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';
import { readJsonFile } from '../utils/fileutils'; import { readJsonFile } from '../utils/fileutils';
import { getNxRequirePaths } from '../utils/installation-directory'; import { getNxRequirePaths } from '../utils/installation-directory';
import { parseJson } from '../utils/json'; import { parseJson } from '../utils/json';
@ -79,7 +82,8 @@ export async function createBuilderContext(
); );
const architectHost = await getWrappedWorkspaceNodeModulesArchitectHost( const architectHost = await getWrappedWorkspaceNodeModulesArchitectHost(
workspace, workspace,
context.root context.root,
context.projectsConfigurations.projects
); );
const registry = new schema.CoreSchemaRegistry(); const registry = new schema.CoreSchemaRegistry();
@ -156,6 +160,7 @@ export async function scheduleTarget(
target: string; target: string;
configuration: string; configuration: string;
runOptions: any; runOptions: any;
projects: Record<string, ProjectConfiguration>;
}, },
verbose: boolean verbose: boolean
): Promise<Observable<import('@angular-devkit/architect').BuilderOutput>> { ): Promise<Observable<import('@angular-devkit/architect').BuilderOutput>> {
@ -177,7 +182,8 @@ export async function scheduleTarget(
const architectHost = await getWrappedWorkspaceNodeModulesArchitectHost( const architectHost = await getWrappedWorkspaceNodeModulesArchitectHost(
workspace, workspace,
root root,
opts.projects
); );
const architect: Architect = new Architect(architectHost, registry); const architect: Architect = new Architect(architectHost, registry);
const run = await architect.scheduleTarget( const run = await architect.scheduleTarget(
@ -204,7 +210,8 @@ export async function scheduleTarget(
} }
function createNodeModulesEngineHost( function createNodeModulesEngineHost(
resolvePaths: string[] resolvePaths: string[],
projects: Record<string, ProjectConfiguration>
): import('@angular-devkit/schematics/tools').NodeModulesEngineHost { ): import('@angular-devkit/schematics/tools').NodeModulesEngineHost {
const NodeModulesEngineHost = require('@angular-devkit/schematics/tools') const NodeModulesEngineHost = require('@angular-devkit/schematics/tools')
.NodeModulesEngineHost as typeof import('@angular-devkit/schematics/tools').NodeModulesEngineHost; .NodeModulesEngineHost as typeof import('@angular-devkit/schematics/tools').NodeModulesEngineHost;
@ -226,7 +233,7 @@ function createNodeModulesEngineHost(
const { const {
json: { generators, schematics }, json: { generators, schematics },
path: packageJsonPath, path: packageJsonPath,
} = readPluginPackageJson(name, paths); } = readPluginPackageJson(name, projects, paths);
if (!schematics && !generators) { if (!schematics && !generators) {
throw new Error( throw new Error(
@ -255,7 +262,8 @@ function createNodeModulesEngineHost(
function createWorkflow( function createWorkflow(
fsHost: virtualFs.Host<Stats>, fsHost: virtualFs.Host<Stats>,
root: string, root: string,
opts: any opts: any,
projects: Record<string, ProjectConfiguration>
): import('@angular-devkit/schematics/tools').NodeWorkflow { ): import('@angular-devkit/schematics/tools').NodeWorkflow {
const NodeWorkflow: typeof import('@angular-devkit/schematics/tools').NodeWorkflow = const NodeWorkflow: typeof import('@angular-devkit/schematics/tools').NodeWorkflow =
require('@angular-devkit/schematics/tools').NodeWorkflow; require('@angular-devkit/schematics/tools').NodeWorkflow;
@ -269,7 +277,7 @@ function createWorkflow(
), ),
resolvePaths: [process.cwd(), root], resolvePaths: [process.cwd(), root],
engineHostCreator: (options) => engineHostCreator: (options) =>
createNodeModulesEngineHost(options.resolvePaths), createNodeModulesEngineHost(options.resolvePaths, projects),
}); });
workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults); workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults);
workflow.engineHost.registerOptionsTransform( workflow.engineHost.registerOptionsTransform(
@ -676,6 +684,7 @@ function findMatchingFileChange(host: Tree, path: Path) {
export async function generate( export async function generate(
root: string, root: string,
opts: GenerateOptions, opts: GenerateOptions,
projects: Record<string, ProjectConfiguration>,
verbose: boolean verbose: boolean
) { ) {
const logger = getLogger(verbose); const logger = getLogger(verbose);
@ -687,7 +696,7 @@ export async function generate(
`ng-cli generator: ${opts.collectionName}:${opts.generatorName}` `ng-cli generator: ${opts.collectionName}:${opts.generatorName}`
) )
); );
const workflow = createWorkflow(fsHost, root, opts); const workflow = createWorkflow(fsHost, root, opts, projects);
const collection = getCollection(workflow, opts.collectionName); const collection = getCollection(workflow, opts.collectionName);
const schematic = collection.createSchematic(opts.generatorName, true); const schematic = collection.createSchematic(opts.generatorName, true);
return ( return (
@ -769,6 +778,7 @@ export async function runMigration(
root: string, root: string,
packageName: string, packageName: string,
migrationName: string, migrationName: string,
projects: Record<string, ProjectConfiguration>,
isVerbose: boolean isVerbose: boolean
) { ) {
const logger = getLogger(isVerbose); const logger = getLogger(isVerbose);
@ -780,7 +790,7 @@ export async function runMigration(
`ng-cli migration: ${packageName}:${migrationName}` `ng-cli migration: ${packageName}:${migrationName}`
) )
); );
const workflow = createWorkflow(fsHost, root, {}); const workflow = createWorkflow(fsHost, root, {}, projects);
const collection = resolveMigrationsCollection(packageName); const collection = resolveMigrationsCollection(packageName);
const record = { loggingQueue: [] as string[], error: false }; const record = { loggingQueue: [] as string[], error: false };
@ -889,6 +899,9 @@ export function wrapAngularDevkitSchematic(
require('./compat'); require('./compat');
return async (host: Tree, generatorOptions: { [k: string]: any }) => { return async (host: Tree, generatorOptions: { [k: string]: any }) => {
const graph = await createProjectGraphAsync();
const { projects } = readProjectsConfigurationFromProjectGraph(graph);
if ( if (
mockedSchematics && mockedSchematics &&
mockedSchematics[`${collectionName}:${generatorName}`] mockedSchematics[`${collectionName}:${generatorName}`]
@ -950,7 +963,7 @@ export function wrapAngularDevkitSchematic(
defaults: false, defaults: false,
quiet: false, quiet: false,
}; };
const workflow = createWorkflow(fsHost, host.root, options); const workflow = createWorkflow(fsHost, host.root, options, projects);
// used for testing // used for testing
if (collectionResolutionOverrides) { if (collectionResolutionOverrides) {
@ -1053,14 +1066,19 @@ function saveProjectsConfigurationsInWrappedSchematic(
async function getWrappedWorkspaceNodeModulesArchitectHost( async function getWrappedWorkspaceNodeModulesArchitectHost(
workspace: workspaces.WorkspaceDefinition, workspace: workspaces.WorkspaceDefinition,
root: string root: string,
projects: Record<string, ProjectConfiguration>
) { ) {
const { const {
WorkspaceNodeModulesArchitectHost: AngularWorkspaceNodeModulesArchitectHost, WorkspaceNodeModulesArchitectHost: AngularWorkspaceNodeModulesArchitectHost,
} = await import('@angular-devkit/architect/node'); } = await import('@angular-devkit/architect/node');
class WrappedWorkspaceNodeModulesArchitectHost extends AngularWorkspaceNodeModulesArchitectHost { class WrappedWorkspaceNodeModulesArchitectHost extends AngularWorkspaceNodeModulesArchitectHost {
constructor(private workspace, private root) { constructor(
private workspace,
private root: string,
private projects: Record<string, ProjectConfiguration>
) {
super(workspace, root); super(workspace, root);
} }
@ -1090,6 +1108,7 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
const { json: packageJson, path: packageJsonPath } = const { json: packageJson, path: packageJsonPath } =
readPluginPackageJson( readPluginPackageJson(
nodeModule, nodeModule,
this.projects,
this.root ? [this.root, __dirname] : [__dirname] this.root ? [this.root, __dirname] : [__dirname]
); );
const executorsFile = packageJson.executors ?? packageJson.builders; const executorsFile = packageJson.executors ?? packageJson.builders;
@ -1170,5 +1189,9 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
} }
} }
return new WrappedWorkspaceNodeModulesArchitectHost(workspace, root); return new WrappedWorkspaceNodeModulesArchitectHost(
workspace,
root,
projects
);
} }

View File

@ -71,7 +71,12 @@ async function promptForCollection(
resolvedCollectionName, resolvedCollectionName,
normalizedGeneratorName, normalizedGeneratorName,
generatorConfiguration: { ['x-deprecated']: deprecated, hidden }, generatorConfiguration: { ['x-deprecated']: deprecated, hidden },
} = getGeneratorInformation(collectionName, generatorName, workspaceRoot); } = getGeneratorInformation(
collectionName,
generatorName,
workspaceRoot,
projectsConfiguration.projects
);
if (hidden) { if (hidden) {
continue; continue;
} }
@ -96,7 +101,12 @@ async function promptForCollection(
resolvedCollectionName, resolvedCollectionName,
normalizedGeneratorName, normalizedGeneratorName,
generatorConfiguration: { ['x-deprecated']: deprecated, hidden }, generatorConfiguration: { ['x-deprecated']: deprecated, hidden },
} = getGeneratorInformation(name, generatorName, workspaceRoot); } = getGeneratorInformation(
name,
generatorName,
workspaceRoot,
projectsConfiguration.projects
);
if (hidden) { if (hidden) {
continue; continue;
} }
@ -158,7 +168,12 @@ async function promptForCollection(
return true; return true;
} }
try { try {
getGeneratorInformation(value, generatorName, workspaceRoot); getGeneratorInformation(
value,
generatorName,
workspaceRoot,
projectsConfiguration.projects
);
return true; return true;
} catch { } catch {
logger.error(`\nCould not find ${value}:${generatorName}`); logger.error(`\nCould not find ${value}:${generatorName}`);
@ -316,7 +331,8 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
} = getGeneratorInformation( } = getGeneratorInformation(
opts.collectionName, opts.collectionName,
opts.generatorName, opts.generatorName,
workspaceRoot workspaceRoot,
projectsConfigurations.projects
); );
if (deprecated) { if (deprecated) {
@ -360,7 +376,8 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
getGeneratorInformation( getGeneratorInformation(
opts.collectionName, opts.collectionName,
normalizedGeneratorName, normalizedGeneratorName,
workspaceRoot workspaceRoot,
projectsConfigurations.projects
).isNxGenerator ).isNxGenerator
) { ) {
const host = new FsTree( const host = new FsTree(
@ -400,6 +417,7 @@ export async function generate(cwd: string, args: { [k: string]: any }) {
...opts, ...opts,
generatorOptions: combinedOpts, generatorOptions: combinedOpts,
}, },
projectsConfigurations.projects,
verbose verbose
); );
} }

View File

@ -4,6 +4,7 @@ import {
GeneratorsJson, GeneratorsJson,
GeneratorsJsonEntry, GeneratorsJsonEntry,
} from '../../config/misc-interfaces'; } from '../../config/misc-interfaces';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import { import {
getImplementationFactory, getImplementationFactory,
resolveSchema, resolveSchema,
@ -14,7 +15,8 @@ import { readPluginPackageJson } from '../../utils/nx-plugin';
export function getGeneratorInformation( export function getGeneratorInformation(
collectionName: string, collectionName: string,
generatorName: string, generatorName: string,
root: string | null root: string | null,
projects: Record<string, ProjectConfiguration>
): { ): {
resolvedCollectionName: string; resolvedCollectionName: string;
normalizedGeneratorName: string; normalizedGeneratorName: string;
@ -30,7 +32,7 @@ export function getGeneratorInformation(
generatorsJson, generatorsJson,
resolvedCollectionName, resolvedCollectionName,
normalizedGeneratorName, normalizedGeneratorName,
} = readGeneratorsJson(collectionName, generatorName, root); } = readGeneratorsJson(collectionName, generatorName, root, projects);
const generatorsDir = dirname(generatorsFilePath); const generatorsDir = dirname(generatorsFilePath);
const generatorConfig = const generatorConfig =
generatorsJson.generators?.[normalizedGeneratorName] || generatorsJson.generators?.[normalizedGeneratorName] ||
@ -71,7 +73,8 @@ export function getGeneratorInformation(
export function readGeneratorsJson( export function readGeneratorsJson(
collectionName: string, collectionName: string,
generator: string, generator: string,
root: string | null root: string | null,
projects: Record<string, ProjectConfiguration>
): { ): {
generatorsFilePath: string; generatorsFilePath: string;
generatorsJson: GeneratorsJson; generatorsJson: GeneratorsJson;
@ -86,6 +89,7 @@ export function readGeneratorsJson(
} else { } else {
const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( const { json: packageJson, path: packageJsonPath } = readPluginPackageJson(
collectionName, collectionName,
projects,
root ? [root, __dirname] : [__dirname] root ? [root, __dirname] : [__dirname]
); );
const generatorsFile = packageJson.generators ?? packageJson.schematics; const generatorsFile = packageJson.generators ?? packageJson.schematics;
@ -109,7 +113,7 @@ export function readGeneratorsJson(
if (!normalizedGeneratorName) { if (!normalizedGeneratorName) {
for (let parent of generatorsJson.extends || []) { for (let parent of generatorsJson.extends || []) {
try { try {
return readGeneratorsJson(parent, generator, root); return readGeneratorsJson(parent, generator, root, projects);
} catch (e) {} } catch (e) {}
} }

View File

@ -31,19 +31,19 @@ export interface ListArgs {
* *
*/ */
export async function listHandler(args: ListArgs): Promise<void> { export async function listHandler(args: ListArgs): Promise<void> {
if (args.plugin) { const nxJson = readNxJson();
await listPluginCapabilities(args.plugin); const projectGraph = await createProjectGraphAsync({ exitOnError: true });
} else { const projects = readProjectsConfigurationFromProjectGraph(projectGraph);
const nxJson = readNxJson();
const corePlugins = fetchCorePlugins();
const projectGraph = await createProjectGraphAsync({ exitOnError: true });
const localPlugins = await getLocalWorkspacePlugins( if (args.plugin) {
readProjectsConfigurationFromProjectGraph(projectGraph), await listPluginCapabilities(args.plugin, projects.projects);
nxJson } else {
); const corePlugins = fetchCorePlugins();
const localPlugins = await getLocalWorkspacePlugins(projects, nxJson);
const installedPlugins = await getInstalledPluginsAndCapabilities( const installedPlugins = await getInstalledPluginsAndCapabilities(
workspaceRoot workspaceRoot,
projects.projects
); );
if (localPlugins.size) { if (localPlugins.size) {

View File

@ -62,6 +62,10 @@ import { readNxJson } from '../../config/configuration';
import { runNxSync } from '../../utils/child-process'; import { runNxSync } from '../../utils/child-process';
import { daemonClient } from '../../daemon/client/client'; import { daemonClient } from '../../daemon/client/client';
import { isNxCloudUsed } from '../../utils/nx-cloud-utils'; import { isNxCloudUsed } from '../../utils/nx-cloud-utils';
import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../../project-graph/project-graph';
export interface ResolvedMigrationConfiguration extends MigrationsJson { export interface ResolvedMigrationConfiguration extends MigrationsJson {
packageGroup?: ArrayPackageGroup; packageGroup?: ArrayPackageGroup;
@ -1404,6 +1408,9 @@ export async function executeMigrations(
root, root,
m.package, m.package,
m.name, m.name,
readProjectsConfigurationFromProjectGraph(
await createProjectGraphAsync()
).projects,
isVerbose isVerbose
); );

View File

@ -16,7 +16,12 @@ export async function newWorkspace(cwd: string, args: { [k: string]: any }) {
async () => { async () => {
const isInteractive = args.interactive; const isInteractive = args.interactive;
const { normalizedGeneratorName, schema, implementationFactory } = const { normalizedGeneratorName, schema, implementationFactory } =
getGeneratorInformation('@nx/workspace/generators.json', 'new', null); getGeneratorInformation(
'@nx/workspace/generators.json',
'new',
null,
{}
);
removeSpecialFlags(args); removeSpecialFlags(args);
const combinedOpts = await combineOptionsForGenerator( const combinedOpts = await combineOptionsForGenerator(
args, args,

View File

@ -51,6 +51,7 @@ export interface ReleaseVersionGeneratorSchema {
export async function versionHandler(args: VersionOptions): Promise<void> { export async function versionHandler(args: VersionOptions): Promise<void> {
const projectGraph = await createProjectGraphAsync({ exitOnError: true }); const projectGraph = await createProjectGraphAsync({ exitOnError: true });
const { projects } = readProjectsConfigurationFromProjectGraph(projectGraph);
const nxJson = readNxJson(); const nxJson = readNxJson();
if (args.verbose) { if (args.verbose) {
@ -98,6 +99,7 @@ export async function versionHandler(args: VersionOptions): Promise<void> {
releaseGroup.version.generator releaseGroup.version.generator
), ),
configGeneratorOptions: releaseGroup.version.generatorOptions, configGeneratorOptions: releaseGroup.version.generatorOptions,
projects,
}); });
const releaseGroupProjectNames = Array.from( const releaseGroupProjectNames = Array.from(
@ -133,6 +135,7 @@ export async function versionHandler(args: VersionOptions): Promise<void> {
releaseGroup.version.generator releaseGroup.version.generator
), ),
configGeneratorOptions: releaseGroup.version.generatorOptions, configGeneratorOptions: releaseGroup.version.generatorOptions,
projects,
}); });
await runVersionOnProjects( await runVersionOnProjects(
@ -260,9 +263,15 @@ function resolveGeneratorData({
collectionName, collectionName,
generatorName, generatorName,
configGeneratorOptions, configGeneratorOptions,
projects,
}): GeneratorData { }): GeneratorData {
const { normalizedGeneratorName, schema, implementationFactory } = const { normalizedGeneratorName, schema, implementationFactory } =
getGeneratorInformation(collectionName, generatorName, workspaceRoot); getGeneratorInformation(
collectionName,
generatorName,
workspaceRoot,
projects
);
return { return {
collectionName, collectionName,

View File

@ -14,6 +14,7 @@ import {
resolveSchema, resolveSchema,
} from '../../config/schema-utils'; } from '../../config/schema-utils';
import { getNxRequirePaths } from '../../utils/installation-directory'; import { getNxRequirePaths } from '../../utils/installation-directory';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
export function normalizeExecutorSchema( export function normalizeExecutorSchema(
schema: Partial<ExecutorConfig['schema']> schema: Partial<ExecutorConfig['schema']>
@ -40,7 +41,8 @@ const cachedExecutorInformation = {};
export function getExecutorInformation( export function getExecutorInformation(
nodeModule: string, nodeModule: string,
executor: string, executor: string,
root: string root: string,
projects: Record<string, ProjectConfiguration>
): ExecutorConfig & { isNgCompat: boolean; isNxExecutor: boolean } { ): ExecutorConfig & { isNgCompat: boolean; isNxExecutor: boolean } {
try { try {
const key = cacheKey(nodeModule, executor, root); const key = cacheKey(nodeModule, executor, root);
@ -49,7 +51,8 @@ export function getExecutorInformation(
const { executorsFilePath, executorConfig, isNgCompat } = readExecutorJson( const { executorsFilePath, executorConfig, isNgCompat } = readExecutorJson(
nodeModule, nodeModule,
executor, executor,
root root,
projects
); );
const executorsDir = dirname(executorsFilePath); const executorsDir = dirname(executorsFilePath);
const schemaPath = resolveSchema(executorConfig.schema, executorsDir); const schemaPath = resolveSchema(executorConfig.schema, executorsDir);
@ -95,7 +98,8 @@ export function getExecutorInformation(
function readExecutorJson( function readExecutorJson(
nodeModule: string, nodeModule: string,
executor: string, executor: string,
root: string root: string,
projects: Record<string, ProjectConfiguration>
): { ): {
executorsFilePath: string; executorsFilePath: string;
executorConfig: { executorConfig: {
@ -108,6 +112,7 @@ function readExecutorJson(
} { } {
const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( const { json: packageJson, path: packageJsonPath } = readPluginPackageJson(
nodeModule, nodeModule,
projects,
root root
? [root, __dirname, process.cwd(), ...getNxRequirePaths()] ? [root, __dirname, process.cwd(), ...getNxRequirePaths()]
: [__dirname, process.cwd(), ...getNxRequirePaths()] : [__dirname, process.cwd(), ...getNxRequirePaths()]

View File

@ -96,7 +96,8 @@ async function parseExecutorAndTarget(
const { schema, implementationFactory } = getExecutorInformation( const { schema, implementationFactory } = getExecutorInformation(
nodeModule, nodeModule,
executor, executor,
root root,
projectsConfigurations.projects
); );
return { executor, implementationFactory, nodeModule, schema, targetConfig }; return { executor, implementationFactory, nodeModule, schema, targetConfig };
@ -154,7 +155,14 @@ async function runExecutorInternal<T extends { success: boolean }>(
isVerbose isVerbose
); );
if (getExecutorInformation(nodeModule, executor, root).isNxExecutor) { if (
getExecutorInformation(
nodeModule,
executor,
root,
projectsConfigurations.projects
).isNxExecutor
) {
const implementation = implementationFactory() as Executor<any>; const implementation = implementationFactory() as Executor<any>;
const r = implementation(combinedOptions, { const r = implementation(combinedOptions, {
root, root,
@ -190,6 +198,7 @@ async function runExecutorInternal<T extends { success: boolean }>(
target, target,
configuration, configuration,
runOptions: combinedOptions, runOptions: combinedOptions,
projects: projectsConfigurations.projects,
}, },
isVerbose isVerbose
); );

View File

@ -60,30 +60,25 @@ describe('Workspaces', () => {
}), }),
}); });
withEnvironmentVariables( const { projectNodes } = await withEnvironmentVariables(
{ {
NX_WORKSPACE_ROOT: fs.tempDir, NX_WORKSPACE_ROOT: fs.tempDir,
}, },
async () => { () => retrieveProjectConfigurations(fs.tempDir, readNxJson(fs.tempDir))
const resolved = await retrieveProjectConfigurations(
fs.tempDir,
readNxJson(fs.tempDir)
);
expect(resolved.projectNodes['my-package']).toEqual({
name: 'my-package',
root: 'packages/my-package',
sourceRoot: 'packages/my-package',
projectType: 'library',
targets: {
'nx-release-publish': {
dependsOn: ['^nx-release-publish'],
executor: '@nx/js:release-publish',
options: {},
},
},
});
}
); );
expect(projectNodes['my-package']).toEqual({
name: 'my-package',
root: 'packages/my-package',
sourceRoot: 'packages/my-package',
projectType: 'library',
targets: {
'nx-release-publish': {
dependsOn: ['^nx-release-publish'],
executor: '@nx/js:release-publish',
options: {},
},
},
});
}); });
}); });
}); });

View File

@ -1,9 +1,12 @@
import { dirname } from 'path'; import { dirname } from 'path';
import {
readCachedProjectGraph,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';
import type { NxJsonConfiguration } from './nx-json'; import type { NxJsonConfiguration } from './nx-json';
import { readNxJson } from './nx-json'; import { readNxJson } from './nx-json';
import { ProjectsConfigurations } from './workspace-json-project-json'; import { ProjectsConfigurations } from './workspace-json-project-json';
import { retrieveProjectConfigurationsSync } from '../project-graph/utils/retrieve-workspace-files';
// TODO(v18): remove this class // TODO(v18): remove this class
/** /**
@ -19,9 +22,7 @@ export class Workspaces {
const nxJson = readNxJson(this.root); const nxJson = readNxJson(this.root);
return { return {
version: 2, ...readProjectsConfigurationFromProjectGraph(readCachedProjectGraph()),
projects: retrieveProjectConfigurationsSync(this.root, nxJson)
.projectNodes,
...nxJson, ...nxJson,
}; };
} }

View File

@ -51,7 +51,9 @@ export type {
NxPluginV2, NxPluginV2,
ProjectTargetConfigurator, ProjectTargetConfigurator,
CreateNodes, CreateNodes,
CreateNodesAsync,
CreateNodesFunction, CreateNodesFunction,
CreateNodesResult,
CreateNodesContext, CreateNodesContext,
CreateDependencies, CreateDependencies,
CreateDependenciesContext, CreateDependenciesContext,

View File

@ -134,9 +134,9 @@ export class Watcher {
export class WorkspaceContext { export class WorkspaceContext {
workspaceRoot: string workspaceRoot: string
constructor(workspaceRoot: string) constructor(workspaceRoot: string)
getWorkspaceFiles(globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Record<string, string>): NxWorkspaceFiles getWorkspaceFiles(globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Promise<Record<string, string>>): Promise<NxWorkspaceFiles>
glob(globs: Array<string>): Array<string> glob(globs: Array<string>): Array<string>
getProjectConfigurations(globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Record<string, string>): Record<string, string> getProjectConfigurations(globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Promise<Record<string, string>>): Promise<Record<string, string>>
incrementalUpdate(updatedFiles: Array<string>, deletedFiles: Array<string>): Record<string, string> incrementalUpdate(updatedFiles: Array<string>, deletedFiles: Array<string>): Record<string, string>
allFileData(): Array<FileData> allFileData(): Array<FileData>
} }

View File

@ -6,7 +6,7 @@ import { readJsonFile } from '../../utils/fileutils';
describe('workspace files', () => { describe('workspace files', () => {
function createParseConfigurationsFunction(tempDir: string) { function createParseConfigurationsFunction(tempDir: string) {
return (filenames: string[]) => { return async (filenames: string[]) => {
const res = {}; const res = {};
for (const filename of filenames) { for (const filename of filenames) {
const json = readJsonFile(join(tempDir, filename)); const json = readJsonFile(join(tempDir, filename));
@ -51,7 +51,7 @@ describe('workspace files', () => {
let globs = ['project.json', '**/project.json', 'libs/*/package.json']; let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
const context = new WorkspaceContext(fs.tempDir); const context = new WorkspaceContext(fs.tempDir);
let { projectFileMap, globalFiles } = context.getWorkspaceFiles( let { projectFileMap, globalFiles } = await context.getWorkspaceFiles(
globs, globs,
createParseConfigurationsFunction(fs.tempDir) createParseConfigurationsFunction(fs.tempDir)
); );
@ -148,7 +148,7 @@ describe('workspace files', () => {
const context = new WorkspaceContext(fs.tempDir); const context = new WorkspaceContext(fs.tempDir);
const globs = ['project.json', '**/project.json', '**/package.json']; const globs = ['project.json', '**/project.json', '**/package.json'];
const { globalFiles, projectFileMap } = context.getWorkspaceFiles( const { globalFiles, projectFileMap } = await context.getWorkspaceFiles(
globs, globs,
createParseConfigurationsFunction(fs.tempDir) createParseConfigurationsFunction(fs.tempDir)
); );

View File

@ -1,7 +1,7 @@
use std::cmp::Ordering; use std::cmp::Ordering;
#[napi(object)] #[napi(object)]
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct FileData { pub struct FileData {
pub file: String, pub file: String,
pub hash: String, pub hash: String,
@ -17,7 +17,7 @@ impl PartialEq<Self> for FileData {
impl PartialOrd<Self> for FileData { impl PartialOrd<Self> for FileData {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.file.partial_cmp(&other.file) Some(self.cmp(other))
} }
} }

View File

@ -1,6 +1,7 @@
use crate::native::glob::build_glob_set; use crate::native::glob::build_glob_set;
use crate::native::utils::path::Normalize; use crate::native::utils::path::Normalize;
use std::collections::HashMap; use std::collections::HashMap;
use napi::bindgen_prelude::Promise;
use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors}; use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors};
use rayon::prelude::*; use rayon::prelude::*;
@ -29,9 +30,9 @@ pub(super) fn get_project_configurations<ConfigurationParser>(
globs: Vec<String>, globs: Vec<String>,
files: Option<&[(PathBuf, String)]>, files: Option<&[(PathBuf, String)]>,
parse_configurations: ConfigurationParser, parse_configurations: ConfigurationParser,
) -> napi::Result<HashMap<String, String>> ) -> napi::Result<Promise<HashMap<String, String>>>
where where
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, String>>, ConfigurationParser: Fn(Vec<String>) -> napi::Result<Promise<HashMap<String, String>>>,
{ {
let config_paths = glob_files(globs, files).map_err(anyhow::Error::from)?; let config_paths = glob_files(globs, files).map_err(anyhow::Error::from)?;

View File

@ -3,6 +3,7 @@ use std::collections::HashMap;
use crate::native::types::FileData; use crate::native::types::FileData;
use crate::native::utils::path::Normalize; use crate::native::utils::path::Normalize;
use napi::bindgen_prelude::*;
use parking_lot::lock_api::MutexGuard; use parking_lot::lock_api::MutexGuard;
use parking_lot::{Condvar, Mutex, RawMutex}; use parking_lot::{Condvar, Mutex, RawMutex};
use rayon::prelude::*; use rayon::prelude::*;
@ -15,7 +16,6 @@ use xxhash_rust::xxh3;
use crate::native::walker::nx_walker; use crate::native::walker::nx_walker;
use crate::native::workspace::errors::WorkspaceErrors; use crate::native::workspace::errors::WorkspaceErrors;
use crate::native::workspace::workspace_files::NxWorkspaceFiles;
use crate::native::workspace::{config_files, workspace_files}; use crate::native::workspace::{config_files, workspace_files};
#[napi] #[napi]
@ -148,20 +148,23 @@ impl WorkspaceContext {
} }
} }
#[napi] #[napi(ts_return_type = "Promise<NxWorkspaceFiles>")]
pub fn get_workspace_files<ConfigurationParser>( pub fn get_workspace_files<ConfigurationParser>(
&self, &self,
env: Env,
globs: Vec<String>, globs: Vec<String>,
parse_configurations: ConfigurationParser, parse_configurations: ConfigurationParser,
) -> napi::Result<NxWorkspaceFiles, WorkspaceErrors> ) -> anyhow::Result<Option<Object>>
where where
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, String>>, ConfigurationParser: Fn(Vec<String>) -> napi::Result<Promise<HashMap<String, String>>>,
{ {
workspace_files::get_files( workspace_files::get_files(
env,
globs, globs,
parse_configurations, parse_configurations,
self.files_worker.get_files().as_deref(), self.files_worker.get_files().as_deref(),
) )
.map_err(anyhow::Error::from)
} }
#[napi] #[napi]
@ -169,20 +172,25 @@ impl WorkspaceContext {
config_files::glob_files(globs, self.files_worker.get_files().as_deref()) config_files::glob_files(globs, self.files_worker.get_files().as_deref())
} }
#[napi] #[napi(ts_return_type = "Promise<Record<string, string>>")]
pub fn get_project_configurations<ConfigurationParser>( pub fn get_project_configurations<ConfigurationParser>(
&self, &self,
env: Env,
globs: Vec<String>, globs: Vec<String>,
parse_configurations: ConfigurationParser, parse_configurations: ConfigurationParser,
) -> napi::Result<HashMap<String, String>> ) -> napi::Result<Object>
where where
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, String>>, ConfigurationParser: Fn(Vec<String>) -> napi::Result<Promise<HashMap<String, String>>>,
{ {
config_files::get_project_configurations( let promise = config_files::get_project_configurations(
globs, globs,
self.files_worker.get_files().as_deref(), self.files_worker.get_files().as_deref(),
parse_configurations, parse_configurations,
) )?;
env.spawn_future(async move {
let result = promise.await?;
Ok(result)
})
} }
#[napi] #[napi]

View File

@ -1,6 +1,8 @@
use napi::bindgen_prelude::{Object, Promise};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use napi::Env;
use rayon::prelude::*; use rayon::prelude::*;
use tracing::trace; use tracing::trace;
@ -18,74 +20,83 @@ pub struct NxWorkspaceFiles {
} }
pub(super) fn get_files<ConfigurationParser>( pub(super) fn get_files<ConfigurationParser>(
env: Env,
globs: Vec<String>, globs: Vec<String>,
parse_configurations: ConfigurationParser, parse_configurations: ConfigurationParser,
file_data: Option<&[(PathBuf, String)]>, file_data: Option<&[(PathBuf, String)]>,
) -> napi::Result<NxWorkspaceFiles, WorkspaceErrors> ) -> napi::Result<Option<Object>>
where where
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, String>>, ConfigurationParser: Fn(Vec<String>) -> napi::Result<Promise<HashMap<String, String>>>,
{ {
let Some(file_data) = file_data else { let Some(file_data) = file_data else {
return Ok(Default::default()); return Ok(Default::default());
}; };
trace!("{globs:?}"); trace!("{globs:?}");
let root_map = transform_root_map( let file_data = file_data.to_vec();
config_files::get_project_configurations(globs, Some(file_data), parse_configurations) let promise =
.map_err(|e| InternalWorkspaceErrors::ParseError(e.to_string()))?, config_files::get_project_configurations(globs, Some(&file_data), parse_configurations)?;
);
trace!(?root_map); let result = env.spawn_future(async move {
let parsed_graph_nodes = promise.await?;
let file_locations = file_data let root_map = transform_root_map(
.into_par_iter() parsed_graph_nodes
.map(|(file_path, hash)| { );
let mut parent = file_path.parent().unwrap_or_else(|| Path::new("."));
while root_map.get(parent).is_none() && parent != Path::new(".") { trace!(?root_map);
parent = parent.parent().unwrap_or_else(|| Path::new("."));
}
let file_data = FileData { let file_locations = file_data
file: file_path.to_normalized_string(), .into_par_iter()
hash: hash.clone(), .map(|(file_path, hash)| {
}; let mut parent = file_path.parent().unwrap_or_else(|| Path::new("."));
match root_map.get(parent) { while root_map.get(parent).is_none() && parent != Path::new(".") {
Some(project_name) => (FileLocation::Project(project_name.into()), file_data), parent = parent.parent().unwrap_or_else(|| Path::new("."));
None => (FileLocation::Global, file_data),
}
})
.collect::<Vec<(FileLocation, FileData)>>();
let mut project_file_map: HashMap<String, Vec<FileData>> = HashMap::with_capacity(
file_locations
.iter()
.filter(|&f| f.0 != FileLocation::Global)
.count(),
);
let mut global_files: Vec<FileData> = Vec::with_capacity(
file_locations
.iter()
.filter(|&f| f.0 == FileLocation::Global)
.count(),
);
for (file_location, file_data) in file_locations {
match file_location {
FileLocation::Global => global_files.push(file_data),
FileLocation::Project(project_name) => match project_file_map.get_mut(&project_name) {
None => {
project_file_map.insert(project_name.clone(), vec![file_data]);
} }
Some(project_files) => project_files.push(file_data),
},
}
}
Ok(NxWorkspaceFiles { let file_data = FileData {
project_file_map, file: file_path.to_normalized_string(),
global_files, hash: hash.clone(),
}) };
match root_map.get(parent) {
Some(project_name) => (FileLocation::Project(project_name.into()), file_data),
None => (FileLocation::Global, file_data),
}
})
.collect::<Vec<(FileLocation, FileData)>>();
let mut project_file_map: HashMap<String, Vec<FileData>> = HashMap::with_capacity(
file_locations
.iter()
.filter(|&f| f.0 != FileLocation::Global)
.count(),
);
let mut global_files: Vec<FileData> = Vec::with_capacity(
file_locations
.iter()
.filter(|&f| f.0 == FileLocation::Global)
.count(),
);
for (file_location, file_data) in file_locations {
match file_location {
FileLocation::Global => global_files.push(file_data),
FileLocation::Project(project_name) => match project_file_map.get_mut(&project_name) {
None => {
project_file_map.insert(project_name.clone(), vec![file_data]);
}
Some(project_files) => project_files.push(file_data),
},
}
}
Ok(NxWorkspaceFiles {
project_file_map,
global_files,
})
})?;
Ok(Some(result))
} }
fn transform_root_map(root_map: HashMap<String, String>) -> hashbrown::HashMap<PathBuf, String> { fn transform_root_map(root_map: HashMap<String, String>) -> hashbrown::HashMap<PathBuf, String> {

View File

@ -3,22 +3,18 @@ import * as memfs from 'memfs';
import '../../../internal-testing-utils/mock-fs'; import '../../../internal-testing-utils/mock-fs';
import { CreatePackageJsonProjectsNextToProjectJson } from './package-json-next-to-project-json'; import { CreatePackageJsonProjectsNextToProjectJson } from './package-json-next-to-project-json';
import { import { CreateNodesContext } from '../../../utils/nx-plugin';
CreateNodesContext,
CreateNodesFunction,
} from '../../../utils/nx-plugin';
const { createNodes } = CreatePackageJsonProjectsNextToProjectJson; const { createNodes } = CreatePackageJsonProjectsNextToProjectJson;
describe('nx project.json plugin', () => { describe('nx project.json plugin', () => {
let context: CreateNodesContext; let context: CreateNodesContext;
let createNodesFunction: CreateNodesFunction; let createNodesFunction = createNodes[1];
beforeEach(() => { beforeEach(() => {
context = { context = {
nxJsonConfiguration: {}, nxJsonConfiguration: {},
workspaceRoot: '/root', workspaceRoot: '/root',
}; };
createNodesFunction = createNodes[1];
}); });
it('should build projects from project.json', () => { it('should build projects from project.json', () => {

View File

@ -3,9 +3,9 @@ import { dirname, join } from 'node:path';
import { ProjectConfiguration } from '../../../config/workspace-json-project-json'; import { ProjectConfiguration } from '../../../config/workspace-json-project-json';
import { toProjectName } from '../../../config/workspaces'; import { toProjectName } from '../../../config/workspaces';
import { readJsonFile } from '../../../utils/fileutils'; import { readJsonFile } from '../../../utils/fileutils';
import { NxPluginV2 } from '../../../utils/nx-plugin'; import { CreateNodes, NxPluginV2 } from '../../../utils/nx-plugin';
export const CreateProjectJsonProjectsPlugin: NxPluginV2 = { export const CreateProjectJsonProjectsPlugin: NxPluginV2<void, CreateNodes> = {
name: 'nx-core-build-project-json-nodes', name: 'nx-core-build-project-json-nodes',
createNodes: [ createNodes: [
'{project.json,**/project.json}', '{project.json,**/project.json}',

View File

@ -22,10 +22,8 @@ import {
import { getRootTsConfigPath } from '../plugins/js/utils/typescript'; import { getRootTsConfigPath } from '../plugins/js/utils/typescript';
import { import {
FileMap, FileMap,
ProjectFileMap,
ProjectGraph, ProjectGraph,
ProjectGraphExternalNode, ProjectGraphExternalNode,
ProjectGraphProcessorContext,
} from '../config/project-graph'; } from '../config/project-graph';
import { readJsonFile } from '../utils/fileutils'; import { readJsonFile } from '../utils/fileutils';
import { NxJsonConfiguration } from '../config/nx-json'; import { NxJsonConfiguration } from '../config/nx-json';
@ -34,6 +32,7 @@ import { ProjectConfiguration } from '../config/workspace-json-project-json';
import { readNxJson } from '../config/configuration'; import { readNxJson } from '../config/configuration';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { PackageJson } from '../utils/package-json'; import { PackageJson } from '../utils/package-json';
import { getNxRequirePaths } from '../utils/installation-directory';
let storedFileMap: FileMap | null = null; let storedFileMap: FileMap | null = null;
let storedAllWorkspaceFiles: FileData[] | null = null; let storedAllWorkspaceFiles: FileData[] | null = null;
@ -231,7 +230,12 @@ async function updateProjectGraphWithPlugins(
context: CreateDependenciesContext, context: CreateDependenciesContext,
initProjectGraph: ProjectGraph initProjectGraph: ProjectGraph
) { ) {
const plugins = await loadNxPlugins(context.nxJsonConfiguration?.plugins); const plugins = await loadNxPlugins(
context.nxJsonConfiguration?.plugins,
getNxRequirePaths(),
context.workspaceRoot,
context.projects
);
let graph = initProjectGraph; let graph = initProjectGraph;
for (const { plugin } of plugins) { for (const { plugin } of plugins) {
try { try {

View File

@ -14,7 +14,10 @@ import {
} from './project-graph'; } from './project-graph';
import { toOldFormat } from '../adapter/angular-json'; import { toOldFormat } from '../adapter/angular-json';
import { getIgnoreObject } from '../utils/ignore'; import { getIgnoreObject } from '../utils/ignore';
import { retrieveProjectConfigurationsSync } from './utils/retrieve-workspace-files'; import { retrieveProjectConfigurationPathsWithoutPluginInference } from './utils/retrieve-workspace-files';
import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './utils/project-configuration-utils';
import { NxJsonConfiguration } from '../config/nx-json';
import { getDefaultPluginsSync } from '../utils/nx-plugin.deprecated';
export interface Change { export interface Change {
type: string; type: string;
@ -139,7 +142,7 @@ export function readWorkspaceConfig(opts: {
} catch { } catch {
configuration = { configuration = {
version: 2, version: 2,
projects: retrieveProjectConfigurationsSync(root, nxJson).projectNodes, projects: getProjectsSyncNoInference(root, nxJson).projects,
}; };
} }
if (opts.format === 'angularCli') { if (opts.format === 'angularCli') {
@ -164,3 +167,14 @@ export function readPackageJson(): any {
export { FileData }; export { FileData };
// TODO(17): Remove these exports // TODO(17): Remove these exports
export { readNxJson, workspaceLayout } from '../config/configuration'; export { readNxJson, workspaceLayout } from '../config/configuration';
function getProjectsSyncNoInference(root: string, nxJson: NxJsonConfiguration) {
const paths = retrieveProjectConfigurationPathsWithoutPluginInference(root);
return buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson,
paths,
getDefaultPluginsSync(root),
root,
true
);
}

View File

@ -5,8 +5,9 @@ import {
TargetConfiguration, TargetConfiguration,
} from '../../config/workspace-json-project-json'; } from '../../config/workspace-json-project-json';
import { NX_PREFIX } from '../../utils/logger'; import { NX_PREFIX } from '../../utils/logger';
import { LoadedNxPlugin } from '../../utils/nx-plugin'; import { CreateNodesResult, LoadedNxPlugin } from '../../utils/nx-plugin';
import { workspaceRoot } from '../../utils/workspace-root'; import { workspaceRoot } from '../../utils/workspace-root';
import { output } from '../../utils/output';
import minimatch = require('minimatch'); import minimatch = require('minimatch');
@ -88,18 +89,48 @@ export function mergeProjectConfigurationIntoRootMap(
); );
} }
type ConfigurationResult = {
projects: Record<string, ProjectConfiguration>;
externalNodes: Record<string, ProjectGraphExternalNode>;
rootMap: Record<string, string>;
};
/**
* ** DO NOT USE ** - Please use without the `skipAsync` parameter.
* @deprecated
* @todo(@agentender): Remove in Nx 18 alongside the removal of its usage.
*/
export function buildProjectsConfigurationsFromProjectPathsAndPlugins( export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson: NxJsonConfiguration, nxJson: NxJsonConfiguration,
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
plugins: LoadedNxPlugin[], plugins: LoadedNxPlugin[],
root: string = workspaceRoot root: string,
): { skipAsync: true
projects: Record<string, ProjectConfiguration>; ): ConfigurationResult;
externalNodes: Record<string, ProjectGraphExternalNode>;
rootMap: Record<string, string>; /**
} { * Transforms a list of project paths into a map of project configurations.
const projectRootMap: Map<string, ProjectConfiguration> = new Map(); *
const externalNodes: Record<string, ProjectGraphExternalNode> = {}; * @param nxJson The NxJson configuration
* @param projectFiles A list of files identified as projects
* @param plugins The plugins that should be used to infer project configuration
* @param root The workspace root
*/
export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson: NxJsonConfiguration,
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
plugins: LoadedNxPlugin[],
root: string,
skipAsync?: false
): Promise<ConfigurationResult>;
export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson: NxJsonConfiguration,
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
plugins: LoadedNxPlugin[],
root: string = workspaceRoot,
skipAsync: boolean = false
): ConfigurationResult | Promise<ConfigurationResult> {
const results: Array<CreateNodesResult | Promise<CreateNodesResult>> = [];
// We iterate over plugins first - this ensures that plugins specified first take precedence. // We iterate over plugins first - this ensures that plugins specified first take precedence.
for (const { plugin, options } of plugins) { for (const { plugin, options } of plugins) {
@ -109,31 +140,53 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
} }
for (const file of projectFiles) { for (const file of projectFiles) {
if (minimatch(file, pattern, { dot: true })) { if (minimatch(file, pattern, { dot: true })) {
const { projects: projectNodes, externalNodes: pluginExternalNodes } = results.push(
createNodes(file, options, { createNodes(file, options, {
nxJsonConfiguration: nxJson, nxJsonConfiguration: nxJson,
workspaceRoot: root, workspaceRoot: root,
}); })
for (const node in projectNodes) { );
mergeProjectConfigurationIntoRootMap(projectRootMap, {
// If root is specified in config, that will overwrite this.
// Specifying it here though allows plugins to return something like
// {
// projects: {
// [root]: { targets: buildTargetsFromFile(f) }
// }
// }
// Otherwise, the root would have to be specified in the config as well
// which would be a bit redundant.
root: node,
...projectNodes[node],
});
}
Object.assign(externalNodes, pluginExternalNodes);
} }
} }
} }
return skipAsync
? combineSyncConfigurationResults(results)
: combineAsyncConfigurationResults(results);
}
function combineSyncConfigurationResults(
results: (CreateNodesResult | Promise<CreateNodesResult>)[]
): ConfigurationResult {
const projectRootMap: Map<string, ProjectConfiguration> = new Map();
const externalNodes: Record<string, ProjectGraphExternalNode> = {};
let warned = false;
for (const result of results) {
if (typeof result === 'object' && 'then' in result) {
if (!warned) {
output.warn({
title: 'One or more plugins in this workspace are async.',
bodyLines: [
'Configuration from these plugins will not be visible to readWorkspaceConfig or readWorkspaceConfiguration. If you are using these methods, consider reading project info from the graph with createProjectGraphAsync instead.',
'If you are not using one of these methods, please open an issue at http://github.com/nrwl/nx',
],
});
warned = true;
}
continue;
}
const { projects: projectNodes, externalNodes: pluginExternalNodes } =
result;
for (const node in projectNodes) {
mergeProjectConfigurationIntoRootMap(projectRootMap, {
root: node,
...projectNodes[node],
});
}
Object.assign(externalNodes, pluginExternalNodes);
}
const rootMap = createRootMap(projectRootMap); const rootMap = createRootMap(projectRootMap);
return { return {
@ -143,6 +196,12 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
}; };
} }
function combineAsyncConfigurationResults(
results: Array<CreateNodesResult | Promise<CreateNodesResult>>
): Promise<ConfigurationResult> {
return Promise.all(results).then((r) => combineSyncConfigurationResults(r));
}
export function readProjectConfigurationsFromRootMap( export function readProjectConfigurationsFromRootMap(
projectRootMap: Map<string, ProjectConfiguration> projectRootMap: Map<string, ProjectConfiguration>
) { ) {

View File

@ -21,13 +21,7 @@ import {
getNxPackageJsonWorkspacesPlugin, getNxPackageJsonWorkspacesPlugin,
} from '../../../plugins/package-json-workspaces'; } from '../../../plugins/package-json-workspaces';
import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project-configuration-utils'; import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project-configuration-utils';
import { import { LoadedNxPlugin, loadNxPlugins } from '../../utils/nx-plugin';
LoadedNxPlugin,
loadNxPlugins,
loadNxPluginsSync,
NxPluginV2,
unregisterPluginTSTranspiler,
} from '../../utils/nx-plugin';
import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json'; import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json';
import { import {
globWithWorkspaceContext, globWithWorkspaceContext,
@ -63,11 +57,11 @@ export async function retrieveWorkspaceFiles(
let projects: Record<string, ProjectConfiguration>; let projects: Record<string, ProjectConfiguration>;
let externalNodes: Record<string, ProjectGraphExternalNode>; let externalNodes: Record<string, ProjectGraphExternalNode>;
const { projectFileMap, globalFiles } = getNxWorkspaceFilesFromContext( const { projectFileMap, globalFiles } = (await getNxWorkspaceFilesFromContext(
workspaceRoot, workspaceRoot,
globs, globs,
(configs: string[]) => { async (configs: string[]) => {
const projectConfigurations = createProjectConfigurations( const projectConfigurations = await createProjectConfigurations(
workspaceRoot, workspaceRoot,
nxJson, nxJson,
configs, configs,
@ -79,7 +73,7 @@ export async function retrieveWorkspaceFiles(
externalNodes = projectConfigurations.externalNodes; externalNodes = projectConfigurations.externalNodes;
return projectConfigurations.rootMap; return projectConfigurations.rootMap;
} }
) as NxWorkspaceFiles; )) as NxWorkspaceFiles;
performance.mark('get-workspace-files:end'); performance.mark('get-workspace-files:end');
performance.measure( performance.measure(
'get-workspace-files', 'get-workspace-files',
@ -148,49 +142,30 @@ export async function retrieveProjectConfigurationsWithAngularProjects(
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs); return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs);
} }
/**
* @deprecated Use {@link retrieveProjectConfigurations} instead.
*/
export function retrieveProjectConfigurationsSync(
workspaceRoot: string,
nxJson: NxJsonConfiguration
): {
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
} {
const plugins = loadNxPluginsSync(
nxJson?.plugins ?? [],
getNxRequirePaths(workspaceRoot),
workspaceRoot
);
const globs = configurationGlobs(workspaceRoot, plugins);
return _retrieveProjectConfigurations(workspaceRoot, nxJson, plugins, globs);
}
function _retrieveProjectConfigurations( function _retrieveProjectConfigurations(
workspaceRoot: string, workspaceRoot: string,
nxJson: NxJsonConfiguration, nxJson: NxJsonConfiguration,
plugins: LoadedNxPlugin[], plugins: LoadedNxPlugin[],
globs: string[] globs: string[]
): { ): Promise<{
externalNodes: Record<string, ProjectGraphExternalNode>; externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>; projectNodes: Record<string, ProjectConfiguration>;
} { }> {
let result: { let result: {
externalNodes: Record<string, ProjectGraphExternalNode>; externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>; projectNodes: Record<string, ProjectConfiguration>;
}; };
getProjectConfigurationsFromContext( return getProjectConfigurationsFromContext(
workspaceRoot, workspaceRoot,
globs, globs,
(configs: string[]) => { async (configs: string[]) => {
const { projects, externalNodes, rootMap } = createProjectConfigurations( const { projects, externalNodes, rootMap } =
workspaceRoot, await createProjectConfigurations(
nxJson, workspaceRoot,
configs, nxJson,
plugins configs,
); plugins
);
result = { result = {
projectNodes: projects, projectNodes: projects,
@ -199,8 +174,7 @@ function _retrieveProjectConfigurations(
return rootMap; return rootMap;
} }
); ).then(() => result);
return result;
} }
export async function retrieveProjectConfigurationPaths( export async function retrieveProjectConfigurationPaths(
@ -226,9 +200,9 @@ const projectsWithoutPluginCache = new Map<
>(); >();
// TODO: This function is called way too often, it should be optimized without this cache // TODO: This function is called way too often, it should be optimized without this cache
export function retrieveProjectConfigurationsWithoutPluginInference( export async function retrieveProjectConfigurationsWithoutPluginInference(
root: string root: string
): Record<string, ProjectConfiguration> { ): Promise<Record<string, ProjectConfiguration>> {
const nxJson = readNxJson(root); const nxJson = readNxJson(root);
const projectGlobPatterns = configurationGlobsWithoutPlugins(root); const projectGlobPatterns = configurationGlobsWithoutPlugins(root);
const cacheKey = root + ',' + projectGlobPatterns.join(','); const cacheKey = root + ',' + projectGlobPatterns.join(',');
@ -238,11 +212,11 @@ export function retrieveProjectConfigurationsWithoutPluginInference(
} }
let projects: Record<string, ProjectConfiguration>; let projects: Record<string, ProjectConfiguration>;
getProjectConfigurationsFromContext( await getProjectConfigurationsFromContext(
root, root,
projectGlobPatterns, projectGlobPatterns,
(configs: string[]) => { async (configs: string[]) => {
const projectConfigurations = createProjectConfigurations( const projectConfigurations = await createProjectConfigurations(
root, root,
nxJson, nxJson,
configs, configs,
@ -280,20 +254,20 @@ function buildAllWorkspaceFiles(
return fileData; return fileData;
} }
export function createProjectConfigurations( export async function createProjectConfigurations(
workspaceRoot: string, workspaceRoot: string,
nxJson: NxJsonConfiguration, nxJson: NxJsonConfiguration,
configFiles: string[], configFiles: string[],
plugins: LoadedNxPlugin[] plugins: LoadedNxPlugin[]
): { ): Promise<{
projects: Record<string, ProjectConfiguration>; projects: Record<string, ProjectConfiguration>;
externalNodes: Record<string, ProjectGraphExternalNode>; externalNodes: Record<string, ProjectGraphExternalNode>;
rootMap: Record<string, string>; rootMap: Record<string, string>;
} { }> {
performance.mark('build-project-configs:start'); performance.mark('build-project-configs:start');
const { projects, externalNodes, rootMap } = const { projects, externalNodes, rootMap } =
buildProjectsConfigurationsFromProjectPathsAndPlugins( await buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson, nxJson,
configFiles, configFiles,
plugins, plugins,

View File

@ -16,10 +16,19 @@ import {
import { readNxJson } from '../../config/configuration'; import { readNxJson } from '../../config/configuration';
import { isAsyncIterator } from '../../utils/async-iterator'; import { isAsyncIterator } from '../../utils/async-iterator';
import { getExecutorInformation } from '../../command-line/run/executor-utils'; import { getExecutorInformation } from '../../command-line/run/executor-utils';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
function getBatchExecutor(executorName: string) { function getBatchExecutor(
executorName: string,
projects: Record<string, ProjectConfiguration>
) {
const [nodeModule, exportName] = executorName.split(':'); const [nodeModule, exportName] = executorName.split(':');
return getExecutorInformation(nodeModule, exportName, workspaceRoot); return getExecutorInformation(
nodeModule,
exportName,
workspaceRoot,
projects
);
} }
async function runTasks( async function runTasks(
@ -32,7 +41,10 @@ async function runTasks(
const projectsConfigurations = const projectsConfigurations =
readProjectsConfigurationFromProjectGraph(projectGraph); readProjectsConfigurationFromProjectGraph(projectGraph);
const nxJsonConfiguration = readNxJson(); const nxJsonConfiguration = readNxJson();
const batchExecutor = getBatchExecutor(executorName); const batchExecutor = getBatchExecutor(
executorName,
projectsConfigurations.projects
);
const tasks = Object.values(batchTaskGraph.tasks); const tasks = Object.values(batchTaskGraph.tasks);
const context: ExecutorContext = { const context: ExecutorContext = {
root: workspaceRoot, root: workspaceRoot,

View File

@ -10,6 +10,7 @@ import { serializeOverridesIntoCommandLine } from '../utils/serialize-overrides-
import { splitByColons } from '../utils/split-target'; import { splitByColons } from '../utils/split-target';
import { getExecutorInformation } from '../command-line/run/executor-utils'; import { getExecutorInformation } from '../command-line/run/executor-utils';
import { CustomHasher } from '../config/misc-interfaces'; import { CustomHasher } from '../config/misc-interfaces';
import { readProjectsConfigurationFromProjectGraph } from '../project-graph/project-graph';
export function getCommandAsString(execCommand: string, task: Task) { export function getCommandAsString(execCommand: string, task: Task) {
const args = getPrintableCommandArgsForTask(task); const args = getPrintableCommandArgsForTask(task);
@ -262,7 +263,12 @@ export async function getExecutorForTask(
const executor = await getExecutorNameForTask(task, projectGraph); const executor = await getExecutorNameForTask(task, projectGraph);
const [nodeModule, executorName] = executor.split(':'); const [nodeModule, executorName] = executor.split(':');
return getExecutorInformation(nodeModule, executorName, workspaceRoot); return getExecutorInformation(
nodeModule,
executorName,
workspaceRoot,
readProjectsConfigurationFromProjectGraph(projectGraph).projects
);
} }
export async function getCustomHasher( export async function getCustomHasher(

View File

@ -200,8 +200,8 @@ describe.each([
iterations iterations
); );
const directTime = time(() => minimatch.match(items, pattern), iterations); const directTime = time(() => minimatch.match(items, pattern), iterations);
// Using minimatch directly takes at least twice as long than using the cache. // Using minimatch directly is slower than using the cache.
expect(directTime / cacheTime).toBeGreaterThan(2); expect(directTime / cacheTime).toBeGreaterThan(1);
}); });
it(`should be comparable to using minimatch a single time (${pattern})`, () => { it(`should be comparable to using minimatch a single time (${pattern})`, () => {

View File

@ -1,5 +1,25 @@
import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces';
import {
NxAngularJsonPlugin,
shouldMergeAngularProjects,
} from '../adapter/angular-json';
import { NxJsonConfiguration, PluginConfiguration } from '../config/nx-json';
import { ProjectGraphProcessor } from '../config/project-graph'; import { ProjectGraphProcessor } from '../config/project-graph';
import { TargetConfiguration } from '../config/workspace-json-project-json'; import {
ProjectConfiguration,
TargetConfiguration,
} from '../config/workspace-json-project-json';
import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json';
import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files';
import { getNxRequirePaths } from './installation-directory';
import {
ensurePluginIsV2,
getPluginPathAndName,
LoadedNxPlugin,
nxPluginCache,
NxPluginV2,
} from './nx-plugin';
import { workspaceRoot } from './workspace-root';
/** /**
* @deprecated Add targets to the projects in a {@link CreateNodes} function instead. This will be removed in Nx 18 * @deprecated Add targets to the projects in a {@link CreateNodes} function instead. This will be removed in Nx 18
@ -30,3 +50,17 @@ export type NxPluginV1 = {
*/ */
projectFilePatterns?: string[]; projectFilePatterns?: string[];
}; };
/**
* @todo(@agentender) v18: Remove this fn when we remove readWorkspaceConfig
*/
export function getDefaultPluginsSync(root: string): LoadedNxPlugin[] {
const plugins: NxPluginV2[] = [require('../plugins/js')];
if (shouldMergeAngularProjects(root, false)) {
plugins.push(require('../adapter/angular-json').NxAngularJsonPlugin);
}
return plugins.map((p) => ({
plugin: p,
}));
}

View File

@ -27,20 +27,26 @@ import { normalizePath } from './path';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
import { getNxRequirePaths } from './installation-directory'; import { getNxRequirePaths } from './installation-directory';
import { readTsConfig } from '../plugins/js/utils/typescript'; import { readTsConfig } from '../plugins/js/utils/typescript';
import { NxJsonConfiguration, PluginConfiguration } from '../config/nx-json'; import {
NxJsonConfiguration,
PluginConfiguration,
readNxJson,
} from '../config/nx-json';
import type * as ts from 'typescript'; import type * as ts from 'typescript';
import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files';
import { NxPluginV1 } from './nx-plugin.deprecated'; import { NxPluginV1 } from './nx-plugin.deprecated';
import { RawProjectGraphDependency } from '../project-graph/project-graph-builder'; import { RawProjectGraphDependency } from '../project-graph/project-graph-builder';
import { combineGlobPatterns } from './globs'; import { combineGlobPatterns } from './globs';
import { import { shouldMergeAngularProjects } from '../adapter/angular-json';
NxAngularJsonPlugin,
shouldMergeAngularProjects,
} from '../adapter/angular-json';
import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces'; import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces';
import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; import { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json';
import { CreatePackageJsonProjectsNextToProjectJson } from '../plugins/project-json/build-nodes/package-json-next-to-project-json'; import { CreatePackageJsonProjectsNextToProjectJson } from '../plugins/project-json/build-nodes/package-json-next-to-project-json';
import {
mergeProjectConfigurationIntoRootMap,
readProjectConfigurationsFromRootMap,
} from '../project-graph/utils/project-configuration-utils';
import { globWithWorkspaceContext } from './workspace-context';
import { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files';
/** /**
* Context for {@link CreateNodesFunction} * Context for {@link CreateNodesFunction}
@ -58,7 +64,19 @@ export type CreateNodesFunction<T = unknown> = (
projectConfigurationFile: string, projectConfigurationFile: string,
options: T | undefined, options: T | undefined,
context: CreateNodesContext context: CreateNodesContext
) => { ) => CreateNodesResult;
/**
* A function which parses a configuration file into a set of nodes.
* Used for creating nodes for the {@link ProjectGraph}
*/
export type CreateNodesFunctionAsync<T = unknown> = (
projectConfigurationFile: string,
options: T | undefined,
context: CreateNodesContext
) => Promise<CreateNodesResult>;
export interface CreateNodesResult {
/** /**
* A map of project root -> project configuration * A map of project root -> project configuration
*/ */
@ -68,7 +86,7 @@ export type CreateNodesFunction<T = unknown> = (
* A map of external node name -> external node. External nodes do not have a root, so the key is their name. * A map of external node name -> external node. External nodes do not have a root, so the key is their name.
*/ */
externalNodes?: Record<string, ProjectGraphExternalNode>; externalNodes?: Record<string, ProjectGraphExternalNode>;
}; }
/** /**
* A pair of file patterns and {@link CreateNodesFunction} * A pair of file patterns and {@link CreateNodesFunction}
@ -78,6 +96,11 @@ export type CreateNodes<T = unknown> = readonly [
createNodesFunction: CreateNodesFunction<T> createNodesFunction: CreateNodesFunction<T>
]; ];
export type CreateNodesAsync<T = unknown> = readonly [
projectFilePattern: string,
createNodesFunction: CreateNodesFunctionAsync<T>
];
/** /**
* Context for {@link CreateDependencies} * Context for {@link CreateDependencies}
*/ */
@ -122,20 +145,25 @@ export type CreateDependencies<T = unknown> = (
/** /**
* A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph} * A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph}
*/ */
export type NxPluginV2<T = unknown> = { export type NxPluginV2<
TOptions = unknown,
TCreateNodes extends CreateNodes<TOptions> | CreateNodesAsync<TOptions> =
| CreateNodes<TOptions>
| CreateNodesAsync<TOptions>
> = {
name: string; name: string;
/** /**
* Provides a file pattern and function that retrieves configuration info from * Provides a file pattern and function that retrieves configuration info from
* those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile } * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile }
*/ */
createNodes?: CreateNodes<T>; createNodes?: TCreateNodes;
// 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? // 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} * Provides a function to analyze files to create dependencies for the {@link ProjectGraph}
*/ */
createDependencies?: CreateDependencies<T>; createDependencies?: CreateDependencies<TOptions>;
}; };
export * from './nx-plugin.deprecated'; export * from './nx-plugin.deprecated';
@ -154,11 +182,12 @@ export type LoadedNxPlugin = {
// holding resolved nx plugin objects. // holding resolved nx plugin objects.
// Allows loadNxPlugins to be called multiple times w/o // Allows loadNxPlugins to be called multiple times w/o
// executing resolution mulitple times. // executing resolution mulitple times.
let nxPluginCache: Map<string, LoadedNxPlugin['plugin']> = new Map(); export const nxPluginCache: Map<string, LoadedNxPlugin['plugin']> = new Map();
function getPluginPathAndName( export function getPluginPathAndName(
moduleName: string, moduleName: string,
paths: string[], paths: string[],
projects: Record<string, ProjectConfiguration>,
root: string root: string
) { ) {
let pluginPath: string; let pluginPath: string;
@ -168,7 +197,12 @@ function getPluginPathAndName(
}); });
} catch (e) { } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') { if (e.code === 'MODULE_NOT_FOUND') {
const plugin = resolveLocalNxPlugin(moduleName, root); const plugin = resolveLocalNxPlugin(
moduleName,
readNxJson(root),
projects,
root
);
if (plugin) { if (plugin) {
const main = readPluginMainFromProjectConfiguration( const main = readPluginMainFromProjectConfiguration(
plugin.projectConfig plugin.projectConfig
@ -203,6 +237,7 @@ function getPluginPathAndName(
export async function loadNxPluginAsync( export async function loadNxPluginAsync(
pluginConfiguration: PluginConfiguration, pluginConfiguration: PluginConfiguration,
paths: string[], paths: string[],
projects: Record<string, ProjectConfiguration>,
root: string root: string
): Promise<LoadedNxPlugin> { ): Promise<LoadedNxPlugin> {
const { plugin: moduleName, options } = const { plugin: moduleName, options } =
@ -214,7 +249,12 @@ export async function loadNxPluginAsync(
return { plugin: pluginModule, options }; return { plugin: pluginModule, options };
} }
let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root); let { pluginPath, name } = await getPluginPathAndName(
moduleName,
paths,
projects,
root
);
const plugin = ensurePluginIsV2( const plugin = ensurePluginIsV2(
(await import(pluginPath)) as LoadedNxPlugin['plugin'] (await import(pluginPath)) as LoadedNxPlugin['plugin']
); );
@ -223,79 +263,21 @@ export async function loadNxPluginAsync(
return { plugin, options }; return { plugin, options };
} }
function loadNxPluginSync(
pluginConfiguration: PluginConfiguration,
paths: string[],
root: string
): LoadedNxPlugin {
const { plugin: moduleName, options } =
typeof pluginConfiguration === 'object'
? pluginConfiguration
: { plugin: pluginConfiguration, options: undefined };
let pluginModule = nxPluginCache.get(moduleName);
if (pluginModule) {
return { plugin: pluginModule, options };
}
let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root);
const plugin = ensurePluginIsV2(
require(pluginPath)
) as LoadedNxPlugin['plugin'];
plugin.name ??= name;
nxPluginCache.set(moduleName, plugin);
return { plugin, options };
}
/**
* @deprecated Use loadNxPlugins instead.
*/
export function loadNxPluginsSync(
plugins: NxJsonConfiguration['plugins'],
paths = getNxRequirePaths(),
root = workspaceRoot
): 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: LoadedNxPlugin[] = [...getDefaultPluginsSync(root)];
if (shouldMergeAngularProjects(root, false)) {
result.push({ plugin: NxAngularJsonPlugin, options: undefined });
}
plugins ??= [];
for (const plugin of plugins) {
try {
result.push(loadNxPluginSync(plugin, paths, root));
} catch (e) {
if (e.code === 'ERR_REQUIRE_ESM') {
throw new Error(
`Unable to load "${plugin}". Plugins cannot be ESM modules. They must be CommonJS modules. Follow the issue on github: https://github.com/nrwl/nx/issues/15682`
);
}
throw e;
}
}
// We push the nx core node plugins onto the end, s.t. it overwrites any other plugins
result.push(
{ plugin: getNxPackageJsonWorkspacesPlugin(root) },
{ plugin: CreateProjectJsonProjectsPlugin }
);
return result;
}
export async function loadNxPlugins( export async function loadNxPlugins(
plugins: PluginConfiguration[], plugins: PluginConfiguration[],
paths = getNxRequirePaths(), paths = getNxRequirePaths(),
root = workspaceRoot root = workspaceRoot,
projects?: Record<string, ProjectConfiguration>
): Promise<LoadedNxPlugin[]> { ): Promise<LoadedNxPlugin[]> {
const result: LoadedNxPlugin[] = [...(await getDefaultPlugins(root))]; const result: LoadedNxPlugin[] = [...(await getDefaultPlugins(root))];
// When loading plugins for `createNodes`, we don't know what projects exist yet.
projects ??= await retrieveProjectConfigurationsWithoutPluginInference(root);
plugins ??= []; plugins ??= [];
for (const plugin of plugins) { for (const plugin of plugins) {
result.push(await loadNxPluginAsync(plugin, paths, root)); result.push(await loadNxPluginAsync(plugin, paths, projects, root));
} }
// We push the nx core node plugins onto the end, s.t. it overwrites any other plugins // We push the nx core node plugins onto the end, s.t. it overwrites any other plugins
@ -307,7 +289,7 @@ export async function loadNxPlugins(
return result; return result;
} }
function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 { export function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 {
if (isNxPluginV2(plugin)) { if (isNxPluginV2(plugin)) {
return plugin; return plugin;
} }
@ -344,6 +326,7 @@ export function isNxPluginV1(plugin: NxPlugin): plugin is NxPluginV1 {
export function readPluginPackageJson( export function readPluginPackageJson(
pluginName: string, pluginName: string,
projects: Record<string, ProjectConfiguration>,
paths = getNxRequirePaths() paths = getNxRequirePaths()
): { ): {
path: string; path: string;
@ -357,7 +340,12 @@ export function readPluginPackageJson(
}; };
} catch (e) { } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') { if (e.code === 'MODULE_NOT_FOUND') {
const localPluginPath = resolveLocalNxPlugin(pluginName); const nxJson = readNxJson();
const localPluginPath = resolveLocalNxPlugin(
pluginName,
nxJson,
projects
);
if (localPluginPath) { if (localPluginPath) {
const localPluginPackageJson = path.join( const localPluginPackageJson = path.join(
localPluginPath.path, localPluginPath.path,
@ -382,15 +370,23 @@ const localPluginCache: Record<
string, string,
{ path: string; projectConfig: ProjectConfiguration } { path: string; projectConfig: ProjectConfiguration }
> = {}; > = {};
export function resolveLocalNxPlugin( export function resolveLocalNxPlugin(
importPath: string, importPath: string,
nxJsonConfiguration: NxJsonConfiguration,
projects: Record<string, ProjectConfiguration>,
root = workspaceRoot root = workspaceRoot
): { path: string; projectConfig: ProjectConfiguration } | null { ): { path: string; projectConfig: ProjectConfiguration } | null {
localPluginCache[importPath] ??= lookupLocalPlugin(importPath, root); localPluginCache[importPath] ??= lookupLocalPlugin(
importPath,
nxJsonConfiguration,
projects,
root
);
return localPluginCache[importPath]; return localPluginCache[importPath];
} }
let tsNodeAndPathsUnregisterCallback = undefined; let tsNodeAndPathsUnregisterCallback: (() => void) | undefined = undefined;
/** /**
* Register swc-node or ts-node if they are not currently registered * Register swc-node or ts-node if they are not currently registered
@ -434,8 +430,12 @@ export function unregisterPluginTSTranspiler() {
} }
} }
function lookupLocalPlugin(importPath: string, root = workspaceRoot) { function lookupLocalPlugin(
const projects = retrieveProjectConfigurationsWithoutPluginInference(root); importPath: string,
nxJsonConfiguration: NxJsonConfiguration,
projects: Record<string, ProjectConfiguration>,
root = workspaceRoot
) {
const plugin = findNxProjectForImportPath(importPath, projects, root); const plugin = findNxProjectForImportPath(importPath, projects, root);
if (!plugin) { if (!plugin) {
return null; return null;
@ -531,15 +531,4 @@ async function getDefaultPlugins(root: string): Promise<LoadedNxPlugin[]> {
})); }));
} }
function getDefaultPluginsSync(root: string): LoadedNxPlugin[] {
const plugins: NxPluginV2[] = [require('../plugins/js')];
if (shouldMergeAngularProjects(root, false)) {
plugins.push(require('../adapter/angular-json').NxAngularJsonPlugin);
}
return plugins.map((p) => ({
plugin: p,
}));
}
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

View File

@ -7,8 +7,9 @@ import { readJsonFile } from '../fileutils';
import { PackageJson, readModulePackageJson } from '../package-json'; import { PackageJson, readModulePackageJson } from '../package-json';
import { workspaceRoot } from '../workspace-root'; import { workspaceRoot } from '../workspace-root';
import { join } from 'path'; import { join } from 'path';
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json'; import { readNxJson } from '../../config/nx-json';
import { getNxRequirePaths } from '../installation-directory'; import { getNxRequirePaths } from '../installation-directory';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
export function findInstalledPlugins(): PackageJson[] { export function findInstalledPlugins(): PackageJson[] {
const packageJsonDeps = getDependenciesFromPackageJson(); const packageJsonDeps = getDependenciesFromPackageJson();
@ -64,14 +65,19 @@ function getDependenciesFromNxJson(): string[] {
} }
export async function getInstalledPluginsAndCapabilities( export async function getInstalledPluginsAndCapabilities(
workspaceRoot: string workspaceRoot: string,
projects: Record<string, ProjectConfiguration>
): Promise<Map<string, PluginCapabilities>> { ): Promise<Map<string, PluginCapabilities>> {
const plugins = findInstalledPlugins().map((p) => p.name); const plugins = findInstalledPlugins().map((p) => p.name);
const result = new Map<string, PluginCapabilities>(); const result = new Map<string, PluginCapabilities>();
for (const plugin of Array.from(plugins).sort()) { for (const plugin of Array.from(plugins).sort()) {
try { try {
const capabilities = await getPluginCapabilities(workspaceRoot, plugin); const capabilities = await getPluginCapabilities(
workspaceRoot,
plugin,
projects
);
if ( if (
capabilities && capabilities &&
(capabilities.executors || (capabilities.executors ||

View File

@ -26,6 +26,7 @@ export async function getLocalWorkspacePlugins(
const capabilities = await getPluginCapabilities( const capabilities = await getPluginCapabilities(
workspaceRoot, workspaceRoot,
packageJson.name, packageJson.name,
projectsConfiguration.projects,
includeRuntimeCapabilities includeRuntimeCapabilities
); );
if ( if (

View File

@ -13,6 +13,7 @@ import {
} from '../nx-plugin'; } from '../nx-plugin';
import { getNxRequirePaths } from '../installation-directory'; import { getNxRequirePaths } from '../installation-directory';
import { PackageJson } from '../package-json'; import { PackageJson } from '../package-json';
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
function tryGetCollection<T extends object>( function tryGetCollection<T extends object>(
packageJsonPath: string, packageJsonPath: string,
@ -34,15 +35,18 @@ function tryGetCollection<T extends object>(
export async function getPluginCapabilities( export async function getPluginCapabilities(
workspaceRoot: string, workspaceRoot: string,
pluginName: string, pluginName: string,
projects: Record<string, ProjectConfiguration>,
includeRuntimeCapabilities = false includeRuntimeCapabilities = false
): Promise<PluginCapabilities | null> { ): Promise<PluginCapabilities | null> {
try { try {
const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( const { json: packageJson, path: packageJsonPath } =
pluginName, await readPluginPackageJson(
getNxRequirePaths(workspaceRoot) pluginName,
); projects,
getNxRequirePaths(workspaceRoot)
);
const pluginModule = includeRuntimeCapabilities const pluginModule = includeRuntimeCapabilities
? await tryGetModule(packageJson, workspaceRoot) ? await tryGetModule(packageJson, workspaceRoot, projects)
: ({} as Record<string, unknown>); : ({} as Record<string, unknown>);
return { return {
name: pluginName, name: pluginName,
@ -95,7 +99,8 @@ export async function getPluginCapabilities(
async function tryGetModule( async function tryGetModule(
packageJson: PackageJson, packageJson: PackageJson,
workspaceRoot: string workspaceRoot: string,
projects: Record<string, ProjectConfiguration>
): Promise<NxPlugin | null> { ): Promise<NxPlugin | null> {
try { try {
return packageJson.generators ?? return packageJson.generators ??
@ -107,6 +112,7 @@ async function tryGetModule(
await loadNxPluginAsync( await loadNxPluginAsync(
packageJson.name, packageJson.name,
getNxRequirePaths(workspaceRoot), getNxRequirePaths(workspaceRoot),
projects,
workspaceRoot workspaceRoot
) )
).plugin ).plugin
@ -118,8 +124,15 @@ async function tryGetModule(
} }
} }
export async function listPluginCapabilities(pluginName: string) { export async function listPluginCapabilities(
const plugin = await getPluginCapabilities(workspaceRoot, pluginName); pluginName: string,
projects: Record<string, ProjectConfiguration>
) {
const plugin = await getPluginCapabilities(
workspaceRoot,
pluginName,
projects
);
if (!plugin) { if (!plugin) {
const pmc = getPackageManagerCommand(); const pmc = getPackageManagerCommand();

View File

@ -19,7 +19,7 @@ export function setupWorkspaceContext(workspaceRoot: string) {
export function getNxWorkspaceFilesFromContext( export function getNxWorkspaceFilesFromContext(
workspaceRoot: string, workspaceRoot: string,
globs: string[], globs: string[],
parseConfigurations: (files: string[]) => Record<string, string> parseConfigurations: (files: string[]) => Promise<Record<string, string>>
) { ) {
ensureContextAvailable(workspaceRoot); ensureContextAvailable(workspaceRoot);
return workspaceContext.getWorkspaceFiles(globs, parseConfigurations); return workspaceContext.getWorkspaceFiles(globs, parseConfigurations);
@ -36,7 +36,7 @@ export function globWithWorkspaceContext(
export function getProjectConfigurationsFromContext( export function getProjectConfigurationsFromContext(
workspaceRoot: string, workspaceRoot: string,
globs: string[], globs: string[],
parseConfigurations: (files: string[]) => Record<string, string> parseConfigurations: (files: string[]) => Promise<Record<string, string>>
) { ) {
ensureContextAvailable(workspaceRoot); ensureContextAvailable(workspaceRoot);
return workspaceContext.getProjectConfigurations(globs, parseConfigurations); return workspaceContext.getProjectConfigurations(globs, parseConfigurations);