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.
- package/package.json +1 -1
- package/src/bin.js +210 -61
package/package.json
CHANGED
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
|
-
|
|
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
|
-
"
|
|
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(
|
|
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(
|
|
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
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
-
const utilsDir = path.join(cwd, "src", "lib")
|
|
291
|
+
const utilsDir = path.join(cwd, config.lib)
|
|
168
292
|
ensureDir(utilsDir)
|
|
169
|
-
|
|
293
|
+
const utilsPath = path.join(utilsDir, "utils.ts")
|
|
294
|
+
writeIfNotExists(utilsPath, UTILS_CONTENT, `${config.lib}/utils.ts`)
|
|
170
295
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
300
|
+
const tailwindContent = getTailwindConfigContent(config)
|
|
177
301
|
writeIfNotExists(
|
|
178
302
|
path.join(cwd, "tailwind.config.js"),
|
|
179
|
-
|
|
303
|
+
tailwindContent,
|
|
180
304
|
"tailwind.config.js"
|
|
181
305
|
)
|
|
182
306
|
|
|
183
|
-
//
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
console.
|
|
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("
|
|
342
|
+
console.log(style(c.bold, " Next steps:"))
|
|
207
343
|
console.log("")
|
|
208
|
-
console.log("
|
|
209
|
-
console.log(
|
|
210
|
-
console.log("
|
|
211
|
-
console.log("
|
|
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
|
|
246
|
-
const
|
|
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,
|
|
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(
|
|
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
|
|
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 (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
|
|
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(
|
|
300
|
-
console.log("")
|
|
301
|
-
console.log("
|
|
302
|
-
console.log("
|
|
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
|
|