sparkle-design-cli 2.0.4 → 2.0.7-beta.0

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
@@ -31,6 +31,25 @@ npm install -g sparkle-design-cli
31
31
 
32
32
  > `npx --yes sparkle-design-cli ...` で都度実行する運用を推奨します(常に最新版が利用されます)。
33
33
 
34
+ ### リリースチャネル
35
+
36
+ npm の dist-tag でチャネルを分離しています。通常は latest を使い、品質保証中の変更を試したい場合のみ beta を指定してください。
37
+
38
+ | チャネル | dist-tag | 用途 | 指定方法 |
39
+ |---------|---------|------|---------|
40
+ | 安定版 | `latest` | 本番運用向け。`npx --yes sparkle-design-cli` は常にこれを取得 | (デフォルト) |
41
+ | Beta | `beta` | 品質保証中の検証用 | `npx --yes sparkle-design-cli@beta` |
42
+
43
+ ```bash
44
+ # beta で setup を試す
45
+ npx --yes sparkle-design-cli@beta setup --assistant claude
46
+
47
+ # beta で check を試す
48
+ npx --yes sparkle-design-cli@beta check src --strict
49
+ ```
50
+
51
+ Beta バージョンは `X.Y.Z-beta.N` 形式で publish されます。GA(正式版)へ昇格するときは、beta の -beta.N を外した `X.Y.Z` を改めて publish します。
52
+
34
53
  ## 使用方法
35
54
 
36
55
  ### サブコマンド
@@ -362,6 +381,39 @@ sparkle-design-cli generate --help
362
381
  sparkle-design-cli check src --strict
363
382
  ```
364
383
 
384
+ ### リリース手順(メンテナ向け)
385
+
386
+ npm publish は GitHub Actions (`Publish to npm`) 経由で行います。ローカルから `npm publish` しないでください。
387
+
388
+ #### 安定版(latest)
389
+
390
+ 1. `main` ブランチに変更をマージ
391
+ 2. `package.json` の `version` を SemVer で更新(例: `2.0.6` → `2.1.0`)し、`CHANGELOG.md` に該当セクションを追加
392
+ 3. PR をマージ後、Actions の **Publish to npm** を `channel: auto`(または `latest`)で手動実行
393
+ 4. workflow は lint / test を流したうえで `npm publish`(dist-tag = latest)を実行
394
+
395
+ #### Beta(beta)
396
+
397
+ 品質保証中の変更を先行公開したいときに使います。latest には影響しません。
398
+
399
+ 1. `package.json` の `version` を `X.Y.Z-beta.N` 形式に更新(例: `2.1.0-beta.0`、次の beta は `2.1.0-beta.1`)
400
+ 2. `CHANGELOG.md` に `[X.Y.Z-beta.N]` セクションを追加
401
+ 3. PR をマージ後、Actions の **Publish to npm** を `channel: auto`(または `beta`)で手動実行
402
+ 4. workflow が version を判定して `npm publish --tag beta` を実行
403
+ 5. 利用者側は `npx --yes sparkle-design-cli@beta ...` で検証
404
+
405
+ #### Beta から GA(latest)へ昇格
406
+
407
+ 同じ成果物を latest にする場合は、新しい安定版 `X.Y.Z` を publish するか、既存 beta バージョンに latest タグを付け替えます。
408
+
409
+ ```bash
410
+ # 選択肢 A: 新たに X.Y.Z を publish する(推奨)
411
+ # version を X.Y.Z に更新 → workflow を latest で実行
412
+
413
+ # 選択肢 B: 既存の X.Y.Z-beta.N に latest タグを付け替える
414
+ npm dist-tag add sparkle-design-cli@X.Y.Z-beta.N latest
415
+ ```
416
+
365
417
  ## ライセンス
366
418
 
367
419
  MIT License - 詳細は [LICENSE](LICENSE) ファイルを参照してください。
@@ -5,7 +5,7 @@
5
5
 
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
- import { REGEX, COMMENTS, IMPORTS, MESSAGES, FONT_DEFAULTS } from './constants.js';
8
+ import { REGEX, COMMENTS, IMPORTS, MESSAGES } from './constants.js';
9
9
 
10
10
  /**
11
11
  * sparkle-design.css からフォントの@import文を抽出する
@@ -26,15 +26,6 @@ export function removeFontImportsFromCSS(cssContent) {
26
26
  return cssContent.replace(REGEX.FONT_IMPORT_WITH_COMMENT, '');
27
27
  }
28
28
 
29
- /**
30
- * フォントimportブロックを生成する
31
- * @param {Array<string>} fontImports フォントimport文の配列
32
- * @returns {string} フォントimportブロック
33
- */
34
- function createFontImportBlock(fontImports) {
35
- return [COMMENTS.FONT_IMPORT, ...fontImports, ''].join('\n');
36
- }
37
-
38
29
  /**
39
30
  * globals.css から node_modules への相対パスを計算する
40
31
  * @param {string} globalsPath globals.css の絶対パス
@@ -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 sourcePackages = 'source-packages' in config ? (config['source-packages'] || []) : null;
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,10 +5,16 @@ import { generateCSS } from './generate-css.js';
5
5
 
6
6
  // デフォルトの sparkle.config.json テンプレート
7
7
  // en: Default sparkle.config.json template
8
+ // sparkle-design 本体の既定値(BIZ UDPGothic / BIZ UDGothic)に揃えておくことで、
9
+ // README やドキュメントサイトと見た目が一致する状態で初回セットアップが完了する。
10
+ // @source ディレクティブの生成は generate 側が package.json から既知のデザインシステム
11
+ // パッケージ(sparkle-design / @goodpatch/sparkle-design-internal 等)を自動検出して
12
+ // 行うため、ここで extend.source-packages を指定する必要はない。独自パッケージを
13
+ // 追加する場合のみユーザーが extend.source-packages に追記する。
8
14
  const DEFAULT_SPARKLE_CONFIG = {
9
15
  primary: 'blue',
10
- 'font-pro': 'Inter',
11
- 'font-mono': 'JetBrains Mono',
16
+ 'font-pro': 'BIZ UDPGothic',
17
+ 'font-mono': 'BIZ UDGothic',
12
18
  radius: 'md',
13
19
  };
14
20
 
@@ -70,7 +76,6 @@ function buildCheckScript(target, mode) {
70
76
  return `npx --yes sparkle-design-cli check ${target} ${trailingArgs}`;
71
77
  }
72
78
 
73
-
74
79
  function ensureDir(filePath) {
75
80
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
76
81
  }
@@ -187,9 +192,11 @@ function buildInstructionBlock(target, assistant) {
187
192
  BLOCK_START,
188
193
  heading,
189
194
  '',
190
- '- Run `lint:sparkle` when Sparkle Design changes are involved.',
195
+ '- **必ず実行**: UI コンポーネントを作成・変更した後は、終了前に `lint:sparkle` を実行してください。Sparkle Design のアンチパターンを検出します。',
196
+ '- **Required**: After creating or modifying any UI component, run `lint:sparkle` before finishing. This catches Sparkle Design anti-patterns.',
191
197
  `- For AI review, prefer \`lint:sparkle:json\` or run \`npx --yes sparkle-design-cli check ${target} --format json\` directly.`,
