From b71c0b537c5fa61446b34e01158d47354991e83f Mon Sep 17 00:00:00 2001 From: AgentEnder Date: Thu, 9 Nov 2023 14:50:29 -0500 Subject: [PATCH] feat(core): support for async createNodes functions --- docs/generated/devkit/CreateNodesAsync.md | 9 + docs/generated/devkit/CreateNodesFunction.md | 11 +- docs/generated/devkit/CreateNodesResult.md | 24 +++ docs/generated/devkit/NxPluginV2.md | 21 ++- docs/generated/devkit/README.md | 2 + .../packages/devkit/documents/nx_devkit.md | 2 + packages/nx/src/config/workspaces.ts | 2 +- packages/nx/src/devkit-exports.ts | 2 + packages/nx/src/native/index.d.ts | 4 +- .../src/native/tests/workspace_files.spec.ts | 120 +----------- .../nx/src/native/workspace/config_files.rs | 2 +- packages/nx/src/native/workspace/context.rs | 5 +- .../src/native/workspace/workspace_files.rs | 3 +- .../project-json/build-nodes/project-json.ts | 4 +- packages/nx/src/project-graph/file-utils.ts | 2 +- .../utils/project-configuration-utils.ts | 113 +++++++++--- .../retrieve-workspace-files.deprecated.ts | 41 +++++ .../utils/retrieve-workspace-files.ts | 76 +++----- packages/nx/src/utils/nx-plugin.deprecated.ts | 90 +++++++++ packages/nx/src/utils/nx-plugin.ts | 173 ++++++++---------- .../src/utils/plugins/plugin-capabilities.ts | 6 +- packages/nx/src/utils/workspace-context.ts | 4 +- 22 files changed, 392 insertions(+), 324 deletions(-) create mode 100644 docs/generated/devkit/CreateNodesAsync.md create mode 100644 docs/generated/devkit/CreateNodesResult.md create mode 100644 packages/nx/src/project-graph/utils/retrieve-workspace-files.deprecated.ts diff --git a/docs/generated/devkit/CreateNodesAsync.md b/docs/generated/devkit/CreateNodesAsync.md new file mode 100644 index 00000000000000..196a714ff7e105 --- /dev/null +++ b/docs/generated/devkit/CreateNodesAsync.md @@ -0,0 +1,9 @@ +# Type alias: CreateNodesAsync + +Ƭ **CreateNodesAsync**<`T`\>: readonly [projectFilePattern: string, createNodesFunction: CreateNodesFunctionAsync] + +#### Type parameters + +| Name | Type | +| :--- | :-------- | +| `T` | `unknown` | diff --git a/docs/generated/devkit/CreateNodesFunction.md b/docs/generated/devkit/CreateNodesFunction.md index 2d47929e9fd510..3f3df71e37667e 100644 --- a/docs/generated/devkit/CreateNodesFunction.md +++ b/docs/generated/devkit/CreateNodesFunction.md @@ -1,6 +1,6 @@ # Type alias: CreateNodesFunction -Ƭ **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) diff --git a/docs/generated/devkit/CreateNodesResult.md b/docs/generated/devkit/CreateNodesResult.md new file mode 100644 index 00000000000000..6a7fafe07f03f4 --- /dev/null +++ b/docs/generated/devkit/CreateNodesResult.md @@ -0,0 +1,24 @@ +# Interface: CreateNodesResult + +## Table of contents + +### Properties + +- [externalNodes](../../devkit/documents/CreateNodesResult#externalnodes): Record<string, ProjectGraphExternalNode> +- [projects](../../devkit/documents/CreateNodesResult#projects): Record<string, Optional<ProjectConfiguration, "root">> + +## 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 diff --git a/docs/generated/devkit/NxPluginV2.md b/docs/generated/devkit/NxPluginV2.md index a8d3f804034724..092f88533ea3b6 100644 --- a/docs/generated/devkit/NxPluginV2.md +++ b/docs/generated/devkit/NxPluginV2.md @@ -1,19 +1,20 @@ -# Type alias: NxPluginV2 +# Type alias: NxPluginV2 -Ƭ **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` | - | diff --git a/docs/generated/devkit/README.md b/docs/generated/devkit/README.md index 6661d52e35a412..c0ea2140cd8ca5 100644 --- a/docs/generated/devkit/README.md +++ b/docs/generated/devkit/README.md @@ -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) diff --git a/docs/generated/packages/devkit/documents/nx_devkit.md b/docs/generated/packages/devkit/documents/nx_devkit.md index 6661d52e35a412..c0ea2140cd8ca5 100644 --- a/docs/generated/packages/devkit/documents/nx_devkit.md +++ b/docs/generated/packages/devkit/documents/nx_devkit.md @@ -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) diff --git a/packages/nx/src/config/workspaces.ts b/packages/nx/src/config/workspaces.ts index 98a9b9bde18eb4..eaa7daacdcc7b8 100644 --- a/packages/nx/src/config/workspaces.ts +++ b/packages/nx/src/config/workspaces.ts @@ -3,7 +3,7 @@ import { dirname } from 'path'; 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'; +import { retrieveProjectConfigurationsSync } from '../project-graph/utils/retrieve-workspace-files.deprecated'; // TODO(v18): remove this class /** diff --git a/packages/nx/src/devkit-exports.ts b/packages/nx/src/devkit-exports.ts index a75dd08b724647..f4865db6ce66fc 100644 --- a/packages/nx/src/devkit-exports.ts +++ b/packages/nx/src/devkit-exports.ts @@ -51,7 +51,9 @@ export type { NxPluginV2, ProjectTargetConfigurator, CreateNodes, + CreateNodesAsync, CreateNodesFunction, + CreateNodesResult, CreateNodesContext, CreateDependencies, CreateDependenciesContext, diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index edce7e11cff6bf..a13b8762cf5def 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -134,9 +134,9 @@ export class Watcher { export class WorkspaceContext { workspaceRoot: string constructor(workspaceRoot: string) - getWorkspaceFiles(globs: Array, parseConfigurations: (arg0: Array) => Promise>): object | null + getWorkspaceFiles(globs: Array, parseConfigurations: (arg0: Array) => Promise>): Promise glob(globs: Array): Array - getProjectConfigurations(globs: Array, parseConfigurations: (arg0: Array) => Promise>): object + getProjectConfigurations(globs: Array, parseConfigurations: (arg0: Array) => Promise>): Promise> incrementalUpdate(updatedFiles: Array, deletedFiles: Array): Record allFileData(): Array } diff --git a/packages/nx/src/native/tests/workspace_files.spec.ts b/packages/nx/src/native/tests/workspace_files.spec.ts index 5782cec7fc6e27..21d527546dbfcc 100644 --- a/packages/nx/src/native/tests/workspace_files.spec.ts +++ b/packages/nx/src/native/tests/workspace_files.spec.ts @@ -51,20 +51,9 @@ describe('workspace files', () => { let globs = ['project.json', '**/project.json', 'libs/*/package.json']; const context = new WorkspaceContext(fs.tempDir); -<<<<<<< HEAD - let { projectFileMap, globalFiles } = context.getWorkspaceFiles( + let { projectFileMap, globalFiles } = await context.getWorkspaceFiles( globs, createParseConfigurationsFunction(fs.tempDir) -======= - let { projectFileMap, projectConfigurations, globalFiles } = - (await context.getWorkspaceFiles( - globs, - createParseConfigurationsFunction(fs.tempDir) - )) as any; - - let sortedConfigs = Object.values(projectConfigurations).sort((a, b) => - a['name'].localeCompare(b['name']) ->>>>>>> 6d080c8bf (feat(core): make createNodes async) ); expect(projectFileMap).toMatchInlineSnapshot(` @@ -139,7 +128,6 @@ describe('workspace files', () => { `); }); -<<<<<<< HEAD it('should assign files to the root project if it exists', async () => { const fs = new TempFs('workspace-files'); const nxJson: NxJsonConfiguration = {}; @@ -160,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) ); @@ -191,110 +179,6 @@ describe('workspace files', () => { ] `); }); -======= - // it('should assign files to the root project if it exists', async () => { - // const fs = new TempFs('workspace-files'); - // const nxJson: NxJsonConfiguration = {}; - // await fs.createFiles({ - // './nx.json': JSON.stringify(nxJson), - // './package.json': JSON.stringify({ - // name: 'repo-name', - // version: '0.0.0', - // dependencies: {}, - // }), - // './project.json': JSON.stringify({ - // name: 'repo-name', - // }), - // './src/index.js': '', - // './jest.config.js': '', - // }); - // - // const context = new WorkspaceContext(fs.tempDir); - // - // const globs = ['project.json', '**/project.json', '**/package.json']; - // const { globalFiles, projectFileMap } = context.getWorkspaceFiles( - // globs, - // createParseConfigurationsFunction(fs.tempDir) - // ); - // - // expect(globalFiles).toEqual([]); - // expect(projectFileMap['repo-name']).toMatchInlineSnapshot(` - // [ - // { - // "file": "jest.config.js", - // "hash": "3244421341483603138", - // }, - // { - // "file": "nx.json", - // "hash": "1389868326933519382", - // }, - // { - // "file": "package.json", - // "hash": "14409636362330144230", - // }, - // { - // "file": "project.json", - // "hash": "4357927788053707201", - // }, - // { - // "file": "src/index.js", - // "hash": "3244421341483603138", - // }, - // ] - // `); - // }); - // - // it('should dedupe configuration files', async () => { - // const fs = new TempFs('workspace-files'); - // const nxJson: NxJsonConfiguration = {}; - // await fs.createFiles({ - // './nx.json': JSON.stringify(nxJson), - // './package.json': JSON.stringify({ - // name: 'repo-name', - // version: '0.0.0', - // dependencies: {}, - // }), - // './project.json': JSON.stringify({ - // name: 'repo-name', - // }), - // './libs/project1/project.json': JSON.stringify({ - // name: 'project1', - // }), - // './libs/project1/package.json': JSON.stringify({ - // name: 'project1', - // }), - // './libs/project1/index.js': '', - // }); - // - // const context = new WorkspaceContext(fs.tempDir); - // let globs = ['project.json', '**/project.json', '**/package.json']; - // - // let nodes = context.getProjectConfigurations(globs, (filenames) => { - // const res = {}; - // for (const filename of filenames) { - // const json = readJsonFile(join(fs.tempDir, filename)); - // res[json.name] = { - // ...json, - // root: dirname(filename), - // }; - // } - // return { - // externalNodes: {}, - // projectNodes: res, - // }; - // }); - // expect(nodes.projectNodes).toEqual({ - // project1: { - // name: 'project1', - // root: 'libs/project1', - // }, - // 'repo-name': expect.objectContaining({ - // name: 'repo-name', - // root: '.', - // }), - // }); - // }); ->>>>>>> 6d080c8bf (feat(core): make createNodes async) // describe('errors', () => { // it('it should infer names of configuration files without a name', async () => { diff --git a/packages/nx/src/native/workspace/config_files.rs b/packages/nx/src/native/workspace/config_files.rs index 9c146081f0a0bc..aaeaaa0ece000f 100644 --- a/packages/nx/src/native/workspace/config_files.rs +++ b/packages/nx/src/native/workspace/config_files.rs @@ -1,7 +1,7 @@ use crate::native::utils::glob::build_glob_set; use crate::native::utils::path::Normalize; use std::collections::HashMap; -use napi::bindgen_prelude::{Object, Promise}; +use napi::bindgen_prelude::Promise; use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors}; use rayon::prelude::*; diff --git a/packages/nx/src/native/workspace/context.rs b/packages/nx/src/native/workspace/context.rs index 937b5da0fccdca..fa65ffab92c0ef 100644 --- a/packages/nx/src/native/workspace/context.rs +++ b/packages/nx/src/native/workspace/context.rs @@ -16,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] @@ -149,7 +148,7 @@ impl WorkspaceContext { } } - #[napi] + #[napi(ts_return_type = "Promise")] pub fn get_workspace_files( &self, env: Env, @@ -173,7 +172,7 @@ impl WorkspaceContext { config_files::glob_files(globs, self.files_worker.get_files().as_deref()) } - #[napi] + #[napi(ts_return_type = "Promise>")] pub fn get_project_configurations( &self, env: Env, diff --git a/packages/nx/src/native/workspace/workspace_files.rs b/packages/nx/src/native/workspace/workspace_files.rs index 1caeaad02824a7..5df4d31cec9030 100644 --- a/packages/nx/src/native/workspace/workspace_files.rs +++ b/packages/nx/src/native/workspace/workspace_files.rs @@ -2,9 +2,8 @@ use napi::bindgen_prelude::{Object, Promise}; use std::collections::HashMap; use std::path::{Path, PathBuf}; -use napi::{Env, JsObject}; +use napi::Env; use rayon::prelude::*; -use serde_json::Value; use tracing::trace; use crate::native::types::FileData; diff --git a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts index bfaa8d03fe0569..3536326e3a1d6f 100644 --- a/packages/nx/src/plugins/project-json/build-nodes/project-json.ts +++ b/packages/nx/src/plugins/project-json/build-nodes/project-json.ts @@ -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 = { name: 'nx-core-build-project-json-nodes', createNodes: [ '{project.json,**/project.json}', diff --git a/packages/nx/src/project-graph/file-utils.ts b/packages/nx/src/project-graph/file-utils.ts index d95d866fb3d4e5..c5ce6e6fe2e190 100644 --- a/packages/nx/src/project-graph/file-utils.ts +++ b/packages/nx/src/project-graph/file-utils.ts @@ -14,7 +14,7 @@ import { } from './project-graph'; import { toOldFormat } from '../adapter/angular-json'; import { getIgnoreObject } from '../utils/ignore'; -import { retrieveProjectConfigurationsSync } from './utils/retrieve-workspace-files'; +import { retrieveProjectConfigurationsSync } from './utils/retrieve-workspace-files.deprecated'; export interface Change { type: string; diff --git a/packages/nx/src/project-graph/utils/project-configuration-utils.ts b/packages/nx/src/project-graph/utils/project-configuration-utils.ts index 2f34174ebbb44e..ed5f3f0081061d 100644 --- a/packages/nx/src/project-graph/utils/project-configuration-utils.ts +++ b/packages/nx/src/project-graph/utils/project-configuration-utils.ts @@ -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; + externalNodes: Record; + rootMap: Record; +}; + +/** + * ** DO NOT USE ** - Please use without the `skipAsync` parameter. + * + * @deprecated This signature should only be used by the deprecated sync functions. + */ 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; - externalNodes: Record; - rootMap: Record; -} { - const projectRootMap: Map = new Map(); - const externalNodes: Record = {}; + 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; +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 { + const results: Array> = []; // 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)[] +): ConfigurationResult { + const projectRootMap: Map = new Map(); + const externalNodes: Record = {}; + + 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> +): Promise { + return Promise.all(results).then((r) => combineSyncConfigurationResults(r)); +} + export function readProjectConfigurationsFromRootMap( projectRootMap: Map ) { diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.deprecated.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.deprecated.ts new file mode 100644 index 00000000000000..181cb209227341 --- /dev/null +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.deprecated.ts @@ -0,0 +1,41 @@ +import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context'; +import { NxJsonConfiguration } from '../../config/nx-json'; +import { ProjectGraphExternalNode } from '../../config/project-graph'; +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; +import { getNxRequirePaths } from '../../utils/installation-directory'; +import { loadNxPluginsSync } from '../../utils/nx-plugin'; +import { buildProjectsConfigurationsFromProjectPathsAndPlugins } from './project-configuration-utils'; +import { configurationGlobs } from './retrieve-workspace-files'; + +/** + * @deprecated Use {@link retrieveProjectConfigurations} instead. + */ +export function retrieveProjectConfigurationsSync( + workspaceRoot: string, + nxJson: NxJsonConfiguration +): { + externalNodes: Record; + projectNodes: Record; +} { + const plugins = loadNxPluginsSync( + nxJson?.plugins ?? [], + getNxRequirePaths(workspaceRoot), + workspaceRoot + ); + + const globs = configurationGlobs(workspaceRoot, plugins); + const files = globWithWorkspaceContext(workspaceRoot, globs); + + const res = buildProjectsConfigurationsFromProjectPathsAndPlugins( + nxJson, + files, + plugins, + workspaceRoot, + true + ); + + return { + externalNodes: res.externalNodes, + projectNodes: res.projects, + }; +} diff --git a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts index 8e36a2f5acea94..cd26dee52fa58e 100644 --- a/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts +++ b/packages/nx/src/project-graph/utils/retrieve-workspace-files.ts @@ -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; let externalNodes: Record; - 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; - projectNodes: Record; -} { - 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; projectNodes: Record; -} { +}> { let result: { externalNodes: Record; projectNodes: Record; }; - 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 { +): Promise> { const nxJson = readNxJson(root); const projectGlobPatterns = configurationGlobsWithoutPlugins(root); const cacheKey = root + ',' + projectGlobPatterns.join(','); @@ -241,8 +215,8 @@ export function retrieveProjectConfigurationsWithoutPluginInference( 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; externalNodes: Record; rootMap: Record; -} { +}> { performance.mark('build-project-configs:start'); const { projects, externalNodes, rootMap } = - buildProjectsConfigurationsFromProjectPathsAndPlugins( + await buildProjectsConfigurationsFromProjectPathsAndPlugins( nxJson, configFiles, plugins, diff --git a/packages/nx/src/utils/nx-plugin.deprecated.ts b/packages/nx/src/utils/nx-plugin.deprecated.ts index 2e94094010d5cb..f87cc366080a3e 100644 --- a/packages/nx/src/utils/nx-plugin.deprecated.ts +++ b/packages/nx/src/utils/nx-plugin.deprecated.ts @@ -1,5 +1,21 @@ +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 { CreateProjectJsonProjectsPlugin } from '../plugins/project-json/build-nodes/project-json'; +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 +46,77 @@ export type NxPluginV1 = { */ projectFilePatterns?: string[]; }; + +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; +} + +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, + })); +} diff --git a/packages/nx/src/utils/nx-plugin.ts b/packages/nx/src/utils/nx-plugin.ts index b890afac47c869..3f16d0dd6e2b56 100644 --- a/packages/nx/src/utils/nx-plugin.ts +++ b/packages/nx/src/utils/nx-plugin.ts @@ -27,20 +27,25 @@ 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'; /** * Context for {@link CreateNodesFunction} @@ -58,7 +63,19 @@ export type CreateNodesFunction = ( 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 = ( + projectConfigurationFile: string, + options: T | undefined, + context: CreateNodesContext +) => Promise; + +export interface CreateNodesResult { /** * A map of project root -> project configuration */ @@ -68,7 +85,7 @@ export type CreateNodesFunction = ( * A map of external node name -> external node. External nodes do not have a root, so the key is their name. */ externalNodes?: Record; -}; +} /** * A pair of file patterns and {@link CreateNodesFunction} @@ -78,6 +95,11 @@ export type CreateNodes = readonly [ createNodesFunction: CreateNodesFunction ]; +export type CreateNodesAsync = readonly [ + projectFilePattern: string, + createNodesFunction: CreateNodesFunctionAsync +]; + /** * Context for {@link CreateDependencies} */ @@ -122,20 +144,25 @@ export type CreateDependencies = ( /** * A plugin for Nx which creates nodes and dependencies for the {@link ProjectGraph} */ -export type NxPluginV2 = { +export type NxPluginV2< + TOptions = unknown, + TCreateNodes extends CreateNodes | CreateNodesAsync = + | CreateNodes + | CreateNodesAsync +> = { name: string; /** * Provides a file pattern and function that retrieves configuration info from * those files. e.g. { '**\/*.csproj': buildProjectsFromCsProjFile } */ - createNodes?: CreateNodes; + 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; + createDependencies?: CreateDependencies; }; export * from './nx-plugin.deprecated'; @@ -154,9 +181,9 @@ export type LoadedNxPlugin = { // holding resolved nx plugin objects. // Allows loadNxPlugins to be called multiple times w/o // executing resolution mulitple times. -let nxPluginCache: Map = new Map(); +export const nxPluginCache: Map = new Map(); -function getPluginPathAndName( +export function getPluginPathAndName( moduleName: string, paths: string[], root: string @@ -168,7 +195,7 @@ function getPluginPathAndName( }); } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { - const plugin = resolveLocalNxPlugin(moduleName, root); + const plugin = resolveLocalNxPlugin(moduleName, readNxJson(root), root); if (plugin) { const main = readPluginMainFromProjectConfiguration( plugin.projectConfig @@ -214,7 +241,11 @@ export async function loadNxPluginAsync( return { plugin: pluginModule, options }; } - let { pluginPath, name } = getPluginPathAndName(moduleName, paths, root); + let { pluginPath, name } = await getPluginPathAndName( + moduleName, + paths, + root + ); const plugin = ensurePluginIsV2( (await import(pluginPath)) as LoadedNxPlugin['plugin'] ); @@ -223,69 +254,6 @@ 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(), @@ -307,7 +275,7 @@ export async function loadNxPlugins( return result; } -function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 { +export function ensurePluginIsV2(plugin: NxPlugin): NxPluginV2 { if (isNxPluginV2(plugin)) { return plugin; } @@ -357,7 +325,8 @@ export function readPluginPackageJson( }; } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { - const localPluginPath = resolveLocalNxPlugin(pluginName); + const nxJson = readNxJson(); + const localPluginPath = resolveLocalNxPlugin(pluginName, nxJson); if (localPluginPath) { const localPluginPackageJson = path.join( localPluginPath.path, @@ -382,15 +351,21 @@ const localPluginCache: Record< string, { path: string; projectConfig: ProjectConfiguration } > = {}; + export function resolveLocalNxPlugin( importPath: string, + nxJsonConfiguration: NxJsonConfiguration, root = workspaceRoot ): { path: string; projectConfig: ProjectConfiguration } | null { - localPluginCache[importPath] ??= lookupLocalPlugin(importPath, root); + localPluginCache[importPath] ??= lookupLocalPlugin( + importPath, + nxJsonConfiguration, + 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 +409,12 @@ export function unregisterPluginTSTranspiler() { } } -function lookupLocalPlugin(importPath: string, root = workspaceRoot) { - const projects = retrieveProjectConfigurationsWithoutPluginInference(root); +function lookupLocalPlugin( + importPath: string, + nxJsonConfiguration: NxJsonConfiguration, + root = workspaceRoot +) { + const projects = getProjectJsonProjects(root, nxJsonConfiguration); const plugin = findNxProjectForImportPath(importPath, projects, root); if (!plugin) { return null; @@ -531,15 +510,25 @@ async function getDefaultPlugins(root: string): Promise { })); } -function getDefaultPluginsSync(root: string): LoadedNxPlugin[] { - const plugins: NxPluginV2[] = [require('../plugins/js')]; - - if (shouldMergeAngularProjects(root, false)) { - plugins.push(require('../adapter/angular-json').NxAngularJsonPlugin); +function getProjectJsonProjects(root: string, nxJson: NxJsonConfiguration) { + const { + createNodes: [pattern, fn], + } = CreateProjectJsonProjectsPlugin; + const projectFiles = globWithWorkspaceContext(root, [pattern]); + const projectRootMap: Map = new Map(); + for (const file of projectFiles) { + const { projects } = fn(file, null, { + workspaceRoot: root, + nxJsonConfiguration: nxJson, + }); + for (const root in projects) { + mergeProjectConfigurationIntoRootMap(projectRootMap, { + root, + ...projects[root], + }); + } } - return plugins.map((p) => ({ - plugin: p, - })); + return readProjectConfigurationsFromRootMap(projectRootMap); } type Optional = Omit & Partial>; diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index 08252297ec3ee0..2e8ab1c4ba7932 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -37,10 +37,8 @@ export async function getPluginCapabilities( includeRuntimeCapabilities = false ): Promise { try { - const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( - pluginName, - getNxRequirePaths(workspaceRoot) - ); + const { json: packageJson, path: packageJsonPath } = + await readPluginPackageJson(pluginName, getNxRequirePaths(workspaceRoot)); const pluginModule = includeRuntimeCapabilities ? await tryGetModule(packageJson, workspaceRoot) : ({} as Record); diff --git a/packages/nx/src/utils/workspace-context.ts b/packages/nx/src/utils/workspace-context.ts index 57dc8a91ae2332..73bf6bbbaf131f 100644 --- a/packages/nx/src/utils/workspace-context.ts +++ b/packages/nx/src/utils/workspace-context.ts @@ -19,7 +19,7 @@ export function setupWorkspaceContext(workspaceRoot: string) { export function getNxWorkspaceFilesFromContext( workspaceRoot: string, globs: string[], - parseConfigurations: (files: string[]) => Record + parseConfigurations: (files: string[]) => Promise> ) { 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 + parseConfigurations: (files: string[]) => Promise> ) { ensureContextAvailable(workspaceRoot); return workspaceContext.getProjectConfigurations(globs, parseConfigurations);