bosia 0.6.13 → 0.6.14

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.6.13",
3
+ "version": "0.6.14",
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": [
package/src/cli/add.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  mergePkgJson,
11
11
  bunAdd,
12
12
  } from "./registry.ts";
13
+ import { recordComponent, readManifest } from "./manifest.ts";
13
14
 
14
15
  // ─── bun x bosia@latest add <component> ──────────────────
15
16
  // Fetches a component from the GitHub registry (or local registry
@@ -164,9 +165,47 @@ export async function addComponent(name: string, root = false, options?: Install
164
165
  }
165
166
  }
166
167
 
168
+ // Record install in bosia.json manifest.
169
+ recordComponent(cwd, fullPath, {
170
+ files: meta.files,
171
+ ...(Object.keys(meta.npmDeps).length > 0 ? { npmDeps: Object.keys(meta.npmDeps) } : {}),
172
+ ...(meta.dependencies.length > 0 ? { dependencies: meta.dependencies } : {}),
173
+ });
174
+
167
175
  if (root) console.log(`\n✅ ${name} installed at src/lib/components/${fullPath}/`);
168
176
  }
169
177
 
178
+ // ─── bosia add list ───────────────────────────────────────
179
+
180
+ export function runAddList(): void {
181
+ const manifest = readManifest();
182
+ const components = Object.entries(manifest.components);
183
+ const blocks = Object.entries(manifest.blocks);
184
+
185
+ if (components.length === 0 && blocks.length === 0) {
186
+ console.log("No components or blocks installed.");
187
+ console.log("Install one with: bun x bosia@latest add <component>");
188
+ return;
189
+ }
190
+
191
+ if (components.length > 0) {
192
+ console.log(`⬡ Installed components (${components.length}):\n`);
193
+ const w = Math.max(...components.map(([n]) => n.length));
194
+ for (const [name, entry] of components) {
195
+ console.log(` ${name.padEnd(w)} ${entry.installedAt.slice(0, 10)}`);
196
+ }
197
+ }
198
+
199
+ if (blocks.length > 0) {
200
+ if (components.length > 0) console.log("");
201
+ console.log(`⬡ Installed blocks (${blocks.length}):\n`);
202
+ const w = Math.max(...blocks.map(([n]) => n.length));
203
+ for (const [name, entry] of blocks) {
204
+ console.log(` ${name.padEnd(w)} ${entry.installedAt.slice(0, 10)}`);
205
+ }
206
+ }
207
+ }
208
+
170
209
  // ─── Ensure $lib/utils.ts exists ─────────────────────────────
171
210
 
