sparkle-design-cli 2.5.0-beta.2 → 2.5.0-beta.4

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.
@@ -88,6 +88,9 @@ let templateNoComments;
88
88
  let radiusCsv;
89
89
  let RADIUS_HEADER = [];
90
90
  let RADIUS_ROWS = [];
91
+ let fontdataCsv;
92
+ let FONTDATA_HEADER = [];
93
+ let FONTDATA_ROWS = [];
91
94
 
92
95
  check('入力ファイルが読み込めること', () => {
93
96
  const problems = [];
@@ -121,6 +124,26 @@ check('入力ファイルが読み込めること', () => {
121
124
  } catch (err) {
122
125
  problems.push(`radius.csv: ${err.message}`);
123
126
  }
127
+ try {
128
+ // fontdata.csv も CRLF。radius.csv と同じ理由で /\r?\n/ で割る。ここを
129
+ // split('\n') にすると最終カラム(variable)の末尾に '\r' が残り、
130
+ // `font-bizudp\r` という**存在しないクラス名**を検査が正常値として扱う。
131
+ // en: CRLF as well — a plain split('\n') would leave '\r' on the last column.
132
+ //
133
+ // .trim() は行末の空行を落とすだけでなく、**先頭の BOM も除去している**
134
+ // (JS の trim は U+FEFF を空白として扱う)。表計算ソフトで保存し直すと
135
+ // BOM が付くことがあるので、.trim() を外すと 1 列目の列名が読めなくなる。
136
+ // en: .trim() also strips a leading BOM (JS treats U+FEFF as whitespace) —
137
+ // dropping it would break the first column name for BOM-prefixed files.
138
+ fontdataCsv = read('fontdata.csv')
139
+ .trim()
140
+ .split(/\r?\n/)
141
+ .map((l) => l.split(','));
142
+ FONTDATA_HEADER = fontdataCsv[0];
143
+ FONTDATA_ROWS = fontdataCsv.slice(1);
144
+ } catch (err) {
145
+ problems.push(`fontdata.csv: ${err.message}`);
146
+ }
124
147
  return problems.length ? problems.join(' / ') : null;
125
148
  });
126
149
 
@@ -716,6 +739,144 @@ check('プレースホルダーが CLI の期待どおりの字面であるこ
716
739
  return problems.length ? problems.join(' / ') : null;
717
740
  });
718
741
 
