css-is-awesome 1.16.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -342,7 +342,19 @@ function validateTokenSet(declared, contract) {
342
342
  for (const optional of Array.isArray(contract.optional) ? contract.optional : []) {
343
343
  if (!declared.has(optional)) optionalMissing.push(optional);
344
344
  }
345
- return { ok: missing.length === 0, missing, optionalMissing, declaredCount: declared.size };
345
+ // Contract 1.2: group by the feature each optional token enables, so the
346
+ // report can say "missing the tokens for print" instead of listing 41 names.
347
+ const featureOf = {};
348
+ const features = contract.features && typeof contract.features === 'object' ? contract.features : {};
349
+ for (const [feature, def] of Object.entries(features)) {
350
+ for (const t of (def && Array.isArray(def.tokens)) ? def.tokens : []) featureOf[t] = feature;
351
+ }
352
+ const optionalMissingByFeature = {};
353
+ for (const t of optionalMissing) {
354
+ const f = featureOf[t] || 'other';
355
+ (optionalMissingByFeature[f] = optionalMissingByFeature[f] || []).push(t);
356
+ }
357
+ return { ok: missing.length === 0, missing, optionalMissing, optionalMissingByFeature, declaredCount: declared.size };
346
358
  }
347
359
 
348
360
  // -----------------------------------------------------------
@@ -367,6 +379,7 @@ function validateText(text, contract, options) {
367
379
  declaredCount: 0,
368
380
  missing: [],
369
381
  optionalMissing: [],
382
+ optionalMissingByFeature: {},
370
383
  themes: null,
371
384
  a11y: null,
372
385
  error: null,
@@ -389,6 +402,7 @@ function validateText(text, contract, options) {
389
402
  declaredCount: v.declaredCount,
390
403
  missing: v.missing,
391
404
  optionalMissing: v.optionalMissing,
405
+ optionalMissingByFeature: v.optionalMissingByFeature,
392
406
  a11y: null,
393
407
  };
394
408
  if (wantA11y) theme.a11y = a11y.auditThemeTokens({ name: b.name, values: b.values });
@@ -417,6 +431,7 @@ function validateText(text, contract, options) {
417
431
  result.declaredCount = v.declaredCount;
418
432
  result.missing = v.missing;
419
433
  result.optionalMissing = v.optionalMissing;
434
+ result.optionalMissingByFeature = v.optionalMissingByFeature;
420
435
  result.ok = v.ok;
421
436
  if (wantA11y) {
422
437
  const inferredName = label !== '(pasted CSS)' ? (path.basename(path.dirname(label)) || path.basename(label, '.css')) : 'theme';
@@ -456,11 +471,18 @@ function relForDisplay(p) {
456
471
 
457
472
  // Optional tokens a theme leaves to the library default. Info only — shown as
458
473
  // a count, or listed with --show-optional. Never affects the exit code.
459
- function optionalInfo(optionalMissing, indent) {
474
+ function optionalInfo(optionalMissing, byFeature, indent) {
460
475
  const list = Array.isArray(optionalMissing) ? optionalMissing : [];
461
476
  if (!list.length) return;
462
- console.log(`${indent}${dim(`i ${list.length} optional token(s) not declared — library default applies`)}`);
463
- if (SHOW_OPTIONAL) for (const token of list) console.log(`${indent} ${dim(token)}`);
477
+ const groups = byFeature && typeof byFeature === 'object' ? byFeature : {};
478
+ const summary = Object.entries(groups).map(([f, ts]) => `${f} ${ts.length}`).join(' · ');
479
+ console.log(`${indent}${dim(`i ${list.length} optional token(s) not declared — library default applies${summary ? ` (${summary})` : ''}`)}`);
480
+ if (SHOW_OPTIONAL) {
481
+ for (const [f, ts] of Object.entries(groups)) {
482
+ console.log(`${indent} ${dim(f + ':')}`);
483
+ for (const token of ts) console.log(`${indent} ${dim(token)}`);
484
+ }
485
+ }
464
486
  }
465
487
 
466
488
  function reportResult(result) {
@@ -483,7 +505,7 @@ function reportResult(result) {
483
505
  console.log(
484
506
  ` ${green('✓')} [data-theme="${t.name}"] ${dim(`(${t.declaredCount} tokens)`)}`
485
507
  );
486
- optionalInfo(t.optionalMissing, ' ');
508
+ optionalInfo(t.optionalMissing, t.optionalMissingByFeature, ' ');
487
509
  } else {
488
510
  const n = t.missing.length;
489
511
  console.log(
@@ -502,7 +524,7 @@ function reportResult(result) {
502
524
  console.log(
503
525
  `${green('✓')} ${bold(rel)} ${dim(`passes (${result.declaredCount} tokens declared)`)}`
504
526
  );
505
- optionalInfo(result.optionalMissing, ' ');
527
+ optionalInfo(result.optionalMissing, result.optionalMissingByFeature, ' ');
506
528
  return;
507
529
  }
508
530
 
@@ -583,7 +605,7 @@ function printUsage() {
583
605
  ' --all validate every theme.css under public/ (CI mode)',
584
606
  ' --no-a11y skip the WCAG 2.2 AA contrast audit',
585
607
  ' --allow-a11y-fail do NOT exit non-zero on a11y FAILs (report only)',
586
- ' --show-optional list optional contract tokens a theme leaves to the library default (info only)',
608
+ ' --show-optional list optional contract tokens a theme leaves to the library default, grouped by feature (info only)',
587
609
  ' --strict accepted for backwards compatibility (no-op; FAIL is now the default)',
588
610
  '',
589
611
  'Exit codes:',
@@ -713,6 +735,10 @@ module.exports = {
713
735
  validateFile,
714
736
  validateText,
715
737
  loadContract,
738
+ // Reporters — reused by `cia theme from-tokens` so its output reads exactly
739
+ // like `npm run validate-themes`.
740
+ reportResult,
741
+ reportA11yForTheme,
716
742
  a11y: a11y,
717
743
  };
718
744