172
211
  const UTILS_CONTENT = `import { twMerge } from "tailwind-merge";
package/src/cli/block.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from "./registry.ts";
12
12
  import { addComponent, initAddRegistry, ensureUtils } from "./add.ts";
13
13
  import { mergeFontImports } from "./fonts.ts";
14
+ import { recordBlock } from "./manifest.ts";
14
15
 
15
16
  // ─── bun x bosia@latest add block <category>/<name> ──────
16
17
  // Installs a composed block into src/lib/blocks/<path>/.
@@ -108,5 +109,19 @@ export async function runAddBlock(
108
109
  }
109
110
  }
110
111
 
112
+ // 5. Record install in bosia.json manifest.
113
+ recordBlock(cwd, name, {
114
+ files: meta.files,
115
+ ...(meta.npmDeps && Object.keys(meta.npmDeps).length > 0
116
+ ? { npmDeps: Object.keys(meta.npmDeps) }
117
+ : {}),
118
+ ...(meta.dependencies && meta.dependencies.length > 0
119
+ ? { dependencies: meta.dependencies }
120
+ : {}),
121
+ ...(meta.fonts && Object.keys(meta.fonts).length > 0
122
+ ? { fonts: Object.keys(meta.fonts) }
123
+ : {}),
124
+ });
125
+
111
126
  console.log(`\n✅ ${name} installed at src/lib/blocks/${name}/`);
112
127
  }
package/src/cli/feat.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  mergePkgJson,
12
12
  bunAdd,
13
13
  } from "./registry.ts";
14
+ import { recordFeature, readManifest } from "./manifest.ts";
14
15
 
15
16
  // ─── bun x bosia@latest feat <feature> [--local] ─────────
16
17
  // Fetches a feature scaffold from the GitHub registry (or local
@@ -268,6 +269,7 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
268
269
 
269
270
  // Apply each file entry per its strategy. Skip entries whose `when` clause doesn't match.
270
271
  const createdDirs = new Set<string>();
272
+ const recordedFiles: { target: string; strategy: string; marker?: string }[] = [];
271
273
  for (const entry of meta.files) {
272
274
  if (entry.when && !whenMatches(entry.when, myOptions)) continue;
273
275
  const dest = join(cwd, entry.target);
@@ -287,6 +289,11 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
287
289
  marker: entry.marker ?? name,
288
290
  skipPrompts: nextOptions.skipPrompts ?? false,
289
291
  });
292
+ recordedFiles.push({
293
+ target: entry.target,
294
+ strategy,
295
+ ...(entry.marker ? { marker: entry.marker } : {}),
296
+ });
290
297
  }
291
298
 
292
299
  // Install npm dependencies
@@ -336,6 +343,32 @@ export async function installFeature(name: string, isRoot: boolean, options?: In
336
343
  }
337
344
  }
338
345
 
346
+ // Record install in bosia.json manifest (overwrites prior entry on re-install).
347
+ recordFeature(cwd, name, {
348
+ ...(Object.keys(myOptions).length > 0 ? { options: myOptions } : {}),
349
+ files: recordedFiles,
350
+ ...(Object.keys(meta.npmDeps).length > 0 ? { npmDeps: Object.keys(meta.npmDeps) } : {}),
351
+ ...(meta.npmDevDeps && Object.keys(meta.npmDevDeps).length > 0
352
+ ? { npmDevDeps: Object.keys(meta.npmDevDeps) }
353
+ : {}),
354
+ ...(meta.envVars && Object.keys(meta.envVars).length > 0
355
+ ? { envVars: Object.keys(meta.envVars) }
356
+ : {}),
357
+ ...((meta.features && meta.features.length > 0) ||
358
+ meta.components.length > 0 ||
359
+ (meta.blocks && meta.blocks.length > 0)
360
+ ? {
361
+ deps: {
362
+ ...(meta.features && meta.features.length > 0
363
+ ? { features: meta.features }
364
+ : {}),
365
+ ...(meta.components.length > 0 ? { components: meta.components } : {}),
366
+ ...(meta.blocks && meta.blocks.length > 0 ? { blocks: meta.blocks } : {}),
367
+ },
368
+ }
369
+ : {}),
370
+ });
371
+
339
372
  if (isRoot) {
340
373
  console.log(`\n✅ Feature "${name}" scaffolded!`);
341
374
  if (meta.description) console.log(` ${meta.description}`);
@@ -496,5 +529,29 @@ function isPlainObject(v: unknown): v is Record<string, unknown> {
496
529
  return typeof v === "object" && v !== null && !Array.isArray(v);
497
530
  }
498
531
 
532
+ // ─── bosia feat list ──────────────────────────────────────
533
+
534
+ export function runFeatList(): void {
535
+ const manifest = readManifest();
536
+ const entries = Object.entries(manifest.features);
537
+ if (entries.length === 0) {
538
+ console.log("No features installed.");
539
+ console.log("Install one with: bun x bosia@latest feat <name>");
540
+ return;
541
+ }
542
+ console.log(`⬡ Installed features (${entries.length}):\n`);
543
+ const nameWidth = Math.max(...entries.map(([n]) => n.length));
544
+ for (const [name, entry] of entries) {
545
+ const opts = entry.options
546
+ ? Object.entries(entry.options)
547
+ .map(([k, v]) => `${k}=${v}`)
548
+ .join(", ")
549
+ : "";
550
+ const date = entry.installedAt.slice(0, 10);
551
+ const optsSuffix = opts ? ` (${opts})` : "";
552
+ console.log(` ${name.padEnd(nameWidth)} ${date}${optsSuffix}`);
553
+ }
554
+ }
555
+
499
556
  // Re-exports for create.ts
500
557
  export { resolveLocalRegistry, type InstallOptions } from "./registry.ts";
package/src/cli/index.ts CHANGED
@@ -73,6 +73,9 @@ async function main() {
73
73
  } else if (sub === "font") {
74
74
  const { runAddFont } = await import("./font.ts");
75
75
  await runAddFont(positional[1], positional[2]);
76
+ } else if (sub === "list") {
77
+ const { runAddList } = await import("./add.ts");
78
+ runAddList();
76
79
  } else {
77
80
  const { runAdd } = await import("./add.ts");
78
81
  await runAdd(positional, flags);
@@ -80,12 +83,17 @@ async function main() {
80
83
  break;
81
84
  }
82
85
  case "feat": {
86
+ const nameIdx = args.findIndex((a) => !a.startsWith("-"));
87
+ const featName = nameIdx === -1 ? undefined : args[nameIdx];
88
+ if (featName === "list") {
89
+ const { runFeatList } = await import("./feat.ts");
90
+ runFeatList();
91
+ break;
92
+ }
83
93
  const { runFeat } = await import("./feat.ts");
84
94
  // First non-flag token is the feature name; everything else flows through to the
85
95
  // feature's own option parser. Global flags (-y, --local) are also accepted here
86
96
  // and get split out inside runFeat.
87
- const nameIdx = args.findIndex((a) => !a.startsWith("-"));
88
- const featName = nameIdx === -1 ? undefined : args[nameIdx];
89
97
  const rest =
90
98
  nameIdx === -1 ? args : [...args.slice(0, nameIdx), ...args.slice(nameIdx + 1)];
91
99
  await runFeat(featName, rest);
@@ -109,9 +117,11 @@ Commands:
109
117
  add block <cat>/<name> Add a composed block from the registry
110
118
  add theme <name> Add a theme (tokens.css) from the registry
111
119
  add font <family> <url> Prepend an @import url(...) for a font family to src/app.css
120
+ add list List installed components and blocks (reads bosia.json)
112
121
  feat [-y] <feature> [feature options...] Add a feature scaffold from the registry [--local]
113
122
  -y / --yes auto-confirms prompts and uses each feature's default option values
114
123
  Feature-specific options (e.g. file-upload's -d) follow the feature name
124
+ feat list List installed features (reads bosia.json)
115
125
 
116
126
  Examples:
117
127
  bun x bosia@latest create my-app
@@ -130,6 +140,8 @@ Examples:
130
140
  bun x bosia@latest add theme editorial
131
141
  bun x bosia@latest add font "Fredoka" "https://fonts.googleapis.com/css2?family=Fredoka:wght@400;700&display=swap"
132
142
  bun x bosia@latest feat login
143
+ bun x bosia feat list
144
+ bun x bosia add list
133
145
  `);