742
+ // ---------------------------------------------------------------------------
743
+ // 10. @font-face 専用の記述子がスタイルルールに紛れ込んでいないこと
744
+ // ---------------------------------------------------------------------------
745
+ // これらは @font-face の**記述子**であって CSS プロパティではない。スタイル
746
+ // ルールの中に書くと、パース時に宣言ごと捨てられる。CSS は無効な宣言を黙って
747
+ // 飛ばす仕様なので、ビルドも lint も警告を出さず「書いてあるのに効いていない」
748
+ // 状態が残る。実際 icon-* の 24 クラスに `font-display: optional` が入っていた。
749
+ // en: These are @font-face descriptors, not properties. Inside a style rule the
750
+ // whole declaration is dropped at parse time — silently, since CSS skips
751
+ // invalid declarations. 24 icon classes carried a dead `font-display`.
752
+ //
753
+ // 実ブラウザで確認済み: スタイルルールに置くと宣言数が 6→5 に減り
754
+ // `getPropertyValue('font-display')` が空文字を返す。@font-face 内では残る。
755
+ //
756
+ // font-weight / font-style / font-stretch / font-variation-settings /
757
+ // font-feature-settings は記述子であると同時に**正当なプロパティ**でもあるので
758
+ // ここには入れない(テンプレートは font-variation-settings を実際に使っている)。
759
+ // en: Deliberately excludes descriptors that are also valid properties.
760
+ const FONT_FACE_ONLY_DESCRIPTORS = [
761
+ 'src',
762
+ 'font-display',
763
+ 'unicode-range',
764
+ 'ascent-override',
765
+ 'descent-override',
766
+ 'line-gap-override',
767
+ 'size-adjust',
768
+ ];
769
+
770
+ check('@font-face 専用の記述子がスタイルルールに書かれていないこと', () => {
771
+ // @font-face ブロックの中は正当な置き場所なので取り除いてから探す。
772
+ // 現在テンプレートに @font-face は 1 つも無い(フォントは {{FONT_IMPORTS}} 経由)
773
+ // が、将来足されても検査が誤検出しないようにしておく。
774
+ const outsideFontFace = templateNoComments.replace(/@font-face\s*\{[^}]*\}/g, '');
775
+
776
+ // 「0 件マッチ=合格」の検査なので、走査対象が消えていないことを先に確かめる。
777
+ // strip の正規表現が暴走してテンプレートを丸ごと食っても、この expect が無いと
778
+ // 満点で通ってしまう。
779
+ // en: This check passes on zero matches, so assert we actually scanned CSS.
780
+ const declarationCount = [...outsideFontFace.matchAll(/^[ \t]*[a-z-]+\s*:/gm)].length;
781
+ expect(declarationCount, 300, 'スタイル宣言');
782
+
783
+ const found = [];
784
+ for (const descriptor of FONT_FACE_ONLY_DESCRIPTORS) {
785
+ const hits = [...outsideFontFace.matchAll(new RegExp(`^[ \\t]*${descriptor}\\s*:`, 'gm'))];
786
+ if (hits.length) found.push(`${descriptor}(${hits.length} 件)`);
787
+ }
788
+
789
+ return found.length
790
+ ? `@font-face の外に記述子が書かれています。CSS のパース時に宣言ごと捨てられるため効きません: ${found.join(', ')}`
791
+ : null;
792
+ });
793
+
794
+ // ---------------------------------------------------------------------------
795
+ // 11. fontdata.csv が消費側の期待する形をしていること
796
+ // ---------------------------------------------------------------------------
797
+ // fontdata.csv の消費側は Figma プラグイン sparkle-design-theme-settings だけで、
798
+ // CLI は読まない。つまり**このリポジトリの他の検査には一切引っかからない**まま
799
+ // 壊せる。しかもプラグイン側は落ちずに静かに誤動作する作りなので、ここで見る。
800
+ // en: Only the Figma plugin reads this file, so nothing else here catches a break
801
+ // — and the plugin degrades silently rather than failing.
802
+ //
803
+ // 消費側の実装(src/ui/hooks/useFontData.ts / src/common/types.ts)から確認した契約:
804
+ // - 5 列を列名で読む(entry.name / regularLabel / boldLabel / proportion / variable)
805
+ // - proportion は enum FontProportion = { both: 'both', monospace: 'mono', proportional: 'pro' }
806
+ // - name が検索キー(fontData.find((font) => font.name === value))
807
+ // - variable は CSS クラス名として使われる(className={font.cssLabel || ""})
808
+ const FONTDATA_HEADER_EXPECTED = ['name', 'regularLabel', 'boldLabel', 'proportion', 'variable'];
809
+ const FONT_PROPORTIONS = new Set(['pro', 'mono', 'both']);
810
+
811
+ check('fontdata.csv が消費側の期待する形をしていること', () => {
812
+ const problems = [];
813
+
814
+ // 列名で読まれるので、リネームすると entry.<列名> が undefined になり、
815
+ // プラグインは**空文字のラベル**を表示したまま動き続ける。
816
+ if (FONTDATA_HEADER.join(',') !== FONTDATA_HEADER_EXPECTED.join(',')) {
817
+ problems.push(
818
+ `ヘッダーが違います: 期待 ${FONTDATA_HEADER_EXPECTED.join(',')} / 実際 ${FONTDATA_HEADER.join(',')}`
819
+ );
820
+ // ヘッダーが違う時点で以降の列位置の解釈が無意味になるので打ち切る。
821
+ return problems.join(' / ');
822
+ }
823
+
824
+ expect(FONTDATA_ROWS.length, 5, 'fontdata.csv の行');
825
+
826
+ const seenNames = new Map();
827
+ const seenVariables = new Map();
828
+
829
+ FONTDATA_ROWS.forEach((cols, i) => {
830
+ const lineNo = i + 2; // 1 行目はヘッダー
831
+ if (cols.length !== FONTDATA_HEADER_EXPECTED.length) {
832
+ problems.push(`${lineNo} 行目: 列数が ${cols.length}(${FONTDATA_HEADER_EXPECTED.length} 列であるべき)`);
833
+ return;
834
+ }
835
+ const [name, regularLabel, boldLabel, proportion, variable] = cols;
836
+
837
+ for (const [label, value] of [
838
+ ['name', name],
839
+ ['regularLabel', regularLabel],
840
+ ['boldLabel', boldLabel],
841
+ ['variable', variable],
842
+ ]) {
843
+ if (!value.trim()) problems.push(`${lineNo} 行目: ${label} が空です`);
844
+ }
845
+
846
+ // proportion が enum 外だと、プラグインの 2 つのリストが
847
+ // `!== 'mono'` と `!== 'pro'` の否定で作られている都合上、そのフォントが
848
+ // **両方のリストに出る**。落ちも警告もしないのでタイポが残り続ける。
849
+ // en: The two pickers filter by negation, so an unknown value lands in BOTH.
850
+ if (!FONT_PROPORTIONS.has(proportion)) {
851
+ problems.push(
852
+ `${lineNo} 行目: proportion が "${proportion}"(${[...FONT_PROPORTIONS].join(' / ')} のいずれか)`
853
+ );
854
+ }
855
+
856
+ // name は検索キー。重複すると find が常に先頭を返し、選んだつもりの
857
+ // フォントと違う方が Figma に書き込まれる。
858
+ if (seenNames.has(name)) {
859
+ problems.push(`${lineNo} 行目: name "${name}" が ${seenNames.get(name)} 行目と重複`);
860
+ } else {
861
+ seenNames.set(name, lineNo);
862
+ }
863
+
864
+ if (seenVariables.has(variable)) {
865
+ problems.push(`${lineNo} 行目: variable "${variable}" が ${seenVariables.get(variable)} 行目と重複`);
866
+ } else {
867
+ seenVariables.set(variable, lineNo);
868
+ }
869
+
870
+ // CSS クラス名としてそのまま className に入る。空白が混ざれば別クラスに
871
+ // 割れるし、CRLF を取りこぼした '\r' もここで捕まる。
872
+ if (variable.trim() && !/^font-[A-Za-z0-9-]+$/.test(variable)) {
873
+ problems.push(`${lineNo} 行目: variable "${variable}" が font-* のクラス名の形をしていません`);
874
+ }
875
+ });
876
+
877
+ return problems.length ? problems.join(' / ') : null;
878
+ });
879
+
719
880
  // ---------------------------------------------------------------------------
