sparkle-design-cli 1.3.7 → 1.3.9

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
@@ -1,6 +1,6 @@
1
1
  # Sparkle Design CLI
2
2
 
3
- デザインシステム CSS を設定ファイルから生成する CLI ツールです。
3
+ デザインシステム CSS の生成と、Sparkle Design のアンチパターン検査を行う CLI ツールです。
4
4
 
5
5
  ## インストール
6
6
 
@@ -10,7 +10,22 @@ npm install -g sparkle-design-cli
10
10
 
11
11
  ## 使用方法
12
12
 
13
- ### 基本的な使用方法
13
+ ### サブコマンド
14
+
15
+ ```bash
16
+ # CSS を生成
17
+ npx sparkle-design-cli generate
18
+
19
+ # アンチパターンを検査
20
+ npx sparkle-design-cli check src --strict
21
+ ```
22
+
23
+ ### 後方互換モード
24
+
25
+ 既存の `npx sparkle-design-cli` も引き続き動作し、内部的には `generate` として扱われます。
26
+ ただし後方互換のための動作なので、新規利用やドキュメント上の案内では `sparkle-design-cli generate` を使用してください。
27
+
28
+ ### generate: 基本的な使用方法
14
29
 
15
30
  1. プロジェクトのルートディレクトリに `sparkle.config.json` ファイルを作成または配置します:
16
31
 
@@ -26,39 +41,77 @@ npm install -g sparkle-design-cli
26
41
  2. CLI ツールを実行します:
27
42
 
28
43
  ```bash
29
- npx sparkle-design-cli
44
+ npx sparkle-design-cli generate
30
45
  ```
31
46
 
32
- または、グローバルにインストールした場合:
47
+ 既存プロジェクトでサブコマンドなしの呼び出しを使っている場合も、後方互換として次の実行方法を継続利用できます:
33
48
 
34
49
  ```bash
35
- sparkle-design-cli
50
+ npx sparkle-design-cli
36
51
  ```
37
52
 
38
53
  3. `src/app/sparkle-design.css` に CSS ファイルが生成されます。
39
54
 
40
- ### コマンドオプション
55
+ ### generate: コマンドオプション
41
56
 
42
57
  ```bash
43
58
  # ヘルプを表示
44
- sparkle-design-cli --help
59
+ sparkle-design-cli generate --help
45
60
 
46
61
  # カスタム設定ファイルを指定
47
- sparkle-design-cli --config ./config/design.json
62
+ sparkle-design-cli generate --config ./config/design.json
48
63
 
49
64
  # カスタム出力先を指定
50
- sparkle-design-cli --output ./styles/design.css
65
+ sparkle-design-cli generate --output ./styles/design.css
51
66
 
52
67
  # 設定ファイルと出力先を両方指定
53
- sparkle-design-cli -c ./config/custom.json -o ./dist/styles.css
68
+ sparkle-design-cli generate -c ./config/custom.json -o ./dist/styles.css
54
69
  ```
55
70
 
56
- #### オプション一覧
71
+ #### generate オプション一覧
57
72
 
58
73
  - `-h, --help`: ヘルプメッセージを表示
59
74
  - `-c, --config <パス>`: 設定ファイルのパス(デフォルト: `./sparkle.config.json`)
60
75
  - `-o, --output <パス>`: 出力ファイルのパス(デフォルト: `./src/app/sparkle-design.css`)
61
76
 
77
+ ### check: アンチパターン検査
78
+
79
+ `sparkle-design-cli check` は、導入先プロジェクトで発生しやすい Sparkle Design のアンチパターンを検出します。
80
+
81
+ ```bash
82
+ # src を検査
83
+ sparkle-design-cli check
84
+
85
+ # 特定ディレクトリを strict モードで検査
86
+ sparkle-design-cli check src/components src/features --strict
87
+
88
+ # check のヘルプを表示
89
+ sparkle-design-cli check --help
90
+ ```
91
+
92
+ #### check オプション一覧
93
+
94
+ - `-h, --help`: ヘルプメッセージを表示
95
+ - `--strict`: 違反が見つかった場合に exit code 1 で終了
96
+
97
+ #### 現在検出するルール
98
+
99
+ - フォーム入力に Dialog を使わない
100
+ - DialogCancel/DialogAction を Button で二重ラップしない
101
+ - shadcn/ui 既定 token を Sparkle Design 内へ持ち込まない
102
+
103
+ #### 導入先での推奨設定
104
+
105
+ ```json
106
+ {
107
+ "scripts": {
108
+ "lint:sparkle": "sparkle-design-cli check src --strict"
109
+ }
110
+ }
111
+ ```
112
+
113
+ AI エージェントや CI からこの `lint:sparkle` を呼ぶ運用にすると、ガイドラインの注意書きだけに頼らず機械的に検査できます。
114
+
62
115
  ## 設定ファイル (sparkle.config.json)
63
116
 
64
117
  ### 設定ファイルの作成
@@ -105,12 +158,12 @@ sparkle-design-cli -c ./config/custom.json -o ./dist/styles.css
105
158
  生成される `globals.css`:
106
159
 
107
160
  ```css
108
- @import "tailwindcss";
161
+ @import 'tailwindcss';
109
162
  /* npm パッケージのコンテンツスキャン(Tailwind がクラスを検出するために必要) */
110
163
  @source "../node_modules/@goodpatch/sparkle-design/dist";
111
164
  @source "../node_modules/@scope/my-extension-design/dist";
112
165
  /* Sparkle Design のカスタム定義(Tailwindの後にインポート) */
113
- @import "./sparkle-design.css";
166
+ @import './sparkle-design.css';
114
167
  ```
115
168
 
116
169
  > **注意**: `source-packages` を指定しない場合、`@source` ディレクティブは生成されません。
@@ -192,14 +245,17 @@ npm run format:check
192
245
  ### CLI実行テスト
193
246
 
194
247
  ```bash
195
- # デフォルト設定でテスト
248
+ # CSS生成
249
+ sparkle-design-cli generate
250
+
251
+ # 既存呼び出しの後方互換モード
196
252
  sparkle-design-cli
197
253
 
198
254
  # ヘルプ表示
199
- sparkle-design-cli --help
255
+ sparkle-design-cli generate --help
200
256
 
201
- # カスタムパスでテスト
202
- sparkle-design-cli -c test-config.json -o test-output.css
257
+ # 検査
258
+ sparkle-design-cli check src --strict
203
259
  ```
204
260
 
205
261
  ## ライセンス
