novaui-cli 1.0.3 → 1.0.5
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/package.json +1 -1
- package/src/bin.js +299 -70
package/package.json
CHANGED
package/src/bin.js
CHANGED
|
@@ -2,11 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs"
|
|
4
4
|
import path from "node:path"
|
|
5
|
-
import
|
|
5
|
+
import readline from "node:readline"
|
|
6
|
+
import { execFileSync } from "node:child_process"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
6
8
|
|
|
7
9
|
const BASE_URL =
|
|
8
10
|
"https://raw.githubusercontent.com/KaloyanBehov/native-ui/main"
|
|
9
11
|
|
|
12
|
+
const CONFIG_FILENAME = "components.json"
|
|
13
|
+
const FETCH_TIMEOUT_MS = 15000
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
16
|
+
const __dirname = path.dirname(__filename)
|
|
17
|
+
const CLI_PACKAGE_JSON_PATH = path.resolve(__dirname, "../package.json")
|
|
18
|
+
|
|
19
|
+
const DEFAULT_CONFIG = {
|
|
20
|
+
globalCss: "src/global.css",
|
|
21
|
+
componentsUi: "src/components/ui",
|
|
22
|
+
lib: "src/lib",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── ANSI styles (no dependency, TTY-safe) ──────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const isTty = process.stdout.isTTY === true
|
|
28
|
+
const c = {
|
|
29
|
+
reset: "\x1b[0m",
|
|
30
|
+
bold: "\x1b[1m",
|
|
31
|
+
dim: "\x1b[2m",
|
|
32
|
+
cyan: "\x1b[36m",
|
|
33
|
+
green: "\x1b[32m",
|
|
34
|
+
yellow: "\x1b[33m",
|
|
35
|
+
blue: "\x1b[34m",
|
|
36
|
+
magenta: "\x1b[35m",
|
|
37
|
+
}
|
|
38
|
+
function style(use, text) {
|
|
39
|
+
return isTty && use ? use + text + c.reset : text
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ASCII_BANNER = `
|
|
43
|
+
${style(c.cyan, " _ __ __ ____ ___ __")}
|
|
44
|
+
${style(c.cyan, " / | / /__ / / / _// | / /")}
|
|
45
|
+
${style(c.cyan, " / |/ / _ \\/ / / / / /| |/ / ")}
|
|
46
|
+
${style(c.cyan, "/_/|_/ \\___/_/ /___/_/ |_/_/ ")}
|
|
47
|
+
${style(c.dim, " React Native + NativeWind UI")}
|
|
48
|
+
`
|
|
49
|
+
|
|
10
50
|
// ─── CSS Variables (global.css) ─────────────────────────────────────────────
|
|
11
51
|
|
|
12
52
|
const GLOBAL_CSS_CONTENT = `@tailwind base;
|
|
@@ -63,11 +103,16 @@ const GLOBAL_CSS_CONTENT = `@tailwind base;
|
|
|
63
103
|
|
|
64
104
|
// ─── Tailwind Config ────────────────────────────────────────────────────────
|
|
65
105
|
|
|
66
|
-
|
|
106
|
+
function getTailwindConfigContent(config) {
|
|
107
|
+
const contentPaths = [
|
|
108
|
+
'"./App.{js,jsx,ts,tsx}"',
|
|
109
|
+
'"./src/**/*.{js,jsx,ts,tsx}"',
|
|
110
|
+
`"./${config.componentsUi}/**/*.{js,jsx,ts,tsx}"`,
|
|
111
|
+
]
|
|
112
|
+
return `/** @type {import('tailwindcss').Config} */
|
|
67
113
|
module.exports = {
|
|
68
114
|
content: [
|
|
69
|
-
"
|
|
70
|
-
"./src/**/*.{js,jsx,ts,tsx}",
|
|
115
|
+
${contentPaths.join(",\n ")},
|
|
71
116
|
],
|
|
72
117
|
presets: [require("nativewind/preset")],
|
|
73
118
|
theme: {
|
|
@@ -117,6 +162,7 @@ module.exports = {
|
|
|
117
162
|
plugins: [],
|
|
118
163
|
};
|
|
119
164
|
`
|
|
165
|
+
}
|
|
120
166
|
|
|
121
167
|
// ─── Utils ──────────────────────────────────────────────────────────────────
|
|
122
168
|
|
|
@@ -132,10 +178,21 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
132
178
|
|
|
133
179
|
function detectPackageManager() {
|
|
134
180
|
const userAgent = process.env.npm_config_user_agent || ""
|
|
135
|
-
if (userAgent.startsWith("yarn")) return "yarn add"
|
|
136
|
-
if (userAgent.startsWith("pnpm")) return "pnpm add"
|
|
137
|
-
if (userAgent.startsWith("bun")) return "bun add"
|
|
138
|
-
return "npm install"
|
|
181
|
+
if (userAgent.startsWith("yarn")) return { command: "yarn", baseArgs: ["add"] }
|
|
182
|
+
if (userAgent.startsWith("pnpm")) return { command: "pnpm", baseArgs: ["add"] }
|
|
183
|
+
if (userAgent.startsWith("bun")) return { command: "bun", baseArgs: ["add"] }
|
|
184
|
+
return { command: "npm", baseArgs: ["install"] }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function installPackages(packages) {
|
|
188
|
+
if (!Array.isArray(packages) || packages.length === 0) return
|
|
189
|
+
const { command, baseArgs } = detectPackageManager()
|
|
190
|
+
execFileSync(command, [...baseArgs, ...packages], { stdio: "inherit" })
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getInstallHint(packages) {
|
|
194
|
+
const { command, baseArgs } = detectPackageManager()
|
|
195
|
+
return `${command} ${[...baseArgs, ...packages].join(" ")}`
|
|
139
196
|
}
|
|
140
197
|
|
|
141
198
|
function ensureDir(dir) {
|
|
@@ -146,46 +203,177 @@ function ensureDir(dir) {
|
|
|
146
203
|
|
|
147
204
|
function writeIfNotExists(filePath, content, label) {
|
|
148
205
|
if (fs.existsSync(filePath)) {
|
|
149
|
-
console.log(
|
|
206
|
+
console.log(style(c.dim, ` ℹ ${label} already exists, skipping.`))
|
|
150
207
|
return false
|
|
151
208
|
}
|
|
152
|
-
fs.writeFileSync(filePath, content)
|
|
153
|
-
console.log(
|
|
209
|
+
fs.writeFileSync(filePath, content, "utf8")
|
|
210
|
+
console.log(style(c.green, ` ✓ Created ${label}`))
|
|
154
211
|
return true
|
|
155
212
|
}
|
|
156
213
|
|
|
214
|
+
/** Returns which of the requested deps are not listed in package.json (dependencies or devDependencies). */
|
|
215
|
+
function getMissingDeps(cwd, deps) {
|
|
216
|
+
const pkgPath = path.join(cwd, "package.json")
|
|
217
|
+
if (!fs.existsSync(pkgPath)) {
|
|
218
|
+
return [...deps]
|
|
219
|
+
}
|
|
220
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
221
|
+
const installed = new Set([
|
|
222
|
+
...Object.keys(pkg.dependencies || {}),
|
|
223
|
+
...Object.keys(pkg.devDependencies || {}),
|
|
224
|
+
])
|
|
225
|
+
return deps.filter((d) => !installed.has(d))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Ask user a question; returns trimmed answer or default. */
|
|
229
|
+
function ask(question, defaultAnswer = "") {
|
|
230
|
+
if (process.stdin.isTTY !== true) {
|
|
231
|
+
return Promise.resolve(defaultAnswer)
|
|
232
|
+
}
|
|
233
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
234
|
+
const defaultPart = defaultAnswer ? style(c.dim, ` (${defaultAnswer})`) : ""
|
|
235
|
+
const prompt = style(c.cyan, " ? ") + question + defaultPart + style(c.dim, " ")
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
rl.question(prompt, (answer) => {
|
|
238
|
+
rl.close()
|
|
239
|
+
const trimmed = answer.trim()
|
|
240
|
+
resolve(trimmed !== "" ? trimmed : defaultAnswer)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Load components.json from cwd; returns null if missing or invalid. */
|
|
246
|
+
function loadConfig(cwd) {
|
|
247
|
+
const configPath = path.join(cwd, CONFIG_FILENAME)
|
|
248
|
+
if (!fs.existsSync(configPath)) return null
|
|
249
|
+
try {
|
|
250
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf8"))
|
|
251
|
+
return { ...DEFAULT_CONFIG, ...raw }
|
|
252
|
+
} catch {
|
|
253
|
+
return null
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Write components.json to cwd. */
|
|
258
|
+
function writeConfig(cwd, config) {
|
|
259
|
+
const configPath = path.join(cwd, CONFIG_FILENAME)
|
|
260
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8")
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getCliVersion() {
|
|
264
|
+
try {
|
|
265
|
+
const pkg = JSON.parse(fs.readFileSync(CLI_PACKAGE_JSON_PATH, "utf8"))
|
|
266
|
+
return pkg.version || "unknown"
|
|
267
|
+
} catch {
|
|
268
|
+
return "unknown"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function assertValidComponentConfig(componentName, componentConfig) {
|
|
273
|
+
if (!componentConfig || typeof componentConfig !== "object") {
|
|
274
|
+
throw new Error(`Registry entry for "${componentName}" is invalid.`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { files, dependencies } = componentConfig
|
|
278
|
+
if (!Array.isArray(files) || files.some((file) => typeof file !== "string" || file.trim() === "")) {
|
|
279
|
+
throw new Error(`Registry entry for "${componentName}" must include a valid "files" array.`)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
dependencies !== undefined &&
|
|
284
|
+
(!Array.isArray(dependencies) ||
|
|
285
|
+
dependencies.some((dep) => typeof dep !== "string" || dep.trim() === ""))
|
|
286
|
+
) {
|
|
287
|
+
throw new Error(`Registry entry for "${componentName}" has an invalid "dependencies" array.`)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function fetchWithTimeout(url) {
|
|
292
|
+
const controller = new AbortController()
|
|
293
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
|
|
294
|
+
try {
|
|
295
|
+
return await fetch(url, { signal: controller.signal })
|
|
296
|
+
} catch (error) {
|
|
297
|
+
if (error && error.name === "AbortError") {
|
|
298
|
+
throw new Error(`Request timed out after ${FETCH_TIMEOUT_MS}ms: ${url}`)
|
|
299
|
+
}
|
|
300
|
+
throw error
|
|
301
|
+
} finally {
|
|
302
|
+
clearTimeout(timeout)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function formatError(error) {
|
|
307
|
+
if (error instanceof Error && error.message) return error.message
|
|
308
|
+
return String(error)
|
|
309
|
+
}
|
|
310
|
+
|
|
157
311
|
// ─── Commands ───────────────────────────────────────────────────────────────
|
|
158
312
|
|
|
159
313
|
async function init() {
|
|
160
|
-
|
|
161
|
-
|
|
314
|
+
const cwd = process.cwd()
|
|
315
|
+
const existingConfig = loadConfig(cwd)
|
|
316
|
+
|
|
317
|
+
// ─── Banner ───────────────────────────────────────────────────────────────
|
|
318
|
+
console.log(ASCII_BANNER)
|
|
319
|
+
console.log(style(c.bold, " Welcome to NovaUI"))
|
|
320
|
+
console.log(style(c.dim, " Let's set up your project in a few steps."))
|
|
162
321
|
console.log("")
|
|
163
322
|
|
|
164
|
-
|
|
323
|
+
let config
|
|
324
|
+
if (existingConfig) {
|
|
325
|
+
console.log(style(c.blue, " ⚙ Config"))
|
|
326
|
+
console.log(style(c.dim, " components.json found in this directory."))
|
|
327
|
+
console.log("")
|
|
328
|
+
const reconfig = await ask("Re-configure paths? (y/N)", "n")
|
|
329
|
+
if (reconfig.toLowerCase() === "y" || reconfig.toLowerCase() === "yes") {
|
|
330
|
+
config = {
|
|
331
|
+
globalCss: (await ask("Where should global.css be placed?", DEFAULT_CONFIG.globalCss)).replace(/\\/g, "/"),
|
|
332
|
+
componentsUi: (await ask("Where should UI components be placed?", DEFAULT_CONFIG.componentsUi)).replace(/\\/g, "/"),
|
|
333
|
+
lib: (await ask("Where should lib (e.g. utils) be placed?", DEFAULT_CONFIG.lib)).replace(/\\/g, "/"),
|
|
334
|
+
}
|
|
335
|
+
writeConfig(cwd, config)
|
|
336
|
+
console.log("")
|
|
337
|
+
console.log(style(c.green, " ✓ Updated components.json"))
|
|
338
|
+
} else {
|
|
339
|
+
config = existingConfig
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
console.log(style(c.blue, " ⚙ Configure paths"))
|
|
343
|
+
console.log(style(c.dim, " Where should NovaUI put its files? Press Enter for defaults."))
|
|
344
|
+
console.log("")
|
|
345
|
+
config = {
|
|
346
|
+
globalCss: (await ask("Path for global.css?", DEFAULT_CONFIG.globalCss)).replace(/\\/g, "/"),
|
|
347
|
+
componentsUi: (await ask("Path for UI components?", DEFAULT_CONFIG.componentsUi)).replace(/\\/g, "/"),
|
|
348
|
+
lib: (await ask("Path for lib (utils)?", DEFAULT_CONFIG.lib)).replace(/\\/g, "/"),
|
|
349
|
+
}
|
|
350
|
+
writeConfig(cwd, config)
|
|
351
|
+
console.log("")
|
|
352
|
+
console.log(style(c.green, " ✓ Created components.json"))
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ─── Setup files ──────────────────────────────────────────────────────────
|
|
356
|
+
console.log("")
|
|
357
|
+
console.log(style(c.blue, " 📁 Setting up project"))
|
|
358
|
+
console.log("")
|
|
165
359
|
|
|
166
|
-
|
|
167
|
-
const utilsDir = path.join(cwd, "src", "lib")
|
|
360
|
+
const utilsDir = path.join(cwd, config.lib)
|
|
168
361
|
ensureDir(utilsDir)
|
|
169
|
-
|
|
362
|
+
const utilsPath = path.join(utilsDir, "utils.ts")
|
|
363
|
+
writeIfNotExists(utilsPath, UTILS_CONTENT, `${config.lib}/utils.ts`)
|
|
170
364
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
writeIfNotExists(path.join(srcDir, "global.css"), GLOBAL_CSS_CONTENT, "src/global.css")
|
|
365
|
+
const globalCssDir = path.dirname(path.join(cwd, config.globalCss))
|
|
366
|
+
ensureDir(globalCssDir)
|
|
367
|
+
writeIfNotExists(path.join(cwd, config.globalCss), GLOBAL_CSS_CONTENT, config.globalCss)
|
|
175
368
|
|
|
176
|
-
|
|
369
|
+
const tailwindContent = getTailwindConfigContent(config)
|
|
177
370
|
writeIfNotExists(
|
|
178
371
|
path.join(cwd, "tailwind.config.js"),
|
|
179
|
-
|
|
372
|
+
tailwindContent,
|
|
180
373
|
"tailwind.config.js"
|
|
181
374
|
)
|
|
182
375
|
|
|
183
|
-
//
|
|
184
|
-
console.log("")
|
|
185
|
-
console.log(" ◆ Installing dependencies...")
|
|
186
|
-
console.log("")
|
|
187
|
-
|
|
188
|
-
const installCmd = detectPackageManager()
|
|
376
|
+
// ─── Dependencies ─────────────────────────────────────────────────────────
|
|
189
377
|
const deps = [
|
|
190
378
|
"nativewind",
|
|
191
379
|
"tailwindcss",
|
|
@@ -193,22 +381,40 @@ async function init() {
|
|
|
193
381
|
"tailwind-merge",
|
|
194
382
|
"class-variance-authority",
|
|
195
383
|
]
|
|
384
|
+
const missingDeps = getMissingDeps(cwd, deps)
|
|
196
385
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
console.
|
|
386
|
+
console.log("")
|
|
387
|
+
console.log(style(c.blue, " 📦 Dependencies"))
|
|
388
|
+
console.log("")
|
|
389
|
+
|
|
390
|
+
if (missingDeps.length === 0) {
|
|
391
|
+
console.log(style(c.dim, " ✓ All required packages already in package.json, skipping install."))
|
|
392
|
+
} else {
|
|
393
|
+
console.log(style(c.dim, ` Installing: ${missingDeps.join(", ")}`))
|
|
394
|
+
console.log("")
|
|
395
|
+
try {
|
|
396
|
+
installPackages(missingDeps)
|
|
397
|
+
} catch {
|
|
398
|
+
console.error("")
|
|
399
|
+
console.error(style(c.yellow, " ✗ Install failed. Run manually:"))
|
|
400
|
+
console.error(style(c.dim, ` ${getInstallHint(missingDeps)}`))
|
|
401
|
+
}
|
|
203
402
|
}
|
|
204
403
|
|
|
404
|
+
// ─── Success ───────────────────────────────────────────────────────────────
|
|
405
|
+
console.log("")
|
|
406
|
+
console.log(style(c.green, " ┌─────────────────────────────────────────┐"))
|
|
407
|
+
console.log(style(c.green, " │ ✓ NovaUI is ready! │"))
|
|
408
|
+
console.log(style(c.green, " └─────────────────────────────────────────┘"))
|
|
205
409
|
console.log("")
|
|
206
|
-
console.log("
|
|
410
|
+
console.log(style(c.bold, " Next steps:"))
|
|
207
411
|
console.log("")
|
|
208
|
-
console.log("
|
|
209
|
-
console.log(
|
|
210
|
-
console.log("
|
|
211
|
-
console.log("
|
|
412
|
+
console.log(style(c.dim, " 1. Import global CSS in your root entry (e.g. App.tsx):"))
|
|
413
|
+
console.log(style(c.cyan, ` import "${config.globalCss}"`))
|
|
414
|
+
console.log("")
|
|
415
|
+
console.log(style(c.dim, " 2. Add components:"))
|
|
416
|
+
console.log(style(c.cyan, " npx novaui add button"))
|
|
417
|
+
console.log(style(c.cyan, " npx novaui add card"))
|
|
212
418
|
console.log("")
|
|
213
419
|
}
|
|
214
420
|
|
|
@@ -226,13 +432,16 @@ async function add(componentName) {
|
|
|
226
432
|
|
|
227
433
|
// 1. Fetch registry
|
|
228
434
|
console.log(" Fetching registry...")
|
|
229
|
-
const registryResponse = await
|
|
435
|
+
const registryResponse = await fetchWithTimeout(`${BASE_URL}/registry.json`)
|
|
230
436
|
|
|
231
437
|
if (!registryResponse.ok) {
|
|
232
438
|
throw new Error(`Failed to fetch registry: ${registryResponse.statusText}`)
|
|
233
439
|
}
|
|
234
440
|
|
|
235
441
|
const registry = await registryResponse.json()
|
|
442
|
+
if (!registry || typeof registry !== "object" || Array.isArray(registry)) {
|
|
443
|
+
throw new Error("Registry response is not a valid object.")
|
|
444
|
+
}
|
|
236
445
|
|
|
237
446
|
if (!registry[componentName]) {
|
|
238
447
|
console.error(` ✗ Component "${componentName}" not found in registry.`)
|
|
@@ -242,28 +451,36 @@ async function add(componentName) {
|
|
|
242
451
|
process.exit(1)
|
|
243
452
|
}
|
|
244
453
|
|
|
245
|
-
const
|
|
246
|
-
|
|
454
|
+
const componentConfig = registry[componentName]
|
|
455
|
+
assertValidComponentConfig(componentName, componentConfig)
|
|
456
|
+
const projectConfig = loadConfig(cwd) || DEFAULT_CONFIG
|
|
457
|
+
const targetBaseDir = path.join(cwd, projectConfig.componentsUi)
|
|
247
458
|
ensureDir(targetBaseDir)
|
|
248
459
|
|
|
249
|
-
// 2. Ensure utils.ts exists
|
|
250
|
-
const utilsDir = path.join(cwd,
|
|
460
|
+
// 2. Ensure utils.ts exists (in configured lib dir)
|
|
461
|
+
const utilsDir = path.join(cwd, projectConfig.lib)
|
|
251
462
|
const utilsPath = path.join(utilsDir, "utils.ts")
|
|
252
463
|
|
|
253
464
|
if (!fs.existsSync(utilsPath)) {
|
|
254
465
|
ensureDir(utilsDir)
|
|
255
466
|
fs.writeFileSync(utilsPath, UTILS_CONTENT)
|
|
256
|
-
console.log(
|
|
467
|
+
console.log(` ✓ Created ${projectConfig.lib}/utils.ts`)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!loadConfig(cwd)) {
|
|
471
|
+
console.log("")
|
|
472
|
+
console.log(" ℹ No components.json found. Using default paths. Run 'npx novaui init' to customize.")
|
|
473
|
+
console.log("")
|
|
257
474
|
}
|
|
258
475
|
|
|
259
476
|
// 3. Fetch and write component files
|
|
260
|
-
for (const file of
|
|
477
|
+
for (const file of componentConfig.files) {
|
|
261
478
|
const fileUrl = `${BASE_URL}/${file}`
|
|
262
479
|
const fileName = path.basename(file)
|
|
263
480
|
const destPath = path.join(targetBaseDir, fileName)
|
|
264
481
|
|
|
265
482
|
console.log(` Downloading ${fileName}...`)
|
|
266
|
-
const fileResponse = await
|
|
483
|
+
const fileResponse = await fetchWithTimeout(fileUrl)
|
|
267
484
|
|
|
268
485
|
if (!fileResponse.ok) {
|
|
269
486
|
console.error(` ✗ Failed to download ${fileName}`)
|
|
@@ -271,21 +488,24 @@ async function add(componentName) {
|
|
|
271
488
|
}
|
|
272
489
|
|
|
273
490
|
const content = await fileResponse.text()
|
|
274
|
-
fs.writeFileSync(destPath, content)
|
|
491
|
+
fs.writeFileSync(destPath, content, "utf8")
|
|
275
492
|
console.log(` ✓ Added ${fileName}`)
|
|
276
493
|
}
|
|
277
494
|
|
|
278
|
-
// 4. Install component dependencies
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
|
|
495
|
+
// 4. Install component dependencies (only those not already in package.json)
|
|
496
|
+
if (componentConfig.dependencies && componentConfig.dependencies.length > 0) {
|
|
497
|
+
const missingDeps = getMissingDeps(cwd, componentConfig.dependencies)
|
|
498
|
+
if (missingDeps.length === 0) {
|
|
499
|
+
console.log("")
|
|
500
|
+
console.log(" ✓ Component dependencies already in package.json, skipping install.")
|
|
501
|
+
} else {
|
|
502
|
+
console.log("")
|
|
503
|
+
console.log(` Installing dependencies: ${missingDeps.join(", ")}...`)
|
|
504
|
+
try {
|
|
505
|
+
installPackages(missingDeps)
|
|
506
|
+
} catch {
|
|
507
|
+
console.error(" ✗ Failed to install dependencies automatically.")
|
|
508
|
+
}
|
|
289
509
|
}
|
|
290
510
|
}
|
|
291
511
|
|
|
@@ -295,20 +515,25 @@ async function add(componentName) {
|
|
|
295
515
|
}
|
|
296
516
|
|
|
297
517
|
function showHelp() {
|
|
518
|
+
console.log(ASCII_BANNER)
|
|
519
|
+
console.log(style(c.dim, ` Version: ${getCliVersion()}`))
|
|
298
520
|
console.log("")
|
|
299
|
-
console.log(
|
|
300
|
-
console.log("")
|
|
301
|
-
console.log("
|
|
302
|
-
console.log("
|
|
303
|
-
console.log(" novaui add <component> Add a component to your project")
|
|
521
|
+
console.log(style(c.bold, " Usage"))
|
|
522
|
+
console.log(style(c.dim, " novaui init Set up NovaUI (config, Tailwind, global.css, utils)"))
|
|
523
|
+
console.log(style(c.dim, " novaui add <component> Add a component (e.g. button, card)"))
|
|
524
|
+
console.log(style(c.dim, " novaui --version Show CLI version"))
|
|
304
525
|
console.log("")
|
|
305
|
-
console.log("
|
|
306
|
-
console.log("
|
|
307
|
-
console.log("
|
|
308
|
-
console.log("
|
|
526
|
+
console.log(style(c.bold, " Examples"))
|
|
527
|
+
console.log(style(c.cyan, " npx novaui init"))
|
|
528
|
+
console.log(style(c.cyan, " npx novaui add button"))
|
|
529
|
+
console.log(style(c.cyan, " npx novaui add card"))
|
|
309
530
|
console.log("")
|
|
310
531
|
}
|
|
311
532
|
|
|
533
|
+
function showVersion() {
|
|
534
|
+
console.log(getCliVersion())
|
|
535
|
+
}
|
|
536
|
+
|
|
312
537
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
313
538
|
|
|
314
539
|
const command = process.argv[2]
|
|
@@ -327,6 +552,10 @@ async function main() {
|
|
|
327
552
|
case "-h":
|
|
328
553
|
showHelp()
|
|
329
554
|
break
|
|
555
|
+
case "--version":
|
|
556
|
+
case "-v":
|
|
557
|
+
showVersion()
|
|
558
|
+
break
|
|
330
559
|
default:
|
|
331
560
|
if (command) {
|
|
332
561
|
// Backwards compatible: treat unknown command as component name
|
|
@@ -338,7 +567,7 @@ async function main() {
|
|
|
338
567
|
}
|
|
339
568
|
} catch (error) {
|
|
340
569
|
console.error("")
|
|
341
|
-
console.error(` ✗ Error: ${error
|
|
570
|
+
console.error(` ✗ Error: ${formatError(error)}`)
|
|
342
571
|
process.exit(1)
|
|
343
572
|
}
|
|
344
573
|
}
|