create-bearnie 0.1.2 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +33 -3
  2. package/dist/index.js +365 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ****# create-bearnie
1
+ # create-bearnie
2
2
 
3
3
  Create a new Astro project with Bearnie UI components pre-configured.
4
4
 
@@ -12,23 +12,53 @@ npx create-bearnie
12
12
  npm create bearnie my-app
13
13
  ```
14
14
 
15
+ ## Options
16
+
17
+ ### `--full`
18
+
19
+ Include all components from the start:
20
+
21
+ ```bash
22
+ npx create-bearnie my-app --full
23
+ ```
24
+
25
+ This fetches all components from the registry and installs them in your project.
26
+
27
+ ### `--theme`
28
+
29
+ Apply a custom theme from the [theme builder](https://bearnie.dev/create-bearnie-theme):
30
+
31
+ ```bash
32
+ npx create-bearnie my-app --theme="https://bearnie.dev/create-bearnie-theme#..."
33
+ ```
34
+
15
35
  ## What's included
16
36
 
17
37
  - Astro 5 with TypeScript
18
38
  - Tailwind CSS v4 (via `@tailwindcss/vite`)
19
39
  - Bearnie CSS variables and theme
20
40
  - Simple landing page to get started
21
- - Ready for components via `npx @bearnie/cli add`
41
+ - Ready for components via `npx bearnie add`
42
+
43
+ With `--full` flag:
44
+ - All Bearnie UI components
45
+ - Utility functions (`cn`, `focus-trap`)
46
+ - Additional dependencies (`clsx`, `tailwind-merge`)
22
47
 
23
48
  ## After creating your project
24
49
 
25
50
  ```bash
26
51
  cd my-app
27
52
  npm install
28
- npx bearnie add button card dialog
29
53
  npm run dev
