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                34x 34x 34x     34x 34x 34x 2x     34x               4x   4x 2x                 2x 2x 2x   1x 1x                                   7x 7x   7x 13x 13x 7x   6x       7x                   57x     57x 8x   24x       57x 8x     49x 49x 49x   49x 3x     46x                 34x 34x 3x           31x                 25x   25x 3x 3x 22x 1x 1x 1x   1x   21x 21x 1x 1x   21x 21x      
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);
  }
}