bearnie 0.1.7 → 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 +69 -9
- package/dist/commands/add.d.ts +1 -0
- package/dist/commands/diff.d.ts +6 -0
- package/dist/commands/update.d.ts +6 -0
- package/dist/index.js +585 -125
- package/dist/utils/installed.d.ts +31 -0
- package/dist/utils/pm.d.ts +11 -0
- package/dist/utils/registry.d.ts +4 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -61,13 +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`
|
|
64
67
|
|
|
65
68
|
Projects created with `create-bearnie` already include `bearnie.json` and can skip init.
|
|
66
69
|
|
|
67
70
|
**Options:**
|
|
68
71
|
|
|
69
72
|
- `-y, --yes` - Skip confirmation prompts and use defaults
|
|
70
|
-
-
|
|
73
|
+
- `--cwd <path>` - Set the working directory (defaults to current directory)
|
|
71
74
|
|
|
72
75
|
### `add`
|
|
73
76
|
|
|
@@ -90,11 +93,14 @@ npx bearnie add barrel
|
|
|
90
93
|
npx bearnie add
|
|
91
94
|
```
|
|
92
95
|
|
|
96
|
+
If a file already exists, `add` asks before overwriting it (existing files are kept if you decline).
|
|
97
|
+
|
|
93
98
|
**Options:**
|
|
94
99
|
|
|
95
|
-
- `-y, --yes` - Skip confirmation prompts
|
|
100
|
+
- `-y, --yes` - Skip confirmation prompts and overwrite existing files
|
|
96
101
|
- `-a, --all` - Add all available components
|
|
97
|
-
- `-
|
|
102
|
+
- `-o, --overwrite` - Overwrite existing files without asking
|
|
103
|
+
- `--cwd <path>` - Set the working directory
|
|
98
104
|
|
|
99
105
|
### `list`
|
|
100
106
|
|
|
@@ -114,6 +120,52 @@ Components are grouped by category, including **Theme** (`styles`) and **Meta**
|
|
|
114
120
|
|
|
115
121
|
- `--json` - Output as JSON
|
|
116
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
|
+
|
|
117
169
|
## Configuration
|
|
118
170
|
|
|
119
171
|
After running `init`, a `bearnie.json` file is created in your project root:
|
|
@@ -187,17 +239,25 @@ BEARNIE_REGISTRY_URL=http://localhost:4321/registry bearnie add button
|
|
|
187
239
|
|
|
188
240
|
## Available Components
|
|
189
241
|
|
|
190
|
-
**Form:** button, input,
|
|
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
|
|
191
251
|
|
|
192
|
-
**
|
|
252
|
+
**Display:** avatar, badge, carousel, hover-card, icon, kbd, table, tooltip
|
|
193
253
|
|
|
194
|
-
**
|
|
254
|
+
**Theme:** styles, theme-toggle
|
|
195
255
|
|
|
196
|
-
**
|
|
256
|
+
**Meta:** barrel
|
|
197
257
|
|
|
198
|
-
|
|
258
|
+
Shared utilities (`cn`, `focus-trap`, and the `ui-runtime-*` modules) are installed automatically as dependencies of the components that need them.
|
|
199
259
|
|
|
200
|
-
|
|
260
|
+
Run `npx bearnie list` for the always-current list.
|
|
201
261
|
|
|
202
262
|
## Usage Examples
|
|
203
263
|
|
package/dist/commands/add.d.ts
CHANGED
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
|
|
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
|
|
15
|
-
import
|
|
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 =
|
|
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 =
|
|
192
|
-
if (await
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
["
|
|
225
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
|
266
|
-
await
|
|
267
|
-
await
|
|
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
|
|
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 =
|
|
282
|
-
if (!await
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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
|
|
322
|
-
import
|
|
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 =
|
|
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
|
|
613
|
+
const absPath = resolveInstallPath(
|
|
485
614
|
cwd,
|
|
486
615
|
config,
|
|
487
616
|
file.path,
|
|
488
617
|
component.type
|
|
489
618
|
);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
496
|
-
await
|
|
497
|
-
writtenFiles.push(file.
|
|
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(
|
|
669
|
+
spinner.succeed(messages.installed(component.name));
|
|
500
670
|
} catch (error) {
|
|
501
|
-
spinner.fail(`Couldn't add ${
|
|
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
|
|
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
|
-
|
|
692
|
+
const { command, args } = installCommand(pm, newDeps);
|
|
693
|
+
await execa2(command, args, { cwd });
|
|
522
694
|
}
|
|
523
695
|
if (newDevDeps.length > 0) {
|
|
524
|
-
|
|
696
|
+
const { command, args } = installCommand(pm, newDevDeps, true);
|
|
697
|
+
await execa2(command, args, { cwd });
|
|
525
698
|
}
|
|
526
|
-
depsSpinner.succeed(
|
|
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("--
|
|
722
|
+
print.hint(`Use ${chalk3.cyan("--overwrite")} to replace them.`);
|
|
550
723
|
}
|
|
551
724
|
print.newline();
|
|
552
725
|
print.success();
|
|
@@ -630,14 +803,296 @@ async function list(options) {
|
|
|
630
803
|
}
|
|
631
804
|
}
|
|
632
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
|
+
|
|
633
1088
|
// src/index.ts
|
|
634
1089
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
635
1090
|
var pkg = JSON.parse(
|
|
636
1091
|
readFileSync(join(__dirname, "../package.json"), "utf-8")
|
|
637
1092
|
);
|
|
638
|
-
var amber =
|
|
639
|
-
var logo2 = `${amber("\u{1F43B}")} ${
|
|
640
|
-
var link = (text, url) => `\x1B]8;;${url}\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`;
|
|
641
1096
|
var program = new Command();
|
|
642
1097
|
program.name("bearnie").description("UI components for Astro").version(pkg.version).configureOutput({
|
|
643
1098
|
writeOut: (str) => process.stdout.write(str),
|
|
@@ -647,7 +1102,7 @@ program.name("bearnie").description("UI components for Astro").version(pkg.versi
|
|
|
647
1102
|
${logo2}
|
|
648
1103
|
|
|
649
1104
|
`);
|
|
650
|
-
write(` ${
|
|
1105
|
+
write(` ${chalk7.red("Oops!")} ${str.replace("error: ", "")}
|
|
651
1106
|
`);
|
|
652
1107
|
}
|
|
653
1108
|
}).addHelpText("beforeAll", `
|
|
@@ -655,40 +1110,45 @@ program.name("bearnie").description("UI components for Astro").version(pkg.versi
|
|
|
655
1110
|
`).addHelpText(
|
|
656
1111
|
"afterAll",
|
|
657
1112
|
`
|
|
658
|
-
${
|
|
1113
|
+
${chalk7.dim("Made with")} ${amber("\u{1F43B}")} ${chalk7.dim("by")} ${link("Michael Andreuzza", "https://michaelandreuzza.com")}
|
|
659
1114
|
`
|
|
660
1115
|
);
|
|
661
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);
|
|
662
|
-
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);
|
|
663
1118
|
program.command("list").description("Browse available components").option("--json", "Output as JSON").action(list);
|
|
664
|
-
program.
|
|
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
|
+
}
|
|
665
1132
|
console.log(`
|
|
666
1133
|
${logo2}
|
|
667
1134
|
|
|
668
1135
|
${amber("Hey!")} UI components for Astro.
|
|
669
1136
|
Built with Tailwind CSS, no frameworks required.
|
|
670
1137
|
|
|
671
|
-
${
|
|
672
|
-
${
|
|
673
|
-
${
|
|
674
|
-
${
|
|
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
|
|
675
1144
|
|
|
676
|
-
${
|
|
677
|
-
${
|
|
678
|
-
${
|
|
679
|
-
${
|
|
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
|
|
680
1150
|
|
|
681
|
-
${
|
|
1151
|
+
${chalk7.dim("Run")} ${chalk7.cyan("bearnie <command> --help")} ${chalk7.dim("for more info")}
|
|
682
1152
|
`);
|
|
683
1153
|
});
|
|
684
1154
|
program.parse();
|
|
685
|
-
program.on("command:*", () => {
|
|
686
|
-
console.log(`
|
|
687
|
-
${logo2}
|
|
688
|
-
|
|
689
|
-
${chalk5.yellow("Hmm,")} I don't know that command: ${chalk5.red(program.args.join(" "))}
|
|
690
|
-
|
|
691
|
-
Run ${chalk5.cyan("npx bearnie --help")} to see what I can do.
|
|
692
|
-
`);
|
|
693
|
-
process.exit(1);
|
|
694
|
-
});
|
|
@@ -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
|
+
};
|
package/dist/utils/registry.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export interface RegistryIndex {
|
|
|
24
24
|
description: string;
|
|
25
25
|
category: string;
|
|
26
26
|
}[];
|
|
27
|
+
utilities?: {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}[];
|
|
27
31
|
}
|
|
28
32
|
export declare function getRegistryIndex(): Promise<RegistryIndex>;
|
|
29
33
|
export declare function getComponent(name: string): Promise<RegistryComponent>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bearnie",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for installing Bearnie UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,23 +15,23 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"chalk": "^
|
|
19
|
-
"commander": "^
|
|
20
|
-
"
|
|
18
|
+
"chalk": "^6.0.0",
|
|
19
|
+
"commander": "^15.0.0",
|
|
20
|
+
"diff": "^9.0.0",
|
|
21
|
+
"execa": "^10.0.1",
|
|
21
22
|
"fs-extra": "^11.2.0",
|
|
22
|
-
"
|
|
23
|
-
"ora": "^8.0.1",
|
|
23
|
+
"ora": "^9.4.1",
|
|
24
24
|
"prompts": "^2.4.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/fs-extra": "^11.0.4",
|
|
28
|
-
"@types/node": "^
|
|
28
|
+
"@types/node": "^22.10.0",
|
|
29
29
|
"@types/prompts": "^2.4.9",
|
|
30
30
|
"tsup": "^8.1.0",
|
|
31
31
|
"typescript": "^5.7.3"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=22.12.0"
|
|
35
35
|
},
|
|
36
36
|
"author": "Michael Andreuzza",
|
|
37
37
|
"repository": {
|