@ultimat3/cli 15.0.0 → 17.0.0
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 +29 -29
- package/src/dev-roles.ts +19 -1
- package/src/dev-traces.ts +5 -1
- package/src/e2e-page.ts +16 -2
- package/src/island-shot.ts +8 -2
- package/src/metrics-endpoint.ts +6 -1
- package/src/sync-authenticator.ts +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,34 +37,34 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@babel/core": "^7.28.4",
|
|
40
|
-
"@ultimat3/action": "
|
|
41
|
-
"@ultimat3/admin": "
|
|
42
|
-
"@ultimat3/ai": "
|
|
43
|
-
"@ultimat3/auth": "
|
|
44
|
-
"@ultimat3/cache": "
|
|
45
|
-
"@ultimat3/core": "
|
|
46
|
-
"@ultimat3/db": "
|
|
47
|
-
"@ultimat3/entity": "
|
|
48
|
-
"@ultimat3/flags": "
|
|
49
|
-
"@ultimat3/http": "
|
|
50
|
-
"@ultimat3/i18n": "
|
|
51
|
-
"@ultimat3/jobs": "
|
|
52
|
-
"@ultimat3/mail": "
|
|
53
|
-
"@ultimat3/manifest": "
|
|
54
|
-
"@ultimat3/mcp": "
|
|
55
|
-
"@ultimat3/money": "
|
|
56
|
-
"@ultimat3/notify": "
|
|
57
|
-
"@ultimat3/policy": "
|
|
58
|
-
"@ultimat3/pwa": "
|
|
59
|
-
"@ultimat3/query": "
|
|
60
|
-
"@ultimat3/realtime": "
|
|
61
|
-
"@ultimat3/render": "
|
|
62
|
-
"@ultimat3/schema": "
|
|
63
|
-
"@ultimat3/scraping": "
|
|
64
|
-
"@ultimat3/seo": "
|
|
65
|
-
"@ultimat3/storage": "
|
|
66
|
-
"@ultimat3/testing": "
|
|
67
|
-
"@ultimat3/time": "
|
|
40
|
+
"@ultimat3/action": "17.0.0",
|
|
41
|
+
"@ultimat3/admin": "17.0.0",
|
|
42
|
+
"@ultimat3/ai": "17.0.0",
|
|
43
|
+
"@ultimat3/auth": "17.0.0",
|
|
44
|
+
"@ultimat3/cache": "17.0.0",
|
|
45
|
+
"@ultimat3/core": "17.0.0",
|
|
46
|
+
"@ultimat3/db": "17.0.0",
|
|
47
|
+
"@ultimat3/entity": "17.0.0",
|
|
48
|
+
"@ultimat3/flags": "17.0.0",
|
|
49
|
+
"@ultimat3/http": "17.0.0",
|
|
50
|
+
"@ultimat3/i18n": "17.0.0",
|
|
51
|
+
"@ultimat3/jobs": "17.0.0",
|
|
52
|
+
"@ultimat3/mail": "17.0.0",
|
|
53
|
+
"@ultimat3/manifest": "17.0.0",
|
|
54
|
+
"@ultimat3/mcp": "17.0.0",
|
|
55
|
+
"@ultimat3/money": "17.0.0",
|
|
56
|
+
"@ultimat3/notify": "17.0.0",
|
|
57
|
+
"@ultimat3/policy": "17.0.0",
|
|
58
|
+
"@ultimat3/pwa": "17.0.0",
|
|
59
|
+
"@ultimat3/query": "17.0.0",
|
|
60
|
+
"@ultimat3/realtime": "17.0.0",
|
|
61
|
+
"@ultimat3/render": "17.0.0",
|
|
62
|
+
"@ultimat3/schema": "17.0.0",
|
|
63
|
+
"@ultimat3/scraping": "17.0.0",
|
|
64
|
+
"@ultimat3/seo": "17.0.0",
|
|
65
|
+
"@ultimat3/storage": "17.0.0",
|
|
66
|
+
"@ultimat3/testing": "17.0.0",
|
|
67
|
+
"@ultimat3/time": "17.0.0",
|
|
68
68
|
"babel-preset-solid": "^1.9.15"
|
|
69
69
|
}
|
|
70
70
|
}
|
package/src/dev-roles.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
configuredHttp,
|
|
15
15
|
createServer,
|
|
16
16
|
defineHttpConfig,
|
|
17
|
+
MAX_PROXY_HOPS,
|
|
17
18
|
mergeHttpConfig,
|
|
18
19
|
} from '@ultimat3/http';
|
|
19
20
|
import type { OutboxRelay, Scheduler, Worker } from '@ultimat3/jobs';
|
|
@@ -188,6 +189,23 @@ function warnIfUnauthenticatable(routes: readonly Route[]): void {
|
|
|
188
189
|
);
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
/**
|
|
193
|
+
* How many proxies is too many, and the number is **`@ultimat3/http`'s**, not this file's.
|
|
194
|
+
*
|
|
195
|
+
* `defineHttpConfig` screens the same setting — `assertFiniteCount('trustedProxyHops', …,
|
|
196
|
+
* MAX_PROXY_HOPS)` — and this screen used to say 16 where that one says 64, so one setting had two
|
|
197
|
+
* ceilings and a deployment behind 20 hops was accepted by the library and refused by the boot.
|
|
198
|
+
* Widened rather than narrowed: tightening the library would break a shipped public API, while
|
|
199
|
+
* this end only ever refused topologies http already supports.
|
|
200
|
+
*
|
|
201
|
+
* It was a duplicated literal until 2026-08-26, because `@ultimat3/http` did not export the
|
|
202
|
+
* constant. It does now, so this file IMPORTS it — a downward edge, tier 5 to tier 2 — and there is
|
|
203
|
+
* no second number left to drift. `runtime-overrides.test.ts` still probes both screens for the
|
|
204
|
+
* highest count each accepts, and it is kept rather than deleted: it compares BEHAVIOUR, so it also
|
|
205
|
+
* catches the two disagreeing for a reason a shared constant cannot fix, such as one side gaining a
|
|
206
|
+
* range check the other does not have.
|
|
207
|
+
*/
|
|
208
|
+
|
|
191
209
|
/**
|
|
192
210
|
* How many proxies append to `x-forwarded-for` between the client and this process, or `null`
|
|
193
211
|
* when nothing in front of it is trusted.
|
|
@@ -207,7 +225,7 @@ export function trustedHopsFromEnv(env: Env): number | null {
|
|
|
207
225
|
const hops = Number(raw);
|
|
208
226
|
// A malformed count is refused rather than defaulted: reading the header at the wrong index is
|
|
209
227
|
// trusting a value the client typed, which is the failure trusting a proxy exists to avoid.
|
|
210
|
-
if (!Number.isInteger(hops) || hops < 1 || hops >
|
|
228
|
+
if (!Number.isInteger(hops) || hops < 1 || hops > MAX_PROXY_HOPS) {
|
|
211
229
|
throw new PortInvalidError({ value: raw, name: 'TRUSTED_PROXY_HOPS' });
|
|
212
230
|
}
|
|
213
231
|
return hops;
|
package/src/dev-traces.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { RequestTrace, SpanKind, TimelineSpan } from '@ultimat3/admin/dev';
|
|
7
7
|
import type { ReadableSpan, SpanExporter } from '@ultimat3/core';
|
|
8
|
+
import { finiteCount } from '@ultimat3/core';
|
|
8
9
|
// The attribute name is `@ultimat3/db`'s to declare — this reads it rather than restating it, so
|
|
9
10
|
// renaming it there is a compile error here instead of a panel that silently groups nothing.
|
|
10
11
|
import { STATEMENT_ATTRIBUTE } from '@ultimat3/db';
|
|
@@ -130,7 +131,10 @@ function toTrace(root: ReadableSpan, spans: readonly ReadableSpan[]): RequestTra
|
|
|
130
131
|
* root is what keeps a half-finished request, and a job's spans, out of a panel about requests.
|
|
131
132
|
*/
|
|
132
133
|
export function createTraceRecorder(options: { limit?: number } = {}): TraceRecorder {
|
|
133
|
-
|
|
134
|
+
// `byTrace.size > NaN` is false on every pass, so an unchecked limit does not widen the buffer —
|
|
135
|
+
// it deletes the eviction loop, and a dev session then holds every span of every request it has
|
|
136
|
+
// ever seen. At least 1: a recorder that retains nothing is what `/_x/timeline` reads.
|
|
137
|
+
const limit = finiteCount('createTraceRecorder', 'limit', options.limit ?? DEFAULT_LIMIT, 1);
|
|
134
138
|
// Insertion-ordered: the oldest trace id is the first key, which is the one eviction drops.
|
|
135
139
|
const byTrace = new Map<string, ReadableSpan[]>();
|
|
136
140
|
|
package/src/e2e-page.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// e2e test drives and `@ultimat3/scraping` owns the only driver that can drive one, and neither
|
|
3
3
|
// may import the other — so the join is here, in the one package allowed to know about both.
|
|
4
4
|
|
|
5
|
+
import { finiteCount } from '@ultimat3/core';
|
|
5
6
|
import type { LocatorLike, PageLike } from '@ultimat3/testing';
|
|
6
7
|
import { E2eServiceWorkerAbsentError } from './e2e-errors';
|
|
7
8
|
import { evaluateClosure } from './e2e-evaluate';
|
|
@@ -30,6 +31,9 @@ export interface E2ePageOptions {
|
|
|
30
31
|
readonly serviceWorkerTimeoutMs?: number | undefined;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/** Named once, so both refusals below name the call an app's test preload actually makes. */
|
|
35
|
+
const SUBJECT = 'installE2eDriver';
|
|
36
|
+
|
|
33
37
|
export const DEFAULT_E2E_TIMEOUT_MS = 30_000;
|
|
34
38
|
export const DEFAULT_SERVICE_WORKER_TIMEOUT_MS = 10_000;
|
|
35
39
|
|
|
@@ -82,8 +86,18 @@ const serviceWorkerExpression = (timeoutMs: number): string =>
|
|
|
82
86
|
*/
|
|
83
87
|
export function e2ePage(options: E2ePageOptions): PageLike {
|
|
84
88
|
const page = options.page;
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
// Both are screened HERE, at construction, and not where they land: `timeout` is handed to a
|
|
90
|
+
// driver that reads it as a deadline, and `swTimeout` is INTERPOLATED into the in-page source,
|
|
91
|
+
// where `setTimeout(fn, NaN)` is `setTimeout(fn, 0)` — so a NaN budget makes every
|
|
92
|
+
// `waitForServiceWorker()` refuse a worker that really did take control. A misdiagnosis reported
|
|
93
|
+
// as a test failure is worse than the failure. Floor 0, because a driver reads `timeout: 0` as
|
|
94
|
+
// "no deadline" and that is a value an app is entitled to declare.
|
|
95
|
+
const timeout = finiteCount(SUBJECT, 'timeoutMs', options.timeoutMs ?? DEFAULT_E2E_TIMEOUT_MS);
|
|
96
|
+
const swTimeout = finiteCount(
|
|
97
|
+
SUBJECT,
|
|
98
|
+
'serviceWorkerTimeoutMs',
|
|
99
|
+
options.serviceWorkerTimeoutMs ?? DEFAULT_SERVICE_WORKER_TIMEOUT_MS,
|
|
100
|
+
);
|
|
87
101
|
const absolute = (url: string): string => new URL(url, options.baseUrl).toString();
|
|
88
102
|
const locate = (selection: E2eSelection): LocatorLike => e2eLocator(page, selection);
|
|
89
103
|
|
package/src/island-shot.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// why: no Bun native joins a path; `Bun.write` and `Bun.file` both take one already joined.
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
+
import { finiteCount } from '@ultimat3/core';
|
|
8
9
|
import type { ScrapeDriver, ScrapeSession } from '@ultimat3/scraping';
|
|
9
10
|
import { systemScrapeClock } from '@ultimat3/scraping';
|
|
10
11
|
import type { IslandShotTarget, IslandStatesManifest, IslandViewport } from '@ultimat3/testing';
|
|
@@ -157,6 +158,7 @@ async function captureOne(
|
|
|
157
158
|
options: IslandShotRun,
|
|
158
159
|
server: ShotServer,
|
|
159
160
|
target: IslandShotTarget,
|
|
161
|
+
floor: number,
|
|
160
162
|
): Promise<IslandStateShot> {
|
|
161
163
|
const url = new URL(`${ISLAND_HARNESS_PATH}${target.query}`, server.url).toString();
|
|
162
164
|
let session: ScrapeSession | undefined;
|
|
@@ -203,7 +205,6 @@ async function captureOne(
|
|
|
203
205
|
// Never `fullPage`: the frame is the state's own declared viewport, and a full-page capture
|
|
204
206
|
// would grow with whatever the component scrolled.
|
|
205
207
|
const bytes = await page.screenshot({ fullPage: false });
|
|
206
|
-
const floor = options.minBytes ?? MIN_SHOT_BYTES;
|
|
207
208
|
if (bytes.byteLength < floor) {
|
|
208
209
|
throw new IslandUnphotographableError({
|
|
209
210
|
island: target.island,
|
|
@@ -257,6 +258,11 @@ export async function runIslandShot(options: IslandShotRun): Promise<IslandArtif
|
|
|
257
258
|
const targets = islandShotTargets(options.manifest).filter(
|
|
258
259
|
(target) => chosen === null || chosen.has(target.state),
|
|
259
260
|
);
|
|
261
|
+
// Before the boot, and before a browser: `bytes.byteLength < NaN` is false for every picture, so
|
|
262
|
+
// an unchecked floor does not lower the backstop — it removes it, and "produced nothing and
|
|
263
|
+
// exited 0" is the one outcome a reader cannot tell from success. 0 stays legal and is what
|
|
264
|
+
// `island-shot.test.ts` passes: the fake driver answers an 8-byte PNG signature.
|
|
265
|
+
const floor = finiteCount('runIslandShot', 'minBytes', options.minBytes ?? MIN_SHOT_BYTES);
|
|
260
266
|
const server = await options.boot();
|
|
261
267
|
const shots: IslandStateShot[] = [];
|
|
262
268
|
const failures: unknown[] = [];
|
|
@@ -266,7 +272,7 @@ export async function runIslandShot(options: IslandShotRun): Promise<IslandArtif
|
|
|
266
272
|
// the app CAN produce plus a named reason for each one it cannot, and the missing-shot gate
|
|
267
273
|
// below is what turns those reasons into a non-zero exit.
|
|
268
274
|
try {
|
|
269
|
-
shots.push(await captureOne(options, server, target));
|
|
275
|
+
shots.push(await captureOne(options, server, target, floor));
|
|
270
276
|
} catch (error) {
|
|
271
277
|
failures.push(error);
|
|
272
278
|
}
|
package/src/metrics-endpoint.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// `docker/helm`'s HPAs read a number instead of `<unknown>`.
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
+
finiteCount,
|
|
6
7
|
logger,
|
|
7
8
|
METRICS_CONTENT_TYPE,
|
|
8
9
|
METRICS_PATH,
|
|
@@ -78,7 +79,11 @@ export interface MetricsEndpoint {
|
|
|
78
79
|
* signal at the moment of load is worse than no autoscaler.
|
|
79
80
|
*/
|
|
80
81
|
export function startMetricsEndpoint(options: MetricsEndpointOptions = {}): MetricsEndpoint {
|
|
81
|
-
|
|
82
|
+
// Screened here rather than left to `Bun.serve`, which refuses a `NaN` with a bare `RangeError`
|
|
83
|
+
// — no code, no `fix:` — at exactly the boot path the refusal below exists to stop reporting
|
|
84
|
+
// that way. Floor 0, because 0 asks the kernel for a free port and `dev-roles.ts` passes it for
|
|
85
|
+
// an ephemeral boot; the ceiling stays Bun's, which names the range it refuses.
|
|
86
|
+
const port = finiteCount('startMetricsEndpoint', 'port', options.port ?? DEFAULT_METRICS_PORT);
|
|
82
87
|
// `startRoles` opens this FIRST, before any role, so `Bun.serve`'s own bare `Error` was what a
|
|
83
88
|
// second `x dev` on one machine reported: no code, no fix, at the boot path this package owns.
|
|
84
89
|
// The return type is inferred, keeping `Bun.serve`'s own shape stated once.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// by wiring, not by design.
|
|
6
6
|
|
|
7
7
|
import type { Actor, Clock } from '@ultimat3/core';
|
|
8
|
-
import { systemClock } from '@ultimat3/core';
|
|
8
|
+
import { finiteCount, systemClock } from '@ultimat3/core';
|
|
9
9
|
import type { HttpConfig } from '@ultimat3/http';
|
|
10
10
|
import {
|
|
11
11
|
configuredAuthenticator,
|
|
@@ -102,7 +102,13 @@ export function syncAuthenticator(
|
|
|
102
102
|
// this per connection otherwise.
|
|
103
103
|
const config = upgradeConfig(buildId);
|
|
104
104
|
const clock = options.clock ?? systemClock;
|
|
105
|
-
|
|
105
|
+
// A credential lifetime, screened where it is declared. `expiresAt` is `now + ttlMs`, and
|
|
106
|
+
// `GrantBook.expired()` asks `expiresAt <= now` — false for every `now` when the sum is `NaN`,
|
|
107
|
+
// so the grant never reaches a sweep and the socket keeps a revoked actor's authority for as
|
|
108
|
+
// long as the tab is open. That is the hole this whole file was written to close, and a `??`
|
|
109
|
+
// does not close it: `NaN` is not nullish. At least 1ms — a window that has already shut when
|
|
110
|
+
// the grant is minted is not a window.
|
|
111
|
+
const ttlMs = finiteCount('syncAuthenticator', 'ttlMs', options.ttlMs ?? SYNC_GRANT_TTL_MS, 1);
|
|
106
112
|
|
|
107
113
|
const resolve = async (credential: Credential): Promise<SyncGrant | null> => {
|
|
108
114
|
const request = new Request(credential.url, {
|