@c9up/ream 0.2.26 → 0.2.28
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 +1 -0
- package/dist/HttpKernel.d.ts +5 -1
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +6 -6
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +3 -1
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +94 -14
- package/dist/Ignitor.js.map +1 -1
- package/dist/console/prompts.d.ts.map +1 -1
- package/dist/console/prompts.js +34 -46
- package/dist/console/prompts.js.map +1 -1
- package/dist/console/selection.d.ts +54 -0
- package/dist/console/selection.d.ts.map +1 -0
- package/dist/console/selection.js +235 -0
- package/dist/console/selection.js.map +1 -0
- package/dist/dev/fullReload.d.ts +35 -10
- package/dist/dev/fullReload.d.ts.map +1 -1
- package/dist/dev/fullReload.js +55 -14
- package/dist/dev/fullReload.js.map +1 -1
- package/dist/dev/hotWatcher.d.ts +19 -3
- package/dist/dev/hotWatcher.d.ts.map +1 -1
- package/dist/dev/hotWatcher.js +168 -18
- package/dist/dev/hotWatcher.js.map +1 -1
- package/dist/dev/readyBanner.d.ts +45 -0
- package/dist/dev/readyBanner.d.ts.map +1 -0
- package/dist/dev/readyBanner.js +72 -0
- package/dist/dev/readyBanner.js.map +1 -0
- package/dist/dev/shortcuts.d.ts +41 -0
- package/dist/dev/shortcuts.d.ts.map +1 -0
- package/dist/dev/shortcuts.js +142 -0
- package/dist/dev/shortcuts.js.map +1 -0
- package/dist/errors/prettyPrintError.d.ts.map +1 -1
- package/dist/errors/prettyPrintError.js +5 -8
- package/dist/errors/prettyPrintError.js.map +1 -1
- package/dist/errors/renderError.d.ts +46 -0
- package/dist/errors/renderError.d.ts.map +1 -0
- package/dist/errors/renderError.js +197 -0
- package/dist/errors/renderError.js.map +1 -0
- package/dist/hot.js +12 -2
- package/dist/hot.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/native/generated.d.ts +34 -0
- package/dist/native/generated.d.ts.map +1 -1
- package/dist/profiler/MemoryProfileStore.d.ts +23 -0
- package/dist/profiler/MemoryProfileStore.d.ts.map +1 -0
- package/dist/profiler/MemoryProfileStore.js +70 -0
- package/dist/profiler/MemoryProfileStore.js.map +1 -0
- package/dist/profiler/Profiler.d.ts +56 -0
- package/dist/profiler/Profiler.d.ts.map +1 -0
- package/dist/profiler/Profiler.js +162 -0
- package/dist/profiler/Profiler.js.map +1 -0
- package/dist/profiler/collectors.d.ts +105 -0
- package/dist/profiler/collectors.d.ts.map +1 -0
- package/dist/profiler/collectors.js +164 -0
- package/dist/profiler/collectors.js.map +1 -0
- package/dist/profiler/endpoint.d.ts +27 -0
- package/dist/profiler/endpoint.d.ts.map +1 -0
- package/dist/profiler/endpoint.js +79 -0
- package/dist/profiler/endpoint.js.map +1 -0
- package/dist/profiler/index.d.ts +26 -0
- package/dist/profiler/index.d.ts.map +1 -0
- package/dist/profiler/index.js +41 -0
- package/dist/profiler/index.js.map +1 -0
- package/dist/profiler/middleware.d.ts +17 -0
- package/dist/profiler/middleware.d.ts.map +1 -0
- package/dist/profiler/middleware.js +79 -0
- package/dist/profiler/middleware.js.map +1 -0
- package/dist/profiler/render.d.ts +23 -0
- package/dist/profiler/render.d.ts.map +1 -0
- package/dist/profiler/render.js +141 -0
- package/dist/profiler/render.js.map +1 -0
- package/dist/profiler/toolbar.d.ts +34 -0
- package/dist/profiler/toolbar.d.ts.map +1 -0
- package/dist/profiler/toolbar.js +104 -0
- package/dist/profiler/toolbar.js.map +1 -0
- package/dist/profiler/types.d.ts +70 -0
- package/dist/profiler/types.d.ts.map +1 -0
- package/dist/profiler/types.js +11 -0
- package/dist/profiler/types.js.map +1 -0
- package/dist/router/Router.d.ts +11 -0
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +16 -0
- package/dist/router/Router.js.map +1 -1
- package/dist/server/Server.d.ts +37 -0
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +30 -0
- package/dist/server/Server.js.map +1 -1
- package/dist/session/Session.d.ts +33 -0
- package/dist/session/Session.d.ts.map +1 -1
- package/dist/session/Session.js +31 -2
- package/dist/session/Session.js.map +1 -1
- package/dist/session/SessionMiddleware.d.ts.map +1 -1
- package/dist/session/SessionMiddleware.js +24 -4
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/session/drivers/CookieDriver.d.ts +12 -3
- package/dist/session/drivers/CookieDriver.d.ts.map +1 -1
- package/dist/session/drivers/CookieDriver.js +20 -6
- package/dist/session/drivers/CookieDriver.js.map +1 -1
- package/dist/session/drivers/DatabaseDriver.d.ts +2 -0
- package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
- package/dist/session/drivers/DatabaseDriver.js +6 -0
- package/dist/session/drivers/DatabaseDriver.js.map +1 -1
- package/dist/session/drivers/FileDriver.d.ts +8 -0
- package/dist/session/drivers/FileDriver.d.ts.map +1 -1
- package/dist/session/drivers/FileDriver.js +85 -11
- package/dist/session/drivers/FileDriver.js.map +1 -1
- package/dist/session/drivers/MemoryDriver.d.ts +2 -0
- package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
- package/dist/session/drivers/MemoryDriver.js +9 -0
- package/dist/session/drivers/MemoryDriver.js.map +1 -1
- package/dist/session/drivers/RedisDriver.d.ts +2 -0
- package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
- package/dist/session/drivers/RedisDriver.js +6 -0
- package/dist/session/drivers/RedisDriver.js.map +1 -1
- package/dist/session/errors.d.ts +10 -0
- package/dist/session/errors.d.ts.map +1 -1
- package/dist/session/errors.js +12 -0
- package/dist/session/errors.js.map +1 -1
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- 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 +1 -1
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/HttpKernel.ts +12 -8
- package/src/Ignitor.ts +109 -15
- package/src/console/prompts.ts +45 -64
- package/src/console/selection.ts +274 -0
- package/src/dev/fullReload.ts +57 -15
- package/src/dev/hotWatcher.ts +165 -21
- package/src/dev/readyBanner.ts +84 -0
- package/src/dev/shortcuts.ts +158 -0
- package/src/errors/prettyPrintError.ts +5 -5
- package/src/errors/renderError.ts +230 -0
- package/src/hot.ts +12 -2
- package/src/index.ts +11 -1
- package/src/native/generated.ts +38 -0
- package/src/profiler/MemoryProfileStore.ts +75 -0
- package/src/profiler/Profiler.ts +182 -0
- package/src/profiler/collectors.ts +230 -0
- package/src/profiler/endpoint.ts +98 -0
- package/src/profiler/index.ts +56 -0
- package/src/profiler/middleware.ts +83 -0
- package/src/profiler/render.ts +173 -0
- package/src/profiler/toolbar.ts +106 -0
- package/src/profiler/types.ts +75 -0
- package/src/router/Router.ts +16 -0
- package/src/server/Server.ts +49 -0
- package/src/session/Session.ts +56 -2
- package/src/session/SessionMiddleware.ts +23 -4
- package/src/session/drivers/CookieDriver.ts +21 -6
- package/src/session/drivers/DatabaseDriver.ts +10 -0
- package/src/session/drivers/FileDriver.ts +91 -11
- package/src/session/drivers/MemoryDriver.ts +9 -0
- package/src/session/drivers/RedisDriver.ts +7 -0
- package/src/session/errors.ts +17 -0
|
Binary file
|
|
Binary file
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/HttpKernel.ts
CHANGED
|
@@ -41,8 +41,12 @@ export interface HttpKernelConfig {
|
|
|
41
41
|
* handler produced. It lives here rather than in a middleware because this
|
|
42
42
|
* is the one place every response passes through on its way out, whichever
|
|
43
43
|
* route or error handler produced it.
|
|
44
|
+
*
|
|
45
|
+
* It is handed the whole context, not just the nonce: the debug toolbar
|
|
46
|
+
* carries the profile token inline, and that token is only on the response
|
|
47
|
+
* by the time the body is serialised.
|
|
44
48
|
*/
|
|
45
|
-
|
|
49
|
+
devScript?: (ctx: HttpContext) => string
|
|
46
50
|
routerMiddleware?: MiddlewareFunction[]
|
|
47
51
|
onError?: (error: unknown, ctx: HttpContext) => void
|
|
48
52
|
debug?: boolean
|
|
@@ -375,7 +379,7 @@ export function createHttpKernel(
|
|
|
375
379
|
correlationId,
|
|
376
380
|
)
|
|
377
381
|
}
|
|
378
|
-
const serialized = await serializeResponse(ctx, config.
|
|
382
|
+
const serialized = await serializeResponse(ctx, config.devScript)
|
|
379
383
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
380
384
|
// now — after the answer is ready, before we hand it back.
|
|
381
385
|
ctx.response.runFinishCallbacks()
|
|
@@ -431,7 +435,7 @@ export function createHttpKernel(
|
|
|
431
435
|
correlationId,
|
|
432
436
|
)
|
|
433
437
|
}
|
|
434
|
-
const serialized = await serializeResponse(ctx, config.
|
|
438
|
+
const serialized = await serializeResponse(ctx, config.devScript)
|
|
435
439
|
// The body is built; anything registered with `response.onFinish()` runs
|
|
436
440
|
// now — after the answer is ready, before we hand it back.
|
|
437
441
|
ctx.response.runFinishCallbacks()
|
|
@@ -484,18 +488,18 @@ function readControllerGuardMetadata(
|
|
|
484
488
|
|
|
485
489
|
async function serializeResponse(
|
|
486
490
|
ctx: HttpContext,
|
|
487
|
-
|
|
491
|
+
devScript?: (ctx: HttpContext) => string,
|
|
488
492
|
): Promise<HttpKernelResponse> {
|
|
489
493
|
// The terminal step, as AdonisJS' server calls `response.finish()`: it
|
|
490
494
|
// stamps the request id and waits for a body still being produced.
|
|
491
495
|
await ctx.response.finish()
|
|
492
496
|
const streamId = ctx.response.getStreamId()
|
|
493
497
|
const headers = ctx.response.getHeaders()
|
|
494
|
-
const body =
|
|
498
|
+
const body = withDevScript(
|
|
495
499
|
ctx.response.getBody(),
|
|
496
500
|
headers,
|
|
497
501
|
streamId,
|
|
498
|
-
|
|
502
|
+
devScript === undefined ? undefined : devScript(ctx),
|
|
499
503
|
)
|
|
500
504
|
return {
|
|
501
505
|
status: ctx.response.getStatus(),
|
|
@@ -548,14 +552,14 @@ function reconcileRepresentation(
|
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
/**
|
|
551
|
-
* Append the
|
|
555
|
+
* Append the development script to an HTML body.
|
|
552
556
|
*
|
|
553
557
|
* Only HTML, and never a stream: appending to an SSE or a download would
|
|
554
558
|
* corrupt it, and a page is the only thing that can act on the script anyway.
|
|
555
559
|
* Before `</body>` when there is one, so the script is inside the document
|
|
556
560
|
* rather than trailing after it.
|
|
557
561
|
*/
|
|
558
|
-
function
|
|
562
|
+
function withDevScript(
|
|
559
563
|
body: string,
|
|
560
564
|
headers: Record<string, string>,
|
|
561
565
|
streamId: string | undefined,
|
package/src/Ignitor.ts
CHANGED
|
@@ -18,6 +18,8 @@ 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
20
|
import { hmrClientScript, hmrEndpoint } from './dev/hmr.js'
|
|
21
|
+
import { devServerUrl, inDevServer, printReadyBanner } from './dev/readyBanner.js'
|
|
22
|
+
import { installShortcuts } from './dev/shortcuts.js'
|
|
21
23
|
import type { DirectoriesNode } from './directories.js'
|
|
22
24
|
import type { ErrorEvent } from './ErrorBoundary.js'
|
|
23
25
|
import { ErrorBoundary } from './ErrorBoundary.js'
|
|
@@ -38,10 +40,18 @@ import { MiddlewareRegistry } from './middleware/Pipeline.js'
|
|
|
38
40
|
import { MigrationRegistry } from './migrations/MigrationRegistry.js'
|
|
39
41
|
import type { AppContext, ProviderContract } from './Provider.js'
|
|
40
42
|
import { callProviderPhase } from './Provider.js'
|
|
43
|
+
import {
|
|
44
|
+
createProfiler,
|
|
45
|
+
PROFILER_TOKEN_HEADER,
|
|
46
|
+
type Profiler,
|
|
47
|
+
profileRequest,
|
|
48
|
+
profilerEndpoint,
|
|
49
|
+
toolbarScript,
|
|
50
|
+
} from './profiler/index.js'
|
|
41
51
|
import { Router } from './router/Router.js'
|
|
42
52
|
import { CookieSigner } from './security/CookieSigner.js'
|
|
43
53
|
import { SignedUrl } from './security/SignedUrl.js'
|
|
44
|
-
import { Server } from './server/Server.js'
|
|
54
|
+
import { type GrpcHandler, Server } from './server/Server.js'
|
|
45
55
|
import { clearApp, setApp } from './services/app.js'
|
|
46
56
|
import { clearEncryption, setEncryption } from './services/encryption.js'
|
|
47
57
|
import { clearLogger, setLogger } from './services/logger.js'
|
|
@@ -258,6 +268,8 @@ export function defineConfig(config: ReamrcConfig): ReamrcConfig {
|
|
|
258
268
|
/** Minimal interface for the HTTP server (NAPI or mock). */
|
|
259
269
|
export interface HyperServerLike {
|
|
260
270
|
onRequest(callback: (request: HttpKernelRequest) => Promise<HttpKernelResponse>): void
|
|
271
|
+
/** Optional — present on the real `HyperServer`; required once `server.grpc()` is used. */
|
|
272
|
+
onGrpc?(callback: GrpcHandler): void
|
|
261
273
|
/**
|
|
262
274
|
* Optional — present on the real `HyperServer` (Rust-backed), absent on
|
|
263
275
|
* trivial test mocks. Ignitor only calls it when a shield config is set.
|
|
@@ -345,6 +357,13 @@ export class Ignitor {
|
|
|
345
357
|
#hotReloadCleanup?: () => void
|
|
346
358
|
#shutdownHandle?: ShutdownHandle
|
|
347
359
|
#host?: string
|
|
360
|
+
/**
|
|
361
|
+
* Stops listening for the dev-server keys. Only ever set under `ream dev`.
|
|
362
|
+
*
|
|
363
|
+
* Removed on shutdown, or a terminal left in raw mode would swallow the
|
|
364
|
+
* next command you type after the server exits.
|
|
365
|
+
*/
|
|
366
|
+
#removeShortcuts?: () => void
|
|
348
367
|
#console?: Console
|
|
349
368
|
|
|
350
369
|
// Inline configuration (for simple use or testing)
|
|
@@ -812,6 +831,10 @@ export class Ignitor {
|
|
|
812
831
|
// Boot the Server (resolves lazy error handler etc.)
|
|
813
832
|
await this.#server.boot()
|
|
814
833
|
|
|
834
|
+
// Before the kernel: the kernel's server middleware and its dev script
|
|
835
|
+
// both read it.
|
|
836
|
+
await this.#startProfiler()
|
|
837
|
+
|
|
815
838
|
// Start HTTP server if this process serves one
|
|
816
839
|
if (this.#servesHttp() && this.#config.serverFactory) {
|
|
817
840
|
// Build the HttpKernel with server middleware + router middleware.
|
|
@@ -830,7 +853,7 @@ export class Ignitor {
|
|
|
830
853
|
...this.#server.getServerMiddleware(),
|
|
831
854
|
],
|
|
832
855
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
833
|
-
|
|
856
|
+
devScript: this.#devScript(),
|
|
834
857
|
onError: (error, ctx) => {
|
|
835
858
|
this.#errorBoundary.serviceError('HttpKernel', error, ctx.id)
|
|
836
859
|
},
|
|
@@ -882,6 +905,17 @@ export class Ignitor {
|
|
|
882
905
|
this.#config.host ?? process.env.HOST ?? (this.#app.inProduction ? '0.0.0.0' : 'localhost')
|
|
883
906
|
this.#_httpServer = this.#config.serverFactory(availablePort, this.#host)
|
|
884
907
|
this.#_httpServer.onRequest(kernel)
|
|
908
|
+
const grpc = this.#server.getGrpcHandler()
|
|
909
|
+
if (grpc) {
|
|
910
|
+
if (typeof this.#_httpServer.onGrpc !== 'function') {
|
|
911
|
+
throw new ReamError(
|
|
912
|
+
'E_IGNITOR_GRPC_UNSUPPORTED',
|
|
913
|
+
'A gRPC handler is registered, but this server cannot serve gRPC.',
|
|
914
|
+
{ hint: 'Use the native HyperServer, or a test server that implements onGrpc().' },
|
|
915
|
+
)
|
|
916
|
+
}
|
|
917
|
+
this.#_httpServer.onGrpc(grpc)
|
|
918
|
+
}
|
|
885
919
|
// Pre-resolve client IPs in Rust from the trusted-proxy CIDRs before
|
|
886
920
|
// listen. Security filtering itself lives in @c9up/blackhole.
|
|
887
921
|
const trustedProxies = this.#server.getTrustedProxies()
|
|
@@ -992,6 +1026,23 @@ export class Ignitor {
|
|
|
992
1026
|
// balancer at a process that cannot answer.
|
|
993
1027
|
this.#app.markReady()
|
|
994
1028
|
this.#phase = 'ready'
|
|
1029
|
+
|
|
1030
|
+
// The address, once there is one to print. Upstream's dev server prints
|
|
1031
|
+
// this sticker when the child reports ready; here the child prints it,
|
|
1032
|
+
// because a port found by scanning past a taken 3000 is known only to the
|
|
1033
|
+
// process that bound it.
|
|
1034
|
+
if (inDevServer() && this.#host !== undefined && this.#_httpServer !== undefined) {
|
|
1035
|
+
const port = await this.#_httpServer.port()
|
|
1036
|
+
printReadyBanner({
|
|
1037
|
+
host: this.#host,
|
|
1038
|
+
port,
|
|
1039
|
+
mode: 'HMR',
|
|
1040
|
+
bootMs: performance.now(),
|
|
1041
|
+
})
|
|
1042
|
+
// The keys upstream's dev server answers to. Installed here for the same
|
|
1043
|
+
// reason the sticker is printed here: `o` needs the address.
|
|
1044
|
+
this.#removeShortcuts = installShortcuts({ url: devServerUrl(this.#host, port) })
|
|
1045
|
+
}
|
|
995
1046
|
}
|
|
996
1047
|
|
|
997
1048
|
/**
|
|
@@ -1154,6 +1205,11 @@ export class Ignitor {
|
|
|
1154
1205
|
async stop(): Promise<void> {
|
|
1155
1206
|
const errors: unknown[] = []
|
|
1156
1207
|
|
|
1208
|
+
// First: a terminal left in raw mode swallows the next command you type
|
|
1209
|
+
// after the server exits, and that outlives the process.
|
|
1210
|
+
this.#removeShortcuts?.()
|
|
1211
|
+
this.#removeShortcuts = undefined
|
|
1212
|
+
|
|
1157
1213
|
// Release the consoleApp locator first — ownership-guarded, so a second Ignitor
|
|
1158
1214
|
// having rebound it is left alone.
|
|
1159
1215
|
if (this.#console !== undefined) {
|
|
@@ -1296,7 +1352,7 @@ export class Ignitor {
|
|
|
1296
1352
|
this.#server.getErrorHandler() ?? new ExceptionHandler(!this.#app.inProduction),
|
|
1297
1353
|
serverMiddleware: this.#server.getServerMiddleware(),
|
|
1298
1354
|
routerMiddleware: this.#router.getRouterMiddleware(),
|
|
1299
|
-
|
|
1355
|
+
devScript: this.#devScript(),
|
|
1300
1356
|
})
|
|
1301
1357
|
return this.#kernel
|
|
1302
1358
|
}
|
|
@@ -1308,29 +1364,67 @@ export class Ignitor {
|
|
|
1308
1364
|
}
|
|
1309
1365
|
|
|
1310
1366
|
/**
|
|
1311
|
-
*
|
|
1367
|
+
* What development appends to an HTML page: the reload client, and the
|
|
1368
|
+
* debug toolbar once a profile exists for the request.
|
|
1312
1369
|
*
|
|
1313
1370
|
* Gated on the environment rather than on `hot-hook` being present: the
|
|
1314
|
-
* script is just as useful without it, since a restart is exactly
|
|
1315
|
-
* detects. In production
|
|
1371
|
+
* reload script is just as useful without it, since a restart is exactly
|
|
1372
|
+
* what it detects. In production this is `undefined` and the response is
|
|
1373
|
+
* untouched.
|
|
1374
|
+
*
|
|
1375
|
+
* The token is read back off the response rather than passed along a side
|
|
1376
|
+
* channel — the profiling middleware has already stamped it there, which is
|
|
1377
|
+
* also where a client outside the page reads it (Symfony's `X-Debug-Token`).
|
|
1316
1378
|
*/
|
|
1317
|
-
#
|
|
1318
|
-
|
|
1379
|
+
#devScript(): ((ctx: HttpContext) => string) | undefined {
|
|
1380
|
+
if (!this.isDevMode()) return undefined
|
|
1381
|
+
return (ctx) => {
|
|
1382
|
+
const nonce = ctx.response.nonce
|
|
1383
|
+
const reload = hmrClientScript(nonce)
|
|
1384
|
+
const token = ctx.response.getHeader(PROFILER_TOKEN_HEADER)
|
|
1385
|
+
return token === undefined ? reload : reload + toolbarScript(token, nonce)
|
|
1386
|
+
}
|
|
1319
1387
|
}
|
|
1320
1388
|
|
|
1389
|
+
/**
|
|
1390
|
+
* The profiler, in development only.
|
|
1391
|
+
*
|
|
1392
|
+
* It needs the application's emitter — `db:query` is where the database
|
|
1393
|
+
* panel comes from — so it is built here rather than in a field initialiser,
|
|
1394
|
+
* once the container has one. An application without an event bus still gets
|
|
1395
|
+
* every other panel.
|
|
1396
|
+
*/
|
|
1397
|
+
async #startProfiler(): Promise<void> {
|
|
1398
|
+
if (!this.isDevMode() || this.#profiler !== undefined) return
|
|
1399
|
+
const events = this.#app.container.has('events')
|
|
1400
|
+
? await this.#app.container.resolve<Emitter>('events')
|
|
1401
|
+
: { on: () => undefined }
|
|
1402
|
+
this.#profiler = createProfiler(events)
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
#profiler?: Profiler
|
|
1406
|
+
|
|
1321
1407
|
/**
|
|
1322
1408
|
* The server middleware the framework itself contributes, ahead of the
|
|
1323
1409
|
* application's.
|
|
1324
1410
|
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
*
|
|
1330
|
-
*
|
|
1411
|
+
* Development only, and the order is the point:
|
|
1412
|
+
*
|
|
1413
|
+
* 1. the profiler's own pages, which must not appear in their own history;
|
|
1414
|
+
* 2. the hot-reload stream, which answers first because it arrives once a
|
|
1415
|
+
* second per open tab and its content depends on nothing — no user, no
|
|
1416
|
+
* session, no body. Behind an application's middleware it paid for the
|
|
1417
|
+
* whole stack each time, which for anything that resolves a user from a
|
|
1418
|
+
* cookie meant a `SELECT` per second for a string holding a process id
|
|
1419
|
+
* and a counter. It is also open for as long as the tab is, so profiling
|
|
1420
|
+
* it would leave one profile unfinished forever;
|
|
1421
|
+
* 3. everything else, wrapped in a profile.
|
|
1331
1422
|
*/
|
|
1332
1423
|
#frameworkServerMiddleware(): MiddlewareFunction[] {
|
|
1333
|
-
|
|
1424
|
+
if (!this.isDevMode()) return []
|
|
1425
|
+
const profiler = this.#profiler
|
|
1426
|
+
if (profiler === undefined) return [hmrEndpoint()]
|
|
1427
|
+
return [profilerEndpoint(profiler), hmrEndpoint(), profileRequest(profiler)]
|
|
1334
1428
|
}
|
|
1335
1429
|
|
|
1336
1430
|
getPhase(): string {
|
package/src/console/prompts.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { stdin, stdout } from 'node:process'
|
|
13
13
|
import { clearLine, createInterface, cursorTo } from 'node:readline'
|
|
14
14
|
import { ReamError } from '../errors/ReamError.js'
|
|
15
|
+
import { runSelection } from './selection.js'
|
|
15
16
|
import { colourise } from './ui.js'
|
|
16
17
|
|
|
17
18
|
/** A selectable option — a bare string, or a value with its display text. */
|
|
@@ -246,31 +247,18 @@ export class Prompt {
|
|
|
246
247
|
return this.#applyResult(picked[0], options)
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const first = matches[0]
|
|
262
|
-
if (matches.length === 1 && first !== undefined) {
|
|
263
|
-
return this.#finish(nameOf(first), options)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const picked = await this.#select(
|
|
267
|
-
message,
|
|
268
|
-
matches.slice(0, limit),
|
|
269
|
-
{ ...options, name: undefined },
|
|
270
|
-
false,
|
|
271
|
-
)
|
|
272
|
-
return picked[0]
|
|
273
|
-
}
|
|
250
|
+
// One list that narrows as you type, rather than a filter question
|
|
251
|
+
// followed by a list: upstream's autocomplete is a single prompt, and
|
|
252
|
+
// answering twice for one value is a different thing wearing its name.
|
|
253
|
+
const picked = await this.#select(
|
|
254
|
+
message,
|
|
255
|
+
choices,
|
|
256
|
+
{ ...options, name: undefined },
|
|
257
|
+
false,
|
|
258
|
+
true,
|
|
259
|
+
options.limit ?? 10,
|
|
260
|
+
)
|
|
261
|
+
return picked[0]
|
|
274
262
|
}
|
|
275
263
|
|
|
276
264
|
// ─── internals ──────────────────────────────────────────────
|
|
@@ -299,6 +287,8 @@ export class Prompt {
|
|
|
299
287
|
choices: readonly PromptChoice[],
|
|
300
288
|
options: SelectPromptOptions | MultiplePromptOptions,
|
|
301
289
|
many: boolean,
|
|
290
|
+
filter?: boolean,
|
|
291
|
+
limit?: number,
|
|
302
292
|
): Promise<unknown[]> {
|
|
303
293
|
if (choices.length === 0) {
|
|
304
294
|
throw new ReamError('E_CONSOLE_EMPTY_CHOICES', `"${message}" was asked with no options.`)
|
|
@@ -313,50 +303,41 @@ export class Prompt {
|
|
|
313
303
|
return many ? picked : picked.map((value) => this.#applyResult(value, options))
|
|
314
304
|
}
|
|
315
305
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
? undefined
|
|
319
|
-
: [options.default]
|
|
320
|
-
.flat()
|
|
321
|
-
.map((index) => {
|
|
322
|
-
const choice = choices[index]
|
|
323
|
-
return choice === undefined ? String(index) : displayOf(choice)
|
|
324
|
-
})
|
|
325
|
-
.join(', ')
|
|
326
|
-
|
|
306
|
+
// Arrow keys, as upstream's prompts navigate. The list redraws in place
|
|
307
|
+
// until the user answers; a cancel is no answer, not an empty one.
|
|
327
308
|
for (;;) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
309
|
+
const outcome = await runSelection({
|
|
310
|
+
title: this.#label(message, { ...options, default: undefined }).trimEnd(),
|
|
311
|
+
items: choices.map((choice) => {
|
|
312
|
+
const hint = typeof choice === 'string' ? undefined : choice.hint
|
|
313
|
+
return hint === undefined
|
|
314
|
+
? { label: displayOf(choice) }
|
|
315
|
+
: { label: displayOf(choice), hint }
|
|
316
|
+
}),
|
|
317
|
+
many,
|
|
318
|
+
...(options.default === undefined
|
|
319
|
+
? {}
|
|
320
|
+
: {
|
|
321
|
+
selected: [options.default]
|
|
322
|
+
.flat()
|
|
323
|
+
.filter(
|
|
324
|
+
(index): index is number =>
|
|
325
|
+
Number.isInteger(index) && index >= 0 && index < choices.length,
|
|
326
|
+
),
|
|
327
|
+
}),
|
|
328
|
+
...(filter === true ? { filter: true } : {}),
|
|
329
|
+
...(limit === undefined ? {} : { limit }),
|
|
335
330
|
})
|
|
336
331
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// An empty answer takes the default — which for a selection prompt is an
|
|
342
|
-
// index, as Adonis spells it. Without this the default was shown in the
|
|
343
|
-
// label and then rejected as an invalid selection.
|
|
344
|
-
const indexes =
|
|
345
|
-
answer === '' && options.default !== undefined
|
|
346
|
-
? [options.default]
|
|
347
|
-
.flat()
|
|
348
|
-
.filter((index) => Number.isInteger(index) && index >= 0 && index < choices.length)
|
|
349
|
-
: answer
|
|
350
|
-
.split(',')
|
|
351
|
-
.map((part) => Number(part.trim()) - 1)
|
|
352
|
-
.filter((index) => Number.isInteger(index) && index >= 0 && index < choices.length)
|
|
353
|
-
|
|
354
|
-
if (indexes.length === 0 || (!many && indexes.length !== 1)) {
|
|
355
|
-
stdout.write(`${colourise('Invalid selection.', 'yellow')}\n`)
|
|
356
|
-
continue
|
|
332
|
+
if (outcome.kind === 'cancelled') {
|
|
333
|
+
throw new ReamError('E_CONSOLE_PROMPT_CANCELLED', `"${message}" was cancelled.`, {
|
|
334
|
+
hint: 'Answer the prompt, or pass the value as a flag.',
|
|
335
|
+
})
|
|
357
336
|
}
|
|
358
337
|
|
|
359
|
-
|
|
338
|
+
// A multiple with nothing ticked is an answer of none, which is only
|
|
339
|
+
// valid if the command says so — `validate` is where that is decided.
|
|
340
|
+
const picked = outcome.indexes.map((index) => nameOf(choices[index] as PromptChoice))
|
|
360
341
|
const problem = await this.#selectionProblem(picked, options, many)
|
|
361
342
|
if (problem !== undefined) {
|
|
362
343
|
stdout.write(`${colourise(problem, 'yellow')}\n`)
|