bearnie 0.1.6 → 0.2.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 CHANGED
@@ -61,11 +61,16 @@ This will:
61
61
  - Set up the `src/components/bearnie` directory
62
62
  - Create the `cn()` utility function
63
63
  - Install `clsx`, `tailwind-merge`, and `tailwindcss` dependencies
64
+ - Add the `@/*` path alias to `tsconfig.json`
65
+ - Wire `@tailwindcss/vite` into your Astro config (simple configs only — you get a hint otherwise)
66
+ - Install the theme variables to `src/styles/bearnie.css`
67
+
68
+ Projects created with `create-bearnie` already include `bearnie.json` and can skip init.
64
69
 
65
70
  **Options:**
66
71
 
67
72
  - `-y, --yes` - Skip confirmation prompts and use defaults
68
- - `-c, --cwd <path>` - Set the working directory (defaults to current directory)
73
+ - `--cwd <path>` - Set the working directory (defaults to current directory)
69
74
 
70
75
  ### `add`
71
76
 
@@ -88,11 +93,14 @@ npx bearnie add barrel
88
93
  npx bearnie add
89
94
  ```
90
95
 
96
+ If a file already exists, `add` asks before overwriting it (existing files are kept if you decline).
97
+
91
98
  **Options:**
92
99
 
93
- - `-y, --yes` - Skip confirmation prompts
100
+ - `-y, --yes` - Skip confirmation prompts and overwrite existing files
94
101
  - `-a, --all` - Add all available components
95
- - `-c, --cwd <path>` - Set the working directory
102
+ - `-o, --overwrite` - Overwrite existing files without asking
103
+ - `--cwd <path>` - Set the working directory
96
104
 
97
105
  ### `list`
98
106
 
@@ -106,10 +114,58 @@ npx bearnie list
106
114
  npx bearnie list --json
107
115
  ```
108
116
 
117
+ Components are grouped by category, including **Theme** (`styles`) and **Meta** (`barrel`).
118
+
109
119
  **Options:**
110
120
 
111
121
  - `--json` - Output as JSON
112
122
 
