@pikku/bun-server 0.12.9 → 0.12.11
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/CHANGELOG.md +68 -0
- package/dist/pikku-bun-server.d.ts +10 -2
- package/dist/pikku-bun-server.js +74 -23
- package/dist/pikku-bun-server.js.map +1 -1
- package/package.json +3 -3
- package/src/pikku-bun-server.ts +147 -26
- package/src/static-mount.test.ts +223 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# @pikku/bun-server
|
|
2
2
|
|
|
3
|
+
## 0.12.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3d75643: Run the app's server lifecycle in generated entries, and make an out-of-band
|
|
8
|
+
account signable-in.
|
|
9
|
+
|
|
10
|
+
`pikkuServerLifecycle` was only ever called by `pikku dev` and `pikku serve`, so
|
|
11
|
+
an app that seeds its first admin account, probes a dependency, or warms a cache
|
|
12
|
+
in `beforeStart` did all of that in development and silently skipped it
|
|
13
|
+
everywhere it was actually deployed. The standalone entry and the shared node
|
|
14
|
+
server entry — the one behind every `target: 'server'` unit — now import the
|
|
15
|
+
app's lifecycle and call it: `beforeStart` after `init` and before the port
|
|
16
|
+
opens, so work that must finish before the first request has, `afterStart` once
|
|
17
|
+
the server is listening, and the stop hooks handed to the signal handler that
|
|
18
|
+
already owns shutdown rather than a second listener racing it. Each shutdown
|
|
19
|
+
step is isolated from the ones after it, so a hook that throws is logged without
|
|
20
|
+
taking the service teardown and the socket close down with it.
|
|
21
|
+
|
|
22
|
+
Separately, `createAuthUser` and `setAuthUserPassword` wrote credential accounts
|
|
23
|
+
with no `issuer`. From better-auth 1.7 a credential account is matched by its
|
|
24
|
+
issuer as well as its provider, so those accounts were invisible to sign-in,
|
|
25
|
+
`updatePassword` and `findCredentialAccount` — a user who plainly existed in the
|
|
26
|
+
table was reported as "user not found". The field is written only when the
|
|
27
|
+
resolved schema has it, so older better-auth keeps working, and
|
|
28
|
+
`setAuthUserPassword` repairs an account that predates the fix.
|
|
29
|
+
|
|
30
|
+
## 0.12.10
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- 80eb5c0: Generate a desktop shell from `pikku deploy apply --desktop`
|
|
35
|
+
|
|
36
|
+
`pikku deploy apply --provider standalone --runtime bun --desktop` now emits a
|
|
37
|
+
`src-tauri/` crate that ships the compiled binary as a sidecar and opens a
|
|
38
|
+
window on the server's own HTTP origin, so cookies, CORS and OAuth behave
|
|
39
|
+
exactly as they do in a browser. Regeneration is idempotent and leaves an
|
|
40
|
+
edited file alone rather than overwriting it.
|
|
41
|
+
|
|
42
|
+
`--desktop-url https://app.example.com` builds the other shape: a shell that
|
|
43
|
+
points at an already-deployed server. Nothing is bundled — no sidecar, no
|
|
44
|
+
binary, and so no bun runtime to compile one — and the window is declared in
|
|
45
|
+
`tauri.conf.json` rather than opened from Rust, because the origin is known up
|
|
46
|
+
front. The url can also live in `pikku.config.json` as `deploy.desktop.url`,
|
|
47
|
+
alongside `deploy.desktop.identifier`.
|
|
48
|
+
|
|
49
|
+
Supporting changes: `SERVER_READY_MARKER` moved to `@pikku/deploy` (the CLI
|
|
50
|
+
re-exports it from its old path), both HTTP runtimes expose the port they
|
|
51
|
+
actually bound so `--port 0` reports a real port, and the generated server
|
|
52
|
+
entry exits when its parent process goes away.
|
|
53
|
+
|
|
54
|
+
- 80eb5c0: feat: serve a built frontend from the pikku server's own origin
|
|
55
|
+
|
|
56
|
+
A new `frontend` key in `pikku.config.json` names a directory of built
|
|
57
|
+
frontend output. `pikku serve` mounts it, and `pikku deploy` ships it inside
|
|
58
|
+
the distributable — into a directory beside the bundle for the node runtime,
|
|
59
|
+
and embedded in the binary for a `bun build --compile` standalone. `pikku dev`
|
|
60
|
+
deliberately ignores it and says so, because the frontend's own dev server owns
|
|
61
|
+
that job.
|
|
62
|
+
|
|
63
|
+
Pikku reads the frontend's output and never builds it, so an unbuilt directory
|
|
64
|
+
fails with a message that says which build to run rather than booting a server
|
|
65
|
+
that answers every page with a 404.
|
|
66
|
+
|
|
67
|
+
- Updated dependencies [80eb5c0]
|
|
68
|
+
- Updated dependencies [2252016]
|
|
69
|
+
- @pikku/core@0.12.98
|
|
70
|
+
|
|
3
71
|
## 0.12.9
|
|
4
72
|
|
|
5
73
|
### Patch Changes
|
|
@@ -7,6 +7,7 @@ export type StaticMount = {
|
|
|
7
7
|
urlPrefix: string;
|
|
8
8
|
directory: string;
|
|
9
9
|
spaFallback?: boolean;
|
|
10
|
+
assets?: Record<string, string>;
|
|
10
11
|
};
|
|
11
12
|
export type BunServerConfig = CoreConfig & {
|
|
12
13
|
port: number;
|
|
@@ -25,6 +26,10 @@ export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
|
25
26
|
mcpPath?: string;
|
|
26
27
|
contentSigningJWT?: JWTService;
|
|
27
28
|
};
|
|
29
|
+
export type ShutdownHooks = {
|
|
30
|
+
beforeStop?: () => void | Promise<void>;
|
|
31
|
+
afterStop?: () => void | Promise<void>;
|
|
32
|
+
};
|
|
28
33
|
export declare class PikkuBunServer {
|
|
29
34
|
private readonly config;
|
|
30
35
|
private readonly logger;
|
|
@@ -40,8 +45,11 @@ export declare class PikkuBunServer {
|
|
|
40
45
|
private initMCP;
|
|
41
46
|
start(): Promise<void>;
|
|
42
47
|
get port(): number;
|
|
43
|
-
private
|
|
48
|
+
private matchesPrefix;
|
|
49
|
+
private staticMountCandidates;
|
|
50
|
+
private serveStaticFiles;
|
|
51
|
+
private serveSpaFallback;
|
|
44
52
|
private resolveStaticFile;
|
|
45
53
|
stop(): Promise<void>;
|
|
46
|
-
enableExitOnSignals(): void;
|
|
54
|
+
enableExitOnSignals(hooks?: ShutdownHooks): void;
|
|
47
55
|
}
|
package/dist/pikku-bun-server.js
CHANGED
|
@@ -118,7 +118,7 @@ export class PikkuBunServer {
|
|
|
118
118
|
if (contentResponse) {
|
|
119
119
|
return contentResponse;
|
|
120
120
|
}
|
|
121
|
-
const staticResponse = await this.
|
|
121
|
+
const staticResponse = await this.serveStaticFiles(req);
|
|
122
122
|
if (staticResponse) {
|
|
123
123
|
return staticResponse;
|
|
124
124
|
}
|
|
@@ -128,7 +128,14 @@ export class PikkuBunServer {
|
|
|
128
128
|
respondWith404: true,
|
|
129
129
|
...options,
|
|
130
130
|
});
|
|
131
|
-
|
|
131
|
+
const response = pikkuRes.toResponse();
|
|
132
|
+
if (response.status === 404) {
|
|
133
|
+
const fallback = await this.serveSpaFallback(req);
|
|
134
|
+
if (fallback) {
|
|
135
|
+
return fallback;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return response;
|
|
132
139
|
},
|
|
133
140
|
websocket: {
|
|
134
141
|
open: (ws) => {
|
|
@@ -169,48 +176,82 @@ export class PikkuBunServer {
|
|
|
169
176
|
},
|
|
170
177
|
});
|
|
171
178
|
eventHub.setServer(this.server);
|
|
172
|
-
logger.info(`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${
|
|
179
|
+
logger.info(`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${this.port}`);
|
|
173
180
|
}
|
|
174
181
|
get port() {
|
|
175
182
|
return this.server?.port ?? this.config.port;
|
|
176
183
|
}
|
|
177
|
-
|
|
184
|
+
matchesPrefix(pathname, prefix) {
|
|
185
|
+
if (prefix === '' || prefix === '/') {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
return pathname === prefix || pathname.startsWith(`${prefix}/`);
|
|
189
|
+
}
|
|
190
|
+
staticMountCandidates(req) {
|
|
178
191
|
const mounts = this.config.staticMounts;
|
|
179
192
|
if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
180
193
|
return null;
|
|
181
194
|
}
|
|
182
195
|
const pathname = decodeURIComponent(new URL(req.url).pathname);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
return {
|
|
197
|
+
pathname,
|
|
198
|
+
mounts: mounts.filter((mount) => this.matchesPrefix(pathname, mount.urlPrefix)),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async serveStaticFiles(req) {
|
|
202
|
+
const candidates = this.staticMountCandidates(req);
|
|
203
|
+
if (!candidates) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
for (const mount of candidates.mounts) {
|
|
207
|
+
const key = candidates.pathname
|
|
208
|
+
.slice(mount.urlPrefix.length)
|
|
209
|
+
.replace(/^\/+/, '');
|
|
210
|
+
const result = await this.resolveStaticFile(mount, key);
|
|
211
|
+
if (result === 'rejected') {
|
|
212
|
+
return new Response('Not Found', { status: 404 });
|
|
187
213
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (file) {
|
|
191
|
-
return new Response(req.method === 'HEAD' ? null : file);
|
|
214
|
+
if (result) {
|
|
215
|
+
return new Response(req.method === 'HEAD' ? null : result);
|
|
192
216
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
async serveSpaFallback(req) {
|
|
221
|
+
const candidates = this.staticMountCandidates(req);
|
|
222
|
+
if (!candidates) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
for (const mount of candidates.mounts) {
|
|
226
|
+
if (!mount.spaFallback) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const index = await this.resolveStaticFile(mount, 'index.html');
|
|
230
|
+
if (index && index !== 'rejected') {
|
|
231
|
+
return new Response(req.method === 'HEAD' ? null : index);
|
|
198
232
|
}
|
|
199
|
-
return new Response('Not Found', { status: 404 });
|
|
200
233
|
}
|
|
201
234
|
return null;
|
|
202
235
|
}
|
|
203
236
|
async resolveStaticFile(mount, key) {
|
|
237
|
+
if (mount.assets) {
|
|
238
|
+
const assetPath = mount.assets[key === '' ? 'index.html' : key];
|
|
239
|
+
if (!assetPath) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const asset = Bun.file(assetPath);
|
|
243
|
+
return (await asset.exists()) ? asset : null;
|
|
244
|
+
}
|
|
204
245
|
const directory = resolve(mount.directory);
|
|
205
246
|
const targetPath = key === '' ? resolve(directory, 'index.html') : resolve(directory, key);
|
|
206
247
|
if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
|
|
207
|
-
return
|
|
248
|
+
return 'rejected';
|
|
208
249
|
}
|
|
209
250
|
let file = Bun.file(targetPath);
|
|
210
251
|
if (!(await file.exists())) {
|
|
211
252
|
const indexPath = resolve(targetPath, 'index.html');
|
|
212
253
|
if (!indexPath.startsWith(`${directory}/`)) {
|
|
213
|
-
return
|
|
254
|
+
return 'rejected';
|
|
214
255
|
}
|
|
215
256
|
file = Bun.file(indexPath);
|
|
216
257
|
if (!(await file.exists())) {
|
|
@@ -223,12 +264,22 @@ export class PikkuBunServer {
|
|
|
223
264
|
await this.server?.stop();
|
|
224
265
|
this.server = null;
|
|
225
266
|
}
|
|
226
|
-
enableExitOnSignals() {
|
|
267
|
+
enableExitOnSignals(hooks) {
|
|
268
|
+
const phase = async (name, run) => {
|
|
269
|
+
try {
|
|
270
|
+
await run();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.error(`pikku-bun-server: ${name} failed during shutdown`, error);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
227
276
|
const shutdown = async (signal) => {
|
|
228
277
|
this.logger.info(`pikku-bun-server: ${signal} received, stopping`);
|
|
229
278
|
try {
|
|
230
|
-
await
|
|
231
|
-
await
|
|
279
|
+
await phase('beforeStop', () => hooks?.beforeStop?.());
|
|
280
|
+
await phase('stopping singleton services', () => stopSingletonServices());
|
|
281
|
+
await phase('stopping the server', () => this.stop());
|
|
282
|
+
await phase('afterStop', () => hooks?.afterStop?.());
|
|
232
283
|
}
|
|
233
284
|
finally {
|
|
234
285
|
process.exit(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/bun-server",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.11",
|
|
4
4
|
"description": "Pikku server adapter for Bun (Bun.serve)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "yasser.fadl@gmail.com",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"prepublishOnly": "bun run build"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@pikku/core": "^0.12.
|
|
19
|
+
"@pikku/core": "^0.12.98",
|
|
20
20
|
"@pikku/modelcontextprotocol": "^0.12.10"
|
|
21
21
|
},
|
|
22
22
|
"peerDependenciesMeta": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@pikku/core": "^0.12.
|
|
28
|
+
"@pikku/core": "^0.12.98",
|
|
29
29
|
"@pikku/modelcontextprotocol": "^0.12.10",
|
|
30
30
|
"@types/bun": "^1.4.0",
|
|
31
31
|
"typescript": "^6.0.3"
|
package/src/pikku-bun-server.ts
CHANGED
|
@@ -31,6 +31,17 @@ export type StaticMount = {
|
|
|
31
31
|
urlPrefix: string
|
|
32
32
|
directory: string
|
|
33
33
|
spaFallback?: boolean
|
|
34
|
+
/**
|
|
35
|
+
* Serve from an explicit key → path map instead of from `directory`, which is
|
|
36
|
+
* then unused. Keys are mount-relative request paths (`index.html`,
|
|
37
|
+
* `assets/app-a1b2c3.js`).
|
|
38
|
+
*
|
|
39
|
+
* This exists for assets that are not laid out as a directory at all: a
|
|
40
|
+
* `bun build --compile` binary embeds each file and hands back an opaque
|
|
41
|
+
* `/$bunfs/…` path, so the only way to find one is to have recorded it at
|
|
42
|
+
* build time.
|
|
43
|
+
*/
|
|
44
|
+
assets?: Record<string, string>
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
export type BunServerConfig = CoreConfig & {
|
|
@@ -100,6 +111,16 @@ const isSerializable = (data: unknown): boolean =>
|
|
|
100
111
|
* Handles HTTP via the fetch handler and WebSocket via Bun.serve's native
|
|
101
112
|
* websocket handler (which is backed by uWebSockets internally).
|
|
102
113
|
*/
|
|
114
|
+
/**
|
|
115
|
+
* Work the process owes its app on the way down, run around the service
|
|
116
|
+
* teardown `enableExitOnSignals` already does. A failing hook is logged and
|
|
117
|
+
* shutdown continues: a process that has been told to stop has to stop.
|
|
118
|
+
*/
|
|
119
|
+
export type ShutdownHooks = {
|
|
120
|
+
beforeStop?: () => void | Promise<void>
|
|
121
|
+
afterStop?: () => void | Promise<void>
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
export class PikkuBunServer {
|
|
104
125
|
private server: BunServer<WsData> | null = null
|
|
105
126
|
private readonly eventHub: BunEventHubService
|
|
@@ -220,7 +241,7 @@ export class PikkuBunServer {
|
|
|
220
241
|
return contentResponse
|
|
221
242
|
}
|
|
222
243
|
|
|
223
|
-
const staticResponse = await this.
|
|
244
|
+
const staticResponse = await this.serveStaticFiles(req)
|
|
224
245
|
if (staticResponse) {
|
|
225
246
|
return staticResponse
|
|
226
247
|
}
|
|
@@ -231,7 +252,14 @@ export class PikkuBunServer {
|
|
|
231
252
|
respondWith404: true,
|
|
232
253
|
...options,
|
|
233
254
|
})
|
|
234
|
-
|
|
255
|
+
const response = pikkuRes.toResponse()
|
|
256
|
+
if (response.status === 404) {
|
|
257
|
+
const fallback = await this.serveSpaFallback(req)
|
|
258
|
+
if (fallback) {
|
|
259
|
+
return fallback
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return response
|
|
235
263
|
},
|
|
236
264
|
|
|
237
265
|
websocket: {
|
|
@@ -279,60 +307,133 @@ export class PikkuBunServer {
|
|
|
279
307
|
|
|
280
308
|
eventHub.setServer(this.server as BunServer<unknown>)
|
|
281
309
|
logger.info(
|
|
282
|
-
`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${
|
|
310
|
+
`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${this.port}`
|
|
283
311
|
)
|
|
284
312
|
}
|
|
285
313
|
|
|
314
|
+
/**
|
|
315
|
+
* The port the server is actually listening on.
|
|
316
|
+
*
|
|
317
|
+
* Not the same as `config.port` whenever that is `0`: the OS picks a free
|
|
318
|
+
* port at bind time, and a parent process that was told `0` has no other way
|
|
319
|
+
* to find out which one. Falls back to the requested port before `start()`.
|
|
320
|
+
*/
|
|
286
321
|
public get port(): number {
|
|
287
322
|
return this.server?.port ?? this.config.port
|
|
288
323
|
}
|
|
289
324
|
|
|
290
|
-
private
|
|
325
|
+
private matchesPrefix(pathname: string, prefix: string): boolean {
|
|
326
|
+
// A root mount covers the whole tree; the generic test below would match
|
|
327
|
+
// only `/` itself, since nothing starts with `//`.
|
|
328
|
+
if (prefix === '' || prefix === '/') {
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
return pathname === prefix || pathname.startsWith(`${prefix}/`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Resolve the mounts a GET/HEAD request could be served from, or null when
|
|
336
|
+
* static serving does not apply to this request at all.
|
|
337
|
+
*/
|
|
338
|
+
private staticMountCandidates(
|
|
339
|
+
req: Request
|
|
340
|
+
): { pathname: string; mounts: StaticMount[] } | null {
|
|
291
341
|
const mounts = this.config.staticMounts
|
|
292
342
|
if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
293
343
|
return null
|
|
294
344
|
}
|
|
295
345
|
const pathname = decodeURIComponent(new URL(req.url).pathname)
|
|
346
|
+
return {
|
|
347
|
+
pathname,
|
|
348
|
+
mounts: mounts.filter((mount) =>
|
|
349
|
+
this.matchesPrefix(pathname, mount.urlPrefix)
|
|
350
|
+
),
|
|
351
|
+
}
|
|
352
|
+
}
|
|
296
353
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Serve an exact file hit only. A miss falls through to route dispatch, so a
|
|
356
|
+
* mount at `/` cannot swallow the API — the app shell is served afterwards by
|
|
357
|
+
* `serveSpaFallback`, once dispatch has had its chance and produced a 404.
|
|
358
|
+
*/
|
|
359
|
+
private async serveStaticFiles(req: Request): Promise<Response | null> {
|
|
360
|
+
const candidates = this.staticMountCandidates(req)
|
|
361
|
+
if (!candidates) {
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
for (const mount of candidates.mounts) {
|
|
366
|
+
const key = candidates.pathname
|
|
367
|
+
.slice(mount.urlPrefix.length)
|
|
368
|
+
.replace(/^\/+/, '')
|
|
369
|
+
const result = await this.resolveStaticFile(mount, key)
|
|
370
|
+
if (result === 'rejected') {
|
|
371
|
+
// A key that escaped the mount directory is refused outright rather
|
|
372
|
+
// than falling through, which would answer a traversal attempt with a
|
|
373
|
+
// 200 and the app shell.
|
|
374
|
+
return new Response('Not Found', { status: 404 })
|
|
303
375
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (file) {
|
|
307
|
-
return new Response(req.method === 'HEAD' ? null : file)
|
|
376
|
+
if (result) {
|
|
377
|
+
return new Response(req.method === 'HEAD' ? null : result)
|
|
308
378
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return null
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Last resort for a client-side route: dispatch has already 404'd, so an
|
|
386
|
+
* unmatched path under a `spaFallback` mount is the SPA's own routing.
|
|
387
|
+
*/
|
|
388
|
+
private async serveSpaFallback(req: Request): Promise<Response | null> {
|
|
389
|
+
const candidates = this.staticMountCandidates(req)
|
|
390
|
+
if (!candidates) {
|
|
391
|
+
return null
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (const mount of candidates.mounts) {
|
|
395
|
+
if (!mount.spaFallback) {
|
|
396
|
+
continue
|
|
397
|
+
}
|
|
398
|
+
const index = await this.resolveStaticFile(mount, 'index.html')
|
|
399
|
+
if (index && index !== 'rejected') {
|
|
400
|
+
return new Response(req.method === 'HEAD' ? null : index)
|
|
314
401
|
}
|
|
315
|
-
return new Response('Not Found', { status: 404 })
|
|
316
402
|
}
|
|
317
403
|
|
|
318
404
|
return null
|
|
319
405
|
}
|
|
320
406
|
|
|
407
|
+
/**
|
|
408
|
+
* `'rejected'` is kept distinct from `null` because the two must not share a
|
|
409
|
+
* fate: a missing file is a candidate for the SPA fallback, while a key that
|
|
410
|
+
* resolved outside the mount directory must never be. An `assets` mount never
|
|
411
|
+
* produces `'rejected'` at all — a map lookup has no directory to escape.
|
|
412
|
+
*/
|
|
321
413
|
private async resolveStaticFile(
|
|
322
414
|
mount: StaticMount,
|
|
323
415
|
key: string
|
|
324
|
-
): Promise<ReturnType<typeof Bun.file> | null> {
|
|
416
|
+
): Promise<ReturnType<typeof Bun.file> | 'rejected' | null> {
|
|
417
|
+
if (mount.assets) {
|
|
418
|
+
const assetPath = mount.assets[key === '' ? 'index.html' : key]
|
|
419
|
+
if (!assetPath) {
|
|
420
|
+
return null
|
|
421
|
+
}
|
|
422
|
+
const asset = Bun.file(assetPath)
|
|
423
|
+
return (await asset.exists()) ? asset : null
|
|
424
|
+
}
|
|
425
|
+
|
|
325
426
|
const directory = resolve(mount.directory)
|
|
326
427
|
const targetPath =
|
|
327
428
|
key === '' ? resolve(directory, 'index.html') : resolve(directory, key)
|
|
328
429
|
if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
|
|
329
|
-
return
|
|
430
|
+
return 'rejected'
|
|
330
431
|
}
|
|
331
432
|
let file = Bun.file(targetPath)
|
|
332
433
|
if (!(await file.exists())) {
|
|
333
434
|
const indexPath = resolve(targetPath, 'index.html')
|
|
334
435
|
if (!indexPath.startsWith(`${directory}/`)) {
|
|
335
|
-
return
|
|
436
|
+
return 'rejected'
|
|
336
437
|
}
|
|
337
438
|
file = Bun.file(indexPath)
|
|
338
439
|
if (!(await file.exists())) {
|
|
@@ -347,12 +448,32 @@ export class PikkuBunServer {
|
|
|
347
448
|
this.server = null
|
|
348
449
|
}
|
|
349
450
|
|
|
350
|
-
public enableExitOnSignals(): void {
|
|
451
|
+
public enableExitOnSignals(hooks?: ShutdownHooks): void {
|
|
452
|
+
/**
|
|
453
|
+
* Every step of the shutdown is isolated from the ones after it. Sharing
|
|
454
|
+
* one catch means a hook that throws takes the service teardown and the
|
|
455
|
+
* socket close down with it, leaving the process to exit with connections
|
|
456
|
+
* still open and services still holding their handles.
|
|
457
|
+
*/
|
|
458
|
+
const phase = async (name: string, run: () => void | Promise<void>) => {
|
|
459
|
+
try {
|
|
460
|
+
await run()
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this.logger.error(
|
|
463
|
+
`pikku-bun-server: ${name} failed during shutdown`,
|
|
464
|
+
error
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
351
468
|
const shutdown = async (signal: string) => {
|
|
352
469
|
this.logger.info(`pikku-bun-server: ${signal} received, stopping`)
|
|
353
470
|
try {
|
|
354
|
-
await
|
|
355
|
-
await
|
|
471
|
+
await phase('beforeStop', () => hooks?.beforeStop?.())
|
|
472
|
+
await phase('stopping singleton services', () =>
|
|
473
|
+
stopSingletonServices()
|
|
474
|
+
)
|
|
475
|
+
await phase('stopping the server', () => this.stop())
|
|
476
|
+
await phase('afterStop', () => hooks?.afterStop?.())
|
|
356
477
|
} finally {
|
|
357
478
|
process.exit(0)
|
|
358
479
|
}
|
package/src/static-mount.test.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
|
4
4
|
import { tmpdir } from 'node:os'
|
|
5
5
|
import { join } from 'node:path'
|
|
6
6
|
import { setSingletonServices } from '@pikku/core/state'
|
|
7
|
-
import { resetPikkuState } from '@pikku/core/state'
|
|
7
|
+
import { pikkuState, resetPikkuState } from '@pikku/core/state'
|
|
8
|
+
import { httpRouter, wireHTTP } from '@pikku/core/http'
|
|
9
|
+
import { addFunction } from '@pikku/core/function'
|
|
8
10
|
import type { Logger } from '@pikku/core/services'
|
|
9
11
|
import { PikkuBunServer } from './pikku-bun-server.js'
|
|
10
12
|
|
|
@@ -106,3 +108,223 @@ describe('PikkuBunServer static mounts', () => {
|
|
|
106
108
|
}
|
|
107
109
|
})
|
|
108
110
|
})
|
|
111
|
+
|
|
112
|
+
describe('PikkuBunServer root-mounted SPA', () => {
|
|
113
|
+
let server: PikkuBunServer
|
|
114
|
+
let tmpDir: string
|
|
115
|
+
let origin: string
|
|
116
|
+
|
|
117
|
+
before(async () => {
|
|
118
|
+
resetPikkuState()
|
|
119
|
+
httpRouter.reset()
|
|
120
|
+
setSingletonServices({
|
|
121
|
+
logger: noopLogger,
|
|
122
|
+
schema: {
|
|
123
|
+
compileSchema: () => {},
|
|
124
|
+
getSchemaNames: () => new Set<string>(),
|
|
125
|
+
},
|
|
126
|
+
} as any)
|
|
127
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-spa-'))
|
|
128
|
+
writeFileSync(
|
|
129
|
+
join(tmpDir, 'index.html'),
|
|
130
|
+
'<!doctype html><title>app shell</title>'
|
|
131
|
+
)
|
|
132
|
+
mkdirSync(join(tmpDir, 'assets'))
|
|
133
|
+
writeFileSync(join(tmpDir, 'assets', 'app.js'), 'console.log("app")')
|
|
134
|
+
|
|
135
|
+
pikkuState(null, 'function', 'meta', {
|
|
136
|
+
things_func: {
|
|
137
|
+
pikkuFuncId: 'things_func',
|
|
138
|
+
inputSchemaName: null,
|
|
139
|
+
outputSchemaName: null,
|
|
140
|
+
sessionless: true,
|
|
141
|
+
},
|
|
142
|
+
} as any)
|
|
143
|
+
pikkuState(null, 'http', 'meta', {
|
|
144
|
+
get: {
|
|
145
|
+
'/api/things': {
|
|
146
|
+
pikkuFuncId: 'things_func',
|
|
147
|
+
route: '/api/things',
|
|
148
|
+
method: 'get',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
post: {},
|
|
152
|
+
delete: {},
|
|
153
|
+
patch: {},
|
|
154
|
+
head: {},
|
|
155
|
+
put: {},
|
|
156
|
+
options: {},
|
|
157
|
+
} as any)
|
|
158
|
+
addFunction('things_func', { func: async () => ({ things: ['one'] }) })
|
|
159
|
+
wireHTTP({
|
|
160
|
+
route: '/api/things',
|
|
161
|
+
method: 'get',
|
|
162
|
+
auth: false,
|
|
163
|
+
func: { func: async () => ({ things: ['one'] }) },
|
|
164
|
+
} as any)
|
|
165
|
+
httpRouter.initialize()
|
|
166
|
+
|
|
167
|
+
server = new PikkuBunServer(
|
|
168
|
+
{
|
|
169
|
+
port: 0,
|
|
170
|
+
hostname: '127.0.0.1',
|
|
171
|
+
staticMounts: [
|
|
172
|
+
{ urlPrefix: '/', directory: tmpDir, spaFallback: true },
|
|
173
|
+
],
|
|
174
|
+
} as any,
|
|
175
|
+
noopLogger
|
|
176
|
+
)
|
|
177
|
+
await server.init()
|
|
178
|
+
await server.start()
|
|
179
|
+
origin = `http://127.0.0.1:${server.port}`
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
after(async () => {
|
|
183
|
+
await server.stop()
|
|
184
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('a wired API route still dispatches under a root SPA mount', async () => {
|
|
188
|
+
const response = await fetch(`${origin}/api/things`)
|
|
189
|
+
assert.equal(response.status, 200)
|
|
190
|
+
assert.match(
|
|
191
|
+
response.headers.get('content-type') ?? '',
|
|
192
|
+
/application\/json/,
|
|
193
|
+
'API route returned the SPA shell instead of dispatching'
|
|
194
|
+
)
|
|
195
|
+
assert.deepEqual(await response.json(), { things: ['one'] })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('static files still win over the SPA fallback', async () => {
|
|
199
|
+
const response = await fetch(`${origin}/assets/app.js`)
|
|
200
|
+
assert.equal(response.status, 200)
|
|
201
|
+
assert.equal(await response.text(), 'console.log("app")')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('unknown client routes still fall back to the app shell', async () => {
|
|
205
|
+
const response = await fetch(`${origin}/records/42`)
|
|
206
|
+
assert.equal(response.status, 200)
|
|
207
|
+
assert.match(await response.text(), /<title>app shell<\/title>/)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe('PikkuBunServer asset-mapped mount', () => {
|
|
212
|
+
let server: PikkuBunServer
|
|
213
|
+
let tmpDir: string
|
|
214
|
+
let origin: string
|
|
215
|
+
|
|
216
|
+
before(async () => {
|
|
217
|
+
resetPikkuState()
|
|
218
|
+
httpRouter.reset()
|
|
219
|
+
setSingletonServices({
|
|
220
|
+
logger: noopLogger,
|
|
221
|
+
schema: {
|
|
222
|
+
compileSchema: () => {},
|
|
223
|
+
getSchemaNames: () => new Set<string>(),
|
|
224
|
+
},
|
|
225
|
+
} as any)
|
|
226
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-assets-'))
|
|
227
|
+
|
|
228
|
+
// The files live in a `store` subdirectory that the mount's own `directory`
|
|
229
|
+
// does not contain, so a hit can only come from the map.
|
|
230
|
+
const store = join(tmpDir, 'store')
|
|
231
|
+
mkdirSync(store)
|
|
232
|
+
writeFileSync(
|
|
233
|
+
join(store, 'shell-abc123.html'),
|
|
234
|
+
'<!doctype html><title>embedded shell</title>'
|
|
235
|
+
)
|
|
236
|
+
writeFileSync(join(store, 'app-def456.js'), 'console.log("embedded")')
|
|
237
|
+
|
|
238
|
+
pikkuState(null, 'function', 'meta', {
|
|
239
|
+
things_func: {
|
|
240
|
+
pikkuFuncId: 'things_func',
|
|
241
|
+
inputSchemaName: null,
|
|
242
|
+
outputSchemaName: null,
|
|
243
|
+
sessionless: true,
|
|
244
|
+
},
|
|
245
|
+
} as any)
|
|
246
|
+
pikkuState(null, 'http', 'meta', {
|
|
247
|
+
get: {
|
|
248
|
+
'/api/things': {
|
|
249
|
+
pikkuFuncId: 'things_func',
|
|
250
|
+
route: '/api/things',
|
|
251
|
+
method: 'get',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
post: {},
|
|
255
|
+
delete: {},
|
|
256
|
+
patch: {},
|
|
257
|
+
head: {},
|
|
258
|
+
put: {},
|
|
259
|
+
options: {},
|
|
260
|
+
} as any)
|
|
261
|
+
addFunction('things_func', { func: async () => ({ things: ['one'] }) })
|
|
262
|
+
wireHTTP({
|
|
263
|
+
route: '/api/things',
|
|
264
|
+
method: 'get',
|
|
265
|
+
auth: false,
|
|
266
|
+
func: { func: async () => ({ things: ['one'] }) },
|
|
267
|
+
} as any)
|
|
268
|
+
httpRouter.initialize()
|
|
269
|
+
|
|
270
|
+
server = new PikkuBunServer(
|
|
271
|
+
{
|
|
272
|
+
port: 0,
|
|
273
|
+
hostname: '127.0.0.1',
|
|
274
|
+
staticMounts: [
|
|
275
|
+
{
|
|
276
|
+
urlPrefix: '/',
|
|
277
|
+
directory: join(tmpDir, 'nonexistent'),
|
|
278
|
+
spaFallback: true,
|
|
279
|
+
assets: {
|
|
280
|
+
'index.html': join(store, 'shell-abc123.html'),
|
|
281
|
+
'assets/app.js': join(store, 'app-def456.js'),
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
} as any,
|
|
286
|
+
noopLogger
|
|
287
|
+
)
|
|
288
|
+
await server.init()
|
|
289
|
+
await server.start()
|
|
290
|
+
origin = `http://127.0.0.1:${server.port}`
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
after(async () => {
|
|
294
|
+
await server.stop()
|
|
295
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('serves a mapped asset from the path the map names', async () => {
|
|
299
|
+
const response = await fetch(`${origin}/assets/app.js`)
|
|
300
|
+
assert.equal(response.status, 200)
|
|
301
|
+
assert.match(response.headers.get('content-type') ?? '', /javascript/)
|
|
302
|
+
assert.equal(await response.text(), 'console.log("embedded")')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
test('serves the mapped index.html at the mount root', async () => {
|
|
306
|
+
const response = await fetch(`${origin}/`)
|
|
307
|
+
assert.equal(response.status, 200)
|
|
308
|
+
assert.match(await response.text(), /<title>embedded shell<\/title>/)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('a wired API route still dispatches under a root asset mount', async () => {
|
|
312
|
+
const response = await fetch(`${origin}/api/things`)
|
|
313
|
+
assert.equal(response.status, 200)
|
|
314
|
+
assert.deepEqual(await response.json(), { things: ['one'] })
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test('an unmapped key falls back to the app shell', async () => {
|
|
318
|
+
const response = await fetch(`${origin}/records/42`)
|
|
319
|
+
assert.equal(response.status, 200)
|
|
320
|
+
assert.match(await response.text(), /<title>embedded shell<\/title>/)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test('a traversal-shaped key is a plain miss, not a rejection', async () => {
|
|
324
|
+
// A map lookup cannot escape a directory, so there is nothing to reject:
|
|
325
|
+
// the key is simply absent and the SPA fallback is allowed to answer.
|
|
326
|
+
const response = await fetch(`${origin}/%2e%2e/pikku-bun-assets-secret.txt`)
|
|
327
|
+
assert.equal(response.status, 200)
|
|
328
|
+
assert.match(await response.text(), /<title>embedded shell<\/title>/)
|
|
329
|
+
})
|
|
330
|
+
})
|