@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/cli",
3
- "version": "27.2.1",
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.1",
43
- "@ultimat3/admin": "27.2.1",
44
- "@ultimat3/ai": "27.2.1",
45
- "@ultimat3/auth": "27.2.1",
46
- "@ultimat3/cache": "27.2.1",
47
- "@ultimat3/core": "27.2.1",
48
- "@ultimat3/db": "27.2.1",
49
- "@ultimat3/entity": "27.2.1",
50
- "@ultimat3/flags": "27.2.1",
51
- "@ultimat3/http": "27.2.1",
52
- "@ultimat3/i18n": "27.2.1",
53
- "@ultimat3/jobs": "27.2.1",
54
- "@ultimat3/mail": "27.2.1",
55
- "@ultimat3/manifest": "27.2.1",
56
- "@ultimat3/mcp": "27.2.1",
57
- "@ultimat3/money": "27.2.1",
58
- "@ultimat3/notify": "27.2.1",
59
- "@ultimat3/policy": "27.2.1",
60
- "@ultimat3/pwa": "27.2.1",
61
- "@ultimat3/query": "27.2.1",
62
- "@ultimat3/realtime": "27.2.1",
63
- "@ultimat3/render": "27.2.1",
64
- "@ultimat3/schema": "27.2.1",
65
- "@ultimat3/seo": "27.2.1",
66
- "@ultimat3/storage": "27.2.1",
67
- "@ultimat3/testing": "27.2.1",
68
- "@ultimat3/time": "27.2.1",
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
  }
@@ -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];
@@ -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
- { status: payload.status, headers: { 'cache-control': 'no-store' } },
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