720
881
  // 結果
721
882
  // ---------------------------------------------------------------------------
@@ -325,7 +325,8 @@
325
325
 
326
326
  --color-border-info: var(--color-blue-300);
327
327
  --color-border-success: var(--color-green-300);
328
- --color-border-warning: var(--color-yellow-300);
328
+ --color-border-warning: var(--color-yellow-400);
329
+ --color-border-inverse: var(--color-white);
329
330
  /* Figma の定義どおり blue/700 固定(primary 設定には追従しない)。
330
331
  旧 --color-ring-normal の移行先。
331
332
  en: Fixed to blue/700 exactly as defined in Figma — it does not follow
@@ -1013,6 +1014,7 @@
1013
1014
  --color-border-info: var(--color-border-info);
1014
1015
  --color-border-success: var(--color-border-success);
1015
1016
  --color-border-warning: var(--color-border-warning);
1017
+ --color-border-inverse: var(--color-border-inverse);
1016
1018
  --color-border-ring: var(--color-border-ring);
1017
1019
 
1018
1020
  /* object — アイコン等の図形 */
@@ -1425,7 +1427,6 @@
1425
1427
  font-size: var(--icon-1-fill-0-font-size);
1426
1428
  font-weight: var(--icon-1-fill-0-font-weight);
1427
1429
  font-variation-settings: var(--font-weight-icon-fill-0);