30
54
  ```
31
55
 
56
+ Without `--full`, add components as needed:
57
+
58
+ ```bash
59
+ npx bearnie add button card dialog
60
+ ```
61
+
32
62
  ## Learn more
33
63
 
34
64
  - [Bearnie Documentation](https://bearnie.dev/docs)
package/dist/index.js CHANGED
@@ -10,13 +10,290 @@ var __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  var amber = (text) => pc.yellow(text);
11
11
  var logo = `${amber("\u{1F43B}")} ${pc.bold("bearnie")}`;
12
12
  var link = (text, url) => `\x1B]8;;${url}\x07${pc.cyan(text)}\x1B]8;;\x07`;
13
+ function parseThemeArg() {
14
+ const themeArg = process.argv.find((arg) => arg.startsWith("--theme="));
15
+ if (!themeArg) return null;
16
+ return themeArg.split("=")[1];
17
+ }
18
+ function parseFullArg() {
19
+ return process.argv.includes("--full");
20
+ }
21
+ var REGISTRY_URL = "https://bearnie.dev/registry";
22
+ var COMPONENT_NAMES = [
23
+ "accordion",
24
+ "alert",
25
+ "alert-dialog",
26
+ "aspect-ratio",
27
+ "avatar",
28
+ "badge",
29
+ "breadcrumb",
30
+ "button",
31
+ "button-group",
32
+ "card",
33
+ "carousel",
34
+ "checkbox",
35
+ "collapsible",
36
+ "command",
37
+ "context-menu",
38
+ "dialog",
39
+ "dropdown-menu",
40
+ "empty",
41
+ "file-upload",
42
+ "hover-card",
43
+ "input",
44
+ "input-group",
45
+ "input-otp",
46
+ "kbd",
47
+ "label",
48
+ "menubar",
49
+ "pagination",
50
+ "popover",
51
+ "progress",
52
+ "radio",
53
+ "scroll-area",
54
+ "select",
55
+ "separator",
56
+ "sheet",
57
+ "sidebar",
58
+ "skeleton",
59
+ "slider",
60
+ "spinner",
61
+ "stepper",
62
+ "switch",
63
+ "table",
64
+ "tabs",
65
+ "textarea",
66
+ "toast",
67
+ "toggle",
68
+ "tooltip",
69
+ "tree"
70
+ ];
71
+ async function fetchComponent(name) {
72
+ try {
73
+ const response = await fetch(`${REGISTRY_URL}/${name}.json`);
74
+ if (!response.ok) return null;
75
+ return await response.json();
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+ async function fetchUtility(name) {
81
+ try {
82
+ const response = await fetch(`${REGISTRY_URL}/${name}.json`);
83
+ if (!response.ok) return null;
84
+ return await response.json();
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function extractThemeHash(themeUrl) {
90
+ try {
91
+ const hashIndex = themeUrl.indexOf("#");
92
+ if (hashIndex === -1) return null;
93
+ return themeUrl.slice(hashIndex + 1);
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+ function decodeThemeConfig(hash) {
99
+ try {
100
+ const decoded = Buffer.from(hash, "base64").toString("utf-8");
101
+ return JSON.parse(decoded);
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+ function oklch(l, c, h) {
107
+ return `oklch(${l} ${c} ${h})`;
108
+ }
109
+ function generateThemeCSS(config) {
110
+ const radiusRem = config.radius / 16;
111
+ const fontFamily = config.font === "geist" ? "'Geist', sans-serif" : "Inter, sans-serif";
112
+ const neutralChroma = config.neutralHue > 0 ? 0.01 : 0;
113
+ const lightPrimary = config.primaryHue === 0 ? "oklch(0.205 0 0)" : oklch(0.55, 0.2, config.primaryHue);
114
+ const lightPrimaryFg = "oklch(0.985 0 0)";
115
+ const darkPrimary = config.primaryHue === 0 ? "oklch(0.985 0 0)" : oklch(0.7, 0.2, config.primaryHue);
116
+ const darkPrimaryFg = "oklch(0.205 0 0)";
117
+ return `@import "tailwindcss";
118
+ @plugin "@tailwindcss/forms";
119
+
120
+ @theme {
121
+ /* Typography */
122
+ --font-sans: ${fontFamily};
123
+ --font-mono: Geist Mono, monospace;
124
+
125
+ /* Border Radius - Base: ${radiusRem}rem */
126
+ --radius-sm: calc(var(--radius) - 4px);
127
+ --radius-md: calc(var(--radius) - 2px);
128
+ --radius-lg: var(--radius);
129
+ --radius-xl: calc(var(--radius) + 4px);
130
+ --radius-2xl: calc(var(--radius) + 8px);
131
+ --radius-full: 9999px;
132
+
133
+ /* Shadows */
134
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
135
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
136
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
137
+ }
138
+
139
+ @theme inline {
140
+ /* Semantic Colors */
141
+ --color-background: var(--background);
142
+ --color-foreground: var(--foreground);
143
+ --color-card: var(--card);
144
+ --color-card-foreground: var(--card-foreground);
145
+ --color-popover: var(--popover);
146
+ --color-popover-foreground: var(--popover-foreground);
147
+ --color-primary: var(--primary);
148
+ --color-primary-foreground: var(--primary-foreground);
149
+ --color-secondary: var(--secondary);
150
+ --color-secondary-foreground: var(--secondary-foreground);
151
+ --color-muted: var(--muted);
152
+ --color-muted-foreground: var(--muted-foreground);
153
+ --color-accent: var(--accent);
154
+ --color-accent-foreground: var(--accent-foreground);
155
+ --color-destructive: var(--destructive);
156
+ --color-destructive-foreground: var(--destructive-foreground);
157
+ --color-success: var(--success);
158
+ --color-success-foreground: var(--success-foreground);
159
+ --color-warning: var(--warning);
160
+ --color-warning-foreground: var(--warning-foreground);
161
+ --color-info: var(--info);
162
+ --color-info-foreground: var(--info-foreground);
163
+ --color-border: var(--border);
164
+ --color-input: var(--input);
165
+ --color-ring: var(--ring);
166
+ --color-chart-1: var(--chart-1);
167
+ --color-chart-2: var(--chart-2);
168
+ --color-chart-3: var(--chart-3);
169
+ --color-chart-4: var(--chart-4);
170
+ --color-chart-5: var(--chart-5);
171
+
172
+ /* Sidebar Colors */
173
+ --color-sidebar: var(--sidebar);
174
+ --color-sidebar-foreground: var(--sidebar-foreground);
175
+ --color-sidebar-primary: var(--sidebar-primary);
176
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
177
+ --color-sidebar-accent: var(--sidebar-accent);
178
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
179
+ --color-sidebar-border: var(--sidebar-border);
180
+ --color-sidebar-ring: var(--sidebar-ring);
181
+ }
182
+
183
+ :root {
184
+ --radius: ${radiusRem}rem;
185
+ --background: ${oklch(1, neutralChroma, config.neutralHue)};
186
+ --foreground: ${oklch(0.145, neutralChroma, config.neutralHue)};
187
+ --card: ${oklch(1, neutralChroma, config.neutralHue)};
188
+ --card-foreground: ${oklch(0.145, neutralChroma, config.neutralHue)};
189
+ --popover: ${oklch(1, neutralChroma, config.neutralHue)};
190
+ --popover-foreground: ${oklch(0.145, neutralChroma, config.neutralHue)};
191
+ --primary: ${lightPrimary};
192
+ --primary-foreground: ${lightPrimaryFg};
193
+ --secondary: ${oklch(0.97, neutralChroma, config.neutralHue)};
194
+ --secondary-foreground: ${oklch(0.205, neutralChroma, config.neutralHue)};
195
+ --muted: ${oklch(0.97, neutralChroma, config.neutralHue)};
196
+ --muted-foreground: ${oklch(0.556, neutralChroma, config.neutralHue)};
197
+ --accent: ${oklch(0.97, neutralChroma, config.neutralHue)};
198
+ --accent-foreground: ${oklch(0.205, neutralChroma, config.neutralHue)};
199
+ --destructive: ${oklch(0.577, 0.245, config.destructiveHue)};
200
+ --destructive-foreground: ${oklch(0.577, 0.245, config.destructiveHue)};
201
+ --success: ${oklch(0.564, 0.168, config.successHue)};
202
+ --success-foreground: ${oklch(0.564, 0.168, config.successHue)};
203
+ --warning: ${oklch(0.681, 0.15, config.warningHue)};
204
+ --warning-foreground: ${oklch(0.681, 0.15, config.warningHue)};
205
+ --info: ${oklch(0.609, 0.202, config.infoHue)};
206
+ --info-foreground: ${oklch(0.609, 0.202, config.infoHue)};
207
+ --border: ${oklch(0.922, neutralChroma, config.neutralHue)};
208
+ --input: ${oklch(0.922, neutralChroma, config.neutralHue)};
209
+ --ring: ${oklch(0.708, neutralChroma, config.neutralHue)};
210
+ --chart-1: oklch(0.646 0.222 41.116);
211
+ --chart-2: oklch(0.6 0.118 184.704);
212
+ --chart-3: oklch(0.398 0.07 227.392);
213
+ --chart-4: oklch(0.828 0.189 84.429);
214
+ --chart-5: oklch(0.769 0.188 70.08);
215
+
216
+ /* Sidebar */
217
+ --sidebar: ${oklch(0.985, neutralChroma, config.neutralHue)};
218
+ --sidebar-foreground: ${oklch(0.145, neutralChroma, config.neutralHue)};
219
+ --sidebar-primary: ${lightPrimary};
220
+ --sidebar-primary-foreground: ${lightPrimaryFg};
221
+ --sidebar-accent: ${oklch(0.97, neutralChroma, config.neutralHue)};
222
+ --sidebar-accent-foreground: ${oklch(0.205, neutralChroma, config.neutralHue)};
223
+ --sidebar-border: ${oklch(0.922, neutralChroma, config.neutralHue)};
224
+ --sidebar-ring: ${oklch(0.708, neutralChroma, config.neutralHue)};
225
+ }
226
+
227
+ .dark {
228
+ --background: ${oklch(0.145, neutralChroma, config.neutralHue)};
229
+ --foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
230
+ --card: ${oklch(0.145, neutralChroma, config.neutralHue)};
231
+ --card-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
232
+ --popover: ${oklch(0.145, neutralChroma, config.neutralHue)};
233
+ --popover-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
234
+ --primary: ${darkPrimary};
235
+ --primary-foreground: ${darkPrimaryFg};
236
+ --secondary: ${oklch(0.269, neutralChroma, config.neutralHue)};
237
+ --secondary-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
238
+ --muted: ${oklch(0.269, neutralChroma, config.neutralHue)};
239
+ --muted-foreground: ${oklch(0.708, neutralChroma, config.neutralHue)};
240
+ --accent: ${oklch(0.269, neutralChroma, config.neutralHue)};
241
+ --accent-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
242
+ --destructive: ${oklch(0.65, 0.245, config.destructiveHue)};
243
+ --destructive-foreground: oklch(0.985 0 0);
244
+ --success: ${oklch(0.634, 0.168, config.successHue)};
245
+ --success-foreground: oklch(0.985 0 0);
246
+ --warning: ${oklch(0.785, 0.15, config.warningHue)};
247
+ --warning-foreground: oklch(0.985 0 0);
248
+ --info: ${oklch(0.701, 0.202, config.infoHue)};
249
+ --info-foreground: oklch(0.985 0 0);
250
+ --border: ${oklch(0.269, neutralChroma, config.neutralHue)};
251
+ --input: ${oklch(0.269, neutralChroma, config.neutralHue)};
252
+ --ring: ${oklch(0.439, neutralChroma, config.neutralHue)};
253
+ --chart-1: oklch(0.488 0.243 264.376);
254
+ --chart-2: oklch(0.696 0.17 162.48);
255
+ --chart-3: oklch(0.769 0.188 70.08);
256
+ --chart-4: oklch(0.627 0.265 303.9);
257
+ --chart-5: oklch(0.645 0.246 16.439);
258
+
259
+ /* Sidebar */
260
+ --sidebar: ${oklch(0.205, neutralChroma, config.neutralHue)};
261
+ --sidebar-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
262
+ --sidebar-primary: ${config.primaryHue === 0 ? "oklch(0.488 0.243 264.376)" : oklch(0.7, 0.2, config.primaryHue)};
263
+ --sidebar-primary-foreground: oklch(0.985 0 0);
264
+ --sidebar-accent: ${oklch(0.269, neutralChroma, config.neutralHue)};
265
+ --sidebar-accent-foreground: ${oklch(0.985, neutralChroma, config.neutralHue)};
266
+ --sidebar-border: ${oklch(0.269, neutralChroma, config.neutralHue)};
267
+ --sidebar-ring: ${oklch(0.439, neutralChroma, config.neutralHue)};
268
+ }
269
+
270
+ /* Base styles */
271
+ * {
272
+ border-color: var(--border);
273
+ }
274
+ `;
275
+ }
13
276
  async function main() {
277
+ const themeUrl = parseThemeArg();
278
+ const fullInstall = parseFullArg();
279
+ let themeConfig = null;
280
+ if (themeUrl) {
281
+ const hash = extractThemeHash(themeUrl);
282
+ if (hash) {
283
+ themeConfig = decodeThemeConfig(hash);
284
+ }
285
+ }
14
286
  console.log(`
