All files / packages/sds-metadata/src/generators stylehooks-metadata.ts

97.5% Statements 39/40
95.83% Branches 23/24
100% Functions 6/6
100% Lines 34/34

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                                        1x     21x 21x 1x   20x       22x 1x   21x                           14x 56x   14x 18x 16x 16x 11x 11x 6x 6x 6x 6x     14x             9x       9x             9x       16x 23x   1x   22x 22x 80x   22x 4x   16x         7x                      
import {
  COSMOS_GLOBAL_TOKENS_FLAT_JSON,
  GLOBAL_STYLING_HOOKS_FILENAME,
  SLDS_GLOBAL_TOKENS_FLAT_JSON,
  VALUE_TO_SLDS_PLUS_STYLING_HOOKS_FILENAME,
  VALUE_TO_SLDS_STYLING_HOOKS_FILENAME,
} from '../constants.js';
import type {
  FlatTokenData,
  StylingHookMetadata,
  StylingHookScope,
  SupportedFileFormat,
  ValueToStyleHookMap,
} from '../types.js';
import { readJSON, writeData } from '../services/file-service.js';
import { resolvePivotValue } from '../utils/value-resolver.js';
 
type Theme = 'slds' | 'cosmos';
type MetadataByScope = Record<StylingHookScope, Record<string, StylingHookMetadata>>;
 
const SCOPES = ['global', 'reference', 'shared', 'component'] as const;
 
function normalizeScope(tokenName: string, scope: string): StylingHookScope {
  const normalized = scope === 'c' ? 'component' : scope;
  if (!SCOPES.includes(normalized as StylingHookScope)) {
    throw new Error(`Token ${tokenName} has unsupported scope ${scope}`);
  }
  return normalized as StylingHookScope;
}
 
function transformToken(token: FlatTokenData, theme: Theme): StylingHookMetadata {
  if (!token.cssProperties?.length) {
    throw new Error(`Token ${token.name} must have nonempty cssProperties`);
  }
  return {
    category: token.category,
    type: token.type,
    description: token.description ?? '',
    properties: token.cssProperties,
    scope: normalizeScope(token.name, token.scope),
    values: { [theme]: token.value },
  };
}
 
function createPivot(
  theme: Theme,
  metadata: MetadataByScope
): Record<string, Array<{ name: string; properties: string[] }>> {
  const pivot: ValueToStyleHookMap = new Map();
  const lookup = Object.assign({}, ...SCOPES.map((scope) => metadata[scope]));
 
  for (const [name, hook] of Object.entries(metadata.global)) {
    if (hook.category === 'shadow') continue;
    const rawValue = hook.values[theme];
    if (!rawValue) continue;
    const value = resolvePivotValue(rawValue, theme, lookup);
    if (/var\(\s*--/i.test(value)) continue;
    const entry = { name, properties: hook.properties };
    const entries = pivot.get(value);
    Iif (entries) entries.push(entry);
    else pivot.set(value, [entry]);
  }
 
  return Object.fromEntries(pivot);
}
 
export async function generateStyleHooksMetadata(
  outputDir: string = process.cwd(),
  format: SupportedFileFormat = 'json'
) {
  const [cosmosTokens, sldsTokens] = await Promise.all([
    readJSON<Record<string, FlatTokenData>>(COSMOS_GLOBAL_TOKENS_FLAT_JSON),
    readJSON<Record<string, FlatTokenData>>(SLDS_GLOBAL_TOKENS_FLAT_JSON),
  ]);
  const metadata: MetadataByScope = {
    global: {},
    reference: {},
    shared: {},
    component: {},
  };
 
  for (const [theme, tokens] of [
    ['cosmos', cosmosTokens],
    ['slds', sldsTokens],
  ] as const) {
    for (const [key, token] of Object.entries(tokens)) {
      if (key.startsWith('slds-is-') || token.deprecated) {
        //console.warn(`${theme} Token ${key} skipped due to deprecated`);
        continue;
      };
      const name = `--${key}`;
      const incoming = transformToken(token, theme);
      const existing = SCOPES.map((scope) => metadata[scope][name]).find(Boolean);
      
      if (existing) {
        existing.values = { ...existing.values, ...incoming.values };
      } else {
        metadata[incoming.scope][name] = incoming;
      }
    }
  }
 
  await Promise.all([
    writeData(GLOBAL_STYLING_HOOKS_FILENAME, metadata, outputDir, format),
    writeData(VALUE_TO_SLDS_STYLING_HOOKS_FILENAME, createPivot('slds', metadata), outputDir, format),
    writeData(
      VALUE_TO_SLDS_PLUS_STYLING_HOOKS_FILENAME,
      createPivot('cosmos', metadata),
      outputDir,
      format
    ),
  ]);
}