sparkle-design-cli 1.3.11 → 1.4.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
@@ -178,45 +178,86 @@ npx --yes sparkle-design-cli setup --assistant generic
178
178
 
179
179
  ### 設定オプション
180
180
 
181
+ #### Core(プラグインが出力)
182
+
181
183
  - `primary`: プライマリカラー(blue, red, orange など)
182
- - `font-pro`: プロポーショナルフォント(Google Fonts の名前)
183
- - `font-mono`: モノスペースフォント(Google Fonts の名前)
184
+ - `font-pro`: プロポーショナルフォント(文字列または配列。配列の場合はフォールバックチェーンになる)
185
+ - `font-mono`: モノスペースフォント(文字列または配列)
184
186
  - `radius`: 角丸設定(sm, md, lg など)
187
+
188
+ #### Core Options
189
+
190
+ - `font-pro-weights`: (オプション)プロポーショナルフォントのウェイト配列(デフォルト: `[400, 700]`)
191
+ - `font-mono-weights`: (オプション)モノスペースフォントのウェイト配列(デフォルト: `[400, 700]`)
185
192
  - `source-packages`: (オプション)`@source` ディレクティブで追加スキャンする npm パッケージの配列
186
193
 
187
- ### source-packages オプション
194
+ #### Custom Settings
188
195
 
189
- `@goodpatch/sparkle-design` を npm パッケージとして利用するプロジェクトでは、TailwindCSS v4 がパッケージ内のユーティリティクラスを検出できるように `@source` ディレクティブが必要です。
196
+ - `custom-css`: (オプション)プロジェクト固有のカスタムトークン CSS ファイルのパス。`globals.css` に `@import` が自動挿入される
190
197
 
191
- `source-packages` を設定すると、CLI が `globals.css` に `@source` ディレクティブを自動挿入します。`@goodpatch/sparkle-design` は常にデフォルトで含まれます。
198
+ ### フォントウェイトとフォールバック
199
+
200
+ `font-pro-weights` / `font-mono-weights` でフォントウェイトをカスタマイズできます。未指定時は `[400, 700]` がデフォルトです。
201
+
202
+ `font-pro` / `font-mono` に配列を指定すると、フォントフォールバックチェーンとして複数の Google Fonts import が生成されます。
192
203
 
193
204
  ```json
194
205
  {
195
206
  "primary": "blue",
196
- "font-mono": "BIZ UDGothic",
197
- "font-pro": "BIZ UDPGothic",
198
- "radius": "md",
199
- "source-packages": []
207
+ "font-pro": ["Montserrat", "Noto Sans JP"],
208
+ "font-pro-weights": [400, 500, 600, 700],
209
+ "font-mono": "Roboto Mono",
210
+ "font-mono-weights": [400, 700],
211
+ "radius": "md"
200
212
  }
201
213
  ```
202
214
 
203
- 追加の npm パッケージ(拡張リポジトリなど)をスキャン対象にする場合:
215
+ 生成される CSS:
216
+
217
+ ```css
218
+ --font-family-pro: 'Montserrat', 'Noto Sans JP', sans-serif;
219
+ ```
220
+
221
+ ```css
222
+ @import 'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap';
223
+ @import 'https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;600;700&display=swap';
224
+ ```
225
+
226
+ ### custom-css オプション
227
+
228
+ プロジェクト固有のカスタムトークン(Display, Heading, Body 等のタイポグラフィトークン、追加のフォントウェイト、メディアクエリなど)を `sparkle-design.css` に直接追加するのは避けてください。`generate` 実行時に上書きされます。
229
+
230
+ 代わりに、カスタムトークンを別の CSS ファイルに定義し、`custom-css` で指定してください:
204
231
 
205
232
  ```json
206
233
  {
207
- "source-packages": ["@scope/my-extension-design"]
234
+ "custom-css": "./src/app/custom-tokens.css"
208
235
  }
209
236
  ```
210
237
 
