@hypoth-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.
@@ -0,0 +1,93 @@
1
+ import {
2
+ fetchRegistry,
3
+ getComponent
4
+ } from "./chunk-GJ6JOQ3Q.js";
5
+ import {
6
+ readConfig
7
+ } from "./chunk-YPKFYE45.js";
8
+
9
+ // src/commands/diff.ts
10
+ import pc from "picocolors";
11
+ async function diffCommand(options = {}) {
12
+ const cwd = process.cwd();
13
+ let config;
14
+ try {
15
+ config = readConfig(cwd);
16
+ } catch (error) {
17
+ console.error(pc.red(error.message));
18
+ process.exit(1);
19
+ }
20
+ if (config.components.length === 0) {
21
+ console.log(pc.yellow("No components installed yet."));
22
+ console.log(pc.dim("Run 'npx @hypoth-ui/cli add <component>' to add components."));
23
+ return;
24
+ }
25
+ console.log(pc.dim("Checking for updates..."));
26
+ const registry = await fetchRegistry();
27
+ const updates = [];
28
+ for (const installed of config.components) {
29
+ const component = getComponent(registry, installed.name);
30
+ if (!component) {
31
+ updates.push({
32
+ name: installed.name,
33
+ currentVersion: installed.version,
34
+ latestVersion: "unknown",
35
+ hasUpdate: false
36
+ });
37
+ continue;
38
+ }
39
+ const hasUpdate = compareVersions(installed.version, component.version) < 0;
40
+ updates.push({
41
+ name: installed.name,
42
+ currentVersion: installed.version,
43
+ latestVersion: component.version,
44
+ hasUpdate
45
+ });
46
+ }
47
+ if (options.json) {
48
+ console.log(JSON.stringify(updates, null, 2));
49
+ return;
50
+ }
51
+ const updatesAvailable = updates.filter((u) => u.hasUpdate);
52
+ console.log("");
53
+ if (updatesAvailable.length === 0) {
54
+ console.log(pc.green("All components are up to date!"));
55
+ console.log("");
56
+ return;
57
+ }
58
+ console.log(pc.bold(`${updatesAvailable.length} update(s) available`));
59
+ console.log("");
60
+ const nameWidth = 20;
61
+ const currentWidth = 15;
62
+ const latestWidth = 15;
63
+ console.log(
64
+ pc.dim(
65
+ `${"Component".padEnd(nameWidth)}${"Current".padEnd(currentWidth)}${"Latest".padEnd(latestWidth)}`
66
+ )
67
+ );
68
+ console.log(pc.dim("\u2500".repeat(nameWidth + currentWidth + latestWidth)));
69
+ for (const update of updates) {
70
+ const name = update.name.padEnd(nameWidth);
71
+ const current = update.currentVersion.padEnd(currentWidth);
72
+ const latest = update.hasUpdate ? pc.green(update.latestVersion.padEnd(latestWidth)) : update.latestVersion.padEnd(latestWidth);
73
+ const indicator = update.hasUpdate ? pc.yellow("\u2191 ") : " ";
74
+ console.log(`${indicator}${name}${current}${latest}`);
75
+ }
76
+ console.log("");
77
+ console.log(pc.dim("To update, run: npx @hypoth-ui/cli add <component> --overwrite"));
78
+ console.log("");
79
+ }
80
+ function compareVersions(a, b) {
81
+ const partsA = a.replace(/^v/, "").split(".").map(Number);
82
+ const partsB = b.replace(/^v/, "").split(".").map(Number);
83
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
84
+ const numA = partsA[i] ?? 0;
85
+ const numB = partsB[i] ?? 0;
86
+ if (numA < numB) return -1;
87
+ if (numA > numB) return 1;
88
+ }
89
+ return 0;
90
+ }
91
+ export {
92
+ diffCommand
93
+ };
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ var VERSION = "0.0.1";
6
+ var program = new Command();
7
+ program.name("hypoth-ui").description("CLI for installing hypoth-ui components").version(VERSION);
8
+ program.command("init").description("Initialize hypoth-ui in your project").option("-s, --style <style>", "Installation style (copy or package)").option("-f, --framework <framework>", "Framework (react, next, wc, vanilla)").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
9
+ const { initCommand } = await import("./init-7AZXYAPJ.js");
10
+ await initCommand(options);
11
+ });
12
+ program.command("add").description("Add components to your project").argument("[components...]", "Component names to add").option("-o, --overwrite", "Overwrite existing components").option("-a, --all", "Add all available components").action(async (components, options) => {
13
+ const { addCommand } = await import("./add-PDBC4JTE.js");
14
+ await addCommand(components, options);
15
+ });
16
+ program.command("list").description("List available components").option("-j, --json", "Output as JSON").action(async (options) => {
17
+ const { listCommand } = await import("./list-X6ZLM2NQ.js");
18
+ await listCommand(options);
19
+ });
20
+ program.command("diff").description("Check for component updates").option("-j, --json", "Output as JSON").action(async (options) => {
21
+ const { diffCommand } = await import("./diff-BQEXG7HU.js");
22
+ await diffCommand(options);
23
+ });
24
+ program.parse();
@@ -0,0 +1,340 @@
1
+ import {
2
+ installPackages
3
+ } from "./chunk-5LTQ2XVL.js";
4
+ import {
5
+ configExists,
6
+ createConfig,
7
+ readConfig,
8
+ writeConfig
9
+ } from "./chunk-YPKFYE45.js";
10
+
11
+ // src/commands/init.ts
12
+ import * as p from "@clack/prompts";
13
+ import pc from "picocolors";
14
+
15
+ // src/utils/detect.ts
16
+ import { existsSync, readFileSync } from "fs";
17
+ import { join } from "path";
18
+ function detectProject(cwd = process.cwd()) {
19
+ const signals = [];
20
+ const packageManager = detectPackageManager(cwd, signals);
21
+ const { typescript, tsconfigPath } = detectTypeScript(cwd, signals);
22
+ const framework = detectFramework(cwd, signals);
23
+ const srcDir = detectSrcDir(cwd, signals);
24
+ const confidence = calculateConfidence(signals);
25
+ return {
26
+ framework,
27
+ packageManager,
28
+ typescript,
29
+ tsconfigPath,
30
+ srcDir,
31
+ confidence,
32
+ signals
33
+ };
34
+ }
35
+ function detectPackageManager(cwd, signals) {
36
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) {
37
+ signals.push("pnpm-lock.yaml found");
38
+ return "pnpm";
39
+ }
40
+ if (existsSync(join(cwd, "yarn.lock"))) {
41
+ signals.push("yarn.lock found");
42
+ return "yarn";
43
+ }
44
+ if (existsSync(join(cwd, "bun.lockb"))) {
45
+ signals.push("bun.lockb found");
46
+ return "bun";
47
+ }
48
+ if (existsSync(join(cwd, "package-lock.json"))) {
49
+ signals.push("package-lock.json found");
50
+ return "npm";
51
+ }
52
+ signals.push("No lock file found, defaulting to npm");
53
+ return "npm";
54
+ }
55
+ function detectTypeScript(cwd, signals) {
56
+ const tsconfigPaths = [
57
+ "tsconfig.json",
58
+ "tsconfig.base.json",
59
+ "jsconfig.json"
60
+ ];
61
+ for (const configPath of tsconfigPaths) {
62
+ const fullPath = join(cwd, configPath);
63
+ if (existsSync(fullPath)) {
64
+ const isTypescript = configPath !== "jsconfig.json";
65
+ signals.push(`${configPath} found (TypeScript: ${isTypescript})`);
66
+ return {
67
+ typescript: isTypescript,
68
+ tsconfigPath: isTypescript ? fullPath : void 0
69
+ };
70
+ }
71
+ }
72
+ signals.push("No tsconfig.json found, assuming JavaScript");
73
+ return { typescript: false };
74
+ }
75
+ function detectFramework(cwd, signals) {
76
+ const packageJsonPath = join(cwd, "package.json");
77
+ if (!existsSync(packageJsonPath)) {
78
+ signals.push("No package.json found");
79
+ return "unknown";
80
+ }
81
+ try {
82
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
83
+ const deps = {
84
+ ...packageJson.dependencies,
85
+ ...packageJson.devDependencies
86
+ };
87
+ if (deps.next) {
88
+ signals.push("next found in dependencies \u2192 Next.js (React)");
89
+ return "next";
90
+ }
91
+ if (deps.react) {
92
+ signals.push("react found in dependencies \u2192 React");
93
+ return "react";
94
+ }
95
+ if (deps.lit) {
96
+ signals.push("lit found in dependencies \u2192 Web Components");
97
+ return "wc";
98
+ }
99
+ if (Object.keys(deps).some((key) => key.startsWith("@lit/"))) {
100
+ signals.push("@lit/* found in dependencies \u2192 Web Components");
101
+ return "wc";
102
+ }
103
+ signals.push("No recognized framework in dependencies \u2192 vanilla");
104
+ return "vanilla";
105
+ } catch {
106
+ signals.push("Failed to parse package.json");
107
+ return "unknown";
108
+ }
109
+ }
110
+ function detectSrcDir(cwd, signals) {
111
+ const srcDirs = ["src", "app", "lib", "source"];
112
+ for (const dir of srcDirs) {
113
+ if (existsSync(join(cwd, dir))) {
114
+ signals.push(`${dir}/ directory found`);
115
+ return dir;
116
+ }
117
+ }
118
+ signals.push("No common source directory found, defaulting to src");
119
+ return "src";
120
+ }
121
+ function calculateConfidence(signals) {
122
+ const positiveSignals = signals.filter(
123
+ (s) => !s.includes("defaulting") && !s.includes("No ")
124
+ ).length;
125
+ if (positiveSignals >= 3) return "high";
126
+ if (positiveSignals >= 2) return "medium";
127
+ return "low";
128
+ }
129
+ function isValidProject(cwd = process.cwd()) {
130
+ return existsSync(join(cwd, "package.json"));
131
+ }
132
+
133
+ // src/commands/init.ts
134
+ var CORE_PACKAGES = ["@hypoth-ui/tokens"];
135
+ async function initCommand(options = {}) {
136
+ p.intro(pc.bgCyan(pc.black(" hypoth-ui init ")));
137
+ const cwd = process.cwd();
138
+ if (!isValidProject(cwd)) {
139
+ p.cancel(pc.red("No package.json found. Please run this command in a valid project directory."));
140
+ process.exit(1);
141
+ }
142
+ if (configExists(cwd)) {
143
+ const shouldReset = await handleExistingConfig(cwd, options);
144
+ if (!shouldReset) {
145
+ p.cancel("Init cancelled.");
146
+ process.exit(0);
147
+ }
148
+ }
149
+ const detection = detectProject(cwd);
150
+ p.log.info(
151
+ `Detected: ${pc.cyan(detection.framework)} framework, ${pc.cyan(detection.packageManager)} package manager, TypeScript: ${pc.cyan(String(detection.typescript))}`
152
+ );
153
+ const config = options.yes ? createConfigFromDefaults(detection) : await promptForConfig(detection, options);
154
+ const spinner2 = p.spinner();
155
+ spinner2.start("Creating ds.config.json");
156
+ writeConfig(config, cwd);
157
+ spinner2.stop("Created ds.config.json");
158
+ spinner2.start(`Installing ${CORE_PACKAGES.join(", ")}...`);
159
+ try {
160
+ await installPackages(CORE_PACKAGES, config.packageManager, { cwd });
161
+ spinner2.stop(`Installed ${CORE_PACKAGES.join(", ")}`);
162
+ } catch (error) {
163
+ spinner2.stop(pc.red(`Failed to install packages: ${error.message}`));
164
+ p.log.warn("You may need to install packages manually:");
165
+ p.log.info(` ${getInstallCommandForDisplay(config.packageManager, CORE_PACKAGES)}`);
166
+ }
167
+ p.outro(pc.green("hypoth-ui initialized successfully!"));
168
+ displayNextSteps(config);
169
+ }
170
+ async function handleExistingConfig(cwd, options) {
171
+ if (options.yes) {
172
+ p.log.warn("Overwriting existing ds.config.json");
173
+ return true;
174
+ }
175
+ const existingConfig = readConfig(cwd);
176
+ const componentsCount = existingConfig.components.length;
177
+ p.log.warn(
178
+ `Found existing ds.config.json with ${componentsCount} installed component(s)`
179
+ );
180
+ const action = await p.select({
181
+ message: "What would you like to do?",
182
+ options: [
183
+ { value: "cancel", label: "Cancel" },
184
+ { value: "reset", label: "Reset configuration (keep installed components)" },
185
+ { value: "overwrite", label: "Overwrite completely (lose component tracking)" }
186
+ ]
187
+ });
188
+ if (p.isCancel(action) || action === "cancel") {
189
+ return false;
190
+ }
191
+ if (action === "reset") {
192
+ p.log.info("Component tracking will be preserved.");
193
+ }
194
+ return true;
195
+ }
196
+ async function promptForConfig(detection, options) {
197
+ const style = options.style ?? await promptForStyle();
198
+ const framework = options.framework ?? await promptForFramework(detection);
199
+ const paths = await promptForPaths(detection, framework);
200
+ return createConfig({
201
+ style,
202
+ framework,
203
+ typescript: detection.typescript,
204
+ packageManager: detection.packageManager,
205
+ paths,
206
+ aliases: {
207
+ components: `@/${paths.components.replace(/^src\//, "")}`,
208
+ lib: `@/${paths.utils.replace(/^src\//, "")}`
209
+ }
210
+ });
211
+ }
212
+ async function promptForStyle() {
213
+ const style = await p.select({
214
+ message: "How would you like to install components?",
215
+ options: [
216
+ {
217
+ value: "package",
218
+ label: "Package mode (recommended)",
219
+ hint: "Install as npm packages - easier updates, tree-shakeable"
220
+ },
221
+ {
222
+ value: "copy",
223
+ label: "Copy mode",
224
+ hint: "Copy source files - full customization, you own the code"
225
+ }
226
+ ]
227
+ });
228
+ if (p.isCancel(style)) {
229
+ p.cancel("Init cancelled.");
230
+ process.exit(0);
231
+ }
232
+ return style;
233
+ }
234
+ async function promptForFramework(detection) {
235
+ if (detection.framework !== "unknown" && detection.confidence === "high") {
236
+ const useDetected = await p.confirm({
237
+ message: `Use detected framework: ${detection.framework}?`,
238
+ initialValue: true
239
+ });
240
+ if (p.isCancel(useDetected)) {
241
+ p.cancel("Init cancelled.");
242
+ process.exit(0);
243
+ }
244
+ if (useDetected) {
245
+ return detection.framework;
246
+ }
247
+ }
248
+ const framework = await p.select({
249
+ message: "Which framework are you using?",
250
+ options: [
251
+ { value: "next", label: "Next.js (React)" },
252
+ { value: "react", label: "React" },
253
+ { value: "wc", label: "Web Components (Lit)" },
254
+ { value: "vanilla", label: "Vanilla JS (Web Components)" }
255
+ ],
256
+ initialValue: detection.framework !== "unknown" ? detection.framework : "react"
257
+ });
258
+ if (p.isCancel(framework)) {
259
+ p.cancel("Init cancelled.");
260
+ process.exit(0);
261
+ }
262
+ return framework;
263
+ }
264
+ async function promptForPaths(detection, framework) {
265
+ const srcDir = detection.srcDir;
266
+ const defaultComponentsPath = framework === "next" ? `${srcDir}/components/ui` : `${srcDir}/components/ui`;
267
+ const defaultUtilsPath = framework === "next" ? `${srcDir}/lib` : `${srcDir}/lib`;
268
+ const componentsPath = await p.text({
269
+ message: "Where should components be placed?",
270
+ initialValue: defaultComponentsPath,
271
+ placeholder: defaultComponentsPath
272
+ });
273
+ if (p.isCancel(componentsPath)) {
274
+ p.cancel("Init cancelled.");
275
+ process.exit(0);
276
+ }
277
+ const utilsPath = await p.text({
278
+ message: "Where should utility files be placed?",
279
+ initialValue: defaultUtilsPath,
280
+ placeholder: defaultUtilsPath
281
+ });
282
+ if (p.isCancel(utilsPath)) {
283
+ p.cancel("Init cancelled.");
284
+ process.exit(0);
285
+ }
286
+ return {
287
+ components: componentsPath,
288
+ utils: utilsPath
289
+ };
290
+ }
291
+ function createConfigFromDefaults(detection) {
292
+ const framework = detection.framework !== "unknown" ? detection.framework : "react";
293
+ const srcDir = detection.srcDir;
294
+ return createConfig({
295
+ style: "package",
296
+ framework,
297
+ typescript: detection.typescript,
298
+ packageManager: detection.packageManager,
299
+ paths: {
300
+ components: `${srcDir}/components/ui`,
301
+ utils: `${srcDir}/lib`
302
+ },
303
+ aliases: {
304
+ components: "@/components/ui",
305
+ lib: "@/lib"
306
+ }
307
+ });
308
+ }
309
+ function displayNextSteps(config) {
310
+ console.log("");
311
+ console.log(pc.bold("Next steps:"));
312
+ console.log("");
313
+ console.log(` ${pc.cyan("1.")} Add a component:`);
314
+ console.log(` ${pc.dim("npx @hypoth-ui/cli add button")}`);
315
+ console.log("");
316
+ console.log(` ${pc.cyan("2.")} List available components:`);
317
+ console.log(` ${pc.dim("npx @hypoth-ui/cli list")}`);
318
+ console.log("");
319
+ if (config.style === "copy") {
320
+ console.log(` ${pc.cyan("3.")} Components will be copied to: ${pc.dim(config.paths.components)}`);
321
+ console.log("");
322
+ }
323
+ }
324
+ function getInstallCommandForDisplay(pm, packages) {
325
+ const pkgList = packages.join(" ");
326
+ switch (pm) {
327
+ case "pnpm":
328
+ return `pnpm add ${pkgList}`;
329
+ case "yarn":
330
+ return `yarn add ${pkgList}`;
331
+ case "bun":
332
+ return `bun add ${pkgList}`;
333
+ case "npm":
334
+ default:
335
+ return `npm install ${pkgList}`;
336
+ }
337
+ }
338
+ export {
339
+ initCommand
340
+ };
@@ -0,0 +1,88 @@
1
+ import {
2
+ getComponentsForFramework,
3
+ loadBundledRegistry
4
+ } from "./chunk-GJ6JOQ3Q.js";
5
+ import {
6
+ configExists,
7
+ readConfig
8
+ } from "./chunk-YPKFYE45.js";
9
+
10
+ // src/commands/list.ts
11
+ import pc from "picocolors";
12
+ async function listCommand(options = {}) {
13
+ const cwd = process.cwd();
14
+ const registry = loadBundledRegistry();
15
+ let config;
16
+ if (configExists(cwd)) {
17
+ try {
18
+ config = readConfig(cwd);
19
+ } catch {
20
+ }
21
+ }
22
+ const components = config ? getComponentsForFramework(registry, config.framework) : registry.components;
23
+ if (options.json) {
24
+ const output = components.map((c) => ({
25
+ name: c.name,
26
+ displayName: c.displayName,
27
+ description: c.description,
28
+ version: c.version,
29
+ status: c.status,
30
+ frameworks: c.frameworks,
31
+ installed: config?.components.some((ic) => ic.name === c.name) ?? false
32
+ }));
33
+ console.log(JSON.stringify(output, null, 2));
34
+ return;
35
+ }
36
+ console.log("");
37
+ console.log(pc.bold("Available Components"));
38
+ console.log("");
39
+ if (config) {
40
+ console.log(pc.dim(`Framework: ${config.framework} | Mode: ${config.style}`));
41
+ console.log("");
42
+ }
43
+ const nameWidth = 20;
44
+ const statusWidth = 10;
45
+ const versionWidth = 10;
46
+ const descWidth = 45;
47
+ console.log(
48
+ pc.dim(
49
+ `${"Name".padEnd(nameWidth)}${"Status".padEnd(statusWidth)}${"Version".padEnd(versionWidth)}Description`
50
+ )
51
+ );
52
+ console.log(pc.dim("\u2500".repeat(nameWidth + statusWidth + versionWidth + descWidth)));
53
+ for (const component of components) {
54
+ const isInstalled = config?.components.some((ic) => ic.name === component.name);
55
+ const installedMark = isInstalled ? pc.green("\u2713 ") : " ";
56
+ const name = (installedMark + component.name).padEnd(nameWidth + 2).slice(0, nameWidth + 2);
57
+ const status = formatStatus(component.status).padEnd(statusWidth);
58
+ const version = component.version.padEnd(versionWidth);
59
+ const desc = component.description.length > descWidth ? component.description.slice(0, descWidth - 3) + "..." : component.description;
60
+ console.log(`${name}${status}${version}${pc.dim(desc)}`);
61
+ }
62
+ console.log("");
63
+ console.log(pc.dim(`Total: ${components.length} components`));
64
+ if (config) {
65
+ const installedCount = config.components.length;
66
+ if (installedCount > 0) {
67
+ console.log(pc.dim(`Installed: ${installedCount} (${pc.green("\u2713")} marked)`));
68
+ }
69
+ } else {
70
+ console.log(pc.dim("Run 'npx @hypoth-ui/cli init' to initialize your project"));
71
+ }
72
+ console.log("");
73
+ }
74
+ function formatStatus(status) {
75
+ switch (status) {
76
+ case "stable":
77
+ return pc.green(status);
78
+ case "beta":
79
+ return pc.yellow(status);
80
+ case "alpha":
81
+ return pc.red(status);
82
+ default:
83
+ return status;
84
+ }
85
+ }
86
+ export {
87
+ listCommand
88
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@hypoth-ui/cli",
3
+ "version": "0.0.1",
4
+ "description": "CLI tool for adding hypoth-ui components to your project",
5
+ "type": "module",
6
+ "bin": {
7
+ "hypoth-ui": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "registry"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "test:unit": "vitest run tests/unit",
24
+ "test:watch": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "commander": "^12.1.0",
28
+ "@clack/prompts": "^0.7.0",
29
+ "picocolors": "^1.1.1",
30
+ "execa": "^9.5.2",
31
+ "gray-matter": "^4.0.3"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.10.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.3.0",
37
+ "vitest": "^1.0.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "keywords": [
43
+ "cli",
44
+ "design-system",
45
+ "components",
46
+ "hypoth-ui"
47
+ ],
48
+ "license": "MIT",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/hypoth-ui/hypoth-ui.git",
52
+ "directory": "packages/cli"
53
+ }
54
+ }