123
+ ### `diff`
124
+
125
+ See how your installed components differ from the current registry — useful after Bearnie ships fixes.
126
+
127
+ ```bash
128
+ # Check all installed components
129
+ npx bearnie diff
130
+
131
+ # Check specific components
132
+ npx bearnie diff button dialog
133
+
134
+ # Just list changed files without the full diff
135
+ npx bearnie diff --name-only
136
+ ```
137
+
138
+ **Options:**
139
+
140
+ - `--name-only` - Only show which files changed, not the diff
141
+ - `--cwd <path>` - Set the working directory
142
+
143
+ ### `update`
144
+
145
+ Pull the latest registry version of your installed components. Shows what will change and asks for confirmation first — updating overwrites local edits to those files, so run `diff` first if you've customized components.
146
+
147
+ ```bash
148
+ # Update everything that drifted
149
+ npx bearnie update
150
+
151
+ # Update specific components
152
+ npx bearnie update button dialog
153
+
154
+ # Skip the confirmation prompt
155
+ npx bearnie update --yes
156
+ ```
157
+
158
+ Any new npm dependencies the updated components need are installed automatically.
159
+
160
+ **Options:**
161
+
162
+ - `-y, --yes` - Skip confirmation prompt
163
+ - `--cwd <path>` - Set the working directory
164
+
165
+ ## Package Managers
166
+
167
+ The CLI detects your package manager from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`) and uses it for all dependency installs. No configuration needed.
168
+
113
169
  ## Configuration
114
170
 
115
171
  After running `init`, a `bearnie.json` file is created in your project root:
@@ -183,17 +239,25 @@ BEARNIE_REGISTRY_URL=http://localhost:4321/registry bearnie add button
183
239
 
184
240
  ## Available Components
185
241
 
186
- **Form:** button, input, textarea, label, checkbox, radio, select, switch
242
+ **Form:** button, button-group, checkbox, combobox, file-upload, input, input-group, input-otp, label, radio, select, slider, switch, textarea, toggle, toggle-group
243
+
244
+ **Layout:** aspect-ratio, card, scroll-area, separator
245
+
246
+ **Navigation:** breadcrumb, command, context-menu, dropdown-menu, menubar, pagination, sidebar, stepper, tabs, tree
247
+
248
+ **Feedback:** alert, empty, progress, skeleton, spinner, toast
249
+
250
+ **Disclosure:** accordion, alert-dialog, collapsible, dialog, popover, sheet
187
251
 
188
- **Layout:** card, separator, scroll-area, aspect-ratio
252
+ **Display:** avatar, badge, carousel, hover-card, icon, kbd, table, tooltip
189
253
 
190
- **Navigation:** breadcrumb, tabs, dropdown-menu
254
+ **Theme:** styles, theme-toggle
191
255
 
192
- **Feedback:** alert, badge, progress, skeleton, spinner, toast, tooltip
256
+ **Meta:** barrel
193
257
 
194
- **Disclosure:** accordion, collapsible, dialog, popover
258
+ Shared utilities (`cn`, `focus-trap`, and the `ui-runtime-*` modules) are installed automatically as dependencies of the components that need them.
195
259
 
196
- **Display:** avatar, table
260
+ Run `npx bearnie list` for the always-current list.
197
261
 
198
262
  ## Usage Examples
199
263
 
@@ -0,0 +1,8 @@
1
+ interface AddOptions {
2
+ yes?: boolean;
3
+ all?: boolean;
4
+ overwrite?: boolean;
5
+ cwd: string;
6
+ }
7
+ export declare function add(components: string[], options: AddOptions): Promise<void>;
8
+ export {};
@@ -0,0 +1,6 @@
1
+ interface DiffOptions {
2
+ cwd: string;
3
+ nameOnly?: boolean;
4
+ }
5
+ export declare function diff(components: string[], options: DiffOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,6 @@
1
+ interface InitOptions {
2
+ yes?: boolean;
3
+ cwd: string;
4
+ }
5
+ export declare function init(options: InitOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,5 @@
1
+ interface ListOptions {
2
+ json?: boolean;
3
+ }
4
+ export declare function list(options: ListOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,6 @@
1
+ interface UpdateOptions {
2
+ cwd: string;
3
+ yes?: boolean;
4
+ }
5
+ export declare function update(components: string[], options: UpdateOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js CHANGED
@@ -5,14 +5,14 @@ import { readFileSync } from "fs";
5
5
  import { dirname, join } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { Command } from "commander";
8
- import chalk5 from "chalk";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/commands/init.ts
11
11
  import chalk2 from "chalk";
12
12
  import ora from "ora";
13
13
  import prompts from "prompts";
14
- import path2 from "path";
15
- import fs2 from "fs-extra";
14
+ import path4 from "path";
15
+ import fs4 from "fs-extra";
16
16
  import { execa } from "execa";
17
17
 
18
18
  // src/utils/config.ts
@@ -74,6 +74,83 @@ function resolveInstallPath(cwd, config, filePath, componentType) {
74
74
  return path.join(cwd, config.componentsDir, filePath);
75
75
  }
76
76
 
77
+ // src/utils/registry.ts
78
+ import fs2 from "fs-extra";
79
+ import path2 from "path";
80
+ var REGISTRY_URL = process.env.BEARNIE_REGISTRY_URL || "https://bearnie.dev/registry";
81
+ var REGISTRY_PATH = process.env.BEARNIE_REGISTRY_PATH;
82
+ var REGISTRY_INDEX_URL = `${REGISTRY_URL}/index.json`;
83
+ async function getRegistryIndex() {
84
+ if (REGISTRY_PATH) {
85
+ const indexPath = path2.join(REGISTRY_PATH, "index.json");
86
+ if (await fs2.pathExists(indexPath)) {
87
+ return fs2.readJson(indexPath);
88
+ }
89
+ throw new Error(`Registry index not found at: ${indexPath}`);
90
+ }
91
+ const response = await fetch(REGISTRY_INDEX_URL);
92
+ if (!response.ok) {
93
+ throw new Error(`Failed to fetch registry index: ${response.statusText}`);
94
+ }
95
+ return response.json();
96
+ }
97
+ async function getComponent(name) {
98
+ if (REGISTRY_PATH) {
99
+ const componentPath = path2.join(REGISTRY_PATH, `${name}.json`);
100
+ if (await fs2.pathExists(componentPath)) {
101
+ return fs2.readJson(componentPath);
102
+ }
103
+ throw new Error(`Component "${name}" not found at: ${componentPath}`);
104
+ }
105
+ const url = `${REGISTRY_URL}/${name}.json`;
106
+ const response = await fetch(url);
107
+ if (!response.ok) {
108
+ throw new Error(`Component "${name}" not found in registry`);
109
+ }
110
+ return response.json();
111
+ }
112
+ async function resolveComponentDependencies(names, resolved = /* @__PURE__ */ new Set()) {
113
+ const result = [];
114
+ for (const name of names) {
115
+ if (resolved.has(name)) continue;
116
+ resolved.add(name);
117
+ const component = await getComponent(name);
118
+ if (component.registryDependencies?.length) {
119
+ const deps = await resolveComponentDependencies(
120
+ component.registryDependencies,
121
+ resolved
122
+ );
123
+ result.push(...deps);
124
+ }
125
+ result.push(name);
126
+ }
127
+ return result;
128
+ }
129
+
130
+ // src/utils/pm.ts
131
+ import fs3 from "fs-extra";
132
+ import path3 from "path";
133
+ function detectPackageManager(cwd) {
134
+ if (fs3.existsSync(path3.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
135
+ if (fs3.existsSync(path3.join(cwd, "bun.lock")) || fs3.existsSync(path3.join(cwd, "bun.lockb"))) {
136
+ return "bun";
137
+ }
138
+ if (fs3.existsSync(path3.join(cwd, "yarn.lock"))) return "yarn";
139
+ if (fs3.existsSync(path3.join(cwd, "package-lock.json"))) return "npm";
140
+ const userAgent = process.env.npm_config_user_agent ?? "";
141
+ if (userAgent.startsWith("pnpm")) return "pnpm";
142
+ if (userAgent.startsWith("yarn")) return "yarn";
143
+ if (userAgent.startsWith("bun")) return "bun";
144
+ return "npm";
145
+ }
146
+ function installCommand(pm, packages, dev = false) {
147
+ const devFlag = dev ? ["-D"] : [];
148
+ if (pm === "npm") {
149
+ return { command: "npm", args: ["install", ...devFlag, ...packages] };
150
+ }
151
+ return { command: pm, args: ["add", ...devFlag, ...packages] };
152
+ }
153
+
77
154
  // src/utils/ui.ts
78
155
  import chalk from "chalk";
79
156
  var brand = {
@@ -176,8 +253,65 @@ var promptsTheme = {
176
253
  };
177
254
 
178
255
  // src/commands/init.ts
256
+ async function ensureTsconfigPaths(cwd) {
257
+ const tsconfigPath = path4.join(cwd, "tsconfig.json");
258
+ if (!await fs4.pathExists(tsconfigPath)) {
259
+ await fs4.writeJson(
260
+ tsconfigPath,
261
+ {
262
+ extends: "astro/tsconfigs/strict",
263
+ compilerOptions: {
264
+ baseUrl: ".",
265
+ paths: { "@/*": ["./src/*"] }
266
+ }
267
+ },
268
+ { spaces: 2 }
269
+ );
270
+ return "done";
271
+ }
272
+ try {
273
+ const tsconfig = await fs4.readJson(tsconfigPath);
274
+ const compilerOptions = tsconfig.compilerOptions ?? {};
275
+ if (compilerOptions.paths?.["@/*"]) return "already";
276
+ tsconfig.compilerOptions = {
277
+ ...compilerOptions,
278
+ baseUrl: compilerOptions.baseUrl ?? ".",
279
+ paths: { ...compilerOptions.paths, "@/*": ["./src/*"] }
280
+ };
281
+ await fs4.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
282
+ return "done";
283
+ } catch {
284
+ return "manual";
285
+ }
286
+ }
287
+ async function wireTailwindPlugin(cwd) {
288
+ const candidates = [
289
+ "astro.config.mjs",
290
+ "astro.config.ts",
291
+ "astro.config.mts",
292
+ "astro.config.js"
293
+ ];
294
+ for (const name of candidates) {
295
+ const configPath = path4.join(cwd, name);
296
+ if (!await fs4.pathExists(configPath)) continue;
297
+ let content = await fs4.readFile(configPath, "utf-8");
298
+ if (content.includes("@tailwindcss/vite")) return "already";
299
+ if (/vite\s*:/.test(content) || !content.includes("defineConfig({")) {
300
+ return "manual";
301
+ }
302
+ content = content.replace(
303
+ "defineConfig({",
304
+ "defineConfig({\n vite: {\n plugins: [tailwindcss()],\n },"
305
+ );
306
+ content = `import tailwindcss from "@tailwindcss/vite";
307
+ ${content}`;
308
+ await fs4.writeFile(configPath, content);
309
+ return "done";
310
+ }
311
+ return "manual";
312
+ }
179
313
  async function init(options) {
180
- const cwd = path2.resolve(options.cwd);
314
+ const cwd = path4.resolve(options.cwd);
181
315
  print.logo();
182
316
  console.log(` ${messages.initStart()}`);
183
317
  print.newline();
@@ -188,8 +322,8 @@ async function init(options) {
188
322
  print.newline();
189
323
  process.exit(1);
190
324
  }
191
- const configPath = path2.join(cwd, CONFIG_FILE);
192
- if (await fs2.pathExists(configPath)) {
325
+ const configPath = path4.join(cwd, CONFIG_FILE);
326
+ if (await fs4.pathExists(configPath)) {
193
327
  print.warning(messages.alreadyInit());
194
328
  print.newline();
195
329
  const { overwrite } = options.yes ? { overwrite: true } : await prompts({
@@ -204,6 +338,7 @@ async function init(options) {
204
338
  process.exit(0);
205
339
  }
206
340
  }
341
+ const pm = detectPackageManager(cwd);
207
342
  const hasTailwind = await hasTailwindInstalled(cwd);
208
343
  if (!hasTailwind) {
209
344
  print.warning("Tailwind CSS isn't installed yet.");
@@ -219,17 +354,21 @@ async function init(options) {
219
354
  color: "green"
220
355
  }).start();
221
356
  try {
222
- await execa(
223
- "npm",
224
- ["install", "-D", "tailwindcss", "@tailwindcss/vite"],
225
- { cwd }
357
+ const { command, args } = installCommand(
358
+ pm,
359
+ ["tailwindcss", "@tailwindcss/vite"],
360
+ true
226
361
  );
362
+ await execa(command, args, { cwd });
227
363
  spinner2.succeed(brand.success("Tailwind CSS is ready"));
228
364
  } catch (error) {
229
365
  spinner2.fail("Couldn't install Tailwind CSS");
230
- print.hint(
231
- "Try manually: npm install -D tailwindcss @tailwindcss/vite"
366
+ const { command, args } = installCommand(
367
+ pm,
368
+ ["tailwindcss", "@tailwindcss/vite"],
369
+ true
232
370
  );
371
+ print.hint(`Try manually: ${command} ${args.join(" ")}`);
233
372
  }
234
373
  }
235
374
  }
@@ -262,15 +401,15 @@ async function init(options) {
262
401
  color: "green"
263
402
  }).start();
264
403
  try {
265
- await fs2.ensureDir(path2.join(cwd, config.componentsDir));
266
- await fs2.ensureDir(path2.join(cwd, config.utilsDir));
267
- await fs2.ensureDir(path2.join(cwd, config.stylesDir));
404
+ await fs4.ensureDir(path4.join(cwd, config.componentsDir));
405
+ await fs4.ensureDir(path4.join(cwd, config.utilsDir));
406
+ await fs4.ensureDir(path4.join(cwd, config.stylesDir));
268
407
  spinner.text = "Creating directories...";
269
408
  } catch (error) {
270
409
  spinner.fail("Couldn't create directories");
271
410
  process.exit(1);
272
411
  }
273
- const cnUtilContent = `import { type ClassValue, clsx } from "clsx";
412
+ const cnFallbackContent = `import { type ClassValue, clsx } from "clsx";
274
413
  import { twMerge } from "tailwind-merge";
275
414
 
276
415
  export function cn(...inputs: ClassValue[]) {
@@ -278,10 +417,17 @@ export function cn(...inputs: ClassValue[]) {
278
417
  }
279
418
  `;
