bosia 0.2.3 → 0.3.0
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 +39 -39
- package/package.json +56 -54
- package/src/ambient.d.ts +31 -0
- package/src/cli/add.ts +120 -114
- package/src/cli/build.ts +10 -10
- package/src/cli/create.ts +142 -137
- package/src/cli/dev.ts +8 -8
- package/src/cli/feat.ts +266 -258
- package/src/cli/index.ts +51 -42
- package/src/cli/registry.ts +136 -115
- package/src/cli/start.ts +17 -17
- package/src/cli/test.ts +25 -0
- package/src/core/build.ts +72 -56
- package/src/core/client/App.svelte +177 -156
- package/src/core/client/appState.svelte.ts +33 -31
- package/src/core/client/enhance.ts +83 -78
- package/src/core/client/hydrate.ts +95 -81
- package/src/core/client/prefetch.ts +101 -94
- package/src/core/client/router.svelte.ts +64 -51
- package/src/core/cookies.ts +70 -66
- package/src/core/cors.ts +44 -35
- package/src/core/csrf.ts +38 -38
- package/src/core/dedup.ts +17 -17
- package/src/core/dev.ts +165 -168
- package/src/core/env.ts +155 -148
- package/src/core/envCodegen.ts +73 -73
- package/src/core/errors.ts +48 -49
- package/src/core/hooks.ts +50 -50
- package/src/core/html.ts +184 -145
- package/src/core/matcher.ts +130 -121
- package/src/core/paths.ts +8 -10
- package/src/core/plugin.ts +113 -107
- package/src/core/prerender.ts +191 -122
- package/src/core/renderer.ts +359 -286
- package/src/core/routeFile.ts +140 -127
- package/src/core/routeTypes.ts +144 -83
- package/src/core/scanner.ts +125 -95
- package/src/core/server.ts +538 -424
- package/src/core/types.ts +25 -20
- package/src/lib/index.ts +8 -8
- package/src/lib/utils.ts +44 -30
- package/templates/default/.prettierignore +5 -0
- package/templates/default/.prettierrc.json +9 -0
- package/templates/default/README.md +5 -5
- package/templates/default/package.json +22 -18
- package/templates/default/src/app.css +80 -80
- package/templates/default/src/app.d.ts +3 -3
- package/templates/default/src/routes/+error.svelte +7 -10
- package/templates/default/src/routes/+layout.svelte +2 -2
- package/templates/default/src/routes/+page.svelte +31 -29
- package/templates/default/src/routes/about/+page.svelte +3 -3
- package/templates/default/tsconfig.json +20 -20
- package/templates/demo/.prettierignore +5 -0
- package/templates/demo/.prettierrc.json +9 -0
- package/templates/demo/README.md +9 -9
- package/templates/demo/package.json +22 -17
- package/templates/demo/src/app.css +80 -80
- package/templates/demo/src/app.d.ts +3 -3
- package/templates/demo/src/hooks.server.ts +9 -9
- package/templates/demo/src/routes/(public)/+layout.svelte +45 -23
- package/templates/demo/src/routes/(public)/+page.svelte +96 -67
- package/templates/demo/src/routes/(public)/about/+page.svelte +13 -25
- package/templates/demo/src/routes/(public)/all/[...catchall]/+page.svelte +24 -28
- package/templates/demo/src/routes/(public)/blog/+page.svelte +55 -46
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.server.ts +36 -38
- package/templates/demo/src/routes/(public)/blog/[slug]/+page.svelte +60 -42
- package/templates/demo/src/routes/+error.svelte +10 -7
- package/templates/demo/src/routes/+layout.server.ts +4 -4
- package/templates/demo/src/routes/+layout.svelte +2 -2
- package/templates/demo/src/routes/actions-test/+page.server.ts +16 -16
- package/templates/demo/src/routes/actions-test/+page.svelte +49 -49
- package/templates/demo/src/routes/api/hello/+server.ts +25 -25
- package/templates/demo/tsconfig.json +20 -20
- package/templates/todo/.prettierignore +5 -0
- package/templates/todo/.prettierrc.json +9 -0
- package/templates/todo/README.md +9 -9
- package/templates/todo/package.json +22 -17
- package/templates/todo/src/app.css +80 -80
- package/templates/todo/src/app.d.ts +7 -7
- package/templates/todo/src/hooks.server.ts +9 -9
- package/templates/todo/src/routes/+error.svelte +10 -7
- package/templates/todo/src/routes/+layout.server.ts +4 -4
- package/templates/todo/src/routes/+layout.svelte +2 -2
- package/templates/todo/src/routes/+page.svelte +44 -44
- package/templates/todo/template.json +1 -1
- package/templates/todo/tsconfig.json +20 -20
package/src/core/dev.ts
CHANGED
|
@@ -17,17 +17,17 @@ const RAPID_CRASH_WINDOW = 5_000; // 5 seconds
|
|
|
17
17
|
// ─── SSE Broadcast ────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
function broadcastReload() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
const msg = new TextEncoder().encode("event: reload\ndata: ok\n\n");
|
|
21
|
+
for (const ctrl of sseClients) {
|
|
22
|
+
try {
|
|
23
|
+
ctrl.enqueue(msg);
|
|
24
|
+
} catch {
|
|
25
|
+
sseClients.delete(ctrl);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (sseClients.size > 0) {
|
|
29
|
+
console.log(`📡 Reload sent to ${sseClients.size} client(s)`);
|
|
30
|
+
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// ─── Build ────────────────────────────────────────────────
|
|
@@ -37,13 +37,13 @@ import { BOSIA_NODE_PATH } from "./paths.ts";
|
|
|
37
37
|
const BUILD_SCRIPT = join(import.meta.dir, "build.ts");
|
|
38
38
|
|
|
39
39
|
async function runBuild(): Promise<boolean> {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
console.log("🏗️ Building...");
|
|
41
|
+
const proc = spawn(["bun", "run", BUILD_SCRIPT], {
|
|
42
|
+
stdout: "inherit",
|
|
43
|
+
stderr: "inherit",
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
});
|
|
46
|
+
return (await proc.exited) === 0;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// ─── Ports ────────────────────────────────────────────────
|
|
@@ -52,57 +52,59 @@ const DEV_PORT = Number(process.env.PORT) || 9000;
|
|
|
52
52
|
const APP_PORT = DEV_PORT + 1; // internal, hidden from user
|
|
53
53
|
|
|
54
54
|
async function startAppServer() {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
55
|
+
if (appProcess) {
|
|
56
|
+
intentionalKill = true;
|
|
57
|
+
appProcess.kill();
|
|
58
|
+
await appProcess.exited;
|
|
59
|
+
intentionalKill = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Read the server entry filename from the manifest written by build.ts
|
|
63
|
+
let serverEntry = "index.js";
|
|
64
|
+
try {
|
|
65
|
+
const manifest = await Bun.file("./dist/manifest.json").json();
|
|
66
|
+
serverEntry = manifest.serverEntry ?? "index.js";
|
|
67
|
+
} catch {}
|
|
68
|
+
|
|
69
|
+
appProcess = spawn(["bun", "run", `dist/server/${serverEntry}`], {
|
|
70
|
+
stdout: "inherit",
|
|
71
|
+
stderr: "inherit",
|
|
72
|
+
cwd: process.cwd(),
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
NODE_ENV: "development",
|
|
76
|
+
// Force app server to APP_PORT — prevents PORT from .env conflicting with the dev proxy
|
|
77
|
+
PORT: String(APP_PORT),
|
|
78
|
+
// Allow externalized deps (elysia, etc.) to resolve from bosia's node_modules
|
|
79
|
+
NODE_PATH: BOSIA_NODE_PATH,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Monitor for unexpected crashes
|
|
84
|
+
const proc = appProcess;
|
|
85
|
+
proc.exited.then((code) => {
|
|
86
|
+
if (proc !== appProcess || intentionalKill) return;
|
|
87
|
+
if (code === 0) return; // clean exit
|
|
88
|
+
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
if (now - lastCrashTime < RAPID_CRASH_WINDOW) {
|
|
91
|
+
crashCount++;
|
|
92
|
+
} else {
|
|
93
|
+
crashCount = 1;
|
|
94
|
+
}
|
|
95
|
+
lastCrashTime = now;
|
|
96
|
+
|
|
97
|
+
if (crashCount >= MAX_RAPID_CRASHES) {
|
|
98
|
+
console.error(
|
|
99
|
+
`\n💥 App crashed ${crashCount} times in ${RAPID_CRASH_WINDOW / 1000}s — waiting for file change to restart\n`,
|
|
100
|
+
);
|
|
101
|
+
crashCount = 0;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.warn(`\n⚠️ App crashed (exit code ${code}). Restarting...\n`);
|
|
106
|
+
startAppServer();
|
|
107
|
+
});
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
// ─── Build & Restart ──────────────────────────────────────
|
|
@@ -112,33 +114,33 @@ let building = false;
|
|
|
112
114
|
let buildPending = false;
|
|
113
115
|
|
|
114
116
|
async function buildAndRestart() {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
if (building) {
|
|
118
|
+
buildPending = true;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
building = true;
|
|
122
|
+
try {
|
|
123
|
+
const ok = await runBuild();
|
|
124
|
+
if (!ok) {
|
|
125
|
+
console.error("❌ Build failed — fix errors and save again");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await startAppServer();
|
|
129
|
+
// Give the app server a moment to bind its port
|
|
130
|
+
await Bun.sleep(200);
|
|
131
|
+
broadcastReload();
|
|
132
|
+
} finally {
|
|
133
|
+
building = false;
|
|
134
|
+
}
|
|
135
|
+
if (buildPending) {
|
|
136
|
+
buildPending = false;
|
|
137
|
+
buildAndRestart();
|
|
138
|
+
}
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
function scheduleBuild() {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
if (buildTimer) clearTimeout(buildTimer);
|
|
143
|
+
buildTimer = setTimeout(buildAndRestart, 300);
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
// ─── Dev Proxy ────────────────────────────────────────────
|
|
@@ -146,65 +148,67 @@ function scheduleBuild() {
|
|
|
146
148
|
// All other requests are proxied to the app server.
|
|
147
149
|
|
|
148
150
|
Bun.serve({
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
151
|
+
port: DEV_PORT,
|
|
152
|
+
idleTimeout: 255,
|
|
153
|
+
async fetch(req) {
|
|
154
|
+
const url = new URL(req.url);
|
|
155
|
+
|
|
156
|
+
// SSE endpoint — owned by dev server, not the app
|
|
157
|
+
if (url.pathname === "/__bosia/sse") {
|
|
158
|
+
return new Response(
|
|
159
|
+
new ReadableStream({
|
|
160
|
+
start(ctrl) {
|
|
161
|
+
sseClients.add(ctrl);
|
|
162
|
+
// Initial keepalive so the browser knows the connection is open
|
|
163
|
+
ctrl.enqueue(new TextEncoder().encode(":ok\n\n"));
|
|
164
|
+
|
|
165
|
+
// Ping every 25s to prevent idle timeout
|
|
166
|
+
const ping = setInterval(() => {
|
|
167
|
+
try {
|
|
168
|
+
ctrl.enqueue(new TextEncoder().encode(":ping\n\n"));
|
|
169
|
+
} catch {
|
|
170
|
+
clearInterval(ping);
|
|
171
|
+
sseClients.delete(ctrl);
|
|
172
|
+
}
|
|
173
|
+
}, 25_000);
|
|
174
|
+
|
|
175
|
+
req.signal.addEventListener("abort", () => {
|
|
176
|
+
clearInterval(ping);
|
|
177
|
+
sseClients.delete(ctrl);
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
{
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
184
|
+
"Cache-Control": "no-cache",
|
|
185
|
+
Connection: "keep-alive",
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Proxy everything else to the app server
|
|
192
|
+
try {
|
|
193
|
+
const target = new URL(req.url);
|
|
194
|
+
target.hostname = "localhost";
|
|
195
|
+
target.port = String(APP_PORT);
|
|
196
|
+
|
|
197
|
+
return await fetch(
|
|
198
|
+
new Request(target.toString(), {
|
|
199
|
+
method: req.method,
|
|
200
|
+
headers: req.headers,
|
|
201
|
+
body: req.body,
|
|
202
|
+
redirect: "manual",
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
} catch {
|
|
206
|
+
return new Response("App server is starting...", {
|
|
207
|
+
status: 503,
|
|
208
|
+
headers: { "Content-Type": "text/plain", "Retry-After": "1" },
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
},
|
|
208
212
|
});
|
|
209
213
|
|
|
210
214
|
// ─── Initial Build ────────────────────────────────────────
|
|
@@ -216,25 +220,18 @@ console.log(`\n🌐 Open http://localhost:${DEV_PORT}\n`);
|
|
|
216
220
|
// ─── File Watcher ─────────────────────────────────────────
|
|
217
221
|
// Watch src/ recursively. Skip generated files to avoid loops.
|
|
218
222
|
|
|
219
|
-
const GENERATED = [
|
|
220
|
-
join(process.cwd(), ".bosia"),
|
|
221
|
-
join(process.cwd(), "public", "bosia-tw.css"),
|
|
222
|
-
];
|
|
223
|
+
const GENERATED = [join(process.cwd(), ".bosia"), join(process.cwd(), "public", "bosia-tw.css")];
|
|
223
224
|
|
|
224
225
|
function isGenerated(path: string): boolean {
|
|
225
|
-
|
|
226
|
+
return GENERATED.some((g) => path.startsWith(g));
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
watch(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(`[watch] changed: ${filename}`);
|
|
236
|
-
scheduleBuild();
|
|
237
|
-
},
|
|
238
|
-
);
|
|
229
|
+
watch(join(process.cwd(), "src"), { recursive: true }, (_event, filename) => {
|
|
230
|
+
if (!filename) return;
|
|
231
|
+
const abs = join(process.cwd(), "src", filename);
|
|
232
|
+
if (isGenerated(abs)) return;
|
|
233
|
+
console.log(`[watch] changed: ${filename}`);
|
|
234
|
+
scheduleBuild();
|
|
235
|
+
});
|
|
239
236
|
|
|
240
237
|
console.log("👀 Watching src/ for changes...\n");
|