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,
|
Executor,
|
||||||
ExecutorConfig,
|
ExecutorConfig,
|
||||||
ExecutorContext,
|
ExecutorContext,
|
||||||
|
ExecutorJsonEntryConfig,
|
||||||
ExecutorsJson,
|
ExecutorsJson,
|
||||||
GeneratorCallback,
|
GeneratorCallback,
|
||||||
TaskGraphExecutor,
|
TaskGraphExecutor,
|
||||||
@ -1174,14 +1175,11 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
|
|||||||
builderName
|
builderName
|
||||||
);
|
);
|
||||||
const builderInfo = this.readExecutor(packageName, builderName);
|
const builderInfo = this.readExecutor(packageName, builderName);
|
||||||
const { builders, executors } =
|
|
||||||
readJsonFile<ExecutorsJson>(executorsFilePath);
|
|
||||||
return {
|
return {
|
||||||
name: builderStr,
|
name: builderStr,
|
||||||
builderName,
|
builderName,
|
||||||
description:
|
description: executorConfig.description,
|
||||||
builders?.[builderName]?.description ??
|
|
||||||
executors?.[builderName]?.description,
|
|
||||||
optionSchema: builderInfo.schema,
|
optionSchema: builderInfo.schema,
|
||||||
import: resolveImplementation(
|
import: resolveImplementation(
|
||||||
executorConfig.implementation,
|
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 } =
|
const { json: packageJson, path: packageJsonPath } =
|
||||||
readPluginPackageJson(
|
readPluginPackageJson(
|
||||||
nodeModule,
|
nodeModule,
|
||||||
@ -1209,18 +1214,19 @@ async function getWrappedWorkspaceNodeModulesArchitectHost(
|
|||||||
join(dirname(packageJsonPath), executorsFile)
|
join(dirname(packageJsonPath), executorsFile)
|
||||||
);
|
);
|
||||||
const executorsJson = readJsonFile<ExecutorsJson>(executorsFilePath);
|
const executorsJson = readJsonFile<ExecutorsJson>(executorsFilePath);
|
||||||
const executorConfig: {
|
const executorConfig =
|
||||||
implementation: string;
|
|
||||||
batchImplementation?: string;
|
|
||||||
schema: string;
|
|
||||||
hasher?: string;
|
|
||||||
} =
|
|
||||||
executorsJson.builders?.[builder] ?? executorsJson.executors?.[builder];
|
executorsJson.builders?.[builder] ?? executorsJson.executors?.[builder];
|
||||||
if (!executorConfig) {
|
if (!executorConfig) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot find builder '${builder}' in ${executorsFilePath}.`
|
`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 };
|
return { executorsFilePath, executorConfig, isNgCompat: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,17 +129,18 @@ function readExecutorJson(
|
|||||||
join(dirname(packageJsonPath), executorsFile)
|
join(dirname(packageJsonPath), executorsFile)
|
||||||
);
|
);
|
||||||
const executorsJson = readJsonFile<ExecutorsJson>(executorsFilePath);
|
const executorsJson = readJsonFile<ExecutorsJson>(executorsFilePath);
|
||||||
const executorConfig: {
|
const executorConfig =
|
||||||
implementation: string;
|
executorsJson.executors?.[executor] || executorsJson.builders?.[executor];
|
||||||
batchImplementation?: string;
|
|
||||||
schema: string;
|
|
||||||
hasher?: string;
|
|
||||||
} = executorsJson.executors?.[executor] || executorsJson.builders?.[executor];
|
|
||||||
if (!executorConfig) {
|
if (!executorConfig) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot find executor '${executor}' in ${executorsFilePath}.`
|
`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];
|
const isNgCompat = !executorsJson.executors?.[executor];
|
||||||
return { executorsFilePath, executorConfig, isNgCompat };
|
return { executorsFilePath, executorConfig, isNgCompat };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,13 +37,14 @@ export interface GeneratorsJsonEntry {
|
|||||||
|
|
||||||
export type OutputCaptureMethod = 'direct-nodejs' | 'pipe';
|
export type OutputCaptureMethod = 'direct-nodejs' | 'pipe';
|
||||||
|
|
||||||
export interface ExecutorsJsonEntry {
|
export interface ExecutorJsonEntryConfig {
|
||||||
schema: string;
|
schema: string;
|
||||||
implementation: string;
|
implementation: string;
|
||||||
batchImplementation?: string;
|
batchImplementation?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
hasher?: string;
|
hasher?: string;
|
||||||
}
|
}
|
||||||
|
export type ExecutorsJsonEntry = string | ExecutorJsonEntryConfig;
|
||||||
|
|
||||||
export type Dependencies = 'dependencies' | 'devDependencies';
|
export type Dependencies = 'dependencies' | 'devDependencies';
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { workspaceRoot } from '../workspace-root';
|
|||||||
import { hasElements } from './shared';
|
import { hasElements } from './shared';
|
||||||
|
|
||||||
import type { PluginCapabilities } from './models';
|
import type { PluginCapabilities } from './models';
|
||||||
|
import type { ExecutorsJsonEntry } from '../../config/misc-interfaces';
|
||||||
|
|
||||||
function tryGetCollection<T extends object>(
|
function tryGetCollection<T extends object>(
|
||||||
packageJsonPath: string,
|
packageJsonPath: string,
|
||||||
@ -179,7 +180,11 @@ export async function listPluginCapabilities(
|
|||||||
bodyLines.push('');
|
bodyLines.push('');
|
||||||
bodyLines.push(
|
bodyLines.push(
|
||||||
...Object.keys(plugin.executors).map(
|
...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,
|
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,
|
entry: ExecutorsJsonEntry | GeneratorsJsonEntry,
|
||||||
tree: Tree
|
tree: Tree
|
||||||
) {
|
) {
|
||||||
if (!entry.schema) {
|
if (typeof entry === 'string' || !entry.schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const schemaPath = joinPathFragments(dirname(collectionPath), entry.schema);
|
const schemaPath = joinPathFragments(dirname(collectionPath), entry.schema);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user