novaui-cli 1.0.2 → 1.0.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/bin.js +210 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novaui-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "CLI for NovaUI - React Native component library with NativeWind",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin.js CHANGED
@@ -2,11 +2,45 @@
2
2
 
3
3
  import fs from "node:fs"
4
4
  import path from "node:path"
5
+ import readline from "node:readline"
5
6
  import { execSync } from "node:child_process"
6
7
 
7
8
  const BASE_URL =
8
9
  "https://raw.githubusercontent.com/KaloyanBehov/native-ui/main"
9
10
 
11
+ const CONFIG_FILENAME = "components.json"
12
+
13
+ const DEFAULT_CONFIG = {
14
+ globalCss: "src/global.css",
15
+ componentsUi: "src/components/ui",
16
+ lib: "src/lib",
17
+ }
18
+
19
+ // ─── ANSI styles (no dependency, TTY-safe) ──────────────────────────────────
20
+
21
+ const isTty = process.stdout.isTTY === true
22
+ const c = {
23
+ reset: "\x1b[0m",
24
+ bold: "\x1b[1m",
25
+ dim: "\x1b[2m",
26
+ cyan: "\x1b[36m",
27
+ green: "\x1b[32m",
28
+ yellow: "\x1b[33m",
29
+ blue: "\x1b[34m",
30
+ magenta: "\x1b[35m",
31
+ }
32
+ function style(use, text) {
33
+ return isTty && use ? use + text + c.reset : text
34
+ }
35
+
36
+ const ASCII_BANNER = `
37
+ ${style(c.cyan, " _ __ __ ____ ___ __")}
38
+ ${style(c.cyan, " / | / /__ / / / _// | / /")}
39
+ ${style(c.cyan, " / |/ / _ \\/ / / / / /| |/ / ")}
40
+ ${style(c.cyan, "/_/|_/ \\___/_/ /___/_/ |_/_/ ")}
41
+ ${style(c.dim, " React Native + NativeWind UI")}
42
+ `
43
+
10
44
  // ─── CSS Variables (global.css) ─────────────────────────────────────────────
11
45
 
12
46
  const GLOBAL_CSS_CONTENT = `@tailwind base;
@@ -63,11 +97,16 @@ const GLOBAL_CSS_CONTENT = `@tailwind base;
63
97
 
64
98
  // ─── Tailwind Config ────────────────────────────────────────────────────────
65
99
 
