From c240c2685e3c5f3747fc434060b091447e0a135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 20 May 2024 15:51:14 +0200 Subject: [PATCH] feat(core): allow executor definition to point to another executor (#23576) Add support for executor definitions that point to another executor. The upcoming Angular 18 uses this feature: https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/build_angular/builders.json#L4, so we need to be able to resolve the builders correctly using such a configuration. Note: the change is also in [the Angular 18 PR](https://github.com/nrwl/nx/pull/22509), where it's tested with the Angular version that requires it. I'm extracting the change to this PR to facilitate reviews and reduce the size of the Angular 18 PR. ## Current Behavior ## Expected Behavior ## Related Issue(s) Fixes # --- packages/nx/src/adapter/ngcli-adapter.ts | 30 +++++++----- .../nx/src/command-line/run/executor-utils.ts | 13 ++--- packages/nx/src/config/misc-interfaces.ts | 3 +- .../src/utils/plugins/plugin-capabilities.ts | 49 ++++++++++++++++++- .../update-16-0-0/cli-in-schema-json.ts | 2 +- 5 files changed, 76 insertions(+), 21 deletions(-) diff --git a/packages/nx/src/adapter/ngcli-adapter.ts b/packages/nx/src/adapter/ngcli-adapter.ts index 33e1645999d40..5eb32c91683e8 100644 --- a/packages/nx/src/adapter/ngcli-adapter.ts +++ b/packages/nx/src/adapter/ngcli-adapter.ts @@ -63,6 +63,7 @@ import { Executor, ExecutorConfig, ExecutorContext, + ExecutorJsonEntryConfig, ExecutorsJson, GeneratorCallback, TaskGraphExecutor, @@ -1174,14 +1175,11 @@ async function getWrappedWorkspaceNodeModulesArchitectHost( builderName ); const builderInfo = this.readExecutor(packageName, builderName); - const { builders, executors } = - readJsonFile(executorsFilePath); + return { name: builderStr, builderName, - description: - builders?.[builderName]?.description ?? - executors?.[builderName]?.description, + description: executorConfig.description, optionSchema: builderInfo.schema, import: resolveImplementation( executorConfig.implementation, @@ -1190,7 +1188,14 @@ async function getWrappedWorkspaceNodeModulesArchitectHost( }; } - private readExecutorsJson(nodeModule: string, builder: string) { + private readExecutorsJson( + nodeModule: string, + builder: string + ): { + executorsFilePath: string; + executorConfig: ExecutorJsonEntryConfig; + isNgCompat: true; + } { const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( nodeModule, @@ -1209,18 +1214,19 @@ async function getWrappedWorkspaceNodeModulesArchitectHost( join(dirname(packageJsonPath), executorsFile) ); const executorsJson = readJsonFile(executorsFilePath); - const executorConfig: { - implementation: string; - batchImplementation?: string; - schema: string; - hasher?: string; - } = + const executorConfig = executorsJson.builders?.[builder] ?? executorsJson.executors?.[builder]; if (!executorConfig) { throw new Error( `Cannot find builder '${builder}' in ${executorsFilePath}.` ); } + if (typeof executorConfig === 'string') { + // Angular CLI can have a builder pointing to another package:builder + const [packageName, executorName] = executorConfig.split(':'); + return this.readExecutorsJson(packageName, executorName); + } + return { executorsFilePath, executorConfig, isNgCompat: true }; } diff --git a/packages/nx/src/command-line/run/executor-utils.ts b/packages/nx/src/command-line/run/executor-utils.ts index ecbac0c0f8bea..d24006801b8f2 100644 --- a/packages/nx/src/command-line/run/executor-utils.ts +++ b/packages/nx/src/command-line/run/executor-utils.ts @@ -129,17 +129,18 @@ function readExecutorJson( join(dirname(packageJsonPath), executorsFile) ); const executorsJson = readJsonFile(executorsFilePath); - const executorConfig: { - implementation: string; - batchImplementation?: string; - schema: string; - hasher?: string; - } = executorsJson.executors?.[executor] || executorsJson.builders?.[executor]; + const executorConfig = + executorsJson.executors?.[executor] || executorsJson.builders?.[executor]; if (!executorConfig) { throw new Error( `Cannot find executor '${executor}' in ${executorsFilePath}.` ); } + if (typeof executorConfig === 'string') { + // Angular CLI can have a builder pointing to another package:builder + const [packageName, executorName] = executorConfig.split(':'); + return readExecutorJson(packageName, executorName, root, projects); + } const isNgCompat = !executorsJson.executors?.[executor]; return { executorsFilePath, executorConfig, isNgCompat }; } diff --git a/packages/nx/src/config/misc-interfaces.ts b/packages/nx/src/config/misc-interfaces.ts index 0736e16e114cd..3485485bc3d0e 100644 --- a/packages/nx/src/config/misc-interfaces.ts +++ b/packages/nx/src/config/misc-interfaces.ts @@ -37,13 +37,14 @@ export interface GeneratorsJsonEntry { export type OutputCaptureMethod = 'direct-nodejs' | 'pipe'; -export interface ExecutorsJsonEntry { +export interface ExecutorJsonEntryConfig { schema: string; implementation: string; batchImplementation?: string; description?: string; hasher?: string; } +export type ExecutorsJsonEntry = string | ExecutorJsonEntryConfig; export type Dependencies = 'dependencies' | 'devDependencies'; diff --git a/packages/nx/src/utils/plugins/plugin-capabilities.ts b/packages/nx/src/utils/plugins/plugin-capabilities.ts index 875a2150d2458..aceb5c4a90cc9 100644 --- a/packages/nx/src/utils/plugins/plugin-capabilities.ts +++ b/packages/nx/src/utils/plugins/plugin-capabilities.ts @@ -13,6 +13,7 @@ import { workspaceRoot } from '../workspace-root'; import { hasElements } from './shared'; import type { PluginCapabilities } from './models'; +import type { ExecutorsJsonEntry } from '../../config/misc-interfaces'; function tryGetCollection( packageJsonPath: string, @@ -179,7 +180,11 @@ export async function listPluginCapabilities( bodyLines.push(''); bodyLines.push( ...Object.keys(plugin.executors).map( - (name) => `${chalk.bold(name)} : ${plugin.executors[name].description}` + (name) => + `${chalk.bold(name)} : ${resolveExecutorDescription( + plugin.executors[name], + projects + )}` ) ); } @@ -197,3 +202,45 @@ export async function listPluginCapabilities( bodyLines, }); } + +function resolveExecutorDescription( + executorJsonEntry: ExecutorsJsonEntry, + projects: Record +) { + try { + if (typeof executorJsonEntry === 'string') { + // it points to another executor, resolve it + const [pkgName, executor] = executorJsonEntry.split(':'); + const collection = loadExecutorsCollection( + workspaceRoot, + pkgName, + projects + ); + + return resolveExecutorDescription(collection[executor], projects); + } + + return executorJsonEntry.description; + } catch { + return 'No description available'; + } +} + +function loadExecutorsCollection( + workspaceRoot: string, + pluginName: string, + projects: Record +): { [name: string]: ExecutorsJsonEntry } { + const { json: packageJson, path: packageJsonPath } = readPluginPackageJson( + pluginName, + projects, + getNxRequirePaths(workspaceRoot) + ); + + return { + ...tryGetCollection(packageJsonPath, packageJson.builders, 'builders'), + ...tryGetCollection(packageJsonPath, packageJson.executors, 'builders'), + ...tryGetCollection(packageJsonPath, packageJson.builders, 'executors'), + ...tryGetCollection(packageJsonPath, packageJson.executors, 'executors'), + }; +} diff --git a/packages/plugin/src/migrations/update-16-0-0/cli-in-schema-json.ts b/packages/plugin/src/migrations/update-16-0-0/cli-in-schema-json.ts index a8428d1417913..3364aa74e79bf 100644 --- a/packages/plugin/src/migrations/update-16-0-0/cli-in-schema-json.ts +++ b/packages/plugin/src/migrations/update-16-0-0/cli-in-schema-json.ts @@ -160,7 +160,7 @@ function deleteCliPropFromSchemaFile( entry: ExecutorsJsonEntry | GeneratorsJsonEntry, tree: Tree ) { - if (!entry.schema) { + if (typeof entry === 'string' || !entry.schema) { return; } const schemaPath = joinPathFragments(dirname(collectionPath), entry.schema);