1428
- font-display: optional;
1429
1430
  letter-spacing: var(--icon-1-fill-0-letter-spacing);
1430
1431
  line-height: var(--icon-1-fill-0-line-height);
1431
1432
  }
@@ -1434,7 +1435,6 @@
1434
1435
  font-size: var(--icon-1-fill-1-font-size);
1435
1436
  font-weight: var(--icon-1-fill-1-font-weight);
1436
1437
  font-variation-settings: var(--font-weight-icon-fill-1);
1437
- font-display: optional;
1438
1438
  letter-spacing: var(--icon-1-fill-1-letter-spacing);
1439
1439
  line-height: var(--icon-1-fill-1-line-height);
1440
1440
  }
@@ -1443,7 +1443,6 @@
1443
1443
  font-size: var(--icon-2-fill-0-font-size);
1444
1444
  font-weight: var(--icon-2-fill-0-font-weight);
1445
1445
  font-variation-settings: var(--font-weight-icon-fill-0);
1446
- font-display: optional;
1447
1446
  letter-spacing: var(--icon-2-fill-0-letter-spacing);
1448
1447
  line-height: var(--icon-2-fill-0-line-height);
1449
1448
  }
@@ -1452,7 +1451,6 @@
1452
1451
  font-size: var(--icon-2-fill-1-font-size);
1453
1452
  font-weight: var(--icon-2-fill-1-font-weight);
1454
1453
  font-variation-settings: var(--font-weight-icon-fill-1);
1455
- font-display: optional;
1456
1454
  letter-spacing: var(--icon-2-fill-1-letter-spacing);
1457
1455
  line-height: var(--icon-2-fill-1-line-height);
1458
1456
  }
@@ -1461,7 +1459,6 @@
1461
1459
  font-size: var(--icon-3-fill-0-font-size);
1462
1460
  font-weight: var(--icon-3-fill-0-font-weight);
1463
1461
  font-variation-settings: var(--font-weight-icon-fill-0);
1464
- font-display: optional;
1465
1462
  letter-spacing: var(--icon-3-fill-0-letter-spacing);
1466
1463
  line-height: var(--icon-3-fill-0-line-height);
1467
1464
  }
@@ -1470,7 +1467,6 @@
1470
1467
  font-size: var(--icon-3-fill-1-font-size);
1471
1468
  font-weight: var(--icon-3-fill-1-font-weight);
1472
1469
  font-variation-settings: var(--font-weight-icon-fill-1);
1473
- font-display: optional;
1474
1470
  letter-spacing: var(--icon-3-fill-1-letter-spacing);
1475
1471
  line-height: var(--icon-3-fill-1-line-height);
1476
1472
  }
@@ -1479,7 +1475,6 @@
1479
1475
  font-size: var(--icon-4-fill-0-font-size);
1480
1476
  font-weight: var(--icon-4-fill-0-font-weight);
1481
1477
  font-variation-settings: var(--font-weight-icon-fill-0);
1482
- font-display: optional;
1483
1478
  letter-spacing: var(--icon-4-fill-0-letter-spacing);
1484
1479
  line-height: var(--icon-4-fill-0-line-height);
1485
1480
  }
@@ -1488,7 +1483,6 @@
1488
1483
  font-size: var(--icon-4-fill-1-font-size);
1489
1484
  font-weight: var(--icon-4-fill-1-font-weight);
1490
1485
  font-variation-settings: var(--font-weight-icon-fill-1);
1491
- font-display: optional;
1492
1486
  letter-spacing: var(--icon-4-fill-1-letter-spacing);
