ksk-design-system 1.61.1 → 1.63.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.
Files changed (52) hide show
  1. package/MIGRATION.md +78 -0
  2. package/README.md +100 -2
  3. package/bin/check-duplicates.js +120 -19
  4. package/bin/init.js +17 -2
  5. package/bin/lint.js +812 -70
  6. package/contracts/components.json +248 -35
  7. package/contracts/rules.json +55 -7
  8. package/contracts/token-hex-cache.json +1 -1
  9. package/dist/class-names.js +1 -1
  10. package/dist/index.js +4743 -4366
  11. package/dist/native/ui.js +24 -13
  12. package/dist/{server-variants-B3uIQjUG.js → server-variants-DTpkG8A7.js} +2 -2
  13. package/dist/types/components/patterns/_internal/carousel-primitives.d.ts +5 -1
  14. package/dist/types/components/patterns/admin/bulk-actions.d.ts +3 -1
  15. package/dist/types/components/patterns/admin/data-table.d.ts +8 -2
  16. package/dist/types/components/patterns/admin/image-uploader.d.ts +3 -1
  17. package/dist/types/components/patterns/admin/kebab-menu.d.ts +3 -1
  18. package/dist/types/components/patterns/app-header.d.ts +19 -1
  19. package/dist/types/components/patterns/banner-carousel.d.ts +3 -1
  20. package/dist/types/components/patterns/celebration.d.ts +3 -1
  21. package/dist/types/components/patterns/chip-selector.d.ts +80 -12
  22. package/dist/types/components/patterns/chip.d.ts +3 -3
  23. package/dist/types/components/patterns/collapsible-chip-field.d.ts +18 -1
  24. package/dist/types/components/patterns/commerce/bottom-tab-bar.d.ts +3 -1
  25. package/dist/types/components/patterns/commerce/filter-bar.d.ts +5 -1
  26. package/dist/types/components/patterns/commerce/image-carousel.d.ts +7 -1
  27. package/dist/types/components/patterns/commerce/quantity-selector.d.ts +9 -1
  28. package/dist/types/components/patterns/content-carousel.d.ts +5 -1
  29. package/dist/types/components/patterns/filter-pill.d.ts +79 -0
  30. package/dist/types/components/patterns/footer.d.ts +48 -12
  31. package/dist/types/components/patterns/image-overlay-action.d.ts +63 -0
  32. package/dist/types/components/patterns/list-item.d.ts +17 -1
  33. package/dist/types/components/patterns/mobile-floating-action-button.d.ts +9 -1
  34. package/dist/types/components/patterns/share-buttons.d.ts +3 -1
  35. package/dist/types/components/patterns/simple-pagination.d.ts +3 -1
  36. package/dist/types/components/patterns/tag.d.ts +48 -1
  37. package/dist/types/components/patterns/thumbnail-card.d.ts +79 -0
  38. package/dist/types/components/ui/button.d.ts +21 -1
  39. package/dist/types/components/ui/dropdown-filter.d.ts +3 -1
  40. package/dist/types/components/ui/input.d.ts +20 -1
  41. package/dist/types/components/ui/multi-select.d.ts +3 -1
  42. package/dist/types/components/ui/pagination.d.ts +5 -1
  43. package/dist/types/components/ui/star-rating.d.ts +15 -1
  44. package/dist/types/components/ui/textarea.d.ts +13 -1
  45. package/dist/types/components/ui/toast.d.ts +16 -3
  46. package/dist/types/index.d.ts +11 -3
  47. package/dist/types/lib/server-variants/unstyled.d.ts +21 -0
  48. package/dist/types/native/components/CollapsibleChipField.d.ts +6 -1
  49. package/package.json +19 -6
  50. package/src/components/COMPONENT_LOOKUP.md +12 -9
  51. package/src/styles/source-safelist.css +55 -0
  52. package/tokens.json +1 -0
package/MIGRATION.md CHANGED
@@ -53,6 +53,84 @@ npx ksk-ds check-migration ./src
53
53
 
54
54
  ## v1 系内の minor 変更(参考)
55
55
 
