All files / packages/design-system/scripts/core core-css-classifier.js

97.27% Statements 107/110
86.36% Branches 38/44
100% Functions 12/12
97.22% Lines 70/72

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        3x 3x       3x     3x 3x 3x 3x     245x       216x 216x   216x 216x 216x 216x 216x 216x 214x       216x     216x       252x 252x   252x 182x 182x   182x 182x 182x 178x     182x     252x       252x 252x 142x   252x   250x 250x 250x 392x 392x   250x   250x       44x       308x 44x   44x 302x 1x     301x 301x 1x   300x 1x       299x       299x     41x 287x 1x       40x       19x 19x   19x 105x 105x 105x 105x       105x                   19x 33x 31x         15x    
import crypto from 'node:crypto';
 
import { CORE_CSS_ARTIFACTS } from './core-css-artifacts.js';
 
const VERSION_TOKEN = String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
const GENERATED_STAMP = new RegExp(
  String.raw`^ \* design-system ${VERSION_TOKEN} \([^\s()]+\)(?=\r?$)`,
  'm',
);
const LDS_BANNER_STAMP = new RegExp(
  String.raw`/\*! Lightning Design System ${VERSION_TOKEN} \*/`,
);
const GENERATED_STAMP_PREFIX = Buffer.from(' * design-system ');
const LDS_BANNER_STAMP_PREFIX = Buffer.from('/*! Lightning Design System ');
const LDS_BANNER_STAMP_END = Buffer.from(' */');
const NORMALIZED_STAMP = Buffer.from('<core-css-release-stamp>');
 
export function hashCoreCss(content) {
  return crypto.createHash('sha256').update(content).digest('hex');
}
 
function findGeneratedStamp(content) {
  const stamps = [];
  let start = content.indexOf(GENERATED_STAMP_PREFIX);
 
  while (start !== -1) {
    Eif (start === 0 || content[start - 1] === 0x0a) {
      const newline = content.indexOf(0x0a, start);
      const lineEnd = newline === -1 ? content.length : newline;
      const match = GENERATED_STAMP.exec(content.subarray(start, lineEnd).toString('latin1'));
      if (match) {
        stamps.push({ start, end: start + match[0].length });
      }
    }
 
    start = content.indexOf(GENERATED_STAMP_PREFIX, start + 1);
  }
 
  return stamps;
}
 
function findLdsBannerStamps(content) {
  const stamps = [];
  let start = content.indexOf(LDS_BANNER_STAMP_PREFIX);
 
  while (start !== -1) {
    const end = content.indexOf(LDS_BANNER_STAMP_END, start + LDS_BANNER_STAMP_PREFIX.length);
    Iif (end === -1) break;
 
    const tokenEnd = end + LDS_BANNER_STAMP_END.length;
    const match = LDS_BANNER_STAMP.exec(content.subarray(start, tokenEnd).toString('latin1'));
    if (match?.index === 0 && match[0].length === tokenEnd - start) {
      stamps.push({ start, end: tokenEnd });
    }
 
    start = content.indexOf(LDS_BANNER_STAMP_PREFIX, start + 1);
  }
 
  return stamps;
}
 
export function normalizeCoreCssStamp(content, artifactId) {
  const generatedStamps = artifactId === 'sldsTouch' ? [] : findGeneratedStamp(content);
  const stamps = [...generatedStamps, ...findLdsBannerStamps(content)].sort(
    (left, right) => left.start - right.start,
  );
  if (stamps.length === 0) return Buffer.from(content);
 
  const normalized = [];
  let offset = 0;
  for (const stamp of stamps) {
    normalized.push(content.subarray(offset, stamp.start), NORMALIZED_STAMP);
    offset = stamp.end;
  }
  normalized.push(content.subarray(offset));
 
  return Buffer.concat(normalized);
}
 
export function validateCoreCssArtifactSet(artifactSet, label = 'artifact set') {
  Iif (!Array.isArray(artifactSet)) {
    throw new Error(`${label} must be an array`);
  }
 
  const expectedById = new Map(CORE_CSS_ARTIFACTS.map((artifact) => [artifact.id, artifact]));
  const artifactsById = new Map();
 
  for (const artifact of artifactSet) {
    if (artifactsById.has(artifact.id)) {
      throw new Error(`${label} has duplicate artifact id: ${artifact.id}`);
    }
 
    const expected = expectedById.get(artifact.id);
    if (!expected) {
      throw new Error(`${label} has unexpected artifact: ${artifact.id}`);
    }
    if (artifact.path !== expected.gitcorePath) {
      throw new Error(
        `${label} path mismatch for ${artifact.id}: expected ${expected.gitcorePath}, received ${artifact.path}`,
      );
    }
    Iif (!Buffer.isBuffer(artifact.content)) {
      throw new Error(`${label} content for ${artifact.id} must be a Buffer`);
    }
 
    artifactsById.set(artifact.id, artifact);
  }
 
  for (const { id } of CORE_CSS_ARTIFACTS) {
    if (!artifactsById.has(id)) {
      throw new Error(`${label} is missing artifact: ${id}`);
    }
  }
 
  return artifactsById;
}
 
export function classifyCoreCssArtifacts(baselineArtifacts, candidateArtifacts) {
  const baselineById = validateCoreCssArtifactSet(baselineArtifacts, 'baseline artifact set');
  const candidateById = validateCoreCssArtifactSet(candidateArtifacts, 'candidate artifact set');
 
  const artifacts = CORE_CSS_ARTIFACTS.map(({ id, gitcorePath }) => {
    const baseline = baselineById.get(id).content;
    const candidate = candidateById.get(id).content;
    const rawEqual = baseline.equals(candidate);
    const stampNormalizedEqual = normalizeCoreCssStamp(baseline, id).equals(
      normalizeCoreCssStamp(candidate, id),
    );
 
    return {
      id,
      path: gitcorePath,
      rawEqual,
      stampNormalizedEqual,
      baselineSha256: hashCoreCss(baseline),
      candidateSha256: hashCoreCss(candidate),
    };
  });
 
  let verdict = 'no-change';
  if (artifacts.some(({ rawEqual }) => !rawEqual)) {
    verdict = artifacts.every(({ stampNormalizedEqual }) => stampNormalizedEqual)
      ? 'version-only'
      : 'content-change';
  }
 
  return { verdict, artifacts };
}