211
- 生成される `globals.css`:
238
+ CLI が `globals.css` に `@import` を自動挿入します。CLI はファイルの中身を一切触りません。
212
239
 
213
240
  ```css
214
- @import 'tailwindcss';
215
- /* npm パッケージのコンテンツスキャン(Tailwind がクラスを検出するために必要) */
216
- @source "../node_modules/@goodpatch/sparkle-design/dist";
217
- @source "../node_modules/@scope/my-extension-design/dist";
218
- /* Sparkle Design のカスタム定義(Tailwindの後にインポート) */
219
- @import './sparkle-design.css';
241
+ /* globals.css に自動生成される構造 */
242
+ /* フォントのインポート */
243
+ @import '...google fonts...';
244
+ @import "tailwindcss";
245
+ /* Sparkle Design のカスタム定義 */
246
+ @import "./sparkle-design.css";
247
+ /* プロジェクト固有のカスタムトークン */
248
+ @import "./custom-tokens.css";
249
+ ```
250
+
251
+ ### source-packages オプション
252
+
253
+ `@goodpatch/sparkle-design` を npm パッケージとして利用するプロジェクトでは、TailwindCSS v4 がパッケージ内のユーティリティクラスを検出できるように `@source` ディレクティブが必要です。
254
+
255
+ `source-packages` を設定すると、CLI が `globals.css` に `@source` ディレクティブを自動挿入します。`@goodpatch/sparkle-design` は常にデフォルトで含まれます。
256
+
257
+ ```json
258
+ {
259
+ "source-packages": ["@scope/my-extension-design"]
260
+ }
220
261
  ```
221
262
 
222
263
  > **注意**: `source-packages` を指定しない場合、`@source` ディレクティブは生成されません。
package/lib/constants.js CHANGED
@@ -37,8 +37,13 @@ export const REGEX = {
37
37
  SOURCE_DIRECTIVE: /@source\s+["'][^"']*["'];?\s*\n?/g,
38
38
  SOURCE_COMMENT: /\/\*\s*npm パッケージのコンテンツスキャン[^*]*\*\/\s*\n?/g,
39
39
 
40
+ // カスタムCSS関連
41
+ CUSTOM_CSS_IMPORT:
42
+ /\/\*\s*プロジェクト固有のカスタムトークン[^*]*\*\/\s*\n?|@import\s+['"][^"']*custom[^"']*['"];?\s*\n?/g,
43
+
40
44
  // テンプレート関連
41
45
  COLOR_TOKENS_PLACEHOLDER: /[ \t]*\/\* \{\{COLOR_TOKENS\}\} \*\/[ \t]*\n?/,
46
+ FONT_IMPORTS_PLACEHOLDER: /[ \t]*\/\* \{\{FONT_IMPORTS\}\} \*\/[ \t]*\n?/,
42
47
  RADIUS_SEMANTIC_PATTERN: (semanticName) =>
43
48
  new RegExp(`--radius-${semanticName}:\\s*var\\(--radius-[^)]+\\)`, 'g'),
44
49
  };