1493
1487
  line-height: var(--icon-4-fill-1-line-height);
1494
1488
  }
@@ -1497,7 +1491,6 @@
1497
1491
  font-size: var(--icon-5-fill-0-font-size);
1498
1492
  font-weight: var(--icon-5-fill-0-font-weight);
1499
1493
  font-variation-settings: var(--font-weight-icon-fill-0);
1500
- font-display: optional;
1501
1494
  letter-spacing: var(--icon-5-fill-0-letter-spacing);
1502
1495
  line-height: var(--icon-5-fill-0-line-height);
1503
1496
  }
@@ -1506,7 +1499,6 @@
1506
1499
  font-size: var(--icon-5-fill-1-font-size);
1507
1500
  font-weight: var(--icon-5-fill-1-font-weight);
1508
1501
  font-variation-settings: var(--font-weight-icon-fill-1);
1509
- font-display: optional;
1510
1502
  letter-spacing: var(--icon-5-fill-1-letter-spacing);
1511
1503
  line-height: var(--icon-5-fill-1-line-height);
1512
1504
  }
@@ -1515,7 +1507,6 @@
1515
1507
  font-size: var(--icon-6-fill-0-font-size);
1516
1508
  font-weight: var(--icon-6-fill-0-font-weight);
1517
1509
  font-variation-settings: var(--font-weight-icon-fill-0);
1518
- font-display: optional;
1519
1510
  letter-spacing: var(--icon-6-fill-0-letter-spacing);
1520
1511
  line-height: var(--icon-6-fill-0-line-height);
1521
1512
  }
@@ -1524,7 +1515,6 @@
1524
1515
  font-size: var(--icon-6-fill-1-font-size);
1525
1516
  font-weight: var(--icon-6-fill-1-font-weight);
1526
1517
  font-variation-settings: var(--font-weight-icon-fill-1);
1527
- font-display: optional;
1528
1518
  letter-spacing: var(--icon-6-fill-1-letter-spacing);
1529
1519
  line-height: var(--icon-6-fill-1-line-height);
1530
1520
  }
@@ -1533,7 +1523,6 @@
1533
1523
  font-size: var(--icon-7-fill-0-font-size);
1534
1524
  font-weight: var(--icon-7-fill-0-font-weight);
1535
1525
  font-variation-settings: var(--font-weight-icon-fill-0);
1536
- font-display: optional;
1537
1526
  letter-spacing: var(--icon-7-fill-0-letter-spacing);
1538
1527
  line-height: var(--icon-7-fill-0-line-height);
1539
1528
  }
@@ -1542,7 +1531,6 @@
1542
1531
  font-size: var(--icon-7-fill-1-font-size);
1543
1532
  font-weight: var(--icon-7-fill-1-font-weight);
1544
1533
  font-variation-settings: var(--font-weight-icon-fill-1);
1545
- font-display: optional;
1546
1534
  letter-spacing: var(--icon-7-fill-1-letter-spacing);
1547
1535
  line-height: var(--icon-7-fill-1-line-height);
1548
1536
  }
@@ -1551,7 +1539,6 @@
1551
1539
  font-size: var(--icon-8-fill-0-font-size);
1552
1540
  font-weight: var(--icon-8-fill-0-font-weight);
1553
1541
  font-variation-settings: var(--font-weight-icon-fill-0);
1554
- font-display: optional;
1555
1542
  letter-spacing: var(--icon-8-fill-0-letter-spacing);
1556
1543
  line-height: var(--icon-8-fill-0-line-height);
1557
1544
  }
@@ -1560,7 +1547,6 @@
1560
1547
  font-size: var(--icon-8-fill-1-font-size);
1561
1548
  font-weight: var(--icon-8-fill-1-font-weight);
1562
1549
  font-variation-settings: var(--font-weight-icon-fill-1);
1563
- font-display: optional;
1564
1550
  letter-spacing: var(--icon-8-fill-1-letter-spacing);
1565
1551
  line-height: var(--icon-8-fill-1-line-height);
