@briancray/belte 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/belte.ts +10 -9
- package/package.json +1 -1
- package/src/buildCli.ts +46 -48
- package/src/bundleApp.ts +5 -3
- package/src/compile.ts +11 -1
- package/src/controlServerWorker.ts +14 -91
- package/src/lib/bundle/spawnEmbeddedServer.ts +61 -0
- package/src/lib/cli/connectToServer.ts +23 -0
- package/src/lib/cli/dispatchCommand.ts +71 -0
- package/src/lib/cli/loadEnvFromBinaryDir.ts +1 -1
- package/src/lib/cli/printHelp.ts +9 -3
- package/src/lib/cli/printSessionHelp.ts +27 -0
- package/src/lib/cli/printSessionStatus.ts +21 -0
- package/src/lib/cli/printTrimmed.ts +8 -0
- package/src/lib/cli/printValue.ts +10 -0
- package/src/lib/cli/resolveCliTarget.ts +48 -0
- package/src/lib/cli/runCli.ts +96 -78
- package/src/lib/cli/runSession.ts +105 -0
- package/src/lib/cli/startLocalInstance.ts +14 -0
- package/src/lib/cli/tokenizeLine.ts +51 -0
- package/src/lib/cli/types/CliTarget.ts +13 -0
- package/src/lib/server/cli/handleCliDownload.ts +26 -9
- package/src/lib/server/runtime/createServer.ts +143 -125
- package/src/lib/server/runtime/listenOnOpenPort.ts +36 -0
- package/src/lib/shared/clearLastConnection.ts +7 -0
- package/src/lib/shared/lastConnectionPath.ts +7 -0
- package/src/lib/shared/readLastConnection.ts +18 -0
- package/src/lib/shared/runningAsStandaloneBinary.ts +13 -0
- package/src/lib/shared/types/LastConnection.ts +9 -0
- package/src/lib/shared/writeLastConnection.ts +13 -0
- package/src/serverEntry.ts +12 -6
|
@@ -27,9 +27,9 @@ import { createAssetHeaderCache } from './createAssetHeaderCache.ts'
|
|
|
27
27
|
import { createPublicAssetServer } from './createPublicAssetServer.ts'
|
|
28
28
|
import { createRouteDispatcher } from './createRouteDispatcher.ts'
|
|
29
29
|
import { disableIdleTimeoutForStream } from './disableIdleTimeoutForStream.ts'
|
|
30
|
-
import { findOpenPort } from './findOpenPort.ts'
|
|
31
30
|
import { globToPathSet } from './globToPathSet.ts'
|
|
32
31
|
import { internalErrorResponse } from './internalErrorResponse.ts'
|
|
32
|
+
import { listenOnOpenPort } from './listenOnOpenPort.ts'
|
|
33
33
|
import { logBrowserOnlyRoutes } from './logBrowserOnlyRoutes.ts'
|
|
34
34
|
import { parseIdleTimeout } from './parseIdleTimeout.ts'
|
|
35
35
|
import { parsePort } from './parsePort.ts'
|
|
@@ -92,9 +92,9 @@ export async function createServer({
|
|
|
92
92
|
distDir = `${process.cwd()}/dist`,
|
|
93
93
|
publicDir = `${process.cwd()}/src/browser/public`,
|
|
94
94
|
resourcesDir = `${process.cwd()}/src/mcp/resources`,
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
port = parsePort(process.env.PORT)
|
|
95
|
+
// A configured PORT is honored as-is; left undefined, the real listener
|
|
96
|
+
// scans upward from 3000 at bind time (see buildServer / listenOnOpenPort).
|
|
97
|
+
port = parsePort(process.env.PORT),
|
|
98
98
|
/*
|
|
99
99
|
Bun's per-connection idle timeout in seconds (its own default is 10).
|
|
100
100
|
Surfaced for apps whose unary handlers legitimately compute longer than
|
|
@@ -310,137 +310,155 @@ export async function createServer({
|
|
|
310
310
|
a busy socket doesn't iterate JS per subscriber per message.
|
|
311
311
|
*/
|
|
312
312
|
const socketDispatcher = createSocketDispatcher(sockets)
|
|
313
|
-
// Server<unknown> pins Bun's WebSocketData generic so upgrade({ data: {} }) typechecks.
|
|
314
|
-
const server: Server<unknown> = Bun.serve({
|
|
315
|
-
port,
|
|
316
|
-
idleTimeout,
|
|
317
313
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
314
|
+
/*
|
|
315
|
+
Bind the real server on `boundPort`. Only the port varies between scan
|
|
316
|
+
attempts, so the rest of the config lives inline and just the port is spread
|
|
317
|
+
in — passing the literal straight to Bun.serve keeps contextual typing of the
|
|
318
|
+
websocket handlers (and Server<unknown> pins Bun's WebSocketData generic so
|
|
319
|
+
upgrade({ data: {} }) typechecks).
|
|
320
|
+
*/
|
|
321
|
+
const bindAt = (boundPort: number): Server<unknown> =>
|
|
322
|
+
Bun.serve({
|
|
323
|
+
port: boundPort,
|
|
324
|
+
idleTimeout,
|
|
325
|
+
|
|
326
|
+
websocket: {
|
|
327
|
+
open(ws) {
|
|
328
|
+
socketDispatcher.open(ws)
|
|
329
|
+
},
|
|
330
|
+
message(ws, data) {
|
|
331
|
+
socketDispatcher.message(ws, data)
|
|
332
|
+
},
|
|
333
|
+
close(ws) {
|
|
334
|
+
socketDispatcher.close(ws)
|
|
335
|
+
},
|
|
327
336
|
},
|
|
328
|
-
},
|
|
329
337
|
|
|
330
|
-
|
|
338
|
+
routes,
|
|
331
339
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
if (url.pathname === SOCKETS_PATH) {
|
|
352
|
-
if (bunServer.upgrade(req, { data: {} })) {
|
|
353
|
-
return undefined as unknown as Response
|
|
340
|
+
async fetch(req, bunServer) {
|
|
341
|
+
const url = new URL(req.url)
|
|
342
|
+
/*
|
|
343
|
+
Identity probe — answered directly, ahead of any app.handle middleware,
|
|
344
|
+
so the bundle's connect screen can confirm a URL really is a belte
|
|
345
|
+
server (and which app) before pointing the desktop window at it. It
|
|
346
|
+
must stay reachable even when the app guards everything behind auth,
|
|
347
|
+
hence the early return that bypasses dispatchRequest.
|
|
348
|
+
*/
|
|
349
|
+
if (url.pathname === IDENTITY_PATH) {
|
|
350
|
+
return Response.json(
|
|
351
|
+
{
|
|
352
|
+
belte: true,
|
|
353
|
+
name: appInfo?.name ?? cliName,
|
|
354
|
+
version: appInfo?.version ?? '0.0.0',
|
|
355
|
+
},
|
|
356
|
+
{ headers: { 'Cache-Control': NO_STORE } },
|
|
357
|
+
)
|
|
354
358
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
dispatchRequest so app.handle auth applies, like the rpc paths.
|
|
361
|
-
The socket name may contain `/` (nested files), so it's the
|
|
362
|
-
whole remaining pathname, percent-decoded.
|
|
363
|
-
*/
|
|
364
|
-
if (url.pathname.startsWith(SOCKETS_REST_PREFIX)) {
|
|
365
|
-
const name = decodeURIComponent(url.pathname.slice(SOCKETS_REST_PREFIX.length))
|
|
366
|
-
return dispatchRequest(req, {}, async () => socketDispatcher.rest(req, name))
|
|
367
|
-
}
|
|
368
|
-
if (url.pathname === MCP_PATH && mcp) {
|
|
369
|
-
return dispatchRequest(req, {}, async () => mcp.handle(req))
|
|
370
|
-
}
|
|
371
|
-
if (url.pathname === CLI_PATH) {
|
|
372
|
-
return dispatchRequest(req, {}, async () => handleCliInstall(req, cliName))
|
|
373
|
-
}
|
|
374
|
-
if (url.pathname.startsWith(CLI_DOWNLOAD_PREFIX)) {
|
|
375
|
-
const platform = url.pathname.slice(CLI_DOWNLOAD_PREFIX.length)
|
|
376
|
-
return dispatchRequest(req, {}, async () =>
|
|
377
|
-
handleCliDownload(req, platform, cliName, cliCwd),
|
|
378
|
-
)
|
|
379
|
-
}
|
|
380
|
-
if (url.pathname === OPENAPI_PATH) {
|
|
381
|
-
return dispatchRequest(req, {}, async () => {
|
|
382
|
-
await ensureRegistriesLoaded()
|
|
383
|
-
const spec = buildOpenApiSpec({
|
|
384
|
-
title: appInfo?.name ?? cliName,
|
|
385
|
-
version: appInfo?.version ?? '0.0.0',
|
|
386
|
-
})
|
|
387
|
-
return Response.json(spec, { headers: { 'Cache-Control': NO_STORE } })
|
|
388
|
-
})
|
|
389
|
-
}
|
|
390
|
-
/*
|
|
391
|
-
Static assets sidestep ALS + the per-request CacheStore + the
|
|
392
|
-
app.handle middleware: they have no need for cache() and the
|
|
393
|
-
allocation overhead matters on a cold page load that pulls
|
|
394
|
-
dozens of chunks. The global server.error() handler still
|
|
395
|
-
catches anything that goes wrong inside serveStaticAsset.
|
|
396
|
-
*/
|
|
397
|
-
if (url.pathname.startsWith('/_app/')) {
|
|
398
|
-
if (!logRequests) {
|
|
399
|
-
return serveStaticAsset(req, url)
|
|
359
|
+
if (url.pathname === SOCKETS_PATH) {
|
|
360
|
+
if (bunServer.upgrade(req, { data: {} })) {
|
|
361
|
+
return undefined as unknown as Response
|
|
362
|
+
}
|
|
363
|
+
return new Response('Upgrade failed', { status: 400 })
|
|
400
364
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
365
|
+
/*
|
|
366
|
+
HTTP face of a socket (`/__belte/sockets/<name>`) — tail over
|
|
367
|
+
SSE / JSON and publish — for the CLI and MCP. Runs through
|
|
368
|
+
dispatchRequest so app.handle auth applies, like the rpc paths.
|
|
369
|
+
The socket name may contain `/` (nested files), so it's the
|
|
370
|
+
whole remaining pathname, percent-decoded.
|
|
371
|
+
*/
|
|
372
|
+
if (url.pathname.startsWith(SOCKETS_REST_PREFIX)) {
|
|
373
|
+
const name = decodeURIComponent(url.pathname.slice(SOCKETS_REST_PREFIX.length))
|
|
374
|
+
return dispatchRequest(req, {}, async () => socketDispatcher.rest(req, name))
|
|
375
|
+
}
|
|
376
|
+
if (url.pathname === MCP_PATH && mcp) {
|
|
377
|
+
return dispatchRequest(req, {}, async () => mcp.handle(req))
|
|
378
|
+
}
|
|
379
|
+
if (url.pathname === CLI_PATH) {
|
|
380
|
+
return dispatchRequest(req, {}, async () => handleCliInstall(req, cliName))
|
|
381
|
+
}
|
|
382
|
+
if (url.pathname.startsWith(CLI_DOWNLOAD_PREFIX)) {
|
|
383
|
+
const platform = url.pathname.slice(CLI_DOWNLOAD_PREFIX.length)
|
|
384
|
+
return dispatchRequest(req, {}, async () =>
|
|
385
|
+
handleCliDownload(req, platform, cliName, cliCwd),
|
|
421
386
|
)
|
|
422
387
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
388
|
+
if (url.pathname === OPENAPI_PATH) {
|
|
389
|
+
return dispatchRequest(req, {}, async () => {
|
|
390
|
+
await ensureRegistriesLoaded()
|
|
391
|
+
const spec = buildOpenApiSpec({
|
|
392
|
+
title: appInfo?.name ?? cliName,
|
|
393
|
+
version: appInfo?.version ?? '0.0.0',
|
|
394
|
+
})
|
|
395
|
+
return Response.json(spec, { headers: { 'Cache-Control': NO_STORE } })
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
/*
|
|
399
|
+
Static assets sidestep ALS + the per-request CacheStore + the
|
|
400
|
+
app.handle middleware: they have no need for cache() and the
|
|
401
|
+
allocation overhead matters on a cold page load that pulls
|
|
402
|
+
dozens of chunks. The global server.error() handler still
|
|
403
|
+
catches anything that goes wrong inside serveStaticAsset.
|
|
404
|
+
*/
|
|
405
|
+
if (url.pathname.startsWith('/_app/')) {
|
|
406
|
+
if (!logRequests) {
|
|
407
|
+
return serveStaticAsset(req, url)
|
|
408
|
+
}
|
|
409
|
+
const start = Bun.nanoseconds()
|
|
410
|
+
const response = await serveStaticAsset(req, url)
|
|
411
|
+
const ms = (Bun.nanoseconds() - start) / 1e6
|
|
412
|
+
log.request(req.method, `${url.pathname}${url.search}`, response.status, ms)
|
|
413
|
+
return response
|
|
414
|
+
}
|
|
415
|
+
/*
|
|
416
|
+
Files under public/ are served at the site root, sidestepping
|
|
417
|
+
ALS + middleware like the /_app/ assets do. A miss returns
|
|
418
|
+
undefined so the request falls through to the 404 / middleware
|
|
419
|
+
path below.
|
|
420
|
+
*/
|
|
421
|
+
const publicResponse = await servePublicAsset(req, url)
|
|
422
|
+
if (publicResponse) {
|
|
423
|
+
if (logRequests) {
|
|
424
|
+
log.request(
|
|
425
|
+
req.method,
|
|
426
|
+
`${url.pathname}${url.search}`,
|
|
427
|
+
publicResponse.status,
|
|
428
|
+
0,
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
return publicResponse
|
|
432
|
+
}
|
|
433
|
+
/*
|
|
434
|
+
Unknown routes still run through dispatchRequest so user-defined
|
|
435
|
+
app.handle middleware can rewrite the request, serve a custom
|
|
436
|
+
404, or branch on the URL. The inner handler returns the
|
|
437
|
+
framework's default 404 when nothing intervenes.
|
|
438
|
+
*/
|
|
439
|
+
return dispatchRequest(req, {}, async () => {
|
|
440
|
+
return new Response('Not Found', {
|
|
441
|
+
status: 404,
|
|
442
|
+
headers: { 'Cache-Control': NO_STORE },
|
|
443
|
+
})
|
|
435
444
|
})
|
|
436
|
-
}
|
|
437
|
-
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
error(err) {
|
|
448
|
+
log.error(err)
|
|
449
|
+
return internalErrorResponse(err)
|
|
450
|
+
},
|
|
451
|
+
})
|
|
438
452
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
453
|
+
/*
|
|
454
|
+
A configured PORT binds that exact port — a collision surfaces loudly rather
|
|
455
|
+
than silently moving, since something connecting to the app needs a known
|
|
456
|
+
address. With none set, scan upward from 3000 binding the real listener, so
|
|
457
|
+
whichever server wins the port keeps it (no probe-release gap to lose it in,
|
|
458
|
+
which used to crash boot on EADDRINUSE instead of stepping to the next port).
|
|
459
|
+
*/
|
|
460
|
+
const server: Server<unknown> =
|
|
461
|
+
port === undefined ? listenOnOpenPort(bindAt, 3000) : bindAt(port)
|
|
444
462
|
|
|
445
463
|
/*
|
|
446
464
|
Publishes the live server through `belte/server` before invoking the
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Server } from 'bun'
|
|
2
|
+
|
|
3
|
+
// Ports tried upward from `start` before giving up and letting the kernel assign one.
|
|
4
|
+
const SCAN_RANGE = 100
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Binds the real server, scanning upward from `start` for the first free port.
|
|
8
|
+
The listener that wins a port is the one that keeps it: unlike probing a
|
|
9
|
+
throwaway server and releasing it before the real bind, this leaves no window
|
|
10
|
+
for the chosen port to be stolen in between — the gap that crashed boot on
|
|
11
|
+
EADDRINUSE instead of stepping to the next port. `bindAt` does the actual
|
|
12
|
+
Bun.serve; only an in-use port is retried, any other failure propagates. After
|
|
13
|
+
SCAN_RANGE occupied ports it binds port 0 so the kernel assigns any free port.
|
|
14
|
+
*/
|
|
15
|
+
export function listenOnOpenPort(
|
|
16
|
+
bindAt: (port: number) => Server<unknown>,
|
|
17
|
+
start: number,
|
|
18
|
+
): Server<unknown> {
|
|
19
|
+
for (let port = start; port < start + SCAN_RANGE; port++) {
|
|
20
|
+
try {
|
|
21
|
+
return bindAt(port)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!isAddressInUse(error)) {
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
26
|
+
// port in use — try the next one up
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// every candidate was taken; bind to 0 so the kernel picks a free port
|
|
30
|
+
return bindAt(0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Bun reports a taken port as an Error carrying code 'EADDRINUSE'.
|
|
34
|
+
function isAddressInUse(error: unknown): boolean {
|
|
35
|
+
return error instanceof Error && (error as { code?: string }).code === 'EADDRINUSE'
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises'
|
|
2
|
+
import { lastConnectionPath } from './lastConnectionPath.ts'
|
|
3
|
+
|
|
4
|
+
// Forgets the saved connection (the `/disconnect` reset). Missing file is a no-op.
|
|
5
|
+
export async function clearLastConnection(programName: string): Promise<void> {
|
|
6
|
+
await rm(lastConnectionPath(programName), { force: true })
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { appDataDir } from './appDataDir.ts'
|
|
3
|
+
|
|
4
|
+
// Path to the per-program last-connection record, beside the data-dir `.env`.
|
|
5
|
+
export function lastConnectionPath(programName: string): string {
|
|
6
|
+
return join(appDataDir(programName), 'last-connection.json')
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { lastConnectionPath } from './lastConnectionPath.ts'
|
|
2
|
+
import type { LastConnection } from './types/LastConnection.ts'
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Reads the saved connection intent, or undefined when none is recorded or the file
|
|
6
|
+
is unreadable/corrupt — callers treat undefined as "nothing to resume".
|
|
7
|
+
*/
|
|
8
|
+
export async function readLastConnection(programName: string): Promise<LastConnection | undefined> {
|
|
9
|
+
const file = Bun.file(lastConnectionPath(programName))
|
|
10
|
+
if (!(await file.exists())) {
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return (await file.json()) as LastConnection
|
|
15
|
+
} catch {
|
|
16
|
+
return undefined
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
True when this code runs inside a `bun build --compile` standalone executable
|
|
3
|
+
(the bundle's embedded server, or an install-tarball server binary) rather than
|
|
4
|
+
under the `bun` CLI. Bun mounts a compiled binary's embedded modules under a
|
|
5
|
+
synthetic root — `/$bunfs/…` on posix, `…~BUN…` on Windows — so `Bun.main`
|
|
6
|
+
carries that marker only in a standalone binary; under `bun dev`/`bun start`
|
|
7
|
+
it's a real on-disk path. Used to scope the bundle's data-dir/binary-dir `.env`
|
|
8
|
+
loading to the shipped app, so `bun dev`/`bun start` keep to their project-local
|
|
9
|
+
CWD `.env` alone.
|
|
10
|
+
*/
|
|
11
|
+
export function runningAsStandaloneBinary(): boolean {
|
|
12
|
+
return Bun.main.includes('$bunfs') || Bun.main.includes('~BUN')
|
|
13
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The launcher/CLI-owned record of the last connection, kept in the data dir so it
|
|
3
|
+
survives relaunch and is readable before any window or session opens. It records
|
|
4
|
+
the *intent*, not a concrete embedded URL — an embedded server picks a fresh port
|
|
5
|
+
each launch, so only `{ kind: 'embedded' }` is durable; a remote connection keeps
|
|
6
|
+
its url. resolveLaunchTarget / resolveCliTarget read it; /connect and /start write
|
|
7
|
+
it; /disconnect clears it.
|
|
8
|
+
*/
|
|
9
|
+
export type LastConnection = { kind: 'embedded' } | { kind: 'url'; url: string }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises'
|
|
2
|
+
import { appDataDir } from './appDataDir.ts'
|
|
3
|
+
import { lastConnectionPath } from './lastConnectionPath.ts'
|
|
4
|
+
import type { LastConnection } from './types/LastConnection.ts'
|
|
5
|
+
|
|
6
|
+
// Persists the connection intent, creating the data dir on first write.
|
|
7
|
+
export async function writeLastConnection(
|
|
8
|
+
programName: string,
|
|
9
|
+
value: LastConnection,
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await mkdir(appDataDir(programName), { recursive: true })
|
|
12
|
+
await Bun.write(lastConnectionPath(programName), JSON.stringify(value))
|
|
13
|
+
}
|
package/src/serverEntry.ts
CHANGED
|
@@ -29,17 +29,23 @@ import { loadEnvFromBinaryDir } from './lib/cli/loadEnvFromBinaryDir.ts'
|
|
|
29
29
|
import { createServer } from './lib/server/runtime/createServer.ts'
|
|
30
30
|
import { requestContext } from './lib/server/runtime/requestContext.ts'
|
|
31
31
|
import { loadEnvFromDataDir } from './lib/shared/loadEnvFromDataDir.ts'
|
|
32
|
+
import { runningAsStandaloneBinary } from './lib/shared/runningAsStandaloneBinary.ts'
|
|
32
33
|
import { setCacheStoreResolver } from './lib/shared/setCacheStoreResolver.ts'
|
|
33
34
|
|
|
34
35
|
/*
|
|
35
36
|
Resolve config into process.env before anything reads it (createServer reads
|
|
36
|
-
PORT, app code reads Bun.env.*).
|
|
37
|
-
over the binary-dir shipped default; both back-fill only what
|
|
38
|
-
|
|
39
|
-
data-dir `.env` is how it gets its config at all.
|
|
37
|
+
PORT, app code reads Bun.env.*). Standalone-only: data-dir first so the user's
|
|
38
|
+
saved config wins over the binary-dir shipped default; both back-fill only what
|
|
39
|
+
the shell didn't already set. A bundle launched via `open` has cwd `/`, so the
|
|
40
|
+
data-dir `.env` is how it gets its config at all. Under `bun dev`/`bun start`
|
|
41
|
+
these bundle layers don't apply — the project's own CWD `.env` (Bun-autoloaded)
|
|
42
|
+
is the config — so loading them would let a stray data-dir `PORT` defeat dev's
|
|
43
|
+
port scan.
|
|
40
44
|
*/
|
|
41
|
-
|
|
42
|
-
await
|
|
45
|
+
if (runningAsStandaloneBinary()) {
|
|
46
|
+
await loadEnvFromDataDir(cliProgramName)
|
|
47
|
+
await loadEnvFromBinaryDir()
|
|
48
|
+
}
|
|
43
49
|
|
|
44
50
|
// In a bundle, tie this server's life to the launcher's (no-op standalone).
|
|
45
51
|
exitWithParent()
|