sparkle-design-cli 2.0.7-beta.2 → 2.0.7-beta.5

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
@@ -15,7 +15,7 @@ setup は次を自動で行います:
15
15
  1. パッケージマネージャー(pnpm / npm / yarn / bun)を自動検出
16
16
  2. `sparkle-design` を dependencies、`tailwindcss` + `@tailwindcss/postcss` を devDependencies に追加
17
17
  3. 未作成の場合のみ初期ファイルを生成:
18
- - `sparkle.config.json`(デフォルトは blue / Inter / JetBrains Mono / md)
18
+ - `sparkle.config.json`(デフォルトは blue / BIZ UDPGothic / BIZ UDGothic / md — sparkle-design 本体のデフォルトに合わせて統一)
19
19
  - `postcss.config.mjs`
20
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 ブロックを追加
@@ -35,10 +35,10 @@ npm install -g sparkle-design-cli
35
35
 
36
36
  npm の dist-tag でチャネルを分離しています。通常は latest を使い、品質保証中の変更を試したい場合のみ beta を指定してください。
37
37
 
38
- | チャネル | dist-tag | 用途 | 指定方法 |
39
- |---------|---------|------|---------|
40
- | 安定版 | `latest` | 本番運用向け。`npx --yes sparkle-design-cli` は常にこれを取得 | (デフォルト) |
41
- | Beta | `beta` | 品質保証中の検証用 | `npx --yes sparkle-design-cli@beta` |
38
+ | チャネル | dist-tag | 用途 | 指定方法 |
39
+ | -------- | -------- | ------------------------------------------------------------- | ----------------------------------- |
40
+ | 安定版 | `latest` | 本番運用向け。`npx --yes sparkle-design-cli` は常にこれを取得 | (デフォルト) |
41
+ | Beta | `beta` | 品質保証中の検証用 | `npx --yes sparkle-design-cli@beta` |
42
42
 
43
43
  ```bash
44
44
  # beta で setup を試す
@@ -103,6 +103,12 @@ sparkle-design-cli generate --output ./styles/design.css
103
103
 
104
104
  # 設定ファイルと出力先を両方指定
105
105
  sparkle-design-cli generate -c ./config/custom.json -o ./dist/styles.css
106
+
107
+ # Tailwind エントリ CSS を明示指定
108
+ sparkle-design-cli generate --globals-path src/styles/app.css
109
+
110
+ # CI 向け: @source 注入や Tailwind import 欠落などの失敗を exit 1 にする
111
+ sparkle-design-cli generate --strict
106
112
  ```
107
113
 
108
114
  #### generate オプション一覧
@@ -110,6 +116,12 @@ sparkle-design-cli generate -c ./config/custom.json -o ./dist/styles.css
110
116
  - `-h, --help`: ヘルプメッセージを表示
111
117
  - `-c, --config <パス>`: 設定ファイルのパス(デフォルト: `./sparkle.config.json`)
112
118
  - `-o, --output <パス>`: 出力ファイルのパス(デフォルト: `./src/app/sparkle-design.css`)
119
+ - `--globals-path <パス>`: Tailwind エントリポイント CSS のパス(デフォルト: 自動検出)。
120
+ **指定したパスが存在しない場合は `--strict` の有無に関わらず常に exit 1**(`sparkle.config.json` の `extend.globals-path` も同様)
121
+ - `--strict`: 以下を warn ではなく **exit 1** に昇格させる(CI 向け。既定は warn + 継続で後方互換を維持):
122
+ - デザインシステムパッケージが `package.json` に入っているのに Tailwind エントリ CSS が見つからない
123
+ - エントリ CSS はあるが `@import "tailwindcss";` が書かれていない(警告メッセージに追記すべき行まで actionable に表示)
124
+ - エントリ CSS への書き込みに失敗した
113
125
 
114
126
  ### check: アンチパターン検査
115
127
 
@@ -142,24 +154,15 @@ sparkle-design-cli check --help
142
154
  - children なしの Button に prefixIcon / suffixIcon を使わない
143
155
  - Material Symbols を className 直書きで使わない
144
156
  - shadcn/ui 既定 token を Sparkle Design 内へ持ち込まない
