@bakery-framework/core 1.2.3 → 2.0.0-alpha.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/package.json +2 -2
- package/src/cache/shared-db.ts +7 -9
- package/src/client/globals.d.ts +1 -1
- package/src/client/utils.ts +41 -9
- package/src/compiler/compiler.ts +458 -39
- package/src/compiler/prompt-tracker.ts +19 -2
- package/src/compiler/tsconfig-sync.ts +116 -45
- package/src/core/bakery.ts +11 -12
- package/src/core/context.ts +42 -8
- package/src/core/index.ts +18 -0
- package/src/core/init.ts +40 -31
- package/src/global.d.ts +24 -6
- package/src/handlers/assets/nm.ts +74 -15
- package/src/handlers/assets/static.ts +53 -10
- package/src/handlers/core/$base.ts +71 -8
- package/src/handlers/core/$dynamic.ts +21 -10
- package/src/handlers/core/$error.ts +10 -1
- package/src/handlers/core/$registry.ts +31 -2
- package/src/handlers/core/$routing.ts +82 -11
- package/src/handlers/routes/api.ts +2 -2
- package/src/handlers/routes/proxy.ts +2 -1
- package/src/logger/serve-log.ts +21 -0
- package/src/plugins/types.ts +7 -5
- package/src/router.ts +5 -5
- package/src/session.ts +0 -3
- package/src/shared.d.ts +7 -0
- package/src/types.d.ts +11 -0
- package/src/utils/fs.ts +29 -0
- package/src/utils/http/authorize.ts +136 -0
- package/src/utils/http/body.ts +2 -2
- package/src/utils/http/credential.ts +70 -0
- package/src/utils/http/dom.ts +73 -64
- package/src/utils/http/etag.ts +27 -0
- package/src/utils/http/html.ts +16 -5
- package/src/utils/http/index.ts +3 -0
- package/src/utils/http/url.ts +35 -0
- package/src/utils/isomorphic/misc.ts +16 -0
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { unlinkSync } from 'node:fs'
|
|
2
|
-
import {
|
|
2
|
+
import { cacheDir } from '../core/context'
|
|
3
3
|
import { Try } from '../utils/common/try'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* **`cacheDir` comes from `core/context`, not from `Bakery` — do not change it
|
|
7
|
+
* back.** `logger.ts` imports this module, so importing `core/bakery` here
|
|
8
|
+
* completed a cycle:
|
|
9
|
+
*
|
|
10
|
+
* logger.ts -> prompt-tracker.ts -> core/bakery.ts -> core/config.ts
|
|
11
|
+
* -> logger/serve-log.ts -> logger.ts
|
|
12
|
+
*
|
|
13
|
+
* `serve-log.ts` runs `new Logger('serve')` at module scope, so whichever
|
|
14
|
+
* import arrived first found `Logger` still in its temporal dead zone. That
|
|
15
|
+
* shipped in 1.2.3 and made `import '@bakery-framework/core'` throw
|
|
16
|
+
* `ReferenceError: Cannot access 'Logger' before initialization` from a clean
|
|
17
|
+
* install — see `tests/module-cycle.test.ts`.
|
|
18
|
+
*
|
|
19
|
+
* `core/context` holds the same single definition of the path and imports
|
|
20
|
+
* nothing that reaches the logger.
|
|
21
|
+
*/
|
|
5
22
|
export const PromptTracker = {
|
|
6
23
|
getFilePath(pid: number): string {
|
|
7
24
|
// Derived, not written out: this lands in the cache directory, which the
|
|
8
25
|
// framework wipes wholesale, and a stale literal here would leave marker
|
|
9
26
|
// files behind in a directory nothing sweeps.
|
|
10
|
-
return `${
|
|
27
|
+
return `${cacheDir()}/.prompt-active-${pid}`
|
|
11
28
|
},
|
|
12
29
|
|
|
13
30
|
async isActive(pid: number): Promise<boolean> {
|
|
@@ -61,6 +61,13 @@ const PROJECT_DIR = fs.resolve(APP_DIR, '.cache/tsconfig')
|
|
|
61
61
|
* Before this existed, one config covered everything and `Bun.hash()` in a
|
|
62
62
|
* client file typechecked clean and failed in the browser.
|
|
63
63
|
*
|
|
64
|
+
* The includes overlap the app's own tsconfig, and that is fine *because
|
|
65
|
+
* nothing references these projects*: each is a standalone projection of one
|
|
66
|
+
* concern, pointed at directly (`tsc -p .cache/tsconfig/client.json`), and
|
|
67
|
+
* subtracting the app's include would gut them into projects that check
|
|
68
|
+
* nothing. What must never come back is the `references` wiring that composed
|
|
69
|
+
* them with the app project — see {@link syncTSConfigProjects}.
|
|
70
|
+
*
|
|
64
71
|
* Globs are app-relative here and rewritten to be relative to the generated
|
|
65
72
|
* file, which sits two levels down.
|
|
66
73
|
*/
|
|
@@ -322,22 +329,81 @@ function mapPaths(paths: MapOf<string[]>): MapOf<string[]> {
|
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
/**
|
|
325
|
-
*
|
|
332
|
+
* A `references` entry the generator wrote in a previous release, as opposed
|
|
333
|
+
* to one the developer owns. Ours always pointed into `.cache/tsconfig/`, and
|
|
334
|
+
* nothing else has a reason to: `.cache/` is the disposable runtime directory,
|
|
335
|
+
* and every project inside it is regenerated on boot.
|
|
336
|
+
*/
|
|
337
|
+
const RE_GENERATED_REF = /^(\.[\\/])?\.cache[\\/]tsconfig[\\/]/
|
|
338
|
+
|
|
339
|
+
function isGeneratedReference(entry: unknown): boolean {
|
|
340
|
+
if (typeof entry !== 'object' || entry === null) return false
|
|
341
|
+
const path = (entry as { path?: unknown }).path
|
|
342
|
+
return typeof path === 'string' && RE_GENERATED_REF.test(path)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The root config with the generator's own `references` removed, or `null`
|
|
347
|
+
* when there is nothing to repair.
|
|
348
|
+
*
|
|
349
|
+
* Until 2026-08-27 `syncTSConfigProjects` *added* those references, wiring the
|
|
350
|
+
* generated projects into the app's project graph. That glue is what broke
|
|
351
|
+
* `tsc -p <app>` for every consumer who had booted once. Measured directly
|
|
352
|
+
* (TypeScript 6.0.3):
|
|
353
|
+
*
|
|
354
|
+
* - `tsc -p` verifies every referenced project whenever the referencing
|
|
355
|
+
* program has input files of its own: a reference to a non-composite
|
|
356
|
+
* project is TS6306 and to a `noEmit` one TS6310 — **even when the
|
|
357
|
+
* referenced project's include is disjoint from the root's, and even when
|
|
358
|
+
* it matches zero files**. No include shape survives.
|
|
359
|
+
* - Each root file a referenced project also claims is redirected to that
|
|
360
|
+
* project's declaration output, which `noEmit` guarantees was never built:
|
|
361
|
+
* TS6305, once per overlapping file — `src/**`, `server.config.ts`, every
|
|
362
|
+
* `.tsx` page.
|
|
363
|
+
*
|
|
364
|
+
* The generated projects extend `noEmit` bases and rely on
|
|
365
|
+
* `allowImportingTsExtensions`, so they are unbuildable by design. Making them
|
|
366
|
+
* `composite` instead would trade the errors above for a `tsc -b` build-order
|
|
367
|
+
* requirement no consumer runs — and TS6305 would still fire for any root file
|
|
368
|
+
* importing into one while unbuilt. The only reference shape `tsc -p`
|
|
369
|
+
* tolerates from a root that has files of its own is no reference at all, so
|
|
370
|
+
* the projects stand alone now and this strips what previous releases wrote.
|
|
371
|
+
*
|
|
372
|
+
* Everything the developer owns is preserved: only entries into
|
|
373
|
+
* `.cache/tsconfig/` are removed, a real project reference (say `../shared`)
|
|
374
|
+
* stays, and the `references` key itself survives unless the generator wrote
|
|
375
|
+
* every entry in it.
|
|
376
|
+
*
|
|
377
|
+
* Pure, and exported, so the repair can be tested without a function that
|
|
378
|
+
* writes to `process.cwd()`. The property that matters is negative — *no key
|
|
379
|
+
* the developer wrote is lost* — which a shape assertion on the source cannot
|
|
380
|
+
* check.
|
|
381
|
+
*/
|
|
382
|
+
export function stripGeneratedReferences(
|
|
383
|
+
current: Record<string, unknown>,
|
|
384
|
+
): Record<string, unknown> | null {
|
|
385
|
+
const refs = current.references
|
|
386
|
+
if (!Array.isArray(refs)) return null
|
|
387
|
+
|
|
388
|
+
const kept = refs.filter(entry => !isGeneratedReference(entry))
|
|
389
|
+
if (kept.length === refs.length) return null
|
|
390
|
+
|
|
391
|
+
const repaired = { ...current }
|
|
392
|
+
if (kept.length) repaired.references = kept
|
|
393
|
+
else delete repaired.references
|
|
394
|
+
return repaired
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The root config written when the app has none at all.
|
|
326
399
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
400
|
+
* The generated projects do not help Bun's runtime — it reads
|
|
401
|
+
* `compilerOptions.jsx*` from the root `tsconfig.json` and follows `extends`
|
|
402
|
+
* only into a relative path, never a package specifier — so the file this
|
|
403
|
+
* writes has to carry the JSX options itself, inline, exactly as the
|
|
404
|
+
* scaffolder spells them.
|
|
331
405
|
*/
|
|
332
|
-
export function
|
|
333
|
-
current: Record<string, unknown> | null,
|
|
334
|
-
references: { path: string }[],
|
|
335
|
-
): Record<string, unknown> {
|
|
336
|
-
// Spread first so `references` is the only key this owns.
|
|
337
|
-
if (current) return { ...current, references }
|
|
338
|
-
|
|
339
|
-
// No root config at all. The generated projects do not help Bun's runtime, so
|
|
340
|
-
// the one this writes has to carry the JSX options itself.
|
|
406
|
+
export function defaultRootConfig(): Record<string, unknown> {
|
|
341
407
|
return {
|
|
342
408
|
extends: '@bakery-framework/core/tsconfig.server.json',
|
|
343
409
|
compilerOptions: {
|
|
@@ -345,58 +411,63 @@ export function mergeRootConfig(
|
|
|
345
411
|
jsxFactory: 'createElement',
|
|
346
412
|
jsxFragmentFactory: 'Fragment',
|
|
347
413
|
},
|
|
348
|
-
references,
|
|
349
414
|
}
|
|
350
415
|
}
|
|
351
416
|
|
|
352
417
|
/**
|
|
353
|
-
* Generate the project configs and
|
|
418
|
+
* Generate the project configs, and keep the app's root tsconfig viable —
|
|
419
|
+
* created with the runtime JSX options when the app has none, and stripped of
|
|
420
|
+
* the `references` a previous release wrote into it.
|
|
354
421
|
*
|
|
355
422
|
* Separate from `syncTSConfigPaths` because an app can reasonably want one and
|
|
356
423
|
* not the other: the paths sync has existed for a long time and rewrites a file
|
|
357
424
|
* people keep in git, while this owns a directory nobody edits.
|
|
358
425
|
*
|
|
359
|
-
* **
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* `
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
* `
|
|
371
|
-
*
|
|
372
|
-
* `SafeHtml` string. Nothing logs, nothing throws, the status is fine.
|
|
426
|
+
* **The generated projects are standalone on purpose; the root config does not
|
|
427
|
+
* reference them.** They exist for direct invocation against the one concern
|
|
428
|
+
* each covers: `vue-tsc -p .cache/tsconfig/vue.json` is the only way an SFC
|
|
429
|
+
* typechecks at all, `tsc -p .cache/tsconfig/client.json` proves browser code
|
|
430
|
+
* clean of `Bun.*`, and a plugin's project carries its own ambients the same
|
|
431
|
+
* way. Their `include` deliberately overlaps the app's own project — they are
|
|
432
|
+
* alternate projections of the same files, used *instead of* the root for
|
|
433
|
+
* their slice, never composed with it. Wiring them in as `references` broke
|
|
434
|
+
* `tsc -p <app>` for any consumer who had booted once (TS6305/6306/6310 — the
|
|
435
|
+
* measurements are on {@link stripGeneratedReferences}), and what the wiring
|
|
436
|
+
* bought was less than it looked: tsserver routes a file to the project whose
|
|
437
|
+
* `include` claims it, so for everything a scaffolded root claims — `src/**`,
|
|
438
|
+
* `server.config.ts`, `orm/**` — the reference walk never ran anyway.
|
|
373
439
|
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
440
|
+
* Two earlier lessons still bind the root-config half. It used to be
|
|
441
|
+
* *replaced* with a references-only stub, which silently broke every `.tsx`
|
|
442
|
+
* page: Bun's runtime reads `compilerOptions.jsx*` from the root and does not
|
|
443
|
+
* follow `references`, so pages transpiled against the automatic JSX runtime
|
|
444
|
+
* and `GET /` answered 200 with a JSON-encoded React element tree. Hence
|
|
445
|
+
* {@link defaultRootConfig} when no root exists, and surgical repair — never
|
|
446
|
+
* wholesale rewrite — when one does. And a boot that dirties git every time
|
|
447
|
+
* trains people to ignore the diff, so an already-clean root is not rewritten.
|
|
378
448
|
*/
|
|
379
449
|
export async function syncTSConfigProjects(): Promise<void> {
|
|
380
450
|
try {
|
|
381
451
|
const written = await writeProjects(buildPaths())
|
|
382
452
|
|
|
383
|
-
const references = written.map(name => ({
|
|
384
|
-
path: `./.cache/tsconfig/${name}.json`,
|
|
385
|
-
}))
|
|
386
|
-
|
|
387
453
|
const current = fs.exists(APP_CONFIG_PATH)
|
|
388
454
|
? parseJSONC(await Bun.file(APP_CONFIG_PATH).text())
|
|
389
455
|
: null
|
|
390
456
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
457
|
+
if (!current) {
|
|
458
|
+
await Bun.write(
|
|
459
|
+
APP_CONFIG_PATH,
|
|
460
|
+
`${JSON.stringify(defaultRootConfig(), null, 2)}\n`,
|
|
461
|
+
)
|
|
462
|
+
serveLog.TSCONFIG_PROJECTS_WRITTEN({ count: String(written.length) })
|
|
463
|
+
return
|
|
464
|
+
}
|
|
395
465
|
|
|
396
|
-
const
|
|
466
|
+
const repaired = stripGeneratedReferences(current)
|
|
467
|
+
if (!repaired) return
|
|
397
468
|
|
|
398
|
-
await Bun.write(APP_CONFIG_PATH, `${JSON.stringify(
|
|
399
|
-
serveLog.
|
|
469
|
+
await Bun.write(APP_CONFIG_PATH, `${JSON.stringify(repaired, null, 2)}\n`)
|
|
470
|
+
serveLog.TSCONFIG_REFERENCES_REMOVED()
|
|
400
471
|
} catch (err: any) {
|
|
401
472
|
serveLog.UNHANDLED_ERR({
|
|
402
473
|
error: `TSConfig project sync error: ${errorMsg(err)}`,
|
package/src/core/bakery.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { HandlerMap } from '../handlers/core/$registry'
|
|
|
2
2
|
import { fs } from '../utils/fs'
|
|
3
3
|
import { SharedMemoryPool } from '../utils/shared-pool'
|
|
4
4
|
import { getConfig, resolveHostname } from './config'
|
|
5
|
-
import { getAppVersion, hostStore } from './context'
|
|
5
|
+
import { cacheDir, dataDir, getAppVersion, hostStore } from './context'
|
|
6
6
|
|
|
7
7
|
export type { HostContext } from './context'
|
|
8
8
|
export { hostStore } from './context'
|
|
@@ -70,17 +70,16 @@ export const Bakery: globalThis.Bakery = {
|
|
|
70
70
|
return getAppVersion()
|
|
71
71
|
},
|
|
72
72
|
sharedPool: new SharedMemoryPool(1024 * 1024),
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
dataDir: `${fs.cwd}/bakery`,
|
|
73
|
+
// Defined in `core/context.ts`, which is low enough that a module needing a
|
|
74
|
+
// path does not have to import `Bakery` to get one — reaching them through
|
|
75
|
+
// here is what closed the logger cycle. These stay the reading surface for
|
|
76
|
+
// application and framework code; context is the single definition.
|
|
77
|
+
//
|
|
78
|
+
// Called here rather than forwarded through a getter, so these remain plain
|
|
79
|
+
// writable properties: `nm.test.ts` repoints them at a fixture tree, which a
|
|
80
|
+
// getter turns into `TypeError: Attempted to assign to readonly property`.
|
|
81
|
+
cacheDir: cacheDir(),
|
|
82
|
+
dataDir: dataDir(),
|
|
84
83
|
startNs: Bun.nanoseconds(),
|
|
85
84
|
handlers: {
|
|
86
85
|
fetch: new HandlerMap(),
|
package/src/core/context.ts
CHANGED
|
@@ -29,6 +29,40 @@ export type HostContext = {
|
|
|
29
29
|
|
|
30
30
|
export const hostStore = new AsyncLocalStorage<HostContext>()
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The two runtime directories, defined here rather than on `Bakery`.
|
|
34
|
+
*
|
|
35
|
+
* `Bakery.cacheDir` / `Bakery.dataDir` remain the way application and framework
|
|
36
|
+
* code reads them — these are the single definition those two forward to, and
|
|
37
|
+
* still the only writer of either path. They live in this module because it is
|
|
38
|
+
* low enough to be imported without pulling in `core/config`, and therefore
|
|
39
|
+
* without pulling in the logger: `compiler/prompt-tracker.ts` needs the cache
|
|
40
|
+
* directory and reaching it through `Bakery` closed a module cycle that made
|
|
41
|
+
* the whole package unimportable. See the note on `prompt-tracker.ts`.
|
|
42
|
+
*
|
|
43
|
+
* **Functions, not constants, and that is not a style choice.** `utils/fs.ts`
|
|
44
|
+
* imports this module for `hostStore`, so the two are themselves a cycle: a
|
|
45
|
+
* top-level `` `${fs.cwd}/.cache` `` here is evaluated with `fs` still
|
|
46
|
+
* uninitialised whenever `core/context` is reached first, and throws
|
|
47
|
+
* `TypeError: undefined is not an object`. Reading `fs.cwd` at call time is
|
|
48
|
+
* what makes the order irrelevant.
|
|
49
|
+
*
|
|
50
|
+
* The disposable directory is the hidden one, and the precious one is not. This
|
|
51
|
+
* is the reverse of the old `.bakery/cache` + `.data` pairing, and the reversal
|
|
52
|
+
* is the whole point: `.cache` is wiped by the framework itself on every version
|
|
53
|
+
* bump and dev<->prod switch, so a `rm -rf .*` or a "clean out the dotfiles"
|
|
54
|
+
* sweep does exactly what the framework already does. The database is not
|
|
55
|
+
* disposable, so it does not live behind a leading dot where such a sweep can
|
|
56
|
+
* reach it, and never under `.cache` — clearing a cache must not destroy data.
|
|
57
|
+
*/
|
|
58
|
+
export function cacheDir(): string {
|
|
59
|
+
return `${fs.cwd}/.cache`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function dataDir(): string {
|
|
63
|
+
return `${fs.cwd}/bakery`
|
|
64
|
+
}
|
|
65
|
+
|
|
32
66
|
/**
|
|
33
67
|
* `matchBlocked`, deduplicated within the current request.
|
|
34
68
|
*
|
|
@@ -51,14 +85,6 @@ export function matchBlockedCached(
|
|
|
51
85
|
return verdict
|
|
52
86
|
}
|
|
53
87
|
|
|
54
|
-
/**
|
|
55
|
-
* The **application's** version, from `<cwd>/package.json`.
|
|
56
|
-
*
|
|
57
|
-
* Named `getBakeryVersion` until 2026-08-09, which is exactly the wrong name:
|
|
58
|
-
* it reads the package.json of whatever is being served, not the framework's.
|
|
59
|
-
* That misnomer hid a real bug for as long as it existed — see
|
|
60
|
-
* {@link getFrameworkVersion}.
|
|
61
|
-
*/
|
|
62
88
|
/**
|
|
63
89
|
* What each version reader returns when it cannot read its manifest.
|
|
64
90
|
*
|
|
@@ -78,6 +104,14 @@ const UNKNOWN_APP = '0.0.0-unknown-app'
|
|
|
78
104
|
const UNKNOWN_FW = '0.0.0-unknown-framework'
|
|
79
105
|
|
|
80
106
|
let _appVersion: string | null = null
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The **application's** version, from `<cwd>/package.json`.
|
|
110
|
+
*
|
|
111
|
+
* Not the framework's — this reads the manifest of whatever is being served.
|
|
112
|
+
* The distinction is load-bearing for cache invalidation; see
|
|
113
|
+
* {@link getFrameworkVersion}.
|
|
114
|
+
*/
|
|
81
115
|
export function getAppVersion() {
|
|
82
116
|
if (_appVersion) return _appVersion
|
|
83
117
|
try {
|
package/src/core/index.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { Case, is, Math2, match, Try } from '../utils/common'
|
|
|
4
4
|
import { encodeSSE, response, sse } from '../utils/http'
|
|
5
5
|
import Bakery, { getHostname, hostKey, hostStore } from './bakery'
|
|
6
6
|
import { getConfig, NOOP } from './config'
|
|
7
|
+
// `./context` is already in this barrel's graph — line 5 reaches it through
|
|
8
|
+
// `./bakery`, which re-exports `hostStore` from exactly here — so naming it
|
|
9
|
+
// adds no module edge, only a name.
|
|
10
|
+
import { getFrameworkVersion } from './context'
|
|
7
11
|
import { createElement, Fragment, html } from './jsx'
|
|
8
12
|
|
|
9
13
|
export const defineConfig = <T extends AppConfig>(config: T): T => config
|
|
@@ -19,6 +23,7 @@ export type {
|
|
|
19
23
|
MixedPromise,
|
|
20
24
|
RouteBody,
|
|
21
25
|
RouteHandler,
|
|
26
|
+
RouteParam,
|
|
22
27
|
RouteResponse,
|
|
23
28
|
Wrapped,
|
|
24
29
|
} from '../types'
|
|
@@ -62,6 +67,19 @@ export {
|
|
|
62
67
|
encodeSSE,
|
|
63
68
|
Fragment,
|
|
64
69
|
getConfig,
|
|
70
|
+
/**
|
|
71
|
+
* The version of `@bakery-framework/core` itself, read from its own manifest.
|
|
72
|
+
*
|
|
73
|
+
* **Not the app's version**, which is what `import.meta.env.BAKERY_VERSION`
|
|
74
|
+
* and `getAppVersion()` report — the compiler reads those from
|
|
75
|
+
* `<cwd>/package.json`, so in an application they answer with the
|
|
76
|
+
* application's number. The name is a long-standing misnomer and this is the
|
|
77
|
+
* one that means what "Bakery version" sounds like it means.
|
|
78
|
+
*
|
|
79
|
+
* Exported because a plugin rendering framework chrome has no other way to
|
|
80
|
+
* ask. The dashboard's footer showed a hardcoded `v3` for want of it.
|
|
81
|
+
*/
|
|
82
|
+
getFrameworkVersion,
|
|
65
83
|
// Multi-host helpers. Documented in docs/configuration/multi-host.md, and
|
|
66
84
|
// the only reason `./core/bakery` had to be a subpath of its own.
|
|
67
85
|
getHostname,
|
package/src/core/init.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomId } from '../utils/isomorphic/misc'
|
|
1
2
|
import { createElement, Fragment, html } from './jsx'
|
|
2
3
|
|
|
3
4
|
const hasDevWorkerArg = process.argv.includes('--dev-worker')
|
|
@@ -26,40 +27,43 @@ const getArgValue = (name: string) => {
|
|
|
26
27
|
const threadId = process.env.THREAD_ID ?? getArgValue('--thread-id') ?? '0'
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
*
|
|
30
|
+
* The mode flags are **strings on `process.env`** — `'1'` for true, `''` for
|
|
31
|
+
* false. They were booleans behind an accessor pair until Bun 1.4.
|
|
30
32
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
33
|
+
* **Bun 1.4.0 rejects accessor descriptors on `process.env` outright.**
|
|
34
|
+
* `Object.defineProperty(process.env, 'X', { get })` throws
|
|
35
|
+
* `ERR_INVALID_OBJECT_DEFINE_PROPERTY`. This block runs at import time in every
|
|
36
|
+
* entry, so the whole framework died on `import` under current Bun — a
|
|
37
|
+
* scaffolded app could not reach step 2 of its own quick start. Data
|
|
38
|
+
* descriptors are accepted and coerce anyway (`{ value: false }` reads back as
|
|
39
|
+
* `"false"`), so a boolean here is no longer expressible at all; and
|
|
40
|
+
* `import.meta.env === process.env` in Bun, so there is no second object to put
|
|
41
|
+
* one on.
|
|
42
|
+
*
|
|
43
|
+
* **`'1'` / `''`, never `'true'` / `'false'`.** Truthiness has to survive the
|
|
44
|
+
* coercion: `"false"` is a truthy string, so every `if (import.meta.env.DEV)`
|
|
45
|
+
* in the codebase would have inverted silently rather than failed. `''` also
|
|
46
|
+
* keeps the key *present* — `'PROD' in process.env` stays true — which is what
|
|
47
|
+
* separates "explicitly not production" from "never booted", a distinction
|
|
48
|
+
* `utils/http/authorize.ts` depends on to fail closed.
|
|
49
|
+
*
|
|
50
|
+
* Plain assignment, so `threads.ts` can still assign `THREAD_ID = '0'` on the
|
|
51
|
+
* single-worker/clamped path (deliberately not `THREAD_WORKER` — a cluster of
|
|
52
|
+
* one must keep full-size caches). That assignment is why these were an
|
|
53
|
+
* accessor *pair* rather than a bare getter: a getter with no setter is
|
|
54
|
+
* readonly, the assignment threw, and the throw was swallowed by a `Try(...)`
|
|
55
|
+
* that made a dead code path look deliberate.
|
|
40
56
|
*/
|
|
41
|
-
const
|
|
42
|
-
let value = initial
|
|
43
|
-
return {
|
|
44
|
-
get: () => value,
|
|
45
|
-
set: (next: any) => {
|
|
46
|
-
value = next
|
|
47
|
-
},
|
|
48
|
-
enumerable: true,
|
|
49
|
-
configurable: true,
|
|
50
|
-
}
|
|
51
|
-
}
|
|
57
|
+
const flag = (on: boolean) => (on ? '1' : '')
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
MODE: accessor(mode),
|
|
62
|
-
})
|
|
59
|
+
process.env.DEV = flag(isDev)
|
|
60
|
+
process.env.TEST = flag(isTest)
|
|
61
|
+
process.env.PROD = flag(!isDev && !hasDevWorkerArg)
|
|
62
|
+
process.env.WORKER = flag(hasDevWorkerArg || isThreadWorker)
|
|
63
|
+
process.env.DEV_WORKER = flag(hasDevWorkerArg)
|
|
64
|
+
process.env.THREAD_WORKER = flag(isThreadWorker)
|
|
65
|
+
process.env.THREAD_ID = threadId
|
|
66
|
+
process.env.MODE = mode
|
|
63
67
|
|
|
64
68
|
/**
|
|
65
69
|
* "This process is the worker of a *development* server."
|
|
@@ -84,6 +88,11 @@ Object.assign(globalThis, {
|
|
|
84
88
|
createElement,
|
|
85
89
|
Fragment,
|
|
86
90
|
html,
|
|
91
|
+
// The same value the browser runtime binds (`client/utils.ts`), so code
|
|
92
|
+
// that moves between an SFC's browser script and its server block — where
|
|
93
|
+
// it runs as a bare global either way — does not lose the name. Declared
|
|
94
|
+
// once, in `shared.d.ts`.
|
|
95
|
+
randomId,
|
|
87
96
|
})
|
|
88
97
|
|
|
89
98
|
process.on('SIGHUP', () => {})
|
package/src/global.d.ts
CHANGED
|
@@ -342,13 +342,31 @@ declare global {
|
|
|
342
342
|
// `ImportMeta.env` with its own incompatible shape, which is TS2687/TS2717 —
|
|
343
343
|
// suppressed, like everything else in a .d.ts, by skipLibCheck.
|
|
344
344
|
interface ImportMetaEnv {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
345
|
+
// `'1'` or `''`, not `true` or `false`. These live on `process.env`, and
|
|
346
|
+
// Bun 1.4 stopped accepting accessor descriptors there — a boolean is no
|
|
347
|
+
// longer expressible on that object at all, since data descriptors coerce
|
|
348
|
+
// too. See the encoding block in `core/init.ts`.
|
|
349
|
+
//
|
|
350
|
+
// Typed as the literal pair rather than `string` on purpose: it makes
|
|
351
|
+
// `import.meta.env.PROD !== false` a *compile* error. That comparison was
|
|
352
|
+
// real, in the dashboard's fail-closed gate, and under the string encoding
|
|
353
|
+
// it is true for every value including the `''` a development server sets —
|
|
354
|
+
// so the gate would have denied on loopback in development while still
|
|
355
|
+
// reading as correct. A type that only said `string` would not have caught
|
|
356
|
+
// it either.
|
|
357
|
+
//
|
|
358
|
+
// In a **browser** bundle these are real booleans: `compiler.ts` substitutes
|
|
359
|
+
// `JSON.stringify(!!import.meta.env.DEV)` at build time, so the literal
|
|
360
|
+
// `true`/`false` is inlined and this declaration is a slight lie there. It
|
|
361
|
+
// is the harmless direction — both encodings agree on truthiness, which is
|
|
362
|
+
// all client code tests — and the server is where the mistakes happen.
|
|
363
|
+
readonly DEV: '1' | ''
|
|
364
|
+
readonly PROD: '1' | ''
|
|
365
|
+
readonly WORKER: '1' | ''
|
|
366
|
+
readonly DEV_WORKER: '1' | ''
|
|
367
|
+
readonly THREAD_WORKER: '1' | ''
|
|
350
368
|
readonly THREAD_ID: string
|
|
351
|
-
readonly TEST:
|
|
369
|
+
readonly TEST: '1' | ''
|
|
352
370
|
readonly MODE: 'production' | 'development' | 'dev-worker' | 'thread-worker'
|
|
353
371
|
// `readonly SERVE_ROOT: string` was declared here. Nothing defines it —
|
|
354
372
|
// not init.ts, not the compiler's `defines` — and nothing reads it, so any
|