@pikku/bun-server 0.12.3 → 0.12.5

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 CHANGED
@@ -1,5 +1,61 @@
1
1
  # @pikku/bun-server
2
2
 
3
+ ## 0.12.5
4
+
5
+ ### Patch Changes
6
+
7
+ - ea8aabf: Serve `LocalContent` uploads and signed reads under Bun.
8
+
9
+ `LocalContent` hands the browser a `PUT <uploadUrlPrefix>/<key>` upload URL and a signed
10
+ `GET <assetUrlPrefix>/<key>` read URL, but it is a `ContentService` and cannot answer
11
+ either — something in the serving path has to. Only `@pikku/node-http-server` did. The
12
+ same project served under Bun handed out upload URLs that 404ed, with nothing naming the
13
+ cause: the config was accepted, the service was constructed, and the URLs looked right.
14
+
15
+ `@pikku/core` now exports `createLocalContentRequestHandler` from
16
+ `@pikku/core/services/local-content-request-handler` — the server half of `LocalContent`,
17
+ expressed in Web `Request`/`Response` so every runtime shares one implementation of the
18
+ signature check rather than each re-deriving it. It returns `null` for anything that is
19
+ not a content request, which is the caller's signal to carry on with its normal routing.
20
+
21
+ `PikkuBunServer` accepts `config.content` and a `contentSigningJWT` option, mirroring
22
+ `PikkuNodeHTTPServer`, and answers both prefixes ahead of static mounts and routing.
23
+ `BunServerRunner` was dropping `contentSigningJWT` on the floor, which silently disabled
24
+ signed asset reads for every Bun project even once the prefixes were served — the config
25
+ arrived, the service that verifies its signatures did not.
26
+
27
+ Signed reads are refused unless every claim matches, the path included: without that, a
28
+ signature minted for one asset would read any other.
29
+
30
+ - Updated dependencies [32277d5]
31
+ - Updated dependencies [ea8aabf]
32
+ - Updated dependencies [33e96ab]
33
+ - Updated dependencies [fd72e58]
34
+ - Updated dependencies [fd72e58]
35
+ - Updated dependencies [fd72e58]
36
+ - Updated dependencies [894b2f8]
37
+ - Updated dependencies [dd19aa7]
38
+ - Updated dependencies [50ec500]
39
+ - @pikku/core@0.12.75
40
+
41
+ ## 0.12.4
42
+
43
+ ### Patch Changes
44
+
45
+ - d4a2503: Serve the console same-origin at /console (#861). Both dev servers gain
46
+ `staticMounts` (prefix → directory static serving with SPA fallback and path
47
+ traversal protection); `pikku serve` / `pikku dev` mount the bundled console
48
+ app at `/console` on the API port whenever it is bundled, so auth cookies are
49
+ first-party and no `?server=` param is needed. The console is built with
50
+ `base: '/console/'` (its router already derives the basename from BASE_URL).
51
+ The separate `--console <port>` static server is removed; `pikku console`
52
+ serves the bundle under /console and redirects the root there.
53
+ - Updated dependencies [61c9ce9]
54
+ - Updated dependencies [f1f39f8]
55
+ - Updated dependencies [c45e98d]
56
+ - Updated dependencies [472a349]
57
+ - @pikku/core@0.12.52
58
+
3
59
  ## 0.12.3
4
60
 
5
61
  ### Patch Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 - present Yasser Fadl and Pikku contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # @pikku/bun-server
2
+
3
+ Pikku server for Bun, built on `Bun.serve`. Includes a Bun-native event hub and
4
+ optional MCP support.
5
+
6
+ For Node use `@pikku/node-http-server`.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ bun add @pikku/bun-server
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```typescript
17
+ import { PikkuBunServer } from '@pikku/bun-server'
18
+
19
+ import './.pikku/pikku-bootstrap.gen.js'
20
+
21
+ const server = new PikkuBunServer(config, logger)
22
+
23
+ await server.init()
24
+ await server.start()
25
+ ```
26
+
27
+ Pass `{ eventHub, mcpJson, mcpPath }` as the third argument to override the
28
+ default `BunEventHubService` or enable MCP.
29
+
30
+ ## Docs
31
+
32
+ https://pikku.dev/docs
@@ -1,11 +1,19 @@
1
1
  import type { CoreConfig } from '@pikku/core';
2
- import type { Logger } from '@pikku/core/services';
2
+ import type { JWTService, Logger } from '@pikku/core/services';
3
+ import type { LocalContentConfig } from '@pikku/core/services/local-content';
3
4
  import { type RunHTTPWiringOptions } from '@pikku/core/http';
4
5
  import { BunEventHubService } from './bun-event-hub-service.js';
6
+ export type StaticMount = {
7
+ urlPrefix: string;
8
+ directory: string;
9
+ spaFallback?: boolean;
10
+ };
5
11
  export type BunServerConfig = CoreConfig & {
6
12
  port: number;
7
13
  hostname?: string;
14
+ staticMounts?: StaticMount[];
8
15
  healthCheckPath?: string;
16
+ content?: LocalContentConfig;
9
17
  };
10
18
  export type PikkuBunServerOptions = RunHTTPWiringOptions & {
11
19
  eventHub?: BunEventHubService;
@@ -15,6 +23,7 @@ export type PikkuBunServerOptions = RunHTTPWiringOptions & {
15
23
  prompts?: unknown[];
16
24
  };
17
25
  mcpPath?: string;
26
+ contentSigningJWT?: JWTService;
18
27
  };
19
28
  export declare class PikkuBunServer {
20
29
  private readonly config;
@@ -25,10 +34,14 @@ export declare class PikkuBunServer {
25
34
  private readonly mcpJson?;
26
35
  private readonly mcpPath;
27
36
  private mcpHandler?;
37
+ private readonly localContent?;
28
38
  constructor(config: BunServerConfig, logger: Logger, options?: PikkuBunServerOptions);
29
39
  init(): Promise<void>;
30
40
  private initMCP;
31
41
  start(): Promise<void>;
42
+ get port(): number;
43
+ private serveStaticMounts;
44
+ private resolveStaticFile;
32
45
  stop(): Promise<void>;
33
46
  enableExitOnSignals(): void;
34
47
  }
@@ -1,8 +1,11 @@
1
1
  import { stopSingletonServices } from '@pikku/core';
2
+ import { pikkuState } from '@pikku/core/internal';
3
+ import { createLocalContentRequestHandler, } from '@pikku/core/services/local-content-request-handler';
2
4
  import { fetchData, PikkuFetchHTTPRequest, PikkuFetchHTTPResponse, logRoutes as logRegisterRoutes, } from '@pikku/core/http';
3
5
  import { logChannels } from '@pikku/core/channel';
4
6
  import { runLocalChannel } from '@pikku/core/channel/local';
5
7
  import { compileAllSchemas } from '@pikku/core/schema';
8
+ import { resolve } from 'node:path';
6
9
  import { BunEventHubService } from './bun-event-hub-service.js';
7
10
  const isSerializable = (data) => !(typeof data === 'string' ||
8
11
  data instanceof ArrayBuffer ||
@@ -23,14 +26,23 @@ export class PikkuBunServer {
23
26
  mcpJson;
24
27
  mcpPath;
25
28
  mcpHandler;
29
+ localContent;
26
30
  constructor(config, logger, options = {}) {
27
31
  this.config = config;
28
32
  this.logger = logger;
29
- const { eventHub, mcpJson, mcpPath, ...httpOptions } = options;
33
+ const { eventHub, mcpJson, mcpPath, contentSigningJWT, ...httpOptions } = options;
30
34
  this.eventHub = eventHub ?? new BunEventHubService();
31
35
  this.mcpJson = mcpJson;
32
36
  this.mcpPath = mcpPath ?? '/mcp';
33
37
  this.options = httpOptions;
38
+ this.localContent = config.content
39
+ ? createLocalContentRequestHandler({
40
+ content: config.content,
41
+ logger,
42
+ getJWT: () => contentSigningJWT ??
43
+ pikkuState(null, 'package', 'singletonServices')?.jwt,
44
+ })
45
+ : undefined;
34
46
  }
35
47
  async init() {
36
48
  compileAllSchemas(this.logger);
@@ -101,6 +113,14 @@ export class PikkuBunServer {
101
113
  return await mcpHandler(req);
102
114
  }
103
115
  }
116
+ const contentResponse = await this.localContent?.(req);
117
+ if (contentResponse) {
118
+ return contentResponse;
119
+ }
120
+ const staticResponse = await this.serveStaticMounts(req);
121
+ if (staticResponse) {
122
+ return staticResponse;
123
+ }
104
124
  const pikkuReq = new PikkuFetchHTTPRequest(req);
105
125
  const pikkuRes = new PikkuFetchHTTPResponse();
106
126
  await fetchData(pikkuReq, pikkuRes, {
@@ -150,6 +170,54 @@ export class PikkuBunServer {
150
170
  eventHub.setServer(this.server);
151
171
  logger.info(`pikku-bun-server: listening on http://${config.hostname ?? 'localhost'}:${config.port}`);
152
172
  }
173
+ get port() {
174
+ return this.server?.port ?? this.config.port;
175
+ }
176
+ async serveStaticMounts(req) {
177
+ const mounts = this.config.staticMounts;
178
+ if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
179
+ return null;
180
+ }
181
+ const pathname = decodeURIComponent(new URL(req.url).pathname);
182
+ for (const mount of mounts) {
183
+ if (pathname !== mount.urlPrefix &&
184
+ !pathname.startsWith(`${mount.urlPrefix}/`)) {
185
+ continue;
186
+ }
187
+ const key = pathname.slice(mount.urlPrefix.length).replace(/^\/+/, '');
188
+ const file = await this.resolveStaticFile(mount, key);
189
+ if (file) {
190
+ return new Response(req.method === 'HEAD' ? null : file);
191
+ }
192
+ if (mount.spaFallback) {
193
+ const index = await this.resolveStaticFile(mount, 'index.html');
194
+ if (index) {
195
+ return new Response(req.method === 'HEAD' ? null : index);
196
+ }
197
+ }
198
+ return new Response('Not Found', { status: 404 });
199
+ }
200
+ return null;
201
+ }
202
+ async resolveStaticFile(mount, key) {
203
+ const directory = resolve(mount.directory);
204
+ const targetPath = key === '' ? resolve(directory, 'index.html') : resolve(directory, key);
205
+ if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
206
+ return null;
207
+ }
208
+ let file = Bun.file(targetPath);
209
+ if (!(await file.exists())) {
210
+ const indexPath = resolve(targetPath, 'index.html');
211
+ if (!indexPath.startsWith(`${directory}/`)) {
212
+ return null;
213
+ }
214
+ file = Bun.file(indexPath);
215
+ if (!(await file.exists())) {
216
+ return null;
217
+ }
218
+ }
219
+ return file;
220
+ }
153
221
  async stop() {
154
222
  await this.server?.stop();
155
223
  this.server = null;
@@ -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",
3
+ "version": "0.12.5",
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.44",
19
+ "@pikku/core": "^0.12.75",
20
20
  "@pikku/modelcontextprotocol": "^0.12.6"
21
21
  },
22
22
  "peerDependenciesMeta": {
@@ -0,0 +1,193 @@
1
+ import { describe, test, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import {
4
+ mkdtempSync,
5
+ mkdirSync,
6
+ writeFileSync,
7
+ readFileSync,
8
+ rmSync,
9
+ } from 'node:fs'
10
+ import { tmpdir } from 'node:os'
11
+ import { join } from 'node:path'
12
+ import { setSingletonServices } from '@pikku/core'
13
+ import { resetPikkuState } from '@pikku/core/internal'
14
+ import { LocalContent } from '@pikku/core/services/local-content'
15
+ import type { JWTService, Logger } from '@pikku/core/services'
16
+ import { PikkuBunServer } from './pikku-bun-server.js'
17
+
18
+ const noopLogger = {
19
+ info: () => {},
20
+ error: () => {},
21
+ warn: () => {},
22
+ debug: () => {},
23
+ trace: () => {},
24
+ setLevel: () => {},
25
+ } as unknown as Logger
26
+
27
+ /**
28
+ * Signs and verifies with a plain base64 round-trip. The point of these tests is
29
+ * that the server checks the SAME claims the signer wrote, not that any
30
+ * particular algorithm is used — a real JWT here would only slow them down.
31
+ */
32
+ const fakeJWT = {
33
+ encode: async (_expiry: string, payload: unknown) =>
34
+ Buffer.from(JSON.stringify(payload)).toString('base64url'),
35
+ decode: async (token: string) =>
36
+ JSON.parse(Buffer.from(token, 'base64url').toString()),
37
+ } as unknown as JWTService
38
+
39
+ describe('PikkuBunServer local content', () => {
40
+ let server: PikkuBunServer
41
+ let content: LocalContent
42
+ let tmpDir: string
43
+ let origin: string
44
+
45
+ before(async () => {
46
+ resetPikkuState()
47
+ setSingletonServices({
48
+ logger: noopLogger,
49
+ schema: {
50
+ compileSchema: () => {},
51
+ getSchemaNames: () => new Set<string>(),
52
+ },
53
+ } as any)
54
+ tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-content-'))
55
+ mkdirSync(join(tmpDir, 'bucket'), { recursive: true })
56
+ writeFileSync(join(tmpDir, 'bucket', 'existing.bin'), 'already here')
57
+
58
+ const contentConfig = {
59
+ localFileUploadPath: tmpDir,
60
+ uploadUrlPrefix: '/upload',
61
+ assetUrlPrefix: '/assets',
62
+ sizeLimit: '1mb',
63
+ }
64
+ // The service under test on the client side: the URLs it hands out are
65
+ // exactly what the server must answer, so they are not hand-written here.
66
+ content = new LocalContent(contentConfig, noopLogger, fakeJWT)
67
+
68
+ server = new PikkuBunServer(
69
+ {
70
+ port: 0,
71
+ hostname: '127.0.0.1',
72
+ content: contentConfig,
73
+ } as any,
74
+ noopLogger,
75
+ { contentSigningJWT: fakeJWT }
76
+ )
77
+ await server.init()
78
+ await server.start()
79
+ origin = `http://127.0.0.1:${server.port}`
80
+ })
81
+
82
+ after(async () => {
83
+ await server.stop()
84
+ rmSync(tmpDir, { recursive: true, force: true })
85
+ })
86
+
87
+ test('answers the upload URL the content service hands out', async () => {
88
+ const { uploadUrl } = await content.getUploadURL({
89
+ bucket: 'bucket',
90
+ fileKey: 'nested/file.bin',
91
+ contentType: 'application/octet-stream',
92
+ } as any)
93
+
94
+ const response = await fetch(`${origin}${uploadUrl}`, {
95
+ method: 'PUT',
96
+ body: 'uploaded bytes',
97
+ })
98
+
99
+ assert.equal(response.status, 200)
100
+ assert.equal(
101
+ readFileSync(join(tmpDir, 'bucket', 'nested', 'file.bin'), 'utf8'),
102
+ 'uploaded bytes'
103
+ )
104
+ })
105
+
106
+ test('serves an asset through the signed URL the content service produces', async () => {
107
+ const signed = await content.signContentKey({
108
+ bucket: 'bucket',
109
+ contentKey: 'existing.bin',
110
+ dateLessThan: new Date(Date.now() + 60_000),
111
+ } as any)
112
+
113
+ const response = await fetch(`${origin}${signed}`)
114
+ assert.equal(response.status, 200)
115
+ assert.equal(await response.text(), 'already here')
116
+ })
117
+
118
+ test('refuses an unsigned asset read', async () => {
119
+ const response = await fetch(`${origin}/assets/bucket/existing.bin`)
120
+ assert.equal(response.status, 403)
121
+ })
122
+
123
+ test('refuses a signature minted for a different asset', async () => {
124
+ // The signature is valid and unexpired — only the path differs. Without the
125
+ // path claim being checked, one signed URL would read the whole bucket.
126
+ const signed = await content.signContentKey({
127
+ bucket: 'bucket',
128
+ contentKey: 'existing.bin',
129
+ dateLessThan: new Date(Date.now() + 60_000),
130
+ } as any)
131
+ writeFileSync(join(tmpDir, 'bucket', 'other.bin'), 'other')
132
+
133
+ const query = signed.slice(signed.indexOf('?'))
134
+ const response = await fetch(`${origin}/assets/bucket/other.bin${query}`)
135
+ assert.equal(response.status, 403)
136
+ })
137
+
138
+ test('refuses an expired signature', async () => {
139
+ const signed = await content.signContentKey({
140
+ bucket: 'bucket',
141
+ contentKey: 'existing.bin',
142
+ dateLessThan: new Date(Date.now() - 1_000),
143
+ } as any)
144
+
145
+ const response = await fetch(`${origin}${signed}`)
146
+ assert.equal(response.status, 403)
147
+ })
148
+
149
+ // `fetch` normalises `..` out of the path before the request leaves the
150
+ // client, so this cannot drive the guard itself — that is covered directly in
151
+ // core's local-content-request-handler.test.ts. What it does prove is that
152
+ // nothing in the server path un-normalises it back into a write.
153
+ test('does not write outside the content directory', async () => {
154
+ writeFileSync(join(tmpDir, '..', 'pikku-bun-content-secret.txt'), 'secret')
155
+ try {
156
+ const response = await fetch(
157
+ `${origin}/upload/%2e%2e/pikku-bun-content-secret.txt`,
158
+ { method: 'PUT', body: 'overwritten' }
159
+ )
160
+ assert.notEqual(response.status, 200)
161
+ assert.equal(
162
+ readFileSync(
163
+ join(tmpDir, '..', 'pikku-bun-content-secret.txt'),
164
+ 'utf8'
165
+ ),
166
+ 'secret'
167
+ )
168
+ } finally {
169
+ rmSync(join(tmpDir, '..', 'pikku-bun-content-secret.txt'), {
170
+ force: true,
171
+ })
172
+ }
173
+ })
174
+
175
+ test('rejects an upload over the size limit', async () => {
176
+ const { uploadUrl } = await content.getUploadURL({
177
+ bucket: 'bucket',
178
+ fileKey: 'too-big.bin',
179
+ contentType: 'application/octet-stream',
180
+ } as any)
181
+
182
+ const response = await fetch(`${origin}${uploadUrl}`, {
183
+ method: 'PUT',
184
+ body: Buffer.alloc(1024 * 1024 + 1),
185
+ })
186
+ assert.equal(response.status, 413)
187
+ })
188
+
189
+ test('leaves non-content paths to the router', async () => {
190
+ const response = await fetch(`${origin}/uploadsomething`)
191
+ assert.equal(response.status, 404)
192
+ })
193
+ })
@@ -2,7 +2,13 @@ import type { Server as BunServer, ServerWebSocket } from 'bun'
2
2
 
3
3
  import type { CoreConfig } from '@pikku/core'
4
4
  import { stopSingletonServices } from '@pikku/core'
5
- import type { Logger } from '@pikku/core/services'
5
+ import type { JWTService, Logger } from '@pikku/core/services'
6
+ import { pikkuState } from '@pikku/core/internal'
7
+ import type { LocalContentConfig } from '@pikku/core/services/local-content'
8
+ import {
9
+ createLocalContentRequestHandler,
10
+ type LocalContentRequestHandler,
11
+ } from '@pikku/core/services/local-content-request-handler'
6
12
  import {
7
13
  fetchData,
8
14
  PikkuFetchHTTPRequest,
@@ -15,12 +21,28 @@ import type { PikkuLocalChannelHandler } from '@pikku/core/channel/local'
15
21
  import { runLocalChannel } from '@pikku/core/channel/local'
16
22
  import { compileAllSchemas } from '@pikku/core/schema'
17
23
 
24
+ import { resolve } from 'node:path'
25
+
18
26
  import { BunEventHubService } from './bun-event-hub-service.js'
19
27
 
28
+ export type StaticMount = {
29
+ urlPrefix: string
30
+ directory: string
31
+ spaFallback?: boolean
32
+ }
33
+
20
34
  export type BunServerConfig = CoreConfig & {
21
35
  port: number
22
36
  hostname?: string
37
+ staticMounts?: StaticMount[]
23
38
  healthCheckPath?: string
39
+ /**
40
+ * Serve the `LocalContent` upload and asset prefixes. Pass the same config the
41
+ * `LocalContent` service was built from — it is that service which hands the
42
+ * browser these URLs, and a prefix that disagrees produces a 404 naming
43
+ * nothing. Mirrors `PikkuNodeHTTPServerConfig.content`.
44
+ */
45
+ content?: LocalContentConfig
24
46
  }
25
47
 
26
48
  export type PikkuBunServerOptions = RunHTTPWiringOptions & {
@@ -44,6 +66,14 @@ export type PikkuBunServerOptions = RunHTTPWiringOptions & {
44
66
  * Path the MCP server is mounted at when `mcpJson` is provided. Default `/mcp`.
45
67
  */
46
68
  mcpPath?: string
69
+ /**
70
+ * The JWT service `LocalContent` signs asset URLs with. Required to serve
71
+ * `config.content`'s asset prefix: without it every signed read is refused,
72
+ * since an unverifiable signature is no signature. Falls back to
73
+ * `singletonServices.jwt`. Mirrors
74
+ * `PikkuNodeHTTPServerOptions.contentSigningJWT`.
75
+ */
76
+ contentSigningJWT?: JWTService
47
77
  }
48
78
 
49
79
  type WsData = { channelHandler: PikkuLocalChannelHandler }
@@ -75,17 +105,30 @@ export class PikkuBunServer {
75
105
  private readonly mcpJson?: PikkuBunServerOptions['mcpJson']
76
106
  private readonly mcpPath: string
77
107
  private mcpHandler?: (request: Request) => Promise<Response>
108
+ private readonly localContent?: LocalContentRequestHandler
78
109
 
79
110
  constructor(
80
111
  private readonly config: BunServerConfig,
81
112
  private readonly logger: Logger,
82
113
  options: PikkuBunServerOptions = {}
83
114
  ) {
84
- const { eventHub, mcpJson, mcpPath, ...httpOptions } = options
115
+ const { eventHub, mcpJson, mcpPath, contentSigningJWT, ...httpOptions } =
116
+ options
85
117
  this.eventHub = eventHub ?? new BunEventHubService()
86
118
  this.mcpJson = mcpJson
87
119
  this.mcpPath = mcpPath ?? '/mcp'
88
120
  this.options = httpOptions
121
+ this.localContent = config.content
122
+ ? createLocalContentRequestHandler({
123
+ content: config.content,
124
+ logger,
125
+ // Resolved per request: `singletonServices` is populated after the
126
+ // server is constructed, so reading it here would always miss.
127
+ getJWT: () =>
128
+ contentSigningJWT ??
129
+ pikkuState(null, 'package', 'singletonServices')?.jwt,
130
+ })
131
+ : undefined
89
132
  }
90
133
 
91
134
  public async init(): Promise<void> {
@@ -167,6 +210,19 @@ export class PikkuBunServer {
167
210
  }
168
211
  }
169
212
 
213
+ // Ahead of static mounts and routing: these prefixes belong to the
214
+ // content service, and `fetchData` would answer them with a 404 since
215
+ // no wiring claims them.
216
+ const contentResponse = await this.localContent?.(req)
217
+ if (contentResponse) {
218
+ return contentResponse
219
+ }
220
+
221
+ const staticResponse = await this.serveStaticMounts(req)
222
+ if (staticResponse) {
223
+ return staticResponse
224
+ }
225
+
170
226
  const pikkuReq = new PikkuFetchHTTPRequest(req)
171
227
  const pikkuRes = new PikkuFetchHTTPResponse()
172
228
  await fetchData(pikkuReq, pikkuRes, {
@@ -225,6 +281,65 @@ export class PikkuBunServer {
225
281
  )
226
282
  }
227
283
 
284
+ public get port(): number {
285
+ return this.server?.port ?? this.config.port
286
+ }
287
+
288
+ private async serveStaticMounts(req: Request): Promise<Response | null> {
289
+ const mounts = this.config.staticMounts
290
+ if (!mounts?.length || (req.method !== 'GET' && req.method !== 'HEAD')) {
291
+ return null
292
+ }
293
+ const pathname = decodeURIComponent(new URL(req.url).pathname)
294
+
295
+ for (const mount of mounts) {
296
+ if (
297
+ pathname !== mount.urlPrefix &&
298
+ !pathname.startsWith(`${mount.urlPrefix}/`)
299
+ ) {
300
+ continue
301
+ }
302
+ const key = pathname.slice(mount.urlPrefix.length).replace(/^\/+/, '')
303
+ const file = await this.resolveStaticFile(mount, key)
304
+ if (file) {
305
+ return new Response(req.method === 'HEAD' ? null : file)
306
+ }
307
+ if (mount.spaFallback) {
308
+ const index = await this.resolveStaticFile(mount, 'index.html')
309
+ if (index) {
310
+ return new Response(req.method === 'HEAD' ? null : index)
311
+ }
312
+ }
313
+ return new Response('Not Found', { status: 404 })
314
+ }
315
+
316
+ return null
317
+ }
318
+
319
+ private async resolveStaticFile(
320
+ mount: StaticMount,
321
+ key: string
322
+ ): Promise<ReturnType<typeof Bun.file> | null> {
323
+ const directory = resolve(mount.directory)
324
+ const targetPath =
325
+ key === '' ? resolve(directory, 'index.html') : resolve(directory, key)
326
+ if (targetPath !== directory && !targetPath.startsWith(`${directory}/`)) {
327
+ return null
328
+ }
329
+ let file = Bun.file(targetPath)
330
+ if (!(await file.exists())) {
331
+ const indexPath = resolve(targetPath, 'index.html')
332
+ if (!indexPath.startsWith(`${directory}/`)) {
333
+ return null
334
+ }
335
+ file = Bun.file(indexPath)
336
+ if (!(await file.exists())) {
337
+ return null
338
+ }
339
+ }
340
+ return file
341
+ }
342
+
228
343
  public async stop(): Promise<void> {
229
344
  await this.server?.stop()
230
345
  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/scenarios?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
+ })