All files / packages/icons/scripts generate-changes.ts

85.1% Statements 40/47
76.31% Branches 29/38
93.33% Functions 14/15
88.09% Lines 37/42

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                                                        7x 8x 8x 2x 2x 1x   1x                 8x 8x 9x 7x   8x       16x 16x               3x 3x 5x 3x 3x 2x     3x                   16x   15x             9x 9x                 2x         2x 2x 2x       2x         2x 2x 2x   2x                   2x 2x                     5x            
/**
 * Generate changes module
 * Pure generators for content strings with no file I/O side effects.
 * Each planner takes the current contents of a target repo file and returns the
 * entries it would add plus the updated file, or null when nothing is missing.
 */
 
import type { ParsedIcon } from './csv-parser.js';
import {
  planReleaseNotesChange as planSharedReleaseNotesChange,
  type ReleaseNotesOptions,
} from '../../sds-release-manager/src/services/release-notes-service';
 
export interface PlannedChange {
  /** Only the lines being added, for previews. */
  added: string;
  /** Full file contents after the addition. */
  content: string;
}
 
/**
 * Resolve palette color names (e.g. "pink-60") to literal hex, since bg YAML and
 * icon.type.css need hex rather than the var() reference csv-parser produces.
 * Hex colors and empty colors pass through unchanged.
 * @param palette - Palette name to hex, e.g. { 'pink-60': '#ff538a' }
 * @throws if a color name isn't in the palette
 */
export function resolvePaletteHex(icons: ParsedIcon[], palette: Record<string, string>): ParsedIcon[] {
  return icons.map((icon) => {
    const color = icon.color.trim();
    if (!color || color.startsWith('#')) return icon;
    const hex = palette[color];
    if (!hex) {
      throw new Error(`Unknown palette color "${color}" for ${icon.icon_name}`);
    }
    return { ...icon, color_hex: hex };
  });
}
 
/**
 * Plan additions to design-tokens/bg-standard.yml or bg-actions.yml.
 * Icons whose token key already exists are skipped.
 */
export function planBgYmlChange(existing: string, icons: ParsedIcon[]): PlannedChange | null {
  const lines = new Set(existing.split('\n'));
  const entries = icons
    .filter((icon) => !lines.has(`  ${bgYmlKey(icon)}:`))
    .map((icon) => `  ${bgYmlKey(icon)}:\n    value: '${(icon.color_hex || '').toUpperCase()}'`);
 
  return entries.length > 0 ? appendBlock(existing, entries.join('\n')) : null;
}
 
function bgYmlKey(icon: ParsedIcon): string {
  const name = icon.icon_name.toUpperCase();
  return icon.icon_type === 'action' ? `ACTION_${name}` : name;
}
 
/**
 * Plan additions to design-system-2 icon/icon.type.css for standard and action icons.
 * Icons whose selector already exists are skipped.
 */
export function planIconTypeCssChange(existing: string, icons: ParsedIcon[]): PlannedChange | null {
  const lines = new Set(existing.split('\n'));
  const rules = icons.flatMap((icon) => {
    if (icon.icon_type !== 'standard' && icon.icon_type !== 'action') return [];
    const selector = `.slds-icon-${icon.icon_type}-${icon.icon_name.replaceAll('_', '-')}`;
    if (lines.has(`${selector} {`)) return [];
    return [`${selector} {\n  --slds-c-icon-color-background: ${hexToRgb(icon.color_hex ?? null)};\n}`];
  });
 
  return rules.length > 0 ? appendBlock(existing, rules.join('\n')) : null;
}
 
/** Plan icon notes in the explicit SLDSv1 version using the shared authoring service. */
export function planReleaseNotesChange(
  existing: string,
  icons: ParsedIcon[],
  iconsVersion: string,
  target: Pick<ReleaseNotesOptions, 'version' | 'date'>,
): PlannedChange | null {
  return planSharedReleaseNotesChange(existing, {
    ...target,
    icons: icons.map(({ icon_type, icon_name }) => ({ icon_type, icon_name })),
    iconsVersion,
  });
}
 
/** Append a block at the end of a file, keeping a single trailing newline. */
function appendBlock(existing: string, added: string): PlannedChange {
  const base = existing.length === 0 || existing.endsWith('\n') ? existing : `${existing}\n`;
  return { added, content: `${base}${added}\n` };
}
 
/**
 * Convert hex color to RGB format
 * @param hex - Hex color string (e.g., "#1b96ff" or "1b96ff")
 * @returns RGB color string (e.g., "rgb(27, 150, 255)") or empty string if invalid
 */
export function hexToRgb(hex: string | null): string {
  Iif (!hex) {
    return '';
  }
 
  // Remove # if present
  let cleanHex = hex;
  Eif (cleanHex[0] === '#') {
    cleanHex = cleanHex.slice(1);
  }
 
  // Validate hex format
  Iif (!/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
    return '';
  }
 
  // Parse RGB components
  const r = parseInt(cleanHex.slice(0, 2), 16);
  const g = parseInt(cleanHex.slice(2, 4), 16);
  const b = parseInt(cleanHex.slice(4, 6), 16);
 
  return `rgb(${r}, ${g}, ${b})`;
}
 
/**
 * Generate metadata JSON content for an icon
 * @param icon - Parsed icon object
 * @returns JSON content string
 */
export function generateMetadataJson(icon: ParsedIcon): string {
  // Match Prettier (printWidth 110): one line when it fits, otherwise one synonym per line.
  const inline = `  "synonyms": ${JSON.stringify(icon.synonyms).replace(/","/g, '", "')}`;
  return inline.length <= 110
    ? `{\n${inline}\n}\n`
    : `${JSON.stringify({ synonyms: icon.synonyms }, null, 2)}\n`;
}
 
/**
 * Sort icons by icon type
 * @param icons - Array of parsed icons
 * @returns Sorted array of icons
 */
export function sortByIconType(icons: ParsedIcon[]): ParsedIcon[] {
  return icons.sort((i1, i2) => {
    if (i1.icon_type < i2.icon_type) return -1;
    if (i1.icon_type > i2.icon_type) return 1;
    return 0;
  });
}