@@ -1,93 +1,176 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { generateCSS } from '../lib/generate-css.js';
4
+ import { checkProject } from '../lib/check.js';
4
5
 
5
- // コマンドライン引数を解析
6
- function parseArguments() {
7
- const args = process.argv.slice(2);
6
+ const SUBCOMMANDS = new Set(['generate', 'check']);
7
+
8
+ function requireOptionValue(args, index, flags) {
9
+ const value = args[index + 1];
10
+ if (!value || value.startsWith('-')) {
11
+ throw new Error(`Missing value for ${flags}`);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ function parseGenerateOptions(args) {
8
17
  const options = {
9
18
  configPath: null,
10
19
  outputPath: null,
11
20
  help: false,
12
21
  };
13
22
 
14
- for (let i = 0; i < args.length; i++) {
23
+ for (let i = 0; i < args.length; i += 1) {
15
24
  const arg = args[i];
16
25
 
17
26
  if (arg === '-h' || arg === '--help') {
18
27
  options.help = true;
19
28
  } else if (arg === '-c' || arg === '--config') {
20
- options.configPath = args[i + 1];
21
- i++; // 次の引数をスキップ
29
+ options.configPath = requireOptionValue(args, i, '-c/--config');
30
+ i += 1;
22
31
  } else if (arg === '-o' || arg === '--output') {
23
- options.outputPath = args[i + 1];
24
- i++; // 次の引数をスキップ
32
+ options.outputPath = requireOptionValue(args, i, '-o/--output');
33
+ i += 1;
34
+ } else {
35
+ throw new Error(`Unknown option for generate: ${arg}`);
36
+ }
37
+ }
38
+
39
+ return options;
40
+ }
41
+
42
+ function parseCheckOptions(args) {
43
+ const options = {
44
+ targets: [],
45
+ strict: false,
46
+ help: false,
47
+ };
48
+
49
+ for (let i = 0; i < args.length; i += 1) {
50
+ const arg = args[i];
51
+
52
+ if (arg === '-h' || arg === '--help') {
53
+ options.help = true;
54
+ } else if (arg === '--strict') {
55
+ options.strict = true;
56
+ } else if (arg.startsWith('-')) {
57
+ throw new Error(`Unknown option for check: ${arg}`);
58
+ } else {
59
+ options.targets.push(arg);
25
60
  }
26
61
  }
27
62
 
28
63
  return options;
29
64
  }
30
65
 
31
- // ヘルプメッセージを表示
32
66
  function showHelp() {
33
67
  console.log(`
34
- Sparkle Design CLI - デザインシステム CSS 生成ツール
68
+ Sparkle Design CLI
35
69
 
36
70
  使用方法:
37
- sparkle-design-cli [オプション]
71
+ sparkle-design-cli <command> [options]
72
+
73
+ Commands:
74
+ generate sparkle.config.json から CSS を生成
75
+ check Sparkle Design のアンチパターンを検査
76
+
77
+ 後方互換:
78
+ sparkle-design-cli [generate options]
79
+ サブコマンドなしの実行は現在も generate として動作しますが、
80
+ 正式リリース時に削除予定です。今後は sparkle-design-cli generate を使ってください。
38
81
 
39
- 基本的な使用方法:
40
- 1. プロジェクトのルートディレクトリに sparkle.config.json ファイルを作成または配置
41
- 2. CLI ツールを実行: sparkle-design-cli
42
- 3. src/app/sparkle-design.css に CSS ファイルが生成されます
82
+ Generate:
83
+ sparkle-design-cli generate
84
+ sparkle-design-cli generate --config ./config/design.json
85
+ sparkle-design-cli generate --output ./styles/design.css
86
+ sparkle-design-cli generate -c ./config/custom.json -o ./dist/styles.css
43
87
 
44
- オプション:
88
+ Check:
89
+ sparkle-design-cli check
90
+ sparkle-design-cli check src --strict
91
+ sparkle-design-cli check src/components src/features
92
+
93
+ Generate options:
45
94
  -h, --help このヘルプメッセージを表示
46
- -c, --config <パス> 設定ファイルのパス (デフォルト: ./sparkle.config.json)
47
- -o, --output <パス> 出力ファイルのパス (デフォルト: ./src/app/sparkle-design.css)
48
-
49
- 使用例:
50
- sparkle-design-cli
51
- sparkle-design-cli --config ./config/design.json
52
- sparkle-design-cli --output ./styles/design.css
53
- sparkle-design-cli -c ./config/custom.json -o ./dist/styles.css
54
-
55
- 設定ファイル (sparkle.config.json):
56
- 設定ファイルは専用のプラグインから自動生成することを推奨します。
57
- プロジェクトのルートディレクトリに配置してください。
58
-
59
- 設定例:
60
- {
61
- "primary": "blue",
62
- "font-pro": "BIZ UDPGothic",
63
- "font-mono": "BIZ UDGothic",
64
- "radius": "md"
65
- }
95
+ -c, --config <path> 設定ファイルのパス (default: ./sparkle.config.json)
96
+ -o, --output <path> 出力ファイルのパス (default: ./src/app/sparkle-design.css)
66
97
 
67
- 設定オプション:
68
- - primary: プライマリカラー (blue, red, orange など)
69
- - font-pro: プロポーショナルフォント (Google Fonts の名前)
70
- - font-mono: モノスペースフォント (Google Fonts の名前)
71
- - radius: 角丸設定 (sm, md, lg など)
98
+ Check options:
99
+ -h, --help このヘルプメッセージを表示
100
+ --strict 違反があれば exit code 1 で終了
72
101
 
73
- 出力:
74
- - デフォルト出力先: src/app/sparkle-design.css
75
- - カスタム出力先: -o オプションで指定可能
76
- - 実行場所を基準として相対パスで処理されます
102
+ Check で検出する主なパターン:
103
+ - フォーム入力に Dialog を使っている
104
+ - DialogCancel / DialogAction を <Button> で二重ラップしている
105
+ - text-muted-foreground / bg-background / border-border を持ち込んでいる
77
106
  `);
78
107
  }
79
108
 
80
- // メイン処理
81
- function main() {
82
- const options = parseArguments();
109
+ function showGenerateDeprecationNotice() {
110
+ console.warn(
111
+ '[sparkle-design-cli] Running without a subcommand defaults to `generate`. ' +
112
+ 'Use `sparkle-design-cli generate` instead. This compatibility mode will be removed at the formal release.'
113
+ );
114
+ }
83
115
 
84
- if (options.help) {
85
- showHelp();
86
- process.exit(0);
87
- }
116
+ function main() {
117
+ const args = process.argv.slice(2);
118
+ const command = args[0];
88
119
 
89
120
  try {
90
- generateCSS(options.configPath, options.outputPath);
121
+ if (args.length === 0) {
122
+ showGenerateDeprecationNotice();
123
+ generateCSS();
124
+ return;
125
+ }
126
+
127
+ if (command === '-h' || command === '--help') {
128
+ showHelp();
129
+ process.exit(0);
130
+ }
131
+ if (command === 'generate') {
132
+ const options = parseGenerateOptions(args.slice(1));
133
+
134
+ if (options.help) {
135
+ showHelp();
136
+ process.exit(0);
137
+ }
138
+
139
+ generateCSS(options.configPath, options.outputPath);
140
+ return;
141
+ }
142
+
143
+ if (command === 'check') {
144
+ const options = parseCheckOptions(args.slice(1));
145
+
146
+ if (options.help) {
147
+ showHelp();
148
+ process.exit(0);
149
+ }
150
+
151
+ const hasFindings = checkProject(options.targets, { strict: options.strict });
152
+ if (hasFindings && options.strict) {
153
+ process.exit(1);
154
+ }
155
+ return;
156
+ }
157
+
158
+ if (!SUBCOMMANDS.has(command) && command.startsWith('-')) {
159
+ showGenerateDeprecationNotice();
160
+ const options = parseGenerateOptions(args);
161
+
162
+ if (options.help) {
163
+ showHelp();
164
+ process.exit(0);
165
+ }
166
+
167
+ generateCSS(options.configPath, options.outputPath);
168
+ return;
169
+ }
170
+
171
+ if (!SUBCOMMANDS.has(command)) {
172
+ throw new Error(`Unknown command: ${command}`);
173
+ }
91
174
  } catch (error) {
92
175
  console.error('❌ エラーが発生しました:', error.message);
93
176
  process.exit(1);
@@ -0,0 +1,698 @@
1
+ const COMMON_INTRO =
2
+ '以下は頻繁に発生する誤用パターンです。コンポーネントが提供する専用 props・サブコンポーネントを使ってください。';
3
+
4
+ function lines(values) {
5
+ return values.join('\n');
6
+ }
7
+
8
+ function renderJSDocSection(section) {
9
+ const output = ['**アンチパターン / Anti-patterns**', ''];
10
+
11
+ for (const bullet of section.bullets) {
12
+ output.push(`- ${bullet.ja}`);
13
+ output.push(` en: ${bullet.en}`);
14
+ }
15
+
16
+ if (section.example) {
17
+ output.push('');
18
+ output.push('```tsx');
19
+ output.push(...section.example.split('\n'));
20
+ output.push('```');
21
+ }
22
+
23
+ return output.map((line) => (line ? ` * ${line}` : ' *')).join('\n');
24
+ }
25
+
26
+ const ANTI_PATTERN_GROUPS = [
27
+ {
28
+ id: 'card-description',
29
+ featureSection: lines([
30
+ '### CardTitle の補足テキストには CardDescription を使う',
31
+ '',
32
+ '```tsx',
33
+ '// ✅ Correct',
34
+ '<CardHeader>',
35
+ ' <CardTitle>',
36
+ ' プロジェクト一覧',
37
+ ' <CardDescription className="character-3-regular-pro text-text-low">',
38
+ ' 全 12 件',
39
+ ' </CardDescription>',
40
+ ' </CardTitle>',
41
+ '</CardHeader>',
42
+ '',
43
+ '// ❌ Wrong — CardTitle 内に span で補足を入れない',
44
+ '<CardHeader>',
45
+ ' <CardTitle>',
46
+ ' プロジェクト一覧',
47
+ ' <span className="text-sm text-neutral-500">(12件)</span>',
48
+ ' </CardTitle>',
49
+ '</CardHeader>',
50
+ '```',
51
+ '',
52
+ 'CardTitle 内の補足情報や件数は CardDescription を使い、必要な typography / color token は className で明示する。',
53
+ ]),
54
+ jsdocTargets: [],
55
+ },
56
+ {
57
+ id: 'size-alignment',
58
+ featureSection: lines([
59
+ '### Input / Select と横並びの Button はサイズを揃える',
60
+ '',
61
+ '```tsx',
62
+ '// ✅ Correct — 同じサイズで統一',
63
+ '<div className="flex gap-2">',
64
+ ' <Input placeholder="検索..." />',
65
+ ' <Button size="md">検索</Button>',
66
+ '</div>',
67
+ '',
68
+ '// ❌ Wrong — サイズ不一致で高さが揃わない',
69
+ '<div className="flex gap-2">',
70
+ ' <Input placeholder="検索..." />',
71
+ ' <Button size="sm">検索</Button>',
72
+ '</div>',
73
+ '```',
74
+ '',
75
+ 'Input / Select / Textarea のデフォルトサイズは md。横並びの Button も原則 md に合わせる。テーブル内アクションなど独立した Button は sm でよい。',
76
+ ]),
77
+ jsdocTargets: [
78
+ {
79
+ file: 'src/components/ui/input/index.tsx',
80
+ targetName: 'Input',
81
+ section: {
82
+ bullets: [
83
+ {
84
+ ja: 'Input の横にアイコンボタンを手動で配置しないでください。`isTrigger` / `triggerIcon` props を使用してください。',
85
+ en: 'Do not manually place an IconButton next to Input. Use `isTrigger` / `triggerIcon` props instead.',
86
+ },
87
+ {
88
+ ja: 'Input と横並びの Button は原則同じサイズにしてください。デフォルトの Input に対しては `Button size="md"` を使ってください。',
89
+ en: 'Keep Button size aligned when placing it next to Input. Use `Button size="md"` with the default Input size.',
90
+ },
91
+ ],
92
+ example: lines([
93
+ '// ✅ Correct',
94
+ '<>',
95
+ ' <Input',
96
+ ' isTrigger',
97
+ ' triggerIcon="search"',
98
+ ' triggerAriaLabel="検索"',
99
+ ' onIconButtonClick={() => {}}',
100
+ ' />',
101
+ ' <div className="flex gap-2">',
102
+ ' <Input placeholder="検索..." />',
103
+ ' <Button size="md">検索</Button>',
104
+ ' </div>',
105
+ '</>',
106
+ '',
107
+ '// ❌ Wrong - 手動配置',
108
+ '<div className="flex">',
109
+ ' <Input />',
110
+ ' <IconButton icon="search" aria-label="検索" />',
111
+ '</div>',
112
+ '',
113
+ '// ❌ Wrong - サイズ不一致',
114
+ '<div className="flex gap-2">',
115
+ ' <Input placeholder="検索..." />',
116
+ ' <Button size="sm">検索</Button>',
117
+ '</div>',
118
+ ]),
119
+ },
120
+ },
121
+ {
122
+ file: 'src/components/ui/select/index.tsx',
123
+ targetName: 'Select',
124
+ section: {
125
+ bullets: [
126
+ {
127
+ ja: '`SelectTrigger` と横並びの Button は原則同じサイズにしてください。デフォルトの `SelectTrigger` に対しては `Button size="md"` を使ってください。',
128
+ en: 'Keep Button size aligned when placing it next to `SelectTrigger`. Use `Button size="md"` with the default `SelectTrigger` size.',
129
+ },
130
+ ],
131
+ example: lines([
132
+ '// ✅ Correct',
133
+ '<div className="flex gap-2">',
134
+ ' <Select>',
135
+ ' <SelectTrigger size="md" className="w-60">',
136
+ ' <SelectValue placeholder="選択してください" />',
137
+ ' </SelectTrigger>',
138
+ ' </Select>',
139
+ ' <Button size="md">追加</Button>',
140
+ '</div>',
141
+ '',
142
+ '// ❌ Wrong - 高さが揃わないサイズ不一致',
143
+ '<div className="flex gap-2">',
144
+ ' <Select>',
145
+ ' <SelectTrigger size="md" className="w-60">',
146
+ ' <SelectValue placeholder="選択してください" />',
147
+ ' </SelectTrigger>',
148
+ ' </Select>',
149
+ ' <Button size="sm">追加</Button>',
150
+ '</div>',
151
+ ]),
152
+ },
153
+ },
154
+ ],
155
+ },
156
+ {
157
+ id: 'badge-tag',
158
+ featureSection: lines([
159
+ '### Badge と Tag を用途で使い分ける',
160
+ '',
161
+ '| コンポーネント | 用途 | 例 |',
162
+ '| --- | --- | --- |',
163
+ '| **Badge** | 特定の要素に**数値情報**を付与 | `<Badge>3</Badge>` |',
164
+ '| **Tag** | 情報の**ラベリング / ステータス付与** | `<Tag status="success">完了</Tag>` |',
165
+ '',
166
+ '```tsx',
167
+ '// ✅ Correct — ステータス表示には Tag を使う',
168
+ '<Tag variant="outline" status="warning">未紐付け</Tag>',
169
+ '',
170
+ '// ❌ Wrong — Badge をステータスラベルに使わない',
171
+ '<Badge>未紐付け</Badge>',
172
+ '```',
173
+ ]),
174
+ jsdocTargets: [
175
+ {
176
+ file: 'src/components/ui/badge/index.tsx',
177
+ targetName: 'Badge',
178
+ section: {
179
+ bullets: [
180
+ {
181
+ ja: 'ステータス表示や分類ラベルには `Badge` を使わず、`Tag` を使ってください。',
182
+ en: 'Do not use `Badge` for status display or category labels. Use `Tag` instead.',
183
+ },
184
+ ],
185
+ example: lines([
186
+ '// ✅ Correct',
187
+ '<>',
188
+ ' <Badge>3</Badge>',
189
+ ' <Tag status="warning" variant="outline">未紐付け</Tag>',
190
+ '</>',
191
+ '',
192
+ '// ❌ Wrong - ステータスラベルに Badge を使わない',
193
+ '<Badge>未紐付け</Badge>',
194
+ ]),
195
+ },
196
+ },
197
+ {
198
+ file: 'src/components/ui/tag/index.tsx',
199
+ targetName: 'Tag',
200
+ section: {
201
+ bullets: [
202
+ {
203
+ ja: '通知数や件数などの数値情報には `Tag` を使わず、`Badge` を使ってください。',
204
+ en: 'Do not use `Tag` for numeric information such as notification counts. Use `Badge` instead.',
205
+ },
206
+ ],
207
+ example: lines([
208
+ '// ✅ Correct',
209
+ '<>',
210
+ ' <Tag status="success">完了</Tag>',
211
+ ' <Badge>3</Badge>',
212
+ '</>',
213
+ '',
214
+ '// ❌ Wrong - 件数表示に Tag を使わない',
215
+ '<Tag status="info">3件</Tag>',
216
+ ]),
217
+ },
218
+ },
219
+ ],
220
+ },
221
+ {
222
+ id: 'shadcn-token',
223
+ check: {
224
+ description: 'shadcn/ui 既定 token を Sparkle Design 内へ持ち込まない',
225
+ recommendation:
226
+ 'text-muted-foreground / bg-background / border-border などは Sparkle Design token に置き換えてください。',
227
+ pattern: /\b(text-muted-foreground|bg-background|border-border)\b/g,
228
+ },
229
+ featureSection: lines([
230
+ '### shadcn/ui 由来の class / token をそのまま使わない',
231
+ '',
232
+ '```tsx',
233
+ '// ✅ Correct — Sparkle Design の typography / color token を使う',
234
+ '<CardDescription className="character-3-regular-pro text-text-low">',
235
+ ' 全 12 件',
236
+ '</CardDescription>',
237
+ '',
238
+ '// ❌ Wrong — shadcn/ui の既定 token や素の font utility を持ち込まない',
239
+ '<CardDescription className="text-sm text-muted-foreground">',
240
+ ' 全 12 件',
241
+ '</CardDescription>',
242
+ '<CardDescription className="font-medium text-slate-500">',
243
+ ' 全 12 件',
244
+ '</CardDescription>',
245
+ '```',
246
+ '',
247
+ 'shadcn/ui と混在するプロジェクトでも、Sparkle Design のコンポーネント内では `character-*` / `text-text-*` / Sparkle の color token を優先する。',
248
+ ]),
249
+ jsdocTargets: [],
250
+ },
251
+ {
252
+ id: 'button-icons',
253
+ featureSection: lines([
254
+ '### Button: prefixIcon / suffixIcon を使う',
255
+ '',
256
+ '```tsx',
257
+ '// ✅ Correct',
258
+ '<Button prefixIcon="check">確定</Button>',
259
+ '<Button suffixIcon="arrow_forward">次へ</Button>',
260
+ '',
261
+ '// ❌ Wrong - Icon を children に入れない(レイアウト崩れ・ローディング時に非表示にならない)',
262
+ '<Button><Icon icon="check" /> 確定</Button>',
263
+ '',
264
+ '// ❌ Wrong - アイコンのみのボタンは IconButton を使う',
265
+ '<Button><Icon icon="edit" /></Button>',
266
+ '// ✅ Correct',
267
+ '<IconButton icon="edit" aria-label="編集" />',
268
+ '```',
269
+ '',
270
+ '> Button は size に応じてアイコンサイズを自動設定する(sm→4, md→5, lg→6)。isLoading 時にアイコンを自動で非表示にする。',
271
+ ]),
272
+ jsdocTargets: [
273
+ {
274
+ file: 'src/components/ui/button/index.tsx',
275
+ targetName: 'Button',
276
+ section: {
277
+ bullets: [
278
+ {
279
+ ja: '`<Icon>` を children として渡さないでください。`prefixIcon` / `suffixIcon` props を使用してください。',
280
+ en: 'Do not pass `<Icon>` as children. Use `prefixIcon` / `suffixIcon` props instead.',
281
+ },
282
+ {
283
+ ja: 'アイコンのみのボタンには `IconButton` を使用してください。',
284
+ en: 'Use `IconButton` for icon-only buttons.',
285
+ },
286
+ ],
287
+ example: lines([
288
+ '// ✅ Correct',
289
+ '<Button prefixIcon="check">確定</Button>',
290
+ '',
291
+ '// ❌ Wrong - Icon を children に入れない',
292
+ '<Button><Icon icon="check" /> 確定</Button>',
293
+ '',
294
+ '// ❌ Wrong - アイコンのみは IconButton を使う',
295
+ '<Button><Icon icon="edit" /></Button>',
296
+ '// ✅ Correct',
297
+ '<IconButton icon="edit" aria-label="編集" />',
298
+ ]),
299
+ },
300
+ },
301
+ ],
302
+ },
303
+ {
304
+ id: 'card-control',
305
+ featureSection: lines([
306
+ '### CardHeader: CardControl を使う',
307
+ '',
308
+ '```tsx',
309
+ '// ✅ Correct',
310
+ '<CardHeader>',
311
+ ' <CardTitle>タイトル</CardTitle>',
312
+ ' <CardControl>',
313
+ ' <Button>アクション</Button>',
314
+ ' </CardControl>',
315
+ '</CardHeader>',
316
+ '',
317
+ '// ❌ Wrong - 手動 flex を使わない(CardHeader は内部で flex justify-between を適用済み)',
318
+ '<CardHeader>',
319
+ ' <div className="flex justify-between">',
320
+ ' <CardTitle>タイトル</CardTitle>',
321
+ ' <Button>アクション</Button>',
322
+ ' </div>',
323
+ '</CardHeader>',
324
+ '```',
325
+ ]),
326
+ jsdocTargets: [
327
+ {
328
+ file: 'src/components/ui/card/index.tsx',
329
+ targetName: 'CardHeader',
330
+ section: {
331
+ bullets: [
332
+ {
333
+ ja: 'CardHeader 内で手動の flex レイアウト(`<div className="flex justify-between">`)を使わないでください。CardHeader は内部で flex レイアウトを適用済みです。アクションボタンは `CardControl` で囲んでください。',
334
+ en: 'Do not use manual flex layout inside CardHeader. CardHeader already applies flex layout internally. Wrap action buttons with `CardControl`.',
335
+ },
336
+ {
337
+ ja: 'CardTitle の補足情報や件数は、`span` などを直書きせず `CardDescription` を使ってください。',
338
+ en: 'For supporting text or counts inside CardTitle, do not inline a `span`; use `CardDescription`.',
339
+ },
340
+ ],
341
+ example: lines([
342
+ '// ✅ Correct',
343
+ '<CardHeader>',
344
+ ' <CardTitle>',
345
+ ' タイトル',
346
+ ' <CardDescription className="character-3-regular-pro text-text-low">',
347
+ ' 全 12 件',
348
+ ' </CardDescription>',
349
+ ' </CardTitle>',
350
+ ' <CardControl>',
351
+ ' <Button>アクション</Button>',
352
+ ' </CardControl>',
353
+ '</CardHeader>',
354
+ '',
355
+ '// ❌ Wrong - 手動 flex を使わない',
356
+ '<CardHeader>',
357
+ ' <div className="flex justify-between">',
358
+ ' <CardTitle>タイトル</CardTitle>',
359
+ ' <Button>アクション</Button>',
360
+ ' </div>',
361
+ '</CardHeader>',
362
+ '',
363
+ '// ❌ Wrong - CardTitle 内に補足を入れない',
364
+ '<CardHeader>',
365
+ ' <CardTitle>',
366
+ ' タイトル',
367
+ ' <span className="text-sm text-neutral-500">全 12 件</span>',
368
+ ' </CardTitle>',
369
+ '</CardHeader>',
370
+ ]),
371
+ },
372
+ },
373
+ ],
374
+ },
375
+ {
376
+ id: 'icon-scale',
377
+ featureSection: lines([
378
+ '### Icon / Spinner: スケール値(1-12)を使う',
379
+ '',
380
+ '```tsx',
381
+ '// ✅ Correct - スケール値',
382
+ '<Icon icon="settings" size={6} /> // 24px 相当',
383
+ '',
384
+ '// ❌ Wrong - ピクセル値は受け付けない',
385
+ '<Icon icon="settings" size={24} />',
386
+ '```',
387
+ '',
388
+ '> スケール対応表は上記「Icon Size Scale」セクション参照。',
389
+ ]),
390
+ jsdocTargets: [
391
+ {
392
+ file: 'src/components/ui/icon/index.tsx',
393
+ targetName: 'Icon',
394
+ section: {
395
+ bullets: [
396
+ {
397
+ ja: '`size` にピクセル値(24, 32 など)を渡さないでください。スケール値(1-12)を使用してください。',
398
+ en: 'Do not pass pixel values to `size`. Use scale values (1-12) instead.',
399
+ },
400
+ ],
401
+ example: lines([
402
+ '// ✅ Correct - スケール値',
403
+ '<Icon icon="settings" size={6} /> // 24px 相当',
404
+ '',
405
+ '// ❌ Wrong - ピクセル値',
406
+ '<Icon icon="settings" size={24} />',
407
+ ]),
408
+ },
409
+ },
410
+ {
411
+ file: 'src/components/ui/spinner/index.tsx',
412
+ targetName: 'Spinner',
413
+ section: {
414
+ bullets: [
415
+ {
416
+ ja: '`size` にピクセル値(24, 32 など)を渡さないでください。スケール値(1-12)を使用してください。',
417
+ en: 'Do not pass pixel values to `size`. Use scale values (1-12) instead.',
418
+ },
419
+ ],
420
+ example: lines([
421
+ '// ✅ Correct - スケール値',
422
+ '<Spinner size={6} />',
423
+ '',
424
+ '// ❌ Wrong - ピクセル値',
425
+ '<Spinner size={24} />',
426
+ ]),
427
+ },
428
+ },
429
+ ],
430
+ },
431
+ {
432
+ id: 'dialog-button-wrap',
433
+ check: {
434
+ description: 'DialogCancel/DialogAction を Button で二重ラップしない',
435
+ recommendation:
436
+ 'DialogCancel と DialogAction は内部で Button を描画するので、children に Button を入れないでください。',
437
+ pattern: /<(DialogCancel|DialogAction)\b[^>]*>\s*<Button\b/g,
438
+ },
439
+ featureSection: lines([
440
+ '### DialogCancel / DialogAction: Button で二重ラップしない',
441
+ '',
442
+ '```tsx',
443
+ '// ✅ Correct - 文字列を渡すだけ(内部で Button を描画)',
444
+ '<DialogCancel>キャンセル</DialogCancel>',
445
+ '<DialogAction>確定</DialogAction>',
446
+ '',
447
+ '// ❌ Wrong - 二重ラップ',
448
+ '<DialogCancel><Button>キャンセル</Button></DialogCancel>',
449
+ '```',
450
+ ]),
451
+ jsdocTargets: [
452
+ {
453
+ file: 'src/components/ui/dialog/index.tsx',
454
+ targetName: 'DialogCancel',
455
+ section: {
456
+ bullets: [
457
+ {
458
+ ja: '`DialogCancel` の children に `<Button>` を渡さないでください。内部で Button を描画します。',
459
+ en: 'Do not pass `<Button>` as children. DialogCancel renders a Button internally.',
460
+ },
461
+ ],
462
+ example: lines([
463
+ '// ✅ Correct',
464
+ '<DialogCancel>キャンセル</DialogCancel>',
465
+ '',
466
+ '// ❌ Wrong - 二重ラップ',
467
+ '<DialogCancel><Button>キャンセル</Button></DialogCancel>',
468
+ ]),
469
+ },
470
+ },
471
+ {
472
+ file: 'src/components/ui/dialog/index.tsx',
473
+ targetName: 'DialogAction',
474
+ section: {
475
+ bullets: [
476
+ {
477
+ ja: '`DialogAction` の children に `<Button>` を渡さないでください。内部で Button を描画します。',
478
+ en: 'Do not pass `<Button>` as children. DialogAction renders a Button internally.',
479
+ },
480
+ ],
481
+ example: lines([
482
+ '// ✅ Correct',
483
+ '<DialogAction>確定</DialogAction>',
484
+ '',
485
+ '// ❌ Wrong - 二重ラップ',
486
+ '<DialogAction><Button>確定</Button></DialogAction>',
487
+ ]),
488
+ },
489
+ },
490
+ ],
491
+ },
492
+ {
493
+ id: 'dialog-form',
494
+ check: {
495
+ description: 'フォーム入力に Dialog を使わない',
496
+ recommendation: '確認ダイアログは Dialog、フォーム入力や詳細表示は Modal を使ってください。',
497
+ pattern:
498
+ /<Dialog(?:\s|>)[\s\S]*?<(Input|Select|Textarea|Checkbox|Radio|Switch|Form|FormField|FormItem)\b[\s\S]*?<\/Dialog>/g,
499
+ },
500
+ featureSection: lines([
501
+ '### Dialog / Modal: 用途を混ぜない',
502
+ '',
503
+ '| コンポーネント | ベース | 用途 | 例 |',
504
+ '| --- | --- | --- | --- |',
505
+ '| **Dialog** | `AlertDialog` | アクション確認 | 削除確認、再試行確認、保存前の警告 |',
506
+ '| **Modal** | `Dialog` | フォーム入力・情報表示 | 作成/編集フォーム、詳細表示 |',
507
+ '',
508
+ '```tsx',
509
+ '// ✅ Correct - フォーム入力には Modal を使う',
510
+ '<Modal>',
511
+ ' <ModalContent size="md">',
512
+ ' <ModalHeader>',
513
+ ' <ModalTitle>ユーザー作成</ModalTitle>',
514
+ ' <ModalClose />',
515
+ ' </ModalHeader>',
516
+ ' <ModalBody isSpace>',
517
+ ' <Input />',
518
+ ' <Select />',
519
+ ' </ModalBody>',
520
+ ' <ModalFooter>',
521
+ ' <Button variant="ghost">キャンセル</Button>',
522
+ ' <Button>作成</Button>',
523
+ ' </ModalFooter>',
524
+ ' </ModalContent>',
525
+ '</Modal>',
526
+ '',
527
+ '// ❌ Wrong - フォーム入力に Dialog を使わない',
528
+ '<Dialog>',
529
+ ' <DialogContent>',
530
+ ' <DialogHeader>',
531
+ ' <DialogTitle>ユーザー作成</DialogTitle>',
532
+ ' </DialogHeader>',
533
+ ' <Input />',
534
+ ' <Select />',
535
+ ' <DialogFooter>',
536
+ ' <DialogCancel>キャンセル</DialogCancel>',
537
+ ' <DialogAction>作成</DialogAction>',
538
+ ' </DialogFooter>',
539
+ ' </DialogContent>',
540
+ '</Dialog>',
541
+ '```',
542
+ ]),
543
+ jsdocTargets: [
544
+ {
545
+ file: 'src/components/ui/dialog/index.tsx',
546
+ targetName: 'Dialog',
547
+ section: {
548
+ bullets: [
549
+ {
550
+ ja: 'フォーム入力や詳細表示には使わず、削除確認や再試行確認などのアクション確認に使ってください。',
551
+ en: 'Do not use Dialog for forms or detail views. Use it for action confirmations such as delete and retry flows.',
552
+ },
553
+ ],
554
+ example: lines([
555
+ '// ✅ Correct',
556
+ '<Dialog>',
557
+ ' <DialogContent>',
558
+ ' <DialogHeader>',
559
+ ' <DialogTitle>削除確認</DialogTitle>',
560
+ ' <DialogDescription>本当に削除しますか?</DialogDescription>',
561
+ ' </DialogHeader>',
562
+ ' <DialogFooter>',
563
+ ' <DialogCancel>キャンセル</DialogCancel>',
564
+ ' <DialogAction>削除</DialogAction>',
565
+ ' </DialogFooter>',
566
+ ' </DialogContent>',
567
+ '</Dialog>',
568
+ '',
569
+ '// ❌ Wrong - フォーム入力に Dialog を使わない',
570
+ '<Dialog>',
571
+ ' <DialogContent>',
572
+ ' <Input />',
573
+ ' <Select />',
574
+ ' </DialogContent>',
575
+ '</Dialog>',
576
+ ]),
577
+ },
578
+ },
579
+ {
580
+ file: 'src/components/ui/modal/index.tsx',
581
+ targetName: 'Modal',
582
+ section: {
583
+ bullets: [
584
+ {
585
+ ja: '作成/編集フォームや詳細表示など、確認だけではない情報入力・閲覧に使ってください。',
586
+ en: 'Use Modal for forms and detail views such as create and edit flows, not for simple action confirmation.',
587
+ },
588
+ ],
589
+ example: lines([
590
+ '// ✅ Correct',
591
+ '<Modal>',
592
+ ' <ModalContent size="md">',
593
+ ' <ModalHeader>',
594
+ ' <ModalTitle>ユーザー作成</ModalTitle>',
595
+ ' <ModalClose />',
596
+ ' </ModalHeader>',
597
+ ' <ModalBody isSpace>',
598
+ ' <Input />',
599
+ ' <Select />',
600
+ ' </ModalBody>',
601
+ ' <ModalFooter>',
602
+ ' <Button>作成</Button>',
603
+ ' </ModalFooter>',
604
+ ' </ModalContent>',
605
+ '</Modal>',
606
+ ]),
607
+ },
608
+ },
609
+ ],
610
+ },
611
+ {
612
+ id: 'input-trigger',
613
+ featureSection: lines([
614
+ '### Input: 組み込みトリガーを使う',
615
+ '',
616
+ '```tsx',
617
+ '// ✅ Correct',
618
+ '<Input isTrigger triggerIcon="search" triggerAriaLabel="検索" onIconButtonClick={handleSearch} />',
619
+ '',
620
+ '// ❌ Wrong - 手動で IconButton を配置しない',
621
+ '<div className="flex">',
622
+ ' <Input />',
623
+ ' <IconButton icon="search" aria-label="検索" />',
624
+ '</div>',
625
+ '```',
626
+ ]),
627
+ jsdocTargets: [],
628
+ },
629
+ {
630
+ id: 'link-open-in-new',
631
+ featureSection: lines([
632
+ '### Link: isOpenInNew を使う',
633
+ '',
634
+ '```tsx',
635
+ '// ✅ Correct - 自動で open_in_new アイコンが表示される',
636
+ '<Link href="https://example.com" isOpenInNew>外部リンク</Link>',
637
+ '',
638
+ '// ❌ Wrong - 手動でアイコンを追加しない',
639
+ '<Link href="https://example.com">外部リンク <Icon icon="open_in_new" /></Link>',
640
+ '```',
641
+ ]),
642
+ jsdocTargets: [
643
+ {
644
+ file: 'src/components/ui/link/index.tsx',
645
+ targetName: 'Link',
646
+ section: {
647
+ bullets: [
648
+ {
649
+ ja: '外部リンクアイコンを手動で追加しないでください。`isOpenInNew` prop が自動で `open_in_new` アイコンを表示します。',
650
+ en: 'Do not manually add external link icons. The `isOpenInNew` prop automatically displays the `open_in_new` icon.',
651
+ },
652
+ ],
653
+ example: lines([
654
+ '// ✅ Correct',
655
+ '<Link href="https://example.com" isOpenInNew>外部リンク</Link>',
656
+ '',
657
+ '// ❌ Wrong - 手動アイコン',
658
+ '<Link href="https://example.com">外部リンク <Icon icon="open_in_new" /></Link>',
659
+ ]),
660
+ },
661
+ },
662
+ ],
663
+ },
664
+ ];
665
+
666
+ function getCheckRules() {
667
+ const order = ['dialog-form', 'dialog-button-wrap', 'shadcn-token'];
668
+
669
+ return ANTI_PATTERN_GROUPS.filter((group) => group.check)
670
+ .map((group) => ({
671
+ id: group.id,
672
+ ...group.check,
673
+ }))
674
+ .sort((left, right) => {
675
+ const leftIndex = order.indexOf(left.id);
676
+ const rightIndex = order.indexOf(right.id);
677
+ const normalizedLeftIndex = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;
678
+ const normalizedRightIndex = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;
679
+ return normalizedLeftIndex - normalizedRightIndex;
680
+ });
681
+ }
682
+
683
+ function getAllJSDocTargets() {
684
+ return ANTI_PATTERN_GROUPS.flatMap((group) => group.jsdocTargets);
685
+ }
686
+
687
+ function renderFeatureSections() {
688
+ return ANTI_PATTERN_GROUPS.map((group) => group.featureSection).join('\n\n');
689
+ }
690
+
691
+ export {
692
+ ANTI_PATTERN_GROUPS,
693
+ COMMON_INTRO,
694
+ getAllJSDocTargets,
695
+ getCheckRules,
696
+ renderFeatureSections,
697
+ renderJSDocSection,
698
+ };
package/lib/check.js ADDED
@@ -0,0 +1,104 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ import { getCheckRules } from './anti-pattern-rules.js';
5
+
6
+ const DEFAULT_TARGET = 'src';
7
+ const TEXT_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']);
8
+ const RULES = getCheckRules();
9
+
10
+ function collectFiles(targetPath, files, visited) {
11
+ if (!fs.existsSync(targetPath)) {
12
+ throw new Error(`Target path does not exist: ${targetPath}`);
13
+ }
14
+
15
+ const realPath = fs.realpathSync(targetPath);
16
+ if (visited.has(realPath)) {
17
+ return;
18
+ }
19
+ visited.add(realPath);
20
+
21
+ const stat = fs.statSync(realPath);
22
+
23
+ if (stat.isFile()) {
24
+ if (TEXT_EXTENSIONS.has(path.extname(realPath))) {
25
+ files.add(realPath);
26
+ }
27
+ return;
28
+ }
29
+
30
+ if (!stat.isDirectory()) {
31
+ return;
32
+ }
33
+
34
+ for (const entry of fs.readdirSync(realPath, { withFileTypes: true })) {
35
+ if (entry.name === 'node_modules' || entry.name === '.git') {
36
+ continue;
37
+ }
38
+
39
+ collectFiles(path.join(realPath, entry.name), files, visited);
40
+ }
41
+ }
42
+
43
+ function getLineNumber(content, index) {
44
+ return content.slice(0, index).split(/\r?\n/).length;
45
+ }
46
+
47
+ function formatMatch(match) {
48
+ return match[0].replace(/\s+/g, ' ').trim().slice(0, 120);
49
+ }
50
+
51
+ function collectFindings(filePath) {
52
+ const content = fs.readFileSync(filePath, 'utf8');
53
+ const findings = [];
54
+
55
+ for (const rule of RULES) {
56
+ for (const match of content.matchAll(rule.pattern)) {
57
+ findings.push({
58
+ filePath,
59
+ id: rule.id,
60
+ description: rule.description,
61
+ recommendation: rule.recommendation,
62
+ line: getLineNumber(content, match.index ?? 0),
63
+ snippet: formatMatch(match),
64
+ });
65
+ }
66
+ }
67
+
68
+ return findings;
69
+ }
70
+
71
+ export function checkProject(targets = [], options = {}) {
72
+ const resolvedTargets = targets.length > 0 ? targets : [DEFAULT_TARGET];
73
+ const files = new Set();
74
+ const visited = new Set();
75
+
76
+ for (const target of resolvedTargets) {
77
+ collectFiles(path.resolve(process.cwd(), target), files, visited);
78
+ }
79
+
80
+ const findings = [...files].flatMap(collectFindings);
81
+
82
+ if (findings.length === 0) {
83
+ console.log('sparkle-design-cli check: no findings');
84
+ return false;
85
+ }
86
+
87
+ console.log(`sparkle-design-cli check: ${findings.length} finding(s)\n`);
88
+
89
+ for (const finding of findings) {
90
+ const relativePath = path.relative(process.cwd(), finding.filePath);
91
+ console.log(`${relativePath}:${finding.line} [${finding.id}] ${finding.description}`);
92
+ console.log(` Recommendation: ${finding.recommendation}`);
93
+ console.log(` Snippet: ${finding.snippet}`);
94
+ console.log('');
95
+ }
96
+
97
+ if (options.strict) {
98
+ console.error('sparkle-design-cli check: failed because --strict was specified');
99
+ }
100
+
101
+ return true;
102
+ }
103
+
104
+ export { RULES, collectFindings };
package/lib/constants.js CHANGED
@@ -21,11 +21,12 @@ export const PATHS = {
21
21
  // 正規表現パターン
22
22
  export const REGEX = {
23
23
  // フォント関連
24
- FONT_IMPORT: /@import\s+url\([^)]+fonts\.googleapis\.com[^)]+\);?/g,
24
+ FONT_IMPORT:
25
+ /@import\s+(?:url\([^)]+fonts\.googleapis\.com[^)]+\)|['"][^"']*fonts\.googleapis\.com[^"']*['"]);?/g,
25
26
  FONT_IMPORT_WITH_COMMENT:
26
- /\/\*\s*フォントのインポート\s*\*\/\s*\n?|@import\s+url\([^)]+fonts\.googleapis\.com[^)]+\);?\s*\n?/g,
27
+ /\/\*\s*フォントのインポート\s*\*\/\s*\n?|@import\s+(?:url\([^)]+fonts\.googleapis\.com[^)]+\)|['"][^"']*fonts\.googleapis\.com[^"']*['"]);?\s*\n?/g,
27
28
  EXISTING_FONT_IMPORT_BLOCK:
28
- /\/\*\s*フォントのインポート[^*]*\*\/\s*\n?(?:@import\s+url\([^)]+fonts\.googleapis\.com[^)]+\);?\s*\n?)*\n?/g,
29
+ /\/\*\s*フォントのインポート[^*]*\*\/\s*\n?(?:@import\s+(?:url\([^)]+fonts\.googleapis\.com[^)]+\)|['"][^"']*fonts\.googleapis\.com[^"']*['"]);?\s*\n?)*\n?/g,
29
30
 
30
31
  // Sparkle Design関連
31
32
  SPARKLE_IMPORT:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-design-cli",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "description": "Sparkle Design CSS Generator - デザインシステムCSSを設定ファイルから生成するツール",
5
5
  "main": "lib/generate-css.js",
6
6
  "type": "module",
@@ -13,7 +13,8 @@
13
13
  "lint": "eslint bin/ lib/ test/",
14
14
  "lint:fix": "eslint bin/ lib/ test/ --fix",
15
15
  "format": "prettier --write bin/ lib/ test/ *.md *.json",
16
- "format:check": "prettier --check bin/ lib/ test/ *.md *.json"
16
+ "format:check": "prettier --check bin/ lib/ test/ *.md *.json",
17
+ "sync:anti-pattern-docs": "node scripts/sync-anti-pattern-docs.mjs"
17
18
  },
18
19
  "keywords": [
19
20
  "css",
@@ -1,7 +1,7 @@
1
1
  /* フォントのインポート */
2
- @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,0..200&display=block');
3
- @import url('https://fonts.googleapis.com/css2?family={{FONT_PRO_URL}}:wght@400;700&display=swap');
4
- @import url('https://fonts.googleapis.com/css2?family={{FONT_MONO_URL}}:wght@400;700&display=swap');
2
+ @import 'https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,0..200&display=block';
3
+ @import 'https://fonts.googleapis.com/css2?family={{FONT_PRO_URL}}:wght@400;700&display=swap';
4
+ @import 'https://fonts.googleapis.com/css2?family={{FONT_MONO_URL}}:wght@400;700&display=swap';
5
5
 
6
6
  /* Primitive Tokens */
7
7
  :root {
@@ -81,13 +81,14 @@
81
81
  0px 4px 6px -4px var(--color-black-alpha-100);
82
82
  }
83
83
 
84
- /* セマンティックトークン - CSS変数としてランタイムで利用可能にする */
84
+ /* ベーススタイル */
85
85
  @layer base {
86
86
  body {
87
87
  font-family: var(--font-family-pro);
88
88
  }
89
89
  }
90
90
 
91
+ /* セマンティックトークン - CSS変数としてランタイムで利用可能にする */
91
92
  :root {
92
93
  /* セマンティクスカラー定義 */
93
94
  --color-neutral-50: var(--color-gray-50);