or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

architect.mdbuild-context.mdbuilder-creation.mdindex.mdjob-system.mdnode-integration.mdprogress-reporting.mdtesting.md
tile.json

builder-creation.mddocs/

Builder Creation

System for creating custom builders that integrate with the Angular CLI build system. Builders are the fundamental unit of work in Angular DevKit Architect, encapsulating build tasks with proper error handling, progress reporting, and observables support.

Capabilities

Create Builder Function

Factory function for creating builders from handler functions.

/**
 * Create a builder from a handler function
 * @param fn - Handler function that performs the build work
 * @returns Builder instance ready for registration
 */
function createBuilder<OptT = json.JsonObject, OutT extends BuilderOutput = BuilderOutput>(
  fn: BuilderHandlerFn<OptT>
): Builder<OptT & json.JsonObject>;

/**
 * Handler function signature for builders
 */
interface BuilderHandlerFn<A> {
  /**
   * Builder implementation function
   * @param input - Validated input options for the builder
   * @param context - Builder context with workspace access and utilities
   * @returns BuilderOutput, Promise, Observable, or AsyncIterable
   */
  (input: A, context: BuilderContext): BuilderOutputLike;
}

/**
 * Accepted return types from builder handler functions
 */
type BuilderOutputLike = ObservableInput<BuilderOutput> | BuilderOutput;

Usage Examples:

import { createBuilder, BuilderContext, BuilderOutput } from "@angular-devkit/architect";
import { JsonObject } from "@angular-devkit/core";

// Simple synchronous builder
interface Options extends JsonObject {
  message: string;
}

export default createBuilder<Options>((options, context) => {
  context.logger.info(`Hello: ${options.message}`);
  
  return { success: true };
});

// Asynchronous builder with Promise
export const asyncBuilder = createBuilder<Options>(async (options, context) => {
  context.reportStatus("Starting async work...");
  
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  context.reportStatus("Async work complete");
  return { success: true };
});

// Observable builder for streaming results
import { of } from "rxjs";

export const streamingBuilder = createBuilder<Options>((options, context) => {
  return of(
    { success: false }, // intermediate result
    { success: true }   // final result  
  );
});

// Builder with progress reporting
export const progressBuilder = createBuilder<Options>((options, context) => {
  return new Promise<BuilderOutput>((resolve) => {
    let current = 0;
    const total = 10;
    
    const interval = setInterval(() => {
      current++;
      context.reportProgress(current, total, `Step ${current}/${total}`);
      
      if (current >= total) {
        clearInterval(interval);
        resolve({ success: true });
      }
    }, 100);
  });
});

Builder Interface

The builder object returned by createBuilder().

/**
 * A Builder instance created by createBuilder()
 */
interface Builder<OptionT extends json.JsonObject = json.JsonObject> {
  /**
   * Job handler that processes builder execution
   */
  handler: JobHandler<json.JsonObject, BuilderInput, BuilderOutput>;
  
  /**
   * Marker indicating this is a builder created by createBuilder()
   */
  [BuilderSymbol]: true;
  
  /**
   * Version of the architect library used to create this builder
   */
  [BuilderVersionSymbol]: string;
  
  /**
   * TypeScript helper for option types
   */
  __OptionT: OptionT;
}

Builder Output

Standard output interface for all builders.

/**
 * Standard output interface that all builders must return
 */
interface BuilderOutput extends json.JsonObject {
  /**
   * Whether the builder execution was successful
   */
  success: boolean;
  
  /**
   * Optional error message if success is false
   */
  error?: string;
  
  /**
   * Additional custom output properties
   */
  [key: string]: json.JsonValue;
}

/**
 * Type guard to check if an object is a valid BuilderOutput
 */
function isBuilderOutput(obj: any): obj is BuilderOutput;

Usage Examples:

// Simple success output
return { success: true };

// Error output with message
return { success: false, error: "Build failed: missing file" };

// Output with additional data
return {
  success: true,
  outputPath: "/dist/my-app",
  assets: ["main.js", "styles.css"],
  warnings: []
};

// Check if value is valid output
const result = await someOperation();
if (isBuilderOutput(result)) {
  console.log(result.success ? "Success" : "Failed");
}

Builder Input

Input interface passed to builder handlers.

/**
 * Input passed to builder handlers, includes validated options
 */
interface BuilderInput extends json.JsonObject {
  /**
   * Validated options from workspace configuration and command line
   */
  options: json.JsonObject;
  
  /**
   * Target information if builder was scheduled via target
   */
  target?: Target;
  
  /**
   * Builder metadata
   */
  info: BuilderInfo;
  
  /**
   * Workspace root directory
   */
  workspaceRoot: string;
  
  /**
   * Current working directory
   */
  currentDirectory: string;
  
  /**
   * Unique identifier for this build
   */
  id: number;
}

Error Handling

Builders should handle errors gracefully and return appropriate output.

// Error handling examples
export const robustBuilder = createBuilder<Options>((options, context) => {
  try {
    // Perform build work
    const result = performBuildWork(options);
    
    return { success: true, output: result };
  } catch (error) {
    context.logger.error(`Build failed: ${error.message}`);
    return { success: false, error: error.message };
  }
});

// Async error handling
export const asyncRobustBuilder = createBuilder<Options>(async (options, context) => {
  try {
    const result = await performAsyncWork(options);
    return { success: true, result };
  } catch (error) {
    return { success: false, error: error.message };
  }
});

// Observable error handling
import { catchError, of } from "rxjs";

export const observableRobustBuilder = createBuilder<Options>((options, context) => {
  return performObservableWork(options).pipe(
    catchError(error => {
      context.logger.error(error.message);
      return of({ success: false, error: error.message });
    })
  );
});

AsyncIterable Support

Builders can return AsyncIterables for streaming results.

// AsyncIterable builder example
export const asyncIterableBuilder = createBuilder<Options>(async function* (options, context) {
  const tasks = ['setup', 'compile', 'optimize', 'bundle'];
  
  for (let i = 0; i < tasks.length; i++) {
    context.reportProgress(i, tasks.length, `Running ${tasks[i]}...`);
    
    // Simulate work
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Yield intermediate result
    if (i < tasks.length - 1) {
      yield { success: false }; // Still working
    }
  }
  
  // Final result
  yield { success: true };
});

Builder Registration

Builders are typically registered through builder configuration files.

{
  "builders": {
    "my-builder": {
      "implementation": "./builders/my-builder",
      "schema": "./builders/my-builder-schema.json",
      "description": "My custom builder"
    }
  }
}

Usage in workspace:

{
  "projects": {
    "my-app": {
      "architect": {
        "custom-build": {
          "builder": "@my-package/builders:my-builder",
          "options": {
            "message": "Hello World"
          }
        }
      }
    }
  }
}
# Run the custom builder
ng run my-app:custom-build