15
287
  ${logo}
16
288
 
17
289
  ${amber("Hey!")} Let's create your Bearnie project.
290
+ ${themeConfig ? ` ${pc.dim("Using custom theme from URL")}
291
+ ` : ""}${fullInstall ? ` ${pc.dim("Full install: including all components")}
292
+ ` : ""}
18
293
  `);
19
- let projectName = process.argv[2];
294
+ let projectName = process.argv.find(
295
+ (arg, index) => index >= 2 && !arg.startsWith("--")
296
+ );
20
297
  if (!projectName) {
21
298
  const response = await prompts({
22
299
  type: "text",
@@ -38,12 +315,13 @@ async function main() {
38
315
  }
39
316
  projectName = response.projectName;
40
317
  }
41
- const targetDir = path.resolve(process.cwd(), projectName);
318
+ const finalName = projectName;
319
+ const targetDir = path.resolve(process.cwd(), finalName);
42
320
  if (fs.existsSync(targetDir)) {
43
321
  const { overwrite } = await prompts({
44
322
  type: "confirm",
45
323
  name: "overwrite",
46
- message: `Directory ${pc.cyan(projectName)} already exists. Overwrite?`,
324
+ message: `Directory ${pc.cyan(finalName)} already exists. Overwrite?`,
47
325
  initial: false
48
326
  });
49
327
  if (!overwrite) {
@@ -61,8 +339,68 @@ async function main() {
61
339
  await fs.copy(templateDir, targetDir);
62
340
  const pkgPath = path.join(targetDir, "package.json");
63
341
  const pkg = await fs.readJson(pkgPath);
64
- pkg.name = projectName;
342
+ pkg.name = finalName;
65
343
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
344
+ if (themeConfig) {
345
+ const globalCssPath = path.join(targetDir, "src", "styles", "global.css");
346
+ const customCSS = generateThemeCSS(themeConfig);
347
+ await fs.writeFile(globalCssPath, customCSS);
348
+ console.log(` ${pc.green("\u2713")} Applied custom theme`);
349
+ }
350
+ if (fullInstall) {
351
+ console.log(`
352
+ ${pc.dim("Fetching components from registry...")}
353
+ `);
354
+ await fs.ensureDir(path.join(targetDir, "src", "components", "ui"));
355
+ await fs.ensureDir(path.join(targetDir, "src", "utils"));
356
+ const focusTrap = await fetchUtility("focus-trap");
357
+ if (focusTrap?.files) {
358
+ for (const file of focusTrap.files) {
359
+ const filePath = path.join(targetDir, "src", file.name);
360
+ await fs.ensureDir(path.dirname(filePath));
361
+ await fs.writeFile(filePath, file.content);
362
+ }
363
+ console.log(` ${pc.green("\u2713")} Added focus-trap utility`);
364
+ }
365
+ const cnContent = `import { clsx, type ClassValue } from "clsx";
366
+ import { twMerge } from "tailwind-merge";
367
+
368
+ export function cn(...inputs: ClassValue[]) {
369
+ return twMerge(clsx(inputs));
370
+ }
371
+ `;
372
+ await fs.writeFile(path.join(targetDir, "src", "utils", "cn.ts"), cnContent);
373
+ console.log(` ${pc.green("\u2713")} Added cn utility`);
374
+ let installed = 0;
375
+ let failed = 0;
376
+ for (const componentName of COMPONENT_NAMES) {
377
+ const component = await fetchComponent(componentName);
378
+ if (component?.files) {
379
+ for (const file of component.files) {
380
+ const filePath = path.join(targetDir, "src", file.name);
381
+ await fs.ensureDir(path.dirname(filePath));
382
+ await fs.writeFile(filePath, file.content);
383
+ }
384
+ installed++;
385
+ process.stdout.write(`\r ${pc.green("\u2713")} Installed ${installed}/${COMPONENT_NAMES.length} components`);
386
+ } else {
387
+ failed++;
388
+ }
389
+ }
390
+ console.log("");
391
+ if (failed > 0) {
392
+ console.log(` ${pc.yellow("!")} ${failed} components failed to fetch`);
393
+ }
394
+ const pkgPath2 = path.join(targetDir, "package.json");
395
+ const pkg2 = await fs.readJson(pkgPath2);
396
+ pkg2.dependencies = {
397
+ ...pkg2.dependencies,
398
+ "clsx": "^2.1.1",
399
+ "tailwind-merge": "^3.3.0"
400
+ };
401
+ await fs.writeJson(pkgPath2, pkg2, { spaces: 2 });
402
+ console.log(` ${pc.green("\u2713")} Added component dependencies`);
403
+ }
66
404
  await fs.writeFile(
67
405
  path.join(targetDir, ".gitignore"),
68
406
  `# Dependencies
