@mizumi25/cli 0.1.0 → 0.1.2
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/docs-generator.js +1302 -536
- package/index.js +866 -325
- package/package.json +1 -1
- package/watcher.js +153 -40
package/index.js
CHANGED
|
@@ -1,416 +1,957 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// packages/cli/index.js
|
|
3
3
|
|
|
4
|
-
import { watch }
|
|
5
|
-
import { DocsGenerator } from './docs-generator.js'
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import Mizumi
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
import { watch } from './watcher.js'
|
|
5
|
+
import { DocsGenerator } from './docs-generator.js'
|
|
6
|
+
import { MizuParser, loadMizuFiles, mergeMizuConfigs } from '../core/mizu-parser.js'
|
|
7
|
+
import { Validator } from '../core/validator.js'
|
|
8
|
+
import Mizumi from '../core/index.js'
|
|
9
|
+
import { generateDevToolsScript } from '../vite-plugin/devtools.js'
|
|
10
|
+
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
14
|
+
import { resolveClass } from '../core/class-resolver.js'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2)
|
|
18
|
+
const command = args[0]
|
|
19
|
+
|
|
20
|
+
// ── Load config — supports mizumi.config.js AND .mizu files ──
|
|
21
|
+
async function loadConfig(configFile = 'mizumi.config.js') {
|
|
22
|
+
const cwd = process.cwd()
|
|
23
|
+
const configPath = path.resolve(cwd, configFile)
|
|
24
|
+
let config = {}
|
|
25
|
+
|
|
26
|
+
// Load JS config if exists
|
|
27
|
+
if (fs.existsSync(configPath)) {
|
|
28
|
+
const mod = await import(pathToFileURL(configPath).href + `?t=${Date.now()}`)
|
|
29
|
+
config = mod.default || mod
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Find and merge all .mizu files in src/
|
|
33
|
+
const srcDir = path.resolve(cwd, 'src')
|
|
34
|
+
const rootDir = cwd
|
|
35
|
+
const mizuDirs = [rootDir, srcDir].filter(fs.existsSync)
|
|
36
|
+
|
|
37
|
+
for (const dir of mizuDirs) {
|
|
38
|
+
const mizuConfig = loadMizuFiles(dir)
|
|
39
|
+
if (Object.keys(mizuConfig.tokens).length > 0 ||
|
|
40
|
+
Object.keys(mizuConfig.patterns).length > 0) {
|
|
41
|
+
config = mergeMizuConfigs(config, mizuConfig)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return config
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
function escapeCSSIdent(str) {
|
|
50
|
+
return str.replace(/([^a-zA-Z0-9_\-])/g, '\\$1')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CLASS_RE = /(?:className|class)\s*=\s*(?:"([^"]*?)"|'([^']*?)'|`([^`]*?)`)/g
|
|
54
|
+
|
|
55
|
+
function extractTokens(source) {
|
|
56
|
+
const tokens = new Set()
|
|
57
|
+
for (const m of source.matchAll(CLASS_RE)) {
|
|
58
|
+
const raw = m[1] || m[2] || m[3] || ''
|
|
59
|
+
raw.split(/\s+/).filter(Boolean).forEach(t => tokens.add(t))
|
|
60
|
+
}
|
|
61
|
+
return tokens
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function walkDir(dir, exts) {
|
|
65
|
+
const results = []
|
|
66
|
+
const extSet = new Set(exts)
|
|
67
|
+
if (!fs.existsSync(dir)) return results
|
|
68
|
+
|
|
69
|
+
function walk(current) {
|
|
70
|
+
let entries
|
|
71
|
+
try { entries = fs.readdirSync(current, { withFileTypes: true }) }
|
|
72
|
+
catch { return }
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const full = path.join(current, entry.name)
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') walk(full)
|
|
77
|
+
} else if (entry.isFile()) {
|
|
78
|
+
const ext = entry.name.split('.').pop()
|
|
79
|
+
if (extSet.has(ext)) results.push(full)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
walk(dir)
|
|
84
|
+
return results
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isNonCSSClass(token) {
|
|
88
|
+
const skip = ['animate-','hover-','active-','scroll-','stagger-','focus-','duration-','delay-','ease-']
|
|
89
|
+
const variants = ['sm:','md:','lg:','xl:','2xl:','dark:','hover:','focus:','active:','disabled:','motion-safe:','motion-reduce:','print:']
|
|
90
|
+
if (skip.some(p => token.startsWith(p))) return true
|
|
91
|
+
if (variants.some(p => token.startsWith(p))) return true
|
|
92
|
+
if (!token.includes(':') && !token.includes('{')) return true
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function scanAndGenerateCSS(root) {
|
|
97
|
+
const exts = ['html', 'htm', 'jsx', 'tsx', 'js', 'ts', 'vue', 'svelte']
|
|
98
|
+
const files = walkDir(root, exts)
|
|
99
|
+
const allToks = new Set()
|
|
100
|
+
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
if (file.includes('node_modules') || file.includes('.mizumi')) continue
|
|
103
|
+
try {
|
|
104
|
+
extractTokens(fs.readFileSync(file, 'utf8')).forEach(t => allToks.add(t))
|
|
105
|
+
} catch { /* skip */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const lines = []
|
|
109
|
+
const seen = new Set()
|
|
110
|
+
|
|
111
|
+
for (const token of allToks) {
|
|
112
|
+
if (seen.has(token) || isNonCSSClass(token)) continue
|
|
113
|
+
seen.add(token)
|
|
114
|
+
const css = resolveClass(token)
|
|
115
|
+
if (css) lines.push(`.${escapeCSSIdent(token)} { ${css} }`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
console.log(` Scanned ${files.length} files, ${lines.length} arbitrary classes found`)
|
|
119
|
+
return lines.join('\n')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
16
124
|
const commands = {
|
|
17
125
|
|
|
18
|
-
|
|
19
|
-
* Initialize Mizumi in current project
|
|
20
|
-
* npx mizumi init
|
|
21
|
-
*/
|
|
126
|
+
// ── mizumi init ──
|
|
22
127
|
init() {
|
|
23
|
-
console.log('
|
|
128
|
+
console.log('💮 Initializing Mizumi...\n')
|
|
24
129
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export {
|
|
130
|
+
const jsConfig = `// mizumi.config.js
|
|
131
|
+
export default {
|
|
28
132
|
tokens: {
|
|
29
133
|
colors: {
|
|
30
|
-
primary:
|
|
31
|
-
DEFAULT: '#3B82F6',
|
|
32
|
-
50: '#EFF6FF',
|
|
33
|
-
600: '#2563EB',
|
|
34
|
-
900: '#1E3A8A'
|
|
35
|
-
},
|
|
134
|
+
primary: { DEFAULT: '#3B82F6', 50: '#EFF6FF', 600: '#2563EB', 900: '#1E3A8A' },
|
|
36
135
|
secondary: '#8B5CF6',
|
|
37
|
-
neutral:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
900: '#111827'
|
|
43
|
-
},
|
|
44
|
-
surface: '#FFFFFF',
|
|
45
|
-
text: '#111827',
|
|
46
|
-
success: '#10B981',
|
|
47
|
-
error: '#EF4444'
|
|
136
|
+
neutral: { 50: '#F9FAFB', 100: '#F3F4F6', 500: '#6B7280', 900: '#111827' },
|
|
137
|
+
surface: '#FFFFFF',
|
|
138
|
+
ink: '#111827',
|
|
139
|
+
success: '#10B981',
|
|
140
|
+
error: '#EF4444',
|
|
48
141
|
},
|
|
49
|
-
|
|
50
142
|
spacing: {
|
|
51
|
-
xs: '4px',
|
|
52
|
-
|
|
53
|
-
md: '16px',
|
|
54
|
-
lg: '24px',
|
|
55
|
-
xl: '32px',
|
|
56
|
-
'2xl': '48px',
|
|
57
|
-
'3xl': '64px'
|
|
143
|
+
xs: '4px', sm: '8px', md: '16px',
|
|
144
|
+
lg: '24px', xl: '32px', '2xl': '48px', '3xl': '64px',
|
|
58
145
|
},
|
|
59
|
-
|
|
60
146
|
typography: {
|
|
61
|
-
h1: { size: '
|
|
62
|
-
h2: { size: '
|
|
63
|
-
h3: { size: '
|
|
64
|
-
|
|
65
|
-
|
|
147
|
+
h1: { size: '3rem', weight: '700', line: '1.1' },
|
|
148
|
+
h2: { size: '2.25rem', weight: '700', line: '1.2' },
|
|
149
|
+
h3: { size: '1.875rem',weight: '600', line: '1.3' },
|
|
150
|
+
h4: { size: '1.5rem', weight: '600', line: '1.4' },
|
|
151
|
+
body: { size: '1rem', weight: '400', line: '1.6' },
|
|
152
|
+
sm: { size: '0.875rem', weight: '400', line: '1.5' },
|
|
153
|
+
xs: { size: '0.75rem', weight: '400', line: '1.4' },
|
|
154
|
+
},
|
|
155
|
+
fonts: {
|
|
156
|
+
sans: 'system-ui, sans-serif',
|
|
157
|
+
serif: 'Georgia, serif',
|
|
158
|
+
mono: 'monospace',
|
|
66
159
|
},
|
|
67
|
-
|
|
68
160
|
radius: {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
md: '8px',
|
|
72
|
-
lg: '16px',
|
|
73
|
-
full: '9999px'
|
|
161
|
+
sm: '4px', md: '8px', lg: '12px',
|
|
162
|
+
xl: '16px', full: '9999px',
|
|
74
163
|
},
|
|
75
|
-
|
|
76
164
|
shadows: {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
165
|
+
sm: '0 1px 2px rgba(0,0,0,0.05)',
|
|
166
|
+
md: '0 4px 12px rgba(0,0,0,0.1)',
|
|
167
|
+
lg: '0 8px 24px rgba(0,0,0,0.15)',
|
|
168
|
+
xl: '0 16px 48px rgba(0,0,0,0.2)',
|
|
169
|
+
},
|
|
170
|
+
easing: {
|
|
171
|
+
smooth: 'cubic-bezier(0.4,0,0.2,1)',
|
|
172
|
+
bouncy: 'cubic-bezier(0.34,1.56,0.64,1)',
|
|
173
|
+
sharp: 'cubic-bezier(0.4,0,1,1)',
|
|
174
|
+
back: 'cubic-bezier(0.34,1.4,0.64,1)',
|
|
175
|
+
},
|
|
176
|
+
duration: {
|
|
177
|
+
fast: '150ms',
|
|
178
|
+
normal: '300ms',
|
|
179
|
+
slow: '500ms',
|
|
180
|
+
slower: '800ms',
|
|
181
|
+
},
|
|
182
|
+
blur: {
|
|
183
|
+
sm: '4px',
|
|
184
|
+
md: '8px',
|
|
185
|
+
lg: '16px',
|
|
186
|
+
glass: '20px',
|
|
187
|
+
},
|
|
188
|
+
opacity: {
|
|
189
|
+
ghost: '0.1',
|
|
190
|
+
muted: '0.5',
|
|
191
|
+
soft: '0.75',
|
|
192
|
+
full: '1',
|
|
193
|
+
},
|
|
194
|
+
zIndex: {
|
|
195
|
+
base: 0,
|
|
196
|
+
float: 10,
|
|
197
|
+
sticky: 20,
|
|
198
|
+
modal: 100,
|
|
199
|
+
toast: 200,
|
|
200
|
+
top: 999,
|
|
201
|
+
},
|
|
202
|
+
leading: {
|
|
203
|
+
tight: '1.25',
|
|
204
|
+
snug: '1.375',
|
|
205
|
+
normal: '1.5',
|
|
206
|
+
relaxed:'1.625',
|
|
207
|
+
loose: '2',
|
|
208
|
+
},
|
|
209
|
+
tracking: {
|
|
210
|
+
tight: '-0.05em',
|
|
211
|
+
normal: '0em',
|
|
212
|
+
wide: '0.05em',
|
|
213
|
+
wider: '0.1em',
|
|
214
|
+
widest: '0.2em',
|
|
82
215
|
},
|
|
83
|
-
|
|
84
|
-
animations: {
|
|
85
|
-
duration: {
|
|
86
|
-
fast: 150,
|
|
87
|
-
normal: 300,
|
|
88
|
-
slow: 500
|
|
89
|
-
},
|
|
90
|
-
easing: {
|
|
91
|
-
smooth: 'power2.out',
|
|
92
|
-
bouncy: 'elastic.out(1, 0.5)',
|
|
93
|
-
sharp: 'power4.inOut'
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
216
|
},
|
|
97
217
|
|
|
98
218
|
patterns: {
|
|
99
|
-
//
|
|
100
|
-
'
|
|
101
|
-
|
|
102
|
-
'
|
|
219
|
+
// Surfaces
|
|
220
|
+
card: 'pad:md paint:surface curve:lg cast:md',
|
|
221
|
+
card-flat: 'pad:md paint:surface curve:lg',
|
|
222
|
+
panel: 'pad:lg paint:surface curve:xl cast:lg',
|
|
103
223
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
224
|
+
// Buttons
|
|
225
|
+
btn: 'pad-y:sm pad-x:lg curve:md ease:default cursor:pointer',
|
|
226
|
+
btn-primary:'btn paint:primary ink:surface type-weight:semi',
|
|
227
|
+
btn-ghost: 'btn stroke-color:primary ink:primary',
|
|
228
|
+
btn-danger: 'btn paint:error ink:surface',
|
|
107
229
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
230
|
+
// Typography
|
|
231
|
+
heading: 'type-face:sans type-weight:bold leading:tight',
|
|
232
|
+
body-text: 'type-face:sans leading:relaxed',
|
|
233
|
+
caption: 'text:xs ink:neutral-500',
|
|
234
|
+
label: 'text:sm type-weight:medium tracking:wide text-case:upper',
|
|
113
235
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Inputs
|
|
122
|
-
input: 'pad-sm rounded-md border border-neutral-200 transition',
|
|
123
|
-
|
|
124
|
-
// Text
|
|
125
|
-
heading: 'text-h1 color-text',
|
|
126
|
-
subheading: 'text-h3 color-neutral-500',
|
|
127
|
-
'text-muted': 'color-neutral-500'
|
|
236
|
+
// Layout
|
|
237
|
+
container: 'canvas-w:full mar-x:auto pad-x:md',
|
|
238
|
+
center: 'display:flex align-x:center align-yi:center',
|
|
239
|
+
stack: 'display:flex flex-dir:col',
|
|
240
|
+
row: 'display:flex flex-dir:row align-yi:center',
|
|
241
|
+
grid-auto: 'display:grid grid-cols:repeat(auto-fit,minmax(250px,1fr)) gap:md',
|
|
128
242
|
},
|
|
129
243
|
|
|
130
244
|
animations: {
|
|
131
|
-
|
|
132
|
-
'animate-fade-in': {
|
|
245
|
+
'fade-in': {
|
|
133
246
|
from: { opacity: 0 },
|
|
134
|
-
to:
|
|
135
|
-
duration:
|
|
136
|
-
ease: '
|
|
247
|
+
to: { opacity: 1 },
|
|
248
|
+
duration: 'normal',
|
|
249
|
+
ease: 'smooth',
|
|
137
250
|
},
|
|
138
|
-
'
|
|
139
|
-
from: {
|
|
140
|
-
to:
|
|
141
|
-
duration:
|
|
142
|
-
ease: '
|
|
251
|
+
'slide-up': {
|
|
252
|
+
from: { opacity: 0, y: 40 },
|
|
253
|
+
to: { opacity: 1, y: 0 },
|
|
254
|
+
duration: 'normal',
|
|
255
|
+
ease: 'smooth',
|
|
143
256
|
},
|
|
144
|
-
'animate-scale-in': {
|
|
145
|
-
from: { scale: 0, opacity: 0 },
|
|
146
|
-
to: { scale: 1, opacity: 1 },
|
|
147
|
-
duration: 300,
|
|
148
|
-
ease: 'back.out'
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
// Scroll
|
|
152
|
-
'scroll-trigger': {
|
|
153
|
-
scrollTrigger: {
|
|
154
|
-
trigger: 'self',
|
|
155
|
-
start: 'top 80%',
|
|
156
|
-
toggleActions: 'play none none reverse'
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
// Hover
|
|
161
257
|
'hover-lift': {
|
|
162
|
-
hover: { y: -
|
|
163
|
-
},
|
|
164
|
-
'hover-scale': {
|
|
165
|
-
hover: { scale: 1.05, duration: 150 }
|
|
258
|
+
hover: { y: -6, duration: 0.2, ease: 'smooth' },
|
|
166
259
|
},
|
|
167
260
|
'hover-glow': {
|
|
168
|
-
hover: {
|
|
169
|
-
boxShadow: '0 0 20px rgba(59, 130, 246, 0.5)',
|
|
170
|
-
duration: 150
|
|
171
|
-
}
|
|
261
|
+
hover: { boxShadow: '0 0 24px rgba(59,130,246,0.4)', duration: 0.3 },
|
|
172
262
|
},
|
|
173
|
-
|
|
174
|
-
// Click
|
|
175
263
|
'active-press': {
|
|
176
|
-
active: { scale: 0.
|
|
264
|
+
active: { scale: 0.97, duration: 0.1 },
|
|
177
265
|
},
|
|
178
|
-
|
|
179
|
-
// Stagger
|
|
180
|
-
'stagger-children-100': {
|
|
181
|
-
targets: 'children',
|
|
182
|
-
stagger: 0.1,
|
|
183
|
-
from: { opacity: 0, y: 20 },
|
|
184
|
-
to: { opacity: 1, y: 0 }
|
|
185
|
-
}
|
|
186
266
|
},
|
|
187
267
|
|
|
188
268
|
rules: {
|
|
189
|
-
|
|
190
|
-
|
|
269
|
+
responsive: true,
|
|
270
|
+
darkMode: 'class',
|
|
271
|
+
print: false,
|
|
272
|
+
motion: true,
|
|
273
|
+
orientation: false,
|
|
274
|
+
|
|
191
275
|
breakpoints: {
|
|
192
|
-
sm:
|
|
193
|
-
md:
|
|
194
|
-
lg:
|
|
195
|
-
xl:
|
|
196
|
-
|
|
276
|
+
sm: '640px',
|
|
277
|
+
md: '768px',
|
|
278
|
+
lg: '1024px',
|
|
279
|
+
xl: '1280px',
|
|
280
|
+
'2xl': '1536px',
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
containers: {
|
|
284
|
+
card: { sm: '300px', md: '500px' },
|
|
285
|
+
sidebar: { collapsed: '200px', expanded: '320px' },
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
`
|
|
290
|
+
|
|
291
|
+
const mimuExample = `/* styles.mizu — CSS-style alternative to mizumi.config.js */
|
|
292
|
+
/* Both files are supported — they get merged automatically */
|
|
293
|
+
|
|
294
|
+
@token {
|
|
295
|
+
colors {
|
|
296
|
+
brand: #6366f1;
|
|
197
297
|
}
|
|
198
|
-
|
|
199
|
-
|
|
298
|
+
spacing {
|
|
299
|
+
section: 80px;
|
|
300
|
+
hero: 120px;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
200
303
|
|
|
201
|
-
|
|
202
|
-
|
|
304
|
+
@pattern hero {
|
|
305
|
+
pad-y: hero;
|
|
306
|
+
display: flex;
|
|
307
|
+
flex-dir: col;
|
|
308
|
+
align-yi: center;
|
|
309
|
+
align-x: center;
|
|
310
|
+
text-align: center;
|
|
311
|
+
}
|
|
203
312
|
|
|
204
|
-
|
|
205
|
-
|
|
313
|
+
@pattern section {
|
|
314
|
+
pad-y: section;
|
|
315
|
+
canvas-w: full;
|
|
316
|
+
}
|
|
317
|
+
`
|
|
318
|
+
|
|
319
|
+
if (!fs.existsSync('mizumi.config.js')) {
|
|
320
|
+
fs.writeFileSync('mizumi.config.js', jsConfig)
|
|
321
|
+
console.log('✅ Created mizumi.config.js')
|
|
206
322
|
} else {
|
|
207
|
-
|
|
208
|
-
console.log('✅ Created: mizumi.config.js');
|
|
323
|
+
console.log('⏭️ mizumi.config.js already exists — skipping')
|
|
209
324
|
}
|
|
210
325
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
fs.mkdirSync(mizumiDir);
|
|
326
|
+
if (!fs.existsSync('styles.mizu')) {
|
|
327
|
+
fs.writeFileSync('styles.mizu', mimuExample)
|
|
328
|
+
console.log('✅ Created styles.mizu')
|
|
215
329
|
}
|
|
216
330
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
331
|
+
console.log('\n💮 Run: npx mizumi build\n')
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// ── mizumi build ──
|
|
335
|
+
async build() {
|
|
336
|
+
console.log('💮 Mizumi: Building...\n')
|
|
337
|
+
try {
|
|
338
|
+
const cwd = process.cwd()
|
|
339
|
+
const config = await loadConfig()
|
|
340
|
+
const mizumi = new Mizumi(config)
|
|
341
|
+
const outDir = '.mizumi'
|
|
342
|
+
|
|
343
|
+
// Generate base CSS from tokens/patterns
|
|
344
|
+
let css = mizumi.generateCSS()
|
|
345
|
+
|
|
346
|
+
// Scan source files and append arbitrary classes
|
|
347
|
+
const scanned = scanAndGenerateCSS(cwd)
|
|
348
|
+
if (scanned) {
|
|
349
|
+
css += '\n\n/* ===== SCANNED ARBITRARY CLASSES ===== */\n' + scanned
|
|
224
350
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
351
|
+
|
|
352
|
+
// Write all outputs
|
|
353
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true })
|
|
354
|
+
|
|
355
|
+
fs.writeFileSync(path.join(outDir, 'mizumi.css'), css)
|
|
356
|
+
console.log(`✅ CSS: ${path.join(outDir, 'mizumi.css')} (${(Buffer.byteLength(css)/1024).toFixed(2)} KB)`)
|
|
357
|
+
|
|
358
|
+
const js = mizumi.generateRuntimeScript()
|
|
359
|
+
fs.writeFileSync(path.join(outDir, 'mizumi-runtime.js'), js)
|
|
360
|
+
console.log(`✅ Runtime: ${path.join(outDir, 'mizumi-runtime.js')} (${(Buffer.byteLength(js)/1024).toFixed(2)} KB)`)
|
|
361
|
+
|
|
362
|
+
const depthJs = mizumi.depthEngine.generateRuntimeScript()
|
|
363
|
+
fs.writeFileSync(path.join(outDir, 'mizumi-depth-runtime.js'), depthJs)
|
|
364
|
+
console.log(`✅ Depth: ${path.join(outDir, 'mizumi-depth-runtime.js')} (${(Buffer.byteLength(depthJs)/1024).toFixed(2)} KB)`)
|
|
365
|
+
|
|
366
|
+
const dts = mizumi.typesGenerator.generateDTS()
|
|
367
|
+
fs.writeFileSync(path.join(outDir, 'mizumi.d.ts'), dts)
|
|
368
|
+
console.log(`✅ Types: ${path.join(outDir, 'mizumi.d.ts')} (${(Buffer.byteLength(dts)/1024).toFixed(2)} KB)`)
|
|
369
|
+
|
|
370
|
+
const helpers = mizumi.typesGenerator.generateHelpers()
|
|
371
|
+
fs.writeFileSync(path.join(outDir, 'mizumi-helpers.js'), helpers)
|
|
372
|
+
console.log(`✅ Helpers: ${path.join(outDir, 'mizumi-helpers.js')} (${(Buffer.byteLength(helpers)/1024).toFixed(2)} KB)`)
|
|
373
|
+
|
|
374
|
+
const metaObj = {
|
|
375
|
+
tokens: config.tokens,
|
|
376
|
+
patterns: config.patterns,
|
|
377
|
+
animations:config.animations,
|
|
378
|
+
rules: config.rules,
|
|
379
|
+
generated: new Date().toISOString()
|
|
380
|
+
}
|
|
381
|
+
const meta = JSON.stringify(metaObj, null, 2)
|
|
382
|
+
fs.writeFileSync(path.join(outDir, 'mizumi.meta.json'), meta)
|
|
383
|
+
console.log(`✅ Meta: ${path.join(outDir, 'mizumi.meta.json')} (${(Buffer.byteLength(meta)/1024).toFixed(2)} KB)`)
|
|
384
|
+
|
|
385
|
+
// Always write devtools — inactive until mizumi-devtools.js is loaded
|
|
386
|
+
const devtools = generateDevToolsScript({ tokens: metaObj.tokens, patterns: metaObj.patterns, animations: metaObj.animations })
|
|
387
|
+
fs.writeFileSync(path.join(outDir, 'mizumi-devtools.js'), devtools)
|
|
388
|
+
console.log(`✅ DevTools:${path.join(outDir, 'mizumi-devtools.js')} (${(Buffer.byteLength(devtools)/1024).toFixed(2)} KB)`)
|
|
389
|
+
|
|
390
|
+
console.log('\n✅ Build complete')
|
|
391
|
+
console.log(' 💡 Add to HTML for DevTools: <script src=".mizumi/mizumi-devtools.js"></script>')
|
|
392
|
+
} catch (err) {
|
|
393
|
+
console.error('❌ Build failed:', err.message)
|
|
394
|
+
process.exit(1)
|
|
228
395
|
}
|
|
396
|
+
},
|
|
229
397
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.log('
|
|
233
|
-
|
|
234
|
-
|
|
398
|
+
// ── mizumi watch ──
|
|
399
|
+
async watch() {
|
|
400
|
+
console.log('💮 Mizumi: Watching...\n')
|
|
401
|
+
await commands.build()
|
|
402
|
+
watch(loadConfig, Mizumi, generateDevToolsScript)
|
|
235
403
|
},
|
|
236
404
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
process.exit(1);
|
|
250
|
-
}
|
|
405
|
+
// ── mizumi validate ──
|
|
406
|
+
async validate() {
|
|
407
|
+
console.log('💮 Mizumi: Validating config...\n')
|
|
408
|
+
try {
|
|
409
|
+
const config = await loadConfig()
|
|
410
|
+
const validator = new Validator(config)
|
|
411
|
+
const result = validator.validate()
|
|
412
|
+
|
|
413
|
+
if (result.valid && result.warnings.length === 0) {
|
|
414
|
+
console.log('✅ Config is valid — no issues found\n')
|
|
415
|
+
return
|
|
416
|
+
}
|
|
251
417
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
418
|
+
if (result.errors.length > 0) {
|
|
419
|
+
console.log(`❌ ${result.errors.length} error(s):\n`)
|
|
420
|
+
result.errors.forEach(e => console.log(e + '\n'))
|
|
421
|
+
}
|
|
255
422
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
423
|
+
if (result.warnings.length > 0) {
|
|
424
|
+
console.log(`⚠️ ${result.warnings.length} warning(s):\n`)
|
|
425
|
+
result.warnings.forEach(w => console.log(w + '\n'))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!result.valid) process.exit(1)
|
|
429
|
+
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.error('❌ Validation failed:', err.message)
|
|
432
|
+
process.exit(1)
|
|
433
|
+
}
|
|
434
|
+
},
|
|
259
435
|
|
|
260
|
-
|
|
261
|
-
|
|
436
|
+
// ── mizumi analyze ──
|
|
437
|
+
async analyze() {
|
|
438
|
+
console.log('💮 Mizumi: Analyzing design system...\n')
|
|
439
|
+
try {
|
|
440
|
+
const config = await loadConfig()
|
|
441
|
+
const mizumi = new Mizumi(config)
|
|
442
|
+
|
|
443
|
+
// Token stats
|
|
444
|
+
const tokens = config.tokens || {}
|
|
445
|
+
const patterns = config.patterns || {}
|
|
446
|
+
const anims = config.animations || {}
|
|
447
|
+
|
|
448
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
449
|
+
console.log(' MIZUMI DESIGN SYSTEM ANALYSIS')
|
|
450
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
|
|
451
|
+
|
|
452
|
+
// Token summary
|
|
453
|
+
console.log('📦 TOKENS')
|
|
454
|
+
for (const [category, values] of Object.entries(tokens)) {
|
|
455
|
+
const count = typeof values === 'object'
|
|
456
|
+
? Object.keys(values).length
|
|
457
|
+
: 1
|
|
458
|
+
console.log(` ${category.padEnd(16)} ${count} values`)
|
|
459
|
+
}
|
|
262
460
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
461
|
+
// Pattern summary
|
|
462
|
+
console.log(`\n🎨 PATTERNS (${Object.keys(patterns).length} total)`)
|
|
463
|
+
for (const [name, value] of Object.entries(patterns)) {
|
|
464
|
+
const classes = value.split(/\s+/).filter(Boolean)
|
|
465
|
+
console.log(` .${name.padEnd(20)} ${classes.length} utilities`)
|
|
466
|
+
}
|
|
269
467
|
|
|
468
|
+
// Animation summary
|
|
469
|
+
console.log(`\n✨ ANIMATIONS (${Object.keys(anims).length} total)`)
|
|
470
|
+
for (const [name] of Object.entries(anims)) {
|
|
471
|
+
console.log(` ${name}`)
|
|
472
|
+
}
|
|
270
473
|
|
|
474
|
+
// CSS output size estimate
|
|
475
|
+
const css = mizumi.generateCSS()
|
|
476
|
+
const kb = (Buffer.byteLength(css) / 1024).toFixed(2)
|
|
477
|
+
console.log(`\n📄 ESTIMATED OUTPUT`)
|
|
478
|
+
console.log(` CSS size: ${kb} KB`)
|
|
479
|
+
console.log(` Utilities generated: ${css.match(/\./g)?.length || 0}`)
|
|
480
|
+
|
|
481
|
+
// Unused token check — only warn for tokens expected in patterns
|
|
482
|
+
// easing/duration/blur/zIndex/leading/tracking are utility tokens
|
|
483
|
+
// used inline in HTML, not required in patterns
|
|
484
|
+
const PATTERN_TOKEN_CATEGORIES = ['colors', 'spacing', 'typography', 'radius', 'shadows', 'fonts']
|
|
485
|
+
console.log('\n🔍 TOKEN USAGE IN PATTERNS')
|
|
486
|
+
const allPatternText = Object.values(patterns).join(' ')
|
|
487
|
+
let unusedCount = 0
|
|
488
|
+
for (const [category, values] of Object.entries(tokens)) {
|
|
489
|
+
if (!PATTERN_TOKEN_CATEGORIES.includes(category)) continue
|
|
490
|
+
if (typeof values !== 'object') continue
|
|
491
|
+
for (const key of Object.keys(values)) {
|
|
492
|
+
const used = allPatternText.includes(`:${key}`)
|
|
493
|
+
if (!used) {
|
|
494
|
+
console.log(` ⚠️ ${category}.${key} — defined but not used in any pattern`)
|
|
495
|
+
unusedCount++
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (unusedCount === 0) {
|
|
500
|
+
console.log(' ✅ All pattern tokens are in use')
|
|
501
|
+
}
|
|
502
|
+
console.log('\n ℹ️ easing/duration/blur/opacity/zIndex/leading/tracking')
|
|
503
|
+
console.log(' are utility tokens — use inline in HTML/JSX as needed')
|
|
271
504
|
|
|
505
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
|
|
272
506
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
list: async function() {
|
|
278
|
-
const configPath = path.join(process.cwd(), 'mizumi.config.js');
|
|
507
|
+
} catch (err) {
|
|
508
|
+
console.error('❌ Analysis failed:', err.message)
|
|
509
|
+
}
|
|
510
|
+
},
|
|
279
511
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
512
|
+
// ── mizumi convert ──
|
|
513
|
+
// Converts Tailwind class strings to Mizumi equivalents
|
|
514
|
+
convert() {
|
|
515
|
+
const input = args[1]
|
|
516
|
+
if (!input) {
|
|
517
|
+
console.log('Usage: mizumi convert "p-4 bg-blue-500 rounded-lg text-white"')
|
|
518
|
+
return
|
|
283
519
|
}
|
|
284
520
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
521
|
+
const TAILWIND_MAP = {
|
|
522
|
+
'flex': 'display:flex',
|
|
523
|
+
'grid': 'display:grid',
|
|
524
|
+
'block': 'display:block',
|
|
525
|
+
'inline': 'display:inline',
|
|
526
|
+
'hidden': 'display:none',
|
|
527
|
+
'relative': 'pos:relative',
|
|
528
|
+
'absolute': 'pos:absolute',
|
|
529
|
+
'fixed': 'pos:fixed',
|
|
530
|
+
'sticky': 'pos:sticky',
|
|
531
|
+
'w-full': 'canvas-w:full',
|
|
532
|
+
'h-full': 'canvas-h:full',
|
|
533
|
+
'w-screen': 'canvas-w:screen',
|
|
534
|
+
'h-screen': 'canvas-h:screen',
|
|
535
|
+
'mx-auto': 'mar-x:auto',
|
|
536
|
+
'items-center': 'align-yi:center',
|
|
537
|
+
'items-start': 'align-yi:start',
|
|
538
|
+
'items-end': 'align-yi:end',
|
|
539
|
+
'justify-center': 'align-x:center',
|
|
540
|
+
'justify-between': 'align-x:between',
|
|
541
|
+
'justify-start': 'align-x:start',
|
|
542
|
+
'justify-end': 'align-x:end',
|
|
543
|
+
'flex-col': 'flex-dir:col',
|
|
544
|
+
'flex-row': 'flex-dir:row',
|
|
545
|
+
'flex-wrap': 'flex-wrap:yes',
|
|
546
|
+
'flex-1': 'flex:1',
|
|
547
|
+
'font-bold': 'type-weight:bold',
|
|
548
|
+
'font-semibold': 'type-weight:semi',
|
|
549
|
+
'font-medium': 'type-weight:medium',
|
|
550
|
+
'font-normal': 'type-weight:normal',
|
|
551
|
+
'font-light': 'type-weight:light',
|
|
552
|
+
'italic': 'type-style:italic',
|
|
553
|
+
'uppercase': 'text-case:upper',
|
|
554
|
+
'lowercase': 'text-case:lower',
|
|
555
|
+
'capitalize': 'text-case:capital',
|
|
556
|
+
'text-center': 'text-align:center',
|
|
557
|
+
'text-left': 'text-align:left',
|
|
558
|
+
'text-right': 'text-align:right',
|
|
559
|
+
'overflow-hidden': 'overflow:hidden',
|
|
560
|
+
'overflow-auto': 'overflow:auto',
|
|
561
|
+
'overflow-scroll': 'overflow:scroll',
|
|
562
|
+
'cursor-pointer': 'cursor:pointer',
|
|
563
|
+
'cursor-default': 'cursor:default',
|
|
564
|
+
'select-none': 'select:none',
|
|
565
|
+
'pointer-events-none': 'events:none',
|
|
566
|
+
'truncate': 'overflow:hidden text-overflow:dots wrap:no',
|
|
567
|
+
'border': 'stroke-width:1px stroke-style:solid',
|
|
568
|
+
'border-none': 'stroke-style:none',
|
|
569
|
+
'list-none': 'list:none',
|
|
570
|
+
}
|
|
288
571
|
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
console.log('\n🌊 Mizumi Patterns:\n');
|
|
572
|
+
const classes = input.split(/\s+/).filter(Boolean)
|
|
573
|
+
const converted = []
|
|
574
|
+
const unknown = []
|
|
293
575
|
|
|
294
|
-
for (const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
576
|
+
for (const cls of classes) {
|
|
577
|
+
if (TAILWIND_MAP[cls]) {
|
|
578
|
+
converted.push(TAILWIND_MAP[cls])
|
|
579
|
+
} else if (/^p-(\d+)$/.exec(cls)) { converted.push(`pad:your-spacing-token /* was ${cls} */`) }
|
|
580
|
+
else if (/^px-(\d+)$/.exec(cls)) { converted.push(`pad-x:your-spacing-token /* was ${cls} */`) }
|
|
581
|
+
else if (/^py-(\d+)$/.exec(cls)) { converted.push(`pad-y:your-spacing-token /* was ${cls} */`) }
|
|
582
|
+
else if (/^m-(\d+)$/.exec(cls)) { converted.push(`mar:your-spacing-token /* was ${cls} */`) }
|
|
583
|
+
else if (/^mx-(\d+)$/.exec(cls)) { converted.push(`mar-x:your-spacing-token /* was ${cls} */`) }
|
|
584
|
+
else if (/^my-(\d+)$/.exec(cls)) { converted.push(`mar-y:your-spacing-token /* was ${cls} */`) }
|
|
585
|
+
else if (/^gap-(\d+)$/.exec(cls)) { converted.push(`gap:your-spacing-token /* was ${cls} */`) }
|
|
586
|
+
else if (/^rounded/.exec(cls)) { converted.push(`curve:your-radius-token /* was ${cls} */`) }
|
|
587
|
+
else if (/^shadow/.exec(cls)) { converted.push(`cast:your-shadow-token /* was ${cls} */`) }
|
|
588
|
+
else if (/^bg-/.exec(cls)) { converted.push(`paint:your-color-token /* was ${cls} */`) }
|
|
589
|
+
else if (/^text-/.exec(cls)) { converted.push(`text:your-type-token /* was ${cls} */`) }
|
|
590
|
+
else if (/^text-\[/.exec(cls)) { converted.push(`type-size:your-size-token /* was ${cls} */`) }
|
|
591
|
+
else if (/^border-/.exec(cls)) { converted.push(`stroke-color:your-color /* was ${cls} */`) }
|
|
592
|
+
else if (/^text-(white|black)/.exec(cls)) { converted.push(`ink:your-color-token /* was ${cls} */`) }
|
|
593
|
+
else { unknown.push(cls) }
|
|
299
594
|
}
|
|
300
595
|
|
|
301
|
-
console.log('\n
|
|
596
|
+
console.log('\n💮 Mizumi equivalent:\n')
|
|
597
|
+
console.log(converted.join('\n'))
|
|
302
598
|
|
|
303
|
-
|
|
304
|
-
console.log(
|
|
305
|
-
console.log(
|
|
306
|
-
console.log('');
|
|
599
|
+
if (unknown.length > 0) {
|
|
600
|
+
console.log(`\n⚠️ Could not convert (no direct equivalent):\n ${unknown.join(', ')}`)
|
|
601
|
+
console.log(' Check: https://github.com/Mizumi25/MizumiPackage')
|
|
307
602
|
}
|
|
603
|
+
|
|
604
|
+
console.log('')
|
|
308
605
|
},
|
|
309
606
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
console.error('
|
|
320
|
-
process.exit(1);
|
|
607
|
+
// ── mizumi docs ──
|
|
608
|
+
async docs() {
|
|
609
|
+
console.log('💮 Mizumi: Generating docs...\n')
|
|
610
|
+
try {
|
|
611
|
+
const config = await loadConfig()
|
|
612
|
+
const docs = new DocsGenerator(config)
|
|
613
|
+
docs.generate('.mizumi/docs')
|
|
614
|
+
console.log('✅ Docs generated in .mizumi/docs/')
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.error('❌ Docs failed:', err.message)
|
|
321
617
|
}
|
|
618
|
+
},
|
|
322
619
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
620
|
+
// ── mizumi help ──
|
|
621
|
+
help() {
|
|
622
|
+
console.log(`
|
|
623
|
+
💮 Mizumi CLI
|
|
624
|
+
|
|
625
|
+
COMMANDS:
|
|
626
|
+
init Create mizumi.config.js and styles.mizu starter files
|
|
627
|
+
build Build CSS, runtime, types and meta from your config
|
|
628
|
+
watch Build then watch for config changes
|
|
629
|
+
validate Check your config for errors and Tailwind/raw CSS usage
|
|
630
|
+
analyze Design system health report — tokens, patterns, usage
|
|
631
|
+
convert Convert Tailwind class string to Mizumi vocabulary
|
|
632
|
+
docs Generate HTML documentation from your config
|
|
633
|
+
help Show this help
|
|
634
|
+
|
|
635
|
+
EXAMPLES:
|
|
636
|
+
npx mizumi init
|
|
637
|
+
npx mizumi build
|
|
638
|
+
npx mizumi validate
|
|
639
|
+
npx mizumi analyze
|
|
640
|
+
npx mizumi convert "p-4 bg-blue-500 flex items-center rounded-lg"
|
|
641
|
+
|
|
642
|
+
FILES:
|
|
643
|
+
mizumi.config.js JS config (for JS developers)
|
|
644
|
+
*.mizu CSS-style config (for CSS developers)
|
|
645
|
+
Both are auto-detected and merged.
|
|
646
|
+
|
|
647
|
+
OUTPUT (.mizumi/):
|
|
648
|
+
mizumi.css All generated CSS
|
|
649
|
+
mizumi-runtime.js GSAP animation runtime
|
|
650
|
+
mizumi.d.ts TypeScript types
|
|
651
|
+
mizumi-helpers.js JS class name helpers
|
|
652
|
+
mizumi.meta.json Config metadata
|
|
653
|
+
`)
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
// ── ADD THESE TO THE commands OBJECT IN packages/cli/index.js ──
|
|
327
660
|
|
|
328
|
-
const mizumi = new Mizumi(config);
|
|
329
661
|
|
|
330
|
-
|
|
662
|
+
// ── mizumi sync:defaults ──
|
|
663
|
+
// Reads packages/core/defaults.js and syncs into pattern-expander.js,
|
|
664
|
+
// parser.js, animation-engine parent (index.js), variant-generator.js
|
|
665
|
+
async ['sync:defaults']() {
|
|
666
|
+
console.log('💮 Mizumi: Syncing defaults...\n')
|
|
331
667
|
|
|
332
|
-
const
|
|
668
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
669
|
+
const coreDir = path.resolve(__dirname, '../core')
|
|
670
|
+
const defaultsPath = path.resolve(coreDir, 'defaults.js')
|
|
333
671
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
672
|
+
if (!fs.existsSync(defaultsPath)) {
|
|
673
|
+
console.error('❌ packages/core/defaults.js not found')
|
|
674
|
+
process.exit(1)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const { DEFAULT_TOKENS, DEFAULT_PATTERNS, DEFAULT_ANIMATIONS, DEFAULT_RULES } =
|
|
678
|
+
await import(pathToFileURL(defaultsPath).href + `?t=${Date.now()}`)
|
|
679
|
+
|
|
680
|
+
// ── 1. Sync patterns into pattern-expander.js ──
|
|
681
|
+
const peFile = path.resolve(coreDir, 'pattern-expander.js')
|
|
682
|
+
let peSrc = fs.readFileSync(peFile, 'utf8')
|
|
683
|
+
|
|
684
|
+
const patternsJSON = JSON.stringify(DEFAULT_PATTERNS, null, 4)
|
|
685
|
+
.replace(/"([^"]+)":/g, "'$1':") // use single quotes for keys
|
|
686
|
+
.replace(/"/g, "'") // use single quotes for values
|
|
687
|
+
|
|
688
|
+
const peMarkerStart = '// <<<DEFAULTS:PATTERNS:START>>>'
|
|
689
|
+
const peMarkerEnd = '// <<<DEFAULTS:PATTERNS:END>>>'
|
|
690
|
+
|
|
691
|
+
if (peSrc.includes(peMarkerStart)) {
|
|
692
|
+
// Replace existing block
|
|
693
|
+
const re = new RegExp(`${peMarkerStart}[\\s\\S]*?${peMarkerEnd}`)
|
|
694
|
+
peSrc = peSrc.replace(re,
|
|
695
|
+
`${peMarkerStart}\n ...${patternsJSON},\n ${peMarkerEnd}`)
|
|
696
|
+
} else {
|
|
697
|
+
// Inject into constructor — find: this.patterns = {
|
|
698
|
+
peSrc = peSrc.replace(
|
|
699
|
+
'this.patterns = {',
|
|
700
|
+
`this.patterns = {\n ${peMarkerStart}\n ...${patternsJSON},\n ${peMarkerEnd}`
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
fs.writeFileSync(peFile, peSrc)
|
|
704
|
+
console.log(`✅ Patterns synced → pattern-expander.js (${Object.keys(DEFAULT_PATTERNS).length} patterns)`)
|
|
705
|
+
|
|
706
|
+
// ── 2. Sync tokens into parser.js ──
|
|
707
|
+
const parserFile = path.resolve(coreDir, 'parser.js')
|
|
708
|
+
let parserSrc = fs.readFileSync(parserFile, 'utf8')
|
|
709
|
+
|
|
710
|
+
const tokensJSON = JSON.stringify(DEFAULT_TOKENS, null, 4)
|
|
711
|
+
|
|
712
|
+
const tokMarkerStart = '// <<<DEFAULTS:TOKENS:START>>>'
|
|
713
|
+
const tokMarkerEnd = '// <<<DEFAULTS:TOKENS:END>>>'
|
|
714
|
+
|
|
715
|
+
if (parserSrc.includes(tokMarkerStart)) {
|
|
716
|
+
const re = new RegExp(`${tokMarkerStart}[\\s\\S]*?${tokMarkerEnd}`)
|
|
717
|
+
parserSrc = parserSrc.replace(re,
|
|
718
|
+
`${tokMarkerStart}\n const DEFAULT_TOKENS = ${tokensJSON}\n c = this._mergeDeep(DEFAULT_TOKENS, c)\n ${tokMarkerEnd}`)
|
|
719
|
+
} else {
|
|
720
|
+
// Inject at start of parse() method, after: const c = this.config
|
|
721
|
+
parserSrc = parserSrc.replace(
|
|
722
|
+
'parse() {\n const vars = {}\n const c = this.config',
|
|
723
|
+
`parse() {\n const vars = {}\n let c = this.config\n ${tokMarkerStart}\n const DEFAULT_TOKENS = ${tokensJSON}\n c = this._mergeDeep(DEFAULT_TOKENS, c)\n ${tokMarkerEnd}`
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
// Add _mergeDeep helper if not present
|
|
727
|
+
if (!parserSrc.includes('_mergeDeep')) {
|
|
728
|
+
parserSrc = parserSrc.replace(
|
|
729
|
+
'export class TokenParser {',
|
|
730
|
+
`export class TokenParser {
|
|
731
|
+
_mergeDeep(target, source) {
|
|
732
|
+
const out = { ...target }
|
|
733
|
+
for (const key of Object.keys(source || {})) {
|
|
734
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
735
|
+
out[key] = this._mergeDeep(target[key] || {}, source[key])
|
|
736
|
+
} else {
|
|
737
|
+
out[key] = source[key]
|
|
345
738
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
739
|
+
}
|
|
740
|
+
return out
|
|
741
|
+
}
|
|
742
|
+
`
|
|
743
|
+
)
|
|
350
744
|
}
|
|
351
|
-
console.log('');
|
|
352
745
|
}
|
|
746
|
+
fs.writeFileSync(parserFile, parserSrc)
|
|
747
|
+
console.log(`✅ Tokens synced → parser.js`)
|
|
748
|
+
|
|
749
|
+
// ── 3. Sync animations into core/index.js ──
|
|
750
|
+
const indexFile = path.resolve(coreDir, 'index.js')
|
|
751
|
+
let indexSrc = fs.readFileSync(indexFile, 'utf8')
|
|
752
|
+
|
|
753
|
+
const animJSON = JSON.stringify(DEFAULT_ANIMATIONS, null, 4)
|
|
754
|
+
const animMarkerStart = '// <<<DEFAULTS:ANIMATIONS:START>>>'
|
|
755
|
+
const animMarkerEnd = '// <<<DEFAULTS:ANIMATIONS:END>>>'
|
|
756
|
+
|
|
757
|
+
if (indexSrc.includes(animMarkerStart)) {
|
|
758
|
+
const re = new RegExp(`${animMarkerStart}[\\s\\S]*?${animMarkerEnd}`)
|
|
759
|
+
indexSrc = indexSrc.replace(re,
|
|
760
|
+
`${animMarkerStart}\n const DEFAULT_ANIMATIONS = ${animJSON}\n ${animMarkerEnd}`)
|
|
761
|
+
} else {
|
|
762
|
+
// Find AnimationEngine constructor call and inject defaults merge
|
|
763
|
+
indexSrc = indexSrc.replace(
|
|
764
|
+
'this.animationEngine = new AnimationEngine(',
|
|
765
|
+
`${animMarkerStart}\n const DEFAULT_ANIMATIONS = ${animJSON}\n ${animMarkerEnd}\n this.animationEngine = new AnimationEngine(`
|
|
766
|
+
)
|
|
767
|
+
// Now fix the actual merge — find the animations arg
|
|
768
|
+
indexSrc = indexSrc.replace(
|
|
769
|
+
'new AnimationEngine(\n config.animations',
|
|
770
|
+
'new AnimationEngine(\n { ...DEFAULT_ANIMATIONS, ...config.animations }'
|
|
771
|
+
)
|
|
772
|
+
// Handle single-line version too
|
|
773
|
+
indexSrc = indexSrc.replace(
|
|
774
|
+
/new AnimationEngine\(\s*config\.animations\s*,/g,
|
|
775
|
+
'new AnimationEngine({ ...DEFAULT_ANIMATIONS, ...config.animations },'
|
|
776
|
+
)
|
|
777
|
+
}
|
|
778
|
+
fs.writeFileSync(indexFile, indexSrc)
|
|
779
|
+
console.log(`✅ Animations synced → index.js (${Object.keys(DEFAULT_ANIMATIONS).length} animations)`)
|
|
780
|
+
|
|
781
|
+
console.log('\n✅ Sync complete — rebuild to apply: npx mizumi build')
|
|
353
782
|
},
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
// ── mizumi add:pattern <name> "<utilities>" ──
|
|
786
|
+
async ['add:pattern']() {
|
|
787
|
+
const name = args[1]
|
|
788
|
+
const value = args[2]
|
|
789
|
+
|
|
790
|
+
if (!name || !value) {
|
|
791
|
+
console.log('Usage: node cli/index.js add:pattern <name> "<utilities>"')
|
|
792
|
+
console.log('Example: node cli/index.js add:pattern flex-center "display:flex align-x:center align-yi:center"')
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
797
|
+
const defaultsPath = path.resolve(__dirname, '../core/defaults.js')
|
|
798
|
+
|
|
799
|
+
if (!fs.existsSync(defaultsPath)) {
|
|
800
|
+
console.error('❌ packages/core/defaults.js not found')
|
|
801
|
+
process.exit(1)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
let src = fs.readFileSync(defaultsPath, 'utf8')
|
|
805
|
+
|
|
806
|
+
// Find the closing of DEFAULT_PATTERNS and inject before it
|
|
807
|
+
const insertBefore = '\n}\n\nexport const DEFAULT_ANIMATIONS'
|
|
808
|
+
if (!src.includes(insertBefore)) {
|
|
809
|
+
console.error('❌ Could not find insertion point in defaults.js')
|
|
810
|
+
process.exit(1)
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const line = ` '${name}': '${value}',`
|
|
814
|
+
|
|
815
|
+
// Check if already exists
|
|
816
|
+
if (src.includes(`'${name}':`)) {
|
|
817
|
+
// Update existing
|
|
818
|
+
src = src.replace(
|
|
819
|
+
new RegExp(`'${name}':\\s*'[^']*'`),
|
|
820
|
+
`'${name}': '${value}'`
|
|
821
|
+
)
|
|
822
|
+
console.log(`✏️ Updated pattern: ${name}`)
|
|
823
|
+
} else {
|
|
824
|
+
// Insert before closing brace of DEFAULT_PATTERNS
|
|
825
|
+
src = src.replace(insertBefore, `\n${line}${insertBefore}`)
|
|
826
|
+
console.log(`✅ Added pattern: ${name}`)
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
fs.writeFileSync(defaultsPath, src)
|
|
830
|
+
console.log(` Run sync:defaults to apply → node packages/cli/index.js sync:defaults`)
|
|
357
831
|
},
|
|
358
832
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
833
|
+
|
|
834
|
+
// ── mizumi add:token <category> <name> <value> ──
|
|
835
|
+
async ['add:token']() {
|
|
836
|
+
const category = args[1]
|
|
837
|
+
const name = args[2]
|
|
838
|
+
const value = args[3]
|
|
839
|
+
|
|
840
|
+
if (!category || !name || !value) {
|
|
841
|
+
console.log('Usage: node cli/index.js add:token <category> <name> <value>')
|
|
842
|
+
console.log('Example: node cli/index.js add:token colors brand "#ff6b6b"')
|
|
843
|
+
console.log('Example: node cli/index.js add:token spacing hero "120px"')
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
848
|
+
const defaultsPath = path.resolve(__dirname, '../core/defaults.js')
|
|
849
|
+
|
|
850
|
+
if (!fs.existsSync(defaultsPath)) {
|
|
851
|
+
console.error('❌ packages/core/defaults.js not found')
|
|
852
|
+
process.exit(1)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
let src = fs.readFileSync(defaultsPath, 'utf8')
|
|
856
|
+
|
|
857
|
+
// Check if already exists
|
|
858
|
+
if (src.includes(`${name}:`) ) {
|
|
859
|
+
src = src.replace(
|
|
860
|
+
new RegExp(`(${name}:\\s*)(['"\`][^'"\`]*['"\`]|\\d+)`),
|
|
861
|
+
`$1'${value}'`
|
|
862
|
+
)
|
|
863
|
+
console.log(`✏️ Updated token: ${category}.${name} = ${value}`)
|
|
864
|
+
} else {
|
|
865
|
+
// Find the category block and inject
|
|
866
|
+
const catMarker = ` ${category}: {`
|
|
867
|
+
if (!src.includes(catMarker)) {
|
|
868
|
+
console.error(`❌ Token category "${category}" not found in defaults.js`)
|
|
869
|
+
console.log(` Valid categories: colors, spacing, typography, fonts, radius, shadows, easing, duration, blur, opacity, zIndex, leading, tracking`)
|
|
870
|
+
process.exit(1)
|
|
871
|
+
}
|
|
872
|
+
src = src.replace(catMarker, `${catMarker}\n ${name}: '${value}',`)
|
|
873
|
+
console.log(`✅ Added token: ${category}.${name} = ${value}`)
|
|
392
874
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const generator = new DocsGenerator(config);
|
|
398
|
-
const html = generator.generate();
|
|
399
|
-
const outDir = path.join(process.cwd(), '.mizumi');
|
|
400
|
-
const outPath = path.join(outDir, 'docs.html');
|
|
401
|
-
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir);
|
|
402
|
-
fs.writeFileSync(outPath, html);
|
|
403
|
-
console.log(`✅ Docs generated: ${outPath}`);
|
|
404
|
-
console.log('\n🎉 Open in browser:');
|
|
405
|
-
console.log(` .mizumi/docs.html`);
|
|
875
|
+
|
|
876
|
+
fs.writeFileSync(defaultsPath, src)
|
|
877
|
+
console.log(` Run sync:defaults to apply → node packages/cli/index.js sync:defaults`)
|
|
406
878
|
},
|
|
407
879
|
|
|
408
|
-
|
|
880
|
+
|
|
881
|
+
// ── mizumi add:animation <name> ──
|
|
882
|
+
// Opens a prompt-style flow (just prints the template for now)
|
|
883
|
+
async ['add:animation']() {
|
|
884
|
+
const name = args[1]
|
|
885
|
+
|
|
886
|
+
if (!name) {
|
|
887
|
+
console.log('Usage: node cli/index.js add:animation <name>')
|
|
888
|
+
console.log('Example: node cli/index.js add:animation hover-glow')
|
|
889
|
+
return
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
893
|
+
const defaultsPath = path.resolve(__dirname, '../core/defaults.js')
|
|
894
|
+
|
|
895
|
+
if (!fs.existsSync(defaultsPath)) {
|
|
896
|
+
console.error('❌ packages/core/defaults.js not found')
|
|
897
|
+
process.exit(1)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
let src = fs.readFileSync(defaultsPath, 'utf8')
|
|
901
|
+
|
|
902
|
+
if (src.includes(`'${name}':`)) {
|
|
903
|
+
console.log(`⚠️ Animation "${name}" already exists in defaults.js`)
|
|
904
|
+
console.log(' Edit it directly in packages/core/defaults.js')
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Detect type from name
|
|
909
|
+
let template
|
|
910
|
+
if (name.startsWith('hover-')) {
|
|
911
|
+
template = ` '${name}': {\n hover: { y: -6, duration: 0.15, ease: 'power2.out' },\n },`
|
|
912
|
+
} else if (name.startsWith('active-')) {
|
|
913
|
+
template = ` '${name}': {\n active: { scale: 0.95, duration: 0.1 },\n },`
|
|
914
|
+
} else if (name.startsWith('scroll-')) {
|
|
915
|
+
template = ` '${name}': {\n from: { opacity: 0, y: 30 },\n to: { opacity: 1, y: 0 },\n duration: 0.6,\n ease: 'power2.out',\n scrollTrigger: { trigger: 'self', start: 'top 85%' },\n },`
|
|
916
|
+
} else if (name.startsWith('stagger-')) {
|
|
917
|
+
template = ` '${name}': {\n targets: 'children',\n stagger: 0.1,\n from: { opacity: 0, y: 20 },\n to: { opacity: 1, y: 0 },\n },`
|
|
918
|
+
} else {
|
|
919
|
+
template = ` '${name}': {\n from: { opacity: 0, y: 40 },\n to: { opacity: 1, y: 0 },\n duration: 0.3,\n ease: 'power2.out',\n },`
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Inject before closing of DEFAULT_ANIMATIONS
|
|
923
|
+
const insertBefore = '\n}\n\nexport const DEFAULT_RULES'
|
|
924
|
+
src = src.replace(insertBefore, `\n${template}${insertBefore}`)
|
|
925
|
+
|
|
926
|
+
fs.writeFileSync(defaultsPath, src)
|
|
927
|
+
console.log(`✅ Added animation: ${name}`)
|
|
928
|
+
console.log(` Edit the config in packages/core/defaults.js if needed`)
|
|
929
|
+
console.log(` Run sync:defaults to apply → node packages/cli/index.js sync:defaults`)
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
}
|
|
409
935
|
|
|
410
936
|
// Run command
|
|
411
|
-
if (
|
|
412
|
-
|
|
413
|
-
} else {
|
|
414
|
-
|
|
415
|
-
|
|
937
|
+
if (!command || command === 'help') {
|
|
938
|
+
commands.help()
|
|
939
|
+
} else if (commands[command]) {
|
|
940
|
+
Promise.resolve(commands[command]()).catch(err => {
|
|
941
|
+
console.error('❌', err.message)
|
|
942
|
+
process.exit(1)
|
|
943
|
+
})
|
|
944
|
+
} else if (command === 'sync:defaults') {
|
|
945
|
+
Promise.resolve(commands['sync:defaults']()).catch(err => { console.error('❌', err.message); process.exit(1) })
|
|
946
|
+
} else if (command === 'add:pattern') {
|
|
947
|
+
Promise.resolve(commands['add:pattern']()).catch(err => { console.error('❌', err.message); process.exit(1) })
|
|
948
|
+
} else if (command === 'add:token') {
|
|
949
|
+
Promise.resolve(commands['add:token']()).catch(err => { console.error('❌', err.message); process.exit(1) })
|
|
950
|
+
} else if (command === 'add:animation') {
|
|
951
|
+
Promise.resolve(commands['add:animation']()).catch(err => { console.error('❌', err.message); process.exit(1) })
|
|
416
952
|
}
|
|
953
|
+
else {
|
|
954
|
+
console.error(`❌ Unknown command: ${command}`)
|
|
955
|
+
console.log('Run: npx mizumi help')
|
|
956
|
+
process.exit(1)
|
|
957
|
+
}
|