sparkle-design-cli 2.0.2 → 2.0.6
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 +10 -9
- package/bin/sparkle-design.js +5 -3
- package/lib/anti-pattern-rules.js +56 -0
- package/lib/generate-css.js +47 -1
- package/lib/setup.js +27 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ setup は次を自動で行います:
|
|
|
17
17
|
3. 未作成の場合のみ初期ファイルを生成:
|
|
18
18
|
- `sparkle.config.json`(デフォルトは blue / Inter / JetBrains Mono / md)
|
|
19
19
|
- `postcss.config.mjs`
|
|
20
|
-
- `src/app/globals.css
|
|
20
|
+
- Tailwind エントリ CSS — Next.js App Router なら `src/app/globals.css`、Vite なら `src/index.css`、それ以外は `src/globals.css`(プロジェクト構成から自動判定)
|
|
21
21
|
4. AI 指示ファイル(`CLAUDE.md` / `AGENTS.md` / Cursor rules)に Sparkle Design Guard ブロックを追加
|
|
22
22
|
5. `sparkle-design-cli generate` を実行し、`sparkle-design.css` と `SparkleHead.tsx` を生成
|
|
23
23
|
|
|
@@ -131,6 +131,7 @@ sparkle-design-cli check --help
|
|
|
131
131
|
- disabled を isDisabled の代わりに使わない
|
|
132
132
|
- Button の prefixIcon に JSX を渡さない
|
|
133
133
|
- Icon の children にテキストを渡さない
|
|
134
|
+
- クリック可能な Card を `<button>` / `<a>` / `role="button"` でラップせず、`ClickableCard` を使う
|
|
134
135
|
|
|
135
136
|
#### Manual Review Reminders
|
|
136
137
|
|
|
@@ -159,7 +160,7 @@ AI エージェントや CI からこの `lint:sparkle` を呼ぶ運用にする
|
|
|
159
160
|
|
|
160
161
|
1. **パッケージマネージャーの検出**(pnpm / yarn / bun / npm)
|
|
161
162
|
2. **パッケージのインストール** — `sparkle-design`(dependencies)、`tailwindcss` + `@tailwindcss/postcss`(devDependencies)
|
|
162
|
-
3. **初期ファイルの生成** — `sparkle.config.json` / `postcss.config.mjs` / `globals.css`
|
|
163
|
+
3. **初期ファイルの生成** — `sparkle.config.json` / `postcss.config.mjs` / Tailwind エントリ CSS(Next.js は `src/app/globals.css`、Vite は `src/index.css`)が無い場合のみ作成
|
|
163
164
|
4. **AI ガード設定** — `CLAUDE.md` などに Sparkle Design Guard ブロックと `lint:sparkle` script を追加
|
|
164
165
|
5. **`generate` の実行** — `sparkle-design.css` と `SparkleHead.tsx` を生成
|
|
165
166
|
|
|
@@ -178,7 +179,7 @@ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaf
|
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
既存ファイルは上書きされません:
|
|
181
|
-
- `sparkle.config.json` / `postcss.config.*` /
|
|
182
|
+
- `sparkle.config.json` / `postcss.config.*` / Tailwind エントリ CSS が存在する場合はそのまま保持
|
|
182
183
|
- 既に依存関係に入っているパッケージはインストールをスキップ
|
|
183
184
|
- `package.json` の既存の `lint:sparkle*` 独自 script は保持(`--force-script-update` で上書き)
|
|
184
185
|
|
|
@@ -267,11 +268,11 @@ Figma プラグインが出力する基本4項目に加えて、プロジェク
|
|
|
267
268
|
|
|
268
269
|
#### extend.source-packages
|
|
269
270
|
|
|
270
|
-
`sparkle-design` を npm
|
|
271
|
+
`sparkle-design` を npm パッケージとして利用する場合に必須。Tailwind エントリ CSS(自動検出)に `@source` ディレクティブを自動挿入します。`sparkle-design` は常にデフォルトで含まれます。
|
|
271
272
|
|
|
272
273
|
#### extend.custom-css
|
|
273
274
|
|
|
274
|
-
プロジェクト固有のカスタムトークンの CSS
|
|
275
|
+
プロジェクト固有のカスタムトークンの CSS ファイルパス。Tailwind エントリ CSS に `@import` が自動挿入されます。`sparkle-design.css` に直接追加すると `generate` 実行時に上書きされるため、必ず別ファイルに分離してください。
|
|
275
276
|
|
|
276
277
|
## 出力
|
|
277
278
|
|
|
@@ -286,7 +287,7 @@ Figma プラグインが出力する基本4項目に加えて、プロジェク
|
|
|
286
287
|
CLI は自動的にフォント管理を行います:
|
|
287
288
|
|
|
288
289
|
1. **フォント検出**: `sparkle-design.css` から Google Fonts の `@import` 文を検出
|
|
289
|
-
2.
|
|
290
|
+
2. **エントリ CSS への移動**: フォントの `@import` を Tailwind エントリ CSS(`globals.css` / `index.css` 等、自動検出)の先頭に移動
|
|
290
291
|
3. **sparkle-design.css から削除**: 元のファイルからフォント `@import` を削除
|
|
291
292
|
|
|
292
293
|
### なぜこの処理が必要なのか?
|
|
@@ -301,15 +302,15 @@ CSS の仕様では、`@import` 文は `@charset` と `@layer` 以外のすべ
|
|
|
301
302
|
|
|
302
303
|
### 動作条件
|
|
303
304
|
|
|
304
|
-
- `
|
|
305
|
-
-
|
|
305
|
+
- Tailwind エントリ CSS(`@import "tailwindcss"` を含む CSS ファイル)が `sparkle-design.css` と同じディレクトリに存在する場合のみ実行されます(ファイル名に依存しません)
|
|
306
|
+
- エントリ CSS が存在しない場合は、フォント管理処理はスキップされます
|
|
306
307
|
|
|
307
308
|
### 出力例
|
|
308
309
|
|
|
309
310
|
```text
|
|
310
311
|
📦 フォント管理処理を開始します...
|
|
311
312
|
📝 3個のフォントimportを検出しました
|
|
312
|
-
✅
|
|
313
|
+
✅ エントリ CSS にフォントimportを追加しました
|
|
313
314
|
✅ sparkle-design.css からフォントimportを削除しました
|
|
314
315
|
```
|
|
315
316
|
|
package/bin/sparkle-design.js
CHANGED
|
@@ -202,13 +202,14 @@ Setup options:
|
|
|
202
202
|
--instructions-path <path> AI 指示ファイルの出力先パス (default: アシスタント別のデフォルト)
|
|
203
203
|
--force-script-update 既存の lint:sparkle 系 script も上書きする
|
|
204
204
|
--skip-install パッケージインストール(sparkle-design, tailwindcss)をスキップ
|
|
205
|
-
--skip-scaffold 初期ファイル(sparkle.config.json, postcss.config,
|
|
205
|
+
--skip-scaffold 初期ファイル(sparkle.config.json, postcss.config, エントリ CSS)生成をスキップ
|
|
206
206
|
--skip-generate generate 実行をスキップ
|
|
207
207
|
|
|
208
208
|
Setup の動作:
|
|
209
209
|
1. パッケージマネージャー検出(pnpm / npm / yarn / bun)
|
|
210
210
|
2. sparkle-design を dependencies に追加、tailwindcss + @tailwindcss/postcss を devDependencies に追加
|
|
211
|
-
3. sparkle.config.json / postcss.config.mjs /
|
|
211
|
+
3. sparkle.config.json / postcss.config.mjs / Tailwind エントリ CSS(Next.js は
|
|
212
|
+
src/app/globals.css、Vite は src/index.css 等)が無ければ初期ファイルを生成
|
|
212
213
|
4. AI アシスタントのガードブロックを指定ファイルに挿入(存在しなければ新規作成)
|
|
213
214
|
5. sparkle-design-cli generate を実行し、sparkle-design.css と SparkleHead.tsx を生成
|
|
214
215
|
|
|
@@ -226,7 +227,8 @@ Check で検出する主なパターン:
|
|
|
226
227
|
- disabled を isDisabled の代わりに使っている
|
|
227
228
|
- Button の prefixIcon に JSX を渡している
|
|
228
229
|
- Icon の children にテキストを渡している
|
|
229
|
-
-
|
|
230
|
+
- <Card> を <button> / <a> / role="button" でラップしている(ClickableCard を使う)
|
|
231
|
+
- エントリ CSS にフォント @import が残っている(SparkleHead への移行が必要)
|
|
230
232
|
- CSP ヘッダーで Google Fonts がブロックされている可能性
|
|
231
233
|
|
|
232
234
|
JSON 出力では manualReviewReminders も返すため、AI から実行する場合は --format json を推奨
|
|
@@ -489,6 +489,62 @@ const ANTI_PATTERN_GROUPS = [
|
|
|
489
489
|
},
|
|
490
490
|
],
|
|
491
491
|
},
|
|
492
|
+
{
|
|
493
|
+
id: 'card-clickable-wrap',
|
|
494
|
+
check: {
|
|
495
|
+
description: 'クリック可能な Card は ClickableCard を使う(button / a / role="button" でラップしない)',
|
|
496
|
+
recommendation:
|
|
497
|
+
'`<Card>` を `<button>` / `<a>` / `role="button"` を持つ要素で包まず、`ClickableCard` を使ってください。`ClickableCard` が適切な role / keyboard 対応 / focus ring を担保します。',
|
|
498
|
+
// <button> / <a> / role="button" が直接 <Card> を子に持つケース
|
|
499
|
+
// en: <button> / <a> / role="button" wrapping a <Card> directly
|
|
500
|
+
pattern:
|
|
501
|
+
/<(?:button\b|a\b|[A-Za-z][A-Za-z0-9]*\b[^>]*\brole\s*=\s*["']button["'])[^>]*>\s*<Card\b/g,
|
|
502
|
+
},
|
|
503
|
+
featureSection: lines([
|
|
504
|
+
'### クリック可能な Card: ClickableCard を使う',
|
|
505
|
+
'',
|
|
506
|
+
'```tsx',
|
|
507
|
+
'// ✅ Correct',
|
|
508
|
+
'<ClickableCard onClick={handle}>',
|
|
509
|
+
' <CardHeader><CardTitle>タイトル</CardTitle></CardHeader>',
|
|
510
|
+
'</ClickableCard>',
|
|
511
|
+
'',
|
|
512
|
+
'// ❌ Wrong - <button> / <a> で Card を包まない',
|
|
513
|
+
'<button type="button" onClick={handle}>',
|
|
514
|
+
' <Card>',
|
|
515
|
+
' <CardHeader><CardTitle>タイトル</CardTitle></CardHeader>',
|
|
516
|
+
' </Card>',
|
|
517
|
+
'</button>',
|
|
518
|
+
'```',
|
|
519
|
+
'',
|
|
520
|
+
'`ClickableCard` はクリック可能な Card のパターンとして必要な `role` / キーボード操作 / focus ring を提供する。`<button>` / `<a>` / `role="button"` でラップすると、ボタンの内側に対話型要素(リンクやフォーム要素)を置いたときにネストされた interactive 要素になりアクセシビリティ違反になる。',
|
|
521
|
+
]),
|
|
522
|
+
jsdocTargets: [
|
|
523
|
+
{
|
|
524
|
+
file: 'src/components/ui/card/index.tsx',
|
|
525
|
+
targetName: 'Card',
|
|
526
|
+
section: {
|
|
527
|
+
bullets: [
|
|
528
|
+
{
|
|
529
|
+
ja: '`<Card>` を `<button>` / `<a>` / `role="button"` で包まないでください。クリック可能な Card には専用の `ClickableCard` を使ってください。',
|
|
530
|
+
en: 'Do not wrap `<Card>` with `<button>` / `<a>` / `role="button"`. Use the dedicated `ClickableCard` component for clickable cards.',
|
|
531
|
+
},
|
|
532
|
+
],
|
|
533
|
+
example: lines([
|
|
534
|
+
'// ✅ Correct',
|
|
535
|
+
'<ClickableCard onClick={handle}>',
|
|
536
|
+
' <CardHeader><CardTitle>タイトル</CardTitle></CardHeader>',
|
|
537
|
+
'</ClickableCard>',
|
|
538
|
+
'',
|
|
539
|
+
'// ❌ Wrong',
|
|
540
|
+
'<button type="button" onClick={handle}>',
|
|
541
|
+
' <Card>...</Card>',
|
|
542
|
+
'</button>',
|
|
543
|
+
]),
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
},
|
|
492
548
|
{
|
|
493
549
|
id: 'icon-scale',
|
|
494
550
|
featureSection: lines([
|
package/lib/generate-css.js
CHANGED
|
@@ -332,6 +332,40 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
|
|
|
332
332
|
* @param {string} cssContent CSS内容
|
|
333
333
|
* @param {string|null} outputPath カスタム出力パス(オプション)
|
|
334
334
|
*/
|
|
335
|
+
// 自動検出対象の既知デザインシステムパッケージ。
|
|
336
|
+
// `sparkle-design` は CLI 側のデフォルトパッケージなのでリストには加えず「存在判定」にだけ使う。
|
|
337
|
+
// en: Known design-system packages to auto-detect. `sparkle-design` is the CLI's
|
|
338
|
+
// default source package, so it only acts as an "enables @source" signal.
|
|
339
|
+
const KNOWN_DESIGN_SYSTEM_PACKAGES = [
|
|
340
|
+
'@goodpatch/sparkle-design-internal',
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* package.json の dependencies / devDependencies から既知のデザインシステムパッケージを検出する。
|
|
345
|
+
* - `sparkle-design` が入っていれば(CLI のデフォルトで @source に含まれるため)空配列を返し、@source 挿入を有効化
|
|
346
|
+
* - `@goodpatch/sparkle-design-internal` など追加の既知パッケージが入っていればリストに含める
|
|
347
|
+
* - いずれも見つからなければ null(従来通り @source を生成しない)
|
|
348
|
+
* en: Scan package.json and surface source-packages when a design-system package is present.
|
|
349
|
+
*/
|
|
350
|
+
function detectSourcePackagesFromPackageJson(cwd = process.cwd()) {
|
|
351
|
+
try {
|
|
352
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
353
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
|
|
354
|
+
|
|
355
|
+
const hasDefault = Object.prototype.hasOwnProperty.call(deps, 'sparkle-design');
|
|
356
|
+
const extras = KNOWN_DESIGN_SYSTEM_PACKAGES.filter((name) =>
|
|
357
|
+
Object.prototype.hasOwnProperty.call(deps, name)
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
if (hasDefault || extras.length > 0) {
|
|
361
|
+
return extras;
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
335
369
|
function writeCSS(cssContent, outputPath = null) {
|
|
336
370
|
// カスタムパスが指定されていない場合は実行場所からsrc/appディレクトリに出力
|
|
337
371
|
const defaultOutputPath = path.resolve(
|
|
@@ -395,8 +429,19 @@ export function generateCSS(configPath = null, outputPath = null, globalsPath =
|
|
|
395
429
|
writeSparkleHead(sparkleHeadContent, resolvedOutputPath);
|
|
396
430
|
|
|
397
431
|
// 8. フォント管理の自動処理を実行(globals.css にはフォント @import を差し込まない)
|
|
432
|
+
// source-packages は以下の合成で決まる:
|
|
433
|
+
// 1. package.json から既知のデザインシステムパッケージを自動検出(baseline)
|
|
434
|
+
// 2. config の extend.source-packages(ユーザー追記分、例: クライアント固有パッケージ)
|
|
435
|
+
// 両方空なら null(@source を生成しない)。
|
|
436
|
+
// en: source-packages are resolved by merging auto-detection (from package.json)
|
|
437
|
+
// with explicit config entries. If neither surfaces anything, @source is skipped.
|
|
398
438
|
console.log(MESSAGES.FONT_MANAGEMENT_START);
|
|
399
|
-
const
|
|
439
|
+
const detectedPackages = detectSourcePackagesFromPackageJson();
|
|
440
|
+
const explicitPackages = 'source-packages' in config ? (config['source-packages'] || []) : null;
|
|
441
|
+
const sourcePackages =
|
|
442
|
+
detectedPackages !== null || explicitPackages !== null
|
|
443
|
+
? [...new Set([...(detectedPackages ?? []), ...(explicitPackages ?? [])])]
|
|
444
|
+
: null;
|
|
400
445
|
const customCssPath = config['custom-css'] || null;
|
|
401
446
|
const globalsPathOverride = globalsPath || config['globals-path'] || null;
|
|
402
447
|
manageFontImports(resolvedOutputPath, sourcePackages, customCssPath, globalsPathOverride);
|
|
@@ -414,6 +459,7 @@ export {
|
|
|
414
459
|
// メイン関数
|
|
415
460
|
processTemplate,
|
|
416
461
|
writeCSS,
|
|
462
|
+
detectSourcePackagesFromPackageJson,
|
|
417
463
|
// ファイル読み込み
|
|
418
464
|
loadConfig,
|
|
419
465
|
loadTemplate,
|
package/lib/setup.js
CHANGED
|
@@ -5,6 +5,10 @@ import { generateCSS } from './generate-css.js';
|
|
|
5
5
|
|
|
6
6
|
// デフォルトの sparkle.config.json テンプレート
|
|
7
7
|
// en: Default sparkle.config.json template
|
|
8
|
+
// @source ディレクティブの生成は generate 側が package.json から既知のデザインシステム
|
|
9
|
+
// パッケージ(sparkle-design / @goodpatch/sparkle-design-internal 等)を自動検出して
|
|
10
|
+
// 行うため、ここで extend.source-packages を指定する必要はない。独自パッケージを
|
|
11
|
+
// 追加する場合のみユーザーが extend.source-packages に追記する。
|
|
8
12
|
const DEFAULT_SPARKLE_CONFIG = {
|
|
9
13
|
primary: 'blue',
|
|
10
14
|
'font-pro': 'Inter',
|
|
@@ -309,12 +313,33 @@ function ensurePackagesInstalled(cwd, packageManager, candidates, { dev = false,
|
|
|
309
313
|
return { ran: true, reason: 'installed', packages: missing, packageManager, dev };
|
|
310
314
|
}
|
|
311
315
|
|
|
316
|
+
// Vite の設定ファイル候補(プロジェクト種別の判定に使う)
|
|
317
|
+
// en: Vite config files used to detect a Vite project
|
|
318
|
+
const VITE_CONFIG_FILES = [
|
|
319
|
+
'vite.config.ts',
|
|
320
|
+
'vite.config.js',
|
|
321
|
+
'vite.config.mjs',
|
|
322
|
+
'vite.config.cjs',
|
|
323
|
+
'vite.config.mts',
|
|
324
|
+
'vite.config.cts',
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
function isViteProject(cwd) {
|
|
328
|
+
return VITE_CONFIG_FILES.some((name) => fs.existsSync(path.join(cwd, name)));
|
|
329
|
+
}
|
|
330
|
+
|
|
312
331
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
332
|
+
* エントリポイント CSS の新規作成先を決定する。
|
|
333
|
+
* - Next.js App Router (src/app あり) → src/app/globals.css
|
|
334
|
+
* - Vite プロジェクト → src/index.css(Vite の慣習に合わせる)
|
|
335
|
+
* - それ以外で src/ がある → src/globals.css
|
|
336
|
+
* - フォールバック → src/app/globals.css
|
|
337
|
+
* en: Pick the scaffold target for the entry CSS. Vite projects get src/index.css
|
|
338
|
+
* to match the common convention, Next.js App Router gets src/app/globals.css.
|
|
315
339
|
*/
|
|
316
340
|
function defaultGlobalsCssTarget(cwd) {
|
|
317
341
|
if (fs.existsSync(path.join(cwd, 'src/app'))) return 'src/app/globals.css';
|
|
342
|
+
if (isViteProject(cwd)) return 'src/index.css';
|
|
318
343
|
if (fs.existsSync(path.join(cwd, 'src'))) return 'src/globals.css';
|
|
319
344
|
return 'src/app/globals.css';
|
|
320
345
|
}
|