bosbun 0.0.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.
Files changed (46) hide show
  1. package/README.md +163 -0
  2. package/package.json +56 -0
  3. package/src/cli/add.ts +83 -0
  4. package/src/cli/build.ts +16 -0
  5. package/src/cli/create.ts +54 -0
  6. package/src/cli/dev.ts +14 -0
  7. package/src/cli/feat.ts +80 -0
  8. package/src/cli/index.ts +75 -0
  9. package/src/cli/start.ts +28 -0
  10. package/src/core/build.ts +157 -0
  11. package/src/core/client/App.svelte +147 -0
  12. package/src/core/client/hydrate.ts +78 -0
  13. package/src/core/client/router.svelte.ts +46 -0
  14. package/src/core/cookies.ts +52 -0
  15. package/src/core/cors.ts +60 -0
  16. package/src/core/csrf.ts +65 -0
  17. package/src/core/dev.ts +193 -0
  18. package/src/core/env.ts +135 -0
  19. package/src/core/envCodegen.ts +94 -0
  20. package/src/core/errors.ts +23 -0
  21. package/src/core/hooks.ts +74 -0
  22. package/src/core/html.ts +170 -0
  23. package/src/core/matcher.ts +85 -0
  24. package/src/core/plugin.ts +59 -0
  25. package/src/core/prerender.ts +79 -0
  26. package/src/core/renderer.ts +222 -0
  27. package/src/core/routeFile.ts +88 -0
  28. package/src/core/routeTypes.ts +95 -0
  29. package/src/core/scanner.ts +99 -0
  30. package/src/core/server.ts +320 -0
  31. package/src/core/types.ts +37 -0
  32. package/src/lib/index.ts +19 -0
  33. package/src/lib/utils.ts +24 -0
  34. package/templates/default/.env.example +34 -0
  35. package/templates/default/README.md +102 -0
  36. package/templates/default/package.json +21 -0
  37. package/templates/default/public/.gitkeep +0 -0
  38. package/templates/default/src/app.css +132 -0
  39. package/templates/default/src/app.d.ts +7 -0
  40. package/templates/default/src/lib/.gitkeep +0 -0
  41. package/templates/default/src/routes/+error.svelte +18 -0
  42. package/templates/default/src/routes/+layout.svelte +6 -0
  43. package/templates/default/src/routes/+page.svelte +36 -0
  44. package/templates/default/src/routes/about/+page.server.ts +1 -0
  45. package/templates/default/src/routes/about/+page.svelte +8 -0
  46. package/templates/default/tsconfig.json +22 -0
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # bunia
2
+
3
+ The `bunia` package — framework core + CLI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add bunia
9
+ ```
10
+
11
+ Or scaffold a new project:
12
+
13
+ ```bash
14
+ bunx bunia create my-app
15
+ ```
16
+
17
+ ## CLI
18
+
19
+ ```
20
+ bunia <command>
21
+
22
+ Commands:
23
+ create <name> Scaffold a new Bunia project
24
+ dev Start the development server (port 3000)
25
+ build Build for production
26
+ start Run the production server
27
+ add <component> Install a UI component from the registry
28
+ feat <feature> Install a feature scaffold from the registry
29
+ ```
30
+
31
+ ## Routing Conventions
32
+
33
+ Files in `src/routes/` map to URLs automatically.
34
+
35
+ | File | Purpose |
36
+ |------|---------|
37
+ | `+page.svelte` | Page component |
38
+ | `+layout.svelte` | Layout that wraps child pages |
39
+ | `+page.server.ts` | Server loader for a page |
40
+ | `+layout.server.ts` | Server loader for a layout |
41
+ | `+server.ts` | API endpoint (export HTTP verbs) |
42
+
43
+ ### Dynamic Routes
44
+
45
+ | Pattern | Matches |
46
+ |---------|---------|
47
+ | `[param]` | `/blog/hello` → `params.param = "hello"` |
48
+ | `[...rest]` | `/a/b/c` → `params.rest = "a/b/c"` |
49
+
50
+ ### Route Groups
51
+
52
+ Wrap a directory in parentheses to share a layout without affecting the URL:
53
+
54
+ ```
55
+ src/routes/
56
+ └── (marketing)/
57
+ ├── +layout.svelte # shared layout
58
+ ├── +page.svelte # /
59
+ └── about/
60
+ └── +page.svelte # /about
61
+ ```
62
+
63
+ ## Server Loaders
64
+
65
+ ```typescript
66
+ // src/routes/blog/[slug]/+page.server.ts
67
+ import type { LoadEvent } from "bunia";
68
+
69
+ export async function load({ params, url, locals, fetch, parent }: LoadEvent) {
70
+ const parentData = await parent(); // data from layout loaders above
71
+ return {
72
+ post: await getPost(params.slug),
73
+ };
74
+ }
75
+ ```
76
+
77
+ Data returned is passed as the `data` prop to `+page.svelte`:
78
+
79
+ ```svelte
80
+ <script lang="ts">
81
+ let { data } = $props();
82
+ // data.post, data.params ...
83
+ </script>
84
+ ```
85
+
86
+ ## API Routes
87
+
88
+ Export named HTTP verb functions from `+server.ts`:
89
+
90
+ ```typescript
91
+ // src/routes/api/items/+server.ts
92
+ import type { RequestEvent } from "bunia";
93
+
94
+ export function GET({ params, url, locals }: RequestEvent) {
95
+ return Response.json({ items: [] });
96
+ }
97
+
98
+ export async function POST({ request }: RequestEvent) {
99
+ const body = await request.json();
100
+ return Response.json({ created: body }, { status: 201 });
101
+ }
102
+ ```
103
+
104
+ ## Middleware Hooks
105
+
106
+ Create `src/hooks.server.ts` to intercept every request:
107
+
108
+ ```typescript
109
+ import { sequence } from "bunia";
110
+ import type { Handle } from "bunia";
111
+
112
+ const authHandle: Handle = async ({ event, resolve }) => {
113
+ event.locals.user = await getUser(event.request);
114
+ return resolve(event);
115
+ };
116
+
117
+ const loggingHandle: Handle = async ({ event, resolve }) => {
118
+ const res = await resolve(event);
119
+ console.log(`${event.request.method} ${event.url.pathname} ${res.status}`);
120
+ return res;
121
+ };
122
+
123
+ export const handle = sequence(authHandle, loggingHandle);
124
+ ```
125
+
126
+ `locals` set here are available in every loader and API handler.
127
+
128
+ ## Public API
129
+
130
+ ```typescript
131
+ import { cn, sequence } from "bunia";
132
+ import type { RequestEvent, LoadEvent, Handle } from "bunia";
133
+ ```
134
+
135
+ | Export | Description |
136
+ |--------|-------------|
137
+ | `cn(...classes)` | Tailwind class merge utility (clsx + tailwind-merge) |
138
+ | `sequence(...handlers)` | Compose multiple `Handle` middleware functions |
139
+ | `RequestEvent` | Type for API route and hook handlers |
140
+ | `LoadEvent` | Type for `load()` in `+page.server.ts` / `+layout.server.ts` |
141
+ | `Handle` | Type for a middleware function in `hooks.server.ts` |
142
+
143
+ ## Path Alias
144
+
145
+ `$lib` maps to `src/lib/` out of the box:
146
+
147
+ ```typescript
148
+ import { myUtil } from "$lib/utils";
149
+ ```
150
+
151
+ ## Project Structure
152
+
153
+ ```
154
+ my-app/
155
+ ├── src/
156
+ │ ├── app.css # Global styles + Tailwind config
157
+ │ ├── hooks.server.ts # Optional request middleware
158
+ │ ├── lib/ # Shared utilities ($lib alias)
159
+ │ └── routes/ # File-based routes
160
+ ├── public/ # Static assets (served as-is)
161
+ ├── dist/ # Build output (git-ignored)
162
+ └── package.json
163
+ ```
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "bosbun",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "A minimalist fullstack framework — SSR + Svelte 5 Runes + Bun + ElysiaJS",
6
+ "keywords": [
7
+ "bun",
8
+ "svelte",
9
+ "ssr",
10
+ "elysia",
11
+ "fullstack",
12
+ "framework"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Jekibus",
17
+ "url": "https://bosapi.com"
18
+ },
19
+ "homepage": "https://github.com/bosapi/bunia#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/bosapi/bunia.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/bosapi/bunia/issues"
26
+ },
27
+ "files": [
28
+ "src",
29
+ "templates",
30
+ "README.md",
31
+ "package.json"
32
+ ],
33
+ "exports": {
34
+ ".": "./src/lib/index.ts"
35
+ },
36
+ "bin": {
37
+ "bunia": "src/cli/index.ts"
38
+ },
39
+ "scripts": {
40
+ "check": "tsc --noEmit"
41
+ },
42
+ "devDependencies": {
43
+ "@types/bun": "latest",
44
+ "typescript": "^5"
45
+ },
46
+ "dependencies": {
47
+ "@elysiajs/static": "^1.4.7",
48
+ "@tailwindcss/cli": "^4.2.1",
49
+ "bun-plugin-svelte": "^0.0.6",
50
+ "clsx": "^2.1.1",
51
+ "elysia": "^1.4.26",
52
+ "svelte": "^5.53.6",
53
+ "tailwind-merge": "^3.5.0",
54
+ "tailwindcss": "^4.2.1"
55
+ }
56
+ }
package/src/cli/add.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { join, dirname } from "path";
2
+ import { mkdirSync, writeFileSync } from "fs";
3
+ import { spawn } from "bun";
4
+
5
+ // ─── bunia add <component> ────────────────────────────────
6
+ // Fetches a component from the GitHub registry and copies it
7
+ // into the user's src/lib/components/ui/<name>/ directory.
8
+
9
+ const REGISTRY_BASE = "https://raw.githubusercontent.com/bosapi/bunia/main/registry";
10
+
11
+ interface ComponentMeta {
12
+ name: string;
13
+ description: string;
14
+ dependencies: string[]; // other bunia components required
15
+ files: string[];
16
+ npmDeps: Record<string, string>;
17
+ }
18
+
19
+ // Track already-installed components within a session to avoid re-running deps
20
+ const installed = new Set<string>();
21
+
22
+ export async function runAdd(name: string | undefined) {
23
+ if (!name) {
24
+ console.error("❌ Please provide a component name.\n Usage: bunia add <component>");
25
+ process.exit(1);
26
+ }
27
+ await addComponent(name, true);
28
+ }
29
+
30
+ export async function addComponent(name: string, root = false) {
31
+ if (installed.has(name)) return;
32
+ installed.add(name);
33
+
34
+ console.log(root ? `🐰 Installing component: ${name}\n` : ` 📦 Dependency: ${name}`);
35
+
36
+ const meta = await fetchJSON<ComponentMeta>(`${REGISTRY_BASE}/components/${name}/meta.json`);
37
+
38
+ // Install component dependencies first (recursive)
39
+ for (const dep of meta.dependencies) {
40
+ await addComponent(dep, false);
41
+ }
42
+
43
+ // Download component files into src/lib/components/ui/<name>/
44
+ const destDir = join(process.cwd(), "src", "lib", "components", "ui", name);
45
+ mkdirSync(destDir, { recursive: true });
46
+
47
+ for (const file of meta.files) {
48
+ const content = await fetchText(`${REGISTRY_BASE}/components/${name}/${file}`);
49
+ const dest = join(destDir, file);
50
+ mkdirSync(dirname(dest), { recursive: true });
51
+ writeFileSync(dest, content, "utf-8");
52
+ console.log(` ✍️ src/lib/components/ui/${name}/${file}`);
53
+ }
54
+
55
+ // Install npm dependencies
56
+ const npmEntries = Object.entries(meta.npmDeps);
57
+ if (npmEntries.length > 0) {
58
+ const packages = npmEntries.map(([pkg, ver]) => (ver ? `${pkg}@${ver}` : pkg));
59
+ console.log(` 📥 npm: ${packages.join(", ")}`);
60
+ const proc = spawn(["bun", "add", ...packages], {
61
+ stdout: "inherit",
62
+ stderr: "inherit",
63
+ cwd: process.cwd(),
64
+ });
65
+ if ((await proc.exited) !== 0) {
66
+ console.warn(` ⚠️ bun add failed for: ${packages.join(", ")}`);
67
+ }
68
+ }
69
+
70
+ if (root) console.log(`\n✅ ${name} installed at src/lib/components/ui/${name}/`);
71
+ }
72
+
73
+ async function fetchJSON<T>(url: string): Promise<T> {
74
+ const res = await fetch(url);
75
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
76
+ return res.json() as Promise<T>;
77
+ }
78
+
79
+ async function fetchText(url: string): Promise<string> {
80
+ const res = await fetch(url);
81
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
82
+ return res.text();
83
+ }
@@ -0,0 +1,16 @@
1
+ import { spawn } from "bun";
2
+ import { resolve } from "path";
3
+ import { loadEnv } from "../core/env.ts";
4
+
5
+ export async function runBuild() {
6
+ loadEnv("production");
7
+ const buildScript = resolve(import.meta.dir, "../core/build.ts");
8
+ const proc = spawn(["bun", "run", buildScript], {
9
+ stdout: "inherit",
10
+ stderr: "inherit",
11
+ cwd: process.cwd(),
12
+ env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "production" },
13
+ });
14
+ const exitCode = await proc.exited;
15
+ if (exitCode !== 0) process.exit(exitCode ?? 1);
16
+ }
@@ -0,0 +1,54 @@
1
+ import { resolve, join, basename } from "path";
2
+ import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync } from "fs";
3
+ import { spawn } from "bun";
4
+
5
+ // ─── bunia create <name> ──────────────────────────────────
6
+
7
+ const TEMPLATE_DIR = resolve(import.meta.dir, "../../templates/default");
8
+
9
+ export async function runCreate(name: string | undefined) {
10
+ if (!name) {
11
+ console.error("❌ Please provide a project name.\n Usage: bunia create my-app");
12
+ process.exit(1);
13
+ }
14
+
15
+ const targetDir = resolve(process.cwd(), name);
16
+
17
+ if (existsSync(targetDir)) {
18
+ console.error(`❌ Directory already exists: ${targetDir}`);
19
+ process.exit(1);
20
+ }
21
+
22
+ console.log(`🐰 Creating Bunia project: ${basename(targetDir)}\n`);
23
+
24
+ copyDir(TEMPLATE_DIR, targetDir, name);
25
+
26
+ console.log(`✅ Project created at ${targetDir}\n`);
27
+
28
+ console.log("Installing dependencies...");
29
+ const proc = spawn(["bun", "install"], {
30
+ stdout: "inherit",
31
+ stderr: "inherit",
32
+ cwd: targetDir,
33
+ });
34
+ const exitCode = await proc.exited;
35
+ if (exitCode !== 0) {
36
+ console.warn("⚠️ bun install failed — run it manually.");
37
+ } else {
38
+ console.log(`\n🎉 Ready!\n\n cd ${name}\n bun x bunia dev\n`);
39
+ }
40
+ }
41
+
42
+ function copyDir(src: string, dest: string, projectName: string) {
43
+ mkdirSync(dest, { recursive: true });
44
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
45
+ const srcPath = join(src, entry.name);
46
+ const destPath = join(dest, entry.name);
47
+ if (entry.isDirectory()) {
48
+ copyDir(srcPath, destPath, projectName);
49
+ } else {
50
+ const content = readFileSync(srcPath, "utf-8").replaceAll("{{PROJECT_NAME}}", projectName);
51
+ writeFileSync(destPath, content, "utf-8");
52
+ }
53
+ }
54
+ }
package/src/cli/dev.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { spawn } from "bun";
2
+ import { resolve } from "path";
3
+ import { loadEnv } from "../core/env.ts";
4
+
5
+ export async function runDev() {
6
+ loadEnv("development");
7
+ const devScript = resolve(import.meta.dir, "../core/dev.ts");
8
+ const proc = spawn(["bun", "run", devScript], {
9
+ stdout: "inherit",
10
+ stderr: "inherit",
11
+ cwd: process.cwd(),
12
+ });
13
+ await proc.exited;
14
+ }
@@ -0,0 +1,80 @@
1
+ import { join, dirname } from "path";
2
+ import { mkdirSync, writeFileSync } from "fs";
3
+ import { spawn } from "bun";
4
+ import { addComponent } from "./add.ts";
5
+
6
+ // ─── bunia feat <feature> ─────────────────────────────────
7
+ // Fetches a feature scaffold from the GitHub registry.
8
+ // Installs required components, copies route/lib files, installs npm deps.
9
+
10
+ const REGISTRY_BASE = "https://raw.githubusercontent.com/bosapi/bunia/main/registry";
11
+
12
+ interface FeatureMeta {
13
+ name: string;
14
+ description: string;
15
+ components: string[]; // bunia components to install via `bunia add`
16
+ files: string[]; // source filenames in the registry feature dir
17
+ targets: string[]; // destination paths relative to project root
18
+ npmDeps: Record<string, string>;
19
+ }
20
+
21
+ export async function runFeat(name: string | undefined) {
22
+ if (!name) {
23
+ console.error("❌ Please provide a feature name.\n Usage: bunia feat <feature>");
24
+ process.exit(1);
25
+ }
26
+
27
+ console.log(`🐰 Installing feature: ${name}\n`);
28
+
29
+ const meta = await fetchJSON<FeatureMeta>(`${REGISTRY_BASE}/features/${name}/meta.json`);
30
+
31
+ // Install required UI components
32
+ if (meta.components.length > 0) {
33
+ console.log("📦 Installing required components...");
34
+ for (const comp of meta.components) {
35
+ await addComponent(comp, false);
36
+ }
37
+ console.log("");
38
+ }
39
+
40
+ // Copy feature files to their target paths
41
+ for (let i = 0; i < meta.files.length; i++) {
42
+ const file = meta.files[i]!;
43
+ const target = meta.targets[i] ?? file;
44
+ const content = await fetchText(`${REGISTRY_BASE}/features/${name}/${file}`);
45
+ const dest = join(process.cwd(), target);
46
+ mkdirSync(dirname(dest), { recursive: true });
47
+ writeFileSync(dest, content, "utf-8");
48
+ console.log(` ✍️ ${target}`);
49
+ }
50
+
51
+ // Install npm dependencies
52
+ const npmEntries = Object.entries(meta.npmDeps);
53
+ if (npmEntries.length > 0) {
54
+ const packages = npmEntries.map(([pkg, ver]) => (ver ? `${pkg}@${ver}` : pkg));
55
+ console.log(`\n📥 npm: ${packages.join(", ")}`);
56
+ const proc = spawn(["bun", "add", ...packages], {
57
+ stdout: "inherit",
58
+ stderr: "inherit",
59
+ cwd: process.cwd(),
60
+ });
61
+ if ((await proc.exited) !== 0) {
62
+ console.warn(`⚠️ bun add failed for: ${packages.join(", ")}`);
63
+ }
64
+ }
65
+
66
+ console.log(`\n✅ Feature "${name}" scaffolded!`);
67
+ if (meta.description) console.log(` ${meta.description}`);
68
+ }
69
+
70
+ async function fetchJSON<T>(url: string): Promise<T> {
71
+ const res = await fetch(url);
72
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
73
+ return res.json() as Promise<T>;
74
+ }
75
+
76
+ async function fetchText(url: string): Promise<string> {
77
+ const res = await fetch(url);
78
+ if (!res.ok) throw new Error(`Failed to fetch ${url} (${res.status})`);
79
+ return res.text();
80
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bun
2
+ // ─── Bunia CLI ────────────────────────────────────────────
3
+ // bunia create <name> scaffold a new project
4
+ // bunia dev start the development server
5
+ // bunia build build for production
6
+ // bunia start run the production server
7
+ // bunia add <name> add a UI component from the registry
8
+ // bunia feat <name> add a feature scaffold from the registry
9
+
10
+ const [, , command, ...args] = process.argv;
11
+
12
+ async function main() {
13
+ switch (command) {
14
+ case "create": {
15
+ const { runCreate } = await import("./create.ts");
16
+ await runCreate(args[0]);
17
+ break;
18
+ }
19
+ case "dev": {
20
+ const { runDev } = await import("./dev.ts");
21
+ await runDev();
22
+ break;
23
+ }
24
+ case "build": {
25
+ const { runBuild } = await import("./build.ts");
26
+ await runBuild();
27
+ break;
28
+ }
29
+ case "start": {
30
+ const { runStart } = await import("./start.ts");
31
+ await runStart();
32
+ break;
33
+ }
34
+ case "add": {
35
+ const { runAdd } = await import("./add.ts");
36
+ await runAdd(args[0]);
37
+ break;
38
+ }
39
+ case "feat": {
40
+ const { runFeat } = await import("./feat.ts");
41
+ await runFeat(args[0]);
42
+ break;
43
+ }
44
+ default: {
45
+ console.log(`
46
+ 🐰 Bunia
47
+
48
+ Usage:
49
+ bunia <command> [options]
50
+
51
+ Commands:
52
+ create <name> Scaffold a new Bunia project
53
+ dev Start the development server
54
+ build Build for production
55
+ start Run the production server
56
+ add <component> Add a UI component from the registry
57
+ feat <feature> Add a feature scaffold from the registry
58
+
59
+ Examples:
60
+ bunia create my-app
61
+ bunia dev
62
+ bunia build
63
+ bunia start
64
+ bunia add button
65
+ bunia feat login
66
+ `);
67
+ break;
68
+ }
69
+ }
70
+ }
71
+
72
+ main().catch((err) => {
73
+ console.error("❌", err.message);
74
+ process.exit(1);
75
+ });
@@ -0,0 +1,28 @@
1
+ import { spawn } from "bun";
2
+ import { join } from "path";
3
+ import { loadEnv } from "../core/env.ts";
4
+
5
+ const BUNIA_NODE_MODULES = join(import.meta.dir, "..", "..", "node_modules");
6
+
7
+ export async function runStart() {
8
+ loadEnv("production");
9
+
10
+ let serverEntry = "index.js";
11
+ try {
12
+ const manifest = await Bun.file("./dist/manifest.json").json();
13
+ serverEntry = manifest.serverEntry ?? "index.js";
14
+ } catch { }
15
+
16
+ const proc = spawn(["bun", "run", `dist/server/${serverEntry}`], {
17
+ stdout: "inherit",
18
+ stderr: "inherit",
19
+ cwd: process.cwd(),
20
+ env: {
21
+ ...process.env,
22
+ NODE_ENV: "production",
23
+ NODE_PATH: BUNIA_NODE_MODULES,
24
+ },
25
+ });
26
+
27
+ await proc.exited;
28
+ }