sdaia-ui 0.3.0

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/dist/index.js +476 -0
  2. package/package.json +31 -0
package/dist/index.js ADDED
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/init.ts
7
+ import fs3 from "node:fs";
8
+ import path3 from "node:path";
9
+ import { execSync } from "node:child_process";
10
+ import prompts from "prompts";
11
+
12
+ // src/utils/config.ts
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ var CONFIG_FILE = "sdaia.config.json";
16
+ function getConfigPath() {
17
+ return path.resolve(process.cwd(), CONFIG_FILE);
18
+ }
19
+ function configExists() {
20
+ return fs.existsSync(getConfigPath());
21
+ }
22
+ function readConfig() {
23
+ const configPath = getConfigPath();
24
+ if (!fs.existsSync(configPath)) {
25
+ throw new Error(
26
+ `No ${CONFIG_FILE} found. Run "npx sdaia-ui init" first.`
27
+ );
28
+ }
29
+ return JSON.parse(fs.readFileSync(configPath, "utf-8"));
30
+ }
31
+ function writeConfig(config) {
32
+ fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
33
+ }
34
+
35
+ // src/utils/registry.ts
36
+ async function fetchRegistry(registryUrl) {
37
+ const res = await fetch(registryUrl);
38
+ if (!res.ok) {
39
+ throw new Error(`Failed to fetch registry from ${registryUrl}: ${res.status}`);
40
+ }
41
+ return res.json();
42
+ }
43
+ async function fetchComponentFile(registryUrl, componentName, fileName) {
44
+ const url = `${registryUrl}/${componentName}/${fileName}`;
45
+ const res = await fetch(url);
46
+ if (!res.ok) {
47
+ throw new Error(`Failed to fetch ${url}: ${res.status}`);
48
+ }
49
+ return res.text();
50
+ }
51
+ function resolveComponent(registry, nameOrSlug) {
52
+ const lower = nameOrSlug.toLowerCase();
53
+ return registry.components.find(
54
+ (c) => c.name.toLowerCase() === lower || c.slug === lower
55
+ );
56
+ }
57
+ function resolveAllDependencies(registry, componentNames) {
58
+ const resolved = /* @__PURE__ */ new Map();
59
+ const visited = /* @__PURE__ */ new Set();
60
+ function walk(name) {
61
+ if (visited.has(name)) return;
62
+ visited.add(name);
63
+ const entry = registry.components.find((c) => c.name === name);
64
+ if (!entry) return;
65
+ for (const dep of entry.internalDependencies) {
66
+ walk(dep);
67
+ }
68
+ resolved.set(name, entry);
69
+ }
70
+ for (const name of componentNames) {
71
+ walk(name);
72
+ }
73
+ return [...resolved.values()];
74
+ }
75
+ function collectNpmDependencies(entries) {
76
+ const deps = /* @__PURE__ */ new Set();
77
+ for (const entry of entries) {
78
+ for (const dep of entry.dependencies) {
79
+ deps.add(dep);
80
+ }
81
+ }
82
+ return [...deps];
83
+ }
84
+
85
+ // src/utils/package-manager.ts
86
+ import fs2 from "node:fs";
87
+ import path2 from "node:path";
88
+ function detectPackageManager() {
89
+ const cwd = process.cwd();
90
+ if (fs2.existsSync(path2.join(cwd, "bun.lockb")) || fs2.existsSync(path2.join(cwd, "bun.lock"))) {
91
+ return "bun";
92
+ }
93
+ if (fs2.existsSync(path2.join(cwd, "pnpm-lock.yaml"))) {
94
+ return "pnpm";
95
+ }
96
+ if (fs2.existsSync(path2.join(cwd, "yarn.lock"))) {
97
+ return "yarn";
98
+ }
99
+ return "npm";
100
+ }
101
+ function getInstallCommand(pm, packages) {
102
+ const pkgs = packages.join(" ");
103
+ switch (pm) {
104
+ case "bun":
105
+ return `bun add ${pkgs}`;
106
+ case "pnpm":
107
+ return `pnpm add ${pkgs}`;
108
+ case "yarn":
109
+ return `yarn add ${pkgs}`;
110
+ default:
111
+ return `npm install ${pkgs}`;
112
+ }
113
+ }
114
+
115
+ // src/utils/logger.ts
116
+ import pc from "picocolors";
117
+ var logger = {
118
+ info: (msg) => console.log(pc.cyan("info"), msg),
119
+ success: (msg) => console.log(pc.green("success"), msg),
120
+ warn: (msg) => console.log(pc.yellow("warn"), msg),
121
+ error: (msg) => console.log(pc.red("error"), msg),
122
+ break: () => console.log("")
123
+ };
124
+
125
+ // src/commands/init.ts
126
+ var DEFAULT_REGISTRY_URL = "https://ds.nacew.com/api/registry";
127
+ var DEFAULT_COMPONENT_DIR = "src/components/ui";
128
+ var DEFAULT_UTILS_DIR = "src/lib";
129
+ function isTailwindInstalled() {
130
+ const pkgJsonPath = path3.resolve(process.cwd(), "package.json");
131
+ if (!fs3.existsSync(pkgJsonPath)) return false;
132
+ const pkgJson = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
133
+ const allDeps = {
134
+ ...pkgJson.dependencies,
135
+ ...pkgJson.devDependencies
136
+ };
137
+ return !!allDeps["tailwindcss"];
138
+ }
139
+ async function init() {
140
+ logger.break();
141
+ logger.info("Initializing SDAIA Design System in your project...");
142
+ logger.break();
143
+ if (configExists()) {
144
+ const { overwrite } = await prompts({
145
+ type: "confirm",
146
+ name: "overwrite",
147
+ message: "sdaia.config.json already exists. Overwrite?",
148
+ initial: false
149
+ });
150
+ if (!overwrite) {
151
+ logger.info("Init cancelled.");
152
+ return;
153
+ }
154
+ }
155
+ const response = await prompts([
156
+ {
157
+ type: "text",
158
+ name: "registryUrl",
159
+ message: "Registry URL (your docs site API):",
160
+ initial: DEFAULT_REGISTRY_URL
161
+ },
162
+ {
163
+ type: "text",
164
+ name: "componentDir",
165
+ message: "Where should components be installed?",
166
+ initial: DEFAULT_COMPONENT_DIR
167
+ },
168
+ {
169
+ type: "text",
170
+ name: "utilsDir",
171
+ message: "Where should utilities (cn.ts) be installed?",
172
+ initial: DEFAULT_UTILS_DIR
173
+ }
174
+ ]);
175
+ if (!response.registryUrl) {
176
+ logger.error("Init cancelled.");
177
+ return;
178
+ }
179
+ const config = {
180
+ registryUrl: response.registryUrl,
181
+ componentDir: response.componentDir,
182
+ utilsDir: response.utilsDir
183
+ };
184
+ writeConfig(config);
185
+ logger.success("Created sdaia.config.json");
186
+ const pm = detectPackageManager();
187
+ if (!isTailwindInstalled()) {
188
+ logger.warn("Tailwind CSS is not installed. Components require Tailwind CSS to work.");
189
+ logger.break();
190
+ const { installTailwind } = await prompts({
191
+ type: "confirm",
192
+ name: "installTailwind",
193
+ message: "Would you like to install Tailwind CSS now?",
194
+ initial: true
195
+ });
196
+ if (installTailwind) {
197
+ const tailwindDeps = ["tailwindcss", "@tailwindcss/postcss", "postcss"];
198
+ const tailwindCmd = getInstallCommand(pm, tailwindDeps.map((d) => `-D ${d}`));
199
+ logger.info("Installing Tailwind CSS...");
200
+ try {
201
+ const devFlag = pm === "npm" ? "--save-dev" : "-D";
202
+ const cmd = getInstallCommand(pm, [devFlag, ...tailwindDeps]);
203
+ execSync(cmd, { stdio: "inherit", cwd: process.cwd() });
204
+ logger.success("Tailwind CSS installed");
205
+ } catch {
206
+ logger.warn(
207
+ `Failed to auto-install Tailwind CSS. Install manually:
208
+ ${tailwindCmd}`
209
+ );
210
+ }
211
+ const postcssConfigPath = path3.resolve(process.cwd(), "postcss.config.mjs");
212
+ if (!fs3.existsSync(postcssConfigPath)) {
213
+ const postcssConfig = `/** @type {import('postcss-load-config').Config} */
214
+ const config = {
215
+ plugins: {
216
+ '@tailwindcss/postcss': {},
217
+ },
218
+ };
219
+
220
+ export default config;
221
+ `;
222
+ fs3.writeFileSync(postcssConfigPath, postcssConfig);
223
+ logger.success("Created postcss.config.mjs");
224
+ }
225
+ const cssFiles = ["src/index.css", "src/App.css", "src/globals.css", "app/globals.css"];
226
+ let cssFilePath = null;
227
+ for (const cssFile of cssFiles) {
228
+ const fullPath = path3.resolve(process.cwd(), cssFile);
229
+ if (fs3.existsSync(fullPath)) {
230
+ cssFilePath = fullPath;
231
+ break;
232
+ }
233
+ }
234
+ if (cssFilePath) {
235
+ const cssContent = fs3.readFileSync(cssFilePath, "utf-8");
236
+ if (!cssContent.includes('@import "tailwindcss"') && !cssContent.includes("@import 'tailwindcss'")) {
237
+ fs3.writeFileSync(cssFilePath, `@import "tailwindcss";
238
+
239
+ ${cssContent}`);
240
+ logger.success(`Added Tailwind import to ${path3.relative(process.cwd(), cssFilePath)}`);
241
+ }
242
+ } else {
243
+ logger.warn(
244
+ 'Could not find a CSS entry file. Add this to your main CSS file:\n @import "tailwindcss";'
245
+ );
246
+ }
247
+ } else {
248
+ logger.break();
249
+ logger.warn("Skipping Tailwind CSS installation.");
250
+ logger.info("Components will not be styled correctly without Tailwind CSS.");
251
+ logger.info("Install it manually later:");
252
+ logger.info(` ${getInstallCommand(pm, ["-D", "tailwindcss", "@tailwindcss/postcss", "postcss"])}`);
253
+ logger.info("Then add to your main CSS file:");
254
+ logger.info(' @import "tailwindcss";');
255
+ }
256
+ } else {
257
+ logger.success("Tailwind CSS detected");
258
+ }
259
+ logger.break();
260
+ logger.info("Installing Icon Factory (required for all components)...");
261
+ const iconFactoryCmd = getInstallCommand(pm, ["sdaia-ui"]);
262
+ try {
263
+ execSync(iconFactoryCmd, { stdio: "inherit", cwd: process.cwd() });
264
+ logger.success("Icon Factory installed");
265
+ } catch {
266
+ logger.warn(`Failed to auto-install Icon Factory. Run manually:
267
+ ${iconFactoryCmd}`);
268
+ }
269
+ const utilityDeps = ["tailwind-merge", "clsx", "class-variance-authority"];
270
+ const utilityCmd = getInstallCommand(pm, utilityDeps);
271
+ logger.break();
272
+ logger.info("Installing utility dependencies...");
273
+ try {
274
+ execSync(utilityCmd, { stdio: "inherit", cwd: process.cwd() });
275
+ logger.success("Utility dependencies installed");
276
+ } catch {
277
+ logger.warn(`Failed to auto-install. Run manually:
278
+ ${utilityCmd}`);
279
+ }
280
+ const utilsPath = path3.resolve(process.cwd(), config.utilsDir);
281
+ const cnPath = path3.join(utilsPath, "cn.ts");
282
+ if (fs3.existsSync(cnPath)) {
283
+ logger.info("cn.ts already exists, skipping");
284
+ } else {
285
+ try {
286
+ const cnSource = await fetchComponentFile(config.registryUrl, "cn", "cn.ts");
287
+ fs3.mkdirSync(utilsPath, { recursive: true });
288
+ fs3.writeFileSync(cnPath, cnSource);
289
+ logger.success(`Created ${path3.relative(process.cwd(), cnPath)}`);
290
+ } catch {
291
+ logger.warn("Could not download cn.ts \u2014 you may need to create it manually.");
292
+ const fallback = `import { clsx, type ClassValue } from 'clsx';
293
+ import { twMerge } from 'tailwind-merge';
294
+
295
+ export function cn(...inputs: ClassValue[]) {
296
+ return twMerge(clsx(inputs));
297
+ }
298
+ `;
299
+ fs3.mkdirSync(utilsPath, { recursive: true });
300
+ fs3.writeFileSync(cnPath, fallback);
301
+ logger.success(`Created ${path3.relative(process.cwd(), cnPath)} (from template)`);
302
+ }
303
+ }
304
+ const compPath = path3.resolve(process.cwd(), config.componentDir);
305
+ fs3.mkdirSync(compPath, { recursive: true });
306
+ logger.break();
307
+ logger.success("SDAIA Design System initialized!");
308
+ logger.break();
309
+ logger.info("You can now add components:");
310
+ logger.info(" npx sdaia-ui add button");
311
+ logger.info(" npx sdaia-ui add modal");
312
+ logger.info(" npx sdaia-ui add button modal tag");
313
+ logger.break();
314
+ }
315
+
316
+ // src/commands/add.ts
317
+ import fs4 from "node:fs";
318
+ import path5 from "node:path";
319
+ import { execSync as execSync2 } from "node:child_process";
320
+
321
+ // src/utils/transformer.ts
322
+ import path4 from "node:path";
323
+ function transformImports(source, componentDir, utilsDir, componentName) {
324
+ const componentPath = path4.posix.join(componentDir, componentName);
325
+ const cnRelative = path4.posix.relative(componentPath, utilsDir);
326
+ const cnImportPath = cnRelative ? `${cnRelative}/cn` : "./cn";
327
+ let result = source.replace(
328
+ /from\s+(['"])\.\.\/\.\.\/lib\/cn\1/g,
329
+ `from '${cnImportPath}'`
330
+ );
331
+ result = result.replace(
332
+ /from\s+(['"])\.\.\/\.\.\/icons\/[^'"]+\1/g,
333
+ `from 'sdaia-ui/icons'`
334
+ );
335
+ result = result.replace(
336
+ /from\s+(['"])\.\.\/\.\.\/hooks\/[^'"]+\1/g,
337
+ `from 'sdaia-ui/hooks'`
338
+ );
339
+ return result;
340
+ }
341
+ function ensureReactImport(source, fileName) {
342
+ if (!fileName.endsWith(".tsx")) return source;
343
+ if (/import\s+React[\s,]/m.test(source)) return source;
344
+ const useClientMatch = source.match(/^['"]use client['"];?\s*\n/);
345
+ if (useClientMatch) {
346
+ const directive = useClientMatch[0];
347
+ return directive + "import React from 'react';\n" + source.slice(directive.length);
348
+ }
349
+ return "import React from 'react';\n" + source;
350
+ }
351
+
352
+ // src/commands/add.ts
353
+ async function add(componentSlugs, options) {
354
+ const config = readConfig();
355
+ logger.break();
356
+ logger.info("Fetching component registry...");
357
+ const registry = await fetchRegistry(config.registryUrl);
358
+ const requestedEntries = [];
359
+ for (const slug of componentSlugs) {
360
+ const entry = resolveComponent(registry, slug);
361
+ if (!entry) {
362
+ logger.error(
363
+ `Component "${slug}" not found in the registry.
364
+ Available: ${registry.components.map((c) => c.slug).join(", ")}`
365
+ );
366
+ return;
367
+ }
368
+ requestedEntries.push(entry);
369
+ }
370
+ const allEntries = resolveAllDependencies(
371
+ registry,
372
+ requestedEntries.map((e) => e.name)
373
+ );
374
+ const toInstall = [];
375
+ const alreadyExists = [];
376
+ for (const entry of allEntries) {
377
+ const compDir = path5.resolve(
378
+ process.cwd(),
379
+ config.componentDir,
380
+ entry.name
381
+ );
382
+ const exists = fs4.existsSync(path5.join(compDir, "index.ts"));
383
+ if (exists && !options.overwrite) {
384
+ alreadyExists.push(entry.name);
385
+ } else {
386
+ toInstall.push(entry);
387
+ }
388
+ }
389
+ if (alreadyExists.length > 0) {
390
+ logger.info(
391
+ `Skipping (already installed): ${alreadyExists.join(", ")}. Use --overwrite to replace.`
392
+ );
393
+ }
394
+ if (toInstall.length === 0) {
395
+ logger.success("All components are already installed.");
396
+ return;
397
+ }
398
+ const requested = requestedEntries.map((e) => e.name);
399
+ const deps = toInstall.filter((e) => !requested.includes(e.name)).map((e) => e.name);
400
+ logger.info(`Installing: ${toInstall.map((e) => e.name).join(", ")}`);
401
+ if (deps.length > 0) {
402
+ logger.info(` (auto-resolved dependencies: ${deps.join(", ")})`);
403
+ }
404
+ for (const entry of toInstall) {
405
+ const compDir = path5.resolve(
406
+ process.cwd(),
407
+ config.componentDir,
408
+ entry.name
409
+ );
410
+ fs4.mkdirSync(compDir, { recursive: true });
411
+ for (const fileName of entry.files) {
412
+ let source = await fetchComponentFile(
413
+ config.registryUrl,
414
+ entry.name,
415
+ fileName
416
+ );
417
+ if (fileName.endsWith(".tsx") || fileName.endsWith(".ts")) {
418
+ source = transformImports(
419
+ source,
420
+ config.componentDir,
421
+ config.utilsDir,
422
+ entry.name
423
+ );
424
+ source = ensureReactImport(source, fileName);
425
+ }
426
+ fs4.writeFileSync(path5.join(compDir, fileName), source);
427
+ }
428
+ logger.success(
429
+ ` ${entry.name} \u2192 ${path5.relative(process.cwd(), compDir)}/`
430
+ );
431
+ }
432
+ const npmDeps = collectNpmDependencies(toInstall);
433
+ const baseDeps = ["clsx", "tailwind-merge"];
434
+ const allDeps = [.../* @__PURE__ */ new Set([...npmDeps, ...baseDeps])];
435
+ const pkgJsonPath = path5.resolve(process.cwd(), "package.json");
436
+ let missingDeps = allDeps;
437
+ if (fs4.existsSync(pkgJsonPath)) {
438
+ const pkgJson = JSON.parse(fs4.readFileSync(pkgJsonPath, "utf-8"));
439
+ const installed = {
440
+ ...pkgJson.dependencies,
441
+ ...pkgJson.devDependencies
442
+ };
443
+ missingDeps = allDeps.filter((dep) => !installed[dep]);
444
+ }
445
+ if (missingDeps.length > 0) {
446
+ const pm = detectPackageManager();
447
+ const cmd = getInstallCommand(pm, missingDeps);
448
+ logger.info(`Installing npm dependencies: ${missingDeps.join(", ")}`);
449
+ try {
450
+ execSync2(cmd, { stdio: "inherit", cwd: process.cwd() });
451
+ } catch {
452
+ logger.warn(`Failed to auto-install. Run manually:
453
+ ${cmd}`);
454
+ }
455
+ }
456
+ logger.break();
457
+ logger.success("Done! Components are ready to use.");
458
+ logger.break();
459
+ const first = requestedEntries[0];
460
+ if (first) {
461
+ logger.info("Import example:");
462
+ logger.info(
463
+ ` import { ${first.name} } from './${config.componentDir}/${first.name}';`
464
+ );
465
+ logger.break();
466
+ }
467
+ }
468
+
469
+ // src/index.ts
470
+ var program = new Command();
471
+ program.name("sdaia-ui").description("Install SDAIA Design System components into your project").version("0.1.0");
472
+ program.command("init").description("Initialize your project for SDAIA Design System components").action(init);
473
+ program.command("add").description("Add components to your project").argument("<components...>", "Component names (e.g. button modal tag)").option("--overwrite", "Overwrite existing components", false).action((components, options) => {
474
+ add(components, options);
475
+ });
476
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "sdaia-ui",
3
+ "version": "0.3.0",
4
+ "description": "CLI for installing SDAIA Design System components into your project",
5
+ "type": "module",
6
+ "bin": {
7
+ "sdaia-ui": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "node build.mjs",
14
+ "dev": "node build.mjs --watch"
15
+ },
16
+ "dependencies": {
17
+ "commander": "^13.1.0",
18
+ "prompts": "^2.4.2",
19
+ "picocolors": "^1.1.1"
20
+ },
21
+ "devDependencies": {
22
+ "@types/prompts": "^2.4.9",
23
+ "@types/node": "^22.0.0",
24
+ "esbuild": "^0.25.0",
25
+ "typescript": "^5.8.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "license": "MIT"
31
+ }