sparkle-design-cli 1.1.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/LICENSE +21 -0
- package/README.md +135 -0
- package/bin/sparkle-design.js +97 -0
- package/lib/generate-css.js +367 -0
- package/package.json +39 -0
- package/templates/sparkle-variables/colors.json +89 -0
- package/templates/sparkle-variables/fontdata.csv +12 -0
- package/templates/sparkle-variables/gray.json +86 -0
- package/templates/sparkle-variables/radius.csv +9 -0
- package/templates/sparkle-variables/sparkle-design.template.css +709 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Goodpatch Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Sparkle Design CLI
|
|
2
|
+
|
|
3
|
+
デザインシステム CSS を設定ファイルから生成する CLI ツールです。
|
|
4
|
+
|
|
5
|
+
## インストール
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g sparkle-design-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 使用方法
|
|
12
|
+
|
|
13
|
+
### 基本的な使用方法
|
|
14
|
+
|
|
15
|
+
1. プロジェクトのルートディレクトリに `sparkle.config.json` ファイルを作成または配置します:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"primary": "blue",
|
|
20
|
+
"font-mono": "BIZ UDGothic",
|
|
21
|
+
"font-pro": "BIZ UDPGothic",
|
|
22
|
+
"radius": "md"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
2. CLI ツールを実行します:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx sparkle-design-cli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
または、グローバルにインストールした場合:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
sparkle-design-cli
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. `src/app/sparkle-design.css` に CSS ファイルが生成されます。
|
|
39
|
+
|
|
40
|
+
### コマンドオプション
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# ヘルプを表示
|
|
44
|
+
sparkle-design-cli --help
|
|
45
|
+
|
|
46
|
+
# カスタム設定ファイルを指定
|
|
47
|
+
sparkle-design-cli --config ./config/design.json
|
|
48
|
+
|
|
49
|
+
# カスタム出力先を指定
|
|
50
|
+
sparkle-design-cli --output ./styles/design.css
|
|
51
|
+
|
|
52
|
+
# 設定ファイルと出力先を両方指定
|
|
53
|
+
sparkle-design-cli -c ./config/custom.json -o ./dist/styles.css
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### オプション一覧
|
|
57
|
+
|
|
58
|
+
- `-h, --help`: ヘルプメッセージを表示
|
|
59
|
+
- `-c, --config <パス>`: 設定ファイルのパス(デフォルト: `./sparkle.config.json`)
|
|
60
|
+
- `-o, --output <パス>`: 出力ファイルのパス(デフォルト: `./src/app/sparkle-design.css`)
|
|
61
|
+
|
|
62
|
+
## 設定ファイル (sparkle.config.json)
|
|
63
|
+
|
|
64
|
+
### 設定ファイルの作成
|
|
65
|
+
|
|
66
|
+
設定ファイルは**専用のプラグインから自動生成**することを推奨します。プラグインを使用すると、デザインシステムに基づいた適切な設定が自動で生成されます。
|
|
67
|
+
|
|
68
|
+
設定ファイルは以下の場所に配置してください:
|
|
69
|
+
|
|
70
|
+
- プロジェクトのルートディレクトリ(推奨)
|
|
71
|
+
- または `-c` オプションで指定した任意の場所
|
|
72
|
+
|
|
73
|
+
### 設定オプション
|
|
74
|
+
|
|
75
|
+
- `primary`: プライマリカラー(blue, red, orange など)
|
|
76
|
+
- `font-pro`: プロポーショナルフォント(Google Fonts の名前)
|
|
77
|
+
- `font-mono`: モノスペースフォント(Google Fonts の名前)
|
|
78
|
+
- `radius`: 角丸設定(sm, md, lg など)
|
|
79
|
+
|
|
80
|
+
## 出力
|
|
81
|
+
|
|
82
|
+
- デフォルト出力先: `src/app/sparkle-design.css`
|
|
83
|
+
- カスタム出力先: `-o` オプションで指定可能
|
|
84
|
+
- 実行場所を基準として相対パスで処理されます
|
|
85
|
+
|
|
86
|
+
## 開発
|
|
87
|
+
|
|
88
|
+
### セットアップ
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 依存関係をインストール
|
|
92
|
+
npm install
|
|
93
|
+
|
|
94
|
+
# パッケージをローカルでリンク
|
|
95
|
+
npm link
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 開発用コマンド
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# テスト実行
|
|
102
|
+
npm test
|
|
103
|
+
|
|
104
|
+
# テスト監視モード
|
|
105
|
+
npm run test:watch
|
|
106
|
+
|
|
107
|
+
# Lint実行
|
|
108
|
+
npm run lint
|
|
109
|
+
|
|
110
|
+
# Lintエラーを自動修正
|
|
111
|
+
npm run lint:fix
|
|
112
|
+
|
|
113
|
+
# コードフォーマット
|
|
114
|
+
npm run format
|
|
115
|
+
|
|
116
|
+
# フォーマットチェック
|
|
117
|
+
npm run format:check
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### CLI実行テスト
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# デフォルト設定でテスト
|
|
124
|
+
sparkle-design-cli
|
|
125
|
+
|
|
126
|
+
# ヘルプ表示
|
|
127
|
+
sparkle-design-cli --help
|
|
128
|
+
|
|
129
|
+
# カスタムパスでテスト
|
|
130
|
+
sparkle-design-cli -c test-config.json -o test-output.css
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## ライセンス
|
|
134
|
+
|
|
135
|
+
MIT License - 詳細は [LICENSE](LICENSE) ファイルを参照してください。
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { generateCSS } from '../lib/generate-css.js';
|
|
4
|
+
|
|
5
|
+
// コマンドライン引数を解析
|
|
6
|
+
function parseArguments() {
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const options = {
|
|
9
|
+
configPath: null,
|
|
10
|
+
outputPath: null,
|
|
11
|
+
help: false,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
|
|
17
|
+
if (arg === '-h' || arg === '--help') {
|
|
18
|
+
options.help = true;
|
|
19
|
+
} else if (arg === '-c' || arg === '--config') {
|
|
20
|
+
options.configPath = args[i + 1];
|
|
21
|
+
i++; // 次の引数をスキップ
|
|
22
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
23
|
+
options.outputPath = args[i + 1];
|
|
24
|
+
i++; // 次の引数をスキップ
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return options;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ヘルプメッセージを表示
|
|
32
|
+
function showHelp() {
|
|
33
|
+
console.log(`
|
|
34
|
+
Sparkle Design CLI - デザインシステム CSS 生成ツール
|
|
35
|
+
|
|
36
|
+
使用方法:
|
|
37
|
+
sparkle-design-cli [オプション]
|
|
38
|
+
|
|
39
|
+
基本的な使用方法:
|
|
40
|
+
1. プロジェクトのルートディレクトリに sparkle.config.json ファイルを作成または配置
|
|
41
|
+
2. CLI ツールを実行: sparkle-design-cli
|
|
42
|
+
3. src/app/sparkle-design.css に CSS ファイルが生成されます
|
|
43
|
+
|
|
44
|
+
オプション:
|
|
45
|
+
-h, --help このヘルプメッセージを表示
|
|
46
|
+
-c, --config <パス> 設定ファイルのパス (デフォルト: ./sparkle.config.json)
|
|
47
|
+
-o, --output <パス> 出力ファイルのパス (デフォルト: ./src/app/sparkle-design.css)
|
|
48
|
+
|
|
49
|
+
使用例:
|
|
50
|
+
sparkle-design-cli
|
|
51
|
+
sparkle-design-cli --config ./config/design.json
|
|
52
|
+
sparkle-design-cli --output ./styles/design.css
|
|
53
|
+
sparkle-design-cli -c ./config/custom.json -o ./dist/styles.css
|
|
54
|
+
|
|
55
|
+
設定ファイル (sparkle.config.json):
|
|
56
|
+
設定ファイルは専用のプラグインから自動生成することを推奨します。
|
|
57
|
+
プロジェクトのルートディレクトリに配置してください。
|
|
58
|
+
|
|
59
|
+
設定例:
|
|
60
|
+
{
|
|
61
|
+
"primary": "blue",
|
|
62
|
+
"font-pro": "BIZ UDPGothic",
|
|
63
|
+
"font-mono": "BIZ UDGothic",
|
|
64
|
+
"radius": "md"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
設定オプション:
|
|
68
|
+
- primary: プライマリカラー (blue, red, orange など)
|
|
69
|
+
- font-pro: プロポーショナルフォント (Google Fonts の名前)
|
|
70
|
+
- font-mono: モノスペースフォント (Google Fonts の名前)
|
|
71
|
+
- radius: 角丸設定 (sm, md, lg など)
|
|
72
|
+
|
|
73
|
+
出力:
|
|
74
|
+
- デフォルト出力先: src/app/sparkle-design.css
|
|
75
|
+
- カスタム出力先: -o オプションで指定可能
|
|
76
|
+
- 実行場所を基準として相対パスで処理されます
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// メイン処理
|
|
81
|
+
function main() {
|
|
82
|
+
const options = parseArguments();
|
|
83
|
+
|
|
84
|
+
if (options.help) {
|
|
85
|
+
showHelp();
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
generateCSS(options.configPath, options.outputPath);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('❌ エラーが発生しました:', error.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main();
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* sparkle.config.jsonを読み込む
|
|
12
|
+
*/
|
|
13
|
+
function loadConfig(configPath = null) {
|
|
14
|
+
// カスタムパスが指定されていない場合は実行場所のsparkle.config.jsonを探す
|
|
15
|
+
const defaultConfigPath = path.resolve(process.cwd(), 'sparkle.config.json');
|
|
16
|
+
const resolvedConfigPath = configPath ? path.resolve(configPath) : defaultConfigPath;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const configContent = fs.readFileSync(resolvedConfigPath, 'utf8');
|
|
20
|
+
const config = JSON.parse(configContent);
|
|
21
|
+
console.log('✅ sparkle.config.json を読み込みました:', resolvedConfigPath);
|
|
22
|
+
console.log(' 設定内容:', config);
|
|
23
|
+
return config;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('❌ sparkle.config.json の読み込みに失敗しました:', error.message);
|
|
26
|
+
console.error('設定ファイルパス:', resolvedConfigPath);
|
|
27
|
+
console.error('設定ファイルは専用プラグインから生成するか、手動で作成してください');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* CSSテンプレートを読み込む
|
|
34
|
+
*/
|
|
35
|
+
function loadTemplate() {
|
|
36
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'sparkle-variables', 'sparkle-design.template.css');
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
40
|
+
console.log('✅ CSSテンプレートを読み込みました');
|
|
41
|
+
return templateContent;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('❌ CSSテンプレートの読み込みに失敗しました:', error.message);
|
|
44
|
+
console.error('テンプレートファイルが存在することを確認してください:', templatePath);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 色定義(colors.json)を読み込む
|
|
51
|
+
*/
|
|
52
|
+
function loadColors() {
|
|
53
|
+
const colorsPath = path.join(__dirname, '..', 'templates', 'sparkle-variables', 'colors.json');
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const colorsContent = fs.readFileSync(colorsPath, 'utf8');
|
|
57
|
+
const colors = JSON.parse(colorsContent);
|
|
58
|
+
console.log('✅ colors.json を読み込みました');
|
|
59
|
+
return colors;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('❌ colors.json の読み込みに失敗しました:', error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* gray.jsonを読み込む
|
|
68
|
+
*/
|
|
69
|
+
function loadGrayMapping() {
|
|
70
|
+
const grayJsonPath = path.join(__dirname, '..', 'templates', 'sparkle-variables', 'gray.json');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const grayContent = fs.readFileSync(grayJsonPath, 'utf8');
|
|
74
|
+
const grayMapping = JSON.parse(grayContent);
|
|
75
|
+
console.log('✅ gray.json を読み込みました');
|
|
76
|
+
return grayMapping;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('❌ gray.json の読み込みに失敗しました:', error.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* radius.csvを読み込む
|
|
85
|
+
*/
|
|
86
|
+
function loadRadiusMapping() {
|
|
87
|
+
const radiusCsvPath = path.join(__dirname, '..', 'templates', 'sparkle-variables', 'radius.csv');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const csvContent = fs.readFileSync(radiusCsvPath, 'utf8');
|
|
91
|
+
const lines = csvContent.trim().split('\n');
|
|
92
|
+
const headers = lines[0].split(',');
|
|
93
|
+
const radiusMapping = {};
|
|
94
|
+
|
|
95
|
+
for (let i = 1; i < lines.length; i++) {
|
|
96
|
+
const values = lines[i].split(',');
|
|
97
|
+
const name = values[0];
|
|
98
|
+
radiusMapping[name] = {};
|
|
99
|
+
|
|
100
|
+
for (let j = 1; j < headers.length; j++) {
|
|
101
|
+
radiusMapping[name][headers[j]] = values[j];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('✅ radius.csv を読み込みました');
|
|
106
|
+
return radiusMapping;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('❌ radius.csv の読み込みに失敗しました:', error.message);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* テンプレート変数を設定値で置換する
|
|
115
|
+
*/
|
|
116
|
+
function processTemplate(template, config, grayMapping, radiusMapping, colors) {
|
|
117
|
+
let processedCSS = template;
|
|
118
|
+
|
|
119
|
+
// 1. 色のトークンを生成して置換
|
|
120
|
+
const colorTokens = generateColorTokens(colors, grayMapping, config.primary);
|
|
121
|
+
// プレースホルダーを色のトークンで置換(改行を含む)
|
|
122
|
+
processedCSS = processedCSS.replace(/\s*\/\* \{\{COLOR_TOKENS\}\} \*\/\s*/, `\n${colorTokens}\n`);
|
|
123
|
+
|
|
124
|
+
// 2. 基本的な設定値による置換
|
|
125
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
126
|
+
// 通常のプレースホルダー(CSS用 - スペースはそのまま)
|
|
127
|
+
const placeholder = `{{${key.toUpperCase().replace('-', '_')}}}`;
|
|
128
|
+
processedCSS = processedCSS.replace(new RegExp(placeholder, 'g'), value);
|
|
129
|
+
|
|
130
|
+
// フォントファミリーの場合はURL用のプレースホルダーも生成(スペースを+に置き換え)
|
|
131
|
+
if (key.toLowerCase().includes('font')) {
|
|
132
|
+
const urlPlaceholder = `{{${key.toUpperCase().replace('-', '_')}_URL}}`;
|
|
133
|
+
const urlValue = value.replace(/\s+/g, '+');
|
|
134
|
+
processedCSS = processedCSS.replace(new RegExp(urlPlaceholder, 'g'), urlValue);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 3. Radiusの一括置換
|
|
139
|
+
if (config.radius && radiusMapping[config.radius]) {
|
|
140
|
+
const radiusValues = radiusMapping[config.radius];
|
|
141
|
+
|
|
142
|
+
Object.entries(radiusValues).forEach(([semanticName, radiusValue]) => {
|
|
143
|
+
if (semanticName === 'round') return; // roundは特別扱い
|
|
144
|
+
|
|
145
|
+
const semanticPattern = new RegExp(
|
|
146
|
+
`--radius-${semanticName}:\\s*var\\(--radius-[^)]+\\)`,
|
|
147
|
+
'g'
|
|
148
|
+
);
|
|
149
|
+
processedCSS = processedCSS.replace(
|
|
150
|
+
semanticPattern,
|
|
151
|
+
`--radius-${semanticName}: var(--radius-${radiusValue})`
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
console.log(`✅ ${config.radius}に対応するradius設定を適用しました`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('✅ テンプレートを設定値で処理しました');
|
|
159
|
+
return processedCSS;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 16進数カラーをrgba形式に変換
|
|
164
|
+
*/
|
|
165
|
+
function hexToRgba(hex) {
|
|
166
|
+
// #を削除
|
|
167
|
+
hex = hex.replace('#', '');
|
|
168
|
+
|
|
169
|
+
// 3桁の場合は6桁に拡張
|
|
170
|
+
if (hex.length === 3) {
|
|
171
|
+
hex = hex
|
|
172
|
+
.split('')
|
|
173
|
+
.map((char) => char + char)
|
|
174
|
+
.join('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
178
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
179
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
180
|
+
|
|
181
|
+
return `rgba(${r}, ${g}, ${b}, 1)`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 16進数カラーをOKLCH形式に変換
|
|
186
|
+
* @param {string} hex 16進数のカラーコード(#付きまたは無し)
|
|
187
|
+
* @returns {string} OKLCH形式の文字列
|
|
188
|
+
*/
|
|
189
|
+
function hexToOklch(hex) {
|
|
190
|
+
// #を削除
|
|
191
|
+
hex = hex.replace('#', '');
|
|
192
|
+
|
|
193
|
+
// 3桁の場合は6桁に拡張
|
|
194
|
+
if (hex.length === 3) {
|
|
195
|
+
hex = hex
|
|
196
|
+
.split('')
|
|
197
|
+
.map((char) => char + char)
|
|
198
|
+
.join('');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
202
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
203
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
204
|
+
|
|
205
|
+
// sRGBからLinear RGBに変換
|
|
206
|
+
function srgbToLinear(c) {
|
|
207
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const linearR = srgbToLinear(r);
|
|
211
|
+
const linearG = srgbToLinear(g);
|
|
212
|
+
const linearB = srgbToLinear(b);
|
|
213
|
+
|
|
214
|
+
// Linear RGBからXYZ(D65)への変換
|
|
215
|
+
const x = linearR * 0.4124564 + linearG * 0.3575761 + linearB * 0.1804375;
|
|
216
|
+
const y = linearR * 0.2126729 + linearG * 0.7151522 + linearB * 0.0721750;
|
|
217
|
+
const z = linearR * 0.0193339 + linearG * 0.1191920 + linearB * 0.9503041;
|
|
218
|
+
|
|
219
|
+
// XYZからOKLabへの変換
|
|
220
|
+
function f(t) {
|
|
221
|
+
const c1 = 0.8359375; // 3^3/29^3
|
|
222
|
+
const c2 = 0.2109375; // 1/3^3
|
|
223
|
+
const c3 = 29 / (6 ** 3); // 29/6^3
|
|
224
|
+
|
|
225
|
+
return t > c1 ? Math.cbrt(t) : c2 * t + c3;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const l = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
|
|
229
|
+
const m = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
|
|
230
|
+
const s = 0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z;
|
|
231
|
+
|
|
232
|
+
const fL = f(l);
|
|
233
|
+
const fM = f(m);
|
|
234
|
+
const fS = f(s);
|
|
235
|
+
|
|
236
|
+
const L = 0.2104542553 * fL + 0.7936177850 * fM - 0.0040720468 * fS;
|
|
237
|
+
const a = 1.9779984951 * fL - 2.4285922050 * fM + 0.4505937099 * fS;
|
|
238
|
+
const b_lab = 0.0259040371 * fL + 0.7827717662 * fM - 0.8086757660 * fS;
|
|
239
|
+
|
|
240
|
+
// OKLabからOKLCHへの変換
|
|
241
|
+
const lightness = L;
|
|
242
|
+
const chroma = Math.sqrt(a * a + b_lab * b_lab);
|
|
243
|
+
let hue = Math.atan2(b_lab, a) * 180 / Math.PI;
|
|
244
|
+
if (hue < 0) hue += 360;
|
|
245
|
+
|
|
246
|
+
// 精度を調整して出力(少数点以下4桁で丸める)
|
|
247
|
+
const l_rounded = Math.round(lightness * 10000) / 10000;
|
|
248
|
+
const c_rounded = Math.round(chroma * 10000) / 10000;
|
|
249
|
+
const h_rounded = chroma < 0.0001 ? 0 : Math.round(hue * 10) / 10;
|
|
250
|
+
|
|
251
|
+
return `oklch(${l_rounded} ${c_rounded} ${h_rounded})`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 色のトークンを生成する
|
|
256
|
+
* @param {Object} colors colors.jsonから読み込んだ色の定義
|
|
257
|
+
* @param {Object} grayMapping gray.jsonから読み込んだグレーの定義
|
|
258
|
+
* @param {string} primaryColor プライマリカラーの名前
|
|
259
|
+
* @returns {string} 色のトークンのCSS文字列
|
|
260
|
+
*/
|
|
261
|
+
function generateColorTokens(colors, grayMapping, primaryColor) {
|
|
262
|
+
const colorTokens = [];
|
|
263
|
+
|
|
264
|
+
// 通常の色トークンを生成
|
|
265
|
+
Object.entries(colors).forEach(([colorName, colorValues]) => {
|
|
266
|
+
if (typeof colorValues === 'object' && colorValues !== null) {
|
|
267
|
+
Object.entries(colorValues).forEach(([level, hexValue]) => {
|
|
268
|
+
const oklchValue = hexToOklch(hexValue);
|
|
269
|
+
colorTokens.push(` --color-${colorName}-${level}: ${oklchValue};`);
|
|
270
|
+
});
|
|
271
|
+
} else if (typeof colorValues === 'string') {
|
|
272
|
+
// black, white, web-focusなどの単色
|
|
273
|
+
const oklchValue = hexToOklch(colorValues);
|
|
274
|
+
colorTokens.push(` --color-${colorName}: ${oklchValue};`);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// グレーの色トークンを生成(プライマリカラーに基づく)
|
|
279
|
+
if (primaryColor && grayMapping[primaryColor]) {
|
|
280
|
+
const grayColors = grayMapping[primaryColor];
|
|
281
|
+
Object.entries(grayColors).forEach(([key, hexValue]) => {
|
|
282
|
+
const grayLevel = key === '0' ? '50'
|
|
283
|
+
: key === '1' ? '100'
|
|
284
|
+
: key === '2' ? '200'
|
|
285
|
+
: key === '3' ? '300'
|
|
286
|
+
: key === '4' ? '400'
|
|
287
|
+
: key === '5' ? '500'
|
|
288
|
+
: key === '6' ? '600'
|
|
289
|
+
: key === '7' ? '700'
|
|
290
|
+
: key === '8' ? '800'
|
|
291
|
+
: key === '9' ? '900'
|
|
292
|
+
: key;
|
|
293
|
+
|
|
294
|
+
const oklchValue = hexToOklch(hexValue);
|
|
295
|
+
colorTokens.push(` --color-gray-${grayLevel}: ${oklchValue};`);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return colorTokens.join('\n');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 生成されたCSSをファイルに書き出す
|
|
304
|
+
*/
|
|
305
|
+
function writeCSS(cssContent, outputPath = null) {
|
|
306
|
+
// カスタムパスが指定されていない場合は実行場所からsrc/appディレクトリに出力
|
|
307
|
+
const defaultOutputPath = path.resolve(process.cwd(), 'src', 'app', 'sparkle-design.css');
|
|
308
|
+
const resolvedOutputPath = outputPath ? path.resolve(outputPath) : defaultOutputPath;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
// 出力ディレクトリが存在しない場合は作成
|
|
312
|
+
const outputDir = path.dirname(resolvedOutputPath);
|
|
313
|
+
if (!fs.existsSync(outputDir)) {
|
|
314
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
fs.writeFileSync(resolvedOutputPath, cssContent, 'utf8');
|
|
318
|
+
console.log('✅ sparkle-design.css を生成しました:', resolvedOutputPath);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('❌ CSSファイルの書き出しに失敗しました:', error.message);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* メイン処理
|
|
327
|
+
*/
|
|
328
|
+
export function generateCSS(configPath = null, outputPath = null) {
|
|
329
|
+
console.log('🚀 Sparkle Design CSS Generator を開始します...\n');
|
|
330
|
+
|
|
331
|
+
// 1. 設定ファイルを読み込み
|
|
332
|
+
const config = loadConfig(configPath);
|
|
333
|
+
|
|
334
|
+
// 2. テンプレートを読み込み
|
|
335
|
+
const template = loadTemplate();
|
|
336
|
+
|
|
337
|
+
// 3. colors.json、gray.json、radius.csvを読み込み
|
|
338
|
+
const colors = loadColors();
|
|
339
|
+
const grayMapping = loadGrayMapping();
|
|
340
|
+
const radiusMapping = loadRadiusMapping();
|
|
341
|
+
|
|
342
|
+
// 4. テンプレートを設定値で処理
|
|
343
|
+
const processedCSS = processTemplate(template, config, grayMapping, radiusMapping, colors);
|
|
344
|
+
|
|
345
|
+
// 5. CSSファイルを書き出し
|
|
346
|
+
writeCSS(processedCSS, outputPath);
|
|
347
|
+
|
|
348
|
+
console.log('\n🎉 CSS生成が完了しました!');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// スクリプトが直接実行された場合のみメイン処理を実行
|
|
352
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
353
|
+
generateCSS();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export {
|
|
357
|
+
loadConfig,
|
|
358
|
+
loadTemplate,
|
|
359
|
+
loadColors,
|
|
360
|
+
loadGrayMapping,
|
|
361
|
+
loadRadiusMapping,
|
|
362
|
+
processTemplate,
|
|
363
|
+
writeCSS,
|
|
364
|
+
hexToRgba,
|
|
365
|
+
hexToOklch,
|
|
366
|
+
generateColorTokens,
|
|
367
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sparkle-design-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Sparkle Design CSS Generator - デザインシステムCSSを設定ファイルから生成するツール",
|
|
5
|
+
"main": "lib/generate-css.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"sparkle-design-cli": "./bin/sparkle-design.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/*.test.js",
|
|
12
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
13
|
+
"lint": "eslint bin/ lib/ test/",
|
|
14
|
+
"lint:fix": "eslint bin/ lib/ test/ --fix",
|
|
15
|
+
"format": "prettier --write bin/ lib/ test/ *.md *.json",
|
|
16
|
+
"format:check": "prettier --check bin/ lib/ test/ *.md *.json"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"css",
|
|
20
|
+
"design-system",
|
|
21
|
+
"css-generator",
|
|
22
|
+
"theming"
|
|
23
|
+
],
|
|
24
|
+
"author": "Goodpatch Inc. <sparkle-design@goodpatch.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"power-assert": "^1.6.1",
|
|
28
|
+
"eslint": "^8.57.0",
|
|
29
|
+
"prettier": "^3.2.5",
|
|
30
|
+
"@eslint/js": "^8.57.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin/",
|
|
34
|
+
"lib/",
|
|
35
|
+
"templates/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
]
|
|
39
|
+
}
|