sparkle-design-cli 1.3.10 → 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 +65 -24
- package/lib/constants.js +20 -0
- package/lib/font-manager.js +53 -10
- package/lib/generate-css.js +119 -4
- package/lib/setup.js +15 -3
- package/package.json +1 -1
- package/templates/sparkle-variables/sparkle-design.template.css +3 -5
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npx sparkle-design-cli check src --strict
|
|
|
23
23
|
npx sparkle-design-cli check src --format json
|
|
24
24
|
|
|
25
25
|
# Claude / Codex / Cursor 向けの guard 設定を差し込む
|
|
26
|
-
npx sparkle-design-cli setup --assistant claude
|
|
26
|
+
npx --yes sparkle-design-cli setup --assistant claude
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### 後方互換モード
|
|
@@ -125,7 +125,7 @@ sparkle-design-cli check --help
|
|
|
125
125
|
```json
|
|
126
126
|
{
|
|
127
127
|
"scripts": {
|
|
128
|
-
"lint:sparkle": "sparkle-design-cli check src --strict"
|
|
128
|
+
"lint:sparkle": "npx --yes sparkle-design-cli check src --strict"
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
```
|
|
@@ -138,16 +138,16 @@ AI エージェントや CI からこの `lint:sparkle` を呼ぶ運用にする
|
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
# Claude Code 向け
|
|
141
|
-
sparkle-design-cli setup --assistant claude
|
|
141
|
+
npx --yes sparkle-design-cli setup --assistant claude
|
|
142
142
|
|
|
143
143
|
# Codex / AGENTS.md 向け
|
|
144
|
-
sparkle-design-cli setup --assistant codex
|
|
144
|
+
npx --yes sparkle-design-cli setup --assistant codex
|
|
145
145
|
|
|
146
146
|
# Cursor rules 向け
|
|
147
|
-
sparkle-design-cli setup --assistant cursor
|
|
147
|
+
npx --yes sparkle-design-cli setup --assistant cursor
|
|
148
148
|
|
|
149
149
|
# generic な Markdown を生成
|
|
150
|
-
sparkle-design-cli setup --assistant generic
|
|
150
|
+
npx --yes sparkle-design-cli setup --assistant generic
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
追加されるもの:
|
|
@@ -178,45 +178,86 @@ sparkle-design-cli setup --assistant generic
|
|
|
178
178
|
|
|
179
179
|
### 設定オプション
|
|
180
180
|
|
|
181
|
+
#### Core(プラグインが出力)
|
|
182
|
+
|
|
181
183
|
- `primary`: プライマリカラー(blue, red, orange など)
|
|
182
|
-
- `font-pro`:
|
|
183
|
-
- `font-mono`:
|
|
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
|
-
|
|
194
|
+
#### Custom Settings
|
|
188
195
|
|
|
189
|
-
|
|
196
|
+
- `custom-css`: (オプション)プロジェクト固有のカスタムトークン CSS ファイルのパス。`globals.css` に `@import` が自動挿入される
|
|
190
197
|
|
|
191
|
-
|
|
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-
|
|
197
|
-
"font-pro":
|
|
198
|
-
"
|
|
199
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
234
|
+
"custom-css": "./src/app/custom-tokens.css"
|
|
208
235
|
}
|
|
209
236
|
```
|
|
210
237
|
|
|
211
|
-
|
|
238
|
+
CLI が `globals.css` に `@import` を自動挿入します。CLI はファイルの中身を一切触りません。
|
|
212
239
|
|
|
213
240
|
```css
|
|
214
|
-
|
|
215
|
-
/*
|
|
216
|
-
@
|
|
217
|
-
@
|
|
218
|
-
/* Sparkle Design
|
|
219
|
-
@import
|
|
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
|
// インポート文のテンプレート
|
package/lib/font-manager.js
CHANGED
|
@@ -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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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);
|
package/lib/generate-css.js
CHANGED
|
@@ -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,
|
|
@@ -23,6 +23,96 @@ import {
|
|
|
23
23
|
updateGlobalsWithFonts,
|
|
24
24
|
} from './font-manager.js';
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* 同一のフォント @import を重複排除する
|
|
28
|
+
* @param {string} cssContent CSS内容
|
|
29
|
+
* @returns {string} 重複排除後のCSS
|
|
30
|
+
*/
|
|
31
|
+
function dedupeFontImports(cssContent) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
|
|
34
|
+
return cssContent
|
|
35
|
+
.split('\n')
|
|
36
|
+
.filter(line => {
|
|
37
|
+
if (!line.includes('fonts.googleapis.com')) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (seen.has(line)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
seen.add(line);
|
|
46
|
+
return true;
|
|
47
|
+
})
|
|
48
|
+
.join('\n');
|
|
49
|
+
}
|
|
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
|
+
|
|
26
116
|
/**
|
|
27
117
|
* テンプレート変数を設定値で置換する
|
|
28
118
|
* @param {string} template CSSテンプレート
|
|
@@ -40,8 +130,24 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
|
|
|
40
130
|
// プレースホルダーを色のトークンで置換(改行を含む)
|
|
41
131
|
processedCSS = processedCSS.replace(REGEX.COLOR_TOKENS_PLACEHOLDER, `${colorTokens}\n`);
|
|
42
132
|
|
|
43
|
-
// 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 の配列以外)
|
|
44
146
|
Object.entries(config).forEach(([key, value]) => {
|
|
147
|
+
// 配列や font-*-weights はスキップ
|
|
148
|
+
if (Array.isArray(value) || key.endsWith('-weights') || key === 'custom-css') {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
45
151
|
// 通常のプレースホルダー(CSS用 - スペースはそのまま)
|
|
46
152
|
const placeholder = `{{${key.toUpperCase().replace('-', '_')}}}`;
|
|
47
153
|
processedCSS = processedCSS.replace(new RegExp(placeholder, 'g'), value);
|
|
@@ -54,7 +160,7 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
|
|
|
54
160
|
}
|
|
55
161
|
});
|
|
56
162
|
|
|
57
|
-
//
|
|
163
|
+
// 6. Radiusの一括置換
|
|
58
164
|
if (config.radius && radiusMapping[config.radius]) {
|
|
59
165
|
const radiusValues = radiusMapping[config.radius];
|
|
60
166
|
|
|
@@ -71,6 +177,8 @@ function processTemplate(template, config, grayMapping, radiusMapping, colors) {
|
|
|
71
177
|
console.log(MESSAGES.RADIUS_APPLIED(config.radius));
|
|
72
178
|
}
|
|
73
179
|
|
|
180
|
+
processedCSS = dedupeFontImports(processedCSS);
|
|
181
|
+
|
|
74
182
|
console.log(MESSAGES.TEMPLATE_PROCESSED);
|
|
75
183
|
return processedCSS;
|
|
76
184
|
}
|
|
@@ -138,7 +246,8 @@ export function generateCSS(configPath = null, outputPath = null) {
|
|
|
138
246
|
// 6. フォント管理の自動処理を実行
|
|
139
247
|
console.log(MESSAGES.FONT_MANAGEMENT_START);
|
|
140
248
|
const sourcePackages = 'source-packages' in config ? (config['source-packages'] || []) : null;
|
|
141
|
-
|
|
249
|
+
const customCssPath = config['custom-css'] || null;
|
|
250
|
+
manageFontImports(resolvedOutputPath, sourcePackages, customCssPath);
|
|
142
251
|
|
|
143
252
|
console.log(MESSAGES.SUCCESS);
|
|
144
253
|
}
|
|
@@ -163,6 +272,12 @@ export {
|
|
|
163
272
|
hexToRgba,
|
|
164
273
|
hexToOklch,
|
|
165
274
|
generateColorTokens,
|
|
275
|
+
dedupeFontImports,
|
|
276
|
+
// フォント処理
|
|
277
|
+
normalizeFontConfig,
|
|
278
|
+
generateGoogleFontImports,
|
|
279
|
+
generateFontFamilyValue,
|
|
280
|
+
generateFontImportsBlock,
|
|
166
281
|
// フォント管理
|
|
167
282
|
extractFontImports,
|
|
168
283
|
removeFontImportsFromCSS,
|
package/lib/setup.js
CHANGED
|
@@ -21,6 +21,11 @@ const BLOCK_END = '<!-- sparkle-design-cli:setup:end -->';
|
|
|
21
21
|
const TARGET_CANDIDATES = ['src', 'src/components', 'src/features', 'app', 'components'];
|
|
22
22
|
const UNSAFE_TARGET_PATTERN = /[\s"'`;$&|<>()[\]{}\\]/;
|
|
23
23
|
|
|
24
|
+
function buildCheckScript(target, mode) {
|
|
25
|
+
const trailingArgs = mode === 'strict' ? '--strict' : '--format json';
|
|
26
|
+
return `npx --yes sparkle-design-cli check ${target} ${trailingArgs}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
|
|
25
30
|
function escapeRegExp(value) {
|
|
26
31
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -91,11 +96,18 @@ function isManagedSparkleScript(value, mode) {
|
|
|
91
96
|
return false;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
let tokens = value.trim().split(/\s+/);
|
|
95
100
|
if (tokens.length < 3) {
|
|
96
101
|
return false;
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
if (tokens[0] === 'npx') {
|
|
105
|
+
tokens = tokens.slice(1);
|
|
106
|
+
if (tokens[0] === '--yes') {
|
|
107
|
+
tokens = tokens.slice(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
99
111
|
if (tokens[0] !== 'sparkle-design-cli' || tokens[1] !== 'check') {
|
|
100
112
|
return false;
|
|
101
113
|
}
|
|
@@ -169,13 +181,13 @@ function updatePackageJson(filePath, target, force) {
|
|
|
169
181
|
|
|
170
182
|
const strictResult = resolveScriptUpdate(
|
|
171
183
|
scripts['lint:sparkle'],
|
|
172
|
-
|
|
184
|
+
buildCheckScript(target, 'strict'),
|
|
173
185
|
'strict',
|
|
174
186
|
force
|
|
175
187
|
);
|
|
176
188
|
const jsonResult = resolveScriptUpdate(
|
|
177
189
|
scripts['lint:sparkle:json'],
|
|
178
|
-
|
|
190
|
+
buildCheckScript(target, 'json'),
|
|
179
191
|
'json',
|
|
180
192
|
force
|
|
181
193
|
);
|
package/package.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/* フォントのインポート */
|
|
2
|
-
|
|
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:
|
|
25
|
-
--font-family-mono:
|
|
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
|
/* フォントサイズ */
|