bosia 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
@@ -44,6 +44,7 @@
44
44
  "typescript": "^5"
45
45
  },
46
46
  "dependencies": {
47
+ "@clack/prompts": "^1.1.0",
47
48
  "@tailwindcss/cli": "^4.2.1",
48
49
  "bun-plugin-svelte": "^0.0.6",
49
50
  "clsx": "^2.1.1",
package/src/cli/add.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { join, dirname } from "path";
2
- import { mkdirSync, writeFileSync } from "fs";
2
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
3
3
  import { spawn } from "bun";
4
4
 
5
5
  // ─── bosia add <component> ────────────────────────────────
6
- // Fetches a component from the GitHub registry and copies it
7
- // into the user's src/lib/components/ui/<name>/ directory.
6
+ // Fetches a component from the GitHub registry (or local registry
7
+ // with --local) and copies it into src/lib/components/ui/<name>/.
8
8
 
9
- const REGISTRY_BASE = "https://raw.githubusercontent.com/bosapi/bosia/main/registry";
9
+ const REMOTE_BASE = "https://raw.githubusercontent.com/bosapi/bosia/main/registry";
10
10
 
11
11
  interface ComponentMeta {
12
12
  name: string;
@@ -19,11 +19,22 @@ interface ComponentMeta {
19
19
  // Track already-installed components within a session to avoid re-running deps
20
20
  const installed = new Set<string>();
21
21
 
22
- export async function runAdd(name: string | undefined) {
22
+ // Resolved once in runAdd, used by addComponent
23
+ let registryRoot: string | null = null;
24
+
25
+ export async function runAdd(name: string | undefined, flags: string[] = []) {
23
26
  if (!name) {
24
- console.error("❌ Please provide a component name.\n Usage: bosia add <component>");
27
+ console.error("❌ Please provide a component name.\n Usage: bosia add <component> [--local]");
25
28
  process.exit(1);
26
29
  }
30
+
31
+ if (flags.includes("--local")) {
32
+ // Walk up from this file to find the repo's registry/ directory
33
+ registryRoot = resolveLocalRegistry();
34
+ console.log(`⬡ Using local registry: ${registryRoot}\n`);
35
+ }
36
+
37
+ ensureUtils();
27
38
  await addComponent(name, true);
28
39
  }
29
40
 
@@ -33,19 +44,19 @@ export async function addComponent(name: string, root = false) {
33
44
 
34
45
  console.log(root ? `⬡ Installing component: ${name}\n` : ` 📦 Dependency: ${name}`);
35
46
 
36
- const meta = await fetchJSON<ComponentMeta>(`${REGISTRY_BASE}/components/${name}/meta.json`);
47
+ const meta = await readMeta(name);
37
48
 
38
49
  // Install component dependencies first (recursive)
39
50
  for (const dep of meta.dependencies) {
40
51
  await addComponent(dep, false);
41
52
  }
42
53
 
43
- // Download component files into src/lib/components/ui/<name>/
54
+ // Download/copy component files into src/lib/components/ui/<name>/
44
55
  const destDir = join(process.cwd(), "src", "lib", "components", "ui", name);
45
56
  mkdirSync(destDir, { recursive: true });
46
57
 
47
58
  for (const file of meta.files) {
48
- const content = await fetchText(`${REGISTRY_BASE}/components/${name}/${file}`);
59
+ const content = await readFile(name, file);
49
60
  const dest = join(destDir, file);
50
61
  mkdirSync(dirname(dest), { recursive: true });
51
62
  writeFileSync(dest, content, "utf-8");
@@ -70,6 +81,63 @@ export async function addComponent(name: string, root = false) {
70
81
  if (root) console.log(`\n✅ ${name} installed at src/lib/components/ui/${name}/`);
71
82
  }
72
83
 
84
+ // ─── Ensure $lib/utils.ts exists ─────────────────────────────
85
+
86
+ const UTILS_CONTENT = `import { clsx, type ClassValue } from "clsx";
87
+ import { twMerge } from "tailwind-merge";
88
+
89
+ export function cn(...inputs: ClassValue[]) {
90
+ return twMerge(clsx(inputs));
91
+ }
92
+ `;
93
+
94
+ function ensureUtils() {
95
+ const utilsPath = join(process.cwd(), "src", "lib", "utils.ts");
96
+ if (!existsSync(utilsPath)) {
97
+ mkdirSync(dirname(utilsPath), { recursive: true });
98
+ writeFileSync(utilsPath, UTILS_CONTENT, "utf-8");
99
+ console.log(" ✍️ src/lib/utils.ts (cn utility)\n");
100
+ }
101
+ }
102
+
103
+ // ─── Registry resolvers ──────────────────────────────────────
104
+
105
+ function resolveLocalRegistry(): string {
106
+ // Walk up from this file's directory to find registry/
107
+ let dir = dirname(new URL(import.meta.url).pathname);
108
+ for (let i = 0; i < 10; i++) {
109
+ const candidate = join(dir, "registry");
110
+ if (existsSync(join(candidate, "index.json"))) return candidate;
111
+ const parent = dirname(dir);
112
+ if (parent === dir) break;
113
+ dir = parent;
114
+ }
115
+ console.error("❌ Could not find local registry/ directory.");
116
+ process.exit(1);
117
+ }
118
+
119
+ async function readMeta(name: string): Promise<ComponentMeta> {
120
+ if (registryRoot) {
121
+ const path = join(registryRoot, "components", name, "meta.json");
122
+ if (!existsSync(path)) {
123
+ throw new Error(`Component "${name}" not found in local registry`);
124
+ }
125
+ return JSON.parse(readFileSync(path, "utf-8"));
126
+ }
127
+ return fetchJSON<ComponentMeta>(`${REMOTE_BASE}/components/${name}/meta.json`);
128
+ }
129
+
130
+ async function readFile(name: string, file: string): Promise<string> {
131
+ if (registryRoot) {
132
+ const path = join(registryRoot, "components", name, file);
133
+ if (!existsSync(path)) {
134
+ throw new Error(`File "${file}" not found for component "${name}" in local registry`);
135
+ }
136
+ return readFileSync(path, "utf-8");
137
+ }
138
+ return fetchText(`${REMOTE_BASE}/components/${name}/${file}`);
139
+ }
140
+
73
141
  async function fetchJSON<T>(url: string): Promise<T> {
74
142
  const res = await fetch(url);
75
143
  if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
package/src/cli/create.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { resolve, join, basename } from "path";
2
- import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
3
3
  import { spawn } from "bun";
4
- import * as readline from "readline";
4
+ import * as p from "@clack/prompts";
5
5
 
6
6
  // ─── bosia create <name> [--template <name>] ──────────────
7
7
 
@@ -79,26 +79,21 @@ async function promptTemplate(): Promise<string> {
79
79
 
80
80
  if (templates.length === 1) return templates[0];
81
81
 
82
- console.log("\n? Which template?\n");
83
- templates.forEach((t, i) => {
84
- const desc = TEMPLATE_DESCRIPTIONS[t] ?? "";
85
- const marker = i === 0 ? "❯" : " ";
86
- console.log(` ${marker} ${t}${desc ? ` — ${desc}` : ""}`);
82
+ const selected = await p.select({
83
+ message: "Which template?",
84
+ options: templates.map((t) => ({
85
+ value: t,
86
+ label: t,
87
+ hint: TEMPLATE_DESCRIPTIONS[t],
88
+ })),
87
89
  });
88
- console.log();
89
90
 
90
- const rl = readline.createInterface({
91
- input: process.stdin,
92
- output: process.stdout,
93
- });
91
+ if (p.isCancel(selected)) {
92
+ p.cancel("Operation cancelled.");
93
+ process.exit(0);
94
+ }
94
95
 
95
- return new Promise<string>((resolvePromise) => {
96
- rl.question(` Template name (default): `, (answer) => {
97
- rl.close();
98
- const trimmed = answer.trim();
99
- resolvePromise(trimmed || "default");
100
- });
101
- });
96
+ return selected as string;
102
97
  }
103
98
 
104
99
  function copyDir(src: string, dest: string, projectName: string) {
package/src/cli/index.ts CHANGED
@@ -33,7 +33,9 @@ async function main() {
33
33
  }
34
34
  case "add": {
35
35
  const { runAdd } = await import("./add.ts");
36
- await runAdd(args[0]);
36
+ const addName = args.find((a) => !a.startsWith("--"));
37
+ const addFlags = args.filter((a) => a.startsWith("--"));
38
+ await runAdd(addName, addFlags);
37
39
  break;
38
40
  }
39
41
  case "feat": {