1566
1552
  }
@@ -1569,7 +1555,6 @@
1569
1555
  font-size: var(--icon-9-fill-0-font-size);
1570
1556
  font-weight: var(--icon-9-fill-0-font-weight);
1571
1557
  font-variation-settings: var(--font-weight-icon-fill-0);
1572
- font-display: optional;
1573
1558
  letter-spacing: var(--icon-9-fill-0-letter-spacing);
1574
1559
  line-height: var(--icon-9-fill-0-line-height);
1575
1560
  }
@@ -1578,7 +1563,6 @@
1578
1563
  font-size: var(--icon-9-fill-1-font-size);
1579
1564
  font-weight: var(--icon-9-fill-1-font-weight);
1580
1565
  font-variation-settings: var(--font-weight-icon-fill-1);
1581
- font-display: optional;
1582
1566
  letter-spacing: var(--icon-9-fill-1-letter-spacing);
1583
1567
  line-height: var(--icon-9-fill-1-line-height);
1584
1568
  }
@@ -1587,7 +1571,6 @@
1587
1571
  font-size: var(--icon-10-fill-0-font-size);
1588
1572
  font-weight: var(--icon-10-fill-0-font-weight);
1589
1573
  font-variation-settings: var(--font-weight-icon-fill-0);
1590
- font-display: optional;
1591
1574
  letter-spacing: var(--icon-10-fill-0-letter-spacing);
1592
1575
  line-height: var(--icon-10-fill-0-line-height);
1593
1576
  }
@@ -1596,7 +1579,6 @@
1596
1579
  font-size: var(--icon-10-fill-1-font-size);
1597
1580
  font-weight: var(--icon-10-fill-1-font-weight);
1598
1581
  font-variation-settings: var(--font-weight-icon-fill-1);
1599
- font-display: optional;
1600
1582
  letter-spacing: var(--icon-10-fill-1-letter-spacing);
1601
1583
  line-height: var(--icon-10-fill-1-line-height);
1602
1584
  }
@@ -1605,7 +1587,6 @@
1605
1587
  font-size: var(--icon-11-fill-0-font-size);
1606
1588
  font-weight: var(--icon-11-fill-0-font-weight);
1607
1589
  font-variation-settings: var(--font-weight-icon-fill-0);
1608
- font-display: optional;
1609
1590
  letter-spacing: var(--icon-11-fill-0-letter-spacing);
1610
1591
  line-height: var(--icon-11-fill-0-line-height);
1611
1592
  }
@@ -1614,7 +1595,6 @@
1614
1595
  font-size: var(--icon-11-fill-1-font-size);
1615
1596
  font-weight: var(--icon-11-fill-1-font-weight);
1616
1597
  font-variation-settings: var(--font-weight-icon-fill-1);
1617
- font-display: optional;
1618
1598
  letter-spacing: var(--icon-11-fill-1-letter-spacing);
1619
1599
  line-height: var(--icon-11-fill-1-line-height);
1620
1600
  }
@@ -1623,7 +1603,6 @@
1623
1603
  font-size: var(--icon-12-fill-0-font-size);
1624
1604
  font-weight: var(--icon-12-fill-0-font-weight);
1625
1605
  font-variation-settings: var(--font-weight-icon-fill-0);
1626
- font-display: optional;
1627
1606
  letter-spacing: var(--icon-12-fill-0-letter-spacing);
1628
1607
  line-height: var(--icon-12-fill-0-line-height);
1629
1608
  }
@@ -1632,7 +1611,6 @@
1632
1611
  font-size: var(--icon-12-fill-1-font-size);
1633
1612
  font-weight: var(--icon-12-fill-1-font-weight);
1634
1613
  font-variation-settings: var(--font-weight-icon-fill-1);
1635
- font-display: optional;
1636
1614
  letter-spacing: var(--icon-12-fill-1-letter-spacing);
1637
1615
  line-height: var(--icon-12-fill-1-line-height);
1638
1616
  }