All files / packages/design-tokens/src/validators validator-utils.js

100% Statements 56/56
97.29% Branches 36/37
100% Functions 8/8
100% Lines 55/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154                56x 56x 56x     56x 56x 56x 2x     56x               4x   4x 2x                 2x 2x 1x   1x 1x                                   7x 7x   7x 13x 13x 7x   6x       7x                   55x     55x 8x   24x       55x 8x     47x 47x 47x   47x 3x     44x                 32x 32x 3x           29x                 47x   47x 22x 22x 25x 1x 1x 1x   1x   24x 24x 1x 1x   24x 24x      
import fs from 'fs';
import path from 'node:path';
 
/**
 * Parse command line arguments for validators
 * @returns {Object} Parsed arguments
 */
export function parseValidatorArgs() {
  const args = process.argv.slice(2);
  const isVerbose = args.includes('--verbose') || args.includes('-v');
  const isReport = args.includes('--report') || args.includes('-r');
 
  // Parse --theme flag
  let themeFilter = null;
  const themeIndex = args.findIndex((arg) => arg.startsWith('--theme='));
  if (themeIndex !== -1) {
    themeFilter = args[themeIndex].split('=')[1];
  }
 
  return { isVerbose, isReport, themeFilter };
}
 
/**
 * Load known issues from the known-issues.json file
 * @returns {Object} Known issues object
 */
export function loadKnownIssues() {
  const knownIssuesPath = path.resolve(process.cwd(), 'src/validators/config/known-issues.json');
 
  if (!fs.existsSync(knownIssuesPath)) {
    return {
      css: { value_mismatches: [], missing: [], extra: [] },
      json: { value_mismatches: [], missing: [], extra: [] },
      aliases: [],
      brand_refs: [],
      light_dark: [],
    };
  }
 
  try {
    const knownIssues = JSON.parse(fs.readFileSync(knownIssuesPath, 'utf8'));
    return knownIssues;
  } catch (error) {
    console.warn(`Warning: Could not parse known-issues.json: ${error.message}`);
    return {
      css: { value_mismatches: [], missing: [], extra: [] },
      json: { value_mismatches: [], missing: [], extra: [] },
      aliases: [],
      brand_refs: [],
      light_dark: [],
    };
  }
}
 
/**
 * Filter issues into known and new issues
 * @param {Array} issues - Array of issue objects
 * @param {Array} knownIssuesList - Array of known issue identifiers
 * @param {string} identifierKey - Key to use for identifying issues (default: 'property')
 * @returns {Object} { newIssues, knownIssues }
 */
export function filterKnownIssues(issues, knownIssuesList, identifierKey = 'property') {
  const newIssues = [];
  const knownIssues = [];
 
  for (const issue of issues) {
    const identifier = issue[identifierKey] || issue.path || issue.alias;
    if (knownIssuesList.includes(identifier)) {
      knownIssues.push(issue);
    } else {
      newIssues.push(issue);
    }
  }
 
  return { newIssues, knownIssues };
}
 
/**
 * Converts a hex color string to RGB values
 * @param {string} hex - Hex color string (with or without #)
 * @returns {Object|null} RGB object {r, g, b} or null if invalid
 */
export function hexToRgb(hex) {
  // Remove # if present
  hex = hex.replace('#', '');
 
  // Handle 3-digit hex
  if (hex.length === 3) {
    hex = hex
      .split('')
      .map((char) => char + char)
      .join('');
  }
 
  if (hex.length !== 6) {
    return null;
  }
 
  const r = parseInt(hex.substring(0, 2), 16);
  const g = parseInt(hex.substring(2, 4), 16);
  const b = parseInt(hex.substring(4, 6), 16);
 
  if (isNaN(r) || isNaN(g) || isNaN(b)) {
    return null;
  }
 
  return { r, g, b };
}
 
/**
 * Creates an ANSI color swatch for terminal display
 * @param {string} hexColor - Hex color string (with or without #)
 * @returns {string} ANSI escape code for colored square
 */
export function colorSwatch(hexColor) {
  const rgb = hexToRgb(hexColor);
  if (!rgb) {
    return '  '; // Return empty space if invalid color
  }
 
  // ANSI escape code for 24-bit color background
  // \x1b[48;2;R;G;Bm sets background color
  // \x1b[0m resets formatting
  return `\x1b[48;2;${rgb.r};${rgb.g};${rgb.b}m  \x1b[0m`;
}
 
/**
 * Exit with appropriate status code and message
 * @param {number} newIssuesCount - Count of new issues
 * @param {number} knownIssuesCount - Count of known issues
 */
export function exitWithStatus(newIssuesCount, knownIssuesCount) {
  console.log('─'.repeat(60));
 
  if (newIssuesCount === 0 && knownIssuesCount === 0) {
    console.log('✅ All validations passed!\n');
    process.exit(0);
  } else if (newIssuesCount === 0 && knownIssuesCount > 0) {
    console.log(`✅ No new issues found (${knownIssuesCount} known issues tracked)`);
    Eif (knownIssuesCount > 0) {
      console.log('ℹ️  Run with --verbose flag to see the full list of known issues\n');
    }
    process.exit(0);
  } else {
    console.log(`❌ Validation failed: ${newIssuesCount} new issue(s) found`);
    if (knownIssuesCount > 0) {
      console.log(`ℹ️  ${knownIssuesCount} known issue(s) tracked`);
      console.log('ℹ️  Run with --verbose flag to see the full list of known issues');
    }
    console.log('');
    process.exit(1);
  }
}