@rokkit/cli 1.0.0-next.127 → 1.0.0-next.129
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 +24 -0
- package/dist/src/init.d.ts +36 -0
- package/package.json +2 -1
- package/src/doctor.js +186 -0
- package/src/index.js +17 -0
- package/src/init.js +254 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run all doctor checks against the given filesystem adapter.
|
|
3
|
+
* @param {{ exists: (p: string) => boolean, read: (p: string) => string, resolve: (p: string) => string }} fs
|
|
4
|
+
* @returns {Array<{ id: string, label: string, status: 'pass'|'fail', fixable: boolean, fix: string, autoFix?: string }>}
|
|
5
|
+
*/
|
|
6
|
+
export function runChecks(fs: {
|
|
7
|
+
exists: (p: string) => boolean;
|
|
8
|
+
read: (p: string) => string;
|
|
9
|
+
resolve: (p: string) => string;
|
|
10
|
+
}): Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
status: "pass" | "fail";
|
|
14
|
+
fixable: boolean;
|
|
15
|
+
fix: string;
|
|
16
|
+
autoFix?: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Interactive doctor command — validates project setup.
|
|
20
|
+
* @param {{ fix?: boolean }} [opts]
|
|
21
|
+
*/
|
|
22
|
+
export function doctor(opts?: {
|
|
23
|
+
fix?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a Rokkit config object from user choices.
|
|
3
|
+
* @param {{ palette: string, customColors?: Record<string, string>, icons: string, iconPath?: string, themes: string[], switcher: string }} opts
|
|
4
|
+
* @returns {Record<string, unknown>}
|
|
5
|
+
*/
|
|
6
|
+
export function generateConfig({ palette, customColors, icons, iconPath, themes, switcher }: {
|
|
7
|
+
palette: string;
|
|
8
|
+
customColors?: Record<string, string>;
|
|
9
|
+
icons: string;
|
|
10
|
+
iconPath?: string;
|
|
11
|
+
themes: string[];
|
|
12
|
+
switcher: string;
|
|
13
|
+
}): Record<string, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Generate the content for uno.config.js.
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function generateUnoConfig(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Generate an array of CSS @import lines for app.css.
|
|
21
|
+
* @param {string[]} themes
|
|
22
|
+
* @returns {string[]}
|
|
23
|
+
*/
|
|
24
|
+
export function generateAppCssImports(themes: string[]): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Generate the flash-prevention script for app.html.
|
|
27
|
+
* Returns null when no script is needed (system switcher).
|
|
28
|
+
* @param {string} switcher — 'system' | 'manual' | 'full'
|
|
29
|
+
* @param {string} [storageKey='rokkit-theme']
|
|
30
|
+
* @returns {string | null}
|
|
31
|
+
*/
|
|
32
|
+
export function generateInitScript(switcher: string, storageKey?: string): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Interactive init command — prompts the user, writes config files.
|
|
35
|
+
*/
|
|
36
|
+
export function init(): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/cli",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.129",
|
|
4
4
|
"description": "Command line utilities for icon bundling and conversion.",
|
|
5
5
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"sade": "^1.8.1",
|
|
26
26
|
"@iconify/tools": "5.0.3",
|
|
27
27
|
"@iconify/types": "^2.0.0",
|
|
28
|
+
"prompts": "^2.4.2",
|
|
28
29
|
"ramda": "^0.32.0"
|
|
29
30
|
},
|
|
30
31
|
"files": [
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
import { generateUnoConfig, generateAppCssImports, generateInitScript } from './init.js'
|
|
5
|
+
|
|
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 }>}
|
|
10
|
+
*/
|
|
11
|
+
export function runChecks(fs) {
|
|
12
|
+
const checks = []
|
|
13
|
+
|
|
14
|
+
// 1. rokkit.config.js exists
|
|
15
|
+
const configExists = fs.exists(fs.resolve('rokkit.config.js'))
|
|
16
|
+
checks.push({
|
|
17
|
+
id: 'config-exists',
|
|
18
|
+
label: 'rokkit.config.js exists',
|
|
19
|
+
status: configExists ? 'pass' : 'fail',
|
|
20
|
+
fixable: true,
|
|
21
|
+
fix: 'Run `rokkit init` to generate config',
|
|
22
|
+
autoFix: 'generate-config'
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// 2. uno.config.js uses presetRokkit
|
|
26
|
+
const unoPath = fs.resolve('uno.config.js')
|
|
27
|
+
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({
|
|
34
|
+
id: 'uno-uses-preset',
|
|
35
|
+
label: 'uno.config.js uses presetRokkit()',
|
|
36
|
+
status: unoUsesPreset ? 'pass' : 'fail',
|
|
37
|
+
fixable: false,
|
|
38
|
+
fix: unoExists
|
|
39
|
+
? `Replace uno.config.js contents with:\n${ generateUnoConfig()}`
|
|
40
|
+
: `Create uno.config.js with:\n${ generateUnoConfig()}`
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// 3. app.css has theme imports
|
|
44
|
+
const cssPath = fs.resolve('src/app.css')
|
|
45
|
+
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({
|
|
52
|
+
id: 'css-imports',
|
|
53
|
+
label: 'app.css has theme imports',
|
|
54
|
+
status: cssHasBase ? 'pass' : 'fail',
|
|
55
|
+
fixable: true,
|
|
56
|
+
fix: 'Append theme imports to src/app.css',
|
|
57
|
+
autoFix: 'patch-css'
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// 4. app.html has init script
|
|
61
|
+
const htmlPath = fs.resolve('src/app.html')
|
|
62
|
+
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({
|
|
69
|
+
id: 'html-init-script',
|
|
70
|
+
label: 'app.html has theme init script',
|
|
71
|
+
status: htmlHasScript ? 'pass' : 'fail',
|
|
72
|
+
fixable: true,
|
|
73
|
+
fix: 'Add flash-prevention script to src/app.html',
|
|
74
|
+
autoFix: 'patch-html'
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return checks
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a real filesystem adapter rooted at cwd.
|
|
82
|
+
* @param {string} cwd
|
|
83
|
+
*/
|
|
84
|
+
function createFsAdapter(cwd) {
|
|
85
|
+
return {
|
|
86
|
+
exists: (p) => existsSync(p),
|
|
87
|
+
read: (p) => readFileSync(p, 'utf-8'),
|
|
88
|
+
resolve: (p) => resolve(cwd, p)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Auto-fix failed checks that are marked fixable.
|
|
94
|
+
* @param {Array<{ id: string, status: string, fixable: boolean, autoFix?: string }>} checks
|
|
95
|
+
* @param {string} cwd
|
|
96
|
+
* @returns {number} number of fixed items
|
|
97
|
+
*/
|
|
98
|
+
function autoFix(checks, cwd) {
|
|
99
|
+
let fixed = 0
|
|
100
|
+
|
|
101
|
+
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++
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return fixed
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Interactive doctor command — validates project setup.
|
|
147
|
+
* @param {{ fix?: boolean }} [opts]
|
|
148
|
+
*/
|
|
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
|
+
|
|
156
|
+
let failures = 0
|
|
157
|
+
for (const check of checks) {
|
|
158
|
+
const icon = check.status === 'pass' ? 'PASS' : 'FAIL'
|
|
159
|
+
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
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
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
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.info('')
|
|
185
|
+
process.exitCode = failures > 0 && !opts.fix ? 1 : 0
|
|
186
|
+
}
|
package/src/index.js
CHANGED
|
@@ -53,4 +53,21 @@ prog
|
|
|
53
53
|
convertFolders(folders, config, opts)
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
prog
|
|
57
|
+
.command('init')
|
|
58
|
+
.describe('Initialize Rokkit in an existing SvelteKit project')
|
|
59
|
+
.action(async (opts) => {
|
|
60
|
+
const { init } = await import('./init.js')
|
|
61
|
+
await init(opts)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
prog
|
|
65
|
+
.command('doctor')
|
|
66
|
+
.describe('Validate Rokkit project setup')
|
|
67
|
+
.option('--fix', 'Auto-fix safe issues')
|
|
68
|
+
.action(async (opts) => {
|
|
69
|
+
const { doctor } = await import('./doctor.js')
|
|
70
|
+
await doctor(opts)
|
|
71
|
+
})
|
|
72
|
+
|
|
56
73
|
prog.parse(process.argv)
|
package/src/init.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import prompts from 'prompts'
|
|
3
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs'
|
|
4
|
+
import { resolve } from 'path'
|
|
5
|
+
|
|
6
|
+
const SKIN_PRESETS = {
|
|
7
|
+
default: { primary: 'orange', secondary: 'pink', accent: 'sky', surface: 'slate' },
|
|
8
|
+
vibrant: { primary: 'blue', secondary: 'purple', accent: 'sky', surface: 'slate' },
|
|
9
|
+
seaweed: {
|
|
10
|
+
primary: 'sky',
|
|
11
|
+
secondary: 'green',
|
|
12
|
+
accent: 'blue',
|
|
13
|
+
surface: 'zinc',
|
|
14
|
+
danger: 'rose',
|
|
15
|
+
error: 'rose',
|
|
16
|
+
success: 'lime',
|
|
17
|
+
warning: 'amber',
|
|
18
|
+
info: 'indigo'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const DEFAULT_COLORS = {
|
|
23
|
+
primary: 'orange',
|
|
24
|
+
secondary: 'pink',
|
|
25
|
+
accent: 'sky',
|
|
26
|
+
surface: 'slate',
|
|
27
|
+
success: 'green',
|
|
28
|
+
warning: 'yellow',
|
|
29
|
+
danger: 'red',
|
|
30
|
+
error: 'red',
|
|
31
|
+
info: 'cyan'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a Rokkit config object from user choices.
|
|
36
|
+
* @param {{ palette: string, customColors?: Record<string, string>, icons: string, iconPath?: string, themes: string[], switcher: string }} opts
|
|
37
|
+
* @returns {Record<string, unknown>}
|
|
38
|
+
*/
|
|
39
|
+
export function generateConfig({ palette, customColors, icons, iconPath, themes, switcher }) {
|
|
40
|
+
const colors =
|
|
41
|
+
palette === 'custom'
|
|
42
|
+
? { ...DEFAULT_COLORS, ...customColors }
|
|
43
|
+
: { ...DEFAULT_COLORS, ...(SKIN_PRESETS[palette] || {}) }
|
|
44
|
+
|
|
45
|
+
const config = { colors, themes, switcher, storageKey: 'rokkit-theme' }
|
|
46
|
+
|
|
47
|
+
if (icons === 'custom' && iconPath) {
|
|
48
|
+
config.icons = { custom: iconPath }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return config
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generate the content for uno.config.js.
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
export function generateUnoConfig() {
|
|
59
|
+
return `import { defineConfig } from 'unocss'
|
|
60
|
+
import { presetRokkit } from '@rokkit/unocss'
|
|
61
|
+
|
|
62
|
+
export default defineConfig({
|
|
63
|
+
presets: [presetRokkit()]
|
|
64
|
+
})
|
|
65
|
+
`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate an array of CSS @import lines for app.css.
|
|
70
|
+
* @param {string[]} themes
|
|
71
|
+
* @returns {string[]}
|
|
72
|
+
*/
|
|
73
|
+
export function generateAppCssImports(themes) {
|
|
74
|
+
const lines = [
|
|
75
|
+
"@import '@unocss/reset/tailwind.css';",
|
|
76
|
+
"@import '@rokkit/themes/dist/base';"
|
|
77
|
+
]
|
|
78
|
+
for (const theme of themes) {
|
|
79
|
+
lines.push(`@import '@rokkit/themes/dist/${theme}';`)
|
|
80
|
+
}
|
|
81
|
+
return lines
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate the flash-prevention script for app.html.
|
|
86
|
+
* Returns null when no script is needed (system switcher).
|
|
87
|
+
* @param {string} switcher — 'system' | 'manual' | 'full'
|
|
88
|
+
* @param {string} [storageKey='rokkit-theme']
|
|
89
|
+
* @returns {string | null}
|
|
90
|
+
*/
|
|
91
|
+
export function generateInitScript(switcher, storageKey = 'rokkit-theme') {
|
|
92
|
+
if (switcher === 'system') return null
|
|
93
|
+
|
|
94
|
+
const setStyle =
|
|
95
|
+
switcher === 'full'
|
|
96
|
+
? `b.dataset.style = t.style || 'rokkit'\n `
|
|
97
|
+
: ''
|
|
98
|
+
|
|
99
|
+
return ` <script>
|
|
100
|
+
(function () {
|
|
101
|
+
try {
|
|
102
|
+
var t = JSON.parse(localStorage.getItem('${storageKey}') || '{}')
|
|
103
|
+
var b = document.body
|
|
104
|
+
${setStyle}b.dataset.mode = t.mode || 'dark'
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
})()
|
|
107
|
+
</script>`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Interactive init command — prompts the user, writes config files.
|
|
112
|
+
*/
|
|
113
|
+
export async function init() {
|
|
114
|
+
console.info('Rokkit Init — Setting up your SvelteKit project\n')
|
|
115
|
+
|
|
116
|
+
const response = await prompts([
|
|
117
|
+
{
|
|
118
|
+
type: 'select',
|
|
119
|
+
name: 'palette',
|
|
120
|
+
message: 'Color palette',
|
|
121
|
+
choices: [
|
|
122
|
+
{ title: 'Default (orange/pink/sky)', value: 'default' },
|
|
123
|
+
{ title: 'Vibrant (blue/purple/sky)', value: 'vibrant' },
|
|
124
|
+
{ title: 'Seaweed (sky/green/blue)', value: 'seaweed' },
|
|
125
|
+
{ title: 'Custom', value: 'custom' }
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
130
|
+
name: 'primary',
|
|
131
|
+
message: 'Primary color (Tailwind palette name)',
|
|
132
|
+
initial: 'orange'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
136
|
+
name: 'secondary',
|
|
137
|
+
message: 'Secondary color',
|
|
138
|
+
initial: 'pink'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
142
|
+
name: 'accent',
|
|
143
|
+
message: 'Accent color',
|
|
144
|
+
initial: 'sky'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
148
|
+
name: 'surface',
|
|
149
|
+
message: 'Surface color',
|
|
150
|
+
initial: 'slate'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'select',
|
|
154
|
+
name: 'icons',
|
|
155
|
+
message: 'Icon collections',
|
|
156
|
+
choices: [
|
|
157
|
+
{ title: 'Rokkit icons only', value: 'rokkit' },
|
|
158
|
+
{ title: 'Rokkit + custom collection', value: 'custom' }
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: (prev) => (prev === 'custom' ? 'text' : null),
|
|
163
|
+
name: 'iconPath',
|
|
164
|
+
message: 'Path to custom icon collection JSON',
|
|
165
|
+
initial: './static/icons/custom.json'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
type: 'multiselect',
|
|
169
|
+
name: 'themes',
|
|
170
|
+
message: 'Theme styles',
|
|
171
|
+
choices: [
|
|
172
|
+
{ title: 'Rokkit', value: 'rokkit', selected: true },
|
|
173
|
+
{ title: 'Minimal', value: 'minimal' },
|
|
174
|
+
{ title: 'Material', value: 'material' }
|
|
175
|
+
],
|
|
176
|
+
min: 1
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: 'select',
|
|
180
|
+
name: 'switcher',
|
|
181
|
+
message: 'Theme switching',
|
|
182
|
+
choices: [
|
|
183
|
+
{ title: 'System only (prefers-color-scheme)', value: 'system' },
|
|
184
|
+
{ title: 'Manual (light/dark toggle)', value: 'manual' },
|
|
185
|
+
{ title: 'Full (light/dark + style variants)', value: 'full' }
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
])
|
|
189
|
+
|
|
190
|
+
if (response.palette === 'custom') {
|
|
191
|
+
response.customColors = {
|
|
192
|
+
primary: response.primary,
|
|
193
|
+
secondary: response.secondary,
|
|
194
|
+
accent: response.accent,
|
|
195
|
+
surface: response.surface
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const config = generateConfig(response)
|
|
200
|
+
const cwd = process.cwd()
|
|
201
|
+
|
|
202
|
+
// --- rokkit.config.js ---
|
|
203
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
204
|
+
if (existsSync(configPath)) {
|
|
205
|
+
console.warn(' rokkit.config.js already exists — skipping')
|
|
206
|
+
} else {
|
|
207
|
+
writeFileSync(configPath, `export default ${JSON.stringify(config, null, 2)}\n`)
|
|
208
|
+
console.info(' Created rokkit.config.js')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- uno.config.js ---
|
|
212
|
+
const unoPath = resolve(cwd, 'uno.config.js')
|
|
213
|
+
if (existsSync(unoPath)) {
|
|
214
|
+
console.warn(' uno.config.js already exists — skipping (see rokkit doctor for migration)')
|
|
215
|
+
} else {
|
|
216
|
+
writeFileSync(unoPath, generateUnoConfig())
|
|
217
|
+
console.info(' Created uno.config.js')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --- src/app.css ---
|
|
221
|
+
const appCssPath = resolve(cwd, 'src/app.css')
|
|
222
|
+
const cssImports = generateAppCssImports(config.themes)
|
|
223
|
+
if (existsSync(appCssPath)) {
|
|
224
|
+
const existing = readFileSync(appCssPath, 'utf-8')
|
|
225
|
+
const missing = cssImports.filter((line) => !existing.includes(line))
|
|
226
|
+
if (missing.length > 0) {
|
|
227
|
+
writeFileSync(appCssPath, `${missing.join('\n') }\n${ existing}`)
|
|
228
|
+
console.info(` Patched app.css — added ${missing.length} imports`)
|
|
229
|
+
} else {
|
|
230
|
+
console.info(' app.css already has theme imports')
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
writeFileSync(appCssPath, `${cssImports.join('\n') }\n`)
|
|
234
|
+
console.info(' Created src/app.css')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// --- src/app.html (theme init script) ---
|
|
238
|
+
const initScript = generateInitScript(config.switcher, config.storageKey)
|
|
239
|
+
if (initScript) {
|
|
240
|
+
const appHtmlPath = resolve(cwd, 'src/app.html')
|
|
241
|
+
if (existsSync(appHtmlPath)) {
|
|
242
|
+
const html = readFileSync(appHtmlPath, 'utf-8')
|
|
243
|
+
if (!html.includes('rokkit-theme') && !html.includes(config.storageKey)) {
|
|
244
|
+
const patched = html.replace(/(<body[^>]*>)/, `$1\n${initScript}`)
|
|
245
|
+
writeFileSync(appHtmlPath, patched)
|
|
246
|
+
console.info(' Patched app.html — added theme init script')
|
|
247
|
+
} else {
|
|
248
|
+
console.info(' app.html already has init script')
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.info('\nDone! Run `rokkit doctor` to verify your setup.')
|
|
254
|
+
}
|