bosia 0.6.19 → 0.6.21
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 +2 -1
- package/src/cli/addRouter.ts +53 -0
- package/src/cli/block.ts +3 -2
- package/src/cli/index.ts +24 -21
- package/src/cli/registry.ts +37 -1
- package/src/core/build.ts +10 -0
- package/src/core/client/page.svelte.ts +28 -0
- package/src/lib/client.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosia",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.21",
|
|
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
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
resolveLocalRegistryOrExit,
|
|
8
8
|
readRegistryJSON,
|
|
9
9
|
readRegistryFile,
|
|
10
|
+
writeRegistryFile,
|
|
10
11
|
mergePkgJson,
|
|
11
12
|
bunAdd,
|
|
12
13
|
} from "./registry.ts";
|
|
@@ -150,7 +151,7 @@ export async function addComponent(name: string, root = false, options?: Install
|
|
|
150
151
|
const content = await readRegistryFile(registryRoot, "components", fullPath, file);
|
|
151
152
|
const dest = join(destDir, file);
|
|
152
153
|
if (file.includes("/")) mkdirSync(dirname(dest), { recursive: true });
|
|
153
|
-
|
|
154
|
+
writeRegistryFile(dest, content);
|
|
154
155
|
console.log(` ✍️ src/lib/components/${fullPath}/${file}`);
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// ─── Dispatch logic for `bosia add ...` ──────────────────
|
|
2
|
+
// Split out from index.ts so the routing can be unit-tested with injected
|
|
3
|
+
// runners (index.ts does top-level `process.argv` parsing on import).
|
|
4
|
+
|
|
5
|
+
export interface AddRunners {
|
|
6
|
+
runAdd: (names: string[], flags: string[]) => Promise<void> | void;
|
|
7
|
+
runAddBlock: (name: string | undefined, flags: string[]) => Promise<void> | void;
|
|
8
|
+
runAddTheme: (name: string | undefined, flags: string[]) => Promise<void> | void;
|
|
9
|
+
runAddFont: (family: string | undefined, url: string | undefined) => Promise<void> | void;
|
|
10
|
+
runAddList: () => Promise<void> | void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function routeAdd(args: string[], runners: AddRunners): Promise<void> {
|
|
14
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
15
|
+
const flags = args.filter((a) => a.startsWith("-"));
|
|
16
|
+
const sub = positional[0];
|
|
17
|
+
|
|
18
|
+
if (sub === "block") {
|
|
19
|
+
await runners.runAddBlock(positional[1], flags);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (sub === "theme") {
|
|
23
|
+
const themeFlags = args.filter((a) => a.startsWith("--"));
|
|
24
|
+
await runners.runAddTheme(positional[1], themeFlags);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (sub === "font") {
|
|
28
|
+
await runners.runAddFont(positional[1], positional[2]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (sub === "list") {
|
|
32
|
+
await runners.runAddList();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Alias: `blocks/<cat>/<name>` tokens dispatch to runAddBlock.
|
|
37
|
+
// Skills/AI agents frequently emit the plural `blocks/...` form alongside
|
|
38
|
+
// `ui/*` components; route those to the block installer transparently
|
|
39
|
+
// and let any remaining plain component names fall through to runAdd.
|
|
40
|
+
const blockTokens = positional.filter((p) => p.startsWith("blocks/"));
|
|
41
|
+
const componentTokens = positional.filter((p) => !p.startsWith("blocks/"));
|
|
42
|
+
if (blockTokens.length > 0) {
|
|
43
|
+
if (componentTokens.length > 0) {
|
|
44
|
+
await runners.runAdd(componentTokens, flags);
|
|
45
|
+
}
|
|
46
|
+
for (const token of blockTokens) {
|
|
47
|
+
await runners.runAddBlock(token.slice("blocks/".length), flags);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await runners.runAdd(positional, flags);
|
|
53
|
+
}
|
package/src/cli/block.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { join, dirname } from "path";
|
|
2
|
-
import { mkdirSync,
|
|
2
|
+
import { mkdirSync, existsSync } from "fs";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import {
|
|
5
5
|
type InstallOptions,
|
|
6
6
|
resolveLocalRegistryOrExit,
|
|
7
7
|
readRegistryJSON,
|
|
8
8
|
readRegistryFile,
|
|
9
|
+
writeRegistryFile,
|
|
9
10
|
mergePkgJson,
|
|
10
11
|
bunAdd,
|
|
11
12
|
} from "./registry.ts";
|
|
@@ -83,7 +84,7 @@ export async function runAddBlock(
|
|
|
83
84
|
const content = await readRegistryFile(registryRoot, "blocks", name, file);
|
|
84
85
|
const dest = join(destDir, file);
|
|
85
86
|
if (file.includes("/")) mkdirSync(dirname(dest), { recursive: true });
|
|
86
|
-
|
|
87
|
+
writeRegistryFile(dest, content);
|
|
87
88
|
console.log(` ✍️ src/lib/blocks/${name}/${file}`);
|
|
88
89
|
}
|
|
89
90
|
|
package/src/cli/index.ts
CHANGED
|
@@ -59,27 +59,29 @@ async function main() {
|
|
|
59
59
|
break;
|
|
60
60
|
}
|
|
61
61
|
case "add": {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
const { routeAdd } = await import("./addRouter.ts");
|
|
63
|
+
await routeAdd(args, {
|
|
64
|
+
runAdd: async (names, flags) => {
|
|
65
|
+
const { runAdd } = await import("./add.ts");
|
|
66
|
+
await runAdd(names, flags);
|
|
67
|
+
},
|
|
68
|
+
runAddBlock: async (name, flags) => {
|
|
69
|
+
const { runAddBlock } = await import("./block.ts");
|
|
70
|
+
await runAddBlock(name, flags);
|
|
71
|
+
},
|
|
72
|
+
runAddTheme: async (name, flags) => {
|
|
73
|
+
const { runAddTheme } = await import("./theme.ts");
|
|
74
|
+
await runAddTheme(name, flags);
|
|
75
|
+
},
|
|
76
|
+
runAddFont: async (family, url) => {
|
|
77
|
+
const { runAddFont } = await import("./font.ts");
|
|
78
|
+
await runAddFont(family, url);
|
|
79
|
+
},
|
|
80
|
+
runAddList: async () => {
|
|
81
|
+
const { runAddList } = await import("./add.ts");
|
|
82
|
+
runAddList();
|
|
83
|
+
},
|
|
84
|
+
});
|
|
83
85
|
break;
|
|
84
86
|
}
|
|
85
87
|
case "feat": {
|
|
@@ -137,6 +139,7 @@ Examples:
|
|
|
137
139
|
bun x bosia@latest add -y button card → auto-confirm overwrites (CI / scripts)
|
|
138
140
|
bun x bosia@latest add shop/cart → src/lib/components/shop/cart/
|
|
139
141
|
bun x bosia@latest add block cards/feature-editorial
|
|
142
|
+
bun x bosia@latest add blocks/cards/feature-editorial (alias for: add block cards/feature-editorial)
|
|
140
143
|
bun x bosia@latest add theme editorial
|
|
141
144
|
bun x bosia@latest add font "Fredoka" "https://fonts.googleapis.com/css2?family=Fredoka:wght@400;700&display=swap"
|
|
142
145
|
bun x bosia@latest feat login
|
package/src/cli/registry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join, dirname } from "path";
|
|
2
|
-
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
2
|
+
import { writeFileSync, readFileSync, existsSync, unlinkSync } from "fs";
|
|
3
3
|
import { spawn } from "bun";
|
|
4
4
|
|
|
5
5
|
// ─── Shared registry utilities for feat.ts and add.ts ─────
|
|
@@ -80,6 +80,42 @@ export async function readRegistryFile(
|
|
|
80
80
|
return fetchText(`${REGISTRY_URL}/${category}/${name}/${file}`);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// ─── Registry file writer ─────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Write a registry file with one EACCES/EPERM recovery attempt.
|
|
87
|
+
*
|
|
88
|
+
* Component/block file loops abort mid-install when a target path is owned by a
|
|
89
|
+
* different uid — bosapi tenant apps run sandboxed as `bosapi-app-N`, so a
|
|
90
|
+
* subsequent install from the bosapi user hits EACCES on the first foreign-owned
|
|
91
|
+
* file and leaves a partial install behind. Unlink + retry recovers; only the
|
|
92
|
+
* unrecoverable case surfaces the chown hint.
|
|
93
|
+
*/
|
|
94
|
+
export function writeRegistryFile(dest: string, content: string): void {
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(dest, content, "utf-8");
|
|
97
|
+
return;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
100
|
+
if (code !== "EACCES" && code !== "EPERM") throw err;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
unlinkSync(dest);
|
|
104
|
+
} catch {
|
|
105
|
+
// ignore — retry will surface the real error
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
writeFileSync(dest, content, "utf-8");
|
|
109
|
+
} catch (retry) {
|
|
110
|
+
const e = retry as NodeJS.ErrnoException;
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Cannot write ${dest}: ${e.code}. ` +
|
|
113
|
+
`The existing file is owned by a different user (likely created from inside ` +
|
|
114
|
+
`the app sandbox). Fix from the project root: chown -R $(whoami) src/lib`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
83
119
|
// ─── package.json helpers ─────────────────────────────────
|
|
84
120
|
|
|
85
121
|
export interface PkgDeps {
|
package/src/core/build.ts
CHANGED
|
@@ -222,10 +222,19 @@ if (!isProduction && svelteMapCache.size > 0) {
|
|
|
222
222
|
// 6. Collect output files for dist/manifest.json
|
|
223
223
|
const jsFiles: string[] = [];
|
|
224
224
|
const cssFiles: string[] = [];
|
|
225
|
+
// 0.6.19 hashed the entry filename; the old `f === "hydrate.js"` exact-match
|
|
226
|
+
// no longer hits, and the fallback `startsWith("hydrate")` picks the first
|
|
227
|
+
// hydrate-* chunk by array order — which can be a small leaf module (e.g.
|
|
228
|
+
// `src/lib/version.ts`) that sorts before the real entry. Use Bun's
|
|
229
|
+
// `output.kind === "entry-point"` instead so we pin the actual entry.
|
|
230
|
+
let clientEntry: string | null = null;
|
|
225
231
|
for (const output of clientResult.outputs) {
|
|
226
232
|
const rel = relative(`${OUT_DIR}/client`, output.path);
|
|
227
233
|
if (output.path.endsWith(".js")) jsFiles.push(rel);
|
|
228
234
|
if (output.path.endsWith(".css")) cssFiles.push(rel);
|
|
235
|
+
if ((output as { kind?: string }).kind === "entry-point" && output.path.endsWith(".js")) {
|
|
236
|
+
clientEntry = rel;
|
|
237
|
+
}
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
// Entry is always "index.js" due to naming: { entry: "index.[ext]" }
|
|
@@ -241,6 +250,7 @@ const distManifest = {
|
|
|
241
250
|
js: jsFiles,
|
|
242
251
|
css: cssFiles,
|
|
243
252
|
entry:
|
|
253
|
+
clientEntry ??
|
|
244
254
|
jsFiles.find((f) => f === "hydrate.js") ??
|
|
245
255
|
jsFiles.find((f) => f.startsWith("hydrate")) ??
|
|
246
256
|
"hydrate.js",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ─── Reactive page object ─────────────────────────────────
|
|
2
|
+
// Mirrors what user-facing skills (bosia-page-shell, bosia-seo,
|
|
3
|
+
// bosia-navigation) teach: `import { page } from "bosia/client"` then read
|
|
4
|
+
// `page.url.pathname`. Backed by `router.currentRoute` (`$state` in
|
|
5
|
+
// router.svelte.ts), so the `$derived` URL re-runs on every nav.
|
|
6
|
+
//
|
|
7
|
+
// No `params` getter on purpose — Bosia already passes `params` as a prop to
|
|
8
|
+
// `+page.svelte` / `+layout.svelte` (see App.svelte), mirroring the modern
|
|
9
|
+
// SvelteKit `$app/state` direction. Route components should destructure
|
|
10
|
+
// `params` from `$props()`, not from here.
|
|
11
|
+
|
|
12
|
+
import { router } from "./router.svelte.ts";
|
|
13
|
+
|
|
14
|
+
class Page {
|
|
15
|
+
#url = $derived.by(() => {
|
|
16
|
+
if (typeof window === "undefined") return new URL("http://localhost/");
|
|
17
|
+
return new URL(router.currentRoute, window.location.origin);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
get url() {
|
|
21
|
+
return this.#url;
|
|
22
|
+
}
|
|
23
|
+
get pathname() {
|
|
24
|
+
return this.#url.pathname;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const page = new Page();
|
package/src/lib/client.ts
CHANGED