Rich context interface providing builders access to workspace information, target scheduling, and progress reporting. The BuilderContext is passed as the second argument to all builder handler functions and provides essential utilities for interacting with the workspace and reporting progress.
The main context interface passed to builder handlers.
/**
* Context provided to builder handlers with workspace access and utilities
*/
interface BuilderContext {
/**
* Unique identifier for this build context, same as corresponding run ID
*/
id: number;
/**
* Builder information and metadata
*/
builder: BuilderInfo;
/**
* Logger instance for this builder
*/
logger: logging.LoggerApi;
/**
* Absolute workspace root directory path
*/
workspaceRoot: string;
/**
* Current directory where builder was invoked
*/
currentDirectory: string;
/**
* Target information if builder was scheduled via target
*/
target?: Target;
/**
* Schedule another target within the same workspace
* @param target - Target to schedule
* @param overrides - Options to override workspace configuration
* @param scheduleOptions - Additional scheduling options
* @returns Promise resolving to BuilderRun
*/
scheduleTarget(
target: Target,
overrides?: json.JsonObject,
scheduleOptions?: ScheduleOptions
): Promise<BuilderRun>;
/**
* Schedule a builder by name
* @param builderName - Builder name in format "package:builder"
* @param options - Options to pass to builder
* @param scheduleOptions - Additional scheduling options
* @returns Promise resolving to BuilderRun
*/
scheduleBuilder(
builderName: string,
options?: json.JsonObject,
scheduleOptions?: ScheduleOptions
): Promise<BuilderRun>;
/**
* Get options for a target from workspace configuration
* @param target - Target to get options for
* @returns Promise resolving to target options
*/
getTargetOptions(target: Target): Promise<json.JsonObject>;
/**
* Get project metadata from workspace
* @param projectName - Name of project or target with project
* @returns Promise resolving to project metadata
*/
getProjectMetadata(projectName: string): Promise<json.JsonObject>;
getProjectMetadata(target: Target): Promise<json.JsonObject>;
/**
* Get builder name for a target
* @param target - Target to resolve builder name for
* @returns Promise resolving to builder name
*/
getBuilderNameForTarget(target: Target): Promise<string>;
/**
* Validate options against a builder schema
* @param options - Options to validate
* @param builderName - Builder name to validate against
* @returns Promise resolving to validated options
*/
validateOptions<T extends json.JsonObject = json.JsonObject>(
options: json.JsonObject,
builderName: string
): Promise<T>;
/**
* Report that the builder is now running
*/
reportRunning(): void;
/**
* Update the status message displayed to users
* @param status - Status message to display
*/
reportStatus(status: string): void;
/**
* Report progress with current and total values
* @param current - Current progress value
* @param total - Total progress value (optional)
* @param status - Status message (optional)
*/
reportProgress(current: number, total?: number, status?: string): void;
/**
* Add cleanup logic to run when context is being stopped
* @param teardown - Function to run during cleanup
*/
addTeardown(teardown: () => Promise<void> | void): void;
}Usage Examples:
import { createBuilder } from "@angular-devkit/architect";
export default createBuilder((options, context) => {
// Access workspace information
console.log(`Workspace root: ${context.workspaceRoot}`);
console.log(`Current directory: ${context.currentDirectory}`);
console.log(`Builder: ${context.builder.builderName}`);
// Use logger
context.logger.info("Starting build process");
context.logger.warn("This is a warning");
// Report progress
context.reportStatus("Initializing...");
context.reportProgress(0, 100, "Starting");
// Perform work...
context.reportProgress(50, 100, "Halfway done");
return { success: true };
});Schedule other targets or builders from within a builder.
// Schedule dependent targets
export const complexBuilder = createBuilder(async (options, context) => {
// Build dependencies first
const libBuild = await context.scheduleTarget(
{ project: "my-lib", target: "build" }
);
const libResult = await libBuild.result;
if (!libResult.success) {
return { success: false, error: "Library build failed" };
}
// Now build the main project
const appBuild = await context.scheduleTarget(
{ project: "my-app", target: "build" },
{ buildOptimizer: true } // override options
);
return appBuild.result;
});
// Schedule builder by name
export const builderSchedulingExample = createBuilder(async (options, context) => {
// Run linting first
const lintRun = await context.scheduleBuilder(
"@angular-eslint/builder:lint",
{ lintFilePatterns: ["src/**/*.ts"] }
);
const lintResult = await lintRun.result;
if (!lintResult.success) {
context.logger.warn("Linting failed, continuing anyway");
}
// Continue with main build logic
return { success: true };
});Access workspace configuration and project metadata.
export const workspaceAwareBuilder = createBuilder(async (options, context) => {
if (!context.target) {
return { success: false, error: "Target required" };
}
// Get target options
const targetOptions = await context.getTargetOptions(context.target);
context.logger.info(`Target options: ${JSON.stringify(targetOptions)}`);
// Get project metadata
const projectMeta = await context.getProjectMetadata(context.target.project);
const projectType = projectMeta.projectType;
if (projectType === "application") {
// Application-specific logic
context.logger.info("Building application");
} else if (projectType === "library") {
// Library-specific logic
context.logger.info("Building library");
}
// Get builder name for another target
const testTarget = { ...context.target, target: "test" };
try {
const testBuilderName = await context.getBuilderNameForTarget(testTarget);
context.logger.info(`Test builder: ${testBuilderName}`);
} catch (error) {
context.logger.info("No test target configured");
}
return { success: true };
});Provide detailed progress updates during build execution.
export const progressReportingBuilder = createBuilder(async (options, context) => {
const tasks = [
"Analyzing dependencies",
"Compiling TypeScript",
"Optimizing bundle",
"Generating assets",
"Writing output"
];
context.reportRunning(); // Mark as running
for (let i = 0; i < tasks.length; i++) {
const task = tasks[i];
context.reportStatus(task);
context.reportProgress(i, tasks.length, task);
// Simulate work
await new Promise(resolve => setTimeout(resolve, 1000));
context.logger.info(`Completed: ${task}`);
}
context.reportProgress(tasks.length, tasks.length, "Complete");
return { success: true };
});Validate options against builder schemas.
interface MyBuilderOptions {
outputPath: string;
optimization: boolean;
target: "es5" | "es2015" | "es2017";
}
export const validatingBuilder = createBuilder(async (options, context) => {
// Validate options against another builder's schema
try {
const validatedOptions = await context.validateOptions(
{ outputPath: "./dist", optimization: true },
"@angular-devkit/build-angular:browser"
);
context.logger.info("Options are valid");
} catch (error) {
return { success: false, error: `Invalid options: ${error.message}` };
}
// Use typed options
const typedOptions = options as MyBuilderOptions;
context.logger.info(`Building to ${typedOptions.outputPath}`);
return { success: true };
});Register cleanup logic that runs when the builder is stopped.
export const cleanupBuilder = createBuilder(async (options, context) => {
const tempFiles: string[] = [];
// Register cleanup
context.addTeardown(async () => {
context.logger.info("Cleaning up temporary files");
for (const file of tempFiles) {
await fs.unlink(file).catch(() => {}); // Ignore errors
}
});
// Create temporary files during build
const tempFile = `/tmp/build-${context.id}.tmp`;
await fs.writeFile(tempFile, "temporary data");
tempFiles.push(tempFile);
// Register additional cleanup
context.addTeardown(() => {
context.logger.info("Final cleanup completed");
});
return { success: true };
});Use the provided logger for consistent output formatting.
export const loggingBuilder = createBuilder((options, context) => {
const logger = context.logger;
// Different log levels
logger.debug("Debug information");
logger.info("General information");
logger.warn("Warning message");
logger.error("Error message");
logger.fatal("Fatal error");
// Create child loggers
const childLogger = logger.createChild("sub-task");
childLogger.info("Child logger message");
// Log structured data
logger.info("Build configuration", {
outputPath: "./dist",
optimization: true,
sourceMap: false
});
return { success: true };
});