280
419
  try {
281
- const utilPath = path2.join(cwd, config.utilsDir, "cn.ts");
282
- if (!await fs2.pathExists(utilPath)) {
283
- await fs2.writeFile(utilPath, cnUtilContent);
284
- const packageJson = await fs2.readJson(path2.join(cwd, "package.json"));
420
+ const utilPath = path4.join(cwd, config.utilsDir, "cn.ts");
421
+ if (!await fs4.pathExists(utilPath)) {
422
+ let cnContent = cnFallbackContent;
423
+ try {
424
+ const cnEntry = await getComponent("cn");
425
+ const cnFile = cnEntry.files.find((f) => f.name === "cn.ts");
426
+ if (cnFile) cnContent = cnFile.content;
427
+ } catch {
428
+ }
429
+ await fs4.writeFile(utilPath, cnContent);
430
+ const packageJson = await fs4.readJson(path4.join(cwd, "package.json"));
285
431
  const deps = {
286
432
  ...packageJson.dependencies,
287
433
  ...packageJson.devDependencies
@@ -291,7 +437,8 @@ export function cn(...inputs: ClassValue[]) {
291
437
  if (!("tailwind-merge" in deps)) toInstall.push("tailwind-merge");
292
438
  if (toInstall.length > 0) {
293
439
  spinner.text = "Installing utilities...";
294
- await execa("npm", ["install", ...toInstall], { cwd });
440
+ const { command, args } = installCommand(pm, toInstall);
441
+ await execa(command, args, { cwd });
295
442
  }
296
443
  }
297
444
  } catch (error) {
@@ -304,12 +451,51 @@ export function cn(...inputs: ClassValue[]) {
304
451
  spinner.fail("Couldn't save configuration");
305
452
  process.exit(1);
306
453
  }
454
+ const manualSteps = [];
455
+ const tsconfigResult = await ensureTsconfigPaths(cwd);
456
+ if (tsconfigResult === "done") {
457
+ print.step(`${brand.success("\u2713")} Added ${chalk2.cyan("@/*")} path alias to tsconfig.json`);
458
+ } else if (tsconfigResult === "manual") {
459
+ manualSteps.push(
460
+ `Add to tsconfig.json: ${chalk2.cyan(`"paths": { "@/*": ["./src/*"] }`)} under compilerOptions`
461
+ );
462
+ }
463
+ const tailwindResult = await wireTailwindPlugin(cwd);
464
+ if (tailwindResult === "done") {
465
+ print.step(`${brand.success("\u2713")} Added ${chalk2.cyan("@tailwindcss/vite")} to your Astro config`);
466
+ } else if (tailwindResult === "manual") {
467
+ manualSteps.push(
468
+ `Add ${chalk2.cyan("tailwindcss()")} from ${chalk2.cyan("@tailwindcss/vite")} to vite.plugins in your Astro config`
469
+ );
470
+ }
471
+ try {
472
+ const stylesEntry = await getComponent("styles");
473
+ let stylesWritten = false;
474
+ for (const file of stylesEntry.files) {
475
+ const stylesPath = path4.join(cwd, config.stylesDir, file.name);
476
+ if (!await fs4.pathExists(stylesPath)) {
477
+ await fs4.ensureDir(path4.dirname(stylesPath));
478
+ await fs4.writeFile(stylesPath, file.content);
479
+ stylesWritten = true;
480
+ }
481
+ }
482
+ if (stylesWritten) {
483
+ print.step(
484
+ `${brand.success("\u2713")} Added theme variables to ${chalk2.cyan(`${config.stylesDir}/bearnie.css`)}`
485
+ );
486
+ manualSteps.push(
487
+ `Import the styles in your global CSS: ${chalk2.cyan(`@import "./bearnie.css";`)} (after ${chalk2.cyan(`@import "tailwindcss";`)})`
488
+ );
489
+ }
490
+ } catch {
491
+ manualSteps.push(`Add theme variables: ${chalk2.cyan("npx bearnie add styles")}`);
492
+ }
307
493
  print.newline();
308
494
  console.log(` ${messages.initSuccess()}`);
309
495
  print.nextSteps([
496
+ ...manualSteps,
310
497
  `Add your first component: ${chalk2.cyan("npx bearnie add button")}`,
311
- `Browse all components: ${chalk2.cyan("npx bearnie list")}`,
312
- `Add CSS variables: ${chalk2.cyan("npx bearnie add styles")}`
498
+ `Browse all components: ${chalk2.cyan("npx bearnie list")}`
313
499
  ]);
314
500
  print.footer();
315
501
  }
@@ -318,66 +504,11 @@ export function cn(...inputs: ClassValue[]) {
318
504
  import chalk3 from "chalk";
319
505
  import ora2 from "ora";
320
506
  import prompts2 from "prompts";
321
- import path4 from "path";
322
- import fs4 from "fs-extra";
507
+ import path5 from "path";
508
+ import fs5 from "fs-extra";
323
509
  import { execa as execa2 } from "execa";
324
-
325
- // src/utils/registry.ts
326
- import fs3 from "fs-extra";
327
- import path3 from "path";
328
- var REGISTRY_URL = process.env.BEARNIE_REGISTRY_URL || "https://bearnie.dev/registry";
329
- var REGISTRY_PATH = process.env.BEARNIE_REGISTRY_PATH;
330
- var REGISTRY_INDEX_URL = `${REGISTRY_URL}/index.json`;
331
- async function getRegistryIndex() {
332
- if (REGISTRY_PATH) {
333
- const indexPath = path3.join(REGISTRY_PATH, "index.json");
334
- if (await fs3.pathExists(indexPath)) {
335
- return fs3.readJson(indexPath);
336
- }
337
- throw new Error(`Registry index not found at: ${indexPath}`);
338
- }
339
- const response = await fetch(REGISTRY_INDEX_URL);
340
- if (!response.ok) {
341
- throw new Error(`Failed to fetch registry index: ${response.statusText}`);
342
- }
343
- return response.json();
344
- }
345
- async function getComponent(name) {
346
- if (REGISTRY_PATH) {
347
- const componentPath = path3.join(REGISTRY_PATH, `${name}.json`);
348
- if (await fs3.pathExists(componentPath)) {
349
- return fs3.readJson(componentPath);
350
- }
351
- throw new Error(`Component "${name}" not found at: ${componentPath}`);
352
- }
353
- const url = `${REGISTRY_URL}/${name}.json`;
354
- const response = await fetch(url);
355
- if (!response.ok) {
356
- throw new Error(`Component "${name}" not found in registry`);
357
- }
358
- return response.json();
359
- }
360
- async function resolveComponentDependencies(names, resolved = /* @__PURE__ */ new Set()) {
361
- const result = [];
362
- for (const name of names) {
363
- if (resolved.has(name)) continue;
364
- resolved.add(name);
365
- const component = await getComponent(name);
366
- if (component.registryDependencies?.length) {
367
- const deps = await resolveComponentDependencies(
368
- component.registryDependencies,
369
- resolved
370
- );
371
- result.push(...deps);
372
- }
373
- result.push(name);
374
- }
375
- return result;
376
- }
377
-
378
- // src/commands/add.ts
379
510
  async function add(components, options) {
380
- const cwd = path4.resolve(options.cwd);
511
+ const cwd = path5.resolve(options.cwd);
381
512
  print.logo();
382
513
  console.log(` ${messages.addStart()}`);
383
514
  print.newline();
@@ -471,46 +602,86 @@ async function add(components, options) {
471
602
  const npmDevDeps = /* @__PURE__ */ new Set();
472
603
  const writtenFiles = [];
473
604
  const skippedFiles = [];
605
+ const plans = [];
474
606
  for (const componentName of allComponents) {
475
- const spinner = ora2({
476
- text: messages.installing(componentName),
477
- color: "green"
478
- }).start();
479
607
  try {
480
608
  const component = await getComponent(componentName);
481
609
  component.dependencies?.forEach((d) => npmDeps.add(d));
482
610
  component.devDependencies?.forEach((d) => npmDevDeps.add(d));
611
+ const files = [];
483
612
  for (const file of component.files) {
484
- const filePath = resolveInstallPath(
613
+ const absPath = resolveInstallPath(
485
614
  cwd,
486
615
  config,
487
616
  file.path,
488
617
  component.type
489
618
  );
490
- const exists = await fs4.pathExists(filePath);
491
- if (exists && !options.yes) {
492
- skippedFiles.push(file.path);
619
+ files.push({
620
+ registryPath: file.path,
621
+ absPath,
622
+ content: file.content,
623
+ exists: await fs5.pathExists(absPath)
624
+ });
625
+ }
626
+ plans.push({ component, files });
627
+ } catch (error) {
628
+ print.error(`Couldn't fetch ${componentName}`);
629
+ print.hint(`${error}`);
630
+ }
631
+ }
632
+ const existingFiles = plans.flatMap((p) => p.files.filter((f) => f.exists));
633
+ let overwrite = Boolean(options.yes || options.overwrite);
634
+ if (existingFiles.length > 0 && !overwrite) {
635
+ print.warning(
636
+ `${existingFiles.length} file${existingFiles.length > 1 ? "s" : ""} already exist${existingFiles.length > 1 ? "" : "s"}:`
637
+ );
638
+ existingFiles.slice(0, 5).forEach((f) => {
639
+ console.log(brand.muted(` ${f.registryPath}`));
640
+ });
641
+ if (existingFiles.length > 5) {
642
+ console.log(brand.muted(` ...and ${existingFiles.length - 5} more`));
643
+ }
644
+ print.newline();
645
+ const { confirmOverwrite } = await prompts2({
646
+ type: "confirm",
647
+ name: "confirmOverwrite",
648
+ message: "Overwrite existing files?",
649
+ initial: false
650
+ });
651
+ overwrite = Boolean(confirmOverwrite);
652
+ print.newline();
653
+ }
654
+ for (const { component, files } of plans) {
655
+ const spinner = ora2({
656
+ text: messages.installing(component.name),
657
+ color: "green"
658
+ }).start();
659
+ try {
660
+ for (const file of files) {
661
+ if (file.exists && !overwrite) {
662
+ skippedFiles.push(file.registryPath);
493
663
  continue;
494
664
  }
495
- await fs4.ensureDir(path4.dirname(filePath));
496
- await fs4.writeFile(filePath, file.content);
497
- writtenFiles.push(file.path);
665
+ await fs5.ensureDir(path5.dirname(file.absPath));
666
+ await fs5.writeFile(file.absPath, file.content);
667
+ writtenFiles.push(file.registryPath);
498
668
  }
499
- spinner.succeed(messages.installed(componentName));
669
+ spinner.succeed(messages.installed(component.name));
500
670
  } catch (error) {
501
- spinner.fail(`Couldn't add ${componentName}`);
671
+ spinner.fail(`Couldn't add ${component.name}`);
502
672
  print.hint(`${error}`);
503
673
  }
504
674
  }
505
675
  const allDeps = [...npmDeps];
506
676
  const allDevDeps = [...npmDevDeps];
507
677
  if (allDeps.length > 0 || allDevDeps.length > 0) {
678
+ const pm = detectPackageManager(cwd);
508
679
  const depsSpinner = ora2({
509
680
  text: messages.installingDeps(),
510
681
  color: "green"
511
682
  }).start();
512
683
  try {
513
- const packageJson = await fs4.readJson(path4.join(cwd, "package.json"));
684
+ const packageJson = await fs5.readJson(path5.join(cwd, "package.json"));
514
685
  const existingDeps = {
515
686
  ...packageJson.dependencies,
516
687
  ...packageJson.devDependencies
@@ -518,12 +689,14 @@ async function add(components, options) {
518
689
  const newDeps = allDeps.filter((d) => !(d in existingDeps));
519
690
  const newDevDeps = allDevDeps.filter((d) => !(d in existingDeps));
520
691
  if (newDeps.length > 0) {
521
- await execa2("npm", ["install", ...newDeps], { cwd });
692
+ const { command, args } = installCommand(pm, newDeps);
693
+ await execa2(command, args, { cwd });
522
694
  }
523
695
  if (newDevDeps.length > 0) {
524
- await execa2("npm", ["install", "-D", ...newDevDeps], { cwd });
696
+ const { command, args } = installCommand(pm, newDevDeps, true);
697
+ await execa2(command, args, { cwd });
525
698
  }
526
- depsSpinner.succeed("Dependencies installed");
699
+ depsSpinner.succeed(`Dependencies installed ${brand.muted(`(${pm})`)}`);
527
700
  } catch {
528
701
  depsSpinner.fail("Some dependencies couldn't be installed");
529
702
  print.hint("You might need to install them manually.");
@@ -546,7 +719,7 @@ async function add(components, options) {
546
719
  console.log(
547
720
  ` ${brand.warning("\u26A0")} Skipped ${skippedFiles.length} existing file${skippedFiles.length > 1 ? "s" : ""}`
548
721
  );
549
- print.hint(`Use ${chalk3.cyan("--yes")} to overwrite.`);
722
+ print.hint(`Use ${chalk3.cyan("--overwrite")} to replace them.`);
550
723
  }
551
724
  print.newline();
552
725
  print.success();
@@ -579,6 +752,8 @@ async function list(options) {
579
752
  categories.get(cat).push(component);
580
753
  }
581
754
  const categoryConfig = {
755
+ theme: { emoji: "\u{1F3A8}", label: "Theme" },
756
+ meta: { emoji: "\u{1F4E6}", label: "Meta" },
582
757
  foundation: { emoji: "\u{1F3A8}", label: "Foundation" },
583
758
  form: { emoji: "\u{1F4DD}", label: "Form" },
584
759
  layout: { emoji: "\u{1F4D0}", label: "Layout" },
@@ -589,6 +764,8 @@ async function list(options) {
589
764
  other: { emoji: "\u{1F4E6}", label: "Other" }
590
765
  };
591
766
  const categoryOrder = [
767
+ "theme",
768
+ "meta",
592
769
  "foundation",
593
770
  "form",
594
771
  "layout",
@@ -617,6 +794,7 @@ async function list(options) {
617
794
  console.log(` ${brand.muted("\u2192")} Add a component: ${chalk4.cyan("npx bearnie add button")}`);
618
795
  console.log(` ${brand.muted("\u2192")} Add everything: ${chalk4.cyan("npx bearnie add --all")}`);
619
796
  console.log(` ${brand.muted("\u2192")} Add CSS variables: ${chalk4.cyan("npx bearnie add styles")}`);
797
+ console.log(` ${brand.muted("\u2192")} Add barrel export: ${chalk4.cyan("npx bearnie add barrel")}`);
620
798
  print.newline();
621
799
  } catch (error) {
622
800
  spinner.fail(messages.networkError());
@@ -625,14 +803,296 @@ async function list(options) {
625
803
  }
626
804
  }
627
805
 
806
+ // src/commands/diff.ts
807
+ import chalk5 from "chalk";
808
+ import ora4 from "ora";
809
+ import path6 from "path";
810
+ import { structuredPatch } from "diff";
811
+
812
+ // src/utils/installed.ts
813
+ import fs6 from "fs-extra";
814
+ async function getAllRegistryNames() {
815
+ const index = await getRegistryIndex();
816
+ const names = index.components.map((c) => c.name);
817
+ for (const utility of index.utilities ?? []) {
818
+ names.push(utility.name);
819
+ }
820
+ return names;
821
+ }
822
+ async function getEntryState(cwd, config, name) {
823
+ const entry = await getComponent(name);
824
+ const files = [];
825
+ let anyExists = false;
826
+ for (const file of entry.files) {
827
+ const absPath = resolveInstallPath(cwd, config, file.path, entry.type);
828
+ const exists = await fs6.pathExists(absPath);
829
+ let status = "missing";
830
+ let localContent;
831
+ if (exists) {
832
+ anyExists = true;
833
+ localContent = await fs6.readFile(absPath, "utf-8");
834
+ status = localContent === file.content ? "unchanged" : "modified";
835
+ }
836
+ files.push({
837
+ path: file.path,
838
+ absPath,
839
+ status,
840
+ localContent,
841
+ registryContent: file.content
842
+ });
843
+ }
844
+ if (!anyExists) return null;
845
+ return {
846
+ entry,
847
+ files,
848
+ hasChanges: files.some((f) => f.status !== "unchanged")
849
+ };
850
+ }
851
+ async function getInstalledEntries(cwd, config, names) {
852
+ const allNames = await getAllRegistryNames();
853
+ if (names?.length) {
854
+ const unknown = names.filter((n) => !allNames.includes(n));
855
+ if (unknown.length > 0) {
856
+ throw new Error(`Unknown components: ${unknown.join(", ")}`);
857
+ }
858
+ }
859
+ const targets = names?.length ? names : allNames;
860
+ const installed = [];
861
+ for (const name of targets) {
862
+ const state = await getEntryState(cwd, config, name);
863
+ if (state) installed.push(state);
864
+ }
865
+ return installed;
866
+ }
867
+
868
+ // src/commands/diff.ts
869
+ function printFileDiff(filePath, local, registry) {
870
+ const patch = structuredPatch(filePath, filePath, local, registry, "", "", {
871
+ context: 3
872
+ });
873
+ for (const hunk of patch.hunks) {
874
+ console.log(
875
+ brand.info(
876
+ ` @@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`
877
+ )
878
+ );
879
+ for (const line of hunk.lines) {
880
+ if (line.startsWith("+")) {
881
+ console.log(` ${chalk5.green(line)}`);
882
+ } else if (line.startsWith("-")) {
883
+ console.log(` ${chalk5.red(line)}`);
884
+ } else {
885
+ console.log(` ${chalk5.dim(line)}`);
886
+ }
887
+ }
888
+ }
889
+ }
890
+ async function diff(components, options) {
891
+ const cwd = path6.resolve(options.cwd);
892
+ const config = await getProjectConfig(cwd) ?? DEFAULT_CONFIG;
893
+ print.logo();
894
+ console.log(` Comparing your components against the registry...`);
895
+ print.newline();
896
+ const spinner = ora4({ text: messages.fetching(), color: "green" }).start();
897
+ let installed;
898
+ try {
899
+ installed = await getInstalledEntries(cwd, config, components);
900
+ } catch (error) {
901
+ spinner.fail(
902
+ error instanceof Error ? error.message : messages.networkError()
903
+ );
904
+ print.hint(`Run ${chalk5.cyan("npx bearnie list")} to see valid names.`);
905
+ print.newline();
906
+ process.exit(1);
907
+ }
908
+ if (installed.length === 0) {
909
+ spinner.fail("No installed Bearnie components found.");
910
+ print.hint(`Add some first: ${chalk5.cyan("npx bearnie add button")}`);
911
+ print.newline();
912
+ return;
913
+ }
914
+ const changed = installed.filter((item) => item.hasChanges);
915
+ spinner.succeed(
916
+ `Checked ${chalk5.bold(installed.length)} installed component${installed.length > 1 ? "s" : ""}`
917
+ );
918
+ print.newline();
919
+ if (changed.length === 0) {
920
+ console.log(
921
+ ` ${brand.success("\u2713")} Everything is up to date with the registry.`
922
+ );
923
+ print.newline();
924
+ return;
925
+ }
926
+ for (const item of changed) {
927
+ console.log(` ${chalk5.bold(item.entry.name)}`);
928
+ for (const file of item.files) {
929
+ if (file.status === "unchanged") continue;
930
+ if (file.status === "missing") {
931
+ console.log(
932
+ ` ${brand.warning("+")} ${file.path} ${brand.muted("(not installed yet)")}`
933
+ );
934
+ continue;
935
+ }
936
+ console.log(` ${brand.warning("~")} ${file.path}`);
937
+ if (!options.nameOnly && file.localContent !== void 0) {
938
+ printFileDiff(file.path, file.localContent, file.registryContent);
939
+ }
940
+ }
941
+ print.newline();
942
+ }
943
+ console.log(
944
+ ` ${chalk5.bold(changed.length)} component${changed.length > 1 ? "s" : ""} differ${changed.length > 1 ? "" : "s"} from the registry.`
945
+ );
946
+ print.hint(
947
+ `Pull the registry version with ${chalk5.cyan(`npx bearnie update ${changed.map((c) => c.entry.name).join(" ")}`)}`
948
+ );
949
+ print.newline();
950
+ }
951
+
952
+ // src/commands/update.ts
953
+ import chalk6 from "chalk";
954
+ import ora5 from "ora";
955
+ import prompts3 from "prompts";
956
+ import path7 from "path";
957
+ import fs7 from "fs-extra";
958
+ import { execa as execa3 } from "execa";
959
+ async function update(components, options) {
960
+ const cwd = path7.resolve(options.cwd);
961
+ const config = await getProjectConfig(cwd) ?? DEFAULT_CONFIG;
962
+ print.logo();
963
+ console.log(` Let's bring your components up to date.`);
964
+ print.newline();
965
+ const spinner = ora5({ text: messages.fetching(), color: "green" }).start();
966
+ let installed;
967
+ try {
968
+ installed = await getInstalledEntries(cwd, config, components);
969
+ } catch (error) {
970
+ spinner.fail(
971
+ error instanceof Error ? error.message : messages.networkError()
972
+ );
973
+ print.hint(`Run ${chalk6.cyan("npx bearnie list")} to see valid names.`);
974
+ print.newline();
975
+ process.exit(1);
976
+ }
977
+ if (installed.length === 0) {
978
+ spinner.fail("No installed Bearnie components found.");
979
+ print.hint(`Add some first: ${chalk6.cyan("npx bearnie add button")}`);
980
+ print.newline();
981
+ return;
982
+ }
983
+ const changed = installed.filter((item) => item.hasChanges);
984
+ spinner.succeed(
985
+ `Checked ${chalk6.bold(installed.length)} installed component${installed.length > 1 ? "s" : ""}`
986
+ );
987
+ print.newline();
988
+ if (changed.length === 0) {
989
+ console.log(
990
+ ` ${brand.success("\u2713")} Everything is already up to date.`
991
+ );
992
+ print.newline();
993
+ return;
994
+ }
995
+ const changedFiles = changed.flatMap(
996
+ (item) => item.files.filter((file) => file.status !== "unchanged")
997
+ );
998
+ const modifiedCount = changedFiles.filter(
999
+ (file) => file.status === "modified"
1000
+ ).length;
1001
+ console.log(` ${chalk6.bold("These will be updated:")}`);
1002
+ for (const item of changed) {
1003
+ const parts = item.files.filter((file) => file.status !== "unchanged").map(
1004
+ (file) => file.status === "missing" ? `${file.path} (new)` : file.path
1005
+ );
1006
+ console.log(
1007
+ ` ${brand.warning("~")} ${chalk6.bold(item.entry.name)} ${brand.muted(`(${parts.join(", ")})`)}`
1008
+ );
1009
+ }
1010
+ print.newline();
1011
+ if (modifiedCount > 0) {
1012
+ print.warning(
1013
+ `${modifiedCount} file${modifiedCount > 1 ? "s" : ""} differ${modifiedCount > 1 ? "" : "s"} locally \u2014 updating overwrites your local edits.`
1014
+ );
1015
+ print.hint(
1016
+ `Review changes first with ${chalk6.cyan("npx bearnie diff")}`
1017
+ );
1018
+ print.newline();
1019
+ }
1020
+ if (!options.yes) {
1021
+ const { proceed } = await prompts3({
1022
+ type: "confirm",
1023
+ name: "proceed",
1024
+ message: `Update ${changed.length} component${changed.length > 1 ? "s" : ""}?`,
1025
+ initial: true
1026
+ });
1027
+ if (!proceed) {
1028
+ print.hint("No changes made.");
1029
+ print.newline();
1030
+ return;
1031
+ }
1032
+ print.newline();
1033
+ }
1034
+ const npmDeps = /* @__PURE__ */ new Set();
1035
+ let written = 0;
1036
+ for (const item of changed) {
1037
+ const writeSpinner = ora5({
1038
+ text: `Updating ${chalk6.cyan(item.entry.name)}...`,
1039
+ color: "green"
1040
+ }).start();
1041
+ try {
1042
+ for (const file of item.files) {
1043
+ if (file.status === "unchanged") continue;
1044
+ await fs7.ensureDir(path7.dirname(file.absPath));
1045
+ await fs7.writeFile(file.absPath, file.registryContent);
1046
+ written++;
1047
+ }
1048
+ item.entry.dependencies?.forEach((dep) => npmDeps.add(dep));
1049
+ writeSpinner.succeed(`${item.entry.name} updated`);
1050
+ } catch (error) {
1051
+ writeSpinner.fail(`Couldn't update ${item.entry.name}`);
1052
+ print.hint(`${error}`);
1053
+ }
1054
+ }
1055
+ if (npmDeps.size > 0) {
1056
+ try {
1057
+ const packageJson = await fs7.readJson(path7.join(cwd, "package.json"));
1058
+ const existing = {
1059
+ ...packageJson.dependencies,
1060
+ ...packageJson.devDependencies
1061
+ };
1062
+ const missing = [...npmDeps].filter((dep) => !(dep in existing));
1063
+ if (missing.length > 0) {
1064
+ const pm = detectPackageManager(cwd);
1065
+ const depsSpinner = ora5({
1066
+ text: `Installing ${missing.join(", ")} with ${pm}...`,
1067
+ color: "green"
1068
+ }).start();
1069
+ try {
1070
+ const { command, args } = installCommand(pm, missing);
1071
+ await execa3(command, args, { cwd });
1072
+ depsSpinner.succeed("Dependencies installed");
1073
+ } catch {
1074
+ depsSpinner.fail("Some dependencies couldn't be installed");
1075
+ print.hint(`Install manually: ${missing.join(" ")}`);
1076
+ }
1077
+ }
1078
+ } catch {
1079
+ }
1080
+ }
1081
+ print.newline();
1082
+ console.log(
1083
+ ` ${brand.success("\u2713")} Updated ${chalk6.bold(written)} file${written > 1 ? "s" : ""} across ${chalk6.bold(changed.length)} component${changed.length > 1 ? "s" : ""}.`
1084
+ );
1085
+ print.success();
1086
+ }
1087
+
628
1088
  // src/index.ts
629
1089
  var __dirname = dirname(fileURLToPath(import.meta.url));
630
1090
  var pkg = JSON.parse(
631
1091
  readFileSync(join(__dirname, "../package.json"), "utf-8")
632
1092
  );
633
- var amber = chalk5.hex("#F59E0B");
634
- var logo2 = `${amber("\u{1F43B}")} ${chalk5.bold("bearnie")}`;
635
- var link = (text, url) => `\x1B]8;;${url}\x07${chalk5.cyan(text)}\x1B]8;;\x07`;
1093
+ var amber = chalk7.hex("#F59E0B");
1094
+ var logo2 = `${amber("\u{1F43B}")} ${chalk7.bold("bearnie")}`;
1095
+ var link = (text, url) => `\x1B]8;;${url}\x07${chalk7.cyan(text)}\x1B]8;;\x07`;
636
1096
  var program = new Command();
637
1097
  program.name("bearnie").description("UI components for Astro").version(pkg.version).configureOutput({
638
1098
  writeOut: (str) => process.stdout.write(str),
@@ -642,7 +1102,7 @@ program.name("bearnie").description("UI components for Astro").version(pkg.versi
642
1102
  ${logo2}
643
1103
 
644
1104
  `);
645
- write(` ${chalk5.red("Oops!")} ${str.replace("error: ", "")}
1105
+ write(` ${chalk7.red("Oops!")} ${str.replace("error: ", "")}
646
1106
  `);
647
1107
  }
648
1108
  }).addHelpText("beforeAll", `
@@ -650,40 +1110,45 @@ program.name("bearnie").description("UI components for Astro").version(pkg.versi
650
1110
  `).addHelpText(
651
1111
  "afterAll",
652
1112
  `
653
- ${chalk5.dim("Made with")} ${amber("\u{1F43B}")} ${chalk5.dim("by")} ${link("Michael Andreuzza", "https://michaelandreuzza.com")}
1113
+ ${chalk7.dim("Made with")} ${amber("\u{1F43B}")} ${chalk7.dim("by")} ${link("Michael Andreuzza", "https://michaelandreuzza.com")}
654
1114
  `
655
1115
  );
656
1116
  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);
657
- 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);
1117
+ 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("-o, --overwrite", "Overwrite existing files without asking").option("--cwd <path>", "Working directory", process.cwd()).action(add);
658
1118
  program.command("list").description("Browse available components").option("--json", "Output as JSON").action(list);
659
- program.action(() => {
1119
+ program.command("diff").description("See how your components differ from the registry").argument("[components...]", "Components to check (defaults to all installed)").option("--name-only", "Only show which files changed, not the diff").option("--cwd <path>", "Working directory", process.cwd()).action(diff);
1120
+ program.command("update").description("Update installed components to the latest registry version").argument("[components...]", "Components to update (defaults to all installed)").option("-y, --yes", "Skip confirmation prompt").option("--cwd <path>", "Working directory", process.cwd()).action(update);
1121
+ program.argument("[args...]").action((args) => {
1122
+ if (args.length > 0) {
1123
+ console.log(`
1124
+ ${logo2}
1125
+
1126
+ ${chalk7.yellow("Hmm,")} I don't know that command: ${chalk7.red(args.join(" "))}
1127
+
1128
+ Run ${chalk7.cyan("npx bearnie --help")} to see what I can do.
1129
+ `);
1130
+ process.exit(1);
1131
+ }
660
1132
  console.log(`
661
1133
  ${logo2}
662
1134
 
663
1135
  ${amber("Hey!")} UI components for Astro.
664
1136
  Built with Tailwind CSS, no frameworks required.
665
1137
 
666
- ${chalk5.bold("Commands:")}
667
- ${chalk5.cyan("init")} Set up Bearnie in your project
668
- ${chalk5.cyan("add")} ${chalk5.dim("<name>")} Add a component
669
- ${chalk5.cyan("list")} Browse all components
1138
+ ${chalk7.bold("Commands:")}
1139
+ ${chalk7.cyan("init")} Set up Bearnie in your project
1140
+ ${chalk7.cyan("add")} ${chalk7.dim("<name>")} Add a component
1141
+ ${chalk7.cyan("list")} Browse all components
1142
+ ${chalk7.cyan("diff")} See what changed in the registry
1143
+ ${chalk7.cyan("update")} Pull the latest component fixes
670
1144
 
671
- ${chalk5.bold("Examples:")}
672
- ${chalk5.dim("$")} npx bearnie init
673
- ${chalk5.dim("$")} npx bearnie add button card
674
- ${chalk5.dim("$")} npx bearnie add --all
1145
+ ${chalk7.bold("Examples:")}
1146
+ ${chalk7.dim("$")} npx bearnie init
1147
+ ${chalk7.dim("$")} npx bearnie add button card
1148
+ ${chalk7.dim("$")} npx bearnie diff
1149
+ ${chalk7.dim("$")} npx bearnie update --yes
675
1150
 
676
- ${chalk5.dim("Run")} ${chalk5.cyan("bearnie <command> --help")} ${chalk5.dim("for more info")}
1151
+ ${chalk7.dim("Run")} ${chalk7.cyan("bearnie <command> --help")} ${chalk7.dim("for more info")}
677
1152
  `);
678
1153
  });
679
1154
  program.parse();
680
- program.on("command:*", () => {
681
- console.log(`
682
- ${logo2}
683
-
684
- ${chalk5.yellow("Hmm,")} I don't know that command: ${chalk5.red(program.args.join(" "))}
685
-
686
- Run ${chalk5.cyan("npx bearnie --help")} to see what I can do.
687
- `);
688
- process.exit(1);
689
- });
@@ -0,0 +1,22 @@
1
+ export interface ProjectConfig {
2
+ componentsDir: string;
3
+ utilsDir: string;
4
+ stylesDir: string;
5
+ tailwindConfig: string;
6
+ typescript: boolean;
7
+ }
8
+ export declare const DEFAULT_CONFIG: ProjectConfig;
9
+ export declare const CONFIG_FILE = "bearnie.json";
10
+ export declare function getProjectConfig(cwd: string): Promise<ProjectConfig | null>;
11
+ export declare function saveProjectConfig(cwd: string, config: ProjectConfig): Promise<void>;
12
+ export declare function isAstroProject(cwd: string): Promise<boolean>;
13
+ export declare function hasTailwindInstalled(cwd: string): Promise<boolean>;
14
+ export declare function writeComponentFile(cwd: string, config: ProjectConfig, componentPath: string, content: string, overwrite?: boolean): Promise<{
15
+ written: boolean;
16
+ path: string;
17
+ }>;
18
+ export declare function resolveInstallPath(cwd: string, config: ProjectConfig, filePath: string, componentType?: string): string;
19
+ export declare function writeUtilFile(cwd: string, config: ProjectConfig, utilPath: string, content: string, overwrite?: boolean): Promise<{
20
+ written: boolean;
21
+ path: string;
22
+ }>;
@@ -0,0 +1,31 @@
1
+ import { type ProjectConfig } from "./config.js";
2
+ import { type RegistryComponent } from "./registry.js";
3
+ export type FileStatus = "unchanged" | "modified" | "missing";
4
+ export interface FileState {
5
+ /** Registry-relative path (e.g. `button/Button.astro`). */
6
+ path: string;
7
+ /** Absolute path in the user's project. */
8
+ absPath: string;
9
+ status: FileStatus;
10
+ /** Current content on disk (undefined when missing). */
11
+ localContent?: string;
12
+ /** Content in the registry. */
13
+ registryContent: string;
14
+ }
15
+ export interface InstalledEntry {
16
+ entry: RegistryComponent;
17
+ files: FileState[];
18
+ hasChanges: boolean;
19
+ }
20
+ /** Every name in the registry: components, utilities, styles, barrel. */
21
+ export declare function getAllRegistryNames(): Promise<string[]>;
22
+ /**
23
+ * Compares one registry entry against the project. Returns null when the
24
+ * entry is not installed (none of its files exist locally).
25
+ */
26
+ export declare function getEntryState(cwd: string, config: ProjectConfig, name: string): Promise<InstalledEntry | null>;
27
+ /**
28
+ * Scans the project for installed registry entries. When `names` is given,
29
+ * only those entries are considered; unknown names throw.
30
+ */
31
+ export declare function getInstalledEntries(cwd: string, config: ProjectConfig, names?: string[]): Promise<InstalledEntry[]>;
@@ -0,0 +1,11 @@
1
+ export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
2
+ /**
3
+ * Detects the project's package manager from its lockfile, falling back to
4
+ * the npm_config_user_agent set by `npx` / `pnpm dlx` / `yarn dlx` / `bunx`.
5
+ */
6
+ export declare function detectPackageManager(cwd: string): PackageManager;
7
+ /** Arguments for installing packages with the given package manager. */
8
+ export declare function installCommand(pm: PackageManager, packages: string[], dev?: boolean): {
9
+ command: string;
10
+ args: string[];
11
+ };
@@ -0,0 +1,34 @@
1
+ export declare const REGISTRY_URL: string;
2
+ export declare const REGISTRY_PATH: string | undefined;
3
+ export declare const REGISTRY_INDEX_URL: string;
4
+ export interface RegistryComponent {
5
+ name: string;
6
+ type?: "component" | "utility" | "styles";
7
+ description: string;
8
+ category: string;
9
+ dependencies?: string[];
10
+ devDependencies?: string[];
11
+ registryDependencies?: string[];
12
+ files: RegistryFile[];
13
+ }
14
+ export interface RegistryFile {
15
+ name: string;
16
+ path: string;
17
+ content: string;
18
+ }
19
+ export interface RegistryIndex {
20
+ name: string;
21
+ version: string;
22
+ components: {
23
+ name: string;
24
+ description: string;
25
+ category: string;
26
+ }[];
27
+ utilities?: {
28
+ name: string;
29
+ description: string;
30
+ }[];
31
+ }
32
+ export declare function getRegistryIndex(): Promise<RegistryIndex>;
33
+ export declare function getComponent(name: string): Promise<RegistryComponent>;
34
+ export declare function resolveComponentDependencies(names: string[], resolved?: Set<string>): Promise<string[]>;
@@ -0,0 +1,57 @@
1
+ export declare const brand: {
2
+ primary: import("chalk").ChalkInstance;
3
+ secondary: import("chalk").ChalkInstance;
4
+ accent: import("chalk").ChalkInstance;
5
+ muted: import("chalk").ChalkInstance;
6
+ success: import("chalk").ChalkInstance;
7
+ warning: import("chalk").ChalkInstance;
8
+ error: import("chalk").ChalkInstance;
9
+ info: import("chalk").ChalkInstance;
10
+ };
11
+ export declare const banner: string;
12
+ export declare const logo: string;
13
+ export declare const messages: {
14
+ greeting: () => string;
15
+ initStart: () => string;
16
+ initSuccess: () => string;
17
+ alreadyInit: () => string;
18
+ addStart: () => string;
19
+ fetching: () => string;
20
+ foundComponents: (count: number) => string;
21
+ resolving: () => string;
22
+ installing: (name: string) => string;
23
+ installed: (name: string) => string;
24
+ installingDeps: () => string;
25
+ listHeader: (version: string) => string;
26
+ listFooter: (count: number) => string;
27
+ success: () => string;
28
+ notAstro: () => string;
29
+ notAstroHelp: () => string;
30
+ networkError: () => string;
31
+ networkErrorHelp: () => string;
32
+ unknownComponent: (names: string[]) => string;
33
+ hint: (text: string) => string;
34
+ nextStep: (step: string) => string;
35
+ };
36
+ export declare function box(content: string, title?: string): string;
37
+ export declare const newline: () => void;
38
+ export declare const space: (n?: number) => void;
39
+ export declare const print: {
40
+ banner: () => void;
41
+ logo: () => void;
42
+ greeting: () => void;
43
+ success: () => void;
44
+ newline: () => void;
45
+ step: (text: string) => void;
46
+ hint: (text: string) => void;
47
+ error: (text: string) => void;
48
+ warning: (text: string) => void;
49
+ info: (text: string) => void;
50
+ nextSteps: (steps: string[]) => void;
51
+ footer: () => void;
52
+ };
53
+ export declare const promptsTheme: {
54
+ prefix: string;
55
+ highlight: import("chalk").ChalkInstance;
56
+ submit: string;
57
+ };
package/package.json CHANGED
@@ -1,36 +1,37 @@
1
1
  {
2
2
  "name": "bearnie",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for installing Bearnie UI components",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": "dist/index.js",
8
+ "types": "dist/index.d.ts",
8
9
  "files": [
9
10
  "dist"
10
11
  ],
11
12
  "scripts": {
12
- "build": "tsup src/index.ts --format esm --clean",
13
+ "build": "tsup src/index.ts --format esm --clean && tsc --emitDeclarationOnly",
13
14
  "dev": "tsup src/index.ts --format esm --watch",
14
15
  "typecheck": "tsc --noEmit"
15
16
  },
16
17
  "dependencies": {
17
- "chalk": "^5.3.0",
18
- "commander": "^12.1.0",
19
- "execa": "^9.3.1",
18
+ "chalk": "^6.0.0",
19
+ "commander": "^15.0.0",
20
+ "diff": "^9.0.0",
21
+ "execa": "^10.0.1",
20
22
  "fs-extra": "^11.2.0",
21
- "node-fetch": "^3.3.2",
22
- "ora": "^8.0.1",
23
+ "ora": "^9.4.1",
23
24
  "prompts": "^2.4.2"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@types/fs-extra": "^11.0.4",
27
- "@types/node": "^20.14.9",
28
+ "@types/node": "^22.10.0",
28
29
  "@types/prompts": "^2.4.9",
29
30
  "tsup": "^8.1.0",
30
31
  "typescript": "^5.7.3"
31
32
  },
32
33
  "engines": {
33
- "node": ">=18"
34
+ "node": ">=22.12.0"
34
35
  },
35
36
  "author": "Michael Andreuzza",
36
37
  "repository": {