@honq-ui/cli 0.0.1
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/dist/index.js +231 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/add.ts
|
|
7
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync } from "fs";
|
|
8
|
+
import { dirname as dirname2 } from "path";
|
|
9
|
+
import { execSync } from "child_process";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
|
|
12
|
+
// src/utils/registry.ts
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
import { dirname, join } from "path";
|
|
15
|
+
var require2 = createRequire(import.meta.url);
|
|
16
|
+
function loadRegistry() {
|
|
17
|
+
return require2("@honq-ui/registry/registry.json");
|
|
18
|
+
}
|
|
19
|
+
function getRegistryRoot() {
|
|
20
|
+
const pkgJsonPath = require2.resolve("@honq-ui/registry/package.json");
|
|
21
|
+
return dirname(pkgJsonPath);
|
|
22
|
+
}
|
|
23
|
+
function resolveComponentFile(registryRoot, filePath) {
|
|
24
|
+
return join(registryRoot, filePath);
|
|
25
|
+
}
|
|
26
|
+
function resolveTransitiveDependencies(registry, componentNames) {
|
|
27
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
28
|
+
function visit(name) {
|
|
29
|
+
if (resolved.has(name)) return;
|
|
30
|
+
resolved.add(name);
|
|
31
|
+
const entry = registry.components[name];
|
|
32
|
+
if (!entry) return;
|
|
33
|
+
for (const dep of entry.registryDependencies) {
|
|
34
|
+
visit(dep);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const name of componentNames) {
|
|
38
|
+
visit(name);
|
|
39
|
+
}
|
|
40
|
+
return Array.from(resolved);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/utils/detect-pm.ts
|
|
44
|
+
import { existsSync } from "fs";
|
|
45
|
+
import { join as join2 } from "path";
|
|
46
|
+
function detectPackageManager(cwd = process.cwd()) {
|
|
47
|
+
if (existsSync(join2(cwd, "bun.lockb"))) return "bun";
|
|
48
|
+
if (existsSync(join2(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
49
|
+
if (existsSync(join2(cwd, "yarn.lock"))) return "yarn";
|
|
50
|
+
return "npm";
|
|
51
|
+
}
|
|
52
|
+
function buildInstallCommand(pm, packages) {
|
|
53
|
+
const pkgList = packages.join(" ");
|
|
54
|
+
switch (pm) {
|
|
55
|
+
case "bun":
|
|
56
|
+
return `bun add ${pkgList}`;
|
|
57
|
+
case "pnpm":
|
|
58
|
+
return `pnpm add ${pkgList}`;
|
|
59
|
+
case "yarn":
|
|
60
|
+
return `yarn add ${pkgList}`;
|
|
61
|
+
case "npm":
|
|
62
|
+
return `npm install ${pkgList}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/utils/resolve-paths.ts
|
|
67
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
68
|
+
import { join as join3 } from "path";
|
|
69
|
+
var CONFIG_FILENAME = "components.json";
|
|
70
|
+
function readProjectConfig(cwd = process.cwd()) {
|
|
71
|
+
const configPath = join3(cwd, CONFIG_FILENAME);
|
|
72
|
+
if (!existsSync2(configPath)) return null;
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function defaultConfig() {
|
|
80
|
+
return {
|
|
81
|
+
componentsDir: "src/components",
|
|
82
|
+
stylesDir: "src/styles"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function resolveTargetPath(config, registryFilePath, cwd = process.cwd()) {
|
|
86
|
+
const relative = registryFilePath.replace(/^components\//, "");
|
|
87
|
+
return join3(cwd, config.componentsDir, relative);
|
|
88
|
+
}
|
|
89
|
+
function resolveUtilsPath(config, cwd = process.cwd()) {
|
|
90
|
+
const srcDir = config.componentsDir.replace(/\/components$/, "");
|
|
91
|
+
return join3(cwd, srcDir, "utils", "cn.ts");
|
|
92
|
+
}
|
|
93
|
+
function resolveStylesPath(config, filename, cwd = process.cwd()) {
|
|
94
|
+
return join3(cwd, config.stylesDir, filename);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/commands/add.ts
|
|
98
|
+
function addCommand(componentNames, options, cwd = process.cwd()) {
|
|
99
|
+
const registry = loadRegistry();
|
|
100
|
+
const config = readProjectConfig(cwd) ?? defaultConfig();
|
|
101
|
+
const registryRoot = getRegistryRoot();
|
|
102
|
+
const unknown = componentNames.filter((name) => !registry.components[name]);
|
|
103
|
+
if (unknown.length > 0) {
|
|
104
|
+
console.error(pc.red(`Unknown component(s): ${unknown.join(", ")}`));
|
|
105
|
+
console.error(`Run ${pc.cyan("honq-ui list")} to see available components.`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const allNames = resolveTransitiveDependencies(registry, componentNames);
|
|
109
|
+
const requested = new Set(componentNames);
|
|
110
|
+
const autoResolved = allNames.filter((name) => !requested.has(name));
|
|
111
|
+
if (autoResolved.length > 0) {
|
|
112
|
+
console.log(pc.dim(`Resolving dependencies: ${autoResolved.join(", ")}`));
|
|
113
|
+
}
|
|
114
|
+
const allDeps = /* @__PURE__ */ new Set();
|
|
115
|
+
allDeps.add("clsx");
|
|
116
|
+
allDeps.add("tailwind-merge");
|
|
117
|
+
for (const name of allNames) {
|
|
118
|
+
const entry = registry.components[name];
|
|
119
|
+
for (const file of entry.files) {
|
|
120
|
+
const src = resolveComponentFile(registryRoot, file);
|
|
121
|
+
const dest = resolveTargetPath(config, file, cwd);
|
|
122
|
+
if (existsSync3(dest) && !options.overwrite) {
|
|
123
|
+
console.log(pc.yellow(` skip`) + ` ${dest.replace(cwd + "/", "")} (already exists)`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!existsSync3(dirname2(dest))) {
|
|
127
|
+
mkdirSync(dirname2(dest), { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
copyFileSync(src, dest);
|
|
130
|
+
console.log(pc.green(`\u2713`) + ` ${dest.replace(cwd + "/", "")}`);
|
|
131
|
+
}
|
|
132
|
+
for (const dep of entry.dependencies) {
|
|
133
|
+
allDeps.add(dep);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const cnDest = resolveUtilsPath(config, cwd);
|
|
137
|
+
if (!existsSync3(cnDest)) {
|
|
138
|
+
const cnSrc = `${registryRoot}/utils/cn.ts`;
|
|
139
|
+
if (!existsSync3(dirname2(cnDest))) {
|
|
140
|
+
mkdirSync(dirname2(cnDest), { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
copyFileSync(cnSrc, cnDest);
|
|
143
|
+
console.log(pc.green(`\u2713`) + ` ${cnDest.replace(cwd + "/", "")}`);
|
|
144
|
+
}
|
|
145
|
+
if (allDeps.size > 0) {
|
|
146
|
+
const pm = detectPackageManager(cwd);
|
|
147
|
+
const cmd = buildInstallCommand(pm, Array.from(allDeps));
|
|
148
|
+
console.log(pc.dim(`
|
|
149
|
+
Installing dependencies...`));
|
|
150
|
+
try {
|
|
151
|
+
execSync(cmd, { cwd, stdio: "inherit" });
|
|
152
|
+
} catch {
|
|
153
|
+
console.error(pc.red(`Failed to install dependencies. Run manually:`));
|
|
154
|
+
console.error(pc.cyan(` ${cmd}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.log(pc.green(`
|
|
158
|
+
Done!`));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/commands/init.ts
|
|
162
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
163
|
+
import { dirname as dirname3 } from "path";
|
|
164
|
+
import pc2 from "picocolors";
|
|
165
|
+
function initCommand(cwd = process.cwd()) {
|
|
166
|
+
const existing = readProjectConfig(cwd);
|
|
167
|
+
if (existing) {
|
|
168
|
+
console.log(pc2.yellow(`${CONFIG_FILENAME} already exists. Run 'honq-ui add' to add components.`));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const config = defaultConfig();
|
|
172
|
+
const registryRoot = getRegistryRoot();
|
|
173
|
+
const configPath = `${cwd}/${CONFIG_FILENAME}`;
|
|
174
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
175
|
+
console.log(pc2.green(`\u2713`) + ` Created ${CONFIG_FILENAME}`);
|
|
176
|
+
const themeSrc = `${registryRoot}/styles/theme.css`;
|
|
177
|
+
const themeDest = resolveStylesPath(config, "theme.css", cwd);
|
|
178
|
+
if (!existsSync4(dirname3(themeDest))) {
|
|
179
|
+
mkdirSync2(dirname3(themeDest), { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
if (existsSync4(themeDest)) {
|
|
182
|
+
console.log(pc2.yellow(` styles/theme.css already exists, skipping`));
|
|
183
|
+
} else {
|
|
184
|
+
copyFileSync2(themeSrc, themeDest);
|
|
185
|
+
console.log(pc2.green(`\u2713`) + ` Copied theme.css \u2192 ${themeDest.replace(cwd + "/", "")}`);
|
|
186
|
+
}
|
|
187
|
+
const componentsDir = `${cwd}/${config.componentsDir}`;
|
|
188
|
+
if (!existsSync4(componentsDir)) {
|
|
189
|
+
mkdirSync2(componentsDir, { recursive: true });
|
|
190
|
+
console.log(pc2.green(`\u2713`) + ` Created ${config.componentsDir}/`);
|
|
191
|
+
}
|
|
192
|
+
console.log(`
|
|
193
|
+
Done! Run ${pc2.cyan("honq-ui add <component>")} to add components.`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/commands/list.ts
|
|
197
|
+
import pc3 from "picocolors";
|
|
198
|
+
function listCommand() {
|
|
199
|
+
const registry = loadRegistry();
|
|
200
|
+
const components = registry.components;
|
|
201
|
+
const groups = {
|
|
202
|
+
primitive: [],
|
|
203
|
+
composite: [],
|
|
204
|
+
layout: []
|
|
205
|
+
};
|
|
206
|
+
for (const [name, entry] of Object.entries(components)) {
|
|
207
|
+
groups[entry.type].push(name);
|
|
208
|
+
}
|
|
209
|
+
for (const type of ["primitive", "composite", "layout"]) {
|
|
210
|
+
const names = groups[type].sort();
|
|
211
|
+
if (names.length === 0) continue;
|
|
212
|
+
const label = type.charAt(0).toUpperCase() + type.slice(1) + "s";
|
|
213
|
+
console.log(`
|
|
214
|
+
${pc3.bold(label)}`);
|
|
215
|
+
for (const name of names) {
|
|
216
|
+
const entry = components[name];
|
|
217
|
+
console.log(` ${pc3.cyan(name.padEnd(24))} ${pc3.dim(entry.description)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
console.log();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/index.ts
|
|
224
|
+
var program = new Command();
|
|
225
|
+
program.name("honq-ui").description("Add Honq-UI components to your project").version("0.0.1");
|
|
226
|
+
program.command("init").description("Set up Honq-UI in your project (copies theme.css, creates config)").action(() => initCommand());
|
|
227
|
+
program.command("add").description("Add one or more components to your project").argument("<components...>", "Component names to add").option("--overwrite", "Overwrite existing files", false).action((components, options) => {
|
|
228
|
+
addCommand(components, options);
|
|
229
|
+
});
|
|
230
|
+
program.command("list").description("List all available components").action(() => listCommand());
|
|
231
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@honq-ui/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI for adding Honq-UI components to your project",
|
|
5
|
+
"author": "Thomas",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/thomasbp/honq-ui",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"honq-ui": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist .turbo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@honq-ui/registry": "workspace:*",
|
|
27
|
+
"commander": "^12.0.0",
|
|
28
|
+
"picocolors": "^1.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"tsup": "^8.3.5",
|
|
33
|
+
"typescript": "^5.7.2"
|
|
34
|
+
}
|
|
35
|
+
}
|