@rokkit/cli 1.0.0-next.137 → 1.0.0-next.139

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.
@@ -21,4 +21,4 @@ export function runChecks(fs: {
21
21
  */
22
22
  export function doctor(opts?: {
23
23
  fix?: boolean;
24
- }): Promise<void>;
24
+ }): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/cli",
3
- "version": "1.0.0-next.137",
3
+ "version": "1.0.0-next.139",
4
4
  "description": "Command line utilities for icon bundling and conversion.",
5
5
  "repository": {
6
6
  "type": "git",
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
- * Run all doctor checks against the given filesystem adapter.
8
- * @param {{ exists: (p: string) => boolean, read: (p: string) => string, resolve: (p: string) => string }} fs
9
- * @returns {Array<{ id: string, label: string, status: 'pass'|'fail', fixable: boolean, fix: string, autoFix?: string }>}
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
- export function runChecks(fs) {
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
- checks.push({
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
- // 2. uno.config.js uses presetRokkit
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
- let unoUsesPreset = false
29
- if (unoExists) {
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
- // 3. app.css has theme imports
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
- let cssHasBase = false
47
- if (cssExists) {
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
- // 4. app.html has init script
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
- let htmlHasScript = false
64
- if (htmlExists) {
65
- const html = fs.read(htmlPath)
66
- htmlHasScript = html.includes('rokkit-theme') || html.includes('data-mode')
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
- return checks
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
- let fixed = 0
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 !== 'fail' || !check.fixable) continue
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
- if (check.autoFix === 'patch-html') {
128
- const htmlPath = resolve(cwd, 'src/app.html')
129
- if (existsSync(htmlPath)) {
130
- const html = readFileSync(htmlPath, 'utf-8')
131
- const script = generateInitScript('manual', 'rokkit-theme')
132
- if (script && !html.includes('rokkit-theme')) {
133
- const patched = html.replace(/(<body[^>]*>)/, `$1\n${script}`)
134
- writeFileSync(htmlPath, patched)
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
- * Interactive doctor command — validates project setup.
147
- * @param {{ fix?: boolean }} [opts]
205
+ * Count failures and print status for each check.
206
+ * @param {Array} checks
207
+ * @returns {number}
148
208
  */
149
- export async function doctor(opts = {}) {
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
- if (failures > 0 && opts.fix) {
169
- console.info('\nAuto-fixing...\n')
170
- const fixed = autoFix(checks, cwd)
171
- const remaining = failures - fixed
172
- if (remaining > 0) {
173
- console.info(`\n${fixed} fixed, ${remaining} require manual action:`)
174
- for (const check of checks) {
175
- if (check.status === 'fail' && !check.fixable) {
176
- console.info(` - ${check.label}: ${check.fix}`)
177
- }
178
- }
179
- } else {
180
- console.info(`\nAll ${fixed} issues fixed!`)
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
- // --- rokkit.config.js ---
197
- const configPath = resolve(cwd, 'rokkit.config.js')
198
- if (existsSync(configPath)) {
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
  }