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;
});
}
|