@pikku/bun-server 0.12.3 → 0.12.4
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 +18 -0
- package/dist/pikku-bun-server.d.ts +9 -0
- package/dist/pikku-bun-server.js +53 -0
- package/dist/pikku-bun-server.js.map +1 -1
- package/package.json +2 -2
- package/src/pikku-bun-server.ts +73 -0
- package/src/static-mount.test.ts +108 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @pikku/bun-server
|
|
2
2
|
|
|
3
|
+
## 0.12.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d4a2503: Serve the console same-origin at /console (#861). Both dev servers gain
|
|
8
|
+
`staticMounts` (prefix → directory static serving with SPA fallback and path
|
|
9
|
+
traversal protection); `pikku serve` / `pikku dev` mount the bundled console
|
|
10
|
+
app at `/console` on the API port whenever it is bundled, so auth cookies are
|
|
11
|
+
first-party and no `?server=` param is needed. The console is built with
|
|
12
|
+
`base: '/console/'` (its router already derives the basename from BASE_URL).
|
|
13
|
+
The separate `--console <port>` static server is removed; `pikku console`
|
|
14
|
+
serves the bundle under /console and redirects the root there.
|
|
15
|
+
- Updated dependencies [61c9ce9]
|
|
16
|
+
- Updated dependencies [f1f39f8]
|
|
17
|
+
- Updated dependencies [c45e98d]
|
|
18
|
+
- Updated dependencies [472a349]
|
|
19
|
+
- @pikku/core@0.12.52
|
|
20
|
+
|
|
3
21
|
## 0.12.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -2,9 +2,15 @@ import type { CoreConfig } from '@pikku/core';
|
|
|
2
2
|
import type { Logger } from '@pikku/core/services';
|
|
3
3
|
import { type RunHTTPWiringOptions } from '@pikku/core/http';
|
|
4
4
|
import { BunEventHubService } from './bun-event-hub-service.js';
|
|
5
|
+
export type StaticMount = {
|
|
6
|
+
urlPrefix: string;
|
|
7
|
+
directory: string;
|
|
8
|
+
spaFallback?: boolean;
|
|
9
|
+
};
|
|
5
10
|
export type BunServerConfig = CoreConfig & {
|
|
6
11
|
port: number;
|
|
7
12
|
hostname?: string;
|
|
13
|
+
staticMounts?: StaticMount[];
|
|
8
14
|
healthCheckPath?: string;
|
|
9
15
|
};
|
|
10
16
|
export type PikkuBunServerOptions = RunHTTPWiringOptions & {
|
|
@@ -29,6 +35,9 @@ export declare class PikkuBunServer {
|
|
|
29
35
|
init(): Promise<void>;
|
|
30
36
|
private initMCP;
|
|
31
37
|
start(): Promise<void>;
|
|
38
|
+
get port(): number;
|
|
39
|
+
private serveStaticMounts;
|
|
40
|
+
private resolveStaticFile;
|
|
32
41
|
stop(): Promise<void>;
|
|
33
42
|
enableExitOnSignals(): void;
|
|
34
43
|
}
|
package/dist/pikku-bun-server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { fetchData, PikkuFetchHTTPRequest, PikkuFetchHTTPResponse, logRoutes as
|
|
|
3
3
|
import { logChannels } from '@pikku/core/channel';
|
|
4
4
|
import { runLocalChannel } from '@pikku/core/channel/local';
|
|
5
5
|
import { compileAllSchemas } from '@pikku/core/schema';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
6
7
|
import { BunEventHubService } from './bun-event-hub-service.js';
|
|
7
8
|
const isSerializable = (data) => !(typeof data === 'string' ||
|
|
8
9
|
data instanceof ArrayBuffer ||
|
|
@@ -101,6 +102,10 @@ export class PikkuBunServer {
|
|
|
101
102
|
return await mcpHandler(req);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
105
|
+
const staticResponse = await this.serveStaticMounts(req);
|
|
106
|
+
if (staticResponse) {
|
|
107
|
+
return staticResponse;
|
|
108
|
+
}
|
|
104
109
|
const pikkuReq = new PikkuFetchHTTPRequest(req);
|
|
105
110
|
const pikkuRes = new PikkuFetchHTTPResponse();
|
|
106
111
|
await fetchData(pikkuReq, pikkuRes, {
|
|
@@ -150,6 +155,54 @@ export class PikkuBunServer {
|
|
|
150
155
|
eventHub.setServer(this.server);
|
|
151
156
|
logger.info(`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${config.port}`);
|
|
152
157
|
}
|
|
158
|
+
get port() {
|
|
159
|
+
return this.server?.port ?? this.config.port;
|
|
160
|
+
}
|
|
161
|
+
async serveStaticMounts(req) {
|
|
162
|
+
const mounts = this.config.staticMounts;
|
|
163
|
+
if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const pathname = decodeURIComponent(new URL(req.url).pathname);
|
|
167
|
+
for (const mount of mounts) {
|
|
168
|
+
if (pathname !== mount.urlPrefix &&
|
|
169
|
+
!pathname.startsWith(`${mount.urlPrefix}/`)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const key = pathname.slice(mount.urlPrefix.length).replace(/^\/+/, '');
|
|
173
|
+
const file = await this.resolveStaticFile(mount, key);
|
|
174
|
+
if (file) {
|
|
175
|
+
return new Response(req.method === 'HEAD' ? null : file);
|
|
176
|
+
}
|
|
177
|
+
if (mount.spaFallback) {
|
|
178
|
+
const index = await this.resolveStaticFile(mount, 'index.html');
|
|
179
|
+
if (index) {
|
|
180
|
+
return new Response(req.method === 'HEAD' ? null : index);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return new Response('Not Found', { status: 404 });
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
async resolveStaticFile(mount, key) {
|
|
188
|
+
const directory = resolve(mount.directory);
|
|
189
|
+
const targetPath = key === '' ? resolve(directory, 'index.html') : resolve(directory, key);
|
|
190
|
+
if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
let file = Bun.file(targetPath);
|
|
194
|
+
if (!(await file.exists())) {
|
|
195
|
+
const indexPath = resolve(targetPath, 'index.html');
|
|
196
|
+
if (!indexPath.startsWith(`${directory}/`)) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
file = Bun.file(indexPath);
|
|
200
|
+
if (!(await file.exists())) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return file;
|
|
205
|
+
}
|
|
153
206
|
async stop() {
|
|
154
207
|
await this.server?.stop();
|
|
155
208
|
this.server = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,SAAS,IAAI,iBAAiB,GAE/B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"pikku-bun-server.js","sourceRoot":"","sources":["../src/pikku-bun-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,EACL,SAAS,EACT,qBAAqB,EACrB,sBAAsB,EACtB,SAAS,IAAI,iBAAiB,GAE/B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAEtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAwC/D,MAAM,cAAc,GAAG,CAAC,IAAa,EAAW,EAAE,CAChD,CAAC,CACC,OAAO,IAAI,KAAK,QAAQ;IACxB,IAAI,YAAY,WAAW;IAC3B,IAAI,YAAY,UAAU;IAC1B,IAAI,YAAY,SAAS;IACzB,IAAI,YAAY,WAAW;IAC3B,IAAI,YAAY,UAAU;IAC1B,IAAI,YAAY,WAAW;IAC3B,IAAI,YAAY,UAAU;IAC1B,IAAI,YAAY,YAAY;IAC5B,IAAI,YAAY,YAAY,CAC7B,CAAA;AAQH,MAAM,OAAO,cAAc;IASN;IACA;IATX,MAAM,GAA6B,IAAI,CAAA;IAC9B,QAAQ,CAAoB;IAC5B,OAAO,CAAsB;IAC7B,OAAO,CAAmC;IAC1C,OAAO,CAAQ;IACxB,UAAU,CAA0C;IAE5D,YACmB,MAAuB,EACvB,MAAc,EAC/B,UAAiC,EAAE;QAFlB,WAAM,GAAN,MAAM,CAAiB;QACvB,WAAM,GAAN,MAAM,CAAQ;QAG/B,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAA;QAC9D,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,IAAI,kBAAkB,EAAE,CAAA;QACpD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,MAAM,CAAA;QAChC,IAAI,CAAC,OAAO,GAAG,WAAW,CAAA;IAC5B,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9B,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC9B,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACxB,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;IACtB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,MAAM,EAAE,KAAK,GAAG,EAAE,EAAE,SAAS,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,OAAO,CAAA;QAC5D,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAClE,IAAI,CAAC;YACH,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAA;YACtE,MAAM,SAAS,GAAG,IAAI,cAAc,CAClC;gBACE,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,OAAO;gBAChB,YAAY,EAAE;oBACZ,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;oBACtC,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;oBAC9C,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;iBAC3C;aACF,EACD,IAAI,CAAC,MAAM,CACZ,CAAA;YACD,MAAM,SAAS,CAAC,IAAI,EAAE,CAAA;YACtB,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;YACxE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAA;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oCAAoC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QACtE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,gDAAgD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACnG,CAAA;QACH,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;QAEvE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAS;YAC9B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YAEzB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;gBAC3B,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC;oBAC9D,MAAM,QAAQ,GAAG,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAA;oBAC/C,MAAM,QAAQ,GAAG,IAAI,sBAAsB,EAAE,CAAA;oBAC7C,MAAM,cAAc,GAAG,MAAM,eAAe,CAAC;wBAC3C,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE;wBAC9B,OAAO,EAAE,QAAQ;wBACjB,QAAQ,EAAE,QAAQ;wBAClB,KAAK,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ;qBACjC,CAAC,CAAA;oBACF,IAAI,CAAC,cAAc,EAAE,CAAC;wBACpB,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;oBACnD,CAAC;oBACD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,EAAE,CAAC,CAAA;oBAClE,IAAI,QAAQ;wBAAE,OAAO,SAAgC,CAAA;oBACrD,OAAO,IAAI,QAAQ,CAAC,0BAA0B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;gBAClE,CAAC;gBAED,IACE,MAAM,CAAC,eAAe;oBACtB,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,eAAe,EACpD,CAAC;oBACD,OAAO,IAAI,QAAQ,CAAC,aAAa,EAAE;wBACjC,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;qBAChD,CAAC,CAAA;gBACJ,CAAC;gBAED,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;oBAC1C,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,EAAE,CAAC;wBAC/D,OAAO,MAAM,UAAU,CAAC,GAAG,CAAC,CAAA;oBAC9B,CAAC;gBACH,CAAC;gBAED,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAA;gBACxD,IAAI,cAAc,EAAE,CAAC;oBACnB,OAAO,cAAc,CAAA;gBACvB,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAI,qBAAqB,CAAC,GAAG,CAAC,CAAA;gBAC/C,MAAM,QAAQ,GAAG,IAAI,sBAAsB,EAAE,CAAA;gBAC7C,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE;oBAClC,cAAc,EAAE,IAAI;oBACpB,GAAG,OAAO;iBACX,CAAC,CAAA;gBACF,OAAO,QAAQ,CAAC,UAAU,EAAE,CAAA;YAC9B,CAAC;YAED,SAAS,EAAE;gBACT,IAAI,EAAE,CAAC,EAA2B,EAAE,EAAE;oBACpC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAA;oBAClC,cAAc,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,EAAE;wBACrC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAAY,CAAC,CAAA;oBACtE,CAAC,CAAC,CAAA;oBACF,cAAc,CAAC,oBAAoB,CAAC,CAAC,IAAI,EAAE,EAAE;wBAC3C,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;oBACrB,CAAC,CAAC,CAAA;oBACF,cAAc,CAAC,eAAe,CAAC,GAAG,EAAE;wBAClC,EAAE,CAAC,KAAK,EAAE,CAAA;oBACZ,CAAC,CAAC,CAAA;oBACF,QAAQ,CAAC,eAAe,CAAC,cAAc,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;oBACtD,cAAc,CAAC,IAAI,EAAE,CAAA;gBACvB,CAAC;gBAED,OAAO,EAAE,KAAK,EAAE,EAA2B,EAAE,OAAO,EAAE,EAAE;oBACtD,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAA;oBAClC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;wBAChC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;wBACpD,IAAI,MAAM;4BAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;oBAC7C,CAAC;yBAAM,CAAC;wBACN,MAAM,KAAK,GACT,OAAO,YAAY,WAAW;4BAC5B,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC;4BACzB,CAAC,CAAC,IAAI,UAAU,CACZ,OAAO,CAAC,MAAM,EACd,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,UAAU,CACnB,CAAA;wBACP,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;wBACxD,IAAI,MAAM;4BAAE,cAAc,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;oBAC/C,CAAC;gBACH,CAAC;gBAED,KAAK,EAAE,CAAC,EAA2B,EAAE,EAAE;oBACrC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAA;oBAClC,QAAQ,CAAC,eAAe,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;oBAClD,cAAc,CAAC,KAAK,EAAE,CAAA;gBACxB,CAAC;aACF;SACF,CAAC,CAAA;QAEF,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,MAA4B,CAAC,CAAA;QACrD,MAAM,CAAC,IAAI,CACT,yCAAyC,MAAM,CAAC,QAAQ,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,CACzF,CAAA;IACH,CAAC;IAED,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAA;IAC9C,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,GAAY;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAA;QACvC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;YACvE,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAA;QAE9D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IACE,QAAQ,KAAK,KAAK,CAAC,SAAS;gBAC5B,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,SAAS,GAAG,CAAC,EAC3C,CAAC;gBACD,SAAQ;YACV,CAAC;YACD,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;YACrD,IAAI,IAAI,EAAE,CAAC;gBACT,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YAC1D,CAAC;YACD,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;gBAC/D,IAAI,KAAK,EAAE,CAAC;oBACV,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;gBAC3D,CAAC;YACH,CAAC;YACD,OAAO,IAAI,QAAQ,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QACnD,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAC7B,KAAkB,EAClB,GAAW;QAEX,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC1C,MAAM,UAAU,GACd,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;QACzE,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;YACxE,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC/B,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAA;YACnD,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;gBAC3C,OAAO,IAAI,CAAA;YACb,CAAC;YACD,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAC1B,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;gBAC3B,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAA;QACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;IACpB,CAAC;IAEM,mBAAmB;QACxB,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAE,EAAE;YACxC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,qBAAqB,CAAC,CAAA;YAClE,IAAI,CAAC;gBACH,MAAM,qBAAqB,EAAE,CAAA;gBAC7B,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;YACnB,CAAC;oBAAS,CAAC;gBACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACjB,CAAC;QACH,CAAC,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IACpD,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/bun-server",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
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.52",
|
|
20
20
|
"@pikku/modelcontextprotocol": "^0.12.6"
|
|
21
21
|
},
|
|
22
22
|
"peerDependenciesMeta": {
|
package/src/pikku-bun-server.ts
CHANGED
|
@@ -15,11 +15,20 @@ import type { PikkuLocalChannelHandler } from '@pikku/core/channel/local'
|
|
|
15
15
|
import { runLocalChannel } from '@pikku/core/channel/local'
|
|
16
16
|
import { compileAllSchemas } from '@pikku/core/schema'
|
|
17
17
|
|
|
18
|
+
import { resolve } from 'node:path'
|
|
19
|
+
|
|
18
20
|
import { BunEventHubService } from './bun-event-hub-service.js'
|
|
19
21
|
|
|
22
|
+
export type StaticMount = {
|
|
23
|
+
urlPrefix: string
|
|
24
|
+
directory: string
|
|
25
|
+
spaFallback?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
export type BunServerConfig = CoreConfig & {
|
|
21
29
|
port: number
|
|
22
30
|
hostname?: string
|
|
31
|
+
staticMounts?: StaticMount[]
|
|
23
32
|
healthCheckPath?: string
|
|
24
33
|
}
|
|
25
34
|
|
|
@@ -167,6 +176,11 @@ export class PikkuBunServer {
|
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
|
|
179
|
+
const staticResponse = await this.serveStaticMounts(req)
|
|
180
|
+
if (staticResponse) {
|
|
181
|
+
return staticResponse
|
|
182
|
+
}
|
|
183
|
+
|
|
170
184
|
const pikkuReq = new PikkuFetchHTTPRequest(req)
|
|
171
185
|
const pikkuRes = new PikkuFetchHTTPResponse()
|
|
172
186
|
await fetchData(pikkuReq, pikkuRes, {
|
|
@@ -225,6 +239,65 @@ export class PikkuBunServer {
|
|
|
225
239
|
)
|
|
226
240
|
}
|
|
227
241
|
|
|
242
|
+
public get port(): number {
|
|
243
|
+
return this.server?.port ?? this.config.port
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private async serveStaticMounts(req: Request): Promise<Response | null> {
|
|
247
|
+
const mounts = this.config.staticMounts
|
|
248
|
+
if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
const pathname = decodeURIComponent(new URL(req.url).pathname)
|
|
252
|
+
|
|
253
|
+
for (const mount of mounts) {
|
|
254
|
+
if (
|
|
255
|
+
pathname !== mount.urlPrefix &&
|
|
256
|
+
!pathname.startsWith(`${mount.urlPrefix}/`)
|
|
257
|
+
) {
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
const key = pathname.slice(mount.urlPrefix.length).replace(/^\/+/, '')
|
|
261
|
+
const file = await this.resolveStaticFile(mount, key)
|
|
262
|
+
if (file) {
|
|
263
|
+
return new Response(req.method === 'HEAD' ? null : file)
|
|
264
|
+
}
|
|
265
|
+
if (mount.spaFallback) {
|
|
266
|
+
const index = await this.resolveStaticFile(mount, 'index.html')
|
|
267
|
+
if (index) {
|
|
268
|
+
return new Response(req.method === 'HEAD' ? null : index)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return new Response('Not Found', { status: 404 })
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async resolveStaticFile(
|
|
278
|
+
mount: StaticMount,
|
|
279
|
+
key: string
|
|
280
|
+
): Promise<ReturnType<typeof Bun.file> | null> {
|
|
281
|
+
const directory = resolve(mount.directory)
|
|
282
|
+
const targetPath =
|
|
283
|
+
key === '' ? resolve(directory, 'index.html') : resolve(directory, key)
|
|
284
|
+
if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
|
|
285
|
+
return null
|
|
286
|
+
}
|
|
287
|
+
let file = Bun.file(targetPath)
|
|
288
|
+
if (!(await file.exists())) {
|
|
289
|
+
const indexPath = resolve(targetPath, 'index.html')
|
|
290
|
+
if (!indexPath.startsWith(`${directory}/`)) {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
file = Bun.file(indexPath)
|
|
294
|
+
if (!(await file.exists())) {
|
|
295
|
+
return null
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return file
|
|
299
|
+
}
|
|
300
|
+
|
|
228
301
|
public async stop(): Promise<void> {
|
|
229
302
|
await this.server?.stop()
|
|
230
303
|
this.server = null
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, test, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
import { setSingletonServices } from '@pikku/core'
|
|
7
|
+
import { resetPikkuState } from '@pikku/core/internal'
|
|
8
|
+
import type { Logger } from '@pikku/core/services'
|
|
9
|
+
import { PikkuBunServer } from './pikku-bun-server.js'
|
|
10
|
+
|
|
11
|
+
const noopLogger = {
|
|
12
|
+
info: () => {},
|
|
13
|
+
error: () => {},
|
|
14
|
+
warn: () => {},
|
|
15
|
+
debug: () => {},
|
|
16
|
+
trace: () => {},
|
|
17
|
+
setLevel: () => {},
|
|
18
|
+
} as unknown as Logger
|
|
19
|
+
|
|
20
|
+
describe('PikkuBunServer static mounts', () => {
|
|
21
|
+
let server: PikkuBunServer
|
|
22
|
+
let tmpDir: string
|
|
23
|
+
let origin: string
|
|
24
|
+
|
|
25
|
+
before(async () => {
|
|
26
|
+
resetPikkuState()
|
|
27
|
+
setSingletonServices({
|
|
28
|
+
logger: noopLogger,
|
|
29
|
+
schema: {
|
|
30
|
+
compileSchema: () => {},
|
|
31
|
+
getSchemaNames: () => new Set<string>(),
|
|
32
|
+
},
|
|
33
|
+
} as any)
|
|
34
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-static-'))
|
|
35
|
+
writeFileSync(
|
|
36
|
+
join(tmpDir, 'index.html'),
|
|
37
|
+
'<!doctype html><title>console</title>'
|
|
38
|
+
)
|
|
39
|
+
mkdirSync(join(tmpDir, 'assets'))
|
|
40
|
+
writeFileSync(join(tmpDir, 'assets', 'app.js'), 'console.log("app")')
|
|
41
|
+
|
|
42
|
+
server = new PikkuBunServer(
|
|
43
|
+
{
|
|
44
|
+
port: 0,
|
|
45
|
+
hostname: '127.0.0.1',
|
|
46
|
+
staticMounts: [
|
|
47
|
+
{ urlPrefix: '/console', directory: tmpDir, spaFallback: true },
|
|
48
|
+
],
|
|
49
|
+
} as any,
|
|
50
|
+
noopLogger
|
|
51
|
+
)
|
|
52
|
+
await server.init()
|
|
53
|
+
await server.start()
|
|
54
|
+
origin = `http://127.0.0.1:${server.port}`
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
after(async () => {
|
|
58
|
+
await server.stop()
|
|
59
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('serves files under the prefix with a content type', async () => {
|
|
63
|
+
const response = await fetch(`${origin}/console/assets/app.js`)
|
|
64
|
+
assert.equal(response.status, 200)
|
|
65
|
+
assert.match(response.headers.get('content-type') ?? '', /javascript/)
|
|
66
|
+
assert.equal(await response.text(), 'console.log("app")')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('serves index.html at the mount root, with and without trailing slash', async () => {
|
|
70
|
+
for (const path of ['/console', '/console/']) {
|
|
71
|
+
const response = await fetch(`${origin}${path}`)
|
|
72
|
+
assert.equal(response.status, 200)
|
|
73
|
+
assert.match(await response.text(), /<title>console<\/title>/)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('SPA fallback serves index.html for unknown paths under the prefix', async () => {
|
|
78
|
+
const response = await fetch(`${origin}/console/tests/userflows?id=x`)
|
|
79
|
+
assert.equal(response.status, 200)
|
|
80
|
+
assert.match(await response.text(), /<title>console<\/title>/)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('does not intercept paths outside the prefix', async () => {
|
|
84
|
+
const response = await fetch(`${origin}/consolerelated`)
|
|
85
|
+
assert.equal(response.status, 404)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('does not intercept non-GET methods', async () => {
|
|
89
|
+
const response = await fetch(`${origin}/console/assets/app.js`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
})
|
|
92
|
+
assert.equal(response.status, 404)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('blocks path traversal out of the mount directory', async () => {
|
|
96
|
+
writeFileSync(join(tmpDir, '..', 'pikku-bun-static-secret.txt'), 'secret')
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(
|
|
99
|
+
`${origin}/console/%2e%2e/pikku-bun-static-secret.txt`
|
|
100
|
+
)
|
|
101
|
+
assert.notEqual(response.status, 200)
|
|
102
|
+
} finally {
|
|
103
|
+
rmSync(join(tmpDir, '..', 'pikku-bun-static-secret.txt'), {
|
|
104
|
+
force: true,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|