bosbun 0.0.5 ā 0.0.7
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 +12 -12
- package/package.json +5 -5
- package/src/cli/add.ts +5 -5
- package/src/cli/create.ts +67 -7
- package/src/cli/feat.ts +5 -5
- package/src/cli/index.ts +18 -17
- package/src/cli/start.ts +2 -4
- package/src/core/build.ts +17 -17
- package/src/core/client/App.svelte +12 -12
- package/src/core/client/hydrate.ts +7 -7
- package/src/core/client/prefetch.ts +8 -8
- package/src/core/client/router.svelte.ts +1 -1
- package/src/core/dev.ts +8 -7
- package/src/core/env.ts +1 -1
- package/src/core/envCodegen.ts +20 -20
- package/src/core/hooks.ts +2 -2
- package/src/core/html.ts +20 -20
- package/src/core/paths.ts +41 -0
- package/src/core/plugin.ts +48 -14
- package/src/core/prerender.ts +3 -2
- package/src/core/renderer.ts +3 -3
- package/src/core/routeFile.ts +6 -6
- package/src/core/routeTypes.ts +10 -10
- package/src/core/server.ts +4 -4
- package/src/core/types.ts +1 -1
- package/src/lib/index.ts +3 -3
- package/templates/default/.env.example +6 -6
- package/templates/default/README.md +3 -3
- package/templates/default/package.json +4 -4
- package/templates/default/public/favicon.svg +14 -0
- package/templates/default/src/routes/+page.svelte +4 -4
- package/templates/default/tsconfig.json +1 -1
- package/templates/demo/.env.example +52 -0
- package/templates/demo/README.md +23 -0
- package/templates/demo/package.json +20 -0
- package/templates/demo/public/.gitkeep +0 -0
- package/templates/demo/public/favicon.svg +14 -0
- package/templates/demo/src/app.css +132 -0
- package/templates/demo/src/app.d.ts +7 -0
- package/templates/demo/src/hooks.server.ts +21 -0
- package/templates/demo/src/lib/utils.ts +1 -0
- package/templates/demo/src/routes/(public)/+layout.svelte +31 -0
- package/templates/demo/src/routes/(public)/+page.svelte +79 -0
- package/templates/demo/src/routes/(public)/about/+page.server.ts +1 -0
- package/templates/demo/src/routes/(public)/about/+page.svelte +31 -0
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +38 -0
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -0
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +62 -0
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +53 -0
- package/templates/demo/src/routes/+error.svelte +15 -0
- package/templates/demo/src/routes/+layout.server.ts +10 -0
- package/templates/demo/src/routes/+layout.svelte +6 -0
- package/templates/demo/src/routes/actions-test/+page.server.ts +28 -0
- package/templates/demo/src/routes/actions-test/+page.svelte +60 -0
- package/templates/demo/src/routes/api/hello/+server.ts +44 -0
- package/templates/demo/tsconfig.json +22 -0
package/src/core/dev.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn, type Subprocess } from "bun";
|
|
|
2
2
|
import { watch } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
|
|
5
|
-
console.log("
|
|
5
|
+
console.log("⬔ Bosbun dev server starting...\n");
|
|
6
6
|
|
|
7
7
|
// āāā State āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
8
8
|
|
|
@@ -27,8 +27,9 @@ function broadcastReload() {
|
|
|
27
27
|
|
|
28
28
|
// āāā Build āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
29
29
|
|
|
30
|
+
import { BOSBUN_NODE_PATH } from "./paths.ts";
|
|
31
|
+
|
|
30
32
|
const BUILD_SCRIPT = join(import.meta.dir, "build.ts");
|
|
31
|
-
const BUNIA_NODE_MODULES = join(import.meta.dir, "..", "..", "node_modules");
|
|
32
33
|
|
|
33
34
|
async function runBuild(): Promise<boolean> {
|
|
34
35
|
console.log("šļø Building...");
|
|
@@ -67,8 +68,8 @@ async function startAppServer() {
|
|
|
67
68
|
NODE_ENV: "development",
|
|
68
69
|
// Force app server to APP_PORT ā prevents PORT from .env conflicting with the dev proxy
|
|
69
70
|
PORT: String(APP_PORT),
|
|
70
|
-
// Allow externalized deps (elysia, etc.) to resolve from
|
|
71
|
-
NODE_PATH:
|
|
71
|
+
// Allow externalized deps (elysia, etc.) to resolve from bosbun's node_modules
|
|
72
|
+
NODE_PATH: BOSBUN_NODE_PATH,
|
|
72
73
|
},
|
|
73
74
|
});
|
|
74
75
|
}
|
|
@@ -105,7 +106,7 @@ Bun.serve({
|
|
|
105
106
|
const url = new URL(req.url);
|
|
106
107
|
|
|
107
108
|
// SSE endpoint ā owned by dev server, not the app
|
|
108
|
-
if (url.pathname === "/
|
|
109
|
+
if (url.pathname === "/__bosbun/sse") {
|
|
109
110
|
return new Response(
|
|
110
111
|
new ReadableStream({
|
|
111
112
|
start(ctrl) {
|
|
@@ -170,8 +171,8 @@ console.log(`\nš Open http://localhost:${DEV_PORT}\n`);
|
|
|
170
171
|
// Watch src/ recursively. Skip generated files to avoid loops.
|
|
171
172
|
|
|
172
173
|
const GENERATED = [
|
|
173
|
-
join(process.cwd(), ".
|
|
174
|
-
join(process.cwd(), "public", "
|
|
174
|
+
join(process.cwd(), ".bosbun"),
|
|
175
|
+
join(process.cwd(), "public", "bosbun-tw.css"),
|
|
175
176
|
];
|
|
176
177
|
|
|
177
178
|
function isGenerated(path: string): boolean {
|
package/src/core/env.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
|
|
4
4
|
// āāā Framework-reserved vars āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
5
|
-
// These are controlled by
|
|
5
|
+
// These are controlled by Bosbun itself ā users access them via process.env directly.
|
|
6
6
|
const FRAMEWORK_VARS = new Set([
|
|
7
7
|
"PORT",
|
|
8
8
|
"NODE_ENV",
|
package/src/core/envCodegen.ts
CHANGED
|
@@ -3,26 +3,26 @@ import { join } from "path";
|
|
|
3
3
|
import type { ClassifiedEnv } from "./env.ts";
|
|
4
4
|
|
|
5
5
|
// āāā Env Module Codegen āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
6
|
-
// Generates three files in .
|
|
6
|
+
// Generates three files in .bosbun/:
|
|
7
7
|
// env.server.ts ā all vars (static inlined, dynamic via process.env)
|
|
8
|
-
// env.client.ts ā only PUBLIC_* vars (PUBLIC_STATIC_* inlined, PUBLIC_* via window.
|
|
9
|
-
// types/env.d.ts ā declare module '
|
|
8
|
+
// env.client.ts ā only PUBLIC_* vars (PUBLIC_STATIC_* inlined, PUBLIC_* via window.__BOSBUN_ENV__)
|
|
9
|
+
// types/env.d.ts ā declare module 'bosbun:env' for IDE autocomplete
|
|
10
10
|
|
|
11
11
|
export function generateEnvModules(classified: ClassifiedEnv): void {
|
|
12
|
-
const
|
|
13
|
-
const typesDir = join(
|
|
14
|
-
mkdirSync(
|
|
12
|
+
const bosbunDir = join(process.cwd(), ".bosbun");
|
|
13
|
+
const typesDir = join(bosbunDir, "types");
|
|
14
|
+
mkdirSync(bosbunDir, { recursive: true });
|
|
15
15
|
mkdirSync(typesDir, { recursive: true });
|
|
16
16
|
|
|
17
|
-
writeServerEnv(classified,
|
|
18
|
-
writeClientEnv(classified,
|
|
17
|
+
writeServerEnv(classified, bosbunDir);
|
|
18
|
+
writeClientEnv(classified, bosbunDir);
|
|
19
19
|
writeEnvTypes(classified, typesDir);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function writeServerEnv(classified: ClassifiedEnv,
|
|
22
|
+
function writeServerEnv(classified: ClassifiedEnv, bosbunDir: string): void {
|
|
23
23
|
const lines: string[] = [
|
|
24
|
-
"// Auto-generated by
|
|
25
|
-
"//
|
|
24
|
+
"// Auto-generated by Bosbun. Do not edit.",
|
|
25
|
+
"// bosbun:env ā server ā all vars",
|
|
26
26
|
"",
|
|
27
27
|
];
|
|
28
28
|
|
|
@@ -46,16 +46,16 @@ function writeServerEnv(classified: ClassifiedEnv, buniaDir: string): void {
|
|
|
46
46
|
lines.push(`export const ${key} = process.env.${key} ?? "";`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
writeFileSync(join(
|
|
49
|
+
writeFileSync(join(bosbunDir, "env.server.ts"), lines.join("\n") + "\n");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function writeClientEnv(classified: ClassifiedEnv,
|
|
52
|
+
function writeClientEnv(classified: ClassifiedEnv, bosbunDir: string): void {
|
|
53
53
|
const lines: string[] = [
|
|
54
|
-
"// Auto-generated by
|
|
55
|
-
"//
|
|
54
|
+
"// Auto-generated by Bosbun. Do not edit.",
|
|
55
|
+
"// bosbun:env ā client ā PUBLIC_* vars only",
|
|
56
56
|
"",
|
|
57
57
|
"const __env: Record<string, string> =",
|
|
58
|
-
" typeof window !== 'undefined' && (window as any).
|
|
58
|
+
" typeof window !== 'undefined' && (window as any).__BOSBUN_ENV__ || {};",
|
|
59
59
|
"",
|
|
60
60
|
];
|
|
61
61
|
|
|
@@ -64,12 +64,12 @@ function writeClientEnv(classified: ClassifiedEnv, buniaDir: string): void {
|
|
|
64
64
|
lines.push(`export const ${key} = ${JSON.stringify(value)};`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// PUBLIC_* dynamic ā read from window.
|
|
67
|
+
// PUBLIC_* dynamic ā read from window.__BOSBUN_ENV__ at runtime
|
|
68
68
|
for (const key of Object.keys(classified.publicDynamic)) {
|
|
69
69
|
lines.push(`export const ${key} = __env.${key} ?? "";`);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
writeFileSync(join(
|
|
72
|
+
writeFileSync(join(bosbunDir, "env.client.ts"), lines.join("\n") + "\n");
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function writeEnvTypes(classified: ClassifiedEnv, typesDir: string): void {
|
|
@@ -83,8 +83,8 @@ function writeEnvTypes(classified: ClassifiedEnv, typesDir: string): void {
|
|
|
83
83
|
const declarations = allKeys.map(key => ` export const ${key}: string;`);
|
|
84
84
|
|
|
85
85
|
const content = [
|
|
86
|
-
"// Auto-generated by
|
|
87
|
-
"declare module '
|
|
86
|
+
"// Auto-generated by Bosbun. Do not edit.",
|
|
87
|
+
"declare module 'bosbun:env' {",
|
|
88
88
|
...declarations,
|
|
89
89
|
"}",
|
|
90
90
|
"",
|
package/src/core/hooks.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// āāā
|
|
1
|
+
// āāā Bosbun Hooks āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
2
|
// SvelteKit-compatible middleware API.
|
|
3
3
|
// Usage in src/hooks.server.ts:
|
|
4
4
|
//
|
|
5
|
-
// import { sequence } from "
|
|
5
|
+
// import { sequence } from "bosbun";
|
|
6
6
|
// export const handle = sequence(authHandle, loggingHandle);
|
|
7
7
|
|
|
8
8
|
// āāā Cookie Types āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
package/src/core/html.ts
CHANGED
|
@@ -70,21 +70,21 @@ export function buildHtml(
|
|
|
70
70
|
.map((f: string) => `<link rel="stylesheet" href="/dist/client/${f}">`)
|
|
71
71
|
.join("\n ");
|
|
72
72
|
|
|
73
|
-
const fallbackTitle = head.includes("<title>") ? "" : "<title>
|
|
73
|
+
const fallbackTitle = head.includes("<title>") ? "" : "<title>Bosbun App</title>";
|
|
74
74
|
|
|
75
75
|
const publicEnv = getPublicDynamicEnv();
|
|
76
76
|
const envScript = Object.keys(publicEnv).length > 0
|
|
77
|
-
? `\n <script>window.
|
|
77
|
+
? `\n <script>window.__BOSBUN_ENV__=${safeJsonStringify(publicEnv)};</script>`
|
|
78
78
|
: "";
|
|
79
79
|
|
|
80
80
|
const formScript = formData != null
|
|
81
|
-
? `window.
|
|
81
|
+
? `window.__BOSBUN_FORM_DATA__=${safeJsonStringify(formData)};`
|
|
82
82
|
: "";
|
|
83
83
|
|
|
84
84
|
const scripts = csr
|
|
85
|
-
? `${envScript}\n <script>window.
|
|
85
|
+
? `${envScript}\n <script>window.__BOSBUN_PAGE_DATA__=${safeJsonStringify(pageData)};window.__BOSBUN_LAYOUT_DATA__=${safeJsonStringify(layoutData)};${formScript}</script>\n <script type="module" src="/dist/client/${distManifest.entry}${cacheBust}"></script>`
|
|
86
86
|
: isDev
|
|
87
|
-
? `\n <script>!function r(){var e=new EventSource("/
|
|
87
|
+
? `\n <script>!function r(){var e=new EventSource("/__bosbun/sse");e.addEventListener("reload",()=>location.reload());e.onopen=()=>r._ok||(r._ok=1);e.onerror=()=>{e.close();setTimeout(r,2000)}}()</script>`
|
|
88
88
|
: "";
|
|
89
89
|
|
|
90
90
|
return `<!DOCTYPE html>
|
|
@@ -93,12 +93,12 @@ export function buildHtml(
|
|
|
93
93
|
<meta charset="UTF-8">
|
|
94
94
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
95
95
|
${fallbackTitle}
|
|
96
|
-
<link rel="icon" href="
|
|
96
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
97
97
|
${head}
|
|
98
98
|
${cssLinks}
|
|
99
|
-
<link rel="stylesheet" href="/
|
|
99
|
+
<link rel="stylesheet" href="/bosbun-tw.css${cacheBust}">
|
|
100
100
|
</head>
|
|
101
|
-
<body data-
|
|
101
|
+
<body data-bosbun-preload="hover">
|
|
102
102
|
<div id="app">${body}</div>${scripts}
|
|
103
103
|
</body>
|
|
104
104
|
</html>`;
|
|
@@ -129,17 +129,17 @@ export function buildHtmlShellOpen(): string {
|
|
|
129
129
|
_shellOpen = `<!DOCTYPE html>\n<html lang="en">\n<head>\n` +
|
|
130
130
|
` <meta charset="UTF-8">\n` +
|
|
131
131
|
` <meta name="viewport" content="width=device-width, initial-scale=1.0">\n` +
|
|
132
|
-
` <link rel="icon" href="
|
|
132
|
+
` <link rel="icon" type="image/svg+xml" href="/favicon.svg">\n` +
|
|
133
133
|
` ${cssLinks}\n` +
|
|
134
|
-
` <link rel="stylesheet" href="/
|
|
134
|
+
` <link rel="stylesheet" href="/bosbun-tw.css${cacheBust}">\n` +
|
|
135
135
|
` <link rel="modulepreload" href="/dist/client/${distManifest.entry}${cacheBust}">`;
|
|
136
136
|
return _shellOpen;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
const SPINNER = `<div id="__bs__"><style>` +
|
|
140
|
-
`:root{--
|
|
140
|
+
`:root{--bosbun-loading-color:#f73b27}` +
|
|
141
141
|
`#__bs__{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}` +
|
|
142
|
-
`#__bs__ i{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:var(--
|
|
142
|
+
`#__bs__ i{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:var(--bosbun-loading-color);` +
|
|
143
143
|
`border-radius:50%;animation:__bs__ .8s linear infinite}` +
|
|
144
144
|
`@keyframes __bs__{to{transform:rotate(360deg)}}</style><i></i></div>`;
|
|
145
145
|
|
|
@@ -158,9 +158,9 @@ export function buildMetadataChunk(metadata: Metadata | null): string {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
} else {
|
|
161
|
-
out += ` <title>
|
|
161
|
+
out += ` <title>Bosbun App</title>\n`;
|
|
162
162
|
}
|
|
163
|
-
out += `</head>\n<body data-
|
|
163
|
+
out += `</head>\n<body data-bosbun-preload="hover">\n${SPINNER}`;
|
|
164
164
|
return out;
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -187,14 +187,14 @@ export function buildHtmlTail(
|
|
|
187
187
|
if (csr) {
|
|
188
188
|
const publicEnv = getPublicDynamicEnv();
|
|
189
189
|
if (Object.keys(publicEnv).length > 0) {
|
|
190
|
-
out += `\n<script>window.
|
|
190
|
+
out += `\n<script>window.__BOSBUN_ENV__=${safeJsonStringify(publicEnv)};</script>`;
|
|
191
191
|
}
|
|
192
|
-
const formInject = formData != null ? `window.
|
|
193
|
-
out += `\n<script>window.
|
|
194
|
-
`window.
|
|
192
|
+
const formInject = formData != null ? `window.__BOSBUN_FORM_DATA__=${safeJsonStringify(formData)};` : "";
|
|
193
|
+
out += `\n<script>window.__BOSBUN_PAGE_DATA__=${safeJsonStringify(pageData)};` +
|
|
194
|
+
`window.__BOSBUN_LAYOUT_DATA__=${safeJsonStringify(layoutData)};${formInject}</script>`;
|
|
195
195
|
out += `\n<script type="module" src="/dist/client/${distManifest.entry}${cacheBust}"></script>`;
|
|
196
196
|
} else if (isDev) {
|
|
197
|
-
out += `\n<script>!function r(){var e=new EventSource("/
|
|
197
|
+
out += `\n<script>!function r(){var e=new EventSource("/__bosbun/sse");e.addEventListener("reload",()=>location.reload());e.onopen=()=>r._ok||(r._ok=1);e.onerror=()=>{e.close();setTimeout(r,2000)}}()</script>`;
|
|
198
198
|
}
|
|
199
199
|
out += `\n</body>\n</html>`;
|
|
200
200
|
return out;
|
|
@@ -218,7 +218,7 @@ export function compress(body: string, contentType: string, req: Request, status
|
|
|
218
218
|
export const STATIC_EXTS = new Set([".ico", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".css", ".js", ".woff", ".woff2", ".ttf"]);
|
|
219
219
|
|
|
220
220
|
export function isStaticPath(path: string): boolean {
|
|
221
|
-
if (path.startsWith("/dist/") || path.startsWith("/
|
|
221
|
+
if (path.startsWith("/dist/") || path.startsWith("/__bosbun/")) return true;
|
|
222
222
|
const dot = path.lastIndexOf(".");
|
|
223
223
|
return dot !== -1 && STATIC_EXTS.has(path.slice(dot));
|
|
224
224
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join, dirname, resolve } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
|
|
4
|
+
// This file lives at src/core/paths.ts ā package root is ../..
|
|
5
|
+
const BOSBUN_PKG_DIR = join(import.meta.dir, "..", "..");
|
|
6
|
+
|
|
7
|
+
// Bun hoists dependencies flat, so bosbun's deps may live in the parent
|
|
8
|
+
// node_modules rather than a nested node_modules/bosbun/node_modules.
|
|
9
|
+
const NESTED_NM = join(BOSBUN_PKG_DIR, "node_modules");
|
|
10
|
+
|
|
11
|
+
// Walk up from the package dir to find the nearest ancestor node_modules.
|
|
12
|
+
// In a workspace, bosbun lives at packages/bosbun/ so we need to find
|
|
13
|
+
// the workspace root's node_modules (not just the parent directory).
|
|
14
|
+
function findAncestorNodeModules(from: string): string | null {
|
|
15
|
+
let dir = resolve(from, "..");
|
|
16
|
+
const root = dirname(dir); // stop at filesystem root
|
|
17
|
+
while (dir !== root) {
|
|
18
|
+
const candidate = join(dir, "node_modules");
|
|
19
|
+
if (candidate !== NESTED_NM && existsSync(candidate)) return candidate;
|
|
20
|
+
dir = dirname(dir);
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HOISTED_NM = findAncestorNodeModules(BOSBUN_PKG_DIR);
|
|
26
|
+
|
|
27
|
+
/** NODE_PATH value covering both nested and hoisted dependency locations */
|
|
28
|
+
export const BOSBUN_NODE_PATH = HOISTED_NM
|
|
29
|
+
? [NESTED_NM, HOISTED_NM].join(":")
|
|
30
|
+
: NESTED_NM;
|
|
31
|
+
|
|
32
|
+
/** Find a binary from bosbun's dependencies (handles hoisting) */
|
|
33
|
+
export function resolveBosbunBin(name: string): string {
|
|
34
|
+
const nested = join(NESTED_NM, ".bin", name);
|
|
35
|
+
if (existsSync(nested)) return nested;
|
|
36
|
+
if (HOISTED_NM) {
|
|
37
|
+
const hoisted = join(HOISTED_NM, ".bin", name);
|
|
38
|
+
if (existsSync(hoisted)) return hoisted;
|
|
39
|
+
}
|
|
40
|
+
return nested; // fallback ā will produce a clear ENOENT
|
|
41
|
+
}
|
package/src/core/plugin.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { join } from "path";
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
2
|
|
|
3
3
|
// āāā Bun Build Plugin āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
4
4
|
// Resolves:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// bosbun:routes ā .bosbun/routes.ts (generated route map)
|
|
6
|
+
// bosbun:env ā .bosbun/env.server.ts (bun) or .bosbun/env.client.ts (browser)
|
|
7
7
|
// $lib/* ā src/lib/* (user library alias)
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function makeBosbunPlugin(target: "browser" | "bun" = "bun") {
|
|
10
10
|
return {
|
|
11
|
-
name: "
|
|
11
|
+
name: "bosbun-resolver",
|
|
12
12
|
setup(build: import("bun").PluginBuilder) {
|
|
13
|
-
//
|
|
14
|
-
build.onResolve({ filter: /^
|
|
15
|
-
path: join(process.cwd(), ".
|
|
13
|
+
// bosbun:routes ā .bosbun/routes.ts
|
|
14
|
+
build.onResolve({ filter: /^bosbun:routes$/ }, () => ({
|
|
15
|
+
path: join(process.cwd(), ".bosbun", "routes.ts"),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
build.onResolve({ filter: /^
|
|
18
|
+
// bosbun:env ā .bosbun/env.client.ts (browser) or .bosbun/env.server.ts (bun)
|
|
19
|
+
build.onResolve({ filter: /^bosbun:env$/ }, () => ({
|
|
20
20
|
path: join(
|
|
21
21
|
process.cwd(),
|
|
22
|
-
".
|
|
22
|
+
".bosbun",
|
|
23
23
|
target === "browser" ? "env.client.ts" : "env.server.ts",
|
|
24
24
|
),
|
|
25
25
|
}));
|
|
@@ -31,14 +31,48 @@ export function makeBuniaPlugin(target: "browser" | "bun" = "bun") {
|
|
|
31
31
|
return { path: await resolveWithExts(base) };
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
// Force svelte imports to resolve from the app's node_modules.
|
|
35
|
+
// Without this, when bosbun is symlinked (bun link / workspace),
|
|
36
|
+
// hydrate.ts resolves "svelte" from the framework's location while
|
|
37
|
+
// compiled components resolve "svelte/internal/client" from the app's.
|
|
38
|
+
// Two different Svelte copies = duplicate runtime state = broken hydration.
|
|
39
|
+
//
|
|
40
|
+
// require.resolve uses the "default" export condition, which for
|
|
41
|
+
// bare "svelte" returns index-server.js. For browser builds we need
|
|
42
|
+
// index-client.js, so we read the "browser" condition from package.json.
|
|
43
|
+
const appDir = process.cwd();
|
|
44
|
+
let svelteBrowserEntry: string | null = null;
|
|
45
|
+
if (target === "browser") {
|
|
46
|
+
try {
|
|
47
|
+
const svelteDir = dirname(require.resolve("svelte/package.json", { paths: [appDir] }));
|
|
48
|
+
const pkg = require(join(svelteDir, "package.json"));
|
|
49
|
+
const dotExport = pkg.exports?.["."];
|
|
50
|
+
const browserPath = typeof dotExport === "object" ? dotExport.browser : null;
|
|
51
|
+
if (browserPath) {
|
|
52
|
+
svelteBrowserEntry = join(svelteDir, browserPath);
|
|
53
|
+
}
|
|
54
|
+
} catch { }
|
|
55
|
+
}
|
|
56
|
+
build.onResolve({ filter: /^svelte(\/.*)?$/ }, (args) => {
|
|
57
|
+
try {
|
|
58
|
+
// Bare "svelte" in browser build: use the "browser" export condition
|
|
59
|
+
if (args.path === "svelte" && svelteBrowserEntry) {
|
|
60
|
+
return { path: svelteBrowserEntry };
|
|
61
|
+
}
|
|
62
|
+
return { path: require.resolve(args.path, { paths: [appDir] }) };
|
|
63
|
+
} catch {
|
|
64
|
+
return undefined; // fall through to default resolution
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
34
68
|
// "tailwindcss" inside app.css is a Tailwind CLI directive ā
|
|
35
|
-
// it's already compiled to public/
|
|
69
|
+
// it's already compiled to public/bosbun-tw.css by the CLI step.
|
|
36
70
|
// Return an empty CSS module so Bun's CSS bundler doesn't choke on it.
|
|
37
71
|
build.onResolve({ filter: /^tailwindcss$/ }, () => ({
|
|
38
72
|
path: "tailwindcss",
|
|
39
|
-
namespace: "
|
|
73
|
+
namespace: "bosbun-empty-css",
|
|
40
74
|
}));
|
|
41
|
-
build.onLoad({ filter: /.*/, namespace: "
|
|
75
|
+
build.onLoad({ filter: /.*/, namespace: "bosbun-empty-css" }, () => ({
|
|
42
76
|
contents: "",
|
|
43
77
|
loader: "css",
|
|
44
78
|
}));
|
package/src/core/prerender.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { writeFileSync, mkdirSync } from "fs";
|
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import type { RouteManifest } from "./types.ts";
|
|
4
4
|
|
|
5
|
+
import { BOSBUN_NODE_PATH } from "./paths.ts";
|
|
6
|
+
|
|
5
7
|
const CORE_DIR = import.meta.dir;
|
|
6
|
-
const BUNIA_NODE_MODULES = join(CORE_DIR, "..", "..", "node_modules");
|
|
7
8
|
|
|
8
9
|
const PRERENDER_TIMEOUT = Number(process.env.PRERENDER_TIMEOUT) || 5_000; // 5s default
|
|
9
10
|
|
|
@@ -36,7 +37,7 @@ export async function prerenderStaticRoutes(manifest: RouteManifest): Promise<vo
|
|
|
36
37
|
const child = Bun.spawn(
|
|
37
38
|
["bun", "run", "./dist/server/index.js"],
|
|
38
39
|
{
|
|
39
|
-
env: { ...process.env, NODE_ENV: "production", PORT: String(port), NODE_PATH:
|
|
40
|
+
env: { ...process.env, NODE_ENV: "production", PORT: String(port), NODE_PATH: BOSBUN_NODE_PATH },
|
|
40
41
|
stdout: "ignore",
|
|
41
42
|
stderr: "ignore",
|
|
42
43
|
},
|
package/src/core/renderer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { render } from "svelte/server";
|
|
2
2
|
|
|
3
3
|
import { findMatch } from "./matcher.ts";
|
|
4
|
-
import { serverRoutes, errorPage } from "
|
|
4
|
+
import { serverRoutes, errorPage } from "bosbun:routes";
|
|
5
5
|
import type { Cookies } from "./hooks.ts";
|
|
6
6
|
import { HttpError, Redirect } from "./errors.ts";
|
|
7
7
|
import App from "./client/App.svelte";
|
|
@@ -59,7 +59,7 @@ function makeFetch(req: Request, url: URL) {
|
|
|
59
59
|
|
|
60
60
|
// āāā Route Data Loader āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
61
61
|
// Runs layout + page server loaders for a given URL.
|
|
62
|
-
// Used by both SSR and the /
|
|
62
|
+
// Used by both SSR and the /__bosbun/data JSON endpoint.
|
|
63
63
|
|
|
64
64
|
export async function loadRouteData(
|
|
65
65
|
url: URL,
|
|
@@ -258,7 +258,7 @@ export async function renderSSRStream(
|
|
|
258
258
|
}
|
|
259
259
|
if (err instanceof HttpError) {
|
|
260
260
|
controller.enqueue(enc.encode(
|
|
261
|
-
`<script>location.replace("/
|
|
261
|
+
`<script>location.replace("/__bosbun/error?status=${err.status}&message="+encodeURIComponent(${safeJsonStringify(err.message)}))</script></body></html>`
|
|
262
262
|
));
|
|
263
263
|
controller.close();
|
|
264
264
|
return;
|
package/src/core/routeFile.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { writeFileSync, mkdirSync } from "fs";
|
|
|
2
2
|
import type { RouteManifest } from "./types.ts";
|
|
3
3
|
|
|
4
4
|
// āāā Route File Generator āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
5
|
-
// Generates .
|
|
5
|
+
// Generates .bosbun/routes.ts ā ONE file with three exports:
|
|
6
6
|
// clientRoutes ā used by client hydrator (page + layout lazy imports)
|
|
7
7
|
// serverRoutes ā used by SSR renderer (+ pageServer + layoutServers)
|
|
8
8
|
// apiRoutes ā used by API handler
|
|
@@ -28,7 +28,7 @@ function sortRoutes<T extends { pattern: string }>(routes: T[]): T[] {
|
|
|
28
28
|
|
|
29
29
|
export function generateRoutesFile(manifest: RouteManifest): void {
|
|
30
30
|
const lines: string[] = [
|
|
31
|
-
"// AUTO-GENERATED by
|
|
31
|
+
"// AUTO-GENERATED by bosbun build ā do not edit\n",
|
|
32
32
|
];
|
|
33
33
|
|
|
34
34
|
const pages = sortRoutes(manifest.pages);
|
|
@@ -99,12 +99,12 @@ export function generateRoutesFile(manifest: RouteManifest): void {
|
|
|
99
99
|
ep ? `() => import(${JSON.stringify(toImportPath(ep))})` : "null"
|
|
100
100
|
};\n`);
|
|
101
101
|
|
|
102
|
-
mkdirSync(".
|
|
103
|
-
writeFileSync(".
|
|
104
|
-
console.log("ā
Routes generated: .
|
|
102
|
+
mkdirSync(".bosbun", { recursive: true });
|
|
103
|
+
writeFileSync(".bosbun/routes.ts", lines.join("\n"));
|
|
104
|
+
console.log("ā
Routes generated: .bosbun/routes.ts");
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// Import path from .
|
|
107
|
+
// Import path from .bosbun/routes.ts to src/routes/<routePath>
|
|
108
108
|
function toImportPath(routePath: string): string {
|
|
109
109
|
return "../src/routes/" + routePath.replace(/\\/g, "/");
|
|
110
110
|
}
|
package/src/core/routeTypes.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "path";
|
|
|
3
3
|
import type { RouteManifest } from "./types.ts";
|
|
4
4
|
|
|
5
5
|
// āāā Route Types Generator āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
6
|
-
// Generates .
|
|
6
|
+
// Generates .bosbun/types/src/routes/**/$types.d.ts for each
|
|
7
7
|
// route directory. Combined with rootDirs in tsconfig.json,
|
|
8
8
|
// this allows `import type { PageData } from './$types'` to
|
|
9
9
|
// work in +page.svelte files ā identical to SvelteKit's API.
|
|
@@ -36,12 +36,12 @@ export function generateRouteTypes(manifest: RouteManifest): void {
|
|
|
36
36
|
const segments = dir === "." ? [] : dir.split("/").filter(Boolean);
|
|
37
37
|
|
|
38
38
|
// Depth of the generated file from project root:
|
|
39
|
-
// .
|
|
39
|
+
// .bosbun/ + types/ + src/ + routes/ + ...segments
|
|
40
40
|
const depth = 4 + segments.length;
|
|
41
41
|
const up = "../".repeat(depth);
|
|
42
42
|
const srcBase = `${up}src/routes/${segments.length ? segments.join("/") + "/" : ""}`;
|
|
43
43
|
|
|
44
|
-
const lines: string[] = ["// AUTO-GENERATED by
|
|
44
|
+
const lines: string[] = ["// AUTO-GENERATED by bosbun ā do not edit\n"];
|
|
45
45
|
|
|
46
46
|
if (info.pageServer) {
|
|
47
47
|
lines.push(`import type { load as _pageLoad } from '${srcBase}+page.server.ts';`);
|
|
@@ -68,16 +68,16 @@ export function generateRouteTypes(manifest: RouteManifest): void {
|
|
|
68
68
|
lines.push(`export type LayoutProps = { data: LayoutData; children: any };`);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const outDir = join(process.cwd(), ".
|
|
71
|
+
const outDir = join(process.cwd(), ".bosbun", "types", "src", "routes", ...segments);
|
|
72
72
|
mkdirSync(outDir, { recursive: true });
|
|
73
73
|
writeFileSync(join(outDir, "$types.d.ts"), lines.join("\n") + "\n");
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
console.log(`ā
Route types generated: .
|
|
76
|
+
console.log(`ā
Route types generated: .bosbun/types/ (${dirs.size} route director${dirs.size === 1 ? "y" : "ies"})`);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// āāā Ensure tsconfig rootDirs āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
80
|
-
// Adds ".
|
|
80
|
+
// Adds ".bosbun/types" to rootDirs so TypeScript resolves
|
|
81
81
|
// `./$types` imports via the generated declaration files.
|
|
82
82
|
// Only patches the file if rootDirs is not already set.
|
|
83
83
|
|
|
@@ -90,17 +90,17 @@ export function ensureRootDirs(): void {
|
|
|
90
90
|
tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
|
|
91
91
|
} catch {
|
|
92
92
|
console.warn("ā ļø Could not parse tsconfig.json ā add rootDirs manually:\n" +
|
|
93
|
-
' "rootDirs": [".", ".
|
|
93
|
+
' "rootDirs": [".", ".bosbun/types"]');
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const rootDirs: string[] = tsconfig.compilerOptions?.rootDirs ?? [];
|
|
98
|
-
if (rootDirs.includes(".
|
|
98
|
+
if (rootDirs.includes(".bosbun/types")) return;
|
|
99
99
|
|
|
100
100
|
tsconfig.compilerOptions ??= {};
|
|
101
|
-
tsconfig.compilerOptions.rootDirs = [".", ".
|
|
101
|
+
tsconfig.compilerOptions.rootDirs = [".", ".bosbun/types",
|
|
102
102
|
...rootDirs.filter((d: string) => d !== ".")];
|
|
103
103
|
|
|
104
104
|
writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
105
|
-
console.log("ā
tsconfig.json: added .
|
|
105
|
+
console.log("ā
tsconfig.json: added .bosbun/types to rootDirs");
|
|
106
106
|
}
|
package/src/core/server.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync } from "fs";
|
|
|
4
4
|
import { join, resolve as resolvePath } from "path";
|
|
5
5
|
|
|
6
6
|
import { findMatch } from "./matcher.ts";
|
|
7
|
-
import { apiRoutes, serverRoutes } from "
|
|
7
|
+
import { apiRoutes, serverRoutes } from "bosbun:routes";
|
|
8
8
|
import type { Handle, RequestEvent } from "./hooks.ts";
|
|
9
9
|
import { HttpError, Redirect, ActionFailure } from "./errors.ts";
|
|
10
10
|
import { CookieJar } from "./cookies.ts";
|
|
@@ -121,13 +121,13 @@ async function resolve(event: RequestEvent): Promise<Response> {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// Data endpoint ā returns server loader data as JSON for client-side navigation
|
|
124
|
-
if (path === "/
|
|
124
|
+
if (path === "/__bosbun/data") {
|
|
125
125
|
const routePath = url.searchParams.get("path") ?? "/";
|
|
126
126
|
if (!isValidRoutePath(routePath, url.origin)) {
|
|
127
127
|
return Response.json({ error: "Invalid path", status: 400 }, { status: 400 });
|
|
128
128
|
}
|
|
129
129
|
const routeUrl = new URL(routePath, url.origin);
|
|
130
|
-
// Rewrite event.url so logging middleware sees the real page path, not /
|
|
130
|
+
// Rewrite event.url so logging middleware sees the real page path, not /__bosbun/data
|
|
131
131
|
event.url = routeUrl;
|
|
132
132
|
try {
|
|
133
133
|
const data = await loadRouteData(routeUrl, locals, request, cookies);
|
|
@@ -398,7 +398,7 @@ const app = new Elysia({ serve: { maxRequestBodySize: BODY_SIZE_LIMIT } })
|
|
|
398
398
|
|
|
399
399
|
app.listen(PORT, () => {
|
|
400
400
|
// In dev mode the proxy owns the user-facing port ā don't print the internal port
|
|
401
|
-
if (!isDev) console.log(
|
|
401
|
+
if (!isDev) console.log(`⬔ Bosbun server running at http://localhost:${PORT}`);
|
|
402
402
|
});
|
|
403
403
|
|
|
404
404
|
function shutdown() {
|
package/src/core/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// āāā
|
|
1
|
+
// āāā Bosbun Core Types āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
2
|
|
|
3
3
|
/** A page route discovered from the file system */
|
|
4
4
|
export interface PageRoute {
|
package/src/lib/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// āāā
|
|
1
|
+
// āāā Bosbun Public API āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
2
|
// Usage in user apps:
|
|
3
|
-
// import { cn, sequence } from "
|
|
4
|
-
// import type { RequestEvent, LoadEvent, Handle, Cookies } from "
|
|
3
|
+
// import { cn, sequence } from "bosbun"
|
|
4
|
+
// import type { RequestEvent, LoadEvent, Handle, Cookies } from "bosbun"
|
|
5
5
|
|
|
6
6
|
export { cn, getServerTime } from "./utils.ts";
|
|
7
7
|
export { sequence } from "../core/hooks.ts";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# āāā
|
|
1
|
+
# āāā Bosbun Environment Variables āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2
2
|
#
|
|
3
3
|
# Copy this file to .env and fill in your values.
|
|
4
4
|
# .env.local overrides .env (gitignored ā for local secrets).
|
|
@@ -7,22 +7,22 @@
|
|
|
7
7
|
# Prefix convention:
|
|
8
8
|
#
|
|
9
9
|
# PUBLIC_STATIC_* Client + server, inlined at build time (e.g. app name)
|
|
10
|
-
# PUBLIC_* Client + server, injected at runtime via window.
|
|
10
|
+
# PUBLIC_* Client + server, injected at runtime via window.__BOSBUN_ENV__
|
|
11
11
|
# STATIC_* Server only, inlined at build time
|
|
12
12
|
# (no prefix) Server only, read from process.env at runtime (secrets, DB creds)
|
|
13
13
|
#
|
|
14
14
|
# Import in your code:
|
|
15
|
-
# import { PUBLIC_STATIC_APP_NAME, DB_PASSWORD } from '
|
|
15
|
+
# import { PUBLIC_STATIC_APP_NAME, DB_PASSWORD } from 'bosbun:env';
|
|
16
16
|
#
|
|
17
17
|
# Framework vars (PORT, NODE_ENV, BODY_SIZE_LIMIT, CSRF_ALLOWED_ORIGINS,
|
|
18
|
-
# CORS_*, LOAD_TIMEOUT, METADATA_TIMEOUT, PRERENDER_TIMEOUT) are NOT exposed via
|
|
18
|
+
# CORS_*, LOAD_TIMEOUT, METADATA_TIMEOUT, PRERENDER_TIMEOUT) are NOT exposed via bosbun:env ā
|
|
19
19
|
# access them via process.env directly.
|
|
20
20
|
# āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
21
21
|
|
|
22
22
|
# Public build-time constant (safe to expose to client)
|
|
23
|
-
PUBLIC_STATIC_APP_NAME=My
|
|
23
|
+
PUBLIC_STATIC_APP_NAME=My Bosbun App
|
|
24
24
|
|
|
25
|
-
# āāā Framework vars ā access via process.env (not via
|
|
25
|
+
# āāā Framework vars ā access via process.env (not via bosbun:env) āāāāāāāāāāāāā
|
|
26
26
|
|
|
27
27
|
# Server port. Defaults to 9000 in production, 9001 in dev (proxied via :9000).
|
|
28
28
|
# PORT=9000
|