novaui-cli 1.1.2 → 1.1.3
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/README.md +111 -0
- package/package.json +33 -13
- package/src/__tests__/commands.test.js +491 -0
- package/src/__tests__/fuzzy.test.js +150 -0
- package/src/__tests__/helpers.test.js +520 -0
- package/src/__tests__/preflight.test.js +379 -0
- package/src/__tests__/version-check.test.js +58 -0
- package/src/bin.js +51 -46
- package/src/commands/add.js +33 -36
- package/src/commands/init.js +232 -137
- package/src/constants.js +86 -5
- package/src/themes/index.js +45 -14
- package/src/utils/config.js +11 -11
- package/src/utils/deps.js +24 -20
- package/src/utils/fetch.js +9 -9
- package/src/utils/fs-helpers.js +8 -8
- package/src/utils/fuzzy.js +99 -0
- package/src/utils/preflight.js +172 -0
- package/src/utils/version-check.js +136 -0
- package/src/components.json +0 -6
- package/src/global.css +0 -50
- package/src/tailwind.config.js +0 -54
package/src/commands/add.js
CHANGED
|
@@ -4,12 +4,37 @@ import ora from 'ora'
|
|
|
4
4
|
import { multiselect, isCancel, cancel } from '@clack/prompts'
|
|
5
5
|
import pc from 'picocolors'
|
|
6
6
|
|
|
7
|
-
import { DEFAULT_CONFIG, UTILS_CONTENT, BASE_URL } from '../constants.js'
|
|
7
|
+
import { DEFAULT_CONFIG, UTILS_CONTENT, BASE_URL, REGISTRY_URL } from '../constants.js'
|
|
8
8
|
import { loadConfig } from '../utils/config.js'
|
|
9
9
|
import { ensureDir } from '../utils/fs-helpers.js'
|
|
10
10
|
import { getMissingDeps, installPackages, getInstallHint } from '../utils/deps.js'
|
|
11
11
|
import { fetchWithTimeout } from '../utils/fetch.js'
|
|
12
12
|
import { assertValidComponentConfig } from '../utils/validate.js'
|
|
13
|
+
import { runAddPreflightChecks } from '../utils/preflight.js'
|
|
14
|
+
import { formatComponentNotFoundError } from '../utils/fuzzy.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetch the registry from GitHub.
|
|
18
|
+
* Always gets the latest registry from the main branch.
|
|
19
|
+
*/
|
|
20
|
+
async function fetchRegistry() {
|
|
21
|
+
const spinner = ora({ text: 'Loading component registry...', stream: process.stderr }).start()
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetchWithTimeout(REGISTRY_URL)
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`Failed to fetch registry: ${response.status} ${response.statusText}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const registry = await response.json()
|
|
31
|
+
spinner.stop()
|
|
32
|
+
return registry
|
|
33
|
+
} catch (error) {
|
|
34
|
+
spinner.fail('Failed to load registry')
|
|
35
|
+
throw new Error(`Could not fetch registry from GitHub: ${error.message}`)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
13
38
|
|
|
14
39
|
/**
|
|
15
40
|
* Fetch the component registry and present an interactive multi-select for
|
|
@@ -17,24 +42,10 @@ import { assertValidComponentConfig } from '../utils/validate.js'
|
|
|
17
42
|
* Only called in TTY mode.
|
|
18
43
|
*/
|
|
19
44
|
export async function pickComponentsInteractively() {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
let registry
|
|
23
|
-
try {
|
|
24
|
-
const res = await fetchWithTimeout(`${BASE_URL}/registry.json`)
|
|
25
|
-
if (!res.ok) {
|
|
26
|
-
spinner.fail(pc.red(` Failed to fetch registry: ${res.statusText}`))
|
|
27
|
-
throw new Error(`Failed to fetch registry: ${res.statusText}`)
|
|
28
|
-
}
|
|
29
|
-
registry = await res.json()
|
|
30
|
-
spinner.succeed(' Registry loaded')
|
|
31
|
-
} catch (err) {
|
|
32
|
-
spinner.fail()
|
|
33
|
-
throw err
|
|
34
|
-
}
|
|
45
|
+
const registry = await fetchRegistry()
|
|
35
46
|
|
|
36
47
|
if (!registry || typeof registry !== 'object' || Array.isArray(registry)) {
|
|
37
|
-
throw new Error('Registry
|
|
48
|
+
throw new Error('Registry is not a valid object.')
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
const selected = await multiselect({
|
|
@@ -65,36 +76,22 @@ export async function add(componentName, options = {}) {
|
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
const cwd = process.cwd()
|
|
79
|
+
runAddPreflightChecks(cwd)
|
|
68
80
|
|
|
69
81
|
console.log('')
|
|
70
82
|
console.log(` ◆ NovaUI – Adding "${componentName}"...`)
|
|
71
83
|
console.log('')
|
|
72
84
|
|
|
73
|
-
// ─── 1. Fetch registry
|
|
74
|
-
|
|
75
|
-
const registrySpinner = ora({ text: ' Fetching registry...', stream: process.stderr }).start()
|
|
85
|
+
// ─── 1. Fetch registry ────────────────────────────────────────────────────
|
|
76
86
|
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const registryResponse = await fetchWithTimeout(`${BASE_URL}/registry.json`)
|
|
80
|
-
if (!registryResponse.ok) {
|
|
81
|
-
registrySpinner.fail(pc.red(` Failed to fetch registry: ${registryResponse.statusText}`))
|
|
82
|
-
throw new Error(`Failed to fetch registry: ${registryResponse.statusText}`)
|
|
83
|
-
}
|
|
84
|
-
registry = await registryResponse.json()
|
|
85
|
-
registrySpinner.succeed(pc.green(' Registry loaded'))
|
|
86
|
-
} catch (err) {
|
|
87
|
-
registrySpinner.fail()
|
|
88
|
-
throw err
|
|
89
|
-
}
|
|
87
|
+
const registry = await fetchRegistry()
|
|
90
88
|
|
|
91
89
|
if (!registry || typeof registry !== 'object' || Array.isArray(registry)) {
|
|
92
|
-
throw new Error('Registry
|
|
90
|
+
throw new Error('Registry is not a valid object.')
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
if (!registry[componentName]) {
|
|
96
|
-
|
|
97
|
-
throw new Error(`Component "${componentName}" not found in registry.\n\n Available components:\n ${available}`)
|
|
94
|
+
throw new Error(formatComponentNotFoundError(componentName, Object.keys(registry)))
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
const componentConfig = registry[componentName]
|
package/src/commands/init.js
CHANGED
|
@@ -1,52 +1,66 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
text,
|
|
4
|
+
select,
|
|
5
|
+
confirm,
|
|
6
|
+
isCancel,
|
|
7
|
+
cancel,
|
|
8
|
+
intro,
|
|
9
|
+
outro,
|
|
10
|
+
} from "@clack/prompts";
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
|
|
13
|
+
import { THEME_KEYS, getThemeCssContent } from "../themes/index.js";
|
|
14
|
+
import { DEFAULT_CONFIG, UTILS_CONTENT, BABEL_CONFIG_CONTENT, METRO_CONFIG_CONTENT } from "../constants.js";
|
|
15
|
+
import { loadConfig, writeConfig } from "../utils/config.js";
|
|
16
|
+
import { ensureDir, writeIfNotExists } from "../utils/fs-helpers.js";
|
|
17
|
+
import {
|
|
18
|
+
getMissingDeps,
|
|
19
|
+
installPackages,
|
|
20
|
+
getInstallHint,
|
|
21
|
+
} from "../utils/deps.js";
|
|
22
|
+
import { getTailwindConfigContent } from "../utils/tailwind.js";
|
|
23
|
+
import { runInitPreflightChecks } from "../utils/preflight.js";
|
|
11
24
|
|
|
12
25
|
// ─── ASCII Banner ────────────────────────────────────────────────────────────
|
|
13
26
|
|
|
14
27
|
export const ASCII_BANNER = `
|
|
15
|
-
${pc.cyan(
|
|
16
|
-
${pc.cyan(
|
|
17
|
-
${pc.cyan(
|
|
18
|
-
${pc.cyan(
|
|
19
|
-
${pc.cyan(
|
|
20
|
-
${pc.cyan(
|
|
21
|
-
${pc.dim(
|
|
22
|
-
|
|
28
|
+
${pc.cyan(" ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ██╗ ██╗██╗")}
|
|
29
|
+
${pc.cyan(" ████╗ ██║██╔═══██╗██║ ██║██╔══██╗ ██║ ██║██║")}
|
|
30
|
+
${pc.cyan(" ██╔██╗ ██║██║ ██║██║ ██║███████║ ██║ ██║██║")}
|
|
31
|
+
${pc.cyan(" ██║╚██╗██║██║ ██║╚██╗ ██╔╝██╔══██║ ██║ ██║██║")}
|
|
32
|
+
${pc.cyan(" ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ╚██████╔╝██║")}
|
|
33
|
+
${pc.cyan(" ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝")}
|
|
34
|
+
${pc.dim(" React Native + NativeWind UI")}
|
|
35
|
+
`;
|
|
23
36
|
|
|
24
37
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
25
38
|
|
|
26
39
|
function normalizeTheme(themeName) {
|
|
27
|
-
if (typeof themeName !==
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
40
|
+
if (typeof themeName !== "string" || themeName.trim() === "")
|
|
41
|
+
return DEFAULT_CONFIG.theme;
|
|
42
|
+
const normalized = themeName.trim().toLowerCase();
|
|
43
|
+
if (!THEME_KEYS.includes(normalized)) return DEFAULT_CONFIG.theme;
|
|
44
|
+
return normalized;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
/** Prompt for a theme selection; returns default immediately in non-TTY mode. */
|
|
34
48
|
export async function askTheme(defaultTheme = DEFAULT_CONFIG.theme) {
|
|
35
|
-
const normalizedDefault = normalizeTheme(defaultTheme)
|
|
36
|
-
if (process.stdin.isTTY !== true) return normalizedDefault
|
|
49
|
+
const normalizedDefault = normalizeTheme(defaultTheme);
|
|
50
|
+
if (process.stdin.isTTY !== true) return normalizedDefault;
|
|
37
51
|
|
|
38
52
|
const theme = await select({
|
|
39
|
-
message:
|
|
40
|
-
options: THEME_KEYS.map(k => ({ value: k, label: k })),
|
|
53
|
+
message: "Select a theme",
|
|
54
|
+
options: THEME_KEYS.map((k) => ({ value: k, label: k })),
|
|
41
55
|
initialValue: normalizedDefault,
|
|
42
|
-
})
|
|
56
|
+
});
|
|
43
57
|
|
|
44
58
|
if (isCancel(theme)) {
|
|
45
|
-
cancel(
|
|
46
|
-
process.exit(0)
|
|
59
|
+
cancel("Setup cancelled.");
|
|
60
|
+
process.exit(0);
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
return theme
|
|
63
|
+
return theme;
|
|
50
64
|
}
|
|
51
65
|
|
|
52
66
|
// ─── init command ─────────────────────────────────────────────────────────────
|
|
@@ -56,180 +70,261 @@ export async function askTheme(defaultTheme = DEFAULT_CONFIG.theme) {
|
|
|
56
70
|
* @param {boolean} [options.yes] Skip all prompts and use defaults (--yes flag)
|
|
57
71
|
*/
|
|
58
72
|
export async function init(options = {}) {
|
|
59
|
-
const { yes = false } = options
|
|
60
|
-
const cwd = process.cwd()
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
const { yes = false } = options;
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
|
|
76
|
+
runInitPreflightChecks(cwd);
|
|
77
|
+
|
|
78
|
+
const existingConfig = loadConfig(cwd);
|
|
79
|
+
const isInteractive = process.stdin.isTTY === true && !yes;
|
|
63
80
|
|
|
64
|
-
console.log(ASCII_BANNER)
|
|
81
|
+
console.log(ASCII_BANNER);
|
|
65
82
|
|
|
66
83
|
if (isInteractive) {
|
|
67
|
-
intro(pc.bold(
|
|
68
|
-
console.log(pc.dim(" Let's set up your project in a few steps."))
|
|
69
|
-
console.log(
|
|
84
|
+
intro(pc.bold("Welcome to NovaUI"));
|
|
85
|
+
console.log(pc.dim(" Let's set up your project in a few steps."));
|
|
86
|
+
console.log("");
|
|
70
87
|
} else {
|
|
71
|
-
console.log(pc.bold(
|
|
72
|
-
console.log(pc.dim(" Let's set up your project in a few steps."))
|
|
73
|
-
console.log(
|
|
88
|
+
console.log(pc.bold(" Welcome to NovaUI"));
|
|
89
|
+
console.log(pc.dim(" Let's set up your project in a few steps."));
|
|
90
|
+
console.log("");
|
|
74
91
|
}
|
|
75
92
|
|
|
76
|
-
let config
|
|
93
|
+
let config;
|
|
77
94
|
|
|
78
95
|
if (existingConfig) {
|
|
79
|
-
console.log(pc.blue(
|
|
80
|
-
console.log(pc.dim(
|
|
81
|
-
console.log(
|
|
96
|
+
console.log(pc.blue(" ⚙ Config"));
|
|
97
|
+
console.log(pc.dim(" components.json found in this directory."));
|
|
98
|
+
console.log("");
|
|
82
99
|
|
|
83
|
-
let reconfigure = false
|
|
100
|
+
let reconfigure = false;
|
|
84
101
|
|
|
85
102
|
if (isInteractive) {
|
|
86
|
-
const answer = await confirm({ message:
|
|
103
|
+
const answer = await confirm({ message: "Re-configure paths?" });
|
|
87
104
|
if (isCancel(answer)) {
|
|
88
|
-
cancel(
|
|
89
|
-
process.exit(0)
|
|
105
|
+
cancel("Setup cancelled.");
|
|
106
|
+
process.exit(0);
|
|
90
107
|
}
|
|
91
|
-
reconfigure = answer === true
|
|
108
|
+
reconfigure = answer === true;
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
if (reconfigure) {
|
|
95
112
|
const globalCss = await text({
|
|
96
|
-
message:
|
|
113
|
+
message: "Where should global.css be placed?",
|
|
97
114
|
placeholder: DEFAULT_CONFIG.globalCss,
|
|
98
115
|
defaultValue: existingConfig.globalCss || DEFAULT_CONFIG.globalCss,
|
|
99
|
-
})
|
|
100
|
-
if (isCancel(globalCss)) {
|
|
116
|
+
});
|
|
117
|
+
if (isCancel(globalCss)) {
|
|
118
|
+
cancel("Setup cancelled.");
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
101
121
|
|
|
102
122
|
const componentsUi = await text({
|
|
103
|
-
message:
|
|
123
|
+
message: "Where should UI components be placed?",
|
|
104
124
|
placeholder: DEFAULT_CONFIG.componentsUi,
|
|
105
|
-
defaultValue:
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
defaultValue:
|
|
126
|
+
existingConfig.componentsUi || DEFAULT_CONFIG.componentsUi,
|
|
127
|
+
});
|
|
128
|
+
if (isCancel(componentsUi)) {
|
|
129
|
+
cancel("Setup cancelled.");
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
108
132
|
|
|
109
133
|
const lib = await text({
|
|
110
|
-
message:
|
|
134
|
+
message: "Where should lib (e.g. utils) be placed?",
|
|
111
135
|
placeholder: DEFAULT_CONFIG.lib,
|
|
112
136
|
defaultValue: existingConfig.lib || DEFAULT_CONFIG.lib,
|
|
113
|
-
})
|
|
114
|
-
if (isCancel(lib)) {
|
|
137
|
+
});
|
|
138
|
+
if (isCancel(lib)) {
|
|
139
|
+
cancel("Setup cancelled.");
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
115
142
|
|
|
116
|
-
const theme = await askTheme(existingConfig.theme)
|
|
143
|
+
const theme = await askTheme(existingConfig.theme);
|
|
117
144
|
|
|
118
145
|
config = {
|
|
119
|
-
globalCss: globalCss.replace(/\\/g,
|
|
120
|
-
componentsUi: componentsUi.replace(/\\/g,
|
|
121
|
-
lib: lib.replace(/\\/g,
|
|
146
|
+
globalCss: globalCss.replace(/\\/g, "/"),
|
|
147
|
+
componentsUi: componentsUi.replace(/\\/g, "/"),
|
|
148
|
+
lib: lib.replace(/\\/g, "/"),
|
|
122
149
|
theme,
|
|
123
|
-
}
|
|
124
|
-
writeConfig(cwd, config)
|
|
125
|
-
console.log(
|
|
126
|
-
console.log(pc.green(
|
|
150
|
+
};
|
|
151
|
+
writeConfig(cwd, config);
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log(pc.green(" ✓ Updated components.json"));
|
|
127
154
|
} else {
|
|
128
|
-
config = existingConfig
|
|
155
|
+
config = existingConfig;
|
|
129
156
|
}
|
|
130
157
|
} else {
|
|
131
|
-
console.log(pc.blue(
|
|
132
|
-
console.log(
|
|
133
|
-
|
|
158
|
+
console.log(pc.blue(" ⚙ Configure paths"));
|
|
159
|
+
console.log(
|
|
160
|
+
pc.dim(" Where should NovaUI put its files? Press Enter for defaults."),
|
|
161
|
+
);
|
|
162
|
+
console.log("");
|
|
134
163
|
|
|
135
164
|
if (isInteractive) {
|
|
136
165
|
const globalCss = await text({
|
|
137
|
-
message:
|
|
166
|
+
message: "Path for global.css?",
|
|
138
167
|
placeholder: DEFAULT_CONFIG.globalCss,
|
|
139
168
|
defaultValue: DEFAULT_CONFIG.globalCss,
|
|
140
|
-
})
|
|
141
|
-
if (isCancel(globalCss)) {
|
|
169
|
+
});
|
|
170
|
+
if (isCancel(globalCss)) {
|
|
171
|
+
cancel("Setup cancelled.");
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
142
174
|
|
|
143
175
|
const componentsUi = await text({
|
|
144
|
-
message:
|
|
176
|
+
message: "Path for UI components?",
|
|
145
177
|
placeholder: DEFAULT_CONFIG.componentsUi,
|
|
146
178
|
defaultValue: DEFAULT_CONFIG.componentsUi,
|
|
147
|
-
})
|
|
148
|
-
if (isCancel(componentsUi)) {
|
|
179
|
+
});
|
|
180
|
+
if (isCancel(componentsUi)) {
|
|
181
|
+
cancel("Setup cancelled.");
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
149
184
|
|
|
150
185
|
const lib = await text({
|
|
151
|
-
message:
|
|
186
|
+
message: "Path for lib (utils)?",
|
|
152
187
|
placeholder: DEFAULT_CONFIG.lib,
|
|
153
188
|
defaultValue: DEFAULT_CONFIG.lib,
|
|
154
|
-
})
|
|
155
|
-
if (isCancel(lib)) {
|
|
189
|
+
});
|
|
190
|
+
if (isCancel(lib)) {
|
|
191
|
+
cancel("Setup cancelled.");
|
|
192
|
+
process.exit(0);
|
|
193
|
+
}
|
|
156
194
|
|
|
157
|
-
const theme = await askTheme()
|
|
195
|
+
const theme = await askTheme();
|
|
158
196
|
|
|
159
197
|
config = {
|
|
160
|
-
globalCss: globalCss.replace(/\\/g,
|
|
161
|
-
componentsUi: componentsUi.replace(/\\/g,
|
|
162
|
-
lib: lib.replace(/\\/g,
|
|
198
|
+
globalCss: globalCss.replace(/\\/g, "/"),
|
|
199
|
+
componentsUi: componentsUi.replace(/\\/g, "/"),
|
|
200
|
+
lib: lib.replace(/\\/g, "/"),
|
|
163
201
|
theme,
|
|
164
|
-
}
|
|
202
|
+
};
|
|
165
203
|
} else {
|
|
166
|
-
config = { ...DEFAULT_CONFIG }
|
|
204
|
+
config = { ...DEFAULT_CONFIG };
|
|
167
205
|
}
|
|
168
206
|
|
|
169
|
-
writeConfig(cwd, config)
|
|
170
|
-
console.log(pc.green(
|
|
207
|
+
writeConfig(cwd, config);
|
|
208
|
+
console.log(pc.green(" ✓ Created components.json"));
|
|
171
209
|
}
|
|
172
210
|
|
|
173
211
|
// ─── Setup files ────────────────────────────────────────────────────────────
|
|
174
212
|
|
|
175
|
-
console.log(
|
|
176
|
-
console.log(pc.blue(
|
|
177
|
-
console.log(
|
|
178
|
-
|
|
179
|
-
const utilsDir = path.join(cwd, config.lib)
|
|
180
|
-
ensureDir(utilsDir)
|
|
181
|
-
const utilsPath = path.join(utilsDir,
|
|
182
|
-
writeIfNotExists(utilsPath, UTILS_CONTENT, `${config.lib}/utils.ts`)
|
|
183
|
-
|
|
184
|
-
const globalCssDir = path.dirname(path.join(cwd, config.globalCss))
|
|
185
|
-
ensureDir(globalCssDir)
|
|
186
|
-
const themeCssContent = getThemeCssContent(normalizeTheme(config.theme))
|
|
187
|
-
writeIfNotExists(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
213
|
+
console.log("");
|
|
214
|
+
console.log(pc.blue(" 📁 Setting up project"));
|
|
215
|
+
console.log("");
|
|
216
|
+
|
|
217
|
+
const utilsDir = path.join(cwd, config.lib);
|
|
218
|
+
ensureDir(utilsDir);
|
|
219
|
+
const utilsPath = path.join(utilsDir, "utils.ts");
|
|
220
|
+
writeIfNotExists(utilsPath, UTILS_CONTENT, `${config.lib}/utils.ts`);
|
|
221
|
+
|
|
222
|
+
const globalCssDir = path.dirname(path.join(cwd, config.globalCss));
|
|
223
|
+
ensureDir(globalCssDir);
|
|
224
|
+
const themeCssContent = await getThemeCssContent(normalizeTheme(config.theme));
|
|
225
|
+
writeIfNotExists(
|
|
226
|
+
path.join(cwd, config.globalCss),
|
|
227
|
+
themeCssContent,
|
|
228
|
+
config.globalCss,
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const tailwindContent = getTailwindConfigContent(config);
|
|
232
|
+
writeIfNotExists(
|
|
233
|
+
path.join(cwd, "tailwind.config.js"),
|
|
234
|
+
tailwindContent,
|
|
235
|
+
"tailwind.config.js",
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
writeIfNotExists(
|
|
239
|
+
path.join(cwd, "babel.config.js"),
|
|
240
|
+
BABEL_CONFIG_CONTENT,
|
|
241
|
+
"babel.config.js",
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const metroContent = METRO_CONFIG_CONTENT(config.globalCss);
|
|
245
|
+
writeIfNotExists(
|
|
246
|
+
path.join(cwd, "metro.config.js"),
|
|
247
|
+
metroContent,
|
|
248
|
+
"metro.config.js",
|
|
249
|
+
);
|
|
191
250
|
|
|
192
251
|
// ─── Dependencies ──────────────────────────────────────────────────────────
|
|
193
252
|
|
|
194
|
-
const deps = [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
253
|
+
const deps = [
|
|
254
|
+
"nativewind",
|
|
255
|
+
"react-native-reanimated",
|
|
256
|
+
"react-native-safe-area-context",
|
|
257
|
+
"clsx",
|
|
258
|
+
"tailwind-merge",
|
|
259
|
+
"class-variance-authority",
|
|
260
|
+
];
|
|
261
|
+
const devDeps = [
|
|
262
|
+
"tailwindcss@^3.4.17",
|
|
263
|
+
"prettier-plugin-tailwindcss@^0.5.11",
|
|
264
|
+
"babel-preset-expo",
|
|
265
|
+
];
|
|
266
|
+
const missingDeps = getMissingDeps(cwd, deps);
|
|
267
|
+
const missingDevDeps = getMissingDeps(cwd, devDeps.map(d => d.split('@')[0]));
|
|
268
|
+
|
|
269
|
+
console.log("");
|
|
270
|
+
console.log(pc.blue(" 📦 Dependencies"));
|
|
271
|
+
console.log("");
|
|
272
|
+
|
|
273
|
+
if (missingDeps.length === 0 && missingDevDeps.length === 0) {
|
|
274
|
+
console.log(
|
|
275
|
+
pc.dim(
|
|
276
|
+
" ✓ All required packages already in package.json, skipping install.",
|
|
277
|
+
),
|
|
278
|
+
);
|
|
203
279
|
} else {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
280
|
+
if (missingDeps.length > 0) {
|
|
281
|
+
console.log(pc.dim(` Installing: ${missingDeps.join(", ")}`));
|
|
282
|
+
console.log("");
|
|
283
|
+
try {
|
|
284
|
+
installPackages(missingDeps);
|
|
285
|
+
} catch {
|
|
286
|
+
console.error("");
|
|
287
|
+
console.error(pc.yellow(" ✗ Install failed. Run manually:"));
|
|
288
|
+
console.error(pc.dim(` ${getInstallHint(missingDeps)}`));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (missingDevDeps.length > 0) {
|
|
293
|
+
const devDepsToInstall = devDeps.filter(d =>
|
|
294
|
+
missingDevDeps.includes(d.split('@')[0])
|
|
295
|
+
);
|
|
296
|
+
console.log(pc.dim(` Installing dev dependencies: ${devDepsToInstall.join(", ")}`));
|
|
297
|
+
console.log("");
|
|
298
|
+
try {
|
|
299
|
+
installPackages(devDepsToInstall, { dev: true });
|
|
300
|
+
} catch {
|
|
301
|
+
console.error("");
|
|
302
|
+
console.error(pc.yellow(" ✗ Dev dependencies install failed. Run manually:"));
|
|
303
|
+
console.error(pc.dim(` ${getInstallHint(devDepsToInstall, { dev: true })}`));
|
|
304
|
+
}
|
|
212
305
|
}
|
|
213
306
|
}
|
|
214
307
|
|
|
215
308
|
// ─── Success ────────────────────────────────────────────────────────────────
|
|
216
309
|
|
|
217
|
-
console.log(
|
|
218
|
-
console.log(pc.green(
|
|
219
|
-
console.log(pc.green(
|
|
220
|
-
console.log(pc.green(
|
|
221
|
-
console.log(
|
|
222
|
-
console.log(pc.bold(
|
|
223
|
-
console.log(
|
|
224
|
-
console.log(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
console.log(pc.
|
|
228
|
-
console.log(
|
|
229
|
-
console.log(pc.
|
|
230
|
-
console.log(
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log(pc.green(" ┌─────────────────────────────────────────┐"));
|
|
312
|
+
console.log(pc.green(" │ ✓ NovaUI is ready! │"));
|
|
313
|
+
console.log(pc.green(" └─────────────────────────────────────────┘"));
|
|
314
|
+
console.log("");
|
|
315
|
+
console.log(pc.bold(" Next steps:"));
|
|
316
|
+
console.log("");
|
|
317
|
+
console.log(
|
|
318
|
+
pc.dim(" 1. Import global CSS in your root entry (e.g. App.tsx):"),
|
|
319
|
+
);
|
|
320
|
+
console.log(pc.cyan(` import "${config.globalCss}"`));
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log(pc.dim(" 2. Add components:"));
|
|
323
|
+
console.log(pc.cyan(" npx novaui-cli@latest add button"));
|
|
324
|
+
console.log(pc.cyan(" npx novaui-cli@latest add card"));
|
|
325
|
+
console.log("");
|
|
231
326
|
|
|
232
327
|
if (isInteractive) {
|
|
233
|
-
outro(pc.green(
|
|
328
|
+
outro(pc.green("Setup complete!"));
|
|
234
329
|
}
|
|
235
330
|
}
|