134
146
  break;
135
147
  }
@@ -0,0 +1,101 @@
1
+ import { join } from "path";
2
+ import { readFileSync, writeFileSync, existsSync } from "fs";
3
+
4
+ // ─── bosia.json — committed install manifest ──────────────
5
+ // Records features/components/blocks installed via the CLI so
6
+ // `bosia feat list` and `bosia add list` can introspect project
7
+ // state. Lays the groundwork for uninstall (Phase 2).
8
+
9
+ export const MANIFEST_FILE = "bosia.json";
10
+ const MANIFEST_VERSION = 1;
11
+
12
+ export interface FeatureManifestEntry {
13
+ installedAt: string;
14
+ options?: Record<string, string>;
15
+ files: { target: string; strategy: string; marker?: string }[];
16
+ npmDeps?: string[];
17
+ npmDevDeps?: string[];
18
+ envVars?: string[];
19
+ deps?: {
20
+ features?: string[];
21
+ components?: string[];
22
+ blocks?: string[];
23
+ };
24
+ }
25
+
26
+ export interface ComponentManifestEntry {
27
+ installedAt: string;
28
+ files: string[];
29
+ npmDeps?: string[];
30
+ dependencies?: string[];
31
+ }
32
+
33
+ export interface BlockManifestEntry {
34
+ installedAt: string;
35
+ files: string[];
36
+ npmDeps?: string[];
37
+ dependencies?: string[];
38
+ fonts?: string[];
39
+ }
40
+
41
+ export interface Manifest {
42
+ version: number;
43
+ features: Record<string, FeatureManifestEntry>;
44
+ components: Record<string, ComponentManifestEntry>;
45
+ blocks: Record<string, BlockManifestEntry>;
46
+ }
47
+
48
+ function emptyManifest(): Manifest {
49
+ return { version: MANIFEST_VERSION, features: {}, components: {}, blocks: {} };
50
+ }
51
+
52
+ export function readManifest(cwd: string = process.cwd()): Manifest {
53
+ const path = join(cwd, MANIFEST_FILE);
54
+ if (!existsSync(path)) return emptyManifest();
55
+ try {
56
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as Partial<Manifest>;
57
+ return {
58
+ version: parsed.version ?? MANIFEST_VERSION,
59
+ features: parsed.features ?? {},
60
+ components: parsed.components ?? {},
61
+ blocks: parsed.blocks ?? {},
62
+ };
63
+ } catch {
64
+ return emptyManifest();
65
+ }
66
+ }
67
+
68
+ export function writeManifest(manifest: Manifest, cwd: string = process.cwd()): void {
69
+ const path = join(cwd, MANIFEST_FILE);
70
+ writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
71
+ }
72
+
73
+ export function recordFeature(
74
+ cwd: string,
75
+ name: string,
76
+ entry: Omit<FeatureManifestEntry, "installedAt">,
77
+ ): void {
78
+ const manifest = readManifest(cwd);
79
+ manifest.features[name] = { installedAt: new Date().toISOString(), ...entry };
80
+ writeManifest(manifest, cwd);
81
+ }
82
+
83
+ export function recordComponent(
84
+ cwd: string,
85
+ fullPath: string,
86
+ entry: Omit<ComponentManifestEntry, "installedAt">,
87
+ ): void {
88
+ const manifest = readManifest(cwd);
89
+ manifest.components[fullPath] = { installedAt: new Date().toISOString(), ...entry };
90
+ writeManifest(manifest, cwd);
91
+ }
92
+
93
+ export function recordBlock(
94
+ cwd: string,
95
+ name: string,
96
+ entry: Omit<BlockManifestEntry, "installedAt">,
97
+ ): void {
98
+ const manifest = readManifest(cwd);
99
+ manifest.blocks[name] = { installedAt: new Date().toISOString(), ...entry };
100
+ writeManifest(manifest, cwd);
101
+ }