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. <!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
0eb86c849c
commit
c240c2685e
@ -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<ExecutorsJson>(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<ExecutorsJson>(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 };
|
||||
}
|
||||
|
||||
|
||||
@ -129,17 +129,18 @@ function readExecutorJson(
|
||||
join(dirname(packageJsonPath), executorsFile)
|
||||
);
|
||||
const executorsJson = readJsonFile<ExecutorsJson>(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 };
|
||||
}
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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<T extends object>(
|
||||
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<string, ProjectConfiguration>
|
||||
) {
|
||||
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<string, ProjectConfiguration>
|
||||
): { [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'),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user