@rokkit/cli 1.0.0-next.137 → 1.0.0-next.138
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/dist/src/doctor.d.ts +1 -1
- package/package.json +1 -1
- package/src/doctor.js +182 -103
- package/src/init.js +149 -121
package/dist/src/doctor.d.ts
CHANGED
package/package.json
CHANGED
package/src/doctor.js
CHANGED
|
@@ -4,33 +4,32 @@ import { resolve } from 'path'
|
|
|
4
4
|
import { generateUnoConfig, generateAppCssImports, generateInitScript } from './init.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* @param {
|
|
9
|
-
* @returns {
|
|
7
|
+
* Check config file existence
|
|
8
|
+
* @param {Object} fs
|
|
9
|
+
* @returns {{ id: string, label: string, status: string, fixable: boolean, fix: string, autoFix: string }}
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
const checks = []
|
|
13
|
-
|
|
14
|
-
// 1. rokkit.config.js exists
|
|
11
|
+
function checkConfig(fs) {
|
|
15
12
|
const configExists = fs.exists(fs.resolve('rokkit.config.js'))
|
|
16
|
-
|
|
13
|
+
return {
|
|
17
14
|
id: 'config-exists',
|
|
18
15
|
label: 'rokkit.config.js exists',
|
|
19
16
|
status: configExists ? 'pass' : 'fail',
|
|
20
17
|
fixable: true,
|
|
21
18
|
fix: 'Run `rokkit init` to generate config',
|
|
22
19
|
autoFix: 'generate-config'
|
|
23
|
-
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Check uno.config.js uses presetRokkit
|
|
25
|
+
* @param {Object} fs
|
|
26
|
+
* @returns {Object}
|
|
27
|
+
*/
|
|
28
|
+
function checkUnoPreset(fs) {
|
|
26
29
|
const unoPath = fs.resolve('uno.config.js')
|
|
27
30
|
const unoExists = fs.exists(unoPath)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const unoContent = fs.read(unoPath)
|
|
31
|
-
unoUsesPreset = unoContent.includes('presetRokkit')
|
|
32
|
-
}
|
|
33
|
-
checks.push({
|
|
31
|
+
const unoUsesPreset = unoExists && fs.read(unoPath).includes('presetRokkit')
|
|
32
|
+
return {
|
|
34
33
|
id: 'uno-uses-preset',
|
|
35
34
|
label: 'uno.config.js uses presetRokkit()',
|
|
36
35
|
status: unoUsesPreset ? 'pass' : 'fail',
|
|
@@ -38,43 +37,56 @@ export function runChecks(fs) {
|
|
|
38
37
|
fix: unoExists
|
|
39
38
|
? `Replace uno.config.js contents with:\n${generateUnoConfig()}`
|
|
40
39
|
: `Create uno.config.js with:\n${generateUnoConfig()}`
|
|
41
|
-
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Check app.css has theme imports
|
|
45
|
+
* @param {Object} fs
|
|
46
|
+
* @returns {Object}
|
|
47
|
+
*/
|
|
48
|
+
function checkCssImports(fs) {
|
|
44
49
|
const cssPath = fs.resolve('src/app.css')
|
|
45
50
|
const cssExists = fs.exists(cssPath)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const css = fs.read(cssPath)
|
|
49
|
-
cssHasBase = css.includes('@rokkit/themes/dist/base')
|
|
50
|
-
}
|
|
51
|
-
checks.push({
|
|
51
|
+
const cssHasBase = cssExists && fs.read(cssPath).includes('@rokkit/themes/dist/base')
|
|
52
|
+
return {
|
|
52
53
|
id: 'css-imports',
|
|
53
54
|
label: 'app.css has theme imports',
|
|
54
55
|
status: cssHasBase ? 'pass' : 'fail',
|
|
55
56
|
fixable: true,
|
|
56
57
|
fix: 'Append theme imports to src/app.css',
|
|
57
58
|
autoFix: 'patch-css'
|
|
58
|
-
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Check app.html has init script
|
|
64
|
+
* @param {Object} fs
|
|
65
|
+
* @returns {Object}
|
|
66
|
+
*/
|
|
67
|
+
function checkHtmlScript(fs) {
|
|
61
68
|
const htmlPath = fs.resolve('src/app.html')
|
|
62
69
|
const htmlExists = fs.exists(htmlPath)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
checks.push({
|
|
70
|
+
const htmlHasScript =
|
|
71
|
+
htmlExists &&
|
|
72
|
+
(fs.read(htmlPath).includes('rokkit-theme') || fs.read(htmlPath).includes('data-mode'))
|
|
73
|
+
return {
|
|
69
74
|
id: 'html-init-script',
|
|
70
75
|
label: 'app.html has theme init script',
|
|
71
76
|
status: htmlHasScript ? 'pass' : 'fail',
|
|
72
77
|
fixable: true,
|
|
73
78
|
fix: 'Add flash-prevention script to src/app.html',
|
|
74
79
|
autoFix: 'patch-html'
|
|
75
|
-
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Run all doctor checks against the given filesystem adapter.
|
|
85
|
+
* @param {{ exists: (p: string) => boolean, read: (p: string) => string, resolve: (p: string) => string }} fs
|
|
86
|
+
* @returns {Array<{ id: string, label: string, status: 'pass'|'fail', fixable: boolean, fix: string, autoFix?: string }>}
|
|
87
|
+
*/
|
|
88
|
+
export function runChecks(fs) {
|
|
89
|
+
return [checkConfig(fs), checkUnoPreset(fs), checkCssImports(fs), checkHtmlScript(fs)]
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
/**
|
|
@@ -89,98 +101,165 @@ function createFsAdapter(cwd) {
|
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Apply the generate-config fix
|
|
106
|
+
* @param {string} cwd
|
|
107
|
+
* @param {string} label
|
|
108
|
+
*/
|
|
109
|
+
function applyGenerateConfig(cwd, label) {
|
|
110
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
111
|
+
writeFileSync(configPath, 'export default {}\n')
|
|
112
|
+
console.info(` Fixed: ${label}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Apply the patch-css fix
|
|
117
|
+
* @param {string} cwd
|
|
118
|
+
* @param {string} label
|
|
119
|
+
*/
|
|
120
|
+
function applyPatchCss(cwd, label) {
|
|
121
|
+
const cssPath = resolve(cwd, 'src/app.css')
|
|
122
|
+
const imports = generateAppCssImports(['rokkit'])
|
|
123
|
+
if (existsSync(cssPath)) {
|
|
124
|
+
const existing = readFileSync(cssPath, 'utf-8')
|
|
125
|
+
const missing = imports.filter((line) => !existing.includes(line))
|
|
126
|
+
if (missing.length > 0) writeFileSync(cssPath, `${missing.join('\n')}\n${existing}`)
|
|
127
|
+
} else {
|
|
128
|
+
writeFileSync(cssPath, `${imports.join('\n')}\n`)
|
|
129
|
+
}
|
|
130
|
+
console.info(` Fixed: ${label}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Apply the patch-html fix
|
|
135
|
+
* @param {string} cwd
|
|
136
|
+
* @param {string} label
|
|
137
|
+
* @returns {boolean} whether fix was applied
|
|
138
|
+
*/
|
|
139
|
+
function applyPatchHtml(cwd, label) {
|
|
140
|
+
const htmlPath = resolve(cwd, 'src/app.html')
|
|
141
|
+
if (!existsSync(htmlPath)) return false
|
|
142
|
+
const html = readFileSync(htmlPath, 'utf-8')
|
|
143
|
+
const script = generateInitScript('manual', 'rokkit-theme')
|
|
144
|
+
if (!script || html.includes('rokkit-theme')) return false
|
|
145
|
+
const patched = html.replace(/(<body[^>]*>)/, `$1\n${script}`)
|
|
146
|
+
writeFileSync(htmlPath, patched)
|
|
147
|
+
console.info(` Fixed: ${label}`)
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** @type {Record<string, (cwd: string, label: string) => boolean|void>} */
|
|
152
|
+
const FIX_HANDLERS = {
|
|
153
|
+
'generate-config': (cwd, label) => { applyGenerateConfig(cwd, label); return true },
|
|
154
|
+
'patch-css': (cwd, label) => { applyPatchCss(cwd, label); return true },
|
|
155
|
+
'patch-html': (cwd, label) => applyPatchHtml(cwd, label)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {number} fixed
|
|
160
|
+
* @param {{ status: string, fixable: boolean, autoFix?: string, label: string }} check
|
|
161
|
+
* @param {string} cwd
|
|
162
|
+
* @returns {number}
|
|
163
|
+
*/
|
|
164
|
+
function applyCheckFix(fixed, check, cwd) {
|
|
165
|
+
if (check.status !== 'fail' || !check.fixable) return fixed
|
|
166
|
+
const handler = FIX_HANDLERS[check.autoFix]
|
|
167
|
+
return handler && handler(cwd, check.label) ? fixed + 1 : fixed
|
|
168
|
+
}
|
|
169
|
+
|
|
92
170
|
/**
|
|
93
171
|
* Auto-fix failed checks that are marked fixable.
|
|
94
|
-
* @param {Array<{ id: string, status: string, fixable: boolean, autoFix?: string }>} checks
|
|
172
|
+
* @param {Array<{ id: string, status: string, fixable: boolean, autoFix?: string, label: string }>} checks
|
|
95
173
|
* @param {string} cwd
|
|
96
174
|
* @returns {number} number of fixed items
|
|
97
175
|
*/
|
|
98
176
|
function autoFix(checks, cwd) {
|
|
99
|
-
|
|
177
|
+
return checks.reduce((fixed, check) => applyCheckFix(fixed, check, cwd), 0)
|
|
178
|
+
}
|
|
100
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Print fix hints for failing checks
|
|
182
|
+
* @param {Array} checks
|
|
183
|
+
*/
|
|
184
|
+
function printFixHints(checks) {
|
|
101
185
|
for (const check of checks) {
|
|
102
|
-
if (check.status
|
|
103
|
-
|
|
104
|
-
if (check.autoFix === 'generate-config') {
|
|
105
|
-
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
106
|
-
writeFileSync(configPath, 'export default {}\n')
|
|
107
|
-
console.info(` Fixed: ${check.label}`)
|
|
108
|
-
fixed++
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (check.autoFix === 'patch-css') {
|
|
112
|
-
const cssPath = resolve(cwd, 'src/app.css')
|
|
113
|
-
const imports = generateAppCssImports(['rokkit'])
|
|
114
|
-
if (existsSync(cssPath)) {
|
|
115
|
-
const existing = readFileSync(cssPath, 'utf-8')
|
|
116
|
-
const missing = imports.filter((line) => !existing.includes(line))
|
|
117
|
-
if (missing.length > 0) {
|
|
118
|
-
writeFileSync(cssPath, `${missing.join('\n')}\n${existing}`)
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
writeFileSync(cssPath, `${imports.join('\n')}\n`)
|
|
122
|
-
}
|
|
123
|
-
console.info(` Fixed: ${check.label}`)
|
|
124
|
-
fixed++
|
|
186
|
+
if (check.status === 'fail') {
|
|
187
|
+
console.info(` ${check.fixable ? '(auto-fixable) ' : ''}${check.fix}`)
|
|
125
188
|
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
126
191
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.info(` Fixed: ${check.label}`)
|
|
136
|
-
fixed++
|
|
137
|
-
}
|
|
138
|
-
}
|
|
192
|
+
/**
|
|
193
|
+
* Print remaining manual action items
|
|
194
|
+
* @param {Array} checks
|
|
195
|
+
*/
|
|
196
|
+
function printManualItems(checks) {
|
|
197
|
+
for (const check of checks) {
|
|
198
|
+
if (check.status === 'fail' && !check.fixable) {
|
|
199
|
+
console.info(` - ${check.label}: ${check.fix}`)
|
|
139
200
|
}
|
|
140
201
|
}
|
|
141
|
-
|
|
142
|
-
return fixed
|
|
143
202
|
}
|
|
144
203
|
|
|
145
204
|
/**
|
|
146
|
-
*
|
|
147
|
-
* @param {
|
|
205
|
+
* Count failures and print status for each check.
|
|
206
|
+
* @param {Array} checks
|
|
207
|
+
* @returns {number}
|
|
148
208
|
*/
|
|
149
|
-
|
|
150
|
-
const cwd = process.cwd()
|
|
151
|
-
const fs = createFsAdapter(cwd)
|
|
152
|
-
const checks = runChecks(fs)
|
|
153
|
-
|
|
154
|
-
console.info('Rokkit Doctor\n')
|
|
155
|
-
|
|
209
|
+
function printChecks(checks) {
|
|
156
210
|
let failures = 0
|
|
157
211
|
for (const check of checks) {
|
|
158
212
|
const icon = check.status === 'pass' ? 'PASS' : 'FAIL'
|
|
159
213
|
console.info(` ${icon} ${check.label}`)
|
|
160
|
-
if (check.status === 'fail')
|
|
161
|
-
failures++
|
|
162
|
-
if (!opts.fix) {
|
|
163
|
-
console.info(` ${check.fixable ? '(auto-fixable) ' : ''}${check.fix}`)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
214
|
+
if (check.status === 'fail') failures++
|
|
166
215
|
}
|
|
216
|
+
return failures
|
|
217
|
+
}
|
|
167
218
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Handle auto-fix flow and print results.
|
|
221
|
+
* @param {Array} checks
|
|
222
|
+
* @param {string} cwd
|
|
223
|
+
* @param {number} failures
|
|
224
|
+
*/
|
|
225
|
+
function handleAutoFix(checks, cwd, failures) {
|
|
226
|
+
console.info('\nAuto-fixing...\n')
|
|
227
|
+
const fixed = autoFix(checks, cwd)
|
|
228
|
+
const remaining = failures - fixed
|
|
229
|
+
if (remaining > 0) {
|
|
230
|
+
console.info(`\n${fixed} fixed, ${remaining} require manual action:`)
|
|
231
|
+
printManualItems(checks)
|
|
232
|
+
} else {
|
|
233
|
+
console.info(`\nAll ${fixed} issues fixed!`)
|
|
182
234
|
}
|
|
235
|
+
}
|
|
183
236
|
|
|
237
|
+
/**
|
|
238
|
+
* @param {Array} checks
|
|
239
|
+
* @param {string} cwd
|
|
240
|
+
* @param {number} failures
|
|
241
|
+
* @param {boolean} fix
|
|
242
|
+
*/
|
|
243
|
+
function handleResults(checks, cwd, failures, fix) {
|
|
244
|
+
if (failures === 0) return
|
|
245
|
+
if (fix) {
|
|
246
|
+
handleAutoFix(checks, cwd, failures)
|
|
247
|
+
} else {
|
|
248
|
+
printFixHints(checks)
|
|
249
|
+
process.exitCode = 1
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Interactive doctor command — validates project setup.
|
|
255
|
+
* @param {{ fix?: boolean }} [opts]
|
|
256
|
+
*/
|
|
257
|
+
export function doctor(opts = {}) {
|
|
258
|
+
const cwd = process.cwd()
|
|
259
|
+
const checks = runChecks(createFsAdapter(cwd))
|
|
260
|
+
|
|
261
|
+
console.info('Rokkit Doctor\n')
|
|
262
|
+
const failures = printChecks(checks)
|
|
263
|
+
handleResults(checks, cwd, failures, opts.fix ?? false)
|
|
184
264
|
console.info('')
|
|
185
|
-
process.exitCode = failures > 0 && !opts.fix ? 1 : 0
|
|
186
265
|
}
|
package/src/init.js
CHANGED
|
@@ -101,85 +101,157 @@ export function generateInitScript(switcher, storageKey = 'rokkit-theme') {
|
|
|
101
101
|
</script>`
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
const PROMPTS_CONFIG = [
|
|
105
|
+
{
|
|
106
|
+
type: 'select',
|
|
107
|
+
name: 'palette',
|
|
108
|
+
message: 'Color palette',
|
|
109
|
+
choices: [
|
|
110
|
+
{ title: 'Default (orange/pink/sky)', value: 'default' },
|
|
111
|
+
{ title: 'Vibrant (blue/purple/sky)', value: 'vibrant' },
|
|
112
|
+
{ title: 'Seaweed (sky/green/blue)', value: 'seaweed' },
|
|
113
|
+
{ title: 'Custom', value: 'custom' }
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
118
|
+
name: 'primary',
|
|
119
|
+
message: 'Primary color (Tailwind palette name)',
|
|
120
|
+
initial: 'orange'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
124
|
+
name: 'secondary',
|
|
125
|
+
message: 'Secondary color',
|
|
126
|
+
initial: 'pink'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
130
|
+
name: 'accent',
|
|
131
|
+
message: 'Accent color',
|
|
132
|
+
initial: 'sky'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
136
|
+
name: 'surface',
|
|
137
|
+
message: 'Surface color',
|
|
138
|
+
initial: 'slate'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'select',
|
|
142
|
+
name: 'icons',
|
|
143
|
+
message: 'Icon collections',
|
|
144
|
+
choices: [
|
|
145
|
+
{ title: 'Rokkit icons only', value: 'rokkit' },
|
|
146
|
+
{ title: 'Rokkit + custom collection', value: 'custom' }
|
|
147
|
+
]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
151
|
+
name: 'iconPath',
|
|
152
|
+
message: 'Path to custom icon collection JSON',
|
|
153
|
+
initial: './static/icons/custom.json'
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'multiselect',
|
|
157
|
+
name: 'themes',
|
|
158
|
+
message: 'Theme styles',
|
|
159
|
+
choices: [
|
|
160
|
+
{ title: 'Rokkit', value: 'rokkit', selected: true },
|
|
161
|
+
{ title: 'Minimal', value: 'minimal' },
|
|
162
|
+
{ title: 'Material', value: 'material' }
|
|
163
|
+
],
|
|
164
|
+
min: 1
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: 'select',
|
|
168
|
+
name: 'switcher',
|
|
169
|
+
message: 'Theme switching',
|
|
170
|
+
choices: [
|
|
171
|
+
{ title: 'System only (prefers-color-scheme)', value: 'system' },
|
|
172
|
+
{ title: 'Manual (light/dark toggle)', value: 'manual' },
|
|
173
|
+
{ title: 'Full (light/dark + style variants)', value: 'full' }
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Write rokkit.config.js
|
|
180
|
+
* @param {string} cwd
|
|
181
|
+
* @param {Record<string, unknown>} config
|
|
182
|
+
*/
|
|
183
|
+
function writeRokkitConfig(cwd, config) {
|
|
184
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
185
|
+
if (existsSync(configPath)) {
|
|
186
|
+
console.warn(' rokkit.config.js already exists — skipping')
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
writeFileSync(configPath, `export default ${JSON.stringify(config, null, 2)}\n`)
|
|
190
|
+
console.info(' Created rokkit.config.js')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Write uno.config.js
|
|
195
|
+
* @param {string} cwd
|
|
196
|
+
*/
|
|
197
|
+
function writeUnoConfig(cwd) {
|
|
198
|
+
const unoPath = resolve(cwd, 'uno.config.js')
|
|
199
|
+
if (existsSync(unoPath)) {
|
|
200
|
+
console.warn(' uno.config.js already exists — skipping (see rokkit doctor for migration)')
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
writeFileSync(unoPath, generateUnoConfig())
|
|
204
|
+
console.info(' Created uno.config.js')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Patch or create app.css
|
|
209
|
+
* @param {string} cwd
|
|
210
|
+
* @param {string[]} cssImports
|
|
211
|
+
*/
|
|
212
|
+
function writeAppCss(cwd, cssImports) {
|
|
213
|
+
const appCssPath = resolve(cwd, 'src/app.css')
|
|
214
|
+
if (!existsSync(appCssPath)) {
|
|
215
|
+
writeFileSync(appCssPath, `${cssImports.join('\n')}\n`)
|
|
216
|
+
console.info(' Created src/app.css')
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
const existing = readFileSync(appCssPath, 'utf-8')
|
|
220
|
+
const missing = cssImports.filter((line) => !existing.includes(line))
|
|
221
|
+
if (missing.length > 0) {
|
|
222
|
+
writeFileSync(appCssPath, `${missing.join('\n')}\n${existing}`)
|
|
223
|
+
console.info(` Patched app.css — added ${missing.length} imports`)
|
|
224
|
+
} else {
|
|
225
|
+
console.info(' app.css already has theme imports')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Patch app.html with init script
|
|
231
|
+
* @param {string} cwd
|
|
232
|
+
* @param {string} initScript
|
|
233
|
+
* @param {string} storageKey
|
|
234
|
+
*/
|
|
235
|
+
function writeAppHtml(cwd, initScript, storageKey) {
|
|
236
|
+
const appHtmlPath = resolve(cwd, 'src/app.html')
|
|
237
|
+
if (!existsSync(appHtmlPath)) return
|
|
238
|
+
const html = readFileSync(appHtmlPath, 'utf-8')
|
|
239
|
+
if (html.includes('rokkit-theme') || html.includes(storageKey)) {
|
|
240
|
+
console.info(' app.html already has init script')
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
const patched = html.replace(/(<body[^>]*>)/, `$1\n${initScript}`)
|
|
244
|
+
writeFileSync(appHtmlPath, patched)
|
|
245
|
+
console.info(' Patched app.html — added theme init script')
|
|
246
|
+
}
|
|
247
|
+
|
|
104
248
|
/**
|
|
105
249
|
* Interactive init command — prompts the user, writes config files.
|
|
106
250
|
*/
|
|
107
251
|
export async function init() {
|
|
108
252
|
console.info('Rokkit Init — Setting up your SvelteKit project\n')
|
|
109
253
|
|
|
110
|
-
const response = await prompts(
|
|
111
|
-
{
|
|
112
|
-
type: 'select',
|
|
113
|
-
name: 'palette',
|
|
114
|
-
message: 'Color palette',
|
|
115
|
-
choices: [
|
|
116
|
-
{ title: 'Default (orange/pink/sky)', value: 'default' },
|
|
117
|
-
{ title: 'Vibrant (blue/purple/sky)', value: 'vibrant' },
|
|
118
|
-
{ title: 'Seaweed (sky/green/blue)', value: 'seaweed' },
|
|
119
|
-
{ title: 'Custom', value: 'custom' }
|
|
120
|
-
]
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
124
|
-
name: 'primary',
|
|
125
|
-
message: 'Primary color (Tailwind palette name)',
|
|
126
|
-
initial: 'orange'
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
130
|
-
name: 'secondary',
|
|
131
|
-
message: 'Secondary color',
|
|
132
|
-
initial: 'pink'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
136
|
-
name: 'accent',
|
|
137
|
-
message: 'Accent color',
|
|
138
|
-
initial: 'sky'
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
142
|
-
name: 'surface',
|
|
143
|
-
message: 'Surface color',
|
|
144
|
-
initial: 'slate'
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
type: 'select',
|
|
148
|
-
name: 'icons',
|
|
149
|
-
message: 'Icon collections',
|
|
150
|
-
choices: [
|
|
151
|
-
{ title: 'Rokkit icons only', value: 'rokkit' },
|
|
152
|
-
{ title: 'Rokkit + custom collection', value: 'custom' }
|
|
153
|
-
]
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
157
|
-
name: 'iconPath',
|
|
158
|
-
message: 'Path to custom icon collection JSON',
|
|
159
|
-
initial: './static/icons/custom.json'
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
type: 'multiselect',
|
|
163
|
-
name: 'themes',
|
|
164
|
-
message: 'Theme styles',
|
|
165
|
-
choices: [
|
|
166
|
-
{ title: 'Rokkit', value: 'rokkit', selected: true },
|
|
167
|
-
{ title: 'Minimal', value: 'minimal' },
|
|
168
|
-
{ title: 'Material', value: 'material' }
|
|
169
|
-
],
|
|
170
|
-
min: 1
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
type: 'select',
|
|
174
|
-
name: 'switcher',
|
|
175
|
-
message: 'Theme switching',
|
|
176
|
-
choices: [
|
|
177
|
-
{ title: 'System only (prefers-color-scheme)', value: 'system' },
|
|
178
|
-
{ title: 'Manual (light/dark toggle)', value: 'manual' },
|
|
179
|
-
{ title: 'Full (light/dark + style variants)', value: 'full' }
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
])
|
|
254
|
+
const response = await prompts(PROMPTS_CONFIG)
|
|
183
255
|
|
|
184
256
|
if (response.palette === 'custom') {
|
|
185
257
|
response.customColors = {
|
|
@@ -193,56 +265,12 @@ export async function init() {
|
|
|
193
265
|
const config = generateConfig(response)
|
|
194
266
|
const cwd = process.cwd()
|
|
195
267
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
console.warn(' rokkit.config.js already exists — skipping')
|
|
200
|
-
} else {
|
|
201
|
-
writeFileSync(configPath, `export default ${JSON.stringify(config, null, 2)}\n`)
|
|
202
|
-
console.info(' Created rokkit.config.js')
|
|
203
|
-
}
|
|
268
|
+
writeRokkitConfig(cwd, config)
|
|
269
|
+
writeUnoConfig(cwd)
|
|
270
|
+
writeAppCss(cwd, generateAppCssImports(config.themes))
|
|
204
271
|
|
|
205
|
-
// --- uno.config.js ---
|
|
206
|
-
const unoPath = resolve(cwd, 'uno.config.js')
|
|
207
|
-
if (existsSync(unoPath)) {
|
|
208
|
-
console.warn(' uno.config.js already exists — skipping (see rokkit doctor for migration)')
|
|
209
|
-
} else {
|
|
210
|
-
writeFileSync(unoPath, generateUnoConfig())
|
|
211
|
-
console.info(' Created uno.config.js')
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// --- src/app.css ---
|
|
215
|
-
const appCssPath = resolve(cwd, 'src/app.css')
|
|
216
|
-
const cssImports = generateAppCssImports(config.themes)
|
|
217
|
-
if (existsSync(appCssPath)) {
|
|
218
|
-
const existing = readFileSync(appCssPath, 'utf-8')
|
|
219
|
-
const missing = cssImports.filter((line) => !existing.includes(line))
|
|
220
|
-
if (missing.length > 0) {
|
|
221
|
-
writeFileSync(appCssPath, `${missing.join('\n')}\n${existing}`)
|
|
222
|
-
console.info(` Patched app.css — added ${missing.length} imports`)
|
|
223
|
-
} else {
|
|
224
|
-
console.info(' app.css already has theme imports')
|
|
225
|
-
}
|
|
226
|
-
} else {
|
|
227
|
-
writeFileSync(appCssPath, `${cssImports.join('\n')}\n`)
|
|
228
|
-
console.info(' Created src/app.css')
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// --- src/app.html (theme init script) ---
|
|
232
272
|
const initScript = generateInitScript(config.switcher, config.storageKey)
|
|
233
|
-
if (initScript)
|
|
234
|
-
const appHtmlPath = resolve(cwd, 'src/app.html')
|
|
235
|
-
if (existsSync(appHtmlPath)) {
|
|
236
|
-
const html = readFileSync(appHtmlPath, 'utf-8')
|
|
237
|
-
if (!html.includes('rokkit-theme') && !html.includes(config.storageKey)) {
|
|
238
|
-
const patched = html.replace(/(<body[^>]*>)/, `$1\n${initScript}`)
|
|
239
|
-
writeFileSync(appHtmlPath, patched)
|
|
240
|
-
console.info(' Patched app.html — added theme init script')
|
|
241
|
-
} else {
|
|
242
|
-
console.info(' app.html already has init script')
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
273
|
+
if (initScript) writeAppHtml(cwd, initScript, config.storageKey)
|
|
246
274
|
|
|
247
275
|
console.info('\nDone! Run `rokkit doctor` to verify your setup.')
|
|
248
276
|
}
|