Execution engine for running schematics with metadata, collections, and workflow orchestration. The engine handles schematic discovery, validation, execution, and task management in a structured, pluggable way.
The main engine interface for orchestrating schematic execution.
/**
* Root interface for creating and running schematics and collections
*/
interface Engine<CollectionMetadataT, SchematicMetadataT> {
readonly defaultMergeStrategy: MergeStrategy;
readonly workflow: Workflow | null;
/** Create a collection from name and optional requester */
createCollection(
name: string,
requester?: Collection<CollectionMetadataT, SchematicMetadataT>
): Collection<CollectionMetadataT, SchematicMetadataT>;
/** Create execution context for a schematic */
createContext(
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
parent?: Readonly<SchematicContext>,
executionOptions?: Partial<ExecutionOptions>
): SchematicContext;
/** Create a schematic from collection */
createSchematic(
name: string,
collection: Collection<CollectionMetadataT, SchematicMetadataT>
): Schematic<CollectionMetadataT, SchematicMetadataT>;
/** Create source from URL */
createSourceFromUrl(url: Url, context: SchematicContext): Source;
/** Transform options using schematic metadata */
transformOptions<OptionT, ResultT>(
schematic: Schematic<CollectionMetadataT, SchematicMetadataT>,
options: OptionT,
context?: SchematicContext
): Observable<ResultT>;
/** Execute post-schematic tasks */
executePostTasks(): Observable<void>;
}Interface for resolving and providing schematic resources.
/**
* Host interface for resolving collections and schematics
*/
interface EngineHost<CollectionMetadataT, SchematicMetadataT> {
/** Create collection description with metadata */
createCollectionDescription(
name: string,
requester?: CollectionDescription<CollectionMetadataT>
): CollectionDescription<CollectionMetadataT>;
/** List available schematic names in collection */
listSchematicNames(
collection: CollectionDescription<CollectionMetadataT>,
includeHidden?: boolean
): string[];
/** Create schematic description with metadata */
createSchematicDescription(
name: string,
collection: CollectionDescription<CollectionMetadataT>
): SchematicDescription<CollectionMetadataT, SchematicMetadataT> | null;
/** Get rule factory for schematic */
getSchematicRuleFactory<OptionT>(
schematic: SchematicDescription<CollectionMetadataT, SchematicMetadataT>,
collection: CollectionDescription<CollectionMetadataT>
): RuleFactory<OptionT>;
/** Create source from URL if supported */
createSourceFromUrl(url: Url, context: SchematicContext): Source | null;
/** Transform options if needed */
transformOptions<OptionT, ResultT>(
schematic: SchematicDescription<CollectionMetadataT, SchematicMetadataT>,
options: OptionT,
context?: SchematicContext
): Observable<ResultT>;
/** Transform execution context */
transformContext(context: SchematicContext): SchematicContext;
/** Create task executor by name */
createTaskExecutor(name: string): Observable<TaskExecutor>;
/** Check if task executor exists */
hasTaskExecutor(name: string): boolean;
/** Default merge strategy for the host */
readonly defaultMergeStrategy?: MergeStrategy;
}Interface representing a collection of schematics with metadata.
/**
* Represents a collection of schematics with metadata
*/
interface Collection<CollectionMetadataT, SchematicMetadataT> {
readonly description: CollectionDescription<CollectionMetadataT>;
readonly baseDescriptions?: Array<CollectionDescription<CollectionMetadataT>>;
/** Create schematic from this collection */
createSchematic(
name: string,
allowPrivate?: boolean
): Schematic<CollectionMetadataT, SchematicMetadataT>;
/** List all schematic names in this collection */
listSchematicNames(includeHidden?: boolean): string[];
}
/**
* Metadata description for a collection
*/
interface CollectionDescription<T> {
readonly name: string;
readonly path: string;
readonly description?: string;
readonly extends?: string[];
readonly dependencies?: string[];
}Interface representing an individual schematic that can be executed.
/**
* Represents an individual schematic that can be executed
*/
interface Schematic<CollectionMetadataT, SchematicMetadataT> {
readonly description: SchematicDescription<CollectionMetadataT, SchematicMetadataT>;
readonly collection: Collection<CollectionMetadataT, SchematicMetadataT>;
/** Execute the schematic with options */
call<OptionT>(
options: OptionT,
host: Observable<Tree>,
parentContext?: Readonly<SchematicContext>,
executionOptions?: Partial<ExecutionOptions>
): Observable<Tree>;
}
/**
* Metadata description for a schematic
*/
interface SchematicDescription<CollectionT, SchematicT> {
readonly name: string;
readonly collection: CollectionDescription<CollectionT>;
readonly description?: string;
readonly schemaJson?: JsonObject;
readonly hidden?: boolean;
readonly private?: boolean;
readonly aliases?: string[];
}Execution context providing access to engine, logging, and task management.
/**
* Execution context for schematics providing engine access and utilities
*/
interface SchematicContext {
readonly debug: boolean;
readonly engine: Engine<any, any>;
readonly logger: logging.LoggerApi;
readonly schematic: Schematic<any, any>;
readonly strategy: MergeStrategy;
readonly interactive: boolean;
/** Add a task to be executed after schematic completion */
addTask<T>(
task: TaskConfigurationGenerator<T>,
dependencies?: Array<TaskId>
): TaskId;
}Usage Examples:
import { SchematicContext, Rule, Tree } from "@angular-devkit/schematics";
function exampleSchematic(options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
// Access logger
context.logger.info(`Generating ${options.name}`);
// Add post-execution task
context.addTask({
name: 'npm-install',
options: {}
});
// Check if in debug mode
if (context.debug) {
context.logger.debug('Debug information');
}
// Access strategy
const strategy = context.strategy;
return tree;
};
}Concrete implementation of the Engine interface.
/**
* Main engine implementation for executing schematics
*/
class SchematicEngine<CollectionT, SchematicT> implements Engine<CollectionT, SchematicT> {
constructor(host: EngineHost<CollectionT, SchematicT>, workflow?: Workflow);
readonly defaultMergeStrategy: MergeStrategy;
readonly workflow: Workflow | null;
createCollection(
name: string,
requester?: Collection<CollectionT, SchematicT>
): Collection<CollectionT, SchematicT>;
createContext(
schematic: Schematic<CollectionT, SchematicT>,
parent?: Readonly<SchematicContext>,
executionOptions?: Partial<ExecutionOptions>
): SchematicContext;
createSchematic(
name: string,
collection: Collection<CollectionT, SchematicT>
): Schematic<CollectionT, SchematicT>;
createSourceFromUrl(url: Url, context: SchematicContext): Source;
transformOptions<OptionT, ResultT>(
schematic: Schematic<CollectionT, SchematicT>,
options: OptionT,
context?: SchematicContext
): Observable<ResultT>;
executePostTasks(): Observable<void>;
}Concrete implementations of collections and schematics.
/**
* Implementation of Collection interface
*/
class CollectionImpl<CollectionT, SchematicT> implements Collection<CollectionT, SchematicT> {
readonly description: CollectionDescription<CollectionT>;
readonly baseDescriptions?: Array<CollectionDescription<CollectionT>>;
constructor(
description: CollectionDescription<CollectionT>,
engine: SchematicEngine<CollectionT, SchematicT>,
baseDescriptions?: Array<CollectionDescription<CollectionT>>
);
createSchematic(name: string, allowPrivate?: boolean): Schematic<CollectionT, SchematicT>;
listSchematicNames(includeHidden?: boolean): string[];
}
/**
* Implementation of Schematic interface
*/
class SchematicImpl<CollectionT, SchematicT> implements Schematic<CollectionT, SchematicT> {
readonly description: SchematicDescription<CollectionT, SchematicT>;
readonly collection: Collection<CollectionT, SchematicT>;
constructor(
description: SchematicDescription<CollectionT, SchematicT>,
collection: Collection<CollectionT, SchematicT>,
engine: SchematicEngine<CollectionT, SchematicT>
);
call<OptionT>(
options: OptionT,
host: Observable<Tree>,
parentContext?: Readonly<SchematicContext>,
executionOptions?: Partial<ExecutionOptions>
): Observable<Tree>;
}Task management system for post-schematic execution.
/**
* Task configuration for post-execution tasks
*/
interface TaskConfigurationGenerator<T = {}> {
name: string;
options: T;
}
/**
* Task executor interface for running tasks
*/
interface TaskExecutor {
execute(options: TaskConfiguration, context?: TaskContext): Promise<void>;
}
/**
* Task execution context
*/
interface TaskContext {
readonly logger: logging.LoggerApi;
readonly workspaceRoot: string;
readonly packageManager: string;
}
type TaskId = symbol;Usage Examples:
import { SchematicContext, TaskConfigurationGenerator } from "@angular-devkit/schematics";
function addNpmInstallTask(context: SchematicContext): void {
const installTask: TaskConfigurationGenerator = {
name: 'node-package',
options: {
command: 'install',
packageManager: 'npm'
}
};
context.addTask(installTask);
}
function addDependentTasks(context: SchematicContext): void {
// Add install task first
const installTaskId = context.addTask({
name: 'node-package',
options: { command: 'install' }
});
// Add build task that depends on install
context.addTask({
name: 'run-schematic',
options: { command: 'build' }
}, [installTaskId]);
}Engine-specific exception types.
/**
* Unknown URL source protocol error
*/
class UnknownUrlSourceProtocol extends SchematicsException {
constructor(url: string);
}
/**
* Unknown collection error
*/
class UnknownCollectionException extends SchematicsException {
constructor(name: string);
}
/**
* Circular collection dependency error
*/
class CircularCollectionException extends SchematicsException {
constructor(name: string);
}
/**
* Unknown schematic in collection error
*/
class UnknownSchematicException extends SchematicsException {
constructor(name: string, collection: CollectionDescription<any>);
}
/**
* Attempt to access private schematic error
*/
class PrivateSchematicException extends SchematicsException {
constructor(name: string, collection: CollectionDescription<any>);
}
/**
* Engine conflicting operation error
*/
class SchematicEngineConflictingException extends SchematicsException {}
/**
* Unregistered task executor error
*/
class UnregisteredTaskException extends SchematicsException {
constructor(name: string);
}
/**
* Unknown task dependency error
*/
class UnknownTaskDependencyException extends SchematicsException {
constructor(id: TaskId);
}Creating a Custom Engine Host:
import {
EngineHost,
CollectionDescription,
SchematicDescription,
RuleFactory
} from "@angular-devkit/schematics";
class CustomEngineHost implements EngineHost<{}, {}> {
createCollectionDescription(name: string): CollectionDescription<{}> {
return {
name,
path: `/collections/${name}`,
description: `Collection ${name}`
};
}
listSchematicNames(collection: CollectionDescription<{}>): string[] {
// Custom logic to discover schematics
return ['component', 'service', 'module'];
}
createSchematicDescription(
name: string,
collection: CollectionDescription<{}>
): SchematicDescription<{}, {}> | null {
return {
name,
collection,
description: `Schematic ${name}`
};
}
getSchematicRuleFactory<T>(
schematic: SchematicDescription<{}, {}>,
collection: CollectionDescription<{}>
): RuleFactory<T> {
// Return appropriate rule factory
return (options: T) => (tree, context) => {
// Custom schematic logic
return tree;
};
}
// Implement other required methods...
}Workflow orchestration for managing schematic execution lifecycle and reporting.
/**
* Interface for workflow execution with lifecycle management
*/
interface Workflow {
readonly context: Readonly<WorkflowExecutionContext>;
execute(
options: Partial<WorkflowExecutionContext> & RequiredWorkflowExecutionContext
): Observable<void>;
}
/**
* Execution context for workflow operations
*/
interface WorkflowExecutionContext {
collection: string;
schematic: string;
options: object;
debug: boolean;
logger: logging.Logger;
parentContext?: Readonly<WorkflowExecutionContext>;
allowPrivate?: boolean;
}
/**
* Required workflow execution context properties
*/
interface RequiredWorkflowExecutionContext {
collection: string;
schematic: string;
options: object;
}
/**
* Lifecycle events emitted during workflow execution
*/
interface LifeCycleEvent {
kind: 'start' | 'end' | 'workflow-start' | 'workflow-end' | 'post-tasks-start' | 'post-tasks-end';
}
/**
* Abstract base class for workflow implementations
*/
abstract class BaseWorkflow implements Workflow {
protected readonly _engine: Engine<{}, {}>;
protected readonly _engineHost: EngineHost<{}, {}>;
protected readonly _registry: schema.CoreSchemaRegistry;
protected readonly _host: virtualFs.Host;
readonly context: Readonly<WorkflowExecutionContext>;
readonly engine: Engine<{}, {}>;
readonly engineHost: EngineHost<{}, {}>;
readonly registry: schema.SchemaRegistry;
readonly reporter: Observable<DryRunEvent>;
readonly lifeCycle: Observable<LifeCycleEvent>;
constructor(options: BaseWorkflowOptions);
abstract execute(
options: Partial<WorkflowExecutionContext> & RequiredWorkflowExecutionContext
): Observable<void>;
}
interface BaseWorkflowOptions {
host: virtualFs.Host;
engineHost: EngineHost<{}, {}>;
registry?: schema.CoreSchemaRegistry;
force?: boolean;
dryRun?: boolean;
}interface ExecutionOptions {
interactive: boolean;
dryRun: boolean;
force: boolean;
defaults: boolean;
}
interface TaskConfiguration {
name: string;
options: any;
dependencies?: TaskId[];
}
type JsonValue = boolean | number | string | null | JsonArray | JsonObject;
type JsonArray = JsonValue[];
type JsonObject = { [key: string]: JsonValue };
type Url = string;