56
+ ### 次のリリース — `typescript` が必須依存から optional peer dependency になった(issue #409)
57
+
58
+ CLI の1ルール(P046)のためだけに typescript(24MB)が全 consumer の
59
+ node_modules に入っていたのを解消した。install サイズが減り、`ksk-ds lint` の
60
+ 起動も速くなる(実測 655ms → 407ms)。
61
+
62
+ - **通常の TypeScript プロジェクト**: 自前で typescript を持っているので影響なし
63
+ - **typescript を持たないプロジェクト**: P046(card-child-spacing)だけが
64
+ 「typescript が無いため skip」の1行案内つきでスキップされる。他の全ルールは動く
65
+ - DS の typescript に transitive 依存していた場合のみ、自分の devDependencies に
66
+ typescript を追加すること
67
+
68
+
69
+ ### 次のリリース — `ksk-ds lint` の severity 語彙が `warn` → `warning` に統一(要確認)
70
+
71
+ `--format json` の `results[].severity` と text 出力の severity 表記が、これまで
72
+ `"warn"` と `"warning"` で不統一だった(contract の `contracts/rules.json` は
73
+ `"warning"`、CLI 出力は `"warn"`)。**CLI 出力側を `"warning"` に揃えた**(issue #406)。
74
+
75
+ CI で JSON をフィルタしている場合は置き換えが必要:
76
+
77
+ ```bash
78
+ # before
79
+ jq '[.results[] | select(.severity == "warn")] | length'
80
+ # after
81
+ jq '[.results[] | select(.severity == "warning")] | length'
82
+ ```
83
+
84
+ text 出力の要約行も `0 error / 3 warn` → `0 error / 3 warning` に変わる。
85
+
86
+ あわせて `ksk-ds lint` に次を追加した:
87
+
88
+ - `--strict` … warning があれば exit 1
89
+ - `--max-warnings N` … warning が N 件を超えたら exit 1
90
+ - **未知のオプションはエラー終了**(従来は黙って捨てられ、CI に書いた
91
+ `--strict` が無言で効かないまま緑になっていた)
92
+
93
+ ### 次のリリース — `ksk-ds lint` の escape がルール単位・行単位で書けるようになった
94
+
95
+ ファイル全体・全ルールを無期限に外す `// ksk-ds-allow-custom-ui: 理由` は
96
+ 後方互換で残るが**非推奨**。新規はルール単位を使う(issue #405):
97
+
98
+ ```tsx
99
+ // ksk-ds-lint-ignore P008 -- ブランドロゴの規定色のため ← 直下 2 行以内に効く
100
+ // ksk-ds-lint-ignore-file P008 -- ブランド定数ファイル ← そのファイル全体・P008 のみ
101
+ ```
102
+
103
+ 理由は **5 文字以上が必須**。空虚な理由は `ESCAPE001` / `ESCAPE002` として報告され、
104
+ ignore は効かない。あわせて escape マーカーは**文字列リテラルの中では発火しなくなった**
105
+ (ドキュメント URL に `ksk-ds-allow-custom-ui` が含まれるだけで lint 全体が黙って
106
+ 無効化されていた)。既に short な理由を書いていたファイルは lint が復活するので、
107
+ 理由を書き足すか違反を直すこと。
108
+
109
+ ### 次のリリース — 自動生成物と DS 自身のトークン CSS を lint 対象外にした
110
+
111
+ - 先頭 12 行に自動生成マーカー(`AUTO-GENERATED` / `DO NOT EDIT` / `自動生成` 等)を
112
+ 持つファイルは全ルールを skip する(issue #408)。直す先は生成物ではなく生成元。
113
+ - P049(product theme の無許可上書き)は、DS 自身 / ベンダリングされた DS の
114
+ トークン定義 CSS を対象外にする(issue #407)。`vendor/ksk-design-system/**` のように
115
+ DS を再梱包しているリポで P049 が大量に出ていたのが解消する。
116
+
117
+ ### 次のリリース — `Footer` の `paymentIcons` 既定値が空になった(挙動変更)
118
+
119
+ `Footer` は `paymentIcons` を省略すると `["VISA", "Master", "JCB", "AmEx", "PayPay", "LINE Pay"]`
120
+ の決済バッジを既定で描画していた。EC 以外のプロダクトでも「取り扱っていない決済手段」が
121
+ 出てしまうため、**既定値を `[]`(非表示)に変更**した。
122
+
123
+ 従来の表示を維持したい場合は明示的に渡す:
124
+
125
+ ```tsx
126
+ <Footer
127
+ paymentIcons={["VISA", "Master", "JCB", "AmEx", "PayPay", "LINE Pay"]}
128
+ copyright="© 2026 KSK Inc."
129
+ />
130
+ ```
131
+
132
+ `paymentIcons` を明示的に渡していた箇所は影響なし。
133
+
56
134
  ### 次のリリース — `postinstall` による AI ルールファイル自動設置を廃止(要確認)
57
135
 
58
136
  これまで `npm install ksk-design-system` の `postinstall` フックが、消費側の
package/README.md CHANGED
@@ -87,6 +87,50 @@ import { Button, Card, Input, FormField } from "ksk-design-system"
87
87
 
88
88
  新規クライアント案件では、テーマファイルで `--Primitive-Brand-500` などブランドカラーの 10 行を定義するだけで、全コンポーネントがそのブランドカラーで動作します。
89
89
 
90
+ ### 段階移行レシピ(既存 CSS を保ったまま DS 化する)
91
+
92
+ 既存の手書き CSS で作られた画面を DS へ移すときは、**見た目を後回しにして挙動と
93
+ アクセシビリティだけ先に統一する**のが最も安全です。生タグ(`<button>` / `<input>` /
94
+ `<textarea>`)を DS コンポーネントへ置き換え、既存クラスを `className` へ渡し、
95
+ `unstyled` を付けます。
96
+
97
+ ```tsx
98
+ // before — 生タグ + 手書き CSS
99
+ <button className="btn btn-primary" onClick={submit}>ログイン</button>
100
+ <textarea className="composer-input" value={text} onChange={onChange} />
101
+
102
+ // after — 挙動と a11y は DS、見た目は既存 CSS のまま
103
+ <Button unstyled className="btn btn-primary" onClick={submit}>ログイン</Button>
104
+ <Textarea unstyled className="composer-input" value={text} onChange={onChange} />
105
+ ```
106
+
107
+ **なぜ `unstyled` が要るのか。** 手書き CSS は非レイヤー、DS のユーティリティは
108
+ `@layer utilities` なので、宣言が衝突すれば手書き側が勝ちます。しかし手書きクラスの
109
+ 大半は height / white-space / font-weight / border-radius / background を宣言して
110
+ おらず、**衝突しないプロパティは DS の base + variant がそのまま効きます**。
111
+ `className` を渡すだけでは見た目は保てません。aikoibito web の実測では、
112
+ チャット入力欄が `--Field-Min-Height: 5rem` の流入で **43px → 80px** に、
113
+ ログイン CTA が **52px → 40px** に変わりました(issue #420)。
114
+
115
+ `unstyled` を付けたときに DS が出すのは次だけです。
116
+
117
+ | コンポーネント | 維持されるもの | 出さないもの |
118
+ |---|---|---|
119
+ | `Button` | `type` 既定 `"button"` / disabled・aria-disabled クリックの抑止 / `haptic` / `asChild` | `inline-flex` `items-center` `justify-center` `gap-*` `whitespace-nowrap` `typo-*` `cursor-pointer` と variant・size・layout の全クラス |
120
+ | `Input` | `showCount`(IME 追従)/ adornment の配置土台 | `h-*` `w-full` `border-*` `bg-*` `px-*` `typo-*` `placeholder:*` |
121
+ | `Textarea` | `autoGrow` / `showCount` | `min-h-*` `w-full` `border-*` `bg-*` `px-*` `py-*` `typo-*` `placeholder:*` |
122
+
123
+ キーボード操作時の `focus-visible` リングだけは a11y のため既定で残ります。手書き CSS が
124
+ 自前のフォーカス表現を持つ場合は `className="focus-visible:ring-0"` で消せます。
125
+
126
+ 移行の順番は次のとおりです。
127
+
128
+ 1. 生タグを DS コンポーネント + `unstyled` + 既存クラスへ置き換える(見た目は 1px も変わらない)
129
+ 2. この状態で `npx ksk-ds lint src` の P001〜P006 を通す
130
+ 3. 画面ごとに、既存クラスを DS のトークン・variant へ置き換えて `unstyled` を外す
131
+
132
+ `unstyled` は移行の足場であって最終形ではありません。3 が済んだ画面から順に外してください。
133
+
90
134
  ### Consumer lint
91
135
 
92
136
  consumer 側のローカル grep script が古くならないよう、DS 本体から `contracts/rules.json` を読む lint CLI を同梱しています。
@@ -95,14 +139,68 @@ consumer 側のローカル grep script が古くならないよう、DS 本体
95
139
  npx ksk-ds lint src
96
140
  npx ksk-ds lint src --format json
97
141
  npx ksk-ds lint --changed
142
+ npx ksk-ds lint src --platform native
143
+ npx ksk-ds lint src --strict # warning も exit 1
144
+ npx ksk-ds lint src --max-warnings=0 # warning が N 件を超えたら exit 1
98
145
  ```
99
146
 
100
- 出力は `file:line rule severity fix` を含みます。どうしても DS で表現できない domain-specific UI は、理由付きの escape コメントを置きます。
147
+ 既定では `severity: "error"` のルールだけが exit code に影響します。a11y 系を含む
148
+ `warning` ルールも CI で締めたい場合は `--strict` か `--max-warnings N` を使ってください。
149
+ **未知のオプションはエラー終了**します(黙って捨てないので、CI に書いたフラグが
150
+ 無言で効かないことはありません)。
151
+
152
+ ルールはファイルごとに **capability(そのファイルがどの記法を持ちうるか)** を判定して
153
+ 出し分けます(`contracts/rules.json` の `appliesTo`)。
154
+
155
+ | ファイル | capability | 当たるルール |
156
+ |---|---|---|
157
+ | web(native シグナルなし) | `dom` `tailwind` `web` | 全部 |
158
+ | React Native + `className` あり(**NativeWind**) | `native` `tailwind` | Tailwind 系は当たる/DOM 生タグ系と CSS 変数系は外れる |
159
+ | React Native + `className` なし(**StyleSheet**) | `native` | Tailwind 系も外れる |
160
+
161
+ native の判定シグナルは `*.native.tsx` というファイル名、`react-native` /
162
+ `ksk-design-system/native` の **import 文**、`StyleSheet.create` の使用です。
163
+ 自動判定が合わない場合は `--platform web` / `--platform native` で上書きできます
164
+ (ファイル判定より優先)。ただし `--platform native` を付けても Tailwind を持つかどうかは
165
+ ソースの `className` の有無で決まるので、NativeWind の consumer が Tailwind 系ルールを
166
+ 失うことはありません。
167
+
168
+ `appliesTo` を持たないルール(`#hex` 直書きの P008 等)は従来どおり全ファイルに適用され、
169
+ native では RN 向けの fix 文言を表示します。
170
+
171
+ 出力は `file:line rule severity fix` を含みます。severity の語彙は `error` / `warning` で、
172
+ `contracts/rules.json` と JSON 出力・text 出力すべてで一致しています。
173
+
174
+ #### 例外(escape)
175
+
176
+ どうしても DS で表現できない箇所は、**ルール単位・理由付き**の escape コメントを置きます
177
+ (理由は 5 文字以上が必須。空虚な理由は `ESCAPE002` として報告され、ignore は効きません)。
101
178
 
102
179
  ```tsx
103
- // ksk-ds-allow-custom-ui: medical chart requires bespoke interaction
180
+ // ksk-ds-lint-ignore P008 -- ブランドロゴの規定色のため
181
+ const BRAND = "#06C755"
104
182
  ```
105
183
 
184
+ | 書き方 | スコープ |
185
+ |---|---|
186
+ | `// ksk-ds-lint-ignore P008 -- 理由` | そのコメント行から**下 2 行以内**・指定ルールのみ |
187
+ | `// ksk-ds-lint-ignore-file P008 -- 理由` | **そのファイル全体**・指定ルールのみ |
188
+ | `// ksk-ds-allow-custom-ui: 理由` | そのファイル全体・**全ルール**(非推奨・後方互換) |
189
+
190
+ `ksk-ds-allow-custom-ui` はスコープが広すぎて将来の違反まで隠すため、新規では使わず
191
+ ルール単位の `ksk-ds-lint-ignore` を使ってください。escape マーカーは**コメントの中でのみ**
192
+ 有効で、文字列リテラル(ドキュメント URL 等)の中では発火しません。
193
+ 理由は 5 文字以上が必須です。また理由文字列に `*` は使えません
194
+ (`*/` を誤って飲み込まないための制約で、`*` 以降は理由として認識されません)。
195
+
196
+ #### lint されないファイル
197
+
198
+ - 先頭 12 行に自動生成マーカー(`AUTO-GENERATED` / `DO NOT EDIT` / `自動生成` 等)を持つ
199
+ ファイルは全ルールを skip します。直す先は生成物ではなく生成元です。
200
+ - DS 自身のトークン定義 CSS(`src/styles/*.css` / `src/themes/*.css` / `src/preset.css` と、
201
+ それを再梱包したベンダリングコピー)は P049 の対象外です。トークンを**定義**している側で
202
+ あって、consumer による上書きではないためです。
203
+
106
204
  ### Jest(CommonJS)でコンポーネントをテストする
107
205
 
108
206
  このパッケージは **ESM-only** です。CJS との dual build は配布せず、Jest
@@ -36,11 +36,15 @@ export function runCheckDuplicatesCli(
36
36
 
37
37
  const registry = loadComponentRegistry(contractsPath)
38
38
  const files = collectSourceFiles(target)
39
- const findings = files
40
- .flatMap((file) => findDuplicateExports(file, cwd, registry))
39
+ const results = files.map((file) => findDuplicateExports(file, cwd, registry))
40
+ const findings = results
41
+ .flatMap((result) => result.findings)
41
42
  .sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name))
43
+ const wrapperExclusions = results
44
+ .flatMap((result) => result.wrapperExclusions)
45
+ .sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name))
42
46
 
43
- printFindings(findings, files.length, options.strict)
47
+ printFindings(findings, wrapperExclusions, files.length, options.strict)
44
48
  return options.strict && findings.length > 0 ? 1 : 0
45
49
  }
46
50
 
@@ -117,10 +121,83 @@ function collectSourceFiles(target) {
117
121
  return files.sort()
118
122
  }
119
123
 
124
+ // ksk-design-system 本体(サブパス含む)からの named import/re-export かどうか。
125
+ // 例: "ksk-design-system", "ksk-design-system/native", "ksk-design-system/native/ui"
126
+ function isDsModuleSpecifier(moduleSpecifier) {
127
+ return moduleSpecifier === "ksk-design-system" || moduleSpecifier.startsWith("ksk-design-system/")
128
+ }
129
+
130
+ // `{ A, B as C, type D }` のような specifier リストを
131
+ // [{ sourceName: "A", localName: "A" }, { sourceName: "B", localName: "C" }, ...] に分解する。
132
+ // エイリアス判定は「import 指定子の imported 名(sourceName)」を用いる。ローカル名(localName)は問わない。
133
+ function parseSpecifierList(specifierList) {
134
+ return specifierList
135
+ .split(",")
136
+ .map((spec) => spec.trim())
137
+ .filter((spec) => spec.length > 0)
138
+ .map((spec) => {
139
+ const withoutTypeModifier = spec.replace(/^type\s+/, "")
140
+ const [sourceName, localName] = withoutTypeModifier.split(/\s+as\s+/).map((part) => part.trim())
141
+ return { sourceName, localName: localName ?? sourceName }
142
+ })
143
+ .filter((entry) => entry.sourceName)
144
+ }
145
+
146
+ // import/re-export 検出はコメントを除外してから行う(JSDoc の使用例コメント
147
+ // `* import { X } from "ksk-design-system"` を実コードの import と誤認しないため)。
148
+ // 文字列リテラル内の `/* `等までは考慮しないが、このファイル全体の regex ベース方針と
149
+ // 整合する範囲の簡易対応とする。
150
+ function stripComments(source) {
151
+ return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "")
152
+ }
153
+
154
+ // このファイルが ksk-design-system から named import している「imported 名(sourceName)」の集合。
155
+ // `import { EmptyState as DSEmptyState } from 'ksk-design-system/native/ui'` なら "EmptyState"。
156
+ // ローカルの alias 名(DSEmptyState)ではなく、パッケージ側の元の名前で判定する。
157
+ function collectDsImportSourceNames(source) {
158
+ const names = new Set()
159
+ const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g
160
+
161
+ for (const match of source.matchAll(importPattern)) {
162
+ const [, specifierList, moduleSpecifier] = match
163
+ if (!isDsModuleSpecifier(moduleSpecifier)) continue
164
+ for (const { sourceName } of parseSpecifierList(specifierList)) {
165
+ names.add(sourceName)
166
+ }
167
+ }
168
+
169
+ return names
170
+ }
171
+
172
+ // `export { X as Y } from 'ksk-design-system/...'` のような re-export 形式。
173
+ // これは常に DS の実装をそのまま右から左へ渡すだけなので、localName が
174
+ // registry と一致する場合は無条件でラッパー(除外対象)として扱う。
175
+ function collectDsReExports(source) {
176
+ const reExports = []
177
+ const reExportPattern = /export\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g
178
+
179
+ for (const match of source.matchAll(reExportPattern)) {
180
+ const [, specifierList, moduleSpecifier] = match
181
+ if (!isDsModuleSpecifier(moduleSpecifier)) continue
182
+ for (const { localName } of parseSpecifierList(specifierList)) {
183
+ reExports.push(localName)
184
+ }
185
+ }
186
+
187
+ return reExports
188
+ }
189
+
120
190
  function findDuplicateExports(file, cwd, registry) {
121
191
  const source = readFileSync(file, "utf8")
122
192
  const lines = source.split(/\r?\n/)
123
193
  const findings = []
194
+ const wrapperExclusions = []
195
+ const relFile = normalize(relative(cwd, file))
196
+
197
+ const codeForImportScan = stripComments(source)
198
+ const dsImportSourceNames = collectDsImportSourceNames(codeForImportScan)
199
+ const dsReExportLocalNames = collectDsReExports(codeForImportScan)
200
+
124
201
  const declarationPattern =
125
202
  /^\s*export\s+(?:default\s+)?(?:(?:async|declare)\s+)?(?:function|const)\s+([A-Z][A-Za-z0-9]*)\b/
126
203
 
@@ -130,37 +207,59 @@ function findDuplicateExports(file, cwd, registry) {
130
207
  const name = match[1]
131
208
  const components = registry.get(name)
132
209
  if (!components) continue
210
+
211
+ // 同名のローカル宣言があっても、同ファイルが ksk-design-system から同名(元の
212
+ // export 名基準。ローカル alias は問わない)を import して使っているなら、
213
+ // DS 委譲ラッパーとして重複報告から除外する(DS が推奨する段階移行の型のため)。
214
+ if (dsImportSourceNames.has(name)) {
215
+ wrapperExclusions.push({ file: relFile, name })
216
+ continue
217
+ }
218
+
133
219
  findings.push({
134
220
  components,
135
- file: normalize(relative(cwd, file)),
221
+ file: relFile,
136
222
  line: index + 1,
137
223
  name,
138
224
  })
139
225
  }
140
226
 
141
- return findings
227
+ // `export { X as Y } from 'ksk-design-system/...'` 形式の re-export。
228
+ // registry と一致する localName だけを対象にする(DS に無い名前の re-export は無関係)。
229
+ for (const localName of dsReExportLocalNames) {
230
+ if (!registry.get(localName)) continue
231
+ wrapperExclusions.push({ file: relFile, name: localName })
232
+ }
233
+
234
+ return { findings, wrapperExclusions }
142
235
  }
