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\>
Ƭ **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
@ -10,7 +10,7 @@
#### 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.
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
`Object`
| 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 |
[`CreateNodesResult`](../../devkit/documents/CreateNodesResult)

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)
#### Type parameters
| Name | Type |
| :--- | :-------- |
| `T` | `unknown` |
| Name | Type |
| :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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
| Name | Type | Description |
| :-------------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)<`T`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) |
| `createNodes?` | [`CreateNodes`](../../devkit/documents/CreateNodes)<`T`\> | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } |
| `name` | `string` | - |
| Name | Type | Description |
| :-------------------- | :----------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
| `createDependencies?` | [`CreateDependencies`](../../devkit/documents/CreateDependencies)<`TOptions`\> | Provides a function to analyze files to create dependencies for the [ProjectGraph](../../devkit/documents/ProjectGraph) |
| `createNodes?` | `TCreateNodes` | Provides a file pattern and function that retrieves configuration info from those files. e.g. { '\*_/_.csproj': buildProjectsFromCsProjFile } |
| `name` | `string` | - |

View File

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

View File

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

View File

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

View File

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

View File

@ -194,7 +194,8 @@ ${e.message || e}`);
const { schema } = getExecutorInformation(
collection,
executor,
context.root
context.root,
context.projectsConfigurations.projects
);
// 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 [nodeModule, executorName] = targetConfiguration.executor.split(':');
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.
(ws as any).readExecutor(nodeModule, executorName);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,30 +60,25 @@ describe('Workspaces', () => {
}),
});
withEnvironmentVariables(
const { projectNodes } = await withEnvironmentVariables(
{
NX_WORKSPACE_ROOT: fs.tempDir,
},
async () => {
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: {},
},
},
});
}
() => retrieveProjectConfigurations(fs.tempDir, readNxJson(fs.tempDir))
);
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 {
readCachedProjectGraph,
readProjectsConfigurationFromProjectGraph,
} from '../project-graph/project-graph';
import type { NxJsonConfiguration } from './nx-json';
import { readNxJson } from './nx-json';
import { ProjectsConfigurations } from './workspace-json-project-json';
import { retrieveProjectConfigurationsSync } from '../project-graph/utils/retrieve-workspace-files';
// TODO(v18): remove this class
/**
@ -19,9 +22,7 @@ export class Workspaces {
const nxJson = readNxJson(this.root);
return {
version: 2,
projects: retrieveProjectConfigurationsSync(this.root, nxJson)
.projectNodes,
...readProjectsConfigurationFromProjectGraph(readCachedProjectGraph()),
...nxJson,
};
}

View File

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

View File

@ -134,9 +134,9 @@ export class Watcher {
export class WorkspaceContext {
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>
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>
allFileData(): Array<FileData>
}

View File

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

View File

@ -1,7 +1,7 @@
use std::cmp::Ordering;
#[napi(object)]
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct FileData {
pub file: String,
pub hash: String,
@ -17,7 +17,7 @@ impl PartialEq<Self> for FileData {
impl PartialOrd<Self> for FileData {
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::utils::path::Normalize;
use std::collections::HashMap;
use napi::bindgen_prelude::Promise;
use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors};
use rayon::prelude::*;
@ -29,9 +30,9 @@ pub(super) fn get_project_configurations<ConfigurationParser>(
globs: Vec<String>,
files: Option<&[(PathBuf, String)]>,
parse_configurations: ConfigurationParser,
) -> napi::Result<HashMap<String, String>>
) -> napi::Result<Promise<HashMap<String, String>>>
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)?;

View File

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

View File

@ -1,6 +1,8 @@
use napi::bindgen_prelude::{Object, Promise};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use napi::Env;
use rayon::prelude::*;
use tracing::trace;
@ -18,74 +20,83 @@ pub struct NxWorkspaceFiles {
}
pub(super) fn get_files<ConfigurationParser>(
env: Env,
globs: Vec<String>,
parse_configurations: ConfigurationParser,
file_data: Option<&[(PathBuf, String)]>,
) -> napi::Result<NxWorkspaceFiles, WorkspaceErrors>
) -> napi::Result<Option<Object>>
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 {
return Ok(Default::default());
};
trace!("{globs:?}");
let root_map = transform_root_map(
config_files::get_project_configurations(globs, Some(file_data), parse_configurations)
.map_err(|e| InternalWorkspaceErrors::ParseError(e.to_string()))?,
);
let file_data = file_data.to_vec();
let promise =
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
.into_par_iter()
.map(|(file_path, hash)| {
let mut parent = file_path.parent().unwrap_or_else(|| Path::new("."));
let root_map = transform_root_map(
parsed_graph_nodes
);
while root_map.get(parent).is_none() && parent != Path::new(".") {
parent = parent.parent().unwrap_or_else(|| Path::new("."));
}
trace!(?root_map);
let file_data = FileData {
file: file_path.to_normalized_string(),
hash: hash.clone(),
};
let file_locations = file_data
.into_par_iter()
.map(|(file_path, hash)| {
let mut parent = file_path.parent().unwrap_or_else(|| Path::new("."));
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]);
while root_map.get(parent).is_none() && parent != Path::new(".") {
parent = parent.parent().unwrap_or_else(|| Path::new("."));
}
Some(project_files) => project_files.push(file_data),
},
}
}
Ok(NxWorkspaceFiles {
project_file_map,
global_files,
})
let file_data = FileData {
file: file_path.to_normalized_string(),
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> {

View File

@ -3,22 +3,18 @@ import * as memfs from 'memfs';
import '../../../internal-testing-utils/mock-fs';
import { CreatePackageJsonProjectsNextToProjectJson } from './package-json-next-to-project-json';
import {
CreateNodesContext,
CreateNodesFunction,
} from '../../../utils/nx-plugin';
import { CreateNodesContext } from '../../../utils/nx-plugin';
const { createNodes } = CreatePackageJsonProjectsNextToProjectJson;
describe('nx project.json plugin', () => {
let context: CreateNodesContext;
let createNodesFunction: CreateNodesFunction;
let createNodesFunction = createNodes[1];
beforeEach(() => {
context = {
nxJsonConfiguration: {},
workspaceRoot: '/root',
};
createNodesFunction = createNodes[1];
});
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 { toProjectName } from '../../../config/workspaces';
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',
createNodes: [
'{project.json,**/project.json}',

View File

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

View File

@ -14,7 +14,10 @@ import {
} from './project-graph';
import { toOldFormat } from '../adapter/angular-json';
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 {
type: string;
@ -139,7 +142,7 @@ export function readWorkspaceConfig(opts: {
} catch {
configuration = {
version: 2,
projects: retrieveProjectConfigurationsSync(root, nxJson).projectNodes,
projects: getProjectsSyncNoInference(root, nxJson).projects,
};
}
if (opts.format === 'angularCli') {
@ -164,3 +167,14 @@ export function readPackageJson(): any {
export { FileData };
// TODO(17): Remove these exports
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,
} from '../../config/workspace-json-project-json';
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 { output } from '../../utils/output';
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(
nxJson: NxJsonConfiguration,
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
plugins: LoadedNxPlugin[],
root: string = workspaceRoot
): {
projects: Record<string, ProjectConfiguration>;
externalNodes: Record<string, ProjectGraphExternalNode>;
rootMap: Record<string, string>;
} {
const projectRootMap: Map<string, ProjectConfiguration> = new Map();
const externalNodes: Record<string, ProjectGraphExternalNode> = {};
root: string,
skipAsync: true
): ConfigurationResult;
/**
* Transforms a list of project paths into a map of project configurations.
*
* @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.
for (const { plugin, options } of plugins) {
@ -109,31 +140,53 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
}
for (const file of projectFiles) {
if (minimatch(file, pattern, { dot: true })) {
const { projects: projectNodes, externalNodes: pluginExternalNodes } =
results.push(
createNodes(file, options, {
nxJsonConfiguration: nxJson,
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);
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(
projectRootMap: Map<string, ProjectConfiguration>
) {

View File

@ -21,13 +21,7 @@ import {
getNxPackageJsonWorkspacesPlugin,
} from '../../../plugins/package-json-workspaces';
import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project-configuration-utils';
import {
LoadedNxPlugin,
loadNxPlugins,
loadNxPluginsSync,
NxPluginV2,
unregisterPluginTSTranspiler,
} from '../../utils/nx-plugin';
import { LoadedNxPlugin, loadNxPlugins } from '../../utils/nx-plugin';
import { CreateProjectJsonProjectsPlugin } from '../../plugins/project-json/build-nodes/project-json';
import {
globWithWorkspaceContext,
@ -63,11 +57,11 @@ export async function retrieveWorkspaceFiles(
let projects: Record<string, ProjectConfiguration>;
let externalNodes: Record<string, ProjectGraphExternalNode>;
const { projectFileMap, globalFiles } = getNxWorkspaceFilesFromContext(
const { projectFileMap, globalFiles } = (await getNxWorkspaceFilesFromContext(
workspaceRoot,
globs,
(configs: string[]) => {
const projectConfigurations = createProjectConfigurations(
async (configs: string[]) => {
const projectConfigurations = await createProjectConfigurations(
workspaceRoot,
nxJson,
configs,
@ -79,7 +73,7 @@ export async function retrieveWorkspaceFiles(
externalNodes = projectConfigurations.externalNodes;
return projectConfigurations.rootMap;
}
) as NxWorkspaceFiles;
)) as NxWorkspaceFiles;
performance.mark('get-workspace-files:end');
performance.measure(
'get-workspace-files',
@ -148,49 +142,30 @@ export async function retrieveProjectConfigurationsWithAngularProjects(
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(
workspaceRoot: string,
nxJson: NxJsonConfiguration,
plugins: LoadedNxPlugin[],
globs: string[]
): {
): Promise<{
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
} {
}> {
let result: {
externalNodes: Record<string, ProjectGraphExternalNode>;
projectNodes: Record<string, ProjectConfiguration>;
};
getProjectConfigurationsFromContext(
return getProjectConfigurationsFromContext(
workspaceRoot,
globs,
(configs: string[]) => {
const { projects, externalNodes, rootMap } = createProjectConfigurations(
workspaceRoot,
nxJson,
configs,
plugins
);
async (configs: string[]) => {
const { projects, externalNodes, rootMap } =
await createProjectConfigurations(
workspaceRoot,
nxJson,
configs,
plugins
);
result = {
projectNodes: projects,
@ -199,8 +174,7 @@ function _retrieveProjectConfigurations(
return rootMap;
}
);
return result;
).then(() => result);
}
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
export function retrieveProjectConfigurationsWithoutPluginInference(
export async function retrieveProjectConfigurationsWithoutPluginInference(
root: string
): Record<string, ProjectConfiguration> {
): Promise<Record<string, ProjectConfiguration>> {
const nxJson = readNxJson(root);
const projectGlobPatterns = configurationGlobsWithoutPlugins(root);
const cacheKey = root + ',' + projectGlobPatterns.join(',');
@ -238,11 +212,11 @@ export function retrieveProjectConfigurationsWithoutPluginInference(
}
let projects: Record<string, ProjectConfiguration>;
getProjectConfigurationsFromContext(
await getProjectConfigurationsFromContext(
root,
projectGlobPatterns,
(configs: string[]) => {
const projectConfigurations = createProjectConfigurations(
async (configs: string[]) => {
const projectConfigurations = await createProjectConfigurations(
root,
nxJson,
configs,
@ -280,20 +254,20 @@ function buildAllWorkspaceFiles(
return fileData;
}
export function createProjectConfigurations(
export async function createProjectConfigurations(
workspaceRoot: string,
nxJson: NxJsonConfiguration,
configFiles: string[],
plugins: LoadedNxPlugin[]
): {
): Promise<{
projects: Record<string, ProjectConfiguration>;
externalNodes: Record<string, ProjectGraphExternalNode>;
rootMap: Record<string, string>;
} {
}> {
performance.mark('build-project-configs:start');
const { projects, externalNodes, rootMap } =
buildProjectsConfigurationsFromProjectPathsAndPlugins(
await buildProjectsConfigurationsFromProjectPathsAndPlugins(
nxJson,
configFiles,
plugins,

View File

@ -16,10 +16,19 @@ import {
import { readNxJson } from '../../config/configuration';
import { isAsyncIterator } from '../../utils/async-iterator';
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(':');
return getExecutorInformation(nodeModule, exportName, workspaceRoot);
return getExecutorInformation(
nodeModule,
exportName,
workspaceRoot,
projects
);
}
async function runTasks(
@ -32,7 +41,10 @@ async function runTasks(
const projectsConfigurations =
readProjectsConfigurationFromProjectGraph(projectGraph);
const nxJsonConfiguration = readNxJson();
const batchExecutor = getBatchExecutor(executorName);
const batchExecutor = getBatchExecutor(
executorName,
projectsConfigurations.projects
);
const tasks = Object.values(batchTaskGraph.tasks);
const context: ExecutorContext = {
root: workspaceRoot,

View File

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

View File

@ -200,8 +200,8 @@ describe.each([
iterations
);
const directTime = time(() => minimatch.match(items, pattern), iterations);
// Using minimatch directly takes at least twice as long than using the cache.
expect(directTime / cacheTime).toBeGreaterThan(2);
// Using minimatch directly is slower than using the cache.
expect(directTime / cacheTime).toBeGreaterThan(1);
});
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 { 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
@ -30,3 +50,17 @@ export type NxPluginV1 = {
*/
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 { getNxRequirePaths } from './installation-directory';
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 { retrieveProjectConfigurationsWithoutPluginInference } from '../project-graph/utils/retrieve-workspace-files';
import { NxPluginV1 } from './nx-plugin.deprecated';
import { RawProjectGraphDependency } from '../project-graph/project-graph-builder';
import { combineGlobPatterns } from './globs';
import {
NxAngularJsonPlugin,
shouldMergeAngularProjects,
} from '../adapter/angular-json';
import { shouldMergeAngularProjects } from '../adapter/angular-json';
import { getNxPackageJsonWorkspacesPlugin } from '../../plugins/package-json-workspaces';
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 {
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}
@ -58,7 +64,19 @@ export type CreateNodesFunction<T = unknown> = (
projectConfigurationFile: string,
options: T | undefined,
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
*/
@ -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.
*/
externalNodes?: Record<string, ProjectGraphExternalNode>;
};
}
/**
* A pair of file patterns and {@link CreateNodesFunction}
@ -78,6 +96,11 @@ export type CreateNodes<T = unknown> = readonly [
createNodesFunction: CreateNodesFunction<T>
];
export type CreateNodesAsync<T = unknown> = readonly [
projectFilePattern: string,
createNodesFunction: CreateNodesFunctionAsync<T>
];
/**
* 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}
*/
export type NxPluginV2<T = unknown> = {
export type NxPluginV2<
TOptions = unknown,
TCreateNodes extends CreateNodes<TOptions> | CreateNodesAsync<TOptions> =
| CreateNodes<TOptions>
| CreateNodesAsync<TOptions>
> = {
name: string;
/**
* Provides a file pattern and function that retrieves configuration info from
* 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?
/**
* Provides a function to analyze files to create dependencies for the {@link ProjectGraph}
*/
createDependencies?: CreateDependencies<T>;
createDependencies?: CreateDependencies<TOptions>;
};
export * from './nx-plugin.deprecated';
@ -154,11 +182,12 @@ export type LoadedNxPlugin = {
// holding resolved nx plugin objects.
// Allows loadNxPlugins to be called multiple times w/o
// 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,
paths: string[],
projects: Record<string, ProjectConfiguration>,
root: string
) {
let pluginPath: string;
@ -168,7 +197,12 @@ function getPluginPathAndName(
});
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
const plugin = resolveLocalNxPlugin(moduleName, root);
const plugin = resolveLocalNxPlugin(
moduleName,
readNxJson(root),
projects,
root
);
if (plugin) {
const main = readPluginMainFromProjectConfiguration(
plugin.projectConfig
@ -203,6 +237,7 @@ function getPluginPathAndName(
export async function loadNxPluginAsync(
pluginConfiguration: PluginConfiguration,
paths: string[],
projects: Record<string, ProjectConfiguration>,
root: string
): Promise<LoadedNxPlugin> {
const { plugin: moduleName, options } =
@ -214,7 +249,12 @@ export async function loadNxPluginAsync(
return { plugin: pluginModule, options };
}
let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root);
let { pluginPath, name } = await getPluginPathAndName(
moduleName,
paths,
projects,
root
);
const plugin = ensurePluginIsV2(
(await import(pluginPath)) as LoadedNxPlugin['plugin']
);
@ -223,79 +263,21 @@ export async function loadNxPluginAsync(
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(
plugins: PluginConfiguration[],
paths = getNxRequirePaths(),
root = workspaceRoot
root = workspaceRoot,
projects?: Record<string, ProjectConfiguration>
): Promise<LoadedNxPlugin[]> {
const result: LoadedNxPlugin[] = [...(await getDefaultPlugins(root))];
// When loading plugins for `createNodes`, we don't know what projects exist yet.
projects ??= await retrieveProjectConfigurationsWithoutPluginInference(root);
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
@ -307,7 +289,7 @@ export async function loadNxPlugins(
return result;
}
function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 {
export function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 {
if (isNxPluginV2(plugin)) {
return plugin;
}
@ -344,6 +326,7 @@ export function isNxPluginV1(plugin: NxPlugin): plugin is NxPluginV1 {
export function readPluginPackageJson(
pluginName: string,
projects: Record<string, ProjectConfiguration>,
paths = getNxRequirePaths()
): {
path: string;
@ -357,7 +340,12 @@ export function readPluginPackageJson(
};
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
const localPluginPath = resolveLocalNxPlugin(pluginName);
const nxJson = readNxJson();
const localPluginPath = resolveLocalNxPlugin(
pluginName,
nxJson,
projects
);
if (localPluginPath) {
const localPluginPackageJson = path.join(
localPluginPath.path,
@ -382,15 +370,23 @@ const localPluginCache: Record<
string,
{ path: string; projectConfig: ProjectConfiguration }
> = {};
export function resolveLocalNxPlugin(
importPath: string,
nxJsonConfiguration: NxJsonConfiguration,
projects: Record<string, ProjectConfiguration>,
root = workspaceRoot
): { path: string; projectConfig: ProjectConfiguration } | null {
localPluginCache[importPath] ??= lookupLocalPlugin(importPath, root);
localPluginCache[importPath] ??= lookupLocalPlugin(
importPath,
nxJsonConfiguration,
projects,
root
);
return localPluginCache[importPath];
}
let tsNodeAndPathsUnregisterCallback = undefined;
let tsNodeAndPathsUnregisterCallback: (() => void) | undefined = undefined;
/**
* 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) {
const projects = retrieveProjectConfigurationsWithoutPluginInference(root);
function lookupLocalPlugin(
importPath: string,
nxJsonConfiguration: NxJsonConfiguration,
projects: Record<string, ProjectConfiguration>,
root = workspaceRoot
) {
const plugin = findNxProjectForImportPath(importPath, projects, root);
if (!plugin) {
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>>;

View File

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

View File

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

View File

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

View File

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