@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.
- package/README.md +154 -0
- package/dist/add-PDBC4JTE.js +238 -0
- package/dist/chunk-5LTQ2XVL.js +52 -0
- package/dist/chunk-GJ6JOQ3Q.js +101 -0
- package/dist/chunk-YPKFYE45.js +123 -0
- package/dist/diff-BQEXG7HU.js +93 -0
- package/dist/index.js +24 -0
- package/dist/init-7AZXYAPJ.js +340 -0
- package/dist/list-X6ZLM2NQ.js +88 -0
- package/package.json +54 -0
- package/registry/components.json +563 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @hypoth-ui/cli
|
|
2
|
+
|
|
3
|
+
CLI tool for adding hypoth-ui components to your project.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @hypoth-ui/cli init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install globally:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @hypoth-ui/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
1. Initialize hypoth-ui in your project:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @hypoth-ui/cli init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Add components:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @hypoth-ui/cli add button
|
|
29
|
+
npx @hypoth-ui/cli add dialog menu
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. List available components:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx @hypoth-ui/cli list
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
### `init`
|
|
41
|
+
|
|
42
|
+
Initialize hypoth-ui in your project. Creates `ds.config.json` and installs core dependencies.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx @hypoth-ui/cli init [options]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
- `-s, --style <style>` - Installation style: `copy` (source files) or `package` (npm packages)
|
|
50
|
+
- `-f, --framework <framework>` - Framework: `react`, `next`, `wc`, or `vanilla`
|
|
51
|
+
- `-y, --yes` - Skip prompts and use defaults
|
|
52
|
+
|
|
53
|
+
### `add`
|
|
54
|
+
|
|
55
|
+
Add components to your project.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx @hypoth-ui/cli add <components...> [options]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
- `-o, --overwrite` - Overwrite existing components
|
|
63
|
+
- `-a, --all` - Add all available components
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
```bash
|
|
67
|
+
npx @hypoth-ui/cli add button # Add single component
|
|
68
|
+
npx @hypoth-ui/cli add button dialog # Add multiple components
|
|
69
|
+
npx @hypoth-ui/cli add --all # Add all components
|
|
70
|
+
npx @hypoth-ui/cli add button -o # Overwrite existing
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `list`
|
|
74
|
+
|
|
75
|
+
List all available components.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx @hypoth-ui/cli list [options]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
- `-j, --json` - Output as JSON
|
|
83
|
+
|
|
84
|
+
### `diff`
|
|
85
|
+
|
|
86
|
+
Check for component updates.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx @hypoth-ui/cli diff [options]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
- `-j, --json` - Output as JSON
|
|
94
|
+
|
|
95
|
+
## Installation Modes
|
|
96
|
+
|
|
97
|
+
### Package Mode (Recommended)
|
|
98
|
+
|
|
99
|
+
Components are installed as npm packages. Easier to update, tree-shakeable.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx @hypoth-ui/cli init --style package
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Copy Mode
|
|
106
|
+
|
|
107
|
+
Component source files are copied to your project. Full customization, you own the code.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx @hypoth-ui/cli init --style copy
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
Configuration is stored in `ds.config.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"$schema": "https://hypoth-ui.dev/schema/ds.config.json",
|
|
120
|
+
"style": "package",
|
|
121
|
+
"framework": "next",
|
|
122
|
+
"typescript": true,
|
|
123
|
+
"packageManager": "pnpm",
|
|
124
|
+
"paths": {
|
|
125
|
+
"components": "src/components/ui",
|
|
126
|
+
"utils": "src/lib"
|
|
127
|
+
},
|
|
128
|
+
"aliases": {
|
|
129
|
+
"components": "@/components/ui",
|
|
130
|
+
"lib": "@/lib"
|
|
131
|
+
},
|
|
132
|
+
"components": []
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Supported Frameworks
|
|
137
|
+
|
|
138
|
+
- **React** - Standard React applications
|
|
139
|
+
- **Next.js** - Next.js App Router projects
|
|
140
|
+
- **Web Components** - Lit-based Web Components
|
|
141
|
+
- **Vanilla** - Vanilla JS using Web Components
|
|
142
|
+
|
|
143
|
+
## Supported Package Managers
|
|
144
|
+
|
|
145
|
+
- npm
|
|
146
|
+
- pnpm
|
|
147
|
+
- yarn
|
|
148
|
+
- bun
|
|
149
|
+
|
|
150
|
+
The CLI automatically detects your package manager from lock files.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installPackages
|
|
3
|
+
} from "./chunk-5LTQ2XVL.js";
|
|
4
|
+
import {
|
|
5
|
+
getComponent,
|
|
6
|
+
getComponentNames,
|
|
7
|
+
getNpmDependencies,
|
|
8
|
+
isComponentCompatible,
|
|
9
|
+
loadBundledRegistry,
|
|
10
|
+
resolveDependencies
|
|
11
|
+
} from "./chunk-GJ6JOQ3Q.js";
|
|
12
|
+
import {
|
|
13
|
+
addInstalledComponent,
|
|
14
|
+
isComponentInstalled,
|
|
15
|
+
readConfig,
|
|
16
|
+
writeConfig
|
|
17
|
+
} from "./chunk-YPKFYE45.js";
|
|
18
|
+
|
|
19
|
+
// src/commands/add.ts
|
|
20
|
+
import * as p from "@clack/prompts";
|
|
21
|
+
import pc from "picocolors";
|
|
22
|
+
|
|
23
|
+
// src/utils/copy.ts
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
25
|
+
import { dirname, join } from "path";
|
|
26
|
+
async function copyComponentFiles(component, config, options = {}) {
|
|
27
|
+
const { cwd = process.cwd(), overwrite = false, sourceDir } = options;
|
|
28
|
+
const targetDir = join(cwd, config.paths.components);
|
|
29
|
+
const files = filterFilesForFramework(component.files, config.framework);
|
|
30
|
+
const result = {
|
|
31
|
+
copied: [],
|
|
32
|
+
skipped: [],
|
|
33
|
+
errors: []
|
|
34
|
+
};
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const targetPath = join(targetDir, component.name, file.target);
|
|
37
|
+
if (existsSync(targetPath) && !overwrite) {
|
|
38
|
+
result.skipped.push({
|
|
39
|
+
file: file.target,
|
|
40
|
+
reason: "exists"
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const content = await getSourceContent(file, component, sourceDir);
|
|
46
|
+
const transformedContent = transformImports(content, config, file.type);
|
|
47
|
+
const dir = dirname(targetPath);
|
|
48
|
+
if (!existsSync(dir)) {
|
|
49
|
+
mkdirSync(dir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
writeFileSync(targetPath, transformedContent, "utf-8");
|
|
52
|
+
result.copied.push(targetPath);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
result.errors.push({
|
|
55
|
+
file: file.target,
|
|
56
|
+
error: error.message
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function filterFilesForFramework(files, framework) {
|
|
63
|
+
const frameworkMap = {
|
|
64
|
+
react: "react",
|
|
65
|
+
next: "react",
|
|
66
|
+
wc: "wc",
|
|
67
|
+
vanilla: "wc"
|
|
68
|
+
};
|
|
69
|
+
const targetFramework = frameworkMap[framework];
|
|
70
|
+
return files.filter((file) => {
|
|
71
|
+
if (!file.framework) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return file.framework === targetFramework;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function getSourceContent(file, component, sourceDir) {
|
|
78
|
+
if (sourceDir) {
|
|
79
|
+
const sourcePath = join(sourceDir, component.name, file.path);
|
|
80
|
+
if (existsSync(sourcePath)) {
|
|
81
|
+
return readFileSync(sourcePath, "utf-8");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const bundledPath = getBundledTemplatePath(component.name, file.path);
|
|
85
|
+
if (existsSync(bundledPath)) {
|
|
86
|
+
return readFileSync(bundledPath, "utf-8");
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Source file not found: ${file.path}`);
|
|
89
|
+
}
|
|
90
|
+
function getBundledTemplatePath(componentName, filePath) {
|
|
91
|
+
const __dirname2 = new URL(".", import.meta.url).pathname;
|
|
92
|
+
return join(__dirname2, "../../templates", componentName, filePath);
|
|
93
|
+
}
|
|
94
|
+
function transformImports(content, config, fileType) {
|
|
95
|
+
if (fileType !== "ts" && fileType !== "tsx") {
|
|
96
|
+
return content;
|
|
97
|
+
}
|
|
98
|
+
let result = content;
|
|
99
|
+
result = result.replace(
|
|
100
|
+
/@\/components\//g,
|
|
101
|
+
`${config.aliases.components}/`
|
|
102
|
+
);
|
|
103
|
+
result = result.replace(
|
|
104
|
+
/@\/lib\//g,
|
|
105
|
+
`${config.aliases.lib}/`
|
|
106
|
+
);
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
function ensureComponentsDir(config, cwd = process.cwd()) {
|
|
110
|
+
const targetDir = join(cwd, config.paths.components);
|
|
111
|
+
if (!existsSync(targetDir)) {
|
|
112
|
+
mkdirSync(targetDir, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/commands/add.ts
|
|
117
|
+
async function addCommand(components, options = {}) {
|
|
118
|
+
p.intro(pc.bgCyan(pc.black(" hypoth-ui add ")));
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
let config;
|
|
121
|
+
try {
|
|
122
|
+
config = readConfig(cwd);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
p.cancel(pc.red(error.message));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const registry = loadBundledRegistry();
|
|
128
|
+
if (options.all) {
|
|
129
|
+
components = getComponentNames(registry);
|
|
130
|
+
p.log.info(`Installing all ${components.length} components`);
|
|
131
|
+
}
|
|
132
|
+
if (components.length === 0) {
|
|
133
|
+
p.cancel(pc.red("No components specified. Usage: npx @hypoth-ui/cli add <component>"));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const invalidComponents = [];
|
|
137
|
+
const validComponents = [];
|
|
138
|
+
for (const name of components) {
|
|
139
|
+
const component = getComponent(registry, name);
|
|
140
|
+
if (!component) {
|
|
141
|
+
invalidComponents.push(name);
|
|
142
|
+
} else if (!isComponentCompatible(component, config.framework)) {
|
|
143
|
+
p.log.warn(`${pc.yellow(name)} is not compatible with ${config.framework}`);
|
|
144
|
+
} else {
|
|
145
|
+
validComponents.push(component);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (invalidComponents.length > 0) {
|
|
149
|
+
p.log.error(`Unknown components: ${invalidComponents.join(", ")}`);
|
|
150
|
+
p.log.info(`Run ${pc.cyan("npx @hypoth-ui/cli list")} to see available components`);
|
|
151
|
+
if (validComponents.length === 0) {
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const toInstall = [];
|
|
156
|
+
const skipped = [];
|
|
157
|
+
for (const component of validComponents) {
|
|
158
|
+
if (isComponentInstalled(config, component.name) && !options.overwrite) {
|
|
159
|
+
skipped.push(component.name);
|
|
160
|
+
} else {
|
|
161
|
+
toInstall.push(component);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (skipped.length > 0) {
|
|
165
|
+
p.log.info(`Skipping already installed: ${skipped.join(", ")} (use --overwrite to replace)`);
|
|
166
|
+
}
|
|
167
|
+
if (toInstall.length === 0) {
|
|
168
|
+
p.outro(pc.yellow("No new components to install"));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const componentsToInstall = resolveDependencies(
|
|
172
|
+
registry,
|
|
173
|
+
toInstall.map((c) => c.name)
|
|
174
|
+
);
|
|
175
|
+
const finalComponents = componentsToInstall.filter(
|
|
176
|
+
(c) => !isComponentInstalled(config, c.name) || options.overwrite
|
|
177
|
+
);
|
|
178
|
+
if (finalComponents.length === 0) {
|
|
179
|
+
p.outro(pc.yellow("All components already installed"));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
p.log.info(`Installing: ${finalComponents.map((c) => pc.cyan(c.name)).join(", ")}`);
|
|
183
|
+
const npmDeps = getNpmDependencies(finalComponents);
|
|
184
|
+
if (npmDeps.length > 0) {
|
|
185
|
+
const spinner3 = p.spinner();
|
|
186
|
+
spinner3.start(`Installing npm dependencies: ${npmDeps.join(", ")}`);
|
|
187
|
+
try {
|
|
188
|
+
await installPackages(npmDeps, config.packageManager, { cwd });
|
|
189
|
+
spinner3.stop(`Installed npm dependencies`);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
spinner3.stop(pc.red(`Failed to install npm dependencies: ${error.message}`));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const spinner2 = p.spinner();
|
|
196
|
+
if (config.style === "copy") {
|
|
197
|
+
ensureComponentsDir(config, cwd);
|
|
198
|
+
for (const component of finalComponents) {
|
|
199
|
+
spinner2.start(`Copying ${component.name}...`);
|
|
200
|
+
try {
|
|
201
|
+
const result = await copyComponentFiles(component, config, {
|
|
202
|
+
cwd,
|
|
203
|
+
overwrite: options.overwrite
|
|
204
|
+
});
|
|
205
|
+
if (result.errors.length > 0) {
|
|
206
|
+
spinner2.stop(pc.red(`Failed to copy ${component.name}: ${result.errors[0]?.error ?? "Unknown error"}`));
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
config = addInstalledComponent(config, {
|
|
210
|
+
name: component.name,
|
|
211
|
+
version: component.version,
|
|
212
|
+
mode: "copy"
|
|
213
|
+
});
|
|
214
|
+
spinner2.stop(`Copied ${component.name}`);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
spinner2.stop(pc.red(`Failed to copy ${component.name}: ${error.message}`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
for (const component of finalComponents) {
|
|
221
|
+
config = addInstalledComponent(config, {
|
|
222
|
+
name: component.name,
|
|
223
|
+
version: component.version,
|
|
224
|
+
mode: "package"
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
writeConfig(config, cwd);
|
|
229
|
+
const installed = finalComponents.map((c) => c.name);
|
|
230
|
+
p.outro(pc.green(`Added ${installed.length} component(s): ${installed.join(", ")}`));
|
|
231
|
+
if (config.style === "copy") {
|
|
232
|
+
console.log("");
|
|
233
|
+
console.log(`Components copied to: ${pc.dim(config.paths.components)}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export {
|
|
237
|
+
addCommand
|
|
238
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/utils/install.ts
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
async function installPackages(packages, packageManager, options = {}) {
|
|
4
|
+
const { cwd = process.cwd(), dev = false, silent = false } = options;
|
|
5
|
+
if (packages.length === 0) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const command = getCommand(packageManager);
|
|
9
|
+
const args = getArgs(packageManager, packages, dev);
|
|
10
|
+
await execa(command, args, {
|
|
11
|
+
cwd,
|
|
12
|
+
stdio: silent ? "ignore" : "inherit"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function getCommand(pm) {
|
|
16
|
+
return pm;
|
|
17
|
+
}
|
|
18
|
+
function getArgs(pm, packages, dev) {
|
|
19
|
+
const devFlag = dev ? getDevFlag(pm) : null;
|
|
20
|
+
const installCommand = getInstallSubcommand(pm);
|
|
21
|
+
const args = [installCommand, ...packages];
|
|
22
|
+
if (devFlag) {
|
|
23
|
+
args.push(devFlag);
|
|
24
|
+
}
|
|
25
|
+
return args;
|
|
26
|
+
}
|
|
27
|
+
function getInstallSubcommand(pm) {
|
|
28
|
+
switch (pm) {
|
|
29
|
+
case "yarn":
|
|
30
|
+
case "pnpm":
|
|
31
|
+
case "bun":
|
|
32
|
+
return "add";
|
|
33
|
+
case "npm":
|
|
34
|
+
default:
|
|
35
|
+
return "install";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getDevFlag(pm) {
|
|
39
|
+
switch (pm) {
|
|
40
|
+
case "yarn":
|
|
41
|
+
case "pnpm":
|
|
42
|
+
case "bun":
|
|
43
|
+
return "-D";
|
|
44
|
+
case "npm":
|
|
45
|
+
default:
|
|
46
|
+
return "--save-dev";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
installPackages
|
|
52
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// src/utils/registry.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
var BUNDLED_REGISTRY_PATH = join(__dirname, "../registry/components.json");
|
|
7
|
+
var REMOTE_REGISTRY_URL = "https://hypoth-ui.dev/registry/components.json";
|
|
8
|
+
function loadBundledRegistry() {
|
|
9
|
+
try {
|
|
10
|
+
const content = readFileSync(BUNDLED_REGISTRY_PATH, "utf-8");
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`Failed to load bundled registry: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function fetchRegistry() {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(REMOTE_REGISTRY_URL);
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`HTTP ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
return await response.json();
|
|
23
|
+
} catch {
|
|
24
|
+
return loadBundledRegistry();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getComponent(registry, name) {
|
|
28
|
+
const normalizedName = name.toLowerCase();
|
|
29
|
+
return registry.components.find((c) => c.name.toLowerCase() === normalizedName);
|
|
30
|
+
}
|
|
31
|
+
function getComponentNames(registry) {
|
|
32
|
+
return registry.components.map((c) => c.name);
|
|
33
|
+
}
|
|
34
|
+
function getComponentsForFramework(registry, framework) {
|
|
35
|
+
const frameworkMap = {
|
|
36
|
+
react: "react",
|
|
37
|
+
next: "react",
|
|
38
|
+
// Next.js uses React components
|
|
39
|
+
wc: "wc",
|
|
40
|
+
vanilla: "wc"
|
|
41
|
+
// Vanilla uses Web Components
|
|
42
|
+
};
|
|
43
|
+
const targetFramework = frameworkMap[framework];
|
|
44
|
+
return registry.components.filter((c) => c.frameworks.includes(targetFramework));
|
|
45
|
+
}
|
|
46
|
+
function resolveDependencies(registry, componentNames) {
|
|
47
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
48
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
49
|
+
function visit(name) {
|
|
50
|
+
const normalizedName = name.toLowerCase();
|
|
51
|
+
if (resolved.has(normalizedName)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (visiting.has(normalizedName)) {
|
|
55
|
+
throw new Error(`Circular dependency detected: ${name}`);
|
|
56
|
+
}
|
|
57
|
+
const component = getComponent(registry, name);
|
|
58
|
+
if (!component) {
|
|
59
|
+
throw new Error(`Component not found in registry: ${name}`);
|
|
60
|
+
}
|
|
61
|
+
visiting.add(normalizedName);
|
|
62
|
+
for (const dep of component.registryDependencies) {
|
|
63
|
+
visit(dep);
|
|
64
|
+
}
|
|
65
|
+
visiting.delete(normalizedName);
|
|
66
|
+
resolved.set(normalizedName, component);
|
|
67
|
+
}
|
|
68
|
+
for (const name of componentNames) {
|
|
69
|
+
visit(name);
|
|
70
|
+
}
|
|
71
|
+
return Array.from(resolved.values());
|
|
72
|
+
}
|
|
73
|
+
function getNpmDependencies(components) {
|
|
74
|
+
const deps = /* @__PURE__ */ new Set();
|
|
75
|
+
for (const component of components) {
|
|
76
|
+
for (const dep of component.dependencies) {
|
|
77
|
+
deps.add(dep);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return Array.from(deps);
|
|
81
|
+
}
|
|
82
|
+
function isComponentCompatible(component, framework) {
|
|
83
|
+
const frameworkMap = {
|
|
84
|
+
react: "react",
|
|
85
|
+
next: "react",
|
|
86
|
+
wc: "wc",
|
|
87
|
+
vanilla: "wc"
|
|
88
|
+
};
|
|
89
|
+
return component.frameworks.includes(frameworkMap[framework]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export {
|
|
93
|
+
loadBundledRegistry,
|
|
94
|
+
fetchRegistry,
|
|
95
|
+
getComponent,
|
|
96
|
+
getComponentNames,
|
|
97
|
+
getComponentsForFramework,
|
|
98
|
+
resolveDependencies,
|
|
99
|
+
getNpmDependencies,
|
|
100
|
+
isComponentCompatible
|
|
101
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/utils/config.ts
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
var CONFIG_FILE_NAME = "ds.config.json";
|
|
5
|
+
var CONFIG_SCHEMA_URL = "https://hypoth-ui.dev/schema/ds.config.json";
|
|
6
|
+
function configExists(cwd = process.cwd()) {
|
|
7
|
+
return existsSync(join(cwd, CONFIG_FILE_NAME));
|
|
8
|
+
}
|
|
9
|
+
function getConfigPath(cwd = process.cwd()) {
|
|
10
|
+
return join(cwd, CONFIG_FILE_NAME);
|
|
11
|
+
}
|
|
12
|
+
function readConfig(cwd = process.cwd()) {
|
|
13
|
+
const configPath = getConfigPath(cwd);
|
|
14
|
+
if (!existsSync(configPath)) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Configuration file not found. Run 'hypoth-ui init' first.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const content = readFileSync(configPath, "utf-8");
|
|
21
|
+
const config = JSON.parse(content);
|
|
22
|
+
return validateConfig(config);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error instanceof SyntaxError) {
|
|
25
|
+
throw new Error(`Invalid JSON in ${CONFIG_FILE_NAME}: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function writeConfig(config, cwd = process.cwd()) {
|
|
31
|
+
const configPath = getConfigPath(cwd);
|
|
32
|
+
const content = JSON.stringify(config, null, 2);
|
|
33
|
+
writeFileSync(configPath, content + "\n", "utf-8");
|
|
34
|
+
}
|
|
35
|
+
function createConfig(options) {
|
|
36
|
+
const config = {
|
|
37
|
+
$schema: CONFIG_SCHEMA_URL,
|
|
38
|
+
style: options.style ?? "package",
|
|
39
|
+
framework: options.framework ?? "react",
|
|
40
|
+
typescript: options.typescript ?? true,
|
|
41
|
+
packageManager: options.packageManager ?? "npm",
|
|
42
|
+
paths: options.paths ?? {
|
|
43
|
+
components: "src/components/ui",
|
|
44
|
+
utils: "src/lib"
|
|
45
|
+
},
|
|
46
|
+
aliases: options.aliases ?? {
|
|
47
|
+
components: "@/components/ui",
|
|
48
|
+
lib: "@/lib"
|
|
49
|
+
},
|
|
50
|
+
components: options.components ?? []
|
|
51
|
+
};
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
54
|
+
function validateConfig(config) {
|
|
55
|
+
if (!config || typeof config !== "object") {
|
|
56
|
+
throw new Error("Invalid configuration: expected object");
|
|
57
|
+
}
|
|
58
|
+
const cfg = config;
|
|
59
|
+
if (!["copy", "package"].includes(cfg.style)) {
|
|
60
|
+
throw new Error('Invalid configuration: style must be "copy" or "package"');
|
|
61
|
+
}
|
|
62
|
+
if (!["react", "next", "wc", "vanilla"].includes(cfg.framework)) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
'Invalid configuration: framework must be "react", "next", "wc", or "vanilla"'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (typeof cfg.typescript !== "boolean") {
|
|
68
|
+
throw new Error("Invalid configuration: typescript must be a boolean");
|
|
69
|
+
}
|
|
70
|
+
if (!["npm", "pnpm", "yarn", "bun"].includes(cfg.packageManager)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'Invalid configuration: packageManager must be "npm", "pnpm", "yarn", or "bun"'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!cfg.paths || typeof cfg.paths !== "object") {
|
|
76
|
+
throw new Error("Invalid configuration: paths must be an object");
|
|
77
|
+
}
|
|
78
|
+
const paths = cfg.paths;
|
|
79
|
+
if (typeof paths.components !== "string" || typeof paths.utils !== "string") {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"Invalid configuration: paths.components and paths.utils must be strings"
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (!cfg.aliases || typeof cfg.aliases !== "object") {
|
|
85
|
+
throw new Error("Invalid configuration: aliases must be an object");
|
|
86
|
+
}
|
|
87
|
+
const aliases = cfg.aliases;
|
|
88
|
+
if (typeof aliases.components !== "string" || typeof aliases.lib !== "string") {
|
|
89
|
+
throw new Error(
|
|
90
|
+
"Invalid configuration: aliases.components and aliases.lib must be strings"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(cfg.components)) {
|
|
94
|
+
throw new Error("Invalid configuration: components must be an array");
|
|
95
|
+
}
|
|
96
|
+
return config;
|
|
97
|
+
}
|
|
98
|
+
function addInstalledComponent(config, component) {
|
|
99
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
100
|
+
const filtered = config.components.filter((c) => c.name !== component.name);
|
|
101
|
+
return {
|
|
102
|
+
...config,
|
|
103
|
+
components: [
|
|
104
|
+
...filtered,
|
|
105
|
+
{
|
|
106
|
+
...component,
|
|
107
|
+
installedAt: now
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function isComponentInstalled(config, name) {
|
|
113
|
+
return config.components.some((c) => c.name === name);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
configExists,
|
|
118
|
+
readConfig,
|
|
119
|
+
writeConfig,
|
|
120
|
+
createConfig,
|
|
121
|
+
addInstalledComponent,
|
|
122
|
+
isComponentInstalled
|
|
123
|
+
};
|