66
- const TAILWIND_CONFIG_CONTENT = `/** @type {import('tailwindcss').Config} */
100
+ function getTailwindConfigContent(config) {
101
+ const contentPaths = [
102
+ '"./App.{js,jsx,ts,tsx}"',
103
+ '"./src/**/*.{js,jsx,ts,tsx}"',
104
+ `"./${config.componentsUi}/**/*.{js,jsx,ts,tsx}"`,
105
+ ]
106
+ return `/** @type {import('tailwindcss').Config} */
67
107
  module.exports = {
68
108
  content: [
69
- "./App.{js,jsx,ts,tsx}",
70
- "./src/**/*.{js,jsx,ts,tsx}",
109
+ ${contentPaths.join(",\n ")},
71
110
  ],
72
111
  presets: [require("nativewind/preset")],
73
112
  theme: {
@@ -146,46 +185,126 @@ function ensureDir(dir) {
146
185
 
147
186
  function writeIfNotExists(filePath, content, label) {
148
187
  if (fs.existsSync(filePath)) {
149
- console.log(`ℹ ${label} already exists, skipping.`)
188
+ console.log(style(c.dim, ` ℹ ${label} already exists, skipping.`))
150
189
  return false
151
190
  }
152
191
  fs.writeFileSync(filePath, content)
153
- console.log(`✓ Created ${label}`)
192
+ console.log(style(c.green, ` ✓ Created ${label}`))
154
193
  return true
155
194
  }
156
195
 
196
+ /** Returns which of the requested deps are not listed in package.json (dependencies or devDependencies). */
197
+ function getMissingDeps(cwd, deps) {
198
+ const pkgPath = path.join(cwd, "package.json")
199
+ if (!fs.existsSync(pkgPath)) {
200
+ return [...deps]
201
+ }
202
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
203
+ const installed = new Set([
204
+ ...Object.keys(pkg.dependencies || {}),
205
+ ...Object.keys(pkg.devDependencies || {}),
206
+ ])
207
+ return deps.filter((d) => !installed.has(d))
208
+ }
209
+
210
+ /** Ask user a question; returns trimmed answer or default. */
211
+ function ask(question, defaultAnswer = "") {
212
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
213
+ const defaultPart = defaultAnswer ? style(c.dim, ` (${defaultAnswer})`) : ""
214
+ const prompt = style(c.cyan, " ? ") + question + defaultPart + style(c.dim, " ")
215
+ return new Promise((resolve) => {
216
+ rl.question(prompt, (answer) => {
217
+ rl.close()
218
+ const trimmed = answer.trim()
219
+ resolve(trimmed !== "" ? trimmed : defaultAnswer)
220
+ })
221
+ })
222
+ }
223
+
224
+ /** Load components.json from cwd; returns null if missing or invalid. */
225
+ function loadConfig(cwd) {
226
+ const configPath = path.join(cwd, CONFIG_FILENAME)
227
+ if (!fs.existsSync(configPath)) return null
228
+ try {
229
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf8"))
230
+ return { ...DEFAULT_CONFIG, ...raw }
231
+ } catch {
232
+ return null
233
+ }
234
+ }
235
+
236
+ /** Write components.json to cwd. */
237
+ function writeConfig(cwd, config) {
238
+ const configPath = path.join(cwd, CONFIG_FILENAME)
239
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
240
+ }
241
+
157
242
  // ─── Commands ───────────────────────────────────────────────────────────────
158
243
 
159
244
  async function init() {
160
- console.log("")
161
- console.log(" ◆ NovaUI Initializing project...")
245
+ const cwd = process.cwd()
246
+ const existingConfig = loadConfig(cwd)
247
+
248
+ // ─── Banner ───────────────────────────────────────────────────────────────
249
+ console.log(ASCII_BANNER)
250
+ console.log(style(c.bold, " Welcome to NovaUI"))
251
+ console.log(style(c.dim, " Let's set up your project in a few steps."))
162
252
  console.log("")
163
253
 
164
- const cwd = process.cwd()
254
+ let config
255
+ if (existingConfig) {
256
+ console.log(style(c.blue, " ⚙ Config"))
257
+ console.log(style(c.dim, " components.json found in this directory."))
258
+ console.log("")
259
+ const reconfig = await ask("Re-configure paths? (y/N)", "n")
260
+ if (reconfig.toLowerCase() === "y" || reconfig.toLowerCase() === "yes") {
261
+ config = {
262
+ globalCss: (await ask("Where should global.css be placed?", DEFAULT_CONFIG.globalCss)).replace(/\\/g, "/"),
263
+ componentsUi: (await ask("Where should UI components be placed?", DEFAULT_CONFIG.componentsUi)).replace(/\\/g, "/"),
264
+ lib: (await ask("Where should lib (e.g. utils) be placed?", DEFAULT_CONFIG.lib)).replace(/\\/g, "/"),
265
+ }
266
+ writeConfig(cwd, config)
267
+ console.log("")
268
+ console.log(style(c.green, " ✓ Updated components.json"))
269
+ } else {
270
+ config = existingConfig
271
+ }
272
+ } else {
273
+ console.log(style(c.blue, " ⚙ Configure paths"))
274
+ console.log(style(c.dim, " Where should NovaUI put its files? Press Enter for defaults."))
275
+ console.log("")
276
+ config = {
277
+ globalCss: (await ask("Path for global.css?", DEFAULT_CONFIG.globalCss)).replace(/\\/g, "/"),
278
+ componentsUi: (await ask("Path for UI components?", DEFAULT_CONFIG.componentsUi)).replace(/\\/g, "/"),
279
+ lib: (await ask("Path for lib (utils)?", DEFAULT_CONFIG.lib)).replace(/\\/g, "/"),
280
+ }
281
+ writeConfig(cwd, config)
282
+ console.log("")
283
+ console.log(style(c.green, " ✓ Created components.json"))
284
+ }
285
+
286
+ // ─── Setup files ──────────────────────────────────────────────────────────
287
+ console.log("")
288
+ console.log(style(c.blue, " 📁 Setting up project"))
289
+ console.log("")
165
290
 
166
- // 1. Create src/lib/utils.ts
167
- const utilsDir = path.join(cwd, "src", "lib")
291
+ const utilsDir = path.join(cwd, config.lib)
168
292
  ensureDir(utilsDir)
169
- writeIfNotExists(path.join(utilsDir, "utils.ts"), UTILS_CONTENT, "src/lib/utils.ts")
293
+ const utilsPath = path.join(utilsDir, "utils.ts")
294
+ writeIfNotExists(utilsPath, UTILS_CONTENT, `${config.lib}/utils.ts`)
170
295
 
171
- // 2. Create src/global.css
172
- const srcDir = path.join(cwd, "src")
173
- ensureDir(srcDir)
174
- writeIfNotExists(path.join(srcDir, "global.css"), GLOBAL_CSS_CONTENT, "src/global.css")
296
+ const globalCssDir = path.dirname(path.join(cwd, config.globalCss))
297
+ ensureDir(globalCssDir)
298
+ writeIfNotExists(path.join(cwd, config.globalCss), GLOBAL_CSS_CONTENT, config.globalCss)
175
299
 
176
- // 3. Create tailwind.config.js
300
+ const tailwindContent = getTailwindConfigContent(config)
177
301
  writeIfNotExists(
178
302
  path.join(cwd, "tailwind.config.js"),
179
- TAILWIND_CONFIG_CONTENT,
303
+ tailwindContent,
180
304
  "tailwind.config.js"
181
305
  )
182
306
 
183
- // 4. Install core dependencies
184
- console.log("")
185
- console.log(" ◆ Installing dependencies...")
186
- console.log("")
187
-
188
- const installCmd = detectPackageManager()
307
+ // ─── Dependencies ─────────────────────────────────────────────────────────
189
308
  const deps = [
190
309
  "nativewind",
191
310
  "tailwindcss",
@@ -193,22 +312,41 @@ async function init() {
193
312
  "tailwind-merge",
194
313
  "class-variance-authority",
195
314
  ]
315
+ const missingDeps = getMissingDeps(cwd, deps)
196
316
 
197
- try {
198
- execSync(`${installCmd} ${deps.join(" ")}`, { stdio: "inherit" })
199
- } catch {
200
- console.error("")
201
- console.error(" ✗ Failed to install dependencies. Please install manually:")
202
- console.error(` ${installCmd} ${deps.join(" ")}`)
317
+ console.log("")
318
+ console.log(style(c.blue, " 📦 Dependencies"))
319
+ console.log("")
320
+
321
+ if (missingDeps.length === 0) {
322
+ console.log(style(c.dim, " ✓ All required packages already in package.json, skipping install."))
323
+ } else {
324
+ console.log(style(c.dim, ` Installing: ${missingDeps.join(", ")}`))
325
+ console.log("")
326
+ const installCmd = detectPackageManager()
327
+ try {
328
+ execSync(`${installCmd} ${missingDeps.join(" ")}`, { stdio: "inherit" })
329
+ } catch {
330
+ console.error("")
331
+ console.error(style(c.yellow, " ✗ Install failed. Run manually:"))
332
+ console.error(style(c.dim, ` ${installCmd} ${missingDeps.join(" ")}`))
333
+ }
203
334
  }
204
335
 
336
+ // ─── Success ───────────────────────────────────────────────────────────────
337
+ console.log("")
338
+ console.log(style(c.green, " ┌─────────────────────────────────────────┐"))
339
+ console.log(style(c.green, " │ ✓ NovaUI is ready! │"))
340
+ console.log(style(c.green, " └─────────────────────────────────────────┘"))
205
341
  console.log("")
206
- console.log(" ✓ Project initialized!")
342
+ console.log(style(c.bold, " Next steps:"))
207
343
  console.log("")
208
- console.log(" Next steps:")
209
- console.log(' 1. Import "src/global.css" in your root layout/entry file')
210
- console.log(" 2. Start adding components:")
211
- console.log(" npx novaui add button")
344
+ console.log(style(c.dim, " 1. Import global CSS in your root entry (e.g. App.tsx):"))
345
+ console.log(style(c.cyan, ` import "${config.globalCss}"`))
346
+ console.log("")
347
+ console.log(style(c.dim, " 2. Add components:"))
348
+ console.log(style(c.cyan, " npx novaui add button"))
349
+ console.log(style(c.cyan, " npx novaui add card"))
212
350
  console.log("")
213
351
  }
214
352
 
@@ -242,22 +380,29 @@ async function add(componentName) {
242
380
  process.exit(1)
243
381
  }
244
382
 
245
- const config = registry[componentName]
246
- const targetBaseDir = path.join(cwd, "src", "components", "ui")
383
+ const componentConfig = registry[componentName]
384
+ const projectConfig = loadConfig(cwd) || DEFAULT_CONFIG
385
+ const targetBaseDir = path.join(cwd, projectConfig.componentsUi)
247
386
  ensureDir(targetBaseDir)
248
387
 
249
- // 2. Ensure utils.ts exists
250
- const utilsDir = path.join(cwd, "src", "lib")
388
+ // 2. Ensure utils.ts exists (in configured lib dir)
389
+ const utilsDir = path.join(cwd, projectConfig.lib)
251
390
  const utilsPath = path.join(utilsDir, "utils.ts")
252
391
 
253
392
  if (!fs.existsSync(utilsPath)) {
254
393
  ensureDir(utilsDir)
255
394
  fs.writeFileSync(utilsPath, UTILS_CONTENT)
256
- console.log(" ✓ Created src/lib/utils.ts")
395
+ console.log(` ✓ Created ${projectConfig.lib}/utils.ts`)
396
+ }
397
+
398
+ if (!loadConfig(cwd)) {
399
+ console.log("")
400
+ console.log(" ℹ No components.json found. Using default paths. Run 'npx novaui init' to customize.")
401
+ console.log("")
257
402
  }
258
403
 
259
404
  // 3. Fetch and write component files
260
- for (const file of config.files) {
405
+ for (const file of componentConfig.files) {
261
406
  const fileUrl = `${BASE_URL}/${file}`
262
407
  const fileName = path.basename(file)
263
408
  const destPath = path.join(targetBaseDir, fileName)
@@ -275,17 +420,23 @@ async function add(componentName) {
275
420
  console.log(` ✓ Added ${fileName}`)
276
421
  }
277
422
 
278
- // 4. Install component dependencies
279
- if (config.dependencies && config.dependencies.length > 0) {
280
- console.log("")
281
- console.log(` Installing dependencies: ${config.dependencies.join(", ")}...`)
282
- try {
283
- const installCmd = detectPackageManager()
284
- execSync(`${installCmd} ${config.dependencies.join(" ")}`, {
285
- stdio: "inherit",
286
- })
287
- } catch {
288
- console.error(" ✗ Failed to install dependencies automatically.")
423
+ // 4. Install component dependencies (only those not already in package.json)
424
+ if (componentConfig.dependencies && componentConfig.dependencies.length > 0) {
425
+ const missingDeps = getMissingDeps(cwd, componentConfig.dependencies)
426
+ if (missingDeps.length === 0) {
427
+ console.log("")
428
+ console.log(" ✓ Component dependencies already in package.json, skipping install.")
429
+ } else {
430
+ console.log("")
431
+ console.log(` Installing dependencies: ${missingDeps.join(", ")}...`)
432
+ try {
433
+ const installCmd = detectPackageManager()
434
+ execSync(`${installCmd} ${missingDeps.join(" ")}`, {
435
+ stdio: "inherit",
436
+ })
437
+ } catch {
438
+ console.error(" ✗ Failed to install dependencies automatically.")
439
+ }
289
440
  }
290
441
  }
291
442
 
@@ -295,17 +446,15 @@ async function add(componentName) {
295
446
  }
296
447
 
297
448
  function showHelp() {
449
+ console.log(ASCII_BANNER)
450
+ console.log(style(c.bold, " Usage"))
451
+ console.log(style(c.dim, " novaui init Set up NovaUI (config, Tailwind, global.css, utils)"))
452
+ console.log(style(c.dim, " novaui add <component> Add a component (e.g. button, card)"))
298
453
  console.log("")
299
- console.log(" NovaUI CLI")
300
- console.log("")
301
- console.log(" Usage:")
302
- console.log(" novaui init Initialize project with Tailwind config, global.css, and utils")
303
- console.log(" novaui add <component> Add a component to your project")
304
- console.log("")
305
- console.log(" Examples:")
306
- console.log(" npx novaui-cli init")
307
- console.log(" npx novaui-cli add button")
308
- console.log(" npx novaui-cli add card")
454
+ console.log(style(c.bold, " Examples"))
455
+ console.log(style(c.cyan, " npx novaui init"))
456
+ console.log(style(c.cyan, " npx novaui add button"))
457
+ console.log(style(c.cyan, " npx novaui add card"))
309
458
  console.log("")
310
459
  }
311
460