bosia 0.6.12 → 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 +1 -1
- package/src/cli/add.ts +39 -0
- package/src/cli/block.ts +15 -0
- package/src/cli/feat.ts +57 -0
- package/src/cli/index.ts +14 -2
- package/src/cli/manifest.ts +101 -0
- package/src/core/plugins/inspector/index.ts +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.6.
|
|
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
|
+
}
|
|
@@ -163,7 +163,11 @@ export function inspector(options: InspectorOptions = {}): BosiaPlugin | false {
|
|
|
163
163
|
if (process.env.NODE_ENV === "production") return false;
|
|
164
164
|
const editor = options.editor ?? "code";
|
|
165
165
|
const endpoint = options.endpoint ?? "/__bosia/locate";
|
|
166
|
-
|
|
166
|
+
// Env override lets hosts (e.g. bosapi running the app inside a podman
|
|
167
|
+
// container) point inspector POSTs at an address reachable from inside
|
|
168
|
+
// the sandbox, since the URL baked into bosia.config.ts resolves to the
|
|
169
|
+
// container's own loopback there.
|
|
170
|
+
const aiEndpoint = process.env.BOSIA_INSPECTOR_AI_ENDPOINT?.trim() || options.aiEndpoint;
|
|
167
171
|
const errorsEnabled = options.errorsEnabled !== false;
|
|
168
172
|
|
|
169
173
|
return {
|