@c9up/ream 0.2.17 → 0.2.19
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/README.md +4 -0
- package/dist/Application.d.ts +15 -0
- package/dist/Application.d.ts.map +1 -1
- package/dist/Application.js +15 -0
- package/dist/Application.js.map +1 -1
- package/dist/HttpKernel.d.ts +10 -0
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +61 -5
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +65 -15
- package/dist/Ignitor.js.map +1 -1
- package/dist/Provider.d.ts +15 -0
- package/dist/Provider.d.ts.map +1 -1
- package/dist/Provider.js.map +1 -1
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +174 -45
- package/dist/container/Container.js.map +1 -1
- package/dist/container/types.d.ts +7 -0
- package/dist/container/types.d.ts.map +1 -1
- package/dist/dev/hmr.d.ts +37 -0
- package/dist/dev/hmr.d.ts.map +1 -0
- package/dist/dev/hmr.js +72 -0
- package/dist/dev/hmr.js.map +1 -0
- package/dist/graphql/GraphQLProvider.d.ts +14 -3
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
- package/dist/graphql/GraphQLProvider.js +14 -3
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/hot.d.ts +29 -0
- package/dist/hot.d.ts.map +1 -0
- package/dist/hot.js +135 -0
- package/dist/hot.js.map +1 -0
- package/dist/http/Exception.d.ts +2 -2
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +3 -3
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/Request.d.ts +24 -3
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +40 -0
- package/dist/http/Request.js.map +1 -1
- package/dist/http/RequestValidator.d.ts +80 -0
- package/dist/http/RequestValidator.d.ts.map +1 -0
- package/dist/http/RequestValidator.js +93 -0
- package/dist/http/RequestValidator.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts +2 -2
- package/dist/middleware/Pipeline.js +2 -2
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/migrations/MigrationRegistry.d.ts +16 -0
- package/dist/migrations/MigrationRegistry.d.ts.map +1 -1
- package/dist/migrations/MigrationRegistry.js +24 -0
- package/dist/migrations/MigrationRegistry.js.map +1 -1
- package/dist/openapi/OpenApiGenerator.d.ts +16 -0
- package/dist/openapi/OpenApiGenerator.d.ts.map +1 -1
- package/dist/openapi/OpenApiGenerator.js +76 -4
- package/dist/openapi/OpenApiGenerator.js.map +1 -1
- package/dist/openapi/OpenApiProvider.d.ts +16 -5
- package/dist/openapi/OpenApiProvider.d.ts.map +1 -1
- package/dist/openapi/OpenApiProvider.js +21 -6
- package/dist/openapi/OpenApiProvider.js.map +1 -1
- package/dist/router/Router.d.ts +1 -1
- package/dist/router/Router.js +1 -1
- package/dist/rpc/RpcProvider.d.ts +13 -2
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +13 -2
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/security/CookieSigner.d.ts +3 -2
- package/dist/security/CookieSigner.d.ts.map +1 -1
- package/dist/security/CookieSigner.js +3 -2
- package/dist/security/CookieSigner.js.map +1 -1
- package/dist/storage/StaticMiddleware.d.ts +75 -7
- package/dist/storage/StaticMiddleware.d.ts.map +1 -1
- package/dist/storage/StaticMiddleware.js +257 -85
- package/dist/storage/StaticMiddleware.js.map +1 -1
- package/dist/storage/StaticProvider.d.ts +12 -2
- package/dist/storage/StaticProvider.d.ts.map +1 -1
- package/dist/storage/StaticProvider.js +24 -5
- package/dist/storage/StaticProvider.js.map +1 -1
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +20 -8
- package/scheduler.win32-x64-msvc.node +0 -0
- package/scripts/copy-napi.mjs +11 -3
- package/src/Application.ts +15 -0
- package/src/HttpKernel.ts +87 -5
- package/src/Ignitor.ts +70 -16
- package/src/Provider.ts +15 -0
- package/src/container/Container.ts +178 -48
- package/src/container/types.ts +11 -0
- package/src/dev/hmr.ts +83 -0
- package/src/graphql/GraphQLProvider.ts +14 -3
- package/src/hot.ts +165 -0
- package/src/http/Exception.ts +4 -4
- package/src/http/Request.ts +67 -3
- package/src/http/RequestValidator.ts +143 -0
- package/src/index.ts +10 -3
- package/src/middleware/Pipeline.ts +4 -4
- package/src/migrations/MigrationRegistry.ts +23 -0
- package/src/openapi/OpenApiGenerator.ts +84 -4
- package/src/openapi/OpenApiProvider.ts +21 -6
- package/src/router/Router.ts +1 -1
- package/src/rpc/RpcProvider.ts +13 -2
- package/src/security/CookieSigner.ts +3 -2
- package/src/storage/StaticMiddleware.ts +338 -97
- package/src/storage/StaticProvider.ts +26 -5
package/scripts/copy-napi.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// crate listed in `NAPI_CRATES` ships in the tarball.
|
|
22
22
|
|
|
23
23
|
import { copyFileSync, existsSync } from 'node:fs'
|
|
24
|
-
import { dirname, join } from 'node:path'
|
|
24
|
+
import { dirname, join, resolve } from 'node:path'
|
|
25
25
|
import { arch, env, platform } from 'node:process'
|
|
26
26
|
import { fileURLToPath } from 'node:url'
|
|
27
27
|
|
|
@@ -48,6 +48,14 @@ const HOST_SUFFIX_MAP = {
|
|
|
48
48
|
'win32-x64': 'win32-x64-msvc',
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Cargo writes its artifacts under CARGO_TARGET_DIR when that is set — a
|
|
52
|
+
// shared cache, a CI mount — so they are not under this package's `target/` at
|
|
53
|
+
// all. A relative value is resolved against the directory cargo ran in, which
|
|
54
|
+
// is this package root.
|
|
55
|
+
const targetDir = env.CARGO_TARGET_DIR
|
|
56
|
+
? resolve(root, env.CARGO_TARGET_DIR)
|
|
57
|
+
: join(root, 'target')
|
|
58
|
+
|
|
51
59
|
const triple = env.CARGO_BUILD_TARGET ?? ''
|
|
52
60
|
let suffix
|
|
53
61
|
let os
|
|
@@ -59,11 +67,11 @@ if (triple) {
|
|
|
59
67
|
}
|
|
60
68
|
suffix = entry.suffix
|
|
61
69
|
os = entry.os
|
|
62
|
-
releaseDir = join(
|
|
70
|
+
releaseDir = join(targetDir, triple, 'release')
|
|
63
71
|
} else {
|
|
64
72
|
suffix = HOST_SUFFIX_MAP[`${platform}-${arch}`]
|
|
65
73
|
os = platform
|
|
66
|
-
releaseDir = join(
|
|
74
|
+
releaseDir = join(targetDir, 'release')
|
|
67
75
|
if (!suffix) {
|
|
68
76
|
throw new Error(`[ream:napi] unsupported platform/arch: ${platform}-${arch}`)
|
|
69
77
|
}
|
package/src/Application.ts
CHANGED
|
@@ -73,6 +73,21 @@ export interface ReamrcContents {
|
|
|
73
73
|
export class Application implements AppContext {
|
|
74
74
|
readonly container: Container
|
|
75
75
|
readonly config: ConfigStore
|
|
76
|
+
/**
|
|
77
|
+
* Whether the inker template engine is installed (AdonisJS `usingEdgeJS`).
|
|
78
|
+
*
|
|
79
|
+
* Set by `InkerProvider`'s CONSTRUCTOR, so it is already true by the time any
|
|
80
|
+
* other provider boots. A package that wants to contribute to templates —
|
|
81
|
+
* rosetta pushing its `t()` global, say — reads this before importing
|
|
82
|
+
* `@c9up/inker`, and stays installable without it when the answer is no.
|
|
83
|
+
*
|
|
84
|
+
* A flag rather than a container lookup, and that is the whole point: the
|
|
85
|
+
* engine is a module singleton, so asking the container would mean resolving
|
|
86
|
+
* a binding whose lifecycle has to have reached the right phase. This answers
|
|
87
|
+
* the only question the caller actually has — is the package there — without
|
|
88
|
+
* one.
|
|
89
|
+
*/
|
|
90
|
+
usingInker = false
|
|
76
91
|
#appRoot?: URL
|
|
77
92
|
#directories: DirectoriesNode = { ...defaultDirectories }
|
|
78
93
|
#rcFile: ReamrcContents = {}
|
package/src/HttpKernel.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface HttpKernelConfig {
|
|
|
32
32
|
container?: Container
|
|
33
33
|
exceptionHandler?: ExceptionHandler
|
|
34
34
|
serverMiddleware?: MiddlewareFunction[]
|
|
35
|
+
/**
|
|
36
|
+
* A `<script>` appended to every HTML response, dev only.
|
|
37
|
+
*
|
|
38
|
+
* Set by the Ignitor when the application runs in development; `undefined`
|
|
39
|
+
* everywhere else, so a production response is byte-for-byte what the
|
|
40
|
+
* handler produced. It lives here rather than in a middleware because this
|
|
41
|
+
* is the one place every response passes through on its way out, whichever
|
|
42
|
+
* route or error handler produced it.
|
|
43
|
+
*/
|
|
44
|
+
devReloadScript?: (nonce?: string) => string
|
|
35
45
|
routerMiddleware?: MiddlewareFunction[]
|
|
36
46
|
onError?: (error: unknown, ctx: HttpContext) => void
|
|
37
47
|
debug?: boolean
|
|
@@ -345,7 +355,7 @@ export function createHttpKernel(
|
|
|
345
355
|
correlationId,
|
|
346
356
|
)
|
|
347
357
|
}
|
|
348
|
-
const serialized = await serializeResponse(ctx)
|
|
358
|
+
const serialized = await serializeResponse(ctx, config.devReloadScript)
|
|
349
359
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
350
360
|
// now — after the answer is ready, before we hand it back.
|
|
351
361
|
ctx.response.runFinishCallbacks()
|
|
@@ -401,7 +411,7 @@ export function createHttpKernel(
|
|
|
401
411
|
correlationId,
|
|
402
412
|
)
|
|
403
413
|
}
|
|
404
|
-
const serialized = await serializeResponse(ctx)
|
|
414
|
+
const serialized = await serializeResponse(ctx, config.devReloadScript)
|
|
405
415
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
406
416
|
// now — after the answer is ready, before we hand it back.
|
|
407
417
|
ctx.response.runFinishCallbacks()
|
|
@@ -452,15 +462,87 @@ function readControllerGuardMetadata(
|
|
|
452
462
|
}
|
|
453
463
|
}
|
|
454
464
|
|
|
455
|
-
async function serializeResponse(
|
|
465
|
+
async function serializeResponse(
|
|
466
|
+
ctx: HttpContext,
|
|
467
|
+
devReloadScript?: (nonce?: string) => string,
|
|
468
|
+
): Promise<HttpKernelResponse> {
|
|
456
469
|
// The terminal step, as AdonisJS' server calls `response.finish()`: it
|
|
457
470
|
// stamps the request id and waits for a body still being produced.
|
|
458
471
|
await ctx.response.finish()
|
|
459
472
|
const streamId = ctx.response.getStreamId()
|
|
473
|
+
const headers = ctx.response.getHeaders()
|
|
474
|
+
const body = withDevReloadScript(
|
|
475
|
+
ctx.response.getBody(),
|
|
476
|
+
headers,
|
|
477
|
+
streamId,
|
|
478
|
+
devReloadScript === undefined ? undefined : devReloadScript(ctx.response.nonce),
|
|
479
|
+
)
|
|
460
480
|
return {
|
|
461
481
|
status: ctx.response.getStatus(),
|
|
462
|
-
headers: ctx.response.
|
|
463
|
-
body
|
|
482
|
+
headers: reconcileRepresentation(headers, ctx.response.getBody(), body),
|
|
483
|
+
body,
|
|
464
484
|
...(streamId !== undefined ? { streamId } : {}),
|
|
465
485
|
}
|
|
466
486
|
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Keep the representation headers describing the body that is actually sent.
|
|
490
|
+
*
|
|
491
|
+
* `content-length`, `etag` and the digest headers are computed from the body
|
|
492
|
+
* BEFORE the dev reload script is appended. Left alone they describe the
|
|
493
|
+
* previous one: a `content-length` short by the length of the script truncates
|
|
494
|
+
* it, and an `etag` computed before the change caches modified content under an
|
|
495
|
+
* unchanged validator.
|
|
496
|
+
*
|
|
497
|
+
* `content-length` is recomputed because it is cheap and exact; the validators
|
|
498
|
+
* are DROPPED rather than recomputed, because a validator this layer invents is
|
|
499
|
+
* not the one the handler meant.
|
|
500
|
+
*/
|
|
501
|
+
function reconcileRepresentation(
|
|
502
|
+
headers: Record<string, string>,
|
|
503
|
+
original: string,
|
|
504
|
+
sent: string,
|
|
505
|
+
): Record<string, string> {
|
|
506
|
+
if (sent === original) return headers
|
|
507
|
+
const reconciled = { ...headers }
|
|
508
|
+
if (reconciled['content-length'] !== undefined) {
|
|
509
|
+
reconciled['content-length'] = String(Buffer.byteLength(sent, 'utf8'))
|
|
510
|
+
}
|
|
511
|
+
// Every validator, not the two that came to mind. `Content-Digest` and
|
|
512
|
+
// `Repr-Digest` (RFC 9530) are what replaced `Digest`, and leaving them
|
|
513
|
+
// describing the old body is the same defect one header along — a client
|
|
514
|
+
// that checks them rejects a response that is perfectly fine.
|
|
515
|
+
for (const name of [
|
|
516
|
+
'etag',
|
|
517
|
+
'content-md5',
|
|
518
|
+
'digest',
|
|
519
|
+
'content-digest',
|
|
520
|
+
'repr-digest',
|
|
521
|
+
'want-digest',
|
|
522
|
+
'want-content-digest',
|
|
523
|
+
'want-repr-digest',
|
|
524
|
+
]) {
|
|
525
|
+
delete reconciled[name]
|
|
526
|
+
}
|
|
527
|
+
return reconciled
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Append the dev reload script to an HTML body.
|
|
532
|
+
*
|
|
533
|
+
* Only HTML, and never a stream: appending to an SSE or a download would
|
|
534
|
+
* corrupt it, and a page is the only thing that can act on the script anyway.
|
|
535
|
+
* Before `</body>` when there is one, so the script is inside the document
|
|
536
|
+
* rather than trailing after it.
|
|
537
|
+
*/
|
|
538
|
+
function withDevReloadScript(
|
|
539
|
+
body: string,
|
|
540
|
+
headers: Record<string, string>,
|
|
541
|
+
streamId: string | undefined,
|
|
542
|
+
script?: string,
|
|
543
|
+
): string {
|
|
544
|
+
if (script === undefined || streamId !== undefined || body === '') return body
|
|
545
|
+
if (!(headers['content-type'] ?? '').includes('text/html')) return body
|
|
546
|
+
const closing = body.lastIndexOf('</body>')
|
|
547
|
+
return closing === -1 ? body + script : body.slice(0, closing) + script + body.slice(closing)
|
|
548
|
+
}
|
package/src/Ignitor.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { Application } from './Application.js'
|
|
|
17
17
|
import type { Console } from './console/Console.js'
|
|
18
18
|
import type { CommandLoader, Kernel as ConsoleKernelInstance } from './console/Kernel.js'
|
|
19
19
|
import { type CommandClass, isCommandClass } from './console/types.js'
|
|
20
|
+
import { HMR_PATH, hmrClientScript, hmrToken } from './dev/hmr.js'
|
|
20
21
|
import type { DirectoriesNode } from './directories.js'
|
|
21
22
|
import type { ErrorEvent } from './ErrorBoundary.js'
|
|
22
23
|
import { ErrorBoundary } from './ErrorBoundary.js'
|
|
@@ -31,6 +32,7 @@ import type { HttpKernelRequest, HttpKernelResponse } from './HttpKernel.js'
|
|
|
31
32
|
import { createHttpKernel } from './HttpKernel.js'
|
|
32
33
|
import { ExceptionHandler } from './http/Exception.js'
|
|
33
34
|
import { type ChildLoggerSource, HttpContext } from './http/HttpContext.js'
|
|
35
|
+
import { RequestValidator } from './http/RequestValidator.js'
|
|
34
36
|
import type { MiddlewareFunction } from './middleware/Pipeline.js'
|
|
35
37
|
import { MiddlewareRegistry } from './middleware/Pipeline.js'
|
|
36
38
|
import { MigrationRegistry } from './migrations/MigrationRegistry.js'
|
|
@@ -325,6 +327,11 @@ export class Ignitor {
|
|
|
325
327
|
// must stay agnostic reaches it through the container instead of importing
|
|
326
328
|
// ream at runtime.
|
|
327
329
|
this.#app.container.singleton('HttpContext', () => HttpContext)
|
|
330
|
+
// The seam a package installs its per-request messages provider or error
|
|
331
|
+
// reporter on. Bound rather than imported so an i18n provider can reach it
|
|
332
|
+
// without depending on ream — which is the only reason it is a container
|
|
333
|
+
// token and not just an import.
|
|
334
|
+
this.#app.container.singleton('requestValidator', () => RequestValidator)
|
|
328
335
|
// `appRoot` is the URL passed to `new Ignitor(new URL('../', import.meta.url))`.
|
|
329
336
|
// Providers resolve it through the container so they can interpret
|
|
330
337
|
// relative paths in config files (e.g. `pages.root: './resources/pages'`)
|
|
@@ -659,6 +666,12 @@ export class Ignitor {
|
|
|
659
666
|
}
|
|
660
667
|
|
|
661
668
|
async #phaseStart(): Promise<void> {
|
|
669
|
+
// What the injected reload script polls, mounted BEFORE the socket accepts
|
|
670
|
+
// anything. Registered in `ready()` it came up after the server was already
|
|
671
|
+
// serving, so a page loaded in that window polled a route that did not
|
|
672
|
+
// exist yet.
|
|
673
|
+
this.#registerDevReloadRoute()
|
|
674
|
+
|
|
662
675
|
// Providers start BEFORE the preload files, matching upstream's warm-up
|
|
663
676
|
// order (`providers.start()` → `starting` hooks → preloads). A preload is
|
|
664
677
|
// where an app writes its routes and kernel, and those reach for services
|
|
@@ -668,18 +681,28 @@ export class Ignitor {
|
|
|
668
681
|
await callProviderPhase(provider, 'start')
|
|
669
682
|
}
|
|
670
683
|
|
|
671
|
-
// Import preload files (routes.ts, kernel.ts, etc.)
|
|
684
|
+
// Import preload files (routes.ts, kernel.ts, etc.).
|
|
685
|
+
//
|
|
686
|
+
// Filtered on the environment first, then imported TOGETHER — upstream's
|
|
687
|
+
// `PreloadsManager.import()` is a filter followed by one `Promise.all`, and
|
|
688
|
+
// this used to walk them one at a time.
|
|
689
|
+
//
|
|
690
|
+
// The consequence is worth knowing: evaluation order between two preload
|
|
691
|
+
// files is now whichever finishes loading first, so an application cannot
|
|
692
|
+
// rely on the array's order to decide which of two overlapping routes
|
|
693
|
+
// answers. Work that must be ordered belongs inside ONE preload, where the
|
|
694
|
+
// order is the file's own — which is how upstream applications are written.
|
|
672
695
|
if (this.#reamrc?.preloads) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const env =
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
696
|
+
const applicable = this.#reamrc.preloads.filter((preloadEntry) => {
|
|
697
|
+
if (typeof preloadEntry === 'function') return true
|
|
698
|
+
const env = (preloadEntry as { environment?: string[] }).environment
|
|
699
|
+
return !env || env.includes(this.#app.getEnvironment())
|
|
700
|
+
})
|
|
701
|
+
await Promise.all(
|
|
702
|
+
applicable.map((preloadEntry) =>
|
|
703
|
+
typeof preloadEntry === 'function' ? preloadEntry() : preloadEntry.file(),
|
|
704
|
+
),
|
|
705
|
+
)
|
|
683
706
|
}
|
|
684
707
|
|
|
685
708
|
// Auto-load module files (routes.ts, etc.) from modules directory
|
|
@@ -737,6 +760,7 @@ export class Ignitor {
|
|
|
737
760
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
738
761
|
serverMiddleware: this.#server.getServerMiddleware(),
|
|
739
762
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
763
|
+
devReloadScript: this.#devReloadScript(),
|
|
740
764
|
onError: (error, ctx) => {
|
|
741
765
|
this.#errorBoundary.serviceError('HttpKernel', error, ctx.id)
|
|
742
766
|
},
|
|
@@ -859,11 +883,13 @@ export class Ignitor {
|
|
|
859
883
|
// already-loaded ESM module returns the CACHED module without re-executing
|
|
860
884
|
// its body (the thunk's specifier is opaque, so it cannot be cache-busted).
|
|
861
885
|
// The "reload" therefore destroyed every preload-registered route on the
|
|
862
|
-
// first file save and never restored them.
|
|
863
|
-
//
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
886
|
+
// first file save and never restored them.
|
|
887
|
+
//
|
|
888
|
+
// Module-level reloading now happens OUTSIDE the Ignitor: `ream dev` loads
|
|
889
|
+
// `@c9up/ream/hot`, which registers hot-hook's loader and can invalidate
|
|
890
|
+
// the ESM cache — the loader hook this comment used to ask for. What is
|
|
891
|
+
// left here only surfaces an informational event for hosts that embed the
|
|
892
|
+
// Ignitor without a supervisor.
|
|
867
893
|
if (this.isDevMode()) {
|
|
868
894
|
const watchDirs = this.#config.watchDirs ?? ['app', 'start']
|
|
869
895
|
this.#hotReloadCleanup = startHotReload({
|
|
@@ -1125,6 +1151,7 @@ export class Ignitor {
|
|
|
1125
1151
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
1126
1152
|
serverMiddleware: this.#server.getServerMiddleware(),
|
|
1127
1153
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
1154
|
+
devReloadScript: this.#devReloadScript(),
|
|
1128
1155
|
})
|
|
1129
1156
|
return this.#kernel
|
|
1130
1157
|
}
|
|
@@ -1135,6 +1162,33 @@ export class Ignitor {
|
|
|
1135
1162
|
return this.#app.inDev
|
|
1136
1163
|
}
|
|
1137
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* The reload script, in development only.
|
|
1167
|
+
*
|
|
1168
|
+
* Gated on the environment rather than on `hot-hook` being present: the
|
|
1169
|
+
* script is just as useful without it, since a restart is exactly what it
|
|
1170
|
+
* detects. In production it is `undefined` and the response is untouched.
|
|
1171
|
+
*/
|
|
1172
|
+
#devReloadScript(): ((nonce?: string) => string) | undefined {
|
|
1173
|
+
return this.isDevMode() ? hmrClientScript : undefined
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Serve the token the injected script polls.
|
|
1178
|
+
*
|
|
1179
|
+
* Registered on the router in development, next to the application's own
|
|
1180
|
+
* routes, so it goes through the same server as everything else. Plain text
|
|
1181
|
+
* and uncached: it is read once a second and its whole content is the answer.
|
|
1182
|
+
*/
|
|
1183
|
+
#registerDevReloadRoute(): void {
|
|
1184
|
+
if (!this.isDevMode()) return
|
|
1185
|
+
this.#router.get(HMR_PATH, ({ response }) => {
|
|
1186
|
+
response.header('cache-control', 'no-store')
|
|
1187
|
+
response.header('content-type', 'text/plain; charset=utf-8')
|
|
1188
|
+
response.send(hmrToken())
|
|
1189
|
+
})
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1138
1192
|
getPhase(): string {
|
|
1139
1193
|
return this.#phase
|
|
1140
1194
|
}
|
package/src/Provider.ts
CHANGED
|
@@ -32,6 +32,21 @@ export interface ConfigReader {
|
|
|
32
32
|
export interface AppContext {
|
|
33
33
|
container: Container
|
|
34
34
|
config: ConfigReader
|
|
35
|
+
/**
|
|
36
|
+
* Whether the inker template engine is installed — see
|
|
37
|
+
* `Application#usingInker`. Optional here because a host that is not ream
|
|
38
|
+
* satisfies this contract structurally and has no such flag; a provider
|
|
39
|
+
* reading it treats `undefined` as "no engine".
|
|
40
|
+
*/
|
|
41
|
+
usingInker?: boolean
|
|
42
|
+
/**
|
|
43
|
+
* Absolute path to the application's `public/` directory — see
|
|
44
|
+
* `Application#publicPath`. Optional for the same reason as the flag above:
|
|
45
|
+
* a host that is not ream satisfies this contract structurally and has no
|
|
46
|
+
* directory layout. A provider that needs it falls back to a relative
|
|
47
|
+
* `'public'`, resolved against the working directory.
|
|
48
|
+
*/
|
|
49
|
+
publicPath?(...paths: string[]): string
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|