@pikku/bun-server 0.12.11 → 0.12.13
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 +62 -0
- package/dist/pikku-bun-server.d.ts +14 -1
- package/dist/pikku-bun-server.js +53 -30
- package/dist/pikku-bun-server.js.map +1 -1
- package/package.json +12 -8
- package/run-tests.sh +35 -21
- package/src/bun-event-hub-service.test.ts +0 -77
- package/src/bun-event-hub-service.ts +0 -54
- package/src/index.ts +0 -2
- package/src/local-content.test.ts +0 -193
- package/src/pikku-bun-server.test.ts +0 -62
- package/src/pikku-bun-server.ts +0 -484
- package/src/static-mount.test.ts +0 -330
- package/tsconfig.json +0 -18
- package/tsconfig.tsbuildinfo +0 -1
package/src/static-mount.test.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
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/state'
|
|
7
|
-
import { pikkuState, resetPikkuState } from '@pikku/core/state'
|
|
8
|
-
import { httpRouter, wireHTTP } from '@pikku/core/http'
|
|
9
|
-
import { addFunction } from '@pikku/core/function'
|
|
10
|
-
import type { Logger } from '@pikku/core/services'
|
|
11
|
-
import { PikkuBunServer } from './pikku-bun-server.js'
|
|
12
|
-
|
|
13
|
-
const noopLogger = {
|
|
14
|
-
info: () => {},
|
|
15
|
-
error: () => {},
|
|
16
|
-
warn: () => {},
|
|
17
|
-
debug: () => {},
|
|
18
|
-
trace: () => {},
|
|
19
|
-
setLevel: () => {},
|
|
20
|
-
} as unknown as Logger
|
|
21
|
-
|
|
22
|
-
describe('PikkuBunServer static mounts', () => {
|
|
23
|
-
let server: PikkuBunServer
|
|
24
|
-
let tmpDir: string
|
|
25
|
-
let origin: string
|
|
26
|
-
|
|
27
|
-
before(async () => {
|
|
28
|
-
resetPikkuState()
|
|
29
|
-
setSingletonServices({
|
|
30
|
-
logger: noopLogger,
|
|
31
|
-
schema: {
|
|
32
|
-
compileSchema: () => {},
|
|
33
|
-
getSchemaNames: () => new Set<string>(),
|
|
34
|
-
},
|
|
35
|
-
} as any)
|
|
36
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'pikku-bun-static-'))
|
|
37
|
-
writeFileSync(
|
|
38
|
-
join(tmpDir, 'index.html'),
|
|
39
|
-
'<!doctype html><title>console</title>'
|
|
40
|
-
)
|
|
41
|
-
mkdirSync(join(tmpDir, 'assets'))
|
|
42
|
-
writeFileSync(join(tmpDir, 'assets', 'app.js'), 'console.log("app")')
|
|
43
|
-
|
|
44
|
-
server = new PikkuBunServer(
|
|
45
|
-
{
|
|
46
|
-
port: 0,
|
|
47
|
-
hostname: '127.0.0.1',
|
|
48
|
-
staticMounts: [
|
|
49
|
-
{ urlPrefix: '/console', directory: tmpDir, spaFallback: true },
|
|
50
|
-
],
|
|
51
|
-
} as any,
|
|
52
|
-
noopLogger
|
|
53
|
-
)
|
|
54
|
-
await server.init()
|
|
55
|
-
await server.start()
|
|
56
|
-
origin = `http://127.0.0.1:${server.port}`
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
after(async () => {
|
|
60
|
-
await server.stop()
|
|
61
|
-
rmSync(tmpDir, { recursive: true, force: true })
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test('serves files under the prefix with a content type', async () => {
|
|
65
|
-
const response = await fetch(`${origin}/console/assets/app.js`)
|
|
66
|
-
assert.equal(response.status, 200)
|
|
67
|
-
assert.match(response.headers.get('content-type') ?? '', /javascript/)
|
|
68
|
-
assert.equal(await response.text(), 'console.log("app")')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('serves index.html at the mount root, with and without trailing slash', async () => {
|
|
72
|
-
for (const path of ['/console', '/console/']) {
|
|
73
|
-
const response = await fetch(`${origin}${path}`)
|
|
74
|
-
assert.equal(response.status, 200)
|
|
75
|
-
assert.match(await response.text(), /<title>console<\/title>/)
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('SPA fallback serves index.html for unknown paths under the prefix', async () => {
|
|
80
|
-
const response = await fetch(`${origin}/console/scenarios?id=x`)
|
|
81
|
-
assert.equal(response.status, 200)
|
|
82
|
-
assert.match(await response.text(), /<title>console<\/title>/)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('does not intercept paths outside the prefix', async () => {
|
|
86
|
-
const response = await fetch(`${origin}/consolerelated`)
|
|
87
|
-
assert.equal(response.status, 404)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('does not intercept non-GET methods', async () => {
|
|
91
|
-
const response = await fetch(`${origin}/console/assets/app.js`, {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
})
|
|
94
|
-
assert.equal(response.status, 404)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('blocks path traversal out of the mount directory', async () => {
|
|
98
|
-
writeFileSync(join(tmpDir, '..', 'pikku-bun-static-secret.txt'), 'secret')
|
|
99
|
-
try {
|
|
100
|
-
const response = await fetch(
|
|
101
|
-
`${origin}/console/%2e%2e/pikku-bun-static-secret.txt`
|
|
102
|
-
)
|
|
103
|
-
assert.notEqual(response.status, 200)
|
|
104
|
-
} finally {
|
|
105
|
-
rmSync(join(tmpDir, '..', 'pikku-bun-static-secret.txt'), {
|
|
106
|
-
force: true,
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
})
|
|
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
|
-
})
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "./src",
|
|
5
|
-
"module": "Node18",
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"target": "esnext",
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"types": ["bun"]
|
|
10
|
-
},
|
|
11
|
-
"include": ["src/**/*.ts"],
|
|
12
|
-
"exclude": ["**/*.test.ts", "node_modules", "dist"],
|
|
13
|
-
"references": [
|
|
14
|
-
{
|
|
15
|
-
"path": "../../core/tsconfig.json"
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
package/tsconfig.tsbuildinfo
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"root":["./src/bun-event-hub-service.ts","./src/index.ts","./src/pikku-bun-server.ts"],"version":"6.0.3"}
|