143
236
 
144
- function printFindings(findings, scannedFiles, strict) {
237
+ function printFindings(findings, wrapperExclusions, scannedFiles, strict) {
145
238
  if (findings.length === 0) {
146
239
  console.log(`ksk-ds check-duplicates: 重複候補はありません(${scannedFiles} files)`)
147
- return
240
+ } else {
241
+ console.log(`ksk-ds check-duplicates: DS と同名のローカル実装を ${findings.length} 件検出しました`)
242
+ for (const finding of findings) {
243
+ console.log(`\n${finding.file}:${finding.line} ${finding.name}`)
244
+ for (const component of finding.components) {
245
+ console.log(` DS: ${component.tier} / ${component.path}`)
246
+ }
247
+ }
248
+ console.log("\nローカル実装を削除して ksk-design-system から import できないか確認してください。")
249
+ console.log("DS に必要な機能が不足する場合は、ローカル台帳を作らず DS 側に Issue を登録してください。")
250
+ console.log(
251
+ strict
252
+ ? "strict mode: 重複候補があるため終了コード 1"
253
+ : "advice mode: 終了コード 0(CI で失敗させる場合は --strict)",
254
+ )
148
255
  }
149
256
 
150
- console.log(`ksk-ds check-duplicates: DS と同名のローカル実装を ${findings.length} 件検出しました`)
151
- for (const finding of findings) {
152
- console.log(`\n${finding.file}:${finding.line} ${finding.name}`)
153
- for (const component of finding.components) {
154
- console.log(` DS: ${component.tier} / ${component.path}`)
257
+ if (wrapperExclusions.length > 0) {
258
+ console.log(`\nℹ ラッパー(DS 委譲済み)として除外: ${wrapperExclusions.length} 件`)
259
+ for (const wrapper of wrapperExclusions) {
260
+ console.log(` ℹ ラッパー(DS 委譲済み)として除外: ${wrapper.name} (${wrapper.file})`)
155
261
  }
156
262
  }
157
- console.log("\nローカル実装を削除して ksk-design-system から import できないか確認してください。")
158
- console.log("DS に必要な機能が不足する場合は、ローカル台帳を作らず DS 側に Issue を登録してください。")
159
- console.log(
160
- strict
161
- ? "strict mode: 重複候補があるため終了コード 1"
162
- : "advice mode: 終了コード 0(CI で失敗させる場合は --strict)",
163
- )
164
263
  }
165
264
 
166
265
  function printHelp() {
@@ -176,6 +275,8 @@ DIR の既定値:
176
275
  動作:
177
276
  export function / export const の PascalCase 宣言を
178
277
  contracts/components.json と名前の完全一致で照合します。
278
+ ただし、同ファイルが ksk-design-system から同名(alias 可)を import/re-export して
279
+ 使っているものは DS 委譲ラッパーとみなし、重複報告から除外します(info 行で件数報告)。
179
280
  既定は助言モード(常に exit 0)、--strict は検出時 exit 1 です。
180
281
  `)
181
282
  }
package/bin/init.js CHANGED
@@ -82,8 +82,23 @@ if (cmd === "lint") {
82
82
  }
83
83
 
84
84
  if (cmd === "check-migration") {
85
- const { runCheckMigrationCli } = await import("./check-migration.js")
86
- const status = runCheckMigrationCli(args.slice(1), { cwd: process.cwd(), pkgRoot })
85
+ // check-migration.js は TypeScript の AST 解析そのものが本体なので、
86
+ // typescript 不在なら raw な ERR_MODULE_NOT_FOUND ではなく案内を出して
87
+ // skip する(exit 0。lint 同様に「無言で落ちる」を避ける/issue #409)。
88
+ let mod
89
+ try {
90
+ mod = await import("./check-migration.js")
91
+ } catch (error) {
92
+ if (error?.code === "ERR_MODULE_NOT_FOUND" && String(error.message ?? "").includes("typescript")) {
93
+ console.log(
94
+ `[ksk-ds check-migration] typescript が見つからないため skip します。` +
95
+ `devDependencies に typescript を追加すると検査できます。`,
96
+ )
97
+ await exitWith(0)
98
+ }
99
+ throw error
100
+ }
101
+ const status = mod.runCheckMigrationCli(args.slice(1), { cwd: process.cwd(), pkgRoot })
87
102
  await exitWith(status)
88
103
  }
89
104