uniqueui-cli 0.1.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.
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.add = add;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const ts_morph_1 = require("ts-morph");
12
+ const child_process_1 = require("child_process");
13
+ async function add(componentName, options) {
14
+ console.log(`Fetching ${componentName} from ${options.url}...`);
15
+ // 1. Load config
16
+ let config;
17
+ try {
18
+ config = await fs_extra_1.default.readJson("components.json");
19
+ }
20
+ catch (e) {
21
+ console.error(chalk_1.default.red("components.json not found. Run 'init' first."));
22
+ process.exit(1);
23
+ }
24
+ // 2. Fetch registry
25
+ let registry;
26
+ try {
27
+ // For local testing, if url is a file path, read it
28
+ if (options.url.startsWith(".")) {
29
+ registry = await fs_extra_1.default.readJson(options.url);
30
+ }
31
+ else {
32
+ const res = await (0, node_fetch_1.default)(`${options.url}/registry.json`);
33
+ if (!res.ok)
34
+ throw new Error("Failed to fetch registry");
35
+ registry = await res.json();
36
+ }
37
+ }
38
+ catch (e) {
39
+ console.error(chalk_1.default.red("Could not fetch registry.json"), e);
40
+ process.exit(1);
41
+ }
42
+ const item = registry.find((c) => c.name === componentName);
43
+ if (!item) {
44
+ console.error(chalk_1.default.red(`Component ${componentName} not found.`));
45
+ process.exit(1);
46
+ }
47
+ // 3. Install dependencies
48
+ if (item.dependencies.length) {
49
+ console.log(chalk_1.default.cyan(`Installing dependencies: ${item.dependencies.join(", ")}`));
50
+ try {
51
+ // Detect package manager - simplified
52
+ const pm = fs_extra_1.default.existsSync("pnpm-lock.yaml") ? "pnpm" : fs_extra_1.default.existsSync("yarn.lock") ? "yarn" : "npm";
53
+ const cmd = pm === "npm" ? "install" : "add"; // yarn add, pnpm add
54
+ (0, child_process_1.execSync)(`${pm} ${cmd} ${item.dependencies.join(" ")}`, { stdio: "inherit" });
55
+ }
56
+ catch (e) {
57
+ console.warn(chalk_1.default.yellow("Failed to install dependencies automatically. Please install them manually."));
58
+ }
59
+ }
60
+ // 4. Update Tailwind Config
61
+ if (item.tailwindConfig) {
62
+ console.log(chalk_1.default.cyan("Updating tailwind.config..."));
63
+ await updateTailwindConfig(config.tailwind.config, item.tailwindConfig);
64
+ }
65
+ // 5. Write Files
66
+ const targetDir = path_1.default.resolve(config.paths.components || "components/ui");
67
+ await fs_extra_1.default.ensureDir(targetDir);
68
+ for (const file of item.files) {
69
+ // We only handle ui components for now
70
+ if (file.type === "registry:ui") {
71
+ const fileName = path_1.default.basename(file.path);
72
+ const targetPath = path_1.default.join(targetDir, fileName);
73
+ await fs_extra_1.default.writeFile(targetPath, file.content);
74
+ console.log(chalk_1.default.green(`Created ${fileName}`));
75
+ }
76
+ // Handle utils if needed, or other types
77
+ }
78
+ }
79
+ async function updateTailwindConfig(configPath, newConfig) {
80
+ const project = new ts_morph_1.Project({
81
+ manipulationSettings: {
82
+ quoteKind: ts_morph_1.QuoteKind.Double,
83
+ }
84
+ });
85
+ // Attempt to add the file
86
+ const sourceFile = project.addSourceFileAtPath(configPath);
87
+ // Simplistic handling: look for export default or module.exports
88
+ // We expect the config to be an object literal.
89
+ // We need to merge `newConfig.theme.extend` into the existing config.
90
+ // logic: find 'theme' property -> find 'extend' property -> merge properties.
91
+ const exportAssignment = sourceFile.getExportAssignment((e) => !e.isExportEquals()); // export default
92
+ let objectLiteral;
93
+ if (exportAssignment) {
94
+ objectLiteral = exportAssignment.getExpression().asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
95
+ }
96
+ else {
97
+ // try module.exports
98
+ const stmt = sourceFile.getStatement((s) => {
99
+ if (s.getKind() === ts_morph_1.SyntaxKind.ExpressionStatement) {
100
+ const expr = s.asKind(ts_morph_1.SyntaxKind.ExpressionStatement)?.getExpression();
101
+ if (expr && expr.getKind() === ts_morph_1.SyntaxKind.BinaryExpression) {
102
+ const binary = expr.asKind(ts_morph_1.SyntaxKind.BinaryExpression);
103
+ const left = binary?.getLeft();
104
+ if (left?.getText() === "module.exports")
105
+ return true;
106
+ }
107
+ }
108
+ return false;
109
+ });
110
+ if (stmt) {
111
+ const binary = stmt.asKind(ts_morph_1.SyntaxKind.ExpressionStatement).getExpression().asKind(ts_morph_1.SyntaxKind.BinaryExpression);
112
+ objectLiteral = binary.getRight().asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
113
+ }
114
+ }
115
+ if (!objectLiteral) {
116
+ console.warn(chalk_1.default.yellow("Could not parse tailwind config object. Skipping update."));
117
+ return;
118
+ }
119
+ // Heuristics:
120
+ // newConfig is { theme: { extend: { animation: {...}, keyframes: {...} } } }
121
+ // Helper to get or create property object
122
+ const getOrCreateObjectProperty = (parentObj, name) => {
123
+ let prop = parentObj.getProperty(name);
124
+ if (!prop) {
125
+ parentObj.addPropertyAssignment({ name, initializer: "{}" });
126
+ prop = parentObj.getProperty(name);
127
+ }
128
+ const init = prop?.asKind(ts_morph_1.SyntaxKind.PropertyAssignment)?.getInitializer();
129
+ return init?.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
130
+ };
131
+ if (newConfig.theme?.extend) {
132
+ const themeObj = getOrCreateObjectProperty(objectLiteral, "theme");
133
+ if (themeObj) {
134
+ const extendObj = getOrCreateObjectProperty(themeObj, "extend");
135
+ if (extendObj) {
136
+ // Merge animations
137
+ const animations = newConfig.theme.extend.animation;
138
+ if (animations) {
139
+ const animObj = getOrCreateObjectProperty(extendObj, "animation");
140
+ if (animObj) {
141
+ for (const [key, value] of Object.entries(animations)) {
142
+ if (!animObj.getProperty(key)) {
143
+ animObj.addPropertyAssignment({ name: `"${key}"`, initializer: `"${value}"` });
144
+ }
145
+ }
146
+ }
147
+ }
148
+ // Merge keyframes
149
+ const keyframes = newConfig.theme.extend.keyframes;
150
+ if (keyframes) {
151
+ const keyframesObj = getOrCreateObjectProperty(extendObj, "keyframes");
152
+ if (keyframesObj) {
153
+ for (const [key, value] of Object.entries(keyframes)) {
154
+ if (!keyframesObj.getProperty(key)) {
155
+ // value is an object, strictly JSON stringify might be valid JS valid object literal needed?
156
+ // "JSON.stringify(value)" produces valid JSON which is valid JS object literal initializer if keys are quoted.
157
+ keyframesObj.addPropertyAssignment({ name: `"${key}"`, initializer: JSON.stringify(value) });
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ await sourceFile.save();
166
+ console.log(chalk_1.default.green("Tailwind config updated successfully."));
167
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.init = init;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ const prompts_1 = __importDefault(require("prompts"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ async function init() {
12
+ console.log(chalk_1.default.bold.green("Initializing UniqueUI..."));
13
+ const cwd = process.cwd();
14
+ const response = await (0, prompts_1.default)([
15
+ {
16
+ type: "text",
17
+ name: "componentsDir",
18
+ message: "Where would you like to install components?",
19
+ initial: "components/ui",
20
+ },
21
+ {
22
+ type: "toggle",
23
+ name: "typescript",
24
+ message: "Are you using TypeScript?",
25
+ initial: true,
26
+ active: "yes",
27
+ inactive: "no",
28
+ },
29
+ {
30
+ type: "text",
31
+ name: "tailwindConfig",
32
+ message: "Where is your tailwind.config.js located?",
33
+ initial: "tailwind.config.ts",
34
+ },
35
+ ]);
36
+ const config = {
37
+ $schema: "https://uniqueui.com/schema.json",
38
+ style: "default",
39
+ rsc: true,
40
+ tsx: response.typescript,
41
+ tailwind: {
42
+ config: response.tailwindConfig,
43
+ css: "app/globals.css", // simplifying assumption or could ask
44
+ baseColor: "slate",
45
+ cssVariables: true,
46
+ },
47
+ aliases: {
48
+ components: "@/components",
49
+ utils: "@/lib/utils",
50
+ },
51
+ paths: {
52
+ components: response.componentsDir,
53
+ lib: "lib" // simplifying
54
+ }
55
+ };
56
+ await fs_1.promises.writeFile(path_1.default.join(cwd, "components.json"), JSON.stringify(config, null, 2));
57
+ console.log(chalk_1.default.green("Configuration saved to components.json"));
58
+ }
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const init_1 = require("./commands/init");
6
+ const add_1 = require("./commands/add");
7
+ const program = new commander_1.Command();
8
+ program
9
+ .name("uniqueui")
10
+ .description("Add components from UniqueUI to your project")
11
+ .version("0.1.0");
12
+ program
13
+ .command("init")
14
+ .description("Configure your project for UniqueUI")
15
+ .action(init_1.init);
16
+ program
17
+ .command("add")
18
+ .description("Add a component to your project")
19
+ .argument("<component>", "the component to add")
20
+ .option("--url <url>", "the base URL of the registry", "https://raw.githubusercontent.com/prashantkumarsingh/uniqueui/main")
21
+ .action(add_1.add);
22
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "uniqueui-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for UniqueUI",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "uniqueui": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts"
13
+ },
14
+ "dependencies": {
15
+ "commander": "^11.1.0",
16
+ "chalk": "^4.1.2",
17
+ "ts-morph": "^21.0.1",
18
+ "zod": "^3.22.4",
19
+ "node-fetch": "^3.3.2",
20
+ "ora": "^5.4.1",
21
+ "prompts": "^2.4.2"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^20.10.0",
25
+ "typescript": "^5.3.3",
26
+ "@types/prompts": "^2.4.9",
27
+ "@types/fs-extra": "^11.0.4",
28
+ "fs-extra": "^11.2.0"
29
+ }
30
+ }
@@ -0,0 +1,184 @@
1
+
2
+ import { Command } from "commander";
3
+ import fetch from "node-fetch";
4
+ import path from "path";
5
+ import fs from "fs-extra";
6
+ import chalk from "chalk";
7
+ import { Project, SyntaxKind, QuoteKind } from "ts-morph";
8
+ import { execSync } from "child_process";
9
+
10
+ // Type definition for Registry (matching what we built)
11
+ type RegistryItem = {
12
+ name: string;
13
+ dependencies: string[];
14
+ files: Array<{ path: string; type: string; content: string }>;
15
+ tailwindConfig?: Record<string, any>;
16
+ };
17
+
18
+ export async function add(componentName: string, options: { url: string }) {
19
+ console.log(`Fetching ${componentName} from ${options.url}...`);
20
+
21
+ // 1. Load config
22
+ let config;
23
+ try {
24
+ config = await fs.readJson("components.json");
25
+ } catch (e) {
26
+ console.error(chalk.red("components.json not found. Run 'init' first."));
27
+ process.exit(1);
28
+ }
29
+
30
+ // 2. Fetch registry
31
+ let registry: RegistryItem[];
32
+ try {
33
+ // For local testing, if url is a file path, read it
34
+ if (options.url.startsWith(".")) {
35
+ registry = await fs.readJson(options.url);
36
+ } else {
37
+ const res = await fetch(`${options.url}/registry.json`);
38
+ if (!res.ok) throw new Error("Failed to fetch registry");
39
+ registry = await res.json() as RegistryItem[];
40
+ }
41
+ } catch (e) {
42
+ console.error(chalk.red("Could not fetch registry.json"), e);
43
+ process.exit(1);
44
+ }
45
+
46
+ const item = registry.find((c) => c.name === componentName);
47
+ if (!item) {
48
+ console.error(chalk.red(`Component ${componentName} not found.`));
49
+ process.exit(1);
50
+ }
51
+
52
+ // 3. Install dependencies
53
+ if (item.dependencies.length) {
54
+ console.log(chalk.cyan(`Installing dependencies: ${item.dependencies.join(", ")}`));
55
+ try {
56
+ // Detect package manager - simplified
57
+ const pm = fs.existsSync("pnpm-lock.yaml") ? "pnpm" : fs.existsSync("yarn.lock") ? "yarn" : "npm";
58
+ const cmd = pm === "npm" ? "install" : "add"; // yarn add, pnpm add
59
+ execSync(`${pm} ${cmd} ${item.dependencies.join(" ")}`, { stdio: "inherit" });
60
+ } catch (e) {
61
+ console.warn(chalk.yellow("Failed to install dependencies automatically. Please install them manually."));
62
+ }
63
+ }
64
+
65
+ // 4. Update Tailwind Config
66
+ if (item.tailwindConfig) {
67
+ console.log(chalk.cyan("Updating tailwind.config..."));
68
+ await updateTailwindConfig(config.tailwind.config, item.tailwindConfig);
69
+ }
70
+
71
+ // 5. Write Files
72
+ const targetDir = path.resolve(config.paths.components || "components/ui");
73
+ await fs.ensureDir(targetDir);
74
+
75
+ for (const file of item.files) {
76
+ // We only handle ui components for now
77
+ if (file.type === "registry:ui") {
78
+ const fileName = path.basename(file.path);
79
+ const targetPath = path.join(targetDir, fileName);
80
+ await fs.writeFile(targetPath, file.content);
81
+ console.log(chalk.green(`Created ${fileName}`));
82
+ }
83
+ // Handle utils if needed, or other types
84
+ }
85
+ }
86
+
87
+ async function updateTailwindConfig(configPath: string, newConfig: any) {
88
+ const project = new Project({
89
+ manipulationSettings: {
90
+ quoteKind: QuoteKind.Double,
91
+ }
92
+ });
93
+
94
+ // Attempt to add the file
95
+ const sourceFile = project.addSourceFileAtPath(configPath);
96
+
97
+ // Simplistic handling: look for export default or module.exports
98
+ // We expect the config to be an object literal.
99
+
100
+ // We need to merge `newConfig.theme.extend` into the existing config.
101
+ // logic: find 'theme' property -> find 'extend' property -> merge properties.
102
+
103
+ const exportAssignment = sourceFile.getExportAssignment((e) => !e.isExportEquals()); // export default
104
+ let objectLiteral;
105
+
106
+ if (exportAssignment) {
107
+ objectLiteral = exportAssignment.getExpression().asKind(SyntaxKind.ObjectLiteralExpression);
108
+ } else {
109
+ // try module.exports
110
+ const stmt = sourceFile.getStatement((s) => {
111
+ if (s.getKind() === SyntaxKind.ExpressionStatement) {
112
+ const expr = s.asKind(SyntaxKind.ExpressionStatement)?.getExpression();
113
+ if (expr && expr.getKind() === SyntaxKind.BinaryExpression) {
114
+ const binary = expr.asKind(SyntaxKind.BinaryExpression);
115
+ const left = binary?.getLeft();
116
+ if (left?.getText() === "module.exports") return true;
117
+ }
118
+ }
119
+ return false;
120
+ });
121
+ if (stmt) {
122
+ const binary = stmt.asKind(SyntaxKind.ExpressionStatement)!.getExpression().asKind(SyntaxKind.BinaryExpression)!;
123
+ objectLiteral = binary.getRight().asKind(SyntaxKind.ObjectLiteralExpression);
124
+ }
125
+ }
126
+
127
+ if (!objectLiteral) {
128
+ console.warn(chalk.yellow("Could not parse tailwind config object. Skipping update."));
129
+ return;
130
+ }
131
+
132
+ // Heuristics:
133
+ // newConfig is { theme: { extend: { animation: {...}, keyframes: {...} } } }
134
+
135
+ // Helper to get or create property object
136
+ const getOrCreateObjectProperty = (parentObj: any, name: string) => {
137
+ let prop = parentObj.getProperty(name);
138
+ if (!prop) {
139
+ parentObj.addPropertyAssignment({ name, initializer: "{}" });
140
+ prop = parentObj.getProperty(name);
141
+ }
142
+ const init = prop?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer();
143
+ return init?.asKind(SyntaxKind.ObjectLiteralExpression);
144
+ };
145
+
146
+ if (newConfig.theme?.extend) {
147
+ const themeObj = getOrCreateObjectProperty(objectLiteral, "theme");
148
+ if (themeObj) {
149
+ const extendObj = getOrCreateObjectProperty(themeObj, "extend");
150
+ if (extendObj) {
151
+ // Merge animations
152
+ const animations = newConfig.theme.extend.animation;
153
+ if (animations) {
154
+ const animObj = getOrCreateObjectProperty(extendObj, "animation");
155
+ if (animObj) {
156
+ for (const [key, value] of Object.entries(animations)) {
157
+ if (!animObj.getProperty(key)) {
158
+ animObj.addPropertyAssignment({ name: `"${key}"`, initializer: `"${value}"` });
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ // Merge keyframes
165
+ const keyframes = newConfig.theme.extend.keyframes;
166
+ if (keyframes) {
167
+ const keyframesObj = getOrCreateObjectProperty(extendObj, "keyframes");
168
+ if (keyframesObj) {
169
+ for (const [key, value] of Object.entries(keyframes)) {
170
+ if (!keyframesObj.getProperty(key)) {
171
+ // value is an object, strictly JSON stringify might be valid JS valid object literal needed?
172
+ // "JSON.stringify(value)" produces valid JSON which is valid JS object literal initializer if keys are quoted.
173
+ keyframesObj.addPropertyAssignment({ name: `"${key}"`, initializer: JSON.stringify(value) });
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ await sourceFile.save();
183
+ console.log(chalk.green("Tailwind config updated successfully."));
184
+ }
@@ -0,0 +1,62 @@
1
+
2
+ import { promises as fs } from "fs";
3
+ import path from "path";
4
+ import prompts from "prompts";
5
+ import chalk from "chalk";
6
+
7
+ export async function init() {
8
+ console.log(chalk.bold.green("Initializing UniqueUI..."));
9
+
10
+ const cwd = process.cwd();
11
+
12
+ const response = await prompts([
13
+ {
14
+ type: "text",
15
+ name: "componentsDir",
16
+ message: "Where would you like to install components?",
17
+ initial: "components/ui",
18
+ },
19
+ {
20
+ type: "toggle",
21
+ name: "typescript",
22
+ message: "Are you using TypeScript?",
23
+ initial: true,
24
+ active: "yes",
25
+ inactive: "no",
26
+ },
27
+ {
28
+ type: "text",
29
+ name: "tailwindConfig",
30
+ message: "Where is your tailwind.config.js located?",
31
+ initial: "tailwind.config.ts",
32
+ },
33
+ ]);
34
+
35
+ const config = {
36
+ $schema: "https://uniqueui.com/schema.json",
37
+ style: "default",
38
+ rsc: true,
39
+ tsx: response.typescript,
40
+ tailwind: {
41
+ config: response.tailwindConfig,
42
+ css: "app/globals.css", // simplifying assumption or could ask
43
+ baseColor: "slate",
44
+ cssVariables: true,
45
+ },
46
+ aliases: {
47
+ components: "@/components",
48
+ utils: "@/lib/utils",
49
+ },
50
+ paths: {
51
+ components: response.componentsDir,
52
+ lib: "lib" // simplifying
53
+ }
54
+ };
55
+
56
+ await fs.writeFile(
57
+ path.join(cwd, "components.json"),
58
+ JSON.stringify(config, null, 2)
59
+ );
60
+
61
+ console.log(chalk.green("Configuration saved to components.json"));
62
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { init } from "./commands/init";
4
+ import { add } from "./commands/add";
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name("uniqueui")
10
+ .description("Add components from UniqueUI to your project")
11
+ .version("0.1.0");
12
+
13
+ program
14
+ .command("init")
15
+ .description("Configure your project for UniqueUI")
16
+ .action(init);
17
+
18
+ program
19
+ .command("add")
20
+ .description("Add a component to your project")
21
+ .argument("<component>", "the component to add")
22
+ .option("--url <url>", "the base URL of the registry", "https://raw.githubusercontent.com/prashantkumarsingh/uniqueui/main")
23
+ .action(add);
24
+
25
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ]
10
+ }