192
- '- Always inspect both `findings` and `manualReviewReminders` from the JSON output.',
198
+ '- JSON 出力の `findings` に加えて、**`manualReviewReminders` の内容も必ず 1 項目ずつ確認**してください。機械検出できない Badge/Tag の使い分けなどが含まれます。',
199
+ '- 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
200
  '- If semantic guidance is still needed, consult Sparkle Design docs and JSDoc examples.',
194
201
  BLOCK_END,
195
202
  ].join('\n');
@@ -222,7 +229,12 @@ function resolveScriptUpdate(existingValue, nextValue, mode, force) {
222
229
  }
223
230
 
224
231
  if (force || isManagedSparkleScript(existingValue, mode)) {
225
- return { value: nextValue, changed: true, conflict: false, reason: force ? 'forced' : 'updated' };
232
+ return {
233
+ value: nextValue,
234
+ changed: true,
235
+ conflict: false,
236
+ reason: force ? 'forced' : 'updated',
237
+ };
226
238
  }
227
239
 
228
240
  return { value: existingValue, changed: false, conflict: true, reason: 'preserved-custom' };
@@ -276,7 +288,8 @@ const PM_COMMANDS = {
276
288
  */
277
289
  function detectPackageManager(cwd) {
278
290
  if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
279
- if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock'))) return 'bun';
291
+ if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock')))
292
+ return 'bun';
280
293
  if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn';
281
294
  return 'npm';
282
295
  }
@@ -285,7 +298,12 @@ function detectPackageManager(cwd) {
285
298
  * package.json に未登録のパッケージだけをインストールする
286
299
  * en: Install only packages not yet listed in package.json
287
300
  */
288
- function ensurePackagesInstalled(cwd, packageManager, candidates, { dev = false, dryRun = false } = {}) {
301
+ function ensurePackagesInstalled(
302
+ cwd,
303
+ packageManager,
304
+ candidates,
305
+ { dev = false, dryRun = false } = {}
306
+ ) {
289
307
  const packageJson = readJson(path.join(cwd, 'package.json'));
290
308
  const deps = { ...(packageJson.dependencies ?? {}), ...(packageJson.devDependencies ?? {}) };
291
309
  const missing = candidates.filter((pkg) => !Object.prototype.hasOwnProperty.call(deps, pkg));
@@ -352,7 +370,12 @@ function buildScaffoldTargets(cwd) {
352
370
  },
353
371
  {
354
372
  name: 'postcssConfig',
355
- candidates: ['postcss.config.mjs', 'postcss.config.js', 'postcss.config.cjs', 'postcss.config.ts'],
373
+ candidates: [
374
+ 'postcss.config.mjs',
375
+ 'postcss.config.js',
376
+ 'postcss.config.cjs',
377
+ 'postcss.config.ts',
378
+ ],
356
379
  target: 'postcss.config.mjs',
357
380
  write: (abs) => fs.writeFileSync(abs, INITIAL_POSTCSS_CONFIG, 'utf8'),
358
381
  },
@@ -396,7 +419,10 @@ function runInstall(cwd, packageManager, { skipInstall, dryRun }) {
396
419
  return {
397
420
  skipped: false,
398
421
  sparkle: ensurePackagesInstalled(cwd, packageManager, SPARKLE_PACKAGES, { dryRun }),
399
- tailwind: ensurePackagesInstalled(cwd, packageManager, TAILWIND_PACKAGES, { dev: true, dryRun }),
422
+ tailwind: ensurePackagesInstalled(cwd, packageManager, TAILWIND_PACKAGES, {
423
+ dev: true,
424
+ dryRun,
425
+ }),
400
426
  };
401
427
  }
402
428
 
@@ -411,7 +437,10 @@ function runAssistantGuard(cwd, packageJsonPath, options, assistantConfig) {
411
437
  ? normalizePath(cwd, options.target, 'Target path')
412
438
  : normalizePath(cwd, detectTarget(cwd), 'Target path');
413
439
  const instructionPath = options.instructionsPath
414
- ? path.resolve(cwd, normalizePath(cwd, options.instructionsPath, 'Instructions path', { checkParentOnly: true }))
440
+ ? path.resolve(
441
+ cwd,
442
+ normalizePath(cwd, options.instructionsPath, 'Instructions path', { checkParentOnly: true })
443
+ )
415
444
  : path.resolve(cwd, assistantConfig.path);
416
445
 
417
446
  const packageResult = updatePackageJson(packageJsonPath, target, force);
@@ -461,9 +490,17 @@ export function setupAssistant(options = {}) {
461
490
  const dryRun = Boolean(options.dryRun);
462
491
  const packageManager = detectPackageManager(cwd);
463
492
 
464
- const install = runInstall(cwd, packageManager, { skipInstall: Boolean(options.skipInstall), dryRun });
493
+ const install = runInstall(cwd, packageManager, {
494
+ skipInstall: Boolean(options.skipInstall),
495
+ dryRun,
496
+ });
465
497
  const scaffold = runScaffold(cwd, { skipScaffold: Boolean(options.skipScaffold), dryRun });
466
- const guard = runAssistantGuard(cwd, packageJsonPath, { ...options, assistant, dryRun }, assistantConfig);
498
+ const guard = runAssistantGuard(
499
+ cwd,
500
+ packageJsonPath,
501
+ { ...options, assistant, dryRun },
502
+ assistantConfig
503
+ );
467
504
  const generate = runGenerate({ skipGenerate: Boolean(options.skipGenerate), dryRun });
468
505
 
469
506
  const summary = {
@@ -493,5 +530,45 @@ export function setupAssistant(options = {}) {
493
530
  };
494
531
 
495
532
  console.log(JSON.stringify(summary, null, 2));
533
+
534
+ // セットアップ後のリマインダー。AI の会話履歴にも残るよう stderr に出す。
535
+ // en: Post-setup reminder. Written to stderr so it stays in the AI transcript
536
+ // without polluting the JSON stdout payload that tooling parses.
537
+ printPostSetupReminder(guard.target, packageManager);
538
+
496
539
  return summary;
497
540
  }
541
+
542
+ // 検出した package manager に応じて `lint:sparkle` を呼び出すコマンドを返す。
543
+ // en: Returns the shell invocation for the `lint:sparkle` script based on the
544
+ // detected package manager.
545
+ function buildLintCommand(packageManager) {
546
+ switch (packageManager) {
547
+ case 'pnpm':
548
+ return 'pnpm lint:sparkle';
549
+ case 'yarn':
550
+ return 'yarn lint:sparkle';
551
+ case 'bun':
552
+ return 'bun run lint:sparkle';
553
+ case 'npm':
554
+ default:
555
+ return 'npm run lint:sparkle';
556
+ }
557
+ }
558
+
559
+ function printPostSetupReminder(target, packageManager) {
560
+ const lintTarget = target || 'src';
561
+ const lintCmd = buildLintCommand(packageManager);
562
+ const lines = [
563
+ '',
564
+ '📝 次のステップ / Next steps:',
565
+ ` 1. UI コンポーネントを作成・変更したら、必ず \`${lintCmd}\` (または \`npx --yes sparkle-design-cli check ${lintTarget} --strict\`) を実行してください。`,
566
+ ' After creating or modifying UI components, always run `lint:sparkle` to catch Sparkle Design anti-patterns.',
567
+ ' 2. AI でレビューする場合は `lint:sparkle:json` を使い、`findings` に加えて `manualReviewReminders` の各項目まで必ず確認してください。',
568
+ ' For AI review, use `lint:sparkle:json` and inspect every entry of both `findings` and `manualReviewReminders`.',
569
+ '',
570
+ ];
571
+ for (const line of lines) {
572
+ console.error(line);
573
+ }
574
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-design-cli",
3
- "version": "2.0.4",
3
+ "version": "2.0.7-beta.0",
4
4
  "description": "Sparkle Design CSS Generator - デザインシステムCSSを設定ファイルから生成するツール",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",