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.
- package/README.md +163 -0
- package/package.json +56 -0
- package/src/cli/add.ts +83 -0
- package/src/cli/build.ts +16 -0
- package/src/cli/create.ts +54 -0
- package/src/cli/dev.ts +14 -0
- package/src/cli/feat.ts +80 -0
- package/src/cli/index.ts +75 -0
- package/src/cli/start.ts +28 -0
- package/src/core/build.ts +157 -0
- package/src/core/client/App.svelte +147 -0
- package/src/core/client/hydrate.ts +78 -0
- package/src/core/client/router.svelte.ts +46 -0
- package/src/core/cookies.ts +52 -0
- package/src/core/cors.ts +60 -0
- package/src/core/csrf.ts +65 -0
- package/src/core/dev.ts +193 -0
- package/src/core/env.ts +135 -0
- package/src/core/envCodegen.ts +94 -0
- package/src/core/errors.ts +23 -0
- package/src/core/hooks.ts +74 -0
- package/src/core/html.ts +170 -0
- package/src/core/matcher.ts +85 -0
- package/src/core/plugin.ts +59 -0
- package/src/core/prerender.ts +79 -0
- package/src/core/renderer.ts +222 -0
- package/src/core/routeFile.ts +88 -0
- package/src/core/routeTypes.ts +95 -0
- package/src/core/scanner.ts +99 -0
- package/src/core/server.ts +320 -0
- package/src/core/types.ts +37 -0
- package/src/lib/index.ts +19 -0
- package/src/lib/utils.ts +24 -0
- package/templates/default/.env.example +34 -0
- package/templates/default/README.md +102 -0
- package/templates/default/package.json +21 -0
- package/templates/default/public/.gitkeep +0 -0
- package/templates/default/src/app.css +132 -0
- package/templates/default/src/app.d.ts +7 -0
- package/templates/default/src/lib/.gitkeep +0 -0
- package/templates/default/src/routes/+error.svelte +18 -0
- package/templates/default/src/routes/+layout.svelte +6 -0
- package/templates/default/src/routes/+page.svelte +36 -0
- package/templates/default/src/routes/about/+page.server.ts +1 -0
- package/templates/default/src/routes/about/+page.svelte +8 -0
- 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
|
+
}
|
package/src/cli/build.ts
ADDED
|
@@ -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
|
+
}
|
package/src/cli/feat.ts
ADDED
|
@@ -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
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -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
|
+
});
|
package/src/cli/start.ts
ADDED
|
@@ -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
|
+
}
|