@@ -89,20 +427,41 @@ Thumbs.db
89
427
  `
90
428
  );
91
429
  console.log(` ${pc.green("\u2713")} Created project files`);
92
- console.log(`
430
+ if (fullInstall) {
431
+ console.log(`
432
+ ${pc.green("Done!")} Your Bearnie project is ready with all components.
433
+
434
+ ${pc.bold("Next steps:")}
435
+
436
+ ${pc.dim("1.")} cd ${pc.cyan(finalName)}
437
+ ${pc.dim("2.")} npm install
438
+ ${pc.dim("3.")} npm run dev
439
+
440
+ ${pc.dim("All components are in")} ${pc.cyan("src/components/ui/")}
441
+
442
+ ${pc.dim("Browse components at")} ${link("bearnie.dev/docs/components", "https://bearnie.dev/docs/components")}
443
+
444
+ ${pc.dim("Made by")} ${link("Michael", "https://michaelandreuzza.com")} ${pc.dim("at")} ${link("Lexington Themes", "https://lexingtonthemes.com")}
445
+ `);
446
+ } else {
447
+ console.log(`
93
448
  ${pc.green("Done!")} Your Bearnie project is ready.
94
449
 
95
450
  ${pc.bold("Next steps:")}
96
451
 
97
- ${pc.dim("1.")} cd ${pc.cyan(projectName)}
452
+ ${pc.dim("1.")} cd ${pc.cyan(finalName)}
98
453
  ${pc.dim("2.")} npm install
99
454
  ${pc.dim("3.")} npx bearnie add button card
100
455
  ${pc.dim("4.")} npm run dev
101
456
 
457
+ ${pc.dim("Or use")} ${pc.cyan("--full")} ${pc.dim("to include all components:")}
458
+ npx create-bearnie my-app --full
459
+
102
460
  ${pc.dim("Browse components at")} ${link("bearnie.dev/docs/components", "https://bearnie.dev/docs/components")}
103
461
 
104
462
  ${pc.dim("Made by")} ${link("Michael", "https://michaelandreuzza.com")} ${pc.dim("at")} ${link("Lexington Themes", "https://lexingtonthemes.com")}
105
463
  `);
464
+ }
106
465
  }
107
466
  main().catch((err) => {
108
467
  console.error(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bearnie",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Create a new Astro project with Bearnie UI components",
5
5
  "type": "module",
6
6
  "license": "MIT",