or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

build-system.mdcli.mdconfiguration.mddev-server.mderror-handling.mdfile-watching.mdindex.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive error system for build failures, node setup issues, and cancellation. Broccoli provides a hierarchy of error classes for different failure scenarios.

Capabilities

BuilderError Class

Base error class for all builder-related errors.

/**
 * Base error class for builder errors
 */
class BuilderError extends Error {
  /** Identifies this as a builder error */
  readonly isBuilderError: boolean;
  
  /**
   * Creates a new BuilderError
   * @param message - Error message (optional)
   */
  constructor(message?: string);
  
  /**
   * Check if an error is a BuilderError
   * @param error - Error to check
   * @returns true if error is a BuilderError
   */
  static isBuilderError(error: any): boolean;
}

Usage Examples:

const { BuilderError } = require("broccoli");

try {
  await builder.build();
} catch (error) {
  if (BuilderError.isBuilderError(error)) {
    console.log('Builder error occurred:', error.message);
    console.log('Is builder error:', error.isBuilderError);
  } else {
    console.log('Non-builder error:', error);
  }
}

// Creating custom builder errors
class CustomBuilderError extends BuilderError {
  constructor(details: string) {
    super(`Custom error: ${details}`);
  }
}

throw new CustomBuilderError('Something went wrong');

BuildError Class

Error thrown during the build process, containing detailed build failure information.

/**
 * Error thrown during build process with detailed failure information
 */
class BuildError extends BuilderError {
  /** Whether this error should be displayed silently */
  readonly isSilent: boolean;
  
  /** Whether this error represents a build cancellation */
  readonly isCancellation: boolean;
  
  /** Detailed error payload with build context */
  readonly broccoliPayload: BroccoliPayloadError;
  
  /**
   * Creates a new BuildError
   * @param originalError - The original error that caused the build failure
   * @param nodeWrapper - Optional node wrapper where the error occurred
   */
  constructor(originalError: any, nodeWrapper?: NodeWrapper);
}

interface BroccoliPayloadError {
  /** Location information for the error */
  location?: {
    file?: string;
    line?: number;
    column?: number;
  };
  
  /** Build node where error occurred */
  node?: NodeWrapper;
  
  /** Stack trace information */
  stack?: string;
  
  /** Additional error details */
  details?: any;
}

Usage Examples:

const { BuildError } = require("broccoli");

try {
  await builder.build();
} catch (error) {
  if (error instanceof BuildError) {
    console.log('Build failed');
    console.log('Silent error:', error.isSilent);
    console.log('Cancellation:', error.isCancellation);
    
    if (error.broccoliPayload) {
      const payload = error.broccoliPayload;
      
      if (payload.location) {
        console.log(`Error at ${payload.location.file}:${payload.location.line}:${payload.location.column}`);
      }
      
      if (payload.node) {
        console.log(`Error in node: ${payload.node.label}`);
      }
      
      if (payload.details) {
        console.log('Error details:', payload.details);
      }
    }
  }
}

// Creating build errors
const nodeWrapper = builder.nodeWrappers[0];
const originalError = new Error('Transform failed');
const buildError = new BuildError(originalError, nodeWrapper);

NodeSetupError Class

Error thrown during node setup phase.

/**
 * Error thrown during node setup phase
 */
class NodeSetupError extends BuilderError {
  /**
   * Creates a new NodeSetupError
   * @param originalError - The original error that caused setup failure
   * @param nodeWrapper - Optional node wrapper being set up
   */
  constructor(originalError: Error, nodeWrapper?: NodeWrapper);
}

Usage Examples:

const { NodeSetupError } = require("broccoli");

try {
  builder.makeNodeWrapper(node);
} catch (error) {
  if (error instanceof NodeSetupError) {
    console.log('Node setup failed:', error.message);
    
    // Access the original error
    console.log('Original error:', error.cause);
    
    // Handle setup failures
    console.log('Failed to set up build node, check plugin configuration');
  }
}

CliError Class

CLI-specific errors for command line interface failures.

/**
 * CLI-specific errors
 */
class CliError extends Error {
  /**
   * Creates a new CliError
   * @param message - Error message
   */
  constructor(message: string);
}

Usage Examples:

const { CliError, cli } = require("broccoli");

try {
  await cli(['build', 'dist', '--invalid-flag']);
} catch (error) {
  if (error instanceof CliError) {
    console.error('CLI Error:', error.message);
    process.exit(1);
  }
}

// Creating CLI errors
function validateArgs(args: string[]): void {
  if (args.length === 0) {
    throw new CliError('No command specified');
  }
  
  const validCommands = ['build', 'serve'];
  if (!validCommands.includes(args[0])) {
    throw new CliError(`Invalid command: ${args[0]}. Valid commands: ${validCommands.join(', ')}`);
  }
}

Cancelation Class

Error class for build cancellation scenarios.

/**
 * Cancellation error for build interruption
 */
class Cancelation extends Error {
  /** Identifies this as a cancellation error */
  readonly isCancelation: boolean;
  
  /** Whether this cancellation should be displayed silently */
  readonly isSilent: boolean;
  
  /**
   * Creates a new Cancelation error
   * @param message - Cancellation message (optional)
   */
  constructor(message?: string);
  
  /**
   * Check if an error is a cancellation error
   * @param error - Error to check
   * @returns true if error represents cancellation
   */
  static isCancelationError(error: any): boolean;
}

Usage Examples:

const { Cancelation } = require("broccoli");

// Handle cancellation
try {
  await builder.build();
} catch (error) {
  if (Cancelation.isCancelationError(error)) {
    console.log('Build was cancelled');
    console.log('Silent cancellation:', error.isSilent);
    
    // Don't treat cancellation as a failure
    process.exit(0);
  }
}

// Manual cancellation
async function buildWithTimeout(builder: Builder, timeoutMs: number): Promise<void> {
  const buildPromise = builder.build();
  
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => {
      reject(new Cancelation('Build timed out'));
    }, timeoutMs);
  });
  
  try {
    await Promise.race([buildPromise, timeoutPromise]);
  } catch (error) {
    if (error instanceof Cancelation) {
      await builder.cancel();
    }
    throw error;
  }
}

Error Handling Patterns

Common patterns for handling different types of errors.

Comprehensive Error Handling:

const { 
  BuilderError, 
  BuildError, 
  NodeSetupError, 
  CliError, 
  Cancelation 
} = require("broccoli");

async function safeBuild(builder: Builder): Promise<boolean> {
  try {
    await builder.build();
    console.log('Build successful');
    return true;
    
  } catch (error) {
    // Handle cancellation
    if (Cancelation.isCancelationError(error)) {
      console.log('Build cancelled');
      return false;
    }
    
    // Handle build errors
    if (error instanceof BuildError) {
      console.error('Build failed:', error.message);
      
      if (!error.isSilent) {
        console.error('Build error details:', error.broccoliPayload);
      }
      
      return false;
    }
    
    // Handle node setup errors
    if (error instanceof NodeSetupError) {
      console.error('Node setup failed:', error.message);
      console.error('Check your Brocfile and plugin configuration');
      return false;
    }
    
    // Handle CLI errors
    if (error instanceof CliError) {
      console.error('CLI error:', error.message);
      return false;
    }
    
    // Handle general builder errors
    if (BuilderError.isBuilderError(error)) {
      console.error('Builder error:', error.message);
      return false;
    }
    
    // Handle unexpected errors
    console.error('Unexpected error:', error);
    throw error;
  }
}

Retry Logic with Error Handling:

async function buildWithRetry(
  builder: Builder, 
  maxRetries: number = 3
): Promise<void> {
  let lastError: Error | null = null;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await builder.build();
      return; // Success
      
    } catch (error) {
      lastError = error;
      
      // Don't retry cancellations
      if (Cancelation.isCancelationError(error)) {
        throw error;
      }
      
      // Don't retry CLI errors
      if (error instanceof CliError) {
        throw error;
      }
      
      // Don't retry node setup errors
      if (error instanceof NodeSetupError) {
        throw error;
      }
      
      // Retry build errors
      if (error instanceof BuildError && attempt < maxRetries) {
        console.log(`Build attempt ${attempt} failed, retrying...`);
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
        continue;
      }
      
      throw error;
    }
  }
  
  throw lastError;
}

Error Reporting and Logging:

const { writeFileSync } from 'fs';
const { join } from 'path';

class ErrorReporter {
  private errorLog: any[] = [];
  
  logError(error: Error, context?: any): void {
    const errorEntry = {
      timestamp: new Date().toISOString(),
      message: error.message,
      stack: error.stack,
      type: error.constructor.name,
      context
    };
    
    // Add specific error details
    if (error instanceof BuildError) {
      errorEntry.context = {
        ...errorEntry.context,
        isSilent: error.isSilent,
        isCancellation: error.isCancellation,
        payload: error.broccoliPayload
      };
    }
    
    if (error instanceof Cancelation) {
      errorEntry.context = {
        ...errorEntry.context,
        isCancelation: error.isCancelation,
        isSilent: error.isSilent
      };
    }
    
    this.errorLog.push(errorEntry);
    
    // Write to file
    this.writeErrorLog();
  }
  
  private writeErrorLog(): void {
    const logPath = join(process.cwd(), 'broccoli-errors.log');
    writeFileSync(logPath, JSON.stringify(this.errorLog, null, 2));
  }
}

// Usage
const errorReporter = new ErrorReporter();

try {
  await builder.build();
} catch (error) {
  errorReporter.logError(error, {
    buildId: builder.buildId,
    outputPath: builder.outputPath,
    nodeCount: builder.nodeWrappers.length
  });
  
  throw error;
}

Graceful Error Recovery:

class ResilientBuilder {
  private builder: Builder;
  private fallbackBuilder?: Builder;
  
  constructor(builder: Builder, fallbackBuilder?: Builder) {
    this.builder = builder;
    this.fallbackBuilder = fallbackBuilder;
  }
  
  async build(): Promise<string> {
    try {
      await this.builder.build();
      return this.builder.outputPath;
      
    } catch (error) {
      console.warn('Primary build failed:', error.message);
      
      // Try fallback builder for certain errors
      if (this.fallbackBuilder && this.shouldUseFallback(error)) {
        console.log('Attempting fallback build...');
        
        try {
          await this.fallbackBuilder.build();
          return this.fallbackBuilder.outputPath;
        } catch (fallbackError) {
          console.error('Fallback build also failed:', fallbackError.message);
          throw error; // Throw original error
        }
      }
      
      throw error;
    }
  }
  
  private shouldUseFallback(error: Error): boolean {
    // Use fallback for build errors but not setup errors
    return error instanceof BuildError && !error.isCancellation;
  }
}