UNPKG

@rbac/rbac

Version:

Blazing Fast, Zero dependency, Hierarchical Role-Based Access Control for Node.js

317 lines (225 loc) 11.8 kB
# Design Document: Logger Color Detection ## Overview This design implements automatic color support detection for the default logger in `src/helpers.ts`. The solution adds a color detection mechanism that checks environment capabilities before applying ANSI escape codes, ensuring readable output across all environments including CI systems, Windows terminals, and redirected output. The implementation follows standard conventions (NO_COLOR, FORCE_COLOR) and uses TTY detection as the primary indicator. The design maintains backward compatibility with the existing logger interface while adding intelligent color handling. ## Architecture The solution consists of three main components: 1. **Color Support Detector**: A function that determines if the environment supports colors 2. **Color Formatter**: A utility that conditionally applies ANSI escape codes based on detection results 3. **Enhanced Logger**: The updated `defaultLogger` function that uses the color formatter ``` ┌─────────────────────────────────────────┐ │ defaultLogger() │ │ (existing interface maintained) │ └──────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ supportsColor() │ │ - Check FORCE_COLOR │ │ - Check NO_COLOR │ │ - Check TTY │ │ - Check CI environment │ │ - Cache result │ └──────────────┬──────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ colorize() │ │ - Apply ANSI codes if supported │ │ - Return plain text if not supported │ └─────────────────────────────────────────┘ ``` ## Components and Interfaces ### 1. Color Support Detection ```typescript function supportsColor(): boolean ``` **Purpose**: Determines if the current environment supports ANSI color codes. **Detection Logic** (in priority order): 1. If `FORCE_COLOR` is set to truthy value → return `true` 2. If `NO_COLOR` is set to any value → return `false` 3. If `process.stdout.isTTY` is `false` or `undefined` → return `false` 4. If in known CI environment with color support → return `true` 5. If `process.stdout.isTTY` is `true` → return `true` 6. Default → return `false` **Environment Variables Checked**: - `FORCE_COLOR`: When set to "1", "true", or any truthy value, forces color output - `NO_COLOR`: When set to any value, disables color output - `CI`: Indicates CI environment - `GITHUB_ACTIONS`, `GITLAB_CI`, `CIRCLECI`: Specific CI platforms that support colors **Caching**: The result is cached in a module-level variable to avoid repeated checks. ### 2. Color Formatter ```typescript function colorize(text: string, colorCode: string, enabled: boolean): string ``` **Parameters**: - `text`: The text to potentially colorize - `colorCode`: The ANSI color code (e.g., "1;32" for bright green) - `enabled`: Whether color support is enabled **Returns**: - If `enabled` is `true`: `\x1b[${colorCode}m${text}\x1b[0m` - If `enabled` is `false`: `text` **Purpose**: Conditionally wraps text in ANSI escape codes. ### 3. Enhanced Default Logger ```typescript function defaultLogger( role: string, operation: string | RegExp, result: boolean ): void ``` **Changes from Current Implementation**: - Calls `supportsColor()` once at the start - Uses `colorize()` helper for all color applications - Maintains identical output structure - Preserves all existing functionality **Color Mappings** (unchanged): - Success (true): Green (`1;32`) - Failure (false): Red (`1;31`) - Role: Yellow (`1;33`) - Operation: Yellow (`1;33`) - RBAC label: White (`1;37`) - Base text: Blue (`1;34`) - Underline: Yellow (`33`) ## Data Models ### Color Support Cache ```typescript let colorSupportCache: boolean | null = null; ``` **Purpose**: Stores the result of color detection to avoid repeated environment checks. **Lifecycle**: - Initialized to `null` - Set on first call to `supportsColor()` - Remains constant for the process lifetime ### Color Code Constants ```typescript const COLORS = { RESET: '0', BRIGHT_GREEN: '1;32', BRIGHT_RED: '1;31', BRIGHT_YELLOW: '1;33', BRIGHT_BLUE: '1;34', BRIGHT_WHITE: '1;37', YELLOW: '33' } as const; ``` **Purpose**: Centralizes color code definitions for maintainability. ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: FORCE_COLOR enables colors *For any* truthy value of FORCE_COLOR environment variable, the logger output should contain ANSI escape codes regardless of TTY status or other environment variables (except when NO_COLOR takes precedence per property 1.7). **Validates: Requirements 1.5** ### Property 2: NO_COLOR disables colors *For any* value of NO_COLOR environment variable (when FORCE_COLOR is not set), the logger output should not contain any ANSI escape codes. **Validates: Requirements 1.6** ### Property 3: Colors present when enabled *For any* combination of role (string), operation (string or RegExp), and result (boolean), when color support is detected, the logger output should contain ANSI escape codes. **Validates: Requirements 2.1** ### Property 4: No colors when disabled *For any* combination of role (string), operation (string or RegExp), and result (boolean), when color support is not detected, the logger output should not contain any ANSI escape codes (specifically, should not contain the pattern `\x1b[`). **Validates: Requirements 2.2** ### Property 5: Content preservation *For any* combination of role (string), operation (string or RegExp), and result (boolean), removing all ANSI escape codes from colored output should produce the same text content as plain text output, preserving all information. **Validates: Requirements 2.3, 2.4** ### Property 6: Safe error handling *For any* unexpected or malformed environment variable values, the color detection function should not throw exceptions and should return a valid boolean result. **Validates: Requirements 4.2** ## Error Handling ### Environment Variable Errors **Strategy**: Defensive checks with safe defaults - If `process.stdout` is `undefined` or `null`, default to `false` (plain text) - If environment variable access throws an error, catch and default to `false` - If `process.stdout.isTTY` is `undefined`, treat as `false` **Implementation**: ```typescript try { const forceColor = process.env.FORCE_COLOR; // ... rest of logic } catch (error) { return false; // Safe default } ``` ### TTY Detection Errors **Strategy**: Graceful degradation - Wrap `process.stdout.isTTY` access in try-catch - Default to `false` if access fails - Log errors only in development mode (not in production) ### Caching Errors **Strategy**: Re-evaluate on cache access failure - If cache read fails, re-run detection - If cache write fails, continue without caching - Never throw errors due to caching issues ## Testing Strategy ### Dual Testing Approach This feature requires both unit tests and property-based tests to ensure comprehensive coverage: - **Unit tests**: Verify specific examples, edge cases, and error conditions - **Property tests**: Verify universal properties across all inputs Together, these approaches provide comprehensive coverage where unit tests catch concrete bugs and property tests verify general correctness. ### Property-Based Testing **Library**: fast-check (for TypeScript/JavaScript) **Configuration**: - Minimum 100 iterations per property test - Each test tagged with format: **Feature: logger-color-detection, Property {number}: {property_text}** **Property Test Cases**: 1. **Property 1: FORCE_COLOR enables colors** - Generate: Random truthy values for FORCE_COLOR - Setup: Set FORCE_COLOR env var, clear NO_COLOR - Execute: Call defaultLogger with random inputs - Assert: Output contains `\x1b[` pattern 2. **Property 2: NO_COLOR disables colors** - Generate: Random values for NO_COLOR - Setup: Set NO_COLOR env var, clear FORCE_COLOR - Execute: Call defaultLogger with random inputs - Assert: Output does not contain `\x1b[` pattern 3. **Property 3: Colors present when enabled** - Generate: Random role (string), operation (string or RegExp), result (boolean) - Setup: Mock supportsColor to return true - Execute: Call defaultLogger - Assert: Output contains ANSI escape codes 4. **Property 4: No colors when disabled** - Generate: Random role (string), operation (string or RegExp), result (boolean) - Setup: Mock supportsColor to return false - Execute: Call defaultLogger - Assert: Output does not contain `\x1b[` pattern 5. **Property 5: Content preservation** - Generate: Random role (string), operation (string or RegExp), result (boolean) - Execute: Call defaultLogger with colors enabled and disabled - Assert: Stripping ANSI codes from colored output equals plain output 6. **Property 6: Safe error handling** - Generate: Random strings including malformed values - Setup: Set environment variables to generated values - Execute: Call supportsColor() - Assert: No exceptions thrown, returns boolean ### Unit Testing **Test Cases**: 1. **FORCE_COLOR precedence over NO_COLOR** (Example test for Requirements 1.7) - Set both FORCE_COLOR and NO_COLOR - Verify colors are enabled 2. **Backward compatibility** (Example test for Requirements 3.4) - Call logger with known inputs in color-supporting environment - Verify output matches expected format 3. **Undefined stdout handling** (Edge case for Requirements 4.1) - Mock process.stdout as undefined - Verify plain text output 4. **Error in detection defaults to plain text** (Example test for Requirements 4.3) - Mock process.stdout.isTTY to throw error - Verify supportsColor returns false 5. **Caching behavior** (Example test for Requirements 4.4) - Call supportsColor multiple times - Verify environment is only checked once 6. **Non-TTY with FORCE_COLOR** (Example test for Requirements 5.5) - Set stdout.isTTY to false - Set FORCE_COLOR - Verify colors are enabled ### Integration Testing **Scenarios**: 1. Test in actual CI environment (GitHub Actions) 2. Test with output redirected to file 3. Test in Windows terminal with and without ANSI support 4. Test with various combinations of environment variables ### Test Coverage Goals - 100% coverage of color detection logic - 100% coverage of error handling paths - All edge cases explicitly tested - All properties verified with minimum 100 iterations each