@@ -46,6 +51,20 @@ export const REGEX = {
46
51
  // テンプレートプレースホルダー
47
52
  export const PLACEHOLDERS = {
48
53
  COLOR_TOKENS: '{{COLOR_TOKENS}}',
54
+ FONT_IMPORTS: '{{FONT_IMPORTS}}',
55
+ FONT_FAMILY_PRO: '{{FONT_FAMILY_PRO}}',
56
+ FONT_FAMILY_MONO: '{{FONT_FAMILY_MONO}}',
57
+ };
58
+
59
+ // フォントのデフォルト設定
60
+ export const FONT_DEFAULTS = {
61
+ WEIGHTS: [400, 700],
62
+ MATERIAL_SYMBOLS_IMPORT:
63
+ "@import 'https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,0..200&display=block';",
64
+ GENERIC_FAMILY: {
65
+ pro: 'sans-serif',
66
+ mono: 'monospace',
67
+ },
49
68
  };
50
69
 
51
70
  // カラー変換精度
@@ -76,6 +95,7 @@ export const COMMENTS = {
76
95
  SPARKLE_IMPORT: '/* Sparkle Design のカスタム定義(Tailwindの後にインポート) */',
77
96
  TAILWIND_IMPORT: '/* Tailwindのインポート */',
78
97
  SOURCE_DIRECTIVE: '/* npm パッケージのコンテンツスキャン(Tailwind がクラスを検出するために必要) */',
98
+ CUSTOM_CSS: '/* プロジェクト固有のカスタムトークン */',
79
99
  };
80
100
 
81
101
  // インポート文のテンプレート
@@ -5,7 +5,7 @@
5
5
 
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
- import { REGEX, COMMENTS, IMPORTS, MESSAGES } from './constants.js';
8
+ import { REGEX, COMMENTS, IMPORTS, MESSAGES, FONT_DEFAULTS } from './constants.js';
9
9
 
10
10
  /**
11
11
  * sparkle-design.css からフォントの@import文を抽出する
@@ -72,18 +72,56 @@ function createSourceBlock(sourcePackages = [], nodeModulesRel = '../node_module
72
72
  return [COMMENTS.SOURCE_DIRECTIVE, ...sourceLines].join('\n');
73
73
  }
74
74
 
75
+ /**
76
+ * カスタムCSS importブロックを生成する
77
+ * @param {string} customCssPath custom-css ファイルの相対パス
78
+ * @param {string} globalsPath globals.css の絶対パス(相対パス計算用)
79
+ * @returns {string} カスタムCSS importブロック
80
+ */
81
+ function createCustomCssImportBlock(customCssPath, globalsPath) {
82
+ if (!customCssPath) {
83
+ return '';
84
+ }
85
+
86
+ // custom-css パスは sparkle.config.json からの相対パス(= プロジェクトルートからの相対パス)
87
+ // globals.css からの相対パスに変換
88
+ const globalsDir = path.dirname(globalsPath);
89
+ const absoluteCustomPath = path.resolve(process.cwd(), customCssPath);
90
+ let relativePath = path.relative(globalsDir, absoluteCustomPath).split(path.sep).join('/');
91
+
92
+ // 相対パスが ./ で始まらない場合は付与
93
+ if (!relativePath.startsWith('.')) {
94
+ relativePath = `./${relativePath}`;
95
+ }
96
+
97
+ return [COMMENTS.CUSTOM_CSS, `@import "${relativePath}";`].join('\n');
98
+ }
99
+
75
100
  /**
76
101
  * sparkle-design.css importブロックを生成する
77
102
  * @param {Array<string>} sourcePackages 追加パッケージ名の配列
78
103
  * @param {string} globalsPath globals.css の絶対パス(@source パス計算用)
104
+ * @param {string|null} customCssPath custom-css ファイルの相対パス
79
105
  * @returns {string} sparkle-design.css importブロック
80
106
  */
81
- function createSparkleImportBlock(sourcePackages = [], globalsPath = null) {
82
- if (sourcePackages === null || sourcePackages === undefined) {
83
- return ['', COMMENTS.SPARKLE_IMPORT, IMPORTS.SPARKLE_DESIGN, ''].join('\n');
107
+ function createSparkleImportBlock(sourcePackages = [], globalsPath = null, customCssPath = null) {
108
+ const parts = [''];
109
+
110
+ if (sourcePackages !== null && sourcePackages !== undefined) {
111
+ const nodeModulesRel = globalsPath ? resolveNodeModulesRelPath(globalsPath) : '../node_modules';
112
+ parts.push(createSourceBlock(sourcePackages, nodeModulesRel));
113
+ }
114
+
115
+ parts.push(COMMENTS.SPARKLE_IMPORT, IMPORTS.SPARKLE_DESIGN);
116
+
117
+ // カスタムCSS import を sparkle-design.css の後に配置
118
+ const customBlock = createCustomCssImportBlock(customCssPath, globalsPath);
119
+ if (customBlock) {
120
+ parts.push(customBlock);
84
121
  }
85
- const nodeModulesRel = globalsPath ? resolveNodeModulesRelPath(globalsPath) : '../node_modules';
86
- return ['', createSourceBlock(sourcePackages, nodeModulesRel), COMMENTS.SPARKLE_IMPORT, IMPORTS.SPARKLE_DESIGN, ''].join('\n');
122
+
123
+ parts.push('');
124
+ return parts.join('\n');
87
125
  }
88
126
 
89
127
  /**
@@ -104,6 +142,9 @@ function removeExistingImports(globalsContent) {
104
142
  // 既存のsparkle-design.css importを削除
105
143
  cleaned = cleaned.replace(REGEX.SPARKLE_IMPORT, '');
106
144
 
145
+ // 既存のカスタムCSS importを削除
146
+ cleaned = cleaned.replace(REGEX.CUSTOM_CSS_IMPORT, '');
147
+
107
148
  return cleaned;
108
149
  }
109
150
 
@@ -159,9 +200,10 @@ function reconstructGlobalsCss(globalsContent, fontImportBlock, sparkleImportBlo
159
200
  * @param {Array<string>} fontImports フォントimport文の配列
160
201
  * @param {string} globalsPath globals.cssのパス
161
202
  * @param {Array<string>|null} sourcePackages 追加パッケージ名の配列(null の場合は @source を生成しない)
203
+ * @param {string|null} customCssPath custom-css ファイルの相対パス
162
204
  * @returns {boolean} globals.css の更新に成功した場合は true
163
205
  */
164
- export function updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages = null) {
206
+ export function updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages = null, customCssPath = null) {
165
207
  if (fontImports.length === 0) {
166
208
  return false;
167
209
  }
@@ -182,7 +224,7 @@ export function updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages
182
224
 
183
225
  // 4. importブロックを生成
184
226
  const fontImportBlock = createFontImportBlock(fontImports);
185
- const sparkleImportBlock = createSparkleImportBlock(sourcePackages, globalsPath);
227
+ const sparkleImportBlock = createSparkleImportBlock(sourcePackages, globalsPath, customCssPath);
186
228
 
187
229
  // 5. globals.css を再構築
188
230
  const reconstructedContent = reconstructGlobalsCss(
@@ -208,8 +250,9 @@ export function updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages
208
250
  * sparkle-design.css からフォントimportを抽出し、globals.css に移動する
209
251
  * @param {string} sparkleDesignPath sparkle-design.cssのパス
210
252
  * @param {Array<string>|null} sourcePackages 追加パッケージ名の配列(null の場合は @source を生成しない)
253
+ * @param {string|null} customCssPath custom-css ファイルの相対パス
211
254
  */
212
- export function manageFontImports(sparkleDesignPath, sourcePackages = null) {
255
+ export function manageFontImports(sparkleDesignPath, sourcePackages = null, customCssPath = null) {
213
256
  try {
214
257
  // 1. globals.cssのパスを推定(sparkle-design.cssと同じディレクトリ)
215
258
  const globalsPath = path.join(path.dirname(sparkleDesignPath), 'globals.css');
@@ -232,7 +275,7 @@ export function manageFontImports(sparkleDesignPath, sourcePackages = null) {
232
275
  console.log(MESSAGES.FONT_DETECTED(fontImports.length));
233
276
 
234
277
  // 4. globals.css にフォントimportを追加
235
- const globalsUpdated = updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages);
278
+ const globalsUpdated = updateGlobalsWithFonts(fontImports, globalsPath, sourcePackages, customCssPath);
236
279
 
237
280
  if (!globalsUpdated) {
238
281
  console.log(MESSAGES.FONT_REMOVE_SKIPPED);
@@ -7,7 +7,7 @@
7
7
 
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
- import { REGEX, PATHS, MESSAGES } from './constants.js';
10
+ import { REGEX, PATHS, MESSAGES, FONT_DEFAULTS } from './constants.js';
11
11
  import {
12
12
  loadConfig,
13
13
  loadTemplate,
@@ -48,6 +48,71 @@ function dedupeFontImports(cssContent) {
48
48
  .join('\n');
49
49
  }
50
50
 
51
+ /**
52
+ * フォント設定を正規化する(文字列 → 配列)
53
+ * @param {string|string[]} fontValue フォント名(文字列または配列)
54
+ * @returns {string[]} フォント名の配列
55
+ */
56
+ function normalizeFontConfig(fontValue) {
57
+ if (Array.isArray(fontValue)) {
58
+ return fontValue;
59
+ }
60
+ return [fontValue];
61
+ }
62
+
63
+ /**
64
+ * Google Fonts の @import 行を生成する
65
+ * @param {string[]} fonts フォント名の配列
66
+ * @param {number[]} weights ウェイトの配列
67
+ * @returns {string[]} @import 文の配列
68
+ */
69
+ function generateGoogleFontImports(fonts, weights) {
70
+ const weightsStr = weights.sort((a, b) => a - b).join(';');
71
+ return fonts.map(font => {
72
+ const urlName = font.replace(/\s+/g, '+');
73
+ return `@import 'https://fonts.googleapis.com/css2?family=${urlName}:wght@${weightsStr}&display=swap';`;
74
+ });
75
+ }
76
+
77
+ /**
78
+ * フォントファミリーの CSS 値を生成する
79
+ * @param {string[]} fonts フォント名の配列
80
+ * @param {string} genericFamily ジェネリックファミリー(sans-serif / monospace)
81
+ * @returns {string} CSS font-family 値(例: 'Montserrat', 'Noto Sans JP', sans-serif)
82
+ */
83
+ function generateFontFamilyValue(fonts, genericFamily) {
84
+ const quoted = fonts.map(f => `'${f}'`);
85
+ return [...quoted, genericFamily].join(', ');
86
+ }
87
+
88
+ /**
89
+ * フォント import ブロック全体を生成する(Material Symbols + PRO + MONO)
90
+ * @param {Object} config 設定オブジェクト
91
+ * @returns {string} フォント import ブロック
92
+ */
93
+ function generateFontImportsBlock(config) {
94
+ const proFonts = normalizeFontConfig(config['font-pro']);
95
+ const monoFonts = normalizeFontConfig(config['font-mono']);
96
+ const proWeights = config['font-pro-weights'] || FONT_DEFAULTS.WEIGHTS;
97
+ const monoWeights = config['font-mono-weights'] || FONT_DEFAULTS.WEIGHTS;
98
+
99
+ const imports = [
100
+ FONT_DEFAULTS.MATERIAL_SYMBOLS_IMPORT,
101
+ ...generateGoogleFontImports(proFonts, proWeights),
102
+ ...generateGoogleFontImports(monoFonts, monoWeights),
103
+ ];
104
+
105
+ // 重複排除(font-pro と font-mono が同じフォントでウェイトも同じ場合)
106
+ const seen = new Set();
107
+ const deduped = imports.filter(line => {
108
+ if (seen.has(line)) return false;
109
+ seen.add(line);
110
+ return true;
111
+ });
112
+
113
+ return deduped.join('\n');
114
+ }
115
+
51
116
  /**
52
117
  * テンプレート変数を設定値で置換する
53
118
  * @param {string} template CSSテンプレート
@@ -65,8 +130,24 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
65
130
  // プレースホルダーを色のトークンで置換(改行を含む)
66
131
  processedCSS = processedCSS.replace(REGEX.COLOR_TOKENS_PLACEHOLDER, `${colorTokens}\n`);
67
132
 
68
- // 2. 基本的な設定値による置換
133
+ // 2. フォント import ブロックを生成して置換
134
+ const fontImportsBlock = generateFontImportsBlock(config);
135
+ processedCSS = processedCSS.replace(REGEX.FONT_IMPORTS_PLACEHOLDER, `${fontImportsBlock}\n`);
136
+
137
+ // 3. フォントファミリー CSS 値を生成して置換
138
+ const proFonts = normalizeFontConfig(config['font-pro']);
139
+ const monoFonts = normalizeFontConfig(config['font-mono']);
140
+ const proFamilyValue = generateFontFamilyValue(proFonts, FONT_DEFAULTS.GENERIC_FAMILY.pro);
141
+ const monoFamilyValue = generateFontFamilyValue(monoFonts, FONT_DEFAULTS.GENERIC_FAMILY.mono);
142
+ processedCSS = processedCSS.replace(/\{\{FONT_FAMILY_PRO\}\}/g, proFamilyValue);
143
+ processedCSS = processedCSS.replace(/\{\{FONT_FAMILY_MONO\}\}/g, monoFamilyValue);
144
+
145
+ // 4. 基本的な設定値による置換(font-pro / font-mono の配列以外)
69
146
  Object.entries(config).forEach(([key, value]) => {
147
+ // 配列や font-*-weights はスキップ
148
+ if (Array.isArray(value) || key.endsWith('-weights') || key === 'custom-css') {
149
+ return;
150
+ }
70
151
  // 通常のプレースホルダー(CSS用 - スペースはそのまま)
71
152
  const placeholder = `{{${key.toUpperCase().replace('-', '_')}}}`;
72
153
  processedCSS = processedCSS.replace(new RegExp(placeholder, 'g'), value);
@@ -79,7 +160,7 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
79
160
  }
80
161
  });
81
162
 
82
- // 3. Radiusの一括置換
163
+ // 6. Radiusの一括置換
83
164
  if (config.radius && radiusMapping[config.radius]) {
84
165
  const radiusValues = radiusMapping[config.radius];
85
166
 
@@ -165,7 +246,8 @@ export function generateCSS(configPath = null, outputPath = null) {
165
246
  // 6. フォント管理の自動処理を実行
166
247
  console.log(MESSAGES.FONT_MANAGEMENT_START);
167
248
  const sourcePackages = 'source-packages' in config ? (config['source-packages'] || []) : null;
168
- manageFontImports(resolvedOutputPath, sourcePackages);
249
+ const customCssPath = config['custom-css'] || null;
250
+ manageFontImports(resolvedOutputPath, sourcePackages, customCssPath);
169
251
 
170
252
  console.log(MESSAGES.SUCCESS);
171
253
  }
@@ -191,6 +273,11 @@ export {
191
273
  hexToOklch,
192
274
  generateColorTokens,
193
275
  dedupeFontImports,
276
+ // フォント処理
277
+ normalizeFontConfig,
278
+ generateGoogleFontImports,
279
+ generateFontFamilyValue,
280
+ generateFontImportsBlock,
194
281
  // フォント管理
195
282
  extractFontImports,
196
283
  removeFontImportsFromCSS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-design-cli",
3
- "version": "1.3.11",
3
+ "version": "1.4.0",
4
4
  "description": "Sparkle Design CSS Generator - デザインシステムCSSを設定ファイルから生成するツール",
5
5
  "main": "lib/generate-css.js",
6
6
  "type": "module",
@@ -1,7 +1,5 @@
1
1
  /* フォントのインポート */
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';
2
+ /* {{FONT_IMPORTS}} */
5
3
 
6
4
  /* Primitive Tokens */
7
5
  :root {
@@ -21,8 +19,8 @@
21
19
 
22
20
  /* タイポグラフィ変数 */
23
21
  /* フォントファミリー */
24
- --font-family-pro: '{{FONT_PRO}}', sans-serif;
25
- --font-family-mono: '{{FONT_MONO}}', monospace;
22
+ --font-family-pro: {{FONT_FAMILY_PRO}};
23
+ --font-family-mono: {{FONT_FAMILY_MONO}};
26
24
  --font-family-icon: 'Material Symbols Rounded';
27
25
 
28
26
  /* フォントサイズ */