sparkle-design-cli 2.4.0 → 2.4.2

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.
package/README.md CHANGED
@@ -648,9 +648,9 @@ npx sparkle-design-cli generate -c ./config/sparkle.admin.json \
648
648
 
649
649
  ```css
650
650
  /* entry CSS: 両方を同時に import してよい */
651
- @import "tailwindcss";
652
- @import "./sparkle-employee.css"; /* @theme inline を含む通常の generate 出力(ベース) */
653
- @import "./sparkle-admin-scope.css"; /* --scope の出力。@theme inline は含まない */
651
+ @import 'tailwindcss';
652
+ @import './sparkle-employee.css'; /* @theme inline を含む通常の generate 出力(ベース) */
653
+ @import './sparkle-admin-scope.css'; /* --scope の出力。@theme inline は含まない */
654
654
  ```
655
655
 
656
656
  ```html
@@ -666,6 +666,7 @@ npx sparkle-design-cli generate -c ./config/sparkle.admin.json \
666
666
  - フォント import・`@source`・SparkleHead.tsx・globals.css パッチ等、ドキュメント全体に関わる**出力**は行いません。これらはベース側の通常 `generate` が既に担っているため、バリアントごとに重複させる必要はありません。ただし `font-pro` / `font-mono` 等の **validation 自体**は通常の `generate` と同様に適用されます(出力しないだけで、config の妥当性チェックは変わりません)。
667
667
  - `-o/--output` の指定が必須です(既定の `sparkle-design.css` を誤って上書きしないため)。`--strict` / `--globals-path` とは併用できません(`--scope` はグローバル CSS のパッチを行わないため)。
668
668
  - 現状 `primary` は 7 色パレットからの選択のみ対応しています。7 色にないカスタムブランドカラーを `--scope` の変数だけで表現したい場合は、`extend.custom-css` の要領で手動でセレクタ配下に `--color-primary-*` を定義してください(`--scope` の出力とマージして使えます)。
669
+ - **v2.4.1 以降**: ベース側 `generate` が出す `@theme inline` の各宣言は、プリミティブへの直接参照ではなく同名のセマンティック変数への自己参照(例: `--color-primary-500: var(--color-primary-500)`)になっています。Tailwind v4 は `@theme inline` の宣言右辺をそのまま compiled utility class にインライン展開する仕様のため、これが無いと `.bg-primary-500` 等の compiled utility は最初からプリミティブ変数だけを参照し、`--scope` がセマンティック変数だけを上書きしても utility class には一切反映されません(v2.4.0 はこの自己参照が無く、`--scope` の上書きが compiled utility に効かない状態でした)。v2.4.1 以降を使ってください。
669
670
 
670
671
  このパターンは、次の「非推奨: 属性スコープでの Tailwind クラス上書き」が抱えていた脆さも同時に解消します。`--scope` が上書きするのは Sparkle が公開しているトークン契約(`--color-primary-*` 等の CSS 変数)だけであり、ユーティリティクラス名やコンポーネント内部の specificity には一切依存しません。そのため、Sparkle 側のコンポーネント実装(クラス名の付け方や compound attribute selector 等)が変わっても、`--scope` の出力が壊れることはありません。
671
672
 
package/lib/constants.js CHANGED
@@ -49,6 +49,17 @@ export const VITE_CONFIG_FILES = [
49
49
  ];
50
50
 
51
51
  // 正規表現パターン
52
+ // `semanticName` のような key 由来の文字列を `new RegExp(...)` に埋め込む箇所で
53
+ // 使う。値は radius.csv の固定ヘッダー行由来で現状メタ文字を含まないが、
54
+ // 将来 radius.csv にメタ文字を含む列が追加された場合に正規表現が壊れる/意図しない
55
+ // マッチをするのを防ぐため、生成前に必ずエスケープする(lib/check.js の
56
+ // 同種ヘルパーと同じ方針)。
57
+ // en: Escape regex metacharacters before interpolating a key-derived string
58
+ // into `new RegExp(...)`. semanticName values come from radius.csv's fixed
59
+ // header row today, but escaping guards against a future column containing
60
+ // a metacharacter breaking or silently mismatching the pattern.
61
+ const escapeRegExpLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
62
+
52
63
  export const REGEX = {
53
64
  // フォント関連
54
65
  FONT_IMPORT:
@@ -116,8 +127,20 @@ export const REGEX = {
116
127
  // テンプレート関連
117
128
  COLOR_TOKENS_PLACEHOLDER: /[ \t]*\/\* \{\{COLOR_TOKENS\}\} \*\/[ \t]*\n?/,
118
129
  FONT_IMPORTS_PLACEHOLDER: /[ \t]*\/\* \{\{FONT_IMPORTS\}\} \*\/[ \t]*\n?/,
119
- RADIUS_SEMANTIC_PATTERN: (semanticName) =>
120
- new RegExp(`--radius-${semanticName}:\\s*var\\(--radius-[^)]+\\)`, 'g'),
130
+ // negative lookahead で `var(--radius-<semanticName>)`(テンプレートの
131
+ // `@theme inline` が issue #229 対応で持つ自己参照)を除外する。self-reference
132
+ // はランタイムでの scope 上書き(generate --scope)を compiled utility に
133
+ // 効かせるための意図的な構造なので、この一括置換で primitive 参照に
134
+ // 巻き戻してはいけない。プレーンな `:root`(セマンティックブロック)側の
135
+ // `var(--radius-{{RADIUS}})` 由来の occurrence だけを引き続き解決対象にする。
136
+ // en: Exclude the self-referencing `var(--radius-<semanticName>)` occurrence
137
+ // (the @theme inline indirection added for issue #229) via negative
138
+ // lookahead, so this bulk substitution only resolves the plain semantic
139
+ // `:root` block's primitive-referencing occurrence.
140
+ RADIUS_SEMANTIC_PATTERN: (semanticName) => {
141
+ const escaped = escapeRegExpLiteral(semanticName);
142
+ return new RegExp(`--radius-${escaped}:\\s*var\\(--radius-(?!${escaped}\\b)[^)]+\\)`, 'g');
143
+ },
121
144
  };
122
145
 
123
146
  // テンプレートプレースホルダー
@@ -162,7 +162,13 @@ export function loadRadiusMapping() {
162
162
  );
163
163
 
164
164
  // CSVをパース
165
- const lines = csvContent.trim().split('\n');
165
+ // `radius.csv` は CRLF 改行のため、単純な `split('\n')` だと各行末尾に `\r` が
166
+ // 残る(末尾カラムの値・ヘッダー名に紛れ込む: 例 `round` -> `"round\r"`)。
167
+ // `\r?\n` で split して LF/CRLF どちらでも正しく行分割する。
168
+ // en: radius.csv uses CRLF line endings; a plain `split('\n')` leaves a
169
+ // trailing `\r` on each line's last field (e.g. the `round` header becomes
170
+ // `"round\r"`). Split on `\r?\n` to handle both LF and CRLF correctly.
171
+ const lines = csvContent.trim().split(/\r?\n/);
166
172
  const headers = lines[0].split(',');
167
173
  const radiusMapping = {};
168
174
 
@@ -562,6 +562,23 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
562
562
  });
563
563
 
564
564
  // 6. Radiusの一括置換
565
+ //
566
+ // この置換は processedCSS 全体(プレーンな :root ブロックと @theme inline
567
+ // ブロックの両方)を対象にした一括正規表現なので、REGEX.RADIUS_SEMANTIC_PATTERN
568
+ // の negative lookahead が「@theme inline 側の自己参照 var(--radius-<semanticName>)
569
+ // には決してマッチしない」という前提の上に成り立っている(issue #229
570
+ // フォローアップ)。この前提は「radius.csv のどのプリセット値も、7つの
571
+ // セマンティックキー名(divide/minimum/notice/action/halfModal/modal/round)
572
+ // のいずれとも文字列衝突しない」という不変条件に依存する。radius.csv に
573
+ // プリセット値としてこれらの名前と同じ列挙値を追加すると壊れるため注意
574
+ // (generate-css.test.js の「全 radius プリセット × 全セマンティックキー」
575
+ // テストが実際の radius.csv に対してこの不変条件を継続的に検証している)。
576
+ // en: This bulk substitution runs over the entire processedCSS (both the
577
+ // plain :root block and @theme inline), so REGEX.RADIUS_SEMANTIC_PATTERN's
578
+ // negative lookahead relies on the invariant that no radius.csv preset
579
+ // value ever collides with one of the 7 semantic key names. The
580
+ // "all radius presets x all semantic keys" test in generate-css.test.js
581
+ // continuously checks this invariant against the real radius.csv.
565
582
  if (config.radius && radiusMapping[config.radius]) {
566
583
  const radiusValues = radiusMapping[config.radius];
567
584
 
@@ -805,6 +822,18 @@ const SEMANTIC_ROOT_MARKER =
805
822
  * `assertSafeFontWeights` 等)自体は通常の `generate` と同様に適用される。
806
823
  * 出力しないだけで、config の妥当性チェックは変わらず全項目に効く。
807
824
  *
825
+ * 重要な前提(issue #229 フォローアップ): この関数がセマンティックトークンだけを
826
+ * 上書きして compiled utility class(`.bg-primary-500` 等)に実際に反映されるのは、
827
+ * ベース側の `generate` が出す `@theme inline` の各宣言(`sparkle-variables` の
828
+ * テンプレート側)が、プリミティブへの直接参照ではなく同名のセマンティック変数への
829
+ * 自己参照(例: `--color-primary-500: var(--color-primary-500)`)になっているから。
830
+ * Tailwind v4 は `@theme inline` の宣言右辺をそのまま compiled utility に埋め込む
831
+ * ため、もしここがプリミティブ直接参照のままだと、compiled utility は最初から
832
+ * セマンティック変数を一切参照せず、この関数の出力(セマンティック変数の上書き)は
833
+ * 死んだコードになる(v2.4.0 で実際に踏んだ回帰。詳細は generate-css.test.js の
834
+ * `integration: compiled Tailwind utility classes ...` テストと sparkle-variables
835
+ * リポジトリの該当コミットを参照)。
836
+ *
808
837
  * en: A `generate` variant for runtime theme switching within a single bundle
809
838
  * (issue #229). Never touches `@theme inline` (which Tailwind v4 requires to
810
839
  * stay top-level and un-nestable) — instead flattens only the semantic color
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-design-cli",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Sparkle Design CLI — プロジェクトセットアップ、CSS・フォント生成、アンチパターン検査、AI エージェント(Claude Code / Cursor / Codex)向けのガードと hook 設定まで一括で行う sparkle-design 公式 CLI。",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -45,9 +45,11 @@
45
45
  "license": "MIT",
46
46
  "devDependencies": {
47
47
  "@eslint/js": "^8.57.0",
48
+ "@tailwindcss/cli": "^4.3.2",
48
49
  "eslint": "^8.57.0",
49
50
  "power-assert": "^1.6.1",
50
- "prettier": "^3.2.5"
51
+ "prettier": "^3.2.5",
52
+ "tailwindcss": "^4.3.2"
51
53
  },
52
54
  "files": [
53
55
  "bin/",
@@ -210,125 +210,131 @@
210
210
  }
211
211
 
212
212
  /* セマンティクスのトークン定義(Tailwind ユーティリティ生成用) */
213
+ /* NOTE: 各宣言は上の「セマンティックトークン」:root ブロックにある同名変数を
214
+ 自己参照している(例: --color-primary-500: var(--color-primary-500))。
215
+ プリミティブへの直接参照にしない意図的な設計。Tailwind v4 は @theme inline
216
+ の宣言右辺を compiled utility class にそのまま埋め込むため、直接参照だと
217
+ セマンティック変数が一切参照されなくなり、別セレクタでのランタイム上書きが
218
+ compiled utility に効かなくなる。 */
213
219
  @theme inline {
214
220
  /* セマンティクスカラー定義 */
215
- --color-neutral-50: var(--color-gray-50);
216
- --color-neutral-100: var(--color-gray-100);
217
- --color-neutral-200: var(--color-gray-200);
218
- --color-neutral-300: var(--color-gray-300);
219
- --color-neutral-400: var(--color-gray-400);
220
- --color-neutral-500: var(--color-gray-500);
221
- --color-neutral-600: var(--color-gray-600);
222
- --color-neutral-700: var(--color-gray-700);
223
- --color-neutral-800: var(--color-gray-800);
224
- --color-neutral-900: var(--color-gray-900);
221
+ --color-neutral-50: var(--color-neutral-50);
222
+ --color-neutral-100: var(--color-neutral-100);
223
+ --color-neutral-200: var(--color-neutral-200);
224
+ --color-neutral-300: var(--color-neutral-300);
225
+ --color-neutral-400: var(--color-neutral-400);
226
+ --color-neutral-500: var(--color-neutral-500);
227
+ --color-neutral-600: var(--color-neutral-600);
228
+ --color-neutral-700: var(--color-neutral-700);
229
+ --color-neutral-800: var(--color-neutral-800);
230
+ --color-neutral-900: var(--color-neutral-900);
225
231
 
226
232
  /* プライマリカラーは設定可能 */
227
- --color-primary-50: var(--color-{{PRIMARY}}-50);
228
- --color-primary-100: var(--color-{{PRIMARY}}-100);
229
- --color-primary-200: var(--color-{{PRIMARY}}-200);
230
- --color-primary-300: var(--color-{{PRIMARY}}-300);
231
- --color-primary-400: var(--color-{{PRIMARY}}-400);
232
- --color-primary-500: var(--color-{{PRIMARY}}-500);
233
- --color-primary-600: var(--color-{{PRIMARY}}-600);
234
- --color-primary-700: var(--color-{{PRIMARY}}-700);
235
- --color-primary-800: var(--color-{{PRIMARY}}-800);
236
- --color-primary-900: var(--color-{{PRIMARY}}-900);
237
-
238
- --color-secondary-50: var(--color-gray-50);
239
- --color-secondary-100: var(--color-gray-100);
240
- --color-secondary-200: var(--color-gray-200);
241
- --color-secondary-300: var(--color-gray-300);
242
- --color-secondary-400: var(--color-gray-400);
243
- --color-secondary-500: var(--color-gray-500);
244
- --color-secondary-600: var(--color-gray-600);
245
- --color-secondary-700: var(--color-gray-700);
246
- --color-secondary-800: var(--color-gray-800);
247
- --color-secondary-900: var(--color-gray-900);
248
-
249
- --color-info-50: var(--color-blue-50);
250
- --color-info-100: var(--color-blue-100);
251
- --color-info-200: var(--color-blue-200);
252
- --color-info-300: var(--color-blue-300);
253
- --color-info-400: var(--color-blue-400);
254
- --color-info-500: var(--color-blue-500);
255
- --color-info-600: var(--color-blue-600);
256
- --color-info-700: var(--color-blue-700);
257
- --color-info-800: var(--color-blue-800);
258
- --color-info-900: var(--color-blue-900);
259
-
260
- --color-success-50: var(--color-green-50);
261
- --color-success-100: var(--color-green-100);
262
- --color-success-200: var(--color-green-200);
263
- --color-success-300: var(--color-green-300);
264
- --color-success-400: var(--color-green-400);
265
- --color-success-500: var(--color-green-500);
266
- --color-success-600: var(--color-green-600);
267
- --color-success-700: var(--color-green-700);
268
- --color-success-800: var(--color-green-800);
269
- --color-success-900: var(--color-green-900);
270
-
271
- --color-warning-50: var(--color-yellow-50);
272
- --color-warning-100: var(--color-yellow-100);
273
- --color-warning-200: var(--color-yellow-200);
274
- --color-warning-300: var(--color-yellow-300);
275
- --color-warning-400: var(--color-yellow-400);
276
- --color-warning-500: var(--color-yellow-500);
277
- --color-warning-600: var(--color-yellow-600);
278
- --color-warning-700: var(--color-yellow-700);
279
- --color-warning-800: var(--color-yellow-800);
280
- --color-warning-900: var(--color-yellow-900);
281
-
282
- --color-negative-50: var(--color-red-50);
283
- --color-negative-100: var(--color-red-100);
284
- --color-negative-200: var(--color-red-200);
285
- --color-negative-300: var(--color-red-300);
286
- --color-negative-400: var(--color-red-400);
287
- --color-negative-500: var(--color-red-500);
288
- --color-negative-600: var(--color-red-600);
289
- --color-negative-700: var(--color-red-700);
290
- --color-negative-800: var(--color-red-800);
291
- --color-negative-900: var(--color-red-900);
233
+ --color-primary-50: var(--color-primary-50);
234
+ --color-primary-100: var(--color-primary-100);
235
+ --color-primary-200: var(--color-primary-200);
236
+ --color-primary-300: var(--color-primary-300);
237
+ --color-primary-400: var(--color-primary-400);
238
+ --color-primary-500: var(--color-primary-500);
239
+ --color-primary-600: var(--color-primary-600);
240
+ --color-primary-700: var(--color-primary-700);
241
+ --color-primary-800: var(--color-primary-800);
242
+ --color-primary-900: var(--color-primary-900);
243
+
244
+ --color-secondary-50: var(--color-secondary-50);
245
+ --color-secondary-100: var(--color-secondary-100);
246
+ --color-secondary-200: var(--color-secondary-200);
247
+ --color-secondary-300: var(--color-secondary-300);
248
+ --color-secondary-400: var(--color-secondary-400);
249
+ --color-secondary-500: var(--color-secondary-500);
250
+ --color-secondary-600: var(--color-secondary-600);
251
+ --color-secondary-700: var(--color-secondary-700);
252
+ --color-secondary-800: var(--color-secondary-800);
253
+ --color-secondary-900: var(--color-secondary-900);
254
+
255
+ --color-info-50: var(--color-info-50);
256
+ --color-info-100: var(--color-info-100);
257
+ --color-info-200: var(--color-info-200);
258
+ --color-info-300: var(--color-info-300);
259
+ --color-info-400: var(--color-info-400);
260
+ --color-info-500: var(--color-info-500);
261
+ --color-info-600: var(--color-info-600);
262
+ --color-info-700: var(--color-info-700);
263
+ --color-info-800: var(--color-info-800);
264
+ --color-info-900: var(--color-info-900);
265
+
266
+ --color-success-50: var(--color-success-50);
267
+ --color-success-100: var(--color-success-100);
268
+ --color-success-200: var(--color-success-200);
269
+ --color-success-300: var(--color-success-300);
270
+ --color-success-400: var(--color-success-400);
271
+ --color-success-500: var(--color-success-500);
272
+ --color-success-600: var(--color-success-600);
273
+ --color-success-700: var(--color-success-700);
274
+ --color-success-800: var(--color-success-800);
275
+ --color-success-900: var(--color-success-900);
276
+
277
+ --color-warning-50: var(--color-warning-50);
278
+ --color-warning-100: var(--color-warning-100);
279
+ --color-warning-200: var(--color-warning-200);
280
+ --color-warning-300: var(--color-warning-300);
281
+ --color-warning-400: var(--color-warning-400);
282
+ --color-warning-500: var(--color-warning-500);
283
+ --color-warning-600: var(--color-warning-600);
284
+ --color-warning-700: var(--color-warning-700);
285
+ --color-warning-800: var(--color-warning-800);
286
+ --color-warning-900: var(--color-warning-900);
287
+
288
+ --color-negative-50: var(--color-negative-50);
289
+ --color-negative-100: var(--color-negative-100);
290
+ --color-negative-200: var(--color-negative-200);
291
+ --color-negative-300: var(--color-negative-300);
292
+ --color-negative-400: var(--color-negative-400);
293
+ --color-negative-500: var(--color-negative-500);
294
+ --color-negative-600: var(--color-negative-600);
295
+ --color-negative-700: var(--color-negative-700);
296
+ --color-negative-800: var(--color-negative-800);
297
+ --color-negative-900: var(--color-negative-900);
292
298
 
293
299
  /* コンポーネントカラー定義 */
294
- --color-divider-low: var(--color-neutral-100);
295
- --color-divider-middle: var(--color-neutral-200);
296
- --color-divider-high: var(--color-neutral-300);
297
- --color-text-disabled: var(--color-neutral-300);
298
- --color-text-placeholder: var(--color-neutral-500);
299
- --color-text-low: var(--color-neutral-500);
300
- --color-text-middle: var(--color-neutral-700);
301
- --color-text-high: var(--color-neutral-900);
302
- --color-skeleton-fill: var(--color-neutral-200);
300
+ --color-divider-low: var(--color-divider-low);
301
+ --color-divider-middle: var(--color-divider-middle);
302
+ --color-divider-high: var(--color-divider-high);
303
+ --color-text-disabled: var(--color-text-disabled);
304
+ --color-text-placeholder: var(--color-text-placeholder);
305
+ --color-text-low: var(--color-text-low);
306
+ --color-text-middle: var(--color-text-middle);
307
+ --color-text-high: var(--color-text-high);
308
+ --color-skeleton-fill: var(--color-skeleton-fill);
303
309
 
304
310
  /* ベースカラー(base) - gray と同等 */
305
- --color-base-50: var(--color-gray-50);
306
- --color-base-100: var(--color-gray-100);
307
- --color-base-200: var(--color-gray-200);
308
- --color-base-300: var(--color-gray-300);
309
- --color-base-400: var(--color-gray-400);
310
- --color-base-500: var(--color-gray-500);
311
- --color-base-600: var(--color-gray-600);
312
- --color-base-700: var(--color-gray-700);
313
- --color-base-800: var(--color-gray-800);
314
- --color-base-900: var(--color-gray-900);
311
+ --color-base-50: var(--color-base-50);
312
+ --color-base-100: var(--color-base-100);
313
+ --color-base-200: var(--color-base-200);
314
+ --color-base-300: var(--color-base-300);
315
+ --color-base-400: var(--color-base-400);
316
+ --color-base-500: var(--color-base-500);
317
+ --color-base-600: var(--color-base-600);
318
+ --color-base-700: var(--color-base-700);
319
+ --color-base-800: var(--color-base-800);
320
+ --color-base-900: var(--color-base-900);
315
321
 
316
322
  /* 角丸 - デフォルト値は設定可能 */
317
- --radius-divide: var(--radius-none);
318
- --radius-minimum: var(--radius-xs);
319
- --radius-notice: var(--radius-sm);
320
- --radius-action: var(--radius-{{RADIUS}});
321
- --radius-halfModal: var(--radius-lg);
322
- --radius-modal: var(--radius-xl);
323
- --radius-round: var(--radius-full);
323
+ --radius-divide: var(--radius-divide);
324
+ --radius-minimum: var(--radius-minimum);
325
+ --radius-notice: var(--radius-notice);
326
+ --radius-action: var(--radius-action);
327
+ --radius-halfModal: var(--radius-halfModal);
328
+ --radius-modal: var(--radius-modal);
329
+ --radius-round: var(--radius-round);
324
330
 
325
331
  /* シャドウ */
326
- --shadow-base: var(--shadow-none);
327
- --shadow-flat: var(--shadow-2xs);
328
- --shadow-raise: var(--shadow-xs);
329
- --shadow-stick: var(--shadow-sm);
330
- --shadow-float: var(--shadow-md);
331
- --shadow-popout: var(--shadow-lg);
332
+ --shadow-base: var(--shadow-base);
333
+ --shadow-flat: var(--shadow-flat);
334
+ --shadow-raise: var(--shadow-raise);
335
+ --shadow-stick: var(--shadow-stick);
336
+ --shadow-float: var(--shadow-float);
337
+ --shadow-popout: var(--shadow-popout);
332
338
  }
333
339
 
334
340
  /* フォントレンダリング */