sparkle-design-cli 2.2.3 → 2.3.1
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 +83 -5
- package/bin/sparkle-design.js +6 -2
- package/lib/color-utils.js +94 -9
- package/lib/generate-css.js +71 -1
- package/lib/load-plugins.js +42 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -457,10 +457,10 @@ Next.js / Vite 以外で導入したい方で困ったときは Issue で教え
|
|
|
457
457
|
|
|
458
458
|
#### Core(プラグインが出力)
|
|
459
459
|
|
|
460
|
-
- `primary`:
|
|
460
|
+
- `primary`: プライマリカラー(必須)。次の7色のいずれかを指定してください: `blue` / `red` / `orange` / `yellow` / `purple` / `green` / `pink`。未指定、またはこれら以外の値を指定した場合、`generate` は例外を投げて停止します(そのブランドカラー専用の `--color-primary-*` / `--color-gray-*` を用意していないため、`var()` の参照先が存在せず壊れた CSS になってしまうのを防ぐためのバリデーションです)。7色にないブランドカラーを使いたい場合は [extend.custom-css](#extendcustom-css) を参照してください。
|
|
461
461
|
- `font-pro`: プロポーショナルフォント([Google Fonts](https://fonts.google.com/) の名前)
|
|
462
462
|
- `font-mono`: モノスペースフォント([Google Fonts](https://fonts.google.com/) の名前)
|
|
463
|
-
- `radius`:
|
|
463
|
+
- `radius`: 角丸設定(必須)。次のいずれかを指定してください: `none` / `xs` / `sm` / `md` / `lg` / `xl` / `2xl` / `3xl`。未指定、またはこれら以外の値を指定した場合、`primary` と同様に `generate` は例外を投げて停止します(無効な値だと `--radius-action` が壊れた参照のまま出力されるのを防ぐためです)。
|
|
464
464
|
|
|
465
465
|
### extend セクション(拡張設定)
|
|
466
466
|
|
|
@@ -510,6 +510,74 @@ Figma プラグインが出力する基本4項目に加えて、プロジェク
|
|
|
510
510
|
|
|
511
511
|
プロジェクト固有のカスタムトークンの CSS ファイルパス。Tailwind エントリ CSS に `@import` が自動挿入されます。`sparkle-design.css` に直接追加すると `generate` 実行時に上書きされるため、必ず別ファイルに分離してください。
|
|
512
512
|
|
|
513
|
+
この `@import` は `sparkle-design.css` の直後に挿入されるため、`custom-css` 側で書いた `:root { --color-xxx: ... }` は CSS の cascade(後勝ち)でそのまま `sparkle-design.css` のトークンを上書きします。属性セレクタや `!important` を使う必要はありません。
|
|
514
|
+
|
|
515
|
+
##### カスタムブランドカラーを primary にしたい場合
|
|
516
|
+
|
|
517
|
+
`primary` は 7 色のいずれかしか受け付けません(前述の Core セクション参照)。7 色にないブランドカラーを使いたい場合は、`primary` は 7 色から見た目が近いものを仮に選んだ上で、`extend.custom-css` で `--color-primary-*` と `--color-gray-*` をブランドカラー基準の値で丸ごと再定義してください。`primary` の選択自体は `custom-css` 側の定義で完全に上書きされるため実質的な意味を持たなくなりますが、フィールドとしては有効な値を入れておく必要があります。
|
|
518
|
+
|
|
519
|
+
```css
|
|
520
|
+
/* custom-tokens.css */
|
|
521
|
+
:root {
|
|
522
|
+
/* ブランドカラー基準の primary パレット(50〜900) */
|
|
523
|
+
--color-primary-50: oklch(97% 0.02 250);
|
|
524
|
+
/* ... */
|
|
525
|
+
--color-primary-500: oklch(55% 0.18 250);
|
|
526
|
+
/* ... */
|
|
527
|
+
--color-primary-900: oklch(20% 0.08 250);
|
|
528
|
+
|
|
529
|
+
/* primary に合わせた gray パレット(50〜900)も必ず一緒に定義する。
|
|
530
|
+
gray だけ 7 色デフォルトのままだと、primary だけ浮いて見えるトーンずれが起きる */
|
|
531
|
+
--color-gray-50: oklch(98% 0.005 250);
|
|
532
|
+
/* ... */
|
|
533
|
+
--color-gray-900: oklch(22% 0.01 250);
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`primary` だけを差し替えて `gray` を既定のままにすると、コンポーネントの枠線・背景・テキストに使われる gray 系トークンと primary のトーンが揃わなくなるため、gray も必ずセットで再定義してください。
|
|
538
|
+
|
|
539
|
+
## 複数のテーマ配色を 1 デプロイでサポートしたい場合
|
|
540
|
+
|
|
541
|
+
管理画面と一般ユーザー画面で配色を出し分けたい、テナントごとに固定の配色バリアントを切り替えたいなど、「少数(数種類程度)の固定配色を 1 つのデプロイでサポートする」ケースの推奨パターンです。
|
|
542
|
+
|
|
543
|
+
### 推奨: config を分割して複数の CSS を生成する
|
|
544
|
+
|
|
545
|
+
`generate` は `-c/--config` と `-o/--output` を組み合わせることで、任意の config から任意の出力先へ CSS を生成できます。バリアントの数だけ config ファイルを用意し、バリアントの数だけ `generate` を実行してください(1 config に複数テーマをまとめて生成する機能はありませんが、CI やスクリプトで複数回呼び出せば十分に運用できます)。
|
|
546
|
+
|
|
547
|
+
```jsonc
|
|
548
|
+
// config/sparkle.admin.json
|
|
549
|
+
{ "primary": "blue", "font-pro": "Inter", "font-mono": "JetBrains Mono", "radius": "md" }
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
```jsonc
|
|
553
|
+
// config/sparkle.employee.json
|
|
554
|
+
{ "primary": "green", "font-pro": "Inter", "font-mono": "JetBrains Mono", "radius": "md" }
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
npx sparkle-design-cli generate -c ./config/sparkle.admin.json -o ./src/styles/sparkle-admin.css
|
|
559
|
+
npx sparkle-design-cli generate -c ./config/sparkle.employee.json -o ./src/styles/sparkle-employee.css
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
あとは consumer 側で、表示するバリアントに応じてどちらの CSS を読み込むかを切り替えるだけです(例: role / tenant を判定できるレイアウト単位で `import "./sparkle-admin.css"` と `import "./sparkle-employee.css"` を出し分ける、Next.js のルートグループごとに読み込む entry CSS を分ける、など)。**同時に両方を読み込まない**(1 画面につき常に 1 バリアントだけを読み込む)ようにすると、後述のファイルサイズ増加の影響を最小化できます。
|
|
563
|
+
|
|
564
|
+
### 非推奨: 単一 CSS + 属性スコープでの Tailwind クラス上書き
|
|
565
|
+
|
|
566
|
+
`sparkle-design.css` を 1 本のまま、`[data-theme="xxx"] .bg-primary-500 { ... }` のような属性セレクタで Tailwind ユーティリティクラスを個別に上書きする方式は **推奨しません**。
|
|
567
|
+
|
|
568
|
+
- コンポーネント側の実装詳細(クラス名の付け方や CSS の specificity)に依存するため、Sparkle 側の内部実装(例: 状態別カラーリングで compound attribute selector を使っているコンポーネント)が変わると、`!important` を使っても上書きが効かなくなることがあります。上書き対象は Sparkle が公開している契約(config / トークン)ではなく非公開の実装詳細なので、バージョンアップのたびに壊れるリスクを consumer 側が抱え続けることになります。
|
|
569
|
+
- 上書きが必要なクラス/コンポーネントが増えるたびに、手動でセレクタを足していく必要があり、考慮漏れに気づきにくいです。
|
|
570
|
+
|
|
571
|
+
上記のリスクを踏まえると、テーマ切り替えは config 分割 + `extend.custom-css` によるトークンレベルの上書き(cascade 順で確実に勝つ)で完結させる方が、Sparkle のバージョンアップに対しても壊れにくく、見通しの良い実装になります。
|
|
572
|
+
|
|
573
|
+
### トレードオフ
|
|
574
|
+
|
|
575
|
+
| 観点 | config 分割(推奨) | 属性スコープ上書き(非推奨) |
|
|
576
|
+
| --- | --- | --- |
|
|
577
|
+
| ファイルサイズ | バリアントの数だけ CSS が増える(各 CSS は 7 色パレット全体を含むため、1 バリアントあたりのベースコストは一定)。1 画面 1 バリアントのみ読み込む運用なら実質的な増分は小さい | 単一 CSS + 上書き分の差分のみで増分は小さい |
|
|
578
|
+
| FOUC リスク | サーバー側 / ビルド時にバリアントを決定できるならほぼ無し。クライアント側でバリアント判定後に切り替える場合は他方式と同程度のリスクがある | クライアント側で属性を付与するタイミング次第でリスクがある(初期描画後に属性が付くと一瞬デフォルト配色が見える) |
|
|
579
|
+
| 保守性 | Sparkle が公開している config / トークンだけに依存するため、コンポーネント内部実装の変更に影響されにくい | コンポーネントの内部実装(クラス名・specificity)に依存するため、Sparkle 側の変更で上書きが効かなくなるリスクがある |
|
|
580
|
+
|
|
513
581
|
## 出力
|
|
514
582
|
|
|
515
583
|
- デフォルト出力先: `src/app/sparkle-design.css`
|
|
@@ -531,6 +599,10 @@ CSS 仕様上 `@import` は他の at-rule より前に書く必要があるた
|
|
|
531
599
|
### セットアップ
|
|
532
600
|
|
|
533
601
|
```bash
|
|
602
|
+
# submodule(templates/sparkle-variables)を取得
|
|
603
|
+
# ※ 未初期化だと generate とそれに依存するテストが失敗する
|
|
604
|
+
git submodule update --init --recursive
|
|
605
|
+
|
|
534
606
|
# 依存関係をインストール
|
|
535
607
|
npm install
|
|
536
608
|
|
|
@@ -541,11 +613,17 @@ npm link
|
|
|
541
613
|
### 開発用コマンド
|
|
542
614
|
|
|
543
615
|
```bash
|
|
544
|
-
npm test
|
|
545
|
-
npm run
|
|
546
|
-
npm run
|
|
616
|
+
npm test # Node.js test runner
|
|
617
|
+
npm run test:watch # テストの watch 実行
|
|
618
|
+
npm run lint # ESLint
|
|
619
|
+
npm run lint:fix # ESLint 自動修正
|
|
620
|
+
npm run format # Prettier
|
|
621
|
+
npm run format:check # Prettier チェックのみ
|
|
622
|
+
npm run sync:anti-pattern-docs # check ルール一覧を README と隣接リポジトリの JSDoc に同期
|
|
547
623
|
```
|
|
548
624
|
|
|
625
|
+
> **`sync:anti-pattern-docs` の注意:** このスクリプトは README だけでなく、ワークスペース内の隣接リポジトリ(`../sparkle-design` / `../sparkle-design-internal`)のファイルも書き換える横断スクリプトです。両リポジトリが隣にある Sparkle ワークスペース内で実行してください。
|
|
626
|
+
|
|
549
627
|
### リリース手順(メンテナ向け)
|
|
550
628
|
|
|
551
629
|
publish は GitHub Actions の **Publish to npm** workflow 経由。ローカル `npm publish` は禁止。
|
package/bin/sparkle-design.js
CHANGED
|
@@ -184,10 +184,14 @@ Generate options:
|
|
|
184
184
|
sparkle.config.json の設定フィールド:
|
|
185
185
|
|
|
186
186
|
基本設定(Figma プラグインで出力可能):
|
|
187
|
-
primary プライマリカラー (blue, red, orange,
|
|
187
|
+
primary プライマリカラー (必須。blue, red, orange, yellow, purple, green, pink の
|
|
188
|
+
いずれか。未指定、またはそれ以外の値は generate 実行時にエラーになります。
|
|
189
|
+
7色にないブランドカラーを使いたい場合は extend.custom-css で
|
|
190
|
+
--color-primary-* / --color-gray-* を再定義してください。詳細は README を参照)
|
|
188
191
|
font-pro プロポーショナルフォント (Google Fonts の名前)
|
|
189
192
|
font-mono モノスペースフォント (Google Fonts の名前)
|
|
190
|
-
radius 角丸設定 (none, sm, md, lg, xl,
|
|
193
|
+
radius 角丸設定 (必須。none, xs, sm, md, lg, xl, 2xl, 3xl のいずれか。
|
|
194
|
+
未指定、またはそれ以外の値は primary と同様に generate 実行時にエラーになります)
|
|
191
195
|
|
|
192
196
|
拡張設定(extend セクション、任意):
|
|
193
197
|
extend オブジェクト直書き、またはファイルパス(例: "./sparkle.extend.json")
|
package/lib/color-utils.js
CHANGED
|
@@ -72,14 +72,99 @@ export function hexToOklch(hex) {
|
|
|
72
72
|
return `oklch(${l} ${c} ${h})`;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* primary に指定できる有効な色名の一覧を返す。
|
|
77
|
+
* colors.json(パレット定義)と gray.json(グレーマッピング)の両方に
|
|
78
|
+
* キーが存在する色だけを「primary として使える色」とみなす。
|
|
79
|
+
* colors.json のキー順(= README / CLI help の記載順)をそのまま維持する
|
|
80
|
+
* ため、`Object.keys(colors)` を起点に走査する(`Object.keys(grayMapping)`
|
|
81
|
+
* を起点にすると gray.json 側のキー順に引きずられ、ドキュメントの記載順と
|
|
82
|
+
* 食い違うエラーメッセージになってしまう)。
|
|
83
|
+
* en: A color is a valid `primary` choice only if it has both a palette
|
|
84
|
+
* definition (colors.json) and a matching gray mapping (gray.json). Iterate
|
|
85
|
+
* `colors` (not `grayMapping`) so the result preserves colors.json's key
|
|
86
|
+
* order, matching the order documented in README/CLI help.
|
|
87
|
+
* @param {Object} colors colors.jsonから読み込んだ色の定義
|
|
88
|
+
* @param {Object} grayMapping gray.jsonから読み込んだグレーの定義
|
|
89
|
+
* @returns {string[]} 有効な primary 色名の配列
|
|
90
|
+
*/
|
|
91
|
+
export function getValidPrimaryColors(colors, grayMapping) {
|
|
92
|
+
return Object.keys(colors).filter(
|
|
93
|
+
(name) =>
|
|
94
|
+
typeof colors[name] === 'object' &&
|
|
95
|
+
colors[name] !== null &&
|
|
96
|
+
Object.prototype.hasOwnProperty.call(grayMapping, name)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* sparkle.config.json の `primary` を検証する。
|
|
102
|
+
*
|
|
103
|
+
* `primary` は `--color-primary-*: var(--color-{{PRIMARY}}-*)` のエイリアス先
|
|
104
|
+
* として使われるため、未知の値を渡すと `var()` の参照先が存在せず、gray token
|
|
105
|
+
* も生成されないまま「壊れているが構文的には正しい CSS」が silent に出力されて
|
|
106
|
+
* しまう(issue #222 発見3)。ここで早期に loud throw することで、consumer が
|
|
107
|
+
* 気付かないまま壊れた配色を deploy してしまう事態を防ぐ。
|
|
108
|
+
*
|
|
109
|
+
* ブランド独自色を primary にしたいケースは、`primary` 自体を拡張するのではなく
|
|
110
|
+
* `extend.custom-css` で `--color-primary-*` / `--color-gray-*` を直接定義して
|
|
111
|
+
* 上書きする方式を案内する(README.md 参照)。
|
|
112
|
+
*
|
|
113
|
+
* en: `primary` is only ever used to alias `--color-primary-*` to one of the
|
|
114
|
+
* shipped palettes (`var(--color-{{PRIMARY}}-*)`). An unknown value silently
|
|
115
|
+
* produces syntactically-valid-but-broken CSS (issue #222, finding 3) — no
|
|
116
|
+
* gray tokens, and primary tokens that resolve to nothing. Fail loud instead.
|
|
117
|
+
* For real custom brand colors, direct users to override the primary/gray
|
|
118
|
+
* tokens wholesale via `extend.custom-css` rather than stretching `primary`.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} primaryColor config.primary の値
|
|
121
|
+
* @param {Object} colors colors.jsonから読み込んだ色の定義
|
|
122
|
+
* @param {Object} grayMapping gray.jsonから読み込んだグレーの定義
|
|
123
|
+
* @throws {Error} primaryColor が有効な色名でない場合(`err.code === 'E_INVALID_PRIMARY_COLOR'`)
|
|
124
|
+
*/
|
|
125
|
+
export function assertValidPrimaryColor(primaryColor, colors, grayMapping) {
|
|
126
|
+
const validColors = getValidPrimaryColors(colors, grayMapping);
|
|
127
|
+
|
|
128
|
+
if (typeof primaryColor === 'string' && validColors.includes(primaryColor)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const err = new Error(
|
|
133
|
+
`sparkle.config.json の "primary" に無効な値が指定されています: ${JSON.stringify(primaryColor)}\n` +
|
|
134
|
+
` 有効な値: ${validColors.join(', ')}\n` +
|
|
135
|
+
` これら以外のブランドカラーを primary にしたい場合は、primary は上記いずれかのままにし、\n` +
|
|
136
|
+
` extend.custom-css 側で --color-primary-*(50〜900)と --color-gray-*(50〜900)を\n` +
|
|
137
|
+
` 独自の値で定義してオーバーライドしてください。primary の値だけでは任意色を表現できません。\n` +
|
|
138
|
+
` 詳細: README.md の「カスタムブランドカラーを primary にしたい場合」を参照してください。`
|
|
139
|
+
);
|
|
140
|
+
err.code = 'E_INVALID_PRIMARY_COLOR';
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
|
|
75
144
|
/**
|
|
76
145
|
* 色のトークンを生成する
|
|
146
|
+
*
|
|
147
|
+
* `generateColorTokens` はこのパッケージの public export(package.json の
|
|
148
|
+
* `main`/`exports["."]` が指す `lib/generate-css.js` から再エクスポートされる)
|
|
149
|
+
* なので、`generateCSS()` を経由しない直接呼び出しでも primary の検証が
|
|
150
|
+
* 効くよう、ここで `assertValidPrimaryColor` を呼ぶ。以前は `generateCSS()`
|
|
151
|
+
* 側だけで検証していたため、`generateColorTokens` を直接 import して不正な
|
|
152
|
+
* primaryColor を渡すと検証をすり抜けて壊れた CSS を返せてしまっていた。
|
|
153
|
+
* en: `generateColorTokens` is part of this package's public export surface
|
|
154
|
+
* (re-exported from `lib/generate-css.js`, which `package.json`'s
|
|
155
|
+
* `main`/`exports["."]` point to). Validate here — not only in
|
|
156
|
+
* `generateCSS()` — so a direct import of this function can't bypass the
|
|
157
|
+
* primary-color check and silently return broken CSS.
|
|
158
|
+
*
|
|
77
159
|
* @param {Object} colors colors.jsonから読み込んだ色の定義
|
|
78
160
|
* @param {Object} grayMapping gray.jsonから読み込んだグレーの定義
|
|
79
161
|
* @param {string} primaryColor プライマリカラーの名前
|
|
80
162
|
* @returns {string} 色のトークンのCSS文字列
|
|
163
|
+
* @throws {Error} primaryColor が有効な色名でない場合(`assertValidPrimaryColor` 参照)
|
|
81
164
|
*/
|
|
82
165
|
export function generateColorTokens(colors, grayMapping, primaryColor) {
|
|
166
|
+
assertValidPrimaryColor(primaryColor, colors, grayMapping);
|
|
167
|
+
|
|
83
168
|
const colorTokens = [];
|
|
84
169
|
|
|
85
170
|
// 通常の色トークンを生成
|
|
@@ -96,15 +181,15 @@ export function generateColorTokens(colors, grayMapping, primaryColor) {
|
|
|
96
181
|
}
|
|
97
182
|
});
|
|
98
183
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
}
|
|
184
|
+
// グレーの色トークンを生成(プライマリカラーに基づく)。primaryColor は
|
|
185
|
+
// 上の assertValidPrimaryColor で検証済みなので grayMapping[primaryColor]
|
|
186
|
+
// の存在は保証されている。
|
|
187
|
+
const grayColors = grayMapping[primaryColor];
|
|
188
|
+
Object.entries(grayColors).forEach(([key, hexValue]) => {
|
|
189
|
+
const grayLevel = GRAY_LEVEL_MAP[key] || key;
|
|
190
|
+
const oklchValue = hexToOklch(hexValue);
|
|
191
|
+
colorTokens.push(` --color-gray-${grayLevel}: ${oklchValue};`);
|
|
192
|
+
});
|
|
108
193
|
|
|
109
194
|
return colorTokens.join('\n');
|
|
110
195
|
}
|
package/lib/generate-css.js
CHANGED
|
@@ -15,7 +15,13 @@ import {
|
|
|
15
15
|
loadGrayMapping,
|
|
16
16
|
loadRadiusMapping,
|
|
17
17
|
} from './file-loader.js';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
generateColorTokens,
|
|
20
|
+
hexToRgba,
|
|
21
|
+
hexToOklch,
|
|
22
|
+
assertValidPrimaryColor,
|
|
23
|
+
getValidPrimaryColors,
|
|
24
|
+
} from './color-utils.js';
|
|
19
25
|
import {
|
|
20
26
|
manageFontImports,
|
|
21
27
|
extractFontImports,
|
|
@@ -434,6 +440,49 @@ function upsertViteIndexHtmlFonts(cwd, resolvedFonts) {
|
|
|
434
440
|
return { status: 'created', path: indexHtmlPath };
|
|
435
441
|
}
|
|
436
442
|
|
|
443
|
+
/**
|
|
444
|
+
* radius に指定できる有効な値の一覧を返す(radius.csv の name 列)。
|
|
445
|
+
* @param {Object} radiusMapping radius.csvから読み込んだ Radius マッピング
|
|
446
|
+
* @returns {string[]} 有効な radius 値の配列
|
|
447
|
+
*/
|
|
448
|
+
function getValidRadiusValues(radiusMapping) {
|
|
449
|
+
return Object.keys(radiusMapping);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* sparkle.config.json の `radius` を検証する。
|
|
454
|
+
*
|
|
455
|
+
* `radius` が radius.csv に無い値だと `--radius-action: var(--radius-<不正な
|
|
456
|
+
* 値>)` が dangling reference のまま出力され、`radius` が config に無いと
|
|
457
|
+
* `{{RADIUS}}` プレースホルダーがそもそも置換されず、テンプレート内に生の
|
|
458
|
+
* `{{RADIUS}}`(`{` `}` を含む)が残って CSS 構文自体が壊れる。primary
|
|
459
|
+
* (issue #222 発見3)と同じ「型強制・validation が無いことによる silent
|
|
460
|
+
* breakage」がここにも存在するため、同じパターンで検証する。
|
|
461
|
+
*
|
|
462
|
+
* en: An unknown `radius` value ships a dangling `var(--radius-<bad-value>)`;
|
|
463
|
+
* a missing `radius` leaves the raw `{{RADIUS}}` placeholder (including its
|
|
464
|
+
* braces) unreplaced, which is invalid CSS syntax. Same silent-breakage class
|
|
465
|
+
* as the `primary` bug (issue #222, finding 3) — validate it the same way.
|
|
466
|
+
*
|
|
467
|
+
* @param {string} radius config.radius の値
|
|
468
|
+
* @param {Object} radiusMapping radius.csvから読み込んだ Radius マッピング
|
|
469
|
+
* @throws {Error} radius が有効な値でない場合(`err.code === 'E_INVALID_RADIUS'`)
|
|
470
|
+
*/
|
|
471
|
+
function assertValidRadius(radius, radiusMapping) {
|
|
472
|
+
const validValues = getValidRadiusValues(radiusMapping);
|
|
473
|
+
|
|
474
|
+
if (typeof radius === 'string' && validValues.includes(radius)) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const err = new Error(
|
|
479
|
+
`sparkle.config.json の "radius" に無効な値が指定されています: ${JSON.stringify(radius)}\n` +
|
|
480
|
+
` 有効な値: ${validValues.join(', ')}`
|
|
481
|
+
);
|
|
482
|
+
err.code = 'E_INVALID_RADIUS';
|
|
483
|
+
throw err;
|
|
484
|
+
}
|
|
485
|
+
|
|
437
486
|
/**
|
|
438
487
|
* テンプレート変数を設定値で置換する
|
|
439
488
|
* @param {string} template CSSテンプレート
|
|
@@ -799,6 +848,23 @@ export function generateCSS(
|
|
|
799
848
|
const grayMapping = loadGrayMapping();
|
|
800
849
|
const radiusMapping = loadRadiusMapping();
|
|
801
850
|
|
|
851
|
+
// 4.5 radius の値を検証する(issue #222 発見3の横展開)。無効な値、または
|
|
852
|
+
// radius 自体が未指定だと `var(--radius-<不正な名前>)` への dangling
|
|
853
|
+
// reference、あるいは `{{RADIUS}}` プレースホルダーが置換されず CSS 構文が
|
|
854
|
+
// 壊れたまま出力されてしまう。primary と同種のバグだが、radius の置換は
|
|
855
|
+
// `processTemplate` 内にインライン実装されており(`generateColorTokens` の
|
|
856
|
+
// ような専用の公開関数を持たない)、`processTemplate` はフォント処理など
|
|
857
|
+
// 他のテストから部分的な config で直接呼ばれる汎用関数でもあるため、検証は
|
|
858
|
+
// `processTemplate` の内部にではなく、実際の CLI 実行経路であるここ
|
|
859
|
+
// (`generateCSS`)に置く。
|
|
860
|
+
// en: Validate radius before generation (same bug class as primary, applied
|
|
861
|
+
// to the sibling enum field). Unlike primary/generateColorTokens, radius
|
|
862
|
+
// substitution is inlined in processTemplate — a general-purpose function
|
|
863
|
+
// also called directly, with partial configs, by unrelated font tests — so
|
|
864
|
+
// validating inside processTemplate would break that flexibility. Validate
|
|
865
|
+
// here instead, at the actual CLI entry point.
|
|
866
|
+
assertValidRadius(config.radius, radiusMapping);
|
|
867
|
+
|
|
802
868
|
// 5. テンプレートを設定値で処理(resolvedFonts も返す)
|
|
803
869
|
const { css: processedCSS, resolvedFonts } = processTemplate(
|
|
804
870
|
template,
|
|
@@ -923,6 +989,10 @@ export {
|
|
|
923
989
|
hexToRgba,
|
|
924
990
|
hexToOklch,
|
|
925
991
|
generateColorTokens,
|
|
992
|
+
assertValidPrimaryColor,
|
|
993
|
+
getValidPrimaryColors,
|
|
994
|
+
assertValidRadius,
|
|
995
|
+
getValidRadiusValues,
|
|
926
996
|
dedupeFontImports,
|
|
927
997
|
// 設定解決
|
|
928
998
|
resolveExtend,
|
package/lib/load-plugins.js
CHANGED
|
@@ -61,7 +61,12 @@ function extractPluginEntry(pkg) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Discover and load anti-pattern plugins from the consumer's package.json deps
|
|
64
|
+
* Discover and load anti-pattern plugins from the consumer's package.json deps, plus
|
|
65
|
+
* a self-reference path: if the consumer's own package.json declares
|
|
66
|
+
* `sparkleCli.antiPatterns`, that entry is loaded too (packageName is recorded as the
|
|
67
|
+
* consumer's own `name`, or `"(self)"` if unnamed). This lets a library check its own
|
|
68
|
+
* source against the anti-patterns it defines for its consumers, without needing to
|
|
69
|
+
* list itself in its own dependencies.
|
|
65
70
|
*
|
|
66
71
|
* Returns an object of the shape:
|
|
67
72
|
* {
|
|
@@ -92,18 +97,8 @@ export async function loadAntiPatternPlugins({ cwd = process.cwd() } = {}) {
|
|
|
92
97
|
const groups = [];
|
|
93
98
|
const reminders = [];
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
if (!depPkgPath) continue;
|
|
98
|
-
|
|
99
|
-
const depPkg = readJsonSafe(depPkgPath);
|
|
100
|
-
if (!depPkg) continue;
|
|
101
|
-
|
|
102
|
-
const entry = extractPluginEntry(depPkg);
|
|
103
|
-
if (!entry) continue;
|
|
104
|
-
|
|
105
|
-
const resolvedFile = path.resolve(path.dirname(depPkgPath), entry);
|
|
106
|
-
const record = { packageName: depName, entry, resolvedFile, status: 'loaded' };
|
|
100
|
+
const loadEntry = async (packageName, entry, resolvedFile) => {
|
|
101
|
+
const record = { packageName, entry, resolvedFile, status: 'loaded' };
|
|
107
102
|
|
|
108
103
|
try {
|
|
109
104
|
if (!fs.existsSync(resolvedFile)) {
|
|
@@ -115,7 +110,7 @@ export async function loadAntiPatternPlugins({ cwd = process.cwd() } = {}) {
|
|
|
115
110
|
// plain object を export する plugin にも同じ厳しさを適用したい。
|
|
116
111
|
// en: Share shape validation with `defineAntiPatternPlugin` so plugins that skip
|
|
117
112
|
// the helper are still rejected at load time.
|
|
118
|
-
validatePluginShape(candidate, `${
|
|
113
|
+
validatePluginShape(candidate, `${packageName} (${entry})`);
|
|
119
114
|
groups.push(...candidate.groups);
|
|
120
115
|
record.groupIds = candidate.groups.map((group) => group.id);
|
|
121
116
|
if (Array.isArray(candidate.manualReviewReminders)) {
|
|
@@ -125,11 +120,43 @@ export async function loadAntiPatternPlugins({ cwd = process.cwd() } = {}) {
|
|
|
125
120
|
record.status = 'error';
|
|
126
121
|
record.error = error?.message ?? String(error);
|
|
127
122
|
console.warn(
|
|
128
|
-
`⚠️ sparkle-design-cli: failed to load anti-pattern plugin from ${
|
|
123
|
+
`⚠️ sparkle-design-cli: failed to load anti-pattern plugin from ${packageName} (entry: ${entry}, error: ${record.error})`
|
|
129
124
|
);
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
discovered.push(record);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 自己参照パス: consumer 自身が sparkleCli.antiPatterns を宣言しているケース
|
|
131
|
+
// (自分自身のソースに対して check を実行する場合、自分自身は自分の dependencies には
|
|
132
|
+
// 現れないため、依存パッケージ経由の discovery だけでは見つからない)。
|
|
133
|
+
// en: Self-reference path — a package checking its own source won't list itself in
|
|
134
|
+
// its own dependencies, so dependency-based discovery alone would miss it.
|
|
135
|
+
const selfEntry = extractPluginEntry(consumerPkg);
|
|
136
|
+
if (selfEntry) {
|
|
137
|
+
const resolvedFile = path.resolve(cwd, selfEntry);
|
|
138
|
+
await loadEntry(consumerPkg.name || '(self)', selfEntry, resolvedFile);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const depName of depNames) {
|
|
142
|
+
// 自分自身の名前を自分の dependencies に含むケース(workspace の self-link 等)では、
|
|
143
|
+
// 上の自己参照パスと重複してロードされてしまうためスキップする。
|
|
144
|
+
// en: Skip the consumer's own name if it also appears in its own dependencies (e.g. a
|
|
145
|
+
// workspace self-link) — otherwise the self-reference path above and this loop would
|
|
146
|
+
// load the same plugin entry twice.
|
|
147
|
+
if (depName === consumerPkg.name) continue;
|
|
148
|
+
|
|
149
|
+
const depPkgPath = resolvePluginPackageJson(depName, cwd);
|
|
150
|
+
if (!depPkgPath) continue;
|
|
151
|
+
|
|
152
|
+
const depPkg = readJsonSafe(depPkgPath);
|
|
153
|
+
if (!depPkg) continue;
|
|
154
|
+
|
|
155
|
+
const entry = extractPluginEntry(depPkg);
|
|
156
|
+
if (!entry) continue;
|
|
157
|
+
|
|
158
|
+
const resolvedFile = path.resolve(path.dirname(depPkgPath), entry);
|
|
159
|
+
await loadEntry(depName, entry, resolvedFile);
|
|
133
160
|
}
|
|
134
161
|
|
|
135
162
|
return { groups, reminders, discovered };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sparkle-design-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
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",
|