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.
@@ -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 spinner = ora({ text: ' Fetching registry...', stream: process.stderr }).start()
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 response is not a valid object.')
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
- let registry
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 response is not a valid object.')
90
+ throw new Error('Registry is not a valid object.')
93
91
  }
94
92
 
95
93
  if (!registry[componentName]) {
96
- const available = Object.keys(registry).join(', ')
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]
@@ -1,52 +1,66 @@
1
- import path from 'node:path'
2
- import { text, select, confirm, isCancel, cancel, intro, outro } from '@clack/prompts'
3
- import pc from 'picocolors'
4
-
5
- import { THEME_KEYS, getThemeCssContent } from '../themes/index.js'
6
- import { DEFAULT_CONFIG, UTILS_CONTENT } from '../constants.js'
7
- import { loadConfig, writeConfig } from '../utils/config.js'
8
- import { ensureDir, writeIfNotExists } from '../utils/fs-helpers.js'
9
- import { getMissingDeps, installPackages, getInstallHint } from '../utils/deps.js'
10
- import { getTailwindConfigContent } from '../utils/tailwind.js'
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(' React Native + NativeWind UI')}
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 !== 'string' || themeName.trim() === '') return DEFAULT_CONFIG.theme
28
- const normalized = themeName.trim().toLowerCase()
29
- if (!THEME_KEYS.includes(normalized)) return DEFAULT_CONFIG.theme
30
- return normalized
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: 'Select a theme',
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('Setup cancelled.')
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
- const existingConfig = loadConfig(cwd)
62
- const isInteractive = process.stdin.isTTY === true && !yes
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('Welcome to NovaUI'))
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(' Welcome to NovaUI'))
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(' ⚙ Config'))
80
- console.log(pc.dim(' components.json found in this directory.'))
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: 'Re-configure paths?' })
103
+ const answer = await confirm({ message: "Re-configure paths?" });
87
104
  if (isCancel(answer)) {
88
- cancel('Setup cancelled.')
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: 'Where should global.css be placed?',
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)) { cancel('Setup cancelled.'); process.exit(0) }
116
+ });
117
+ if (isCancel(globalCss)) {
118
+ cancel("Setup cancelled.");
119
+ process.exit(0);
120
+ }
101
121
 
102
122
  const componentsUi = await text({
103
- message: 'Where should UI components be placed?',
123
+ message: "Where should UI components be placed?",
104
124
  placeholder: DEFAULT_CONFIG.componentsUi,
105
- defaultValue: existingConfig.componentsUi || DEFAULT_CONFIG.componentsUi,
106
- })
107
- if (isCancel(componentsUi)) { cancel('Setup cancelled.'); process.exit(0) }
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: 'Where should lib (e.g. utils) be placed?',
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)) { cancel('Setup cancelled.'); process.exit(0) }
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(' ✓ Updated components.json'))
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(' ⚙ Configure paths'))
132
- console.log(pc.dim(' Where should NovaUI put its files? Press Enter for defaults.'))
133
- console.log('')
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: 'Path for global.css?',
166
+ message: "Path for global.css?",
138
167
  placeholder: DEFAULT_CONFIG.globalCss,
139
168
  defaultValue: DEFAULT_CONFIG.globalCss,
140
- })
141
- if (isCancel(globalCss)) { cancel('Setup cancelled.'); process.exit(0) }
169
+ });
170
+ if (isCancel(globalCss)) {
171
+ cancel("Setup cancelled.");
172
+ process.exit(0);
173
+ }
142
174
 
143
175
  const componentsUi = await text({
144
- message: 'Path for UI components?',
176
+ message: "Path for UI components?",
145
177
  placeholder: DEFAULT_CONFIG.componentsUi,
146
178
  defaultValue: DEFAULT_CONFIG.componentsUi,
147
- })
148
- if (isCancel(componentsUi)) { cancel('Setup cancelled.'); process.exit(0) }
179
+ });
180
+ if (isCancel(componentsUi)) {
181
+ cancel("Setup cancelled.");
182
+ process.exit(0);
183
+ }
149
184
 
150
185
  const lib = await text({
151
- message: 'Path for lib (utils)?',
186
+ message: "Path for lib (utils)?",
152
187
  placeholder: DEFAULT_CONFIG.lib,
153
188
  defaultValue: DEFAULT_CONFIG.lib,
154
- })
155
- if (isCancel(lib)) { cancel('Setup cancelled.'); process.exit(0) }
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(' ✓ Created components.json'))
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(' 📁 Setting up project'))
177
- console.log('')
178
-
179
- const utilsDir = path.join(cwd, config.lib)
180
- ensureDir(utilsDir)
181
- const utilsPath = path.join(utilsDir, 'utils.ts')
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(path.join(cwd, config.globalCss), themeCssContent, config.globalCss)
188
-
189
- const tailwindContent = getTailwindConfigContent(config)
190
- writeIfNotExists(path.join(cwd, 'tailwind.config.js'), tailwindContent, 'tailwind.config.js')
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 = ['nativewind', 'tailwindcss', 'clsx', 'tailwind-merge', 'class-variance-authority']
195
- const missingDeps = getMissingDeps(cwd, deps)
196
-
197
- console.log('')
198
- console.log(pc.blue(' 📦 Dependencies'))
199
- console.log('')
200
-
201
- if (missingDeps.length === 0) {
202
- console.log(pc.dim(' ✓ All required packages already in package.json, skipping install.'))
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
- console.log(pc.dim(` Installing: ${missingDeps.join(', ')}`))
205
- console.log('')
206
- try {
207
- installPackages(missingDeps)
208
- } catch {
209
- console.error('')
210
- console.error(pc.yellow(' ✗ Install failed. Run manually:'))
211
- console.error(pc.dim(` ${getInstallHint(missingDeps)}`))
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(' │ ✓ NovaUI is ready! │'))
220
- console.log(pc.green(' └─────────────────────────────────────────┘'))
221
- console.log('')
222
- console.log(pc.bold(' Next steps:'))
223
- console.log('')
224
- console.log(pc.dim(' 1. Import global CSS in your root entry (e.g. App.tsx):'))
225
- console.log(pc.cyan(` import "${config.globalCss}"`))
226
- console.log('')
227
- console.log(pc.dim(' 2. Add components:'))
228
- console.log(pc.cyan(' npx novaui-cli@latest add button'))
229
- console.log(pc.cyan(' npx novaui-cli@latest add card'))
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('Setup complete!'))
328
+ outro(pc.green("Setup complete!"));
234
329
  }
235
330
  }