@ultimat3/cli 27.2.1 → 27.2.3
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 +28 -28
- package/src/app-runtime.ts +7 -0
- package/src/metrics-endpoint.ts +23 -3
- package/src/role-start.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/cli",
|
|
3
|
-
"version": "27.2.
|
|
3
|
+
"version": "27.2.3",
|
|
4
4
|
"description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,33 +39,33 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@babel/core": "^8.0.1",
|
|
42
|
-
"@ultimat3/action": "27.2.
|
|
43
|
-
"@ultimat3/admin": "27.2.
|
|
44
|
-
"@ultimat3/ai": "27.2.
|
|
45
|
-
"@ultimat3/auth": "27.2.
|
|
46
|
-
"@ultimat3/cache": "27.2.
|
|
47
|
-
"@ultimat3/core": "27.2.
|
|
48
|
-
"@ultimat3/db": "27.2.
|
|
49
|
-
"@ultimat3/entity": "27.2.
|
|
50
|
-
"@ultimat3/flags": "27.2.
|
|
51
|
-
"@ultimat3/http": "27.2.
|
|
52
|
-
"@ultimat3/i18n": "27.2.
|
|
53
|
-
"@ultimat3/jobs": "27.2.
|
|
54
|
-
"@ultimat3/mail": "27.2.
|
|
55
|
-
"@ultimat3/manifest": "27.2.
|
|
56
|
-
"@ultimat3/mcp": "27.2.
|
|
57
|
-
"@ultimat3/money": "27.2.
|
|
58
|
-
"@ultimat3/notify": "27.2.
|
|
59
|
-
"@ultimat3/policy": "27.2.
|
|
60
|
-
"@ultimat3/pwa": "27.2.
|
|
61
|
-
"@ultimat3/query": "27.2.
|
|
62
|
-
"@ultimat3/realtime": "27.2.
|
|
63
|
-
"@ultimat3/render": "27.2.
|
|
64
|
-
"@ultimat3/schema": "27.2.
|
|
65
|
-
"@ultimat3/seo": "27.2.
|
|
66
|
-
"@ultimat3/storage": "27.2.
|
|
67
|
-
"@ultimat3/testing": "27.2.
|
|
68
|
-
"@ultimat3/time": "27.2.
|
|
42
|
+
"@ultimat3/action": "27.2.3",
|
|
43
|
+
"@ultimat3/admin": "27.2.3",
|
|
44
|
+
"@ultimat3/ai": "27.2.3",
|
|
45
|
+
"@ultimat3/auth": "27.2.3",
|
|
46
|
+
"@ultimat3/cache": "27.2.3",
|
|
47
|
+
"@ultimat3/core": "27.2.3",
|
|
48
|
+
"@ultimat3/db": "27.2.3",
|
|
49
|
+
"@ultimat3/entity": "27.2.3",
|
|
50
|
+
"@ultimat3/flags": "27.2.3",
|
|
51
|
+
"@ultimat3/http": "27.2.3",
|
|
52
|
+
"@ultimat3/i18n": "27.2.3",
|
|
53
|
+
"@ultimat3/jobs": "27.2.3",
|
|
54
|
+
"@ultimat3/mail": "27.2.3",
|
|
55
|
+
"@ultimat3/manifest": "27.2.3",
|
|
56
|
+
"@ultimat3/mcp": "27.2.3",
|
|
57
|
+
"@ultimat3/money": "27.2.3",
|
|
58
|
+
"@ultimat3/notify": "27.2.3",
|
|
59
|
+
"@ultimat3/policy": "27.2.3",
|
|
60
|
+
"@ultimat3/pwa": "27.2.3",
|
|
61
|
+
"@ultimat3/query": "27.2.3",
|
|
62
|
+
"@ultimat3/realtime": "27.2.3",
|
|
63
|
+
"@ultimat3/render": "27.2.3",
|
|
64
|
+
"@ultimat3/schema": "27.2.3",
|
|
65
|
+
"@ultimat3/seo": "27.2.3",
|
|
66
|
+
"@ultimat3/storage": "27.2.3",
|
|
67
|
+
"@ultimat3/testing": "27.2.3",
|
|
68
|
+
"@ultimat3/time": "27.2.3",
|
|
69
69
|
"babel-preset-solid": "^1.9.15"
|
|
70
70
|
}
|
|
71
71
|
}
|
package/src/app-runtime.ts
CHANGED
|
@@ -30,6 +30,13 @@ export async function loadAppRuntime(root: string): Promise<RuntimeOverrides | u
|
|
|
30
30
|
if (!existsSync(join(root, 'apps'))) return undefined;
|
|
31
31
|
const files: string[] = [];
|
|
32
32
|
for await (const file of new Bun.Glob(APP_RUNTIME_GLOB).scan({ cwd: root })) files.push(file);
|
|
33
|
+
// The `.tsx` loader BEFORE the first app import, which this is on `runRole`'s path: a Bun plugin
|
|
34
|
+
// only transforms modules loaded after it, and nothing on `@ultimat3/cli/serve`'s static graph
|
|
35
|
+
// installs it. Without this, every component `runtime.ts` reaches compiled to
|
|
36
|
+
// `React.createElement` and stayed cached that way for the pages (27.2.x images: `React is not
|
|
37
|
+
// defined`). Dynamic, so a root with no `runtime.ts` loads nothing, and the serve entry's static
|
|
38
|
+
// graph (`serve-graph.test.ts`) is unchanged.
|
|
39
|
+
if (files.length > 0) await import('@ultimat3/render/server');
|
|
33
40
|
for (const file of files.sort()) {
|
|
34
41
|
const module = (await import(join(root, file))) as Record<string, unknown>;
|
|
35
42
|
const exported = module[APP_RUNTIME_EXPORT];
|
package/src/metrics-endpoint.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// series (`process_*`): a role that can be scraped reports what it costs.
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
BUILD_ID_HEADER,
|
|
7
8
|
finiteCount,
|
|
8
9
|
type HealthPayload,
|
|
9
10
|
healthzPayload,
|
|
@@ -124,6 +125,13 @@ export function bindScrapePort<T>(
|
|
|
124
125
|
export interface MetricsEndpoint {
|
|
125
126
|
/** `http://host:port` — the base the scrape target appends `METRICS_PATH` to. */
|
|
126
127
|
readonly url: string;
|
|
128
|
+
/**
|
|
129
|
+
* The build this process serves, sent as `x-ultimate-build` on `/healthz` and `/readyz` from here
|
|
130
|
+
* on — as every page sends it (#734). Told rather than passed at open: a container opens this
|
|
131
|
+
* port BEFORE its boot computes the build id (`serve.ts`), and `startRoles` is the one caller
|
|
132
|
+
* that has it, whichever opened the port. Until then the health answers carry no header.
|
|
133
|
+
*/
|
|
134
|
+
announceBuild(buildId: string): void;
|
|
127
135
|
stop(): void;
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -132,10 +140,17 @@ export interface MetricsEndpoint {
|
|
|
132
140
|
* `replicator` — so the replicator's readiness check (`role-replicator.ts`) is read by a probe.
|
|
133
141
|
* The verdict only, never the report: this port is unauthenticated, and check names are topology.
|
|
134
142
|
*/
|
|
135
|
-
function healthResponse(payload: HealthPayload, role: string): Response {
|
|
143
|
+
function healthResponse(payload: HealthPayload, role: string, buildId: string | null): Response {
|
|
136
144
|
return Response.json(
|
|
137
145
|
{ state: payload.body.state, ready: payload.body.ready, role },
|
|
138
|
-
{
|
|
146
|
+
{
|
|
147
|
+
status: payload.status,
|
|
148
|
+
// The build in the header (public on every page), never in the body (#53, #734).
|
|
149
|
+
headers: {
|
|
150
|
+
'cache-control': 'no-store',
|
|
151
|
+
...(buildId === null ? {} : { [BUILD_ID_HEADER]: buildId }),
|
|
152
|
+
},
|
|
153
|
+
},
|
|
139
154
|
);
|
|
140
155
|
}
|
|
141
156
|
|
|
@@ -151,6 +166,7 @@ export function startMetricsEndpoint(options: MetricsEndpointOptions = {}): Metr
|
|
|
151
166
|
// that way. Floor 0, because 0 asks the kernel for a free port and `role-start.ts` passes it for
|
|
152
167
|
// an ephemeral boot; the ceiling stays Bun's, which names the range it refuses.
|
|
153
168
|
const port = finiteCount('startMetricsEndpoint', 'port', options.port ?? DEFAULT_METRICS_PORT);
|
|
169
|
+
let buildId: string | null = null;
|
|
154
170
|
// `startRoles` opens this FIRST, before any role, so `Bun.serve`'s own bare `Error` was what a
|
|
155
171
|
// second `x dev` on one machine reported: no code, no fix, at the boot path this package owns.
|
|
156
172
|
const server = bindScrapePort(port, options.whenTaken ?? 'refuse', (candidate) =>
|
|
@@ -160,11 +176,12 @@ export function startMetricsEndpoint(options: MetricsEndpointOptions = {}): Metr
|
|
|
160
176
|
fetch(request: Request): Response {
|
|
161
177
|
const url = new URL(request.url);
|
|
162
178
|
const role = options.role ?? 'unknown';
|
|
163
|
-
if (url.pathname === '/healthz') return healthResponse(healthzPayload(), role);
|
|
179
|
+
if (url.pathname === '/healthz') return healthResponse(healthzPayload(), role, buildId);
|
|
164
180
|
if (url.pathname === '/readyz') {
|
|
165
181
|
return healthResponse(
|
|
166
182
|
readyzPayload({ deep: url.searchParams.get('deep') === '1' }),
|
|
167
183
|
role,
|
|
184
|
+
buildId,
|
|
168
185
|
);
|
|
169
186
|
}
|
|
170
187
|
if (url.pathname !== METRICS_PATH) {
|
|
@@ -186,6 +203,9 @@ export function startMetricsEndpoint(options: MetricsEndpointOptions = {}): Metr
|
|
|
186
203
|
logger.info('ultimate metrics listening', { url: `${server.url.origin}${METRICS_PATH}` });
|
|
187
204
|
return {
|
|
188
205
|
url: server.url.origin,
|
|
206
|
+
announceBuild(id: string): void {
|
|
207
|
+
buildId = id;
|
|
208
|
+
},
|
|
189
209
|
stop(): void {
|
|
190
210
|
server.stop(true);
|
|
191
211
|
stopListening();
|
package/src/role-start.ts
CHANGED
|
@@ -289,6 +289,8 @@ export async function startRoles(options: StartRolesOptions): Promise<RunningRol
|
|
|
289
289
|
whenTaken: whenMetricsPortTaken(binding.dev, options.env),
|
|
290
290
|
});
|
|
291
291
|
started.push(async () => metrics.stop());
|
|
292
|
+
// Opened here or adopted from `serve-boot.ts`, this is where the build id is known (#734).
|
|
293
|
+
metrics.announceBuild(options.buildId);
|
|
292
294
|
|
|
293
295
|
// BUILT here, BOUND below, the web role between them: `web` serves the node's socket on its
|
|
294
296
|
// own port and a listening server cannot be handed one, while the neighbouring-port refusals
|