@rokkit/cli 1.0.0-next.145 → 1.0.0-next.146
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/skin.d.ts +51 -0
- package/dist/src/theme.d.ts +48 -0
- package/dist/src/upgrade.d.ts +51 -0
- package/package.json +1 -1
- package/src/doctor.js +8 -2
- package/src/index.js +43 -0
- package/src/skin.js +151 -0
- package/src/theme.js +162 -0
- package/src/upgrade.js +125 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a skin scaffold object with all token keys set to default color names.
|
|
3
|
+
* @returns {Record<string, string>}
|
|
4
|
+
*/
|
|
5
|
+
export function generateSkinScaffold(): Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Add a new skin entry to a config object (pure, no side effects).
|
|
8
|
+
* @param {Record<string, unknown>} config
|
|
9
|
+
* @param {string} name
|
|
10
|
+
* @returns {Record<string, unknown>}
|
|
11
|
+
*/
|
|
12
|
+
export function addSkinToConfig(config: Record<string, unknown>, name: string): Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Serialize a rokkit config back to a JS module string.
|
|
15
|
+
* @param {Record<string, unknown>} config
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function serializeConfig(config: Record<string, unknown>): string;
|
|
19
|
+
/**
|
|
20
|
+
* List all skins defined in rokkit.config.js.
|
|
21
|
+
* @param {{
|
|
22
|
+
* readConfig?: () => Record<string, unknown>,
|
|
23
|
+
* cwd?: string
|
|
24
|
+
* }} adapters
|
|
25
|
+
*/
|
|
26
|
+
export function runSkinList(adapters?: {
|
|
27
|
+
readConfig?: () => Record<string, unknown>;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Create a new skin entry in rokkit.config.js.
|
|
32
|
+
* @param {string} name
|
|
33
|
+
* @param {{
|
|
34
|
+
* readConfig?: () => Record<string, unknown>,
|
|
35
|
+
* writeConfig?: (config: Record<string, unknown>) => void,
|
|
36
|
+
* cwd?: string
|
|
37
|
+
* }} adapters
|
|
38
|
+
*/
|
|
39
|
+
export function runSkinCreate(name: string, adapters?: {
|
|
40
|
+
readConfig?: () => Record<string, unknown>;
|
|
41
|
+
writeConfig?: (config: Record<string, unknown>) => void;
|
|
42
|
+
cwd?: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* CLI entry point for `rokkit skin` subcommands.
|
|
46
|
+
* @param {string} sub — 'list' | 'create'
|
|
47
|
+
* @param {{ name?: string }} opts
|
|
48
|
+
*/
|
|
49
|
+
export function skin(sub: string, opts?: {
|
|
50
|
+
name?: string;
|
|
51
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a full CSS theme stub for the given style name.
|
|
3
|
+
* Produces one `[data-style='<name>']` block per component.
|
|
4
|
+
* @param {string} name
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function generateThemeStub(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* List all theme styles defined in rokkit.config.js plus any custom CSS files in src/themes/.
|
|
10
|
+
* @param {{
|
|
11
|
+
* readConfig?: () => Record<string, unknown>,
|
|
12
|
+
* listFiles?: (dir: string) => string[],
|
|
13
|
+
* cwd?: string
|
|
14
|
+
* }} adapters
|
|
15
|
+
*/
|
|
16
|
+
export function runThemeList(adapters?: {
|
|
17
|
+
readConfig?: () => Record<string, unknown>;
|
|
18
|
+
listFiles?: (dir: string) => string[];
|
|
19
|
+
cwd?: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Create a new custom theme CSS stub at src/themes/<name>.css.
|
|
23
|
+
* @param {string} name
|
|
24
|
+
* @param {{
|
|
25
|
+
* writeFile?: (path: string, content: string) => void,
|
|
26
|
+
* exists?: (path: string) => boolean,
|
|
27
|
+
* mkdir?: (dir: string) => void,
|
|
28
|
+
* cwd?: string
|
|
29
|
+
* }} adapters
|
|
30
|
+
*/
|
|
31
|
+
export function runThemeCreate(name: string, adapters?: {
|
|
32
|
+
writeFile?: (path: string, content: string) => void;
|
|
33
|
+
exists?: (path: string) => boolean;
|
|
34
|
+
mkdir?: (dir: string) => void;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* CLI entry point for `rokkit theme` subcommands.
|
|
39
|
+
* @param {string} sub — 'list' | 'create'
|
|
40
|
+
* @param {{ name?: string }} opts
|
|
41
|
+
*/
|
|
42
|
+
export function theme(sub: string, opts?: {
|
|
43
|
+
name?: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* All component names that ship with Rokkit — one CSS block will be generated for each.
|
|
47
|
+
*/
|
|
48
|
+
export const THEME_COMPONENTS: string[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract @rokkit/* packages from a parsed package.json object.
|
|
3
|
+
* @param {Record<string, unknown>} pkgJson
|
|
4
|
+
* @returns {{ name: string, current: string }[]}
|
|
5
|
+
*/
|
|
6
|
+
export function getRokkitPackages(pkgJson: Record<string, unknown>): {
|
|
7
|
+
name: string;
|
|
8
|
+
current: string;
|
|
9
|
+
}[];
|
|
10
|
+
/**
|
|
11
|
+
* Detect the package manager from lockfile presence.
|
|
12
|
+
* @param {string[]} lockfiles — list of lockfile names that exist in cwd
|
|
13
|
+
* @returns {'bun' | 'pnpm' | 'yarn' | 'npm'}
|
|
14
|
+
*/
|
|
15
|
+
export function detectPackageManager(lockfiles: string[]): "bun" | "pnpm" | "yarn" | "npm";
|
|
16
|
+
/**
|
|
17
|
+
* Build the install command for the given package manager.
|
|
18
|
+
* @param {'bun' | 'pnpm' | 'yarn' | 'npm'} pm
|
|
19
|
+
* @param {string[]} packages — package specs like `@rokkit/ui@1.2.3`
|
|
20
|
+
* @returns {{ bin: string, args: string[] }}
|
|
21
|
+
*/
|
|
22
|
+
export function buildInstallCommand(pm: "bun" | "pnpm" | "yarn" | "npm", packages: string[]): {
|
|
23
|
+
bin: string;
|
|
24
|
+
args: string[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Run upgrade check and optionally apply it.
|
|
28
|
+
*
|
|
29
|
+
* @param {{ apply?: boolean }} opts
|
|
30
|
+
* @param {{
|
|
31
|
+
* readFile?: (p: string) => string,
|
|
32
|
+
* exists?: (p: string) => boolean,
|
|
33
|
+
* fetchVersion?: (name: string) => string | null,
|
|
34
|
+
* runInstall?: (bin: string, args: string[]) => void
|
|
35
|
+
* }} adapters — injectable for testing
|
|
36
|
+
*/
|
|
37
|
+
export function runUpgrade(opts?: {
|
|
38
|
+
apply?: boolean;
|
|
39
|
+
}, adapters?: {
|
|
40
|
+
readFile?: (p: string) => string;
|
|
41
|
+
exists?: (p: string) => boolean;
|
|
42
|
+
fetchVersion?: (name: string) => string | null;
|
|
43
|
+
runInstall?: (bin: string, args: string[]) => void;
|
|
44
|
+
}): void;
|
|
45
|
+
/**
|
|
46
|
+
* CLI entry point for `rokkit upgrade`
|
|
47
|
+
* @param {{ apply?: boolean }} opts
|
|
48
|
+
*/
|
|
49
|
+
export function upgrade(opts?: {
|
|
50
|
+
apply?: boolean;
|
|
51
|
+
}): void;
|
package/package.json
CHANGED
package/src/doctor.js
CHANGED
|
@@ -150,8 +150,14 @@ function applyPatchHtml(cwd, label) {
|
|
|
150
150
|
|
|
151
151
|
/** @type {Record<string, (cwd: string, label: string) => boolean|void>} */
|
|
152
152
|
const FIX_HANDLERS = {
|
|
153
|
-
'generate-config': (cwd, label) => {
|
|
154
|
-
|
|
153
|
+
'generate-config': (cwd, label) => {
|
|
154
|
+
applyGenerateConfig(cwd, label)
|
|
155
|
+
return true
|
|
156
|
+
},
|
|
157
|
+
'patch-css': (cwd, label) => {
|
|
158
|
+
applyPatchCss(cwd, label)
|
|
159
|
+
return true
|
|
160
|
+
},
|
|
155
161
|
'patch-html': (cwd, label) => applyPatchHtml(cwd, label)
|
|
156
162
|
}
|
|
157
163
|
|
package/src/index.js
CHANGED
|
@@ -70,4 +70,47 @@ prog
|
|
|
70
70
|
await doctor(opts)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
|
+
prog
|
|
74
|
+
.command('upgrade')
|
|
75
|
+
.describe('Check for @rokkit/* package updates and optionally install them')
|
|
76
|
+
.option('--apply', 'Install available upgrades')
|
|
77
|
+
.action(async (opts) => {
|
|
78
|
+
const { upgrade } = await import('./upgrade.js')
|
|
79
|
+
upgrade(opts)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
prog
|
|
83
|
+
.command('skin list')
|
|
84
|
+
.describe('List all skins defined in rokkit.config.js')
|
|
85
|
+
.action(async () => {
|
|
86
|
+
const { skin } = await import('./skin.js')
|
|
87
|
+
await skin('list')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
prog
|
|
91
|
+
.command('skin create')
|
|
92
|
+
.describe('Scaffold a new skin entry in rokkit.config.js')
|
|
93
|
+
.option('--name', 'Skin name')
|
|
94
|
+
.action(async (opts) => {
|
|
95
|
+
const { skin } = await import('./skin.js')
|
|
96
|
+
await skin('create', opts)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
prog
|
|
100
|
+
.command('theme list')
|
|
101
|
+
.describe('List built-in and custom themes')
|
|
102
|
+
.action(async () => {
|
|
103
|
+
const { theme } = await import('./theme.js')
|
|
104
|
+
await theme('list')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
prog
|
|
108
|
+
.command('theme create')
|
|
109
|
+
.describe('Scaffold a new custom theme CSS file')
|
|
110
|
+
.option('--name', 'Theme name')
|
|
111
|
+
.action(async (opts) => {
|
|
112
|
+
const { theme } = await import('./theme.js')
|
|
113
|
+
await theme('create', opts)
|
|
114
|
+
})
|
|
115
|
+
|
|
73
116
|
prog.parse(process.argv)
|
package/src/skin.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { writeFileSync, existsSync } from 'fs'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
|
|
5
|
+
const SKIN_TOKEN_DEFAULTS = {
|
|
6
|
+
primary: 'orange',
|
|
7
|
+
secondary: 'pink',
|
|
8
|
+
accent: 'sky',
|
|
9
|
+
surface: 'slate',
|
|
10
|
+
success: 'green',
|
|
11
|
+
warning: 'yellow',
|
|
12
|
+
danger: 'red',
|
|
13
|
+
error: 'red',
|
|
14
|
+
info: 'cyan'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate a skin scaffold object with all token keys set to default color names.
|
|
19
|
+
* @returns {Record<string, string>}
|
|
20
|
+
*/
|
|
21
|
+
export function generateSkinScaffold() {
|
|
22
|
+
return { ...SKIN_TOKEN_DEFAULTS }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Add a new skin entry to a config object (pure, no side effects).
|
|
27
|
+
* @param {Record<string, unknown>} config
|
|
28
|
+
* @param {string} name
|
|
29
|
+
* @returns {Record<string, unknown>}
|
|
30
|
+
*/
|
|
31
|
+
export function addSkinToConfig(config, name) {
|
|
32
|
+
const skins = config.skins ?? {}
|
|
33
|
+
return {
|
|
34
|
+
...config,
|
|
35
|
+
skins: { ...skins, [name]: generateSkinScaffold() }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Serialize a rokkit config back to a JS module string.
|
|
41
|
+
* @param {Record<string, unknown>} config
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
export function serializeConfig(config) {
|
|
45
|
+
return `export default ${JSON.stringify(config, null, 2)}\n`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* List all skins defined in rokkit.config.js.
|
|
50
|
+
* @param {{
|
|
51
|
+
* readConfig?: () => Record<string, unknown>,
|
|
52
|
+
* cwd?: string
|
|
53
|
+
* }} adapters
|
|
54
|
+
*/
|
|
55
|
+
export async function runSkinList(adapters = {}) {
|
|
56
|
+
const config = await loadConfig(adapters)
|
|
57
|
+
if (!config) return
|
|
58
|
+
const skins = config.skins ?? {}
|
|
59
|
+
const names = Object.keys(skins)
|
|
60
|
+
if (names.length === 0) {
|
|
61
|
+
console.info('No skins defined in rokkit.config.js.')
|
|
62
|
+
console.info('Run `rokkit skin create --name <name>` to add one.')
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
console.info('Defined skins:\n')
|
|
66
|
+
for (const name of names) {
|
|
67
|
+
const tokens = Object.entries(skins[name])
|
|
68
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
69
|
+
.join(', ')
|
|
70
|
+
console.info(` ${name} — ${tokens}`)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a new skin entry in rokkit.config.js.
|
|
76
|
+
* @param {string} name
|
|
77
|
+
* @param {{
|
|
78
|
+
* readConfig?: () => Record<string, unknown>,
|
|
79
|
+
* writeConfig?: (config: Record<string, unknown>) => void,
|
|
80
|
+
* cwd?: string
|
|
81
|
+
* }} adapters
|
|
82
|
+
*/
|
|
83
|
+
export async function runSkinCreate(name, adapters = {}) {
|
|
84
|
+
if (!name) {
|
|
85
|
+
console.error('Usage: rokkit skin create --name <name>')
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const config = await loadConfig(adapters)
|
|
90
|
+
if (!config) return
|
|
91
|
+
|
|
92
|
+
if (config.skins?.[name]) {
|
|
93
|
+
console.warn(`Skin "${name}" already exists in rokkit.config.js.`)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const updated = addSkinToConfig(config, name)
|
|
98
|
+
await saveConfig(updated, adapters)
|
|
99
|
+
|
|
100
|
+
console.info(`Added skin "${name}" to rokkit.config.js`)
|
|
101
|
+
console.info(
|
|
102
|
+
`\nCustomize the color tokens in rokkit.config.js → skins.${name}\n` +
|
|
103
|
+
`Then apply with: <body data-skin="${name}">`
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load rokkit.config.js using injected adapter or real filesystem.
|
|
109
|
+
* @param {{ readConfig?: () => Record<string, unknown>, cwd?: string }} adapters
|
|
110
|
+
* @returns {Promise<Record<string, unknown> | null>}
|
|
111
|
+
*/
|
|
112
|
+
async function loadConfig(adapters) {
|
|
113
|
+
if (adapters.readConfig) return adapters.readConfig()
|
|
114
|
+
|
|
115
|
+
const cwd = adapters.cwd ?? process.cwd()
|
|
116
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
117
|
+
if (!existsSync(configPath)) {
|
|
118
|
+
console.error('rokkit.config.js not found. Run `rokkit init` first.')
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { default: config } = await import(`file://${configPath}?t=${Date.now()}`)
|
|
123
|
+
return config
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Write updated config using injected adapter or real filesystem.
|
|
128
|
+
* @param {Record<string, unknown>} config
|
|
129
|
+
* @param {{ writeConfig?: (config: Record<string, unknown>) => void, cwd?: string }} adapters
|
|
130
|
+
*/
|
|
131
|
+
async function saveConfig(config, adapters) {
|
|
132
|
+
if (adapters.writeConfig) {
|
|
133
|
+
adapters.writeConfig(config)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
const cwd = adapters.cwd ?? process.cwd()
|
|
137
|
+
writeFileSync(resolve(cwd, 'rokkit.config.js'), serializeConfig(config))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* CLI entry point for `rokkit skin` subcommands.
|
|
142
|
+
* @param {string} sub — 'list' | 'create'
|
|
143
|
+
* @param {{ name?: string }} opts
|
|
144
|
+
*/
|
|
145
|
+
export async function skin(sub, opts = {}) {
|
|
146
|
+
if (sub === 'create') {
|
|
147
|
+
await runSkinCreate(opts.name)
|
|
148
|
+
} else {
|
|
149
|
+
await runSkinList()
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/theme.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs'
|
|
3
|
+
import { resolve, join } from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* All component names that ship with Rokkit — one CSS block will be generated for each.
|
|
7
|
+
*/
|
|
8
|
+
export const THEME_COMPONENTS = [
|
|
9
|
+
'button',
|
|
10
|
+
'card',
|
|
11
|
+
'connector',
|
|
12
|
+
'dropdown',
|
|
13
|
+
'floating-action',
|
|
14
|
+
'floating-navigation',
|
|
15
|
+
'grid',
|
|
16
|
+
'input',
|
|
17
|
+
'list',
|
|
18
|
+
'menu',
|
|
19
|
+
'message',
|
|
20
|
+
'range',
|
|
21
|
+
'search-filter',
|
|
22
|
+
'select',
|
|
23
|
+
'status-list',
|
|
24
|
+
'switch',
|
|
25
|
+
'table',
|
|
26
|
+
'tabs',
|
|
27
|
+
'timeline',
|
|
28
|
+
'toc',
|
|
29
|
+
'toggle',
|
|
30
|
+
'toolbar',
|
|
31
|
+
'tree',
|
|
32
|
+
'upload-progress',
|
|
33
|
+
'upload-target'
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate a full CSS theme stub for the given style name.
|
|
38
|
+
* Produces one `[data-style='<name>']` block per component.
|
|
39
|
+
* @param {string} name
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export function generateThemeStub(name) {
|
|
43
|
+
const header = `/* Custom theme: ${name} */
|
|
44
|
+
/* Apply this theme with: <body data-style="${name}"> */
|
|
45
|
+
/* Customize each component block below using CSS custom properties and UnoCSS utilities. */
|
|
46
|
+
/* Reference: https://rokkit.dev/docs/theming */
|
|
47
|
+
|
|
48
|
+
`
|
|
49
|
+
const blocks = THEME_COMPONENTS.map((component) => {
|
|
50
|
+
const selector = `[data-style='${name}'] [data-${component}]`
|
|
51
|
+
return `${selector} {\n /* ${component} styles */\n}\n`
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return header + blocks.join('\n')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List all theme styles defined in rokkit.config.js plus any custom CSS files in src/themes/.
|
|
59
|
+
* @param {{
|
|
60
|
+
* readConfig?: () => Record<string, unknown>,
|
|
61
|
+
* listFiles?: (dir: string) => string[],
|
|
62
|
+
* cwd?: string
|
|
63
|
+
* }} adapters
|
|
64
|
+
*/
|
|
65
|
+
export async function runThemeList(adapters = {}) {
|
|
66
|
+
const config = await loadConfig(adapters)
|
|
67
|
+
const builtIn = config?.themes ?? []
|
|
68
|
+
|
|
69
|
+
const cwd = adapters.cwd ?? process.cwd()
|
|
70
|
+
const themesDir = resolve(cwd, 'src/themes')
|
|
71
|
+
const listFiles = adapters.listFiles ?? ((dir) => (existsSync(dir) ? readdirSync(dir) : []))
|
|
72
|
+
const custom = listFiles(themesDir)
|
|
73
|
+
.filter((f) => f.endsWith('.css'))
|
|
74
|
+
.map((f) => f.replace(/\.css$/, ''))
|
|
75
|
+
|
|
76
|
+
console.info('Rokkit Themes\n')
|
|
77
|
+
|
|
78
|
+
if (builtIn.length > 0) {
|
|
79
|
+
console.info(' Built-in (from @rokkit/themes):')
|
|
80
|
+
for (const t of builtIn) console.info(` ${t}`)
|
|
81
|
+
}
|
|
82
|
+
if (custom.length > 0) {
|
|
83
|
+
console.info('\n Custom (src/themes/):')
|
|
84
|
+
for (const t of custom) console.info(` ${t}`)
|
|
85
|
+
}
|
|
86
|
+
if (builtIn.length === 0 && custom.length === 0) {
|
|
87
|
+
console.info(' No themes found.')
|
|
88
|
+
console.info(' Run `rokkit theme create --name <name>` to scaffold a custom theme.')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a new custom theme CSS stub at src/themes/<name>.css.
|
|
94
|
+
* @param {string} name
|
|
95
|
+
* @param {{
|
|
96
|
+
* writeFile?: (path: string, content: string) => void,
|
|
97
|
+
* exists?: (path: string) => boolean,
|
|
98
|
+
* mkdir?: (dir: string) => void,
|
|
99
|
+
* cwd?: string
|
|
100
|
+
* }} adapters
|
|
101
|
+
*/
|
|
102
|
+
export async function runThemeCreate(name, adapters = {}) {
|
|
103
|
+
if (!name) {
|
|
104
|
+
console.error('Usage: rokkit theme create --name <name>')
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cwd = adapters.cwd ?? process.cwd()
|
|
109
|
+
const writeFile = adapters.writeFile ?? writeFileSync
|
|
110
|
+
const exists = adapters.exists ?? existsSync
|
|
111
|
+
const mkdir = adapters.mkdir ?? ((dir) => mkdirSync(dir, { recursive: true }))
|
|
112
|
+
|
|
113
|
+
const themesDir = resolve(cwd, 'src/themes')
|
|
114
|
+
const filePath = join(themesDir, `${name}.css`)
|
|
115
|
+
|
|
116
|
+
if (exists(filePath)) {
|
|
117
|
+
console.warn(`Theme file already exists: src/themes/${name}.css`)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!exists(themesDir)) mkdir(themesDir)
|
|
122
|
+
|
|
123
|
+
writeFile(filePath, generateThemeStub(name))
|
|
124
|
+
console.info(`Created src/themes/${name}.css`)
|
|
125
|
+
console.info(
|
|
126
|
+
`\nImport it in src/app.css:\n @import './themes/${name}.css';\n` +
|
|
127
|
+
`\nApply it: <body data-style="${name}">`
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load rokkit.config.js using injected adapter or real filesystem.
|
|
133
|
+
* Returns null (not an error) if config is absent — theme list can still show custom files.
|
|
134
|
+
* @param {{ readConfig?: () => Record<string, unknown>, cwd?: string }} adapters
|
|
135
|
+
*/
|
|
136
|
+
async function loadConfig(adapters) {
|
|
137
|
+
if (adapters.readConfig) return adapters.readConfig()
|
|
138
|
+
|
|
139
|
+
const cwd = adapters.cwd ?? process.cwd()
|
|
140
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
141
|
+
if (!existsSync(configPath)) return null
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const { default: config } = await import(`file://${configPath}?t=${Date.now()}`)
|
|
145
|
+
return config
|
|
146
|
+
} catch {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* CLI entry point for `rokkit theme` subcommands.
|
|
153
|
+
* @param {string} sub — 'list' | 'create'
|
|
154
|
+
* @param {{ name?: string }} opts
|
|
155
|
+
*/
|
|
156
|
+
export async function theme(sub, opts = {}) {
|
|
157
|
+
if (sub === 'create') {
|
|
158
|
+
await runThemeCreate(opts.name)
|
|
159
|
+
} else {
|
|
160
|
+
await runThemeList()
|
|
161
|
+
}
|
|
162
|
+
}
|
package/src/upgrade.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { readFileSync, existsSync } from 'fs'
|
|
3
|
+
import { resolve } from 'path'
|
|
4
|
+
import { execFileSync } from 'child_process'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract @rokkit/* packages from a parsed package.json object.
|
|
8
|
+
* @param {Record<string, unknown>} pkgJson
|
|
9
|
+
* @returns {{ name: string, current: string }[]}
|
|
10
|
+
*/
|
|
11
|
+
export function getRokkitPackages(pkgJson) {
|
|
12
|
+
const deps = { ...(pkgJson.dependencies || {}), ...(pkgJson.devDependencies || {}) }
|
|
13
|
+
return Object.entries(deps)
|
|
14
|
+
.filter(([name]) => name.startsWith('@rokkit/'))
|
|
15
|
+
.map(([name, version]) => ({ name, current: String(version).replace(/^[\^~>=<]+/, '') }))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detect the package manager from lockfile presence.
|
|
20
|
+
* @param {string[]} lockfiles — list of lockfile names that exist in cwd
|
|
21
|
+
* @returns {'bun' | 'pnpm' | 'yarn' | 'npm'}
|
|
22
|
+
*/
|
|
23
|
+
export function detectPackageManager(lockfiles) {
|
|
24
|
+
if (lockfiles.includes('bun.lock') || lockfiles.includes('bun.lockb')) return 'bun'
|
|
25
|
+
if (lockfiles.includes('pnpm-lock.yaml')) return 'pnpm'
|
|
26
|
+
if (lockfiles.includes('yarn.lock')) return 'yarn'
|
|
27
|
+
return 'npm'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the install command for the given package manager.
|
|
32
|
+
* @param {'bun' | 'pnpm' | 'yarn' | 'npm'} pm
|
|
33
|
+
* @param {string[]} packages — package specs like `@rokkit/ui@1.2.3`
|
|
34
|
+
* @returns {{ bin: string, args: string[] }}
|
|
35
|
+
*/
|
|
36
|
+
export function buildInstallCommand(pm, packages) {
|
|
37
|
+
if (pm === 'bun') return { bin: 'bun', args: ['add', ...packages] }
|
|
38
|
+
if (pm === 'pnpm') return { bin: 'pnpm', args: ['add', ...packages] }
|
|
39
|
+
if (pm === 'yarn') return { bin: 'yarn', args: ['add', ...packages] }
|
|
40
|
+
return { bin: 'npm', args: ['install', ...packages] }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run upgrade check and optionally apply it.
|
|
45
|
+
*
|
|
46
|
+
* @param {{ apply?: boolean }} opts
|
|
47
|
+
* @param {{
|
|
48
|
+
* readFile?: (p: string) => string,
|
|
49
|
+
* exists?: (p: string) => boolean,
|
|
50
|
+
* fetchVersion?: (name: string) => string | null,
|
|
51
|
+
* runInstall?: (bin: string, args: string[]) => void
|
|
52
|
+
* }} adapters — injectable for testing
|
|
53
|
+
*/
|
|
54
|
+
export function runUpgrade(opts = {}, adapters = {}) {
|
|
55
|
+
const cwd = process.cwd()
|
|
56
|
+
const readFile = adapters.readFile ?? ((p) => readFileSync(p, 'utf-8'))
|
|
57
|
+
const exists = adapters.exists ?? ((p) => existsSync(p))
|
|
58
|
+
const fetchVersion =
|
|
59
|
+
adapters.fetchVersion ??
|
|
60
|
+
((name) => {
|
|
61
|
+
try {
|
|
62
|
+
return execFileSync('npm', ['view', name, 'version'], { encoding: 'utf-8' }).trim()
|
|
63
|
+
} catch {
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
const runInstall =
|
|
68
|
+
adapters.runInstall ??
|
|
69
|
+
((bin, args) => execFileSync(bin, args, { stdio: 'inherit' }))
|
|
70
|
+
|
|
71
|
+
const pkgPath = resolve(cwd, 'package.json')
|
|
72
|
+
if (!exists(pkgPath)) {
|
|
73
|
+
console.error('No package.json found in current directory.')
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pkgJson = JSON.parse(readFile(pkgPath))
|
|
78
|
+
const packages = getRokkitPackages(pkgJson)
|
|
79
|
+
|
|
80
|
+
if (packages.length === 0) {
|
|
81
|
+
console.info('No @rokkit/* packages found in package.json.')
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const LOCKFILES = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json']
|
|
86
|
+
const presentLockfiles = LOCKFILES.filter((f) => exists(resolve(cwd, f)))
|
|
87
|
+
const pm = detectPackageManager(presentLockfiles)
|
|
88
|
+
|
|
89
|
+
console.info('Rokkit Upgrade\n')
|
|
90
|
+
|
|
91
|
+
const upgrades = []
|
|
92
|
+
for (const pkg of packages) {
|
|
93
|
+
const latest = fetchVersion(pkg.name)
|
|
94
|
+
const upToDate = latest && latest === pkg.current
|
|
95
|
+
const status = !latest ? '?' : upToDate ? 'up-to-date' : 'outdated'
|
|
96
|
+
const arrow = latest ? `${pkg.current} → ${latest}` : pkg.current
|
|
97
|
+
console.info(` ${status === 'outdated' ? 'OUT' : 'OK '} ${pkg.name} ${arrow}`)
|
|
98
|
+
if (!upToDate && latest) upgrades.push(`${pkg.name}@${latest}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (upgrades.length === 0) {
|
|
102
|
+
console.info('\nAll packages are up to date.')
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { bin, args } = buildInstallCommand(pm, upgrades)
|
|
107
|
+
const cmdStr = `${bin} ${args.join(' ')}`
|
|
108
|
+
|
|
109
|
+
if (opts.apply) {
|
|
110
|
+
console.info(`\nRunning: ${cmdStr}\n`)
|
|
111
|
+
runInstall(bin, args)
|
|
112
|
+
console.info('\nUpgrade complete.')
|
|
113
|
+
} else {
|
|
114
|
+
console.info(`\n${upgrades.length} package(s) can be upgraded.`)
|
|
115
|
+
console.info(`Run with --apply to install, or manually:\n ${cmdStr}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* CLI entry point for `rokkit upgrade`
|
|
121
|
+
* @param {{ apply?: boolean }} opts
|
|
122
|
+
*/
|
|
123
|
+
export function upgrade(opts = {}) {
|
|
124
|
+
runUpgrade(opts)
|
|
125
|
+
}
|