145
- - Tailwind デフォルト typography(text-sm, font-medium 等)を使わない
146
- - CardTitle に typography を上書きしない
157
+ - Tailwind デフォルト typography を Sparkle Design コンポーネント内で使わない
158
+ - CardTitle に typography 系クラスを付与しない
147
159
  - CardControl に Button / IconButton 以外を入れない
148
- - CardHeader / CardContent の padding を上書きしない
149
- - asChild と prefixIcon / suffixIcon / isLoading を併用しない
150
- - disabled を isDisabled の代わりに使わない
151
- - Button の prefixIcon に JSX を渡さない
160
+ - Card 系コンポーネントのデフォルト padding を安易に上書きしない
161
+ - asChild 使用時に prefixIcon / suffixIcon / isLoading を使わない
162
+ - Sparkle Design コンポーネントでは isDisabled を使う
163
+ - Button の prefixIcon / suffixIcon に JSX を渡さない
152
164
  - Icon の children にテキストを渡さない
153
- - クリック可能な Card を `<button>` / `<a>` / `role="button"` でラップせず、`ClickableCard` を使う
154
-
155
- #### Manual Review Reminders
156
-
157
- `--format json` では、機械検出できない観点も `manualReviewReminders` として返します。現在は次を含みます。
158
-
159
- - Badge と Tag の意味的な使い分け
160
- - CardDescription の typography / color token 明示
161
- - Dialog と Modal の UX 上の使い分け
162
- - 手書き span が Tag で置き換え可能か
165
+ - クリック可能な Card は ClickableCard を使う(button / a / role="button" でラップしない)
163
166
 
164
167
  #### 導入先での推奨設定
165
168
 
@@ -198,6 +201,7 @@ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaf
198
201
  ```
199
202
 
200
203
  既存ファイルは上書きされません:
204
+
201
205
  - `sparkle.config.json` / `postcss.config.*` / Tailwind エントリ CSS が存在する場合はそのまま保持
202
206
  - 既に依存関係に入っているパッケージはインストールをスキップ
203
207
  - `package.json` の既存の `lint:sparkle*` 独自 script は保持(`--force-script-update` で上書き)
@@ -205,6 +209,7 @@ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaf
205
209
  `--target` を省略した場合、lint 対象は `src`, `src/components`, `src/features`, `app`, `components` の順で自動検出されます。
206
210
 
207
211
  利用できる assistant:
212
+
208
213
  - `claude`: `CLAUDE.md`
209
214
  - `codex`: `AGENTS.md`
210
215
  - `cursor`: `.cursor/rules/sparkle-design-guard.mdc`
@@ -220,6 +225,9 @@ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaf
220
225
  - `--skip-generate`: `generate` 実行をスキップ
221
226
  - `--dry-run`: ファイルを変更せず結果だけ表示
222
227
  - `--force-script-update`: 既存の `lint:sparkle` 系 script も上書き
228
+ - `--strict`: `generate` ステージの失敗を exit 1 に昇格(CI 向け)。
229
+ 現状は `generate` の失敗のみ対象(install / scaffold / guard の失敗は従来どおり warn)。
230
+ `--skip-generate` / `--dry-run` と併用した場合は strict チェック対象が無くなるため警告を出します。
223
231
 
224
232
  `setup` は通常実行時も JSON サマリーを stdout に表示します。`--dry-run` を付けると、その JSON を表示したままファイル変更だけを抑止します。
225
233
 
@@ -259,9 +267,7 @@ Figma プラグインが出力する基本4項目に加えて、プロジェク
259
267
  { "family": "Montserrat", "weights": [500, 600, 700] },
260
268
  { "family": "Noto Sans JP", "weights": [400, 500, 600, 700] }
261
269
  ],
262
- "mono": [
263
- { "family": "Roboto Mono", "weights": [400, 700] }
264
- ]
270
+ "mono": [{ "family": "Roboto Mono", "weights": [400, 700] }]
265
271
  },
266
272
  "source-packages": ["@goodpatch/sparkle-design-internal"],
267
273
  "custom-css": "./src/app/custom-tokens.css"
@@ -971,21 +971,35 @@ const ANTI_PATTERN_GROUPS = [
971
971
  description: 'CardControl に Button / IconButton 以外を入れない',
972
972
  recommendation:
973
973
  'CardControl はアクションボタン用です。ステータス表示には CardDescription を使ってください。',
974
- // 以前の regex は `[\s\S]*?` が `</CardControl>` を越えて貪欲に探索してしまい、
975
- // 隣接する <CardContent> 等を非 Button 子要素として誤検知していた。
976
- // 内部コンテンツが `</CardControl>` を越えないよう lazy 側に停止条件を入れ、
977
- // 開始タグの直後から終了タグ直前までの範囲だけを検査する。
978
- // 加えて (1) 開始タグ自体が自己閉じ `/>` で終わっていたら子要素は存在しない
979
- // ので検査対象外とする(`[^>/]*[^>/]?`)、(2) ネストした `<CardControl>` も
980
- // Button/IconButton 扱いのスキップリストに加えて誤発報を防ぐ。
981
- // en: The previous pattern let `[\s\S]*?` leak past `</CardControl>`,
982
- // flagging adjacent siblings (e.g. `<CardContent>`) as non-Button children.
983
- // We now (a) constrain the lazy body so it cannot cross the closing tag,
984
- // (b) skip self-closing tags `<CardControl />` entirely, and
985
- // (c) treat a nested `<CardControl>` as an allowed child so we don't
986
- // report the inner wrapper of a pathological nested structure.
987
- pattern:
988
- /<CardControl\b(?:[^>]*[^>/])?>(?:(?!<\/CardControl\b)[\s\S])*?<(?!Button\b|IconButton\b|CardControl\b|\/CardControl\b)[A-Za-z][\w.]*/g,
974
+ // 2-pass matcher。1 本の regex に入れ子 negative lookahead を押し込む代わりに、
975
+ // まず <CardControl>…</CardControl> ブロックを抽出し、次にブロック内部だけを
976
+ // スキャンして Button / IconButton / 入れ子 CardControl 以外の開始タグを見つける。
977
+ // 1. 入れ子 negative lookahead が無くなり読みやすい
978
+ // 2. 自己閉じ `<CardControl />` は 1st pass でそもそも拾わない(special case 不要)
979
+ // 3. O(総コンテンツ長) で評価が済むため、大規模 JSX でも backtracking で
980
+ // 処理時間が跳ねない
981
+ // en: Two-pass matcher. First pass extracts `<CardControl>…</CardControl>`
982
+ // blocks (self-closing variants are skipped because the opening-tag regex
983
+ // requires the previous char not to be `/`). Second pass scans the inner
984
+ // content for any element that is not Button / IconButton / nested
985
+ // CardControl. This replaces the nested tempered-lazy regex with a shape
986
+ // that is easier to reason about and stays linear in total content length.
987
+ match: (content) => {
988
+ const BLOCK_PATTERN = /<CardControl\b[^>]*(?<!\/)>([\s\S]*?)<\/CardControl\b[^>]*>/g;
989
+ const NON_BUTTON_CHILD = /<(?!Button\b|IconButton\b|CardControl\b)[A-Za-z][\w.]*/g;
990
+ const results = [];
991
+ for (const blockMatch of content.matchAll(BLOCK_PATTERN)) {
992
+ const openingEnd = blockMatch.index + blockMatch[0].indexOf('>') + 1;
993
+ const inner = blockMatch[1];
994
+ for (const childMatch of inner.matchAll(NON_BUTTON_CHILD)) {
995
+ results.push({
996
+ index: openingEnd + childMatch.index,
997
+ text: childMatch[0],
998
+ });
999
+ }
1000
+ }
1001
+ return results;
1002
+ },
989
1003
  },
990
1004
  featureSection: lines([
991
1005
  '### CardControl にはアクションボタンのみを入れる',
package/lib/check.js CHANGED
@@ -12,7 +12,12 @@ const BASE_MANUAL_REVIEW_REMINDERS = getManualReviewReminders();
12
12
 
13
13
  const SPARKLE_HEAD_JSX_PATTERN = /<SparkleHead\s*\/?\s*>/;
14
14
  const CSP_PATTERN = /content-security-policy|contentSecurityPolicy|Content-Security-Policy/i;
15
- const NEXT_CONFIG_CANDIDATES = ['next.config.js', 'next.config.ts', 'next.config.mjs', 'next.config.cjs'];
15
+ const NEXT_CONFIG_CANDIDATES = [
16
+ 'next.config.js',
17
+ 'next.config.ts',
18
+ 'next.config.mjs',
19
+ 'next.config.cjs',
20
+ ];
16
21
 
17
22
  function toRelativeReportPath(filePath) {
18
23
  return path.relative(process.cwd(), filePath).split(path.sep).join('/');
@@ -65,10 +70,30 @@ function formatMatch(match) {
65
70
  return match[0].replace(/\s+/g, ' ').trim().slice(0, 120);
66
71
  }
67
72
 
73
+ function formatSnippet(text) {
74
+ return text.replace(/\s+/g, ' ').trim().slice(0, 120);
75
+ }
76
+
68
77
  function collectFindings(filePath, content) {
69
78
  const findings = [];
70
79
 
71
80
  for (const rule of RULES) {
81
+ if (typeof rule.match === 'function') {
82
+ // rule.match(content) -> Array<{ index: number, text: string }>
83
+ // 複雑な照合が必要なルール向けの opt-in API。2-pass 方式などで regex 単体の
84
+ // backtracking リスクを回避したいときに使う。index は content 内の絶対オフセット。
85
+ for (const hit of rule.match(content)) {
86
+ findings.push({
87
+ filePath,
88
+ id: rule.id,
89
+ description: rule.description,
90
+ recommendation: rule.recommendation,
91
+ line: getLineNumber(content, hit.index ?? 0),
92
+ snippet: formatSnippet(hit.text ?? ''),
93
+ });
94
+ }
95
+ continue;
96
+ }
72
97
  for (const match of content.matchAll(rule.pattern)) {
73
98
  findings.push({
74
99
  filePath,
@@ -96,8 +121,10 @@ function collectFontImportFindings(cssFiles) {
96
121
  findings.push({
97
122
  filePath,
98
123
  id: 'font-import-in-css',
99
- description: 'CSS にフォント @import が残っています。SparkleHead コンポーネントに移行してください。',
100
- recommendation: 'sparkle-design-cli generate を再実行すると、フォント読み込みが SparkleHead.tsx に移行され、globals.css から @import が除去されます。',
124
+ description:
125
+ 'CSS にフォント @import が残っています。SparkleHead コンポーネントに移行してください。',
126
+ recommendation:
127
+ 'sparkle-design-cli generate を再実行すると、フォント読み込みが SparkleHead.tsx に移行され、globals.css から @import が除去されます。',
101
128
  line: getLineNumber(content, match.index ?? 0),
102
129
  snippet: match[0].slice(0, 120),
103
130
  });
@@ -140,12 +167,20 @@ function collectNextjsCspFindings(cwd) {
140
167
  if (!CSP_PATTERN.test(content)) continue;
141
168
 
142
169
  const missingDomains = [
143
- { domain: 'fonts.googleapis.com', directive: 'style-src', pattern: new RegExp(FONT_DOMAINS.GOOGLEAPIS.replace('https://', '')) },
144
- { domain: 'fonts.gstatic.com', directive: 'font-src', pattern: new RegExp(FONT_DOMAINS.GSTATIC.replace('https://', '')) },
170
+ {
171
+ domain: 'fonts.googleapis.com',
172
+ directive: 'style-src',
173
+ pattern: new RegExp(FONT_DOMAINS.GOOGLEAPIS.replace('https://', '')),
174
+ },
175
+ {
176
+ domain: 'fonts.gstatic.com',
177
+ directive: 'font-src',
178
+ pattern: new RegExp(FONT_DOMAINS.GSTATIC.replace('https://', '')),
179
+ },
145
180
  ].filter(({ pattern }) => !pattern.test(content));
146
181
 
147
182
  if (missingDomains.length > 0) {
148
- const missing = missingDomains.map(d => `${d.domain} (${d.directive})`).join(', ');
183
+ const missing = missingDomains.map((d) => `${d.domain} (${d.directive})`).join(', ');
149
184
  findings.push({
150
185
  filePath: configPath,
151
186
  id: 'csp-font-block',
@@ -206,7 +241,8 @@ function createCheckReport(targets = []) {
206
241
  if (!hasSparkleHeadUsage(fileContents)) {
207
242
  manualReviewReminders.push({
208
243
  id: 'sparkle-head-missing',
209
- message: 'SparkleHead がソースコード内に見つかりませんでした。ルートレイアウトの <head> 内に <SparkleHead /> を配置してフォントの早期読み込みを有効にしてください。',
244
+ message:
245
+ 'SparkleHead がソースコード内に見つかりませんでした。ルートレイアウトの <head> 内に <SparkleHead /> を配置してフォントの早期読み込みを有効にしてください。',
210
246
  });
211
247
  }
212
248
 
@@ -277,4 +313,9 @@ export function checkProject(targets = [], options = {}) {
277
313
  return report.findings.length > 0;
278
314
  }
279
315
 
280
- export { RULES, collectFindings, createCheckReport, BASE_MANUAL_REVIEW_REMINDERS as MANUAL_REVIEW_REMINDERS };
316
+ export {
317
+ RULES,
318
+ collectFindings,
319
+ createCheckReport,
320
+ BASE_MANUAL_REVIEW_REMINDERS as MANUAL_REVIEW_REMINDERS,
321
+ };
package/lib/setup.js CHANGED
@@ -183,13 +183,11 @@ function buildInstructionBlock(target, assistant) {
183
183
  BLOCK_START,
184
184
  heading,
185
185
  '',
186
- '- **必ず読む**: Sparkle Design のコンポーネントを使う前に、インストール済みパッケージの型定義 `node_modules/<パッケージ名>/dist/components/ui/<コンポーネント名>/index.d.ts` を必ず読んでください。Prop 仕様・使用例・アンチパターンは JSDoc に書かれている(✅ / ❌ 例込み)ので、これが Source of Truth です。`node_modules/sparkle-design/dist/` や `node_modules/@goodpatch/sparkle-design-internal/dist/` を対象に、`index.d.ts` の JSDoc まで読み切ること。',
186
+ '- **Scope**: Sparkle Design is a UI component library. For capability areas it does not cover (charts, data visualization, maps, rich text editors, animation libraries, etc.), **feel free to adopt other libraries** (e.g. Recharts, D3, Chart.js) — do not try to solve everything inside Sparkle Design. Pass Sparkle Design CSS tokens (`--color-primary-*` etc.) into those libraries to keep visuals consistent.',
187
187
  "- **Required reading**: Before using any Sparkle Design component, read the installed package's type definitions at `node_modules/<package>/dist/components/ui/<component>/index.d.ts` — prop specs, usage, and anti-patterns (with ✅ / ❌ examples) live in the JSDoc and are the source of truth. Target the installed package(s), e.g. `sparkle-design` and/or `@goodpatch/sparkle-design-internal`, and read the full JSDoc, not just the type signature.",
188
- '- **必ず実行**: UI コンポーネントを作成・変更した後は、終了前に `lint:sparkle` を実行してください。Sparkle Design のアンチパターンを検出します。',
189
188
  '- **Required**: After creating or modifying any UI component, run `lint:sparkle` before finishing. This catches Sparkle Design anti-patterns.',
190
189
  `- For AI review, prefer \`lint:sparkle:json\` or run \`npx --yes sparkle-design-cli check ${target} --format json\` directly.`,
191
- '- JSON 出力の `findings` に加えて、**`manualReviewReminders` の内容も必ず 1 項目ずつ確認**してください。機械検出できない Badge/Tag の使い分けなどが含まれます。',
192
- '- You must inspect every entry in both `findings` and `manualReviewReminders` — the reminders flag judgment calls (Badge vs Tag etc.) that the linter cannot detect.',
190
+ '- You must inspect every entry in both `findings` and `manualReviewReminders` — the reminders flag judgment calls (Badge vs Tag, etc.) that the linter cannot detect.',
193
191
  BLOCK_END,
194
192
  ].join('\n');
195
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-design-cli",
3
- "version": "2.0.7-beta.2",
3
+ "version": "2.0.7-beta.5",
4
4
  "description": "Sparkle Design CSS Generator - デザインシステムCSSを設定ファイルから生成するツール",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",