bearnie 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.
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # Bearnie CLI
2
+
3
+ A command-line interface for adding Bearnie UI components to your Astro project.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # 1. Navigate to your Astro project
9
+ cd my-astro-project
10
+
11
+ # 2. Initialize Bearnie
12
+ npx bearnie init
13
+
14
+ # 3. Add components
15
+ npx bearnie add button card input
16
+
17
+ # 4. Use in your Astro pages
18
+ ```
19
+
20
+ ```astro
21
+ ---
22
+ import Button from "@/components/ui/button/Button.astro";
23
+ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
24
+ ---
25
+
26
+ <Card>
27
+ <CardHeader>
28
+ <CardTitle>Welcome</CardTitle>
29
+ </CardHeader>
30
+ <CardContent>
31
+ <Button>Click me</Button>
32
+ </CardContent>
33
+ </Card>
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ # Install globally
40
+ npm install -g bearnie
41
+
42
+ # Or use npx (recommended)
43
+ npx bearnie add button
44
+ ```
45
+
46
+ ## Commands
47
+
48
+ ### `init`
49
+
50
+ Initialize Bearnie in your project. This sets up the necessary configuration and utilities.
51
+
52
+ ```bash
53
+ npx bearnie init
54
+ ```
55
+
56
+ This will:
57
+
58
+ - Create a `bearnie.json` configuration file
59
+ - Set up the `src/components/bearnie` directory
60
+ - Create the `cn()` utility function
61
+ - Install `clsx`, `tailwind-merge`, and `tailwindcss` dependencies
62
+
63
+ **Options:**
64
+
65
+ - `-y, --yes` - Skip confirmation prompts and use defaults
66
+ - `-c, --cwd <path>` - Set the working directory (defaults to current directory)
67
+
68
+ ### `add`
69
+
70
+ Add components to your project.
71
+
72
+ ```bash
73
+ # Add a single component
74
+ npx bearnie add button
75
+
76
+ # Add multiple components
77
+ npx bearnie add button card input
78
+
79
+ # Add all available components
80
+ npx bearnie add --all
81
+
82
+ # Interactive component selection
83
+ npx bearnie add
84
+ ```
85
+
86
+ **Options:**
87
+
88
+ - `-y, --yes` - Skip confirmation prompts
89
+ - `-a, --all` - Add all available components
90
+ - `-c, --cwd <path>` - Set the working directory
91
+
92
+ ### `list`
93
+
94
+ List all available components.
95
+
96
+ ```bash
97
+ # Display formatted list
98
+ npx bearnie list
99
+
100
+ # Output as JSON
101
+ npx bearnie list --json
102
+ ```
103
+
104
+ **Options:**
105
+
106
+ - `--json` - Output as JSON
107
+
108
+ ## Configuration
109
+
110
+ After running `init`, a `bearnie.json` file is created in your project root:
111
+
112
+ ```json
113
+ {
114
+ "componentsDir": "src/components/bearnie",
115
+ "utilsDir": "src/utils",
116
+ "typescript": true
117
+ }
118
+ ```
119
+
120
+ ### Configuration Options
121
+
122
+ | Option | Type | Default | Description |
123
+ | --------------- | --------- | --------------------- | -------------------------------------------- |
124
+ | `componentsDir` | `string` | `"src/components/bearnie"` | Directory where components will be installed |
125
+ | `utilsDir` | `string` | `"src/utils"` | Directory for utility functions |
126
+ | `typescript` | `boolean` | `true` | Whether to use TypeScript |
127
+
128
+ ## Environment Variables
129
+
130
+ | Variable | Description |
131
+ | ---------------------- | ------------------------------------------------ |
132
+ | `BEARNIE_REGISTRY_URL` | Custom registry URL (for self-hosted registries) |
133
+ | `BEARNIE_REGISTRY_PATH` | Local file path to registry (for development) |
134
+
135
+ ## Local Development
136
+
137
+ For local development and testing:
138
+
139
+ ```bash
140
+ # Clone the repository
141
+ git clone https://github.com/michael-andreuzza/bearnie.git
142
+ cd bearnie
143
+
144
+ # Install CLI dependencies
145
+ cd packages/cli
146
+ npm install
147
+
148
+ # Build the CLI
149
+ npm run build
150
+
151
+ # Link for local testing
152
+ npm link
153
+
154
+ # Now you can use it
155
+ bearnie add button
156
+ ```
157
+
158
+ ### Testing with Local Registry
159
+
160
+ 1. Generate the registry files:
161
+
162
+ ```bash
163
+ npm run generate-registry
164
+ ```
165
+
166
+ 2. Use the local registry path:
167
+ ```bash
168
+ BEARNIE_REGISTRY_PATH=/path/to/bearnie/public/registry bearnie add button
169
+ ```
170
+
171
+ Or start the dev server and use the URL:
172
+
173
+ ```bash
174
+ npm run dev
175
+ BEARNIE_REGISTRY_URL=http://localhost:4321/registry bearnie add button
176
+ ```
177
+
178
+ ## Available Components
179
+
180
+ **Form:** button, input, textarea, label, checkbox, radio, select, switch
181
+
182
+ **Layout:** card, separator, scroll-area, aspect-ratio
183
+
184
+ **Navigation:** breadcrumb, tabs, dropdown-menu
185
+
186
+ **Feedback:** alert, badge, progress, skeleton, spinner, toast, tooltip
187
+
188
+ **Disclosure:** accordion, collapsible, dialog, popover
189
+
190
+ **Display:** avatar, table
191
+
192
+ ## Usage Examples
193
+
194
+ ### Basic Button
195
+
196
+ ```astro
197
+ ---
198
+ import Button from "@/components/ui/button/Button.astro";
199
+ ---
200
+
201
+ <Button>Default</Button>
202
+ <Button variant="secondary">Secondary</Button>
203
+ <Button variant="outline">Outline</Button>
204
+ <Button variant="destructive">Delete</Button>
205
+ <Button size="sm">Small</Button>
206
+ <Button size="lg">Large</Button>
207
+ ```
208
+
209
+ ### Form with Input and Label
210
+
211
+ ```astro
212
+ ---
213
+ import Input from "@/components/ui/input/Input.astro";
214
+ import Label from "@/components/ui/label/Label.astro";
215
+ import Button from "@/components/ui/button/Button.astro";
216
+ ---
217
+
218
+ <form class="space-y-4">
219
+ <div>
220
+ <Label for="email">Email</Label>
221
+ <Input type="email" id="email" placeholder="you@example.com" />
222
+ </div>
223
+ <div>
224
+ <Label for="password">Password</Label>
225
+ <Input type="password" id="password" />
226
+ </div>
227
+ <Button type="submit">Sign In</Button>
228
+ </form>
229
+ ```
230
+
231
+ ### Card with Content
232
+
233
+ ```astro
234
+ ---
235
+ import Card from "@/components/ui/card/Card.astro";
236
+ import CardHeader from "@/components/ui/card/CardHeader.astro";
237
+ import CardTitle from "@/components/ui/card/CardTitle.astro";
238
+ import CardDescription from "@/components/ui/card/CardDescription.astro";
239
+ import CardContent from "@/components/ui/card/CardContent.astro";
240
+ import CardFooter from "@/components/ui/card/CardFooter.astro";
241
+ import Button from "@/components/ui/button/Button.astro";
242
+ ---
243
+
244
+ <Card class="w-96">
245
+ <CardHeader>
246
+ <CardTitle>Create Account</CardTitle>
247
+ <CardDescription>Enter your details below</CardDescription>
248
+ </CardHeader>
249
+ <CardContent>
250
+ <!-- Form fields here -->
251
+ </CardContent>
252
+ <CardFooter>
253
+ <Button class="w-full">Submit</Button>
254
+ </CardFooter>
255
+ </Card>
256
+ ```
257
+
258
+ ### Alert Messages
259
+
260
+ ```astro
261
+ ---
262
+ import Alert from "@/components/ui/alert/Alert.astro";
263
+ import AlertTitle from "@/components/ui/alert/AlertTitle.astro";
264
+ import AlertDescription from "@/components/ui/alert/AlertDescription.astro";
265
+ ---
266
+
267
+ <Alert>
268
+ <AlertTitle>Heads up!</AlertTitle>
269
+ <AlertDescription>This is an informational message.</AlertDescription>
270
+ </Alert>
271
+
272
+ <Alert variant="destructive">
273
+ <AlertTitle>Error</AlertTitle>
274
+ <AlertDescription>Something went wrong.</AlertDescription>
275
+ </Alert>
276
+ ```
277
+
278
+ ## Path Aliases
279
+
280
+ Bearnie components use the `@/` path alias. Make sure your `tsconfig.json` has:
281
+
282
+ ```json
283
+ {
284
+ "compilerOptions": {
285
+ "baseUrl": ".",
286
+ "paths": {
287
+ "@/*": ["src/*"]
288
+ }
289
+ }
290
+ }
291
+ ```
292
+
293
+ ## License
294
+
295
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,663 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk5 from "chalk";
6
+
7
+ // src/commands/init.ts
8
+ import chalk2 from "chalk";
9
+ import ora from "ora";
10
+ import prompts from "prompts";
11
+ import path2 from "path";
12
+ import fs2 from "fs-extra";
13
+ import { execa } from "execa";
14
+
15
+ // src/utils/config.ts
16
+ import path from "path";
17
+ import fs from "fs-extra";
18
+ var DEFAULT_CONFIG = {
19
+ componentsDir: "src/components/bearnie",
20
+ utilsDir: "src/utils",
21
+ tailwindConfig: "tailwind.config.mjs",
22
+ typescript: true
23
+ };
24
+ var CONFIG_FILE = "bearnie.json";
25
+ async function getProjectConfig(cwd) {
26
+ const configPath = path.join(cwd, CONFIG_FILE);
27
+ if (await fs.pathExists(configPath)) {
28
+ const content = await fs.readFile(configPath, "utf-8");
29
+ return JSON.parse(content);
30
+ }
31
+ return null;
32
+ }
33
+ async function saveProjectConfig(cwd, config) {
34
+ const configPath = path.join(cwd, CONFIG_FILE);
35
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
36
+ }
37
+ async function isAstroProject(cwd) {
38
+ const packageJsonPath = path.join(cwd, "package.json");
39
+ if (!await fs.pathExists(packageJsonPath)) {
40
+ return false;
41
+ }
42
+ const packageJson = await fs.readJson(packageJsonPath);
43
+ const deps = {
44
+ ...packageJson.dependencies,
45
+ ...packageJson.devDependencies
46
+ };
47
+ return "astro" in deps;
48
+ }
49
+ async function hasTailwindInstalled(cwd) {
50
+ const packageJsonPath = path.join(cwd, "package.json");
51
+ if (!await fs.pathExists(packageJsonPath)) {
52
+ return false;
53
+ }
54
+ const packageJson = await fs.readJson(packageJsonPath);
55
+ const deps = {
56
+ ...packageJson.dependencies,
57
+ ...packageJson.devDependencies
58
+ };
59
+ return "tailwindcss" in deps;
60
+ }
61
+
62
+ // src/utils/ui.ts
63
+ import chalk from "chalk";
64
+ var brand = {
65
+ primary: chalk.hex("#F59E0B"),
66
+ // Amber (bear/honey color)
67
+ secondary: chalk.hex("#FCD34D"),
68
+ // Light amber
69
+ accent: chalk.hex("#FBBF24"),
70
+ // Mid amber
71
+ muted: chalk.dim,
72
+ success: chalk.green,
73
+ warning: chalk.yellow,
74
+ error: chalk.red,
75
+ info: chalk.cyan
76
+ };
77
+ var banner = `
78
+ ${brand.primary(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}
79
+ ${brand.primary(" \u2502")} ${brand.secondary("\u{1F43B}")} ${chalk.bold.white("bearnie")} ${brand.primary("\u2502")}
80
+ ${brand.primary(" \u2502")} ${brand.muted("UI components for Astro")} ${brand.primary("\u2502")}
81
+ ${brand.primary(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}
82
+ `;
83
+ var logo = `${brand.secondary("\u{1F43B}")} ${chalk.bold.white("bearnie")}`;
84
+ var messages = {
85
+ // Greetings
86
+ greeting: () => `${brand.primary("Hey!")} Let's build something beautiful.`,
87
+ // Init messages
88
+ initStart: () => `${brand.primary("Hey!")} Let's set up your project.`,
89
+ initSuccess: () => `${brand.success("\u2713")} ${chalk.bold("You're all set!")} Time to add some components.`,
90
+ alreadyInit: () => `Looks like Bearnie is already set up here.`,
91
+ // Add messages
92
+ addStart: () => `${brand.primary("Hey!")} Let's add some components.`,
93
+ fetching: () => `Fetching the good stuff...`,
94
+ foundComponents: (count) => `Found ${chalk.bold(count)} components ready to use`,
95
+ resolving: () => `Gathering what you need...`,
96
+ installing: (name) => `Adding ${chalk.cyan(name)}...`,
97
+ installed: (name) => `${brand.success("\u2713")} ${name} is ready`,
98
+ installingDeps: () => `Installing dependencies...`,
99
+ // List messages
100
+ listHeader: (version) => `${logo} ${brand.muted(`v${version}`)}`,
101
+ listFooter: (count) => `${brand.accent("\u{1F43B}")} ${count} components available`,
102
+ // Success messages (randomized for fun)
103
+ success: () => {
104
+ const messages2 = [
105
+ `${brand.success("\u{1F389}")} ${chalk.bold("All done!")} Happy building!`,
106
+ `${brand.success("\u2728")} ${chalk.bold("Looking good!")} Your components are ready.`,
107
+ `${brand.success("\u{1F43B}")} ${chalk.bold("Done!")} Time to make something awesome.`,
108
+ `${brand.success("\u{1F680}")} ${chalk.bold("Ready to go!")} Have fun building!`
109
+ ];
110
+ return messages2[Math.floor(Math.random() * messages2.length)];
111
+ },
112
+ // Error messages (friendly, not scary)
113
+ notAstro: () => `Hmm, this doesn't look like an Astro project.`,
114
+ notAstroHelp: () => `Make sure you're in the root of an Astro project with a package.json.`,
115
+ networkError: () => `Couldn't reach the component registry.`,
116
+ networkErrorHelp: () => `Check your internet connection and try again.`,
117
+ unknownComponent: (names) => `Couldn't find: ${names.map((n) => chalk.yellow(n)).join(", ")}`,
118
+ // Hints
119
+ hint: (text) => brand.muted(` \u{1F4A1} ${text}`),
120
+ nextStep: (step) => ` ${brand.muted("\u2192")} ${step}`
121
+ };
122
+ var print = {
123
+ banner: () => console.log(banner),
124
+ logo: () => console.log(`
125
+ ${logo}
126
+ `),
127
+ greeting: () => console.log(`
128
+ ${messages.greeting()}
129
+ `),
130
+ success: () => console.log(`
131
+ ${messages.success()}
132
+ `),
133
+ newline: () => console.log(),
134
+ // Formatted output
135
+ step: (text) => console.log(` ${text}`),
136
+ hint: (text) => console.log(messages.hint(text)),
137
+ error: (text) => console.log(` ${brand.error("\u2717")} ${text}`),
138
+ warning: (text) => console.log(` ${brand.warning("\u26A0")} ${text}`),
139
+ info: (text) => console.log(` ${brand.info("\u2139")} ${text}`),
140
+ // Next steps box
141
+ nextSteps: (steps) => {
142
+ console.log();
143
+ console.log(` ${chalk.bold("Next steps:")}`);
144
+ steps.forEach((step, i) => {
145
+ console.log(` ${brand.muted(`${i + 1}.`)} ${step}`);
146
+ });
147
+ console.log();
148
+ },
149
+ // Footer
150
+ footer: () => {
151
+ const link2 = (text, url) => `\x1B]8;;${url}\x07${chalk.cyan(text)}\x1B]8;;\x07`;
152
+ console.log(` ${brand.muted("Built with")} ${brand.secondary("\u{1F43B}")} ${brand.muted("by")} ${link2("Michael Andreuzza", "https://michaelandreuzza.com")}`);
153
+ console.log();
154
+ }
155
+ };
156
+ var promptsTheme = {
157
+ prefix: brand.secondary("?"),
158
+ highlight: brand.accent,
159
+ submit: brand.success("\u2713")
160
+ };
161
+
162
+ // src/commands/init.ts
163
+ async function init(options) {
164
+ const cwd = path2.resolve(options.cwd);
165
+ print.logo();
166
+ console.log(` ${messages.initStart()}`);
167
+ print.newline();
168
+ const isAstro = await isAstroProject(cwd);
169
+ if (!isAstro) {
170
+ print.error(messages.notAstro());
171
+ print.hint(messages.notAstroHelp());
172
+ print.newline();
173
+ process.exit(1);
174
+ }
175
+ const configPath = path2.join(cwd, CONFIG_FILE);
176
+ if (await fs2.pathExists(configPath)) {
177
+ print.warning(messages.alreadyInit());
178
+ print.newline();
179
+ const { overwrite } = options.yes ? { overwrite: true } : await prompts({
180
+ type: "confirm",
181
+ name: "overwrite",
182
+ message: "Want to start fresh?",
183
+ initial: false
184
+ });
185
+ if (!overwrite) {
186
+ print.hint("No changes made. Your config is safe!");
187
+ print.newline();
188
+ process.exit(0);
189
+ }
190
+ }
191
+ const hasTailwind = await hasTailwindInstalled(cwd);
192
+ if (!hasTailwind) {
193
+ print.warning("Tailwind CSS isn't installed yet.");
194
+ const { installTailwind } = options.yes ? { installTailwind: true } : await prompts({
195
+ type: "confirm",
196
+ name: "installTailwind",
197
+ message: "Want me to install it for you?",
198
+ initial: true
199
+ });
200
+ if (installTailwind) {
201
+ const spinner2 = ora({
202
+ text: "Installing Tailwind CSS...",
203
+ color: "green"
204
+ }).start();
205
+ try {
206
+ await execa(
207
+ "npm",
208
+ ["install", "-D", "tailwindcss", "@tailwindcss/vite"],
209
+ { cwd }
210
+ );
211
+ spinner2.succeed(brand.success("Tailwind CSS is ready"));
212
+ } catch (error) {
213
+ spinner2.fail("Couldn't install Tailwind CSS");
214
+ print.hint(
215
+ "Try manually: npm install -D tailwindcss @tailwindcss/vite"
216
+ );
217
+ }
218
+ }
219
+ }
220
+ let config = { ...DEFAULT_CONFIG };
221
+ if (!options.yes) {
222
+ print.newline();
223
+ console.log(` ${chalk2.bold("Where should things go?")}`);
224
+ print.newline();
225
+ const responses = await prompts([
226
+ {
227
+ type: "text",
228
+ name: "componentsDir",
229
+ message: "Components directory",
230
+ initial: DEFAULT_CONFIG.componentsDir
231
+ },
232
+ {
233
+ type: "text",
234
+ name: "utilsDir",
235
+ message: "Utilities directory",
236
+ initial: DEFAULT_CONFIG.utilsDir
237
+ }
238
+ ]);
239
+ config = {
240
+ ...config,
241
+ ...responses
242
+ };
243
+ }
244
+ const spinner = ora({
245
+ text: "Setting things up...",
246
+ color: "green"
247
+ }).start();
248
+ try {
249
+ await fs2.ensureDir(path2.join(cwd, config.componentsDir));
250
+ await fs2.ensureDir(path2.join(cwd, config.utilsDir));
251
+ spinner.text = "Creating directories...";
252
+ } catch (error) {
253
+ spinner.fail("Couldn't create directories");
254
+ process.exit(1);
255
+ }
256
+ const cnUtilContent = `import { type ClassValue, clsx } from "clsx";
257
+ import { twMerge } from "tailwind-merge";
258
+
259
+ export function cn(...inputs: ClassValue[]) {
260
+ return twMerge(clsx(inputs));
261
+ }
262
+ `;
263
+ try {
264
+ const utilPath = path2.join(cwd, config.utilsDir, "cn.ts");
265
+ if (!await fs2.pathExists(utilPath)) {
266
+ await fs2.writeFile(utilPath, cnUtilContent);
267
+ const packageJson = await fs2.readJson(path2.join(cwd, "package.json"));
268
+ const deps = {
269
+ ...packageJson.dependencies,
270
+ ...packageJson.devDependencies
271
+ };
272
+ const toInstall = [];
273
+ if (!("clsx" in deps)) toInstall.push("clsx");
274
+ if (!("tailwind-merge" in deps)) toInstall.push("tailwind-merge");
275
+ if (toInstall.length > 0) {
276
+ spinner.text = "Installing utilities...";
277
+ await execa("npm", ["install", ...toInstall], { cwd });
278
+ }
279
+ }
280
+ } catch (error) {
281
+ spinner.fail("Couldn't create utility files");
282
+ }
283
+ try {
284
+ await saveProjectConfig(cwd, config);
285
+ spinner.succeed(brand.success("Everything is set up"));
286
+ } catch (error) {
287
+ spinner.fail("Couldn't save configuration");
288
+ process.exit(1);
289
+ }
290
+ print.newline();
291
+ console.log(` ${messages.initSuccess()}`);
292
+ print.nextSteps([
293
+ `Add your first component: ${chalk2.cyan("npx bearnie add button")}`,
294
+ `Browse all components: ${chalk2.cyan("npx bearnie list")}`,
295
+ `Add CSS variables: ${chalk2.cyan("npx bearnie add styles")}`
296
+ ]);
297
+ print.footer();
298
+ }
299
+
300
+ // src/commands/add.ts
301
+ import chalk3 from "chalk";
302
+ import ora2 from "ora";
303
+ import prompts2 from "prompts";
304
+ import path4 from "path";
305
+ import fs4 from "fs-extra";
306
+ import { execa as execa2 } from "execa";
307
+
308
+ // src/utils/registry.ts
309
+ import fs3 from "fs-extra";
310
+ import path3 from "path";
311
+ var REGISTRY_URL = process.env.BEARNIE_REGISTRY_URL || "https://bearnie.dev/registry";
312
+ var REGISTRY_PATH = process.env.BEARNIE_REGISTRY_PATH;
313
+ var REGISTRY_INDEX_URL = `${REGISTRY_URL}/index.json`;
314
+ async function getRegistryIndex() {
315
+ if (REGISTRY_PATH) {
316
+ const indexPath = path3.join(REGISTRY_PATH, "index.json");
317
+ if (await fs3.pathExists(indexPath)) {
318
+ return fs3.readJson(indexPath);
319
+ }
320
+ throw new Error(`Registry index not found at: ${indexPath}`);
321
+ }
322
+ const response = await fetch(REGISTRY_INDEX_URL);
323
+ if (!response.ok) {
324
+ throw new Error(`Failed to fetch registry index: ${response.statusText}`);
325
+ }
326
+ return response.json();
327
+ }
328
+ async function getComponent(name) {
329
+ if (REGISTRY_PATH) {
330
+ const componentPath = path3.join(REGISTRY_PATH, `${name}.json`);
331
+ if (await fs3.pathExists(componentPath)) {
332
+ return fs3.readJson(componentPath);
333
+ }
334
+ throw new Error(`Component "${name}" not found at: ${componentPath}`);
335
+ }
336
+ const url = `${REGISTRY_URL}/${name}.json`;
337
+ const response = await fetch(url);
338
+ if (!response.ok) {
339
+ throw new Error(`Component "${name}" not found in registry`);
340
+ }
341
+ return response.json();
342
+ }
343
+ async function resolveComponentDependencies(names, resolved = /* @__PURE__ */ new Set()) {
344
+ const result = [];
345
+ for (const name of names) {
346
+ if (resolved.has(name)) continue;
347
+ resolved.add(name);
348
+ const component = await getComponent(name);
349
+ if (component.registryDependencies?.length) {
350
+ const deps = await resolveComponentDependencies(
351
+ component.registryDependencies,
352
+ resolved
353
+ );
354
+ result.push(...deps);
355
+ }
356
+ result.push(name);
357
+ }
358
+ return result;
359
+ }
360
+
361
+ // src/commands/add.ts
362
+ async function add(components, options) {
363
+ const cwd = path4.resolve(options.cwd);
364
+ print.logo();
365
+ console.log(` ${messages.addStart()}`);
366
+ print.newline();
367
+ let config = await getProjectConfig(cwd);
368
+ if (!config) {
369
+ print.warning(
370
+ `Project not initialized. Run ${chalk3.cyan("npx bearnie init")} first.`
371
+ );
372
+ print.newline();
373
+ const { proceed } = options.yes ? { proceed: true } : await prompts2({
374
+ type: "confirm",
375
+ name: "proceed",
376
+ message: "Want to use default settings for now?",
377
+ initial: true
378
+ });
379
+ if (!proceed) {
380
+ process.exit(0);
381
+ }
382
+ config = DEFAULT_CONFIG;
383
+ }
384
+ const indexSpinner = ora2({
385
+ text: messages.fetching(),
386
+ color: "green"
387
+ }).start();
388
+ let registryIndex;
389
+ try {
390
+ registryIndex = await getRegistryIndex();
391
+ indexSpinner.succeed(messages.foundComponents(registryIndex.components.length));
392
+ } catch (error) {
393
+ indexSpinner.fail(messages.networkError());
394
+ print.hint(messages.networkErrorHelp());
395
+ process.exit(1);
396
+ }
397
+ let selectedComponents = [];
398
+ if (options.all) {
399
+ selectedComponents = registryIndex.components.map((c) => c.name);
400
+ } else if (components.length === 0) {
401
+ print.newline();
402
+ const { selected } = await prompts2({
403
+ type: "multiselect",
404
+ name: "selected",
405
+ message: "What would you like to add?",
406
+ choices: registryIndex.components.map((c) => ({
407
+ title: c.name,
408
+ description: c.description,
409
+ value: c.name
410
+ })),
411
+ hint: "Space to select, Enter to confirm",
412
+ instructions: false
413
+ });
414
+ if (!selected || selected.length === 0) {
415
+ print.hint("No components selected.");
416
+ print.newline();
417
+ process.exit(0);
418
+ }
419
+ selectedComponents = selected;
420
+ } else {
421
+ const availableNames = registryIndex.components.map((c) => c.name);
422
+ const invalid = components.filter((c) => !availableNames.includes(c));
423
+ if (invalid.length > 0) {
424
+ print.error(messages.unknownComponent(invalid));
425
+ print.hint(`Run ${chalk3.cyan("npx bearnie list")} to see what's available.`);
426
+ print.newline();
427
+ process.exit(1);
428
+ }
429
+ selectedComponents = components;
430
+ }
431
+ const resolveSpinner = ora2({
432
+ text: messages.resolving(),
433
+ color: "green"
434
+ }).start();
435
+ let allComponents;
436
+ try {
437
+ allComponents = await resolveComponentDependencies(selectedComponents);
438
+ const extraDeps = allComponents.length - selectedComponents.length;
439
+ if (extraDeps > 0) {
440
+ resolveSpinner.succeed(
441
+ `Adding ${chalk3.bold(selectedComponents.length)} component${selectedComponents.length > 1 ? "s" : ""} ${brand.muted(`(+${extraDeps} dependencies)`)}`
442
+ );
443
+ } else {
444
+ resolveSpinner.succeed(
445
+ `Adding ${chalk3.bold(allComponents.length)} component${allComponents.length > 1 ? "s" : ""}`
446
+ );
447
+ }
448
+ } catch (error) {
449
+ resolveSpinner.fail("Couldn't resolve dependencies");
450
+ process.exit(1);
451
+ }
452
+ print.newline();
453
+ const npmDeps = /* @__PURE__ */ new Set();
454
+ const npmDevDeps = /* @__PURE__ */ new Set();
455
+ const writtenFiles = [];
456
+ const skippedFiles = [];
457
+ for (const componentName of allComponents) {
458
+ const spinner = ora2({
459
+ text: messages.installing(componentName),
460
+ color: "green"
461
+ }).start();
462
+ try {
463
+ const component = await getComponent(componentName);
464
+ component.dependencies?.forEach((d) => npmDeps.add(d));
465
+ component.devDependencies?.forEach((d) => npmDevDeps.add(d));
466
+ for (const file of component.files) {
467
+ const filePath = path4.join(cwd, config.componentsDir, file.path);
468
+ const exists = await fs4.pathExists(filePath);
469
+ if (exists && !options.yes) {
470
+ skippedFiles.push(file.path);
471
+ continue;
472
+ }
473
+ await fs4.ensureDir(path4.dirname(filePath));
474
+ await fs4.writeFile(filePath, file.content);
475
+ writtenFiles.push(file.path);
476
+ }
477
+ spinner.succeed(messages.installed(componentName));
478
+ } catch (error) {
479
+ spinner.fail(`Couldn't add ${componentName}`);
480
+ print.hint(`${error}`);
481
+ }
482
+ }
483
+ const allDeps = [...npmDeps];
484
+ const allDevDeps = [...npmDevDeps];
485
+ if (allDeps.length > 0 || allDevDeps.length > 0) {
486
+ const depsSpinner = ora2({
487
+ text: messages.installingDeps(),
488
+ color: "green"
489
+ }).start();
490
+ try {
491
+ const packageJson = await fs4.readJson(path4.join(cwd, "package.json"));
492
+ const existingDeps = {
493
+ ...packageJson.dependencies,
494
+ ...packageJson.devDependencies
495
+ };
496
+ const newDeps = allDeps.filter((d) => !(d in existingDeps));
497
+ const newDevDeps = allDevDeps.filter((d) => !(d in existingDeps));
498
+ if (newDeps.length > 0) {
499
+ await execa2("npm", ["install", ...newDeps], { cwd });
500
+ }
501
+ if (newDevDeps.length > 0) {
502
+ await execa2("npm", ["install", "-D", ...newDevDeps], { cwd });
503
+ }
504
+ depsSpinner.succeed("Dependencies installed");
505
+ } catch (error) {
506
+ depsSpinner.fail("Some dependencies couldn't be installed");
507
+ print.hint("You might need to install them manually.");
508
+ }
509
+ }
510
+ print.newline();
511
+ if (writtenFiles.length > 0) {
512
+ console.log(
513
+ ` ${brand.success("\u2713")} Created ${chalk3.bold(writtenFiles.length)} file${writtenFiles.length > 1 ? "s" : ""}:`
514
+ );
515
+ writtenFiles.slice(0, 5).forEach((f) => {
516
+ console.log(brand.muted(` ${f}`));
517
+ });
518
+ if (writtenFiles.length > 5) {
519
+ console.log(brand.muted(` ...and ${writtenFiles.length - 5} more`));
520
+ }
521
+ }
522
+ if (skippedFiles.length > 0) {
523
+ print.newline();
524
+ console.log(
525
+ ` ${brand.warning("\u26A0")} Skipped ${skippedFiles.length} existing file${skippedFiles.length > 1 ? "s" : ""}`
526
+ );
527
+ print.hint(`Use ${chalk3.cyan("--yes")} to overwrite.`);
528
+ }
529
+ print.newline();
530
+ print.success();
531
+ }
532
+
533
+ // src/commands/list.ts
534
+ import chalk4 from "chalk";
535
+ import ora3 from "ora";
536
+ async function list(options) {
537
+ const spinner = ora3({
538
+ text: messages.fetching(),
539
+ color: "green"
540
+ }).start();
541
+ try {
542
+ const registry = await getRegistryIndex();
543
+ spinner.stop();
544
+ if (options.json) {
545
+ console.log(JSON.stringify(registry.components, null, 2));
546
+ return;
547
+ }
548
+ print.newline();
549
+ console.log(` ${messages.listHeader(registry.version)}`);
550
+ print.newline();
551
+ const categories = /* @__PURE__ */ new Map();
552
+ for (const component of registry.components) {
553
+ const cat = component.category || "other";
554
+ if (!categories.has(cat)) {
555
+ categories.set(cat, []);
556
+ }
557
+ categories.get(cat).push(component);
558
+ }
559
+ const categoryConfig = {
560
+ foundation: { emoji: "\u{1F3A8}", label: "Foundation" },
561
+ form: { emoji: "\u{1F4DD}", label: "Form" },
562
+ layout: { emoji: "\u{1F4D0}", label: "Layout" },
563
+ navigation: { emoji: "\u{1F9ED}", label: "Navigation" },
564
+ feedback: { emoji: "\u{1F4AC}", label: "Feedback" },
565
+ disclosure: { emoji: "\u{1F4C2}", label: "Disclosure" },
566
+ display: { emoji: "\u2728", label: "Display" },
567
+ other: { emoji: "\u{1F4E6}", label: "Other" }
568
+ };
569
+ const categoryOrder = [
570
+ "foundation",
571
+ "form",
572
+ "layout",
573
+ "navigation",
574
+ "feedback",
575
+ "disclosure",
576
+ "display",
577
+ "other"
578
+ ];
579
+ for (const category of categoryOrder) {
580
+ const components = categories.get(category);
581
+ if (!components || components.length === 0) continue;
582
+ const config = categoryConfig[category] || { emoji: "\u{1F4E6}", label: category };
583
+ console.log(` ${config.emoji} ${chalk4.bold(config.label)}`);
584
+ print.newline();
585
+ for (const component of components) {
586
+ const name = brand.accent(component.name.padEnd(18));
587
+ const desc = brand.muted(component.description);
588
+ console.log(` ${name} ${desc}`);
589
+ }
590
+ print.newline();
591
+ }
592
+ console.log(` ${messages.listFooter(registry.components.length)}`);
593
+ print.newline();
594
+ console.log(` ${chalk4.bold("Quick commands:")}`);
595
+ console.log(` ${brand.muted("\u2192")} Add a component: ${chalk4.cyan("npx bearnie add button")}`);
596
+ console.log(` ${brand.muted("\u2192")} Add everything: ${chalk4.cyan("npx bearnie add --all")}`);
597
+ console.log(` ${brand.muted("\u2192")} Add CSS variables: ${chalk4.cyan("npx bearnie add styles")}`);
598
+ print.newline();
599
+ } catch (error) {
600
+ spinner.fail(messages.networkError());
601
+ print.hint(messages.networkErrorHelp());
602
+ process.exit(1);
603
+ }
604
+ }
605
+
606
+ // src/index.ts
607
+ var amber = chalk5.hex("#F59E0B");
608
+ var logo2 = `${amber("\u{1F43B}")} ${chalk5.bold("bearnie")}`;
609
+ var link = (text, url) => `\x1B]8;;${url}\x07${chalk5.cyan(text)}\x1B]8;;\x07`;
610
+ var program = new Command();
611
+ program.name("bearnie").description("UI components for Astro").version("0.1.0").configureOutput({
612
+ writeOut: (str) => process.stdout.write(str),
613
+ writeErr: (str) => process.stdout.write(str),
614
+ outputError: (str, write) => {
615
+ write(`
616
+ ${logo2}
617
+
618
+ `);
619
+ write(` ${chalk5.red("Oops!")} ${str.replace("error: ", "")}
620
+ `);
621
+ }
622
+ }).addHelpText("beforeAll", `
623
+ ${logo2}
624
+ `).addHelpText(
625
+ "afterAll",
626
+ `
627
+ ${chalk5.dim("Built with")} ${amber("\u{1F43B}")} ${chalk5.dim("by")} ${link("Michael Andreuzza", "https://michaelandreuzza.com")}
628
+ `
629
+ );
630
+ program.command("init").description("Set up Bearnie in your project").option("-y, --yes", "Skip prompts and use defaults").option("--cwd <path>", "Working directory", process.cwd()).action(init);
631
+ program.command("add").description("Add components to your project").argument("[components...]", "Components to add").option("-y, --yes", "Skip prompts and overwrite files").option("-a, --all", "Add all components").option("--cwd <path>", "Working directory", process.cwd()).action(add);
632
+ program.command("list").description("Browse available components").option("--json", "Output as JSON").action(list);
633
+ program.action(() => {
634
+ console.log(`
635
+ ${logo2}
636
+
637
+ ${amber("Hey!")} UI components for Astro.
638
+ Built with Tailwind CSS, no frameworks required.
639
+
640
+ ${chalk5.bold("Commands:")}
641
+ ${chalk5.cyan("init")} Set up Bearnie in your project
642
+ ${chalk5.cyan("add")} ${chalk5.dim("<name>")} Add a component
643
+ ${chalk5.cyan("list")} Browse all components
644
+
645
+ ${chalk5.bold("Examples:")}
646
+ ${chalk5.dim("$")} npx bearnie init
647
+ ${chalk5.dim("$")} npx bearnie add button card
648
+ ${chalk5.dim("$")} npx bearnie add --all
649
+
650
+ ${chalk5.dim("Run")} ${chalk5.cyan("bearnie <command> --help")} ${chalk5.dim("for more info")}
651
+ `);
652
+ });
653
+ program.parse();
654
+ program.on("command:*", () => {
655
+ console.log(`
656
+ ${logo2}
657
+
658
+ ${chalk5.yellow("Hmm,")} I don't know that command: ${chalk5.red(program.args.join(" "))}
659
+
660
+ Run ${chalk5.cyan("npx bearnie --help")} to see what I can do.
661
+ `);
662
+ process.exit(1);
663
+ });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "bearnie",
3
+ "version": "0.1.0",
4
+ "description": "CLI for installing Bearnie UI components",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": "dist/index.js",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format esm --dts --clean",
13
+ "dev": "tsup src/index.ts --format esm --watch",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "dependencies": {
17
+ "chalk": "^5.3.0",
18
+ "commander": "^12.1.0",
19
+ "execa": "^9.3.1",
20
+ "fs-extra": "^11.2.0",
21
+ "node-fetch": "^3.3.2",
22
+ "ora": "^8.0.1",
23
+ "prompts": "^2.4.2"
24
+ },
25
+ "devDependencies": {
26
+ "@types/fs-extra": "^11.0.4",
27
+ "@types/node": "^20.14.9",
28
+ "@types/prompts": "^2.4.9",
29
+ "tsup": "^8.1.0",
30
+ "typescript": "^5.5.3"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "author": "Michael Andreuzza",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/michael-andreuzza/bearnie.git",
39
+ "directory": "packages/cli"
40
+ },
41
+ "homepage": "https://bearnie.dev",
42
+ "bugs": {
43
+ "url": "https://github.com/michael-andreuzza/bearnie/issues"
44
+ },
45
+ "keywords": [
46
+ "astro",
47
+ "ui",
48
+ "components",
49
+ "cli",
50
+ "tailwindcss",
51
+ "bearnie"
52
+ ]
53
+ }