@irtio/cli 0.5.1 → 0.6.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/dist/api.d.ts +52 -0
- package/dist/api.js +15 -0
- package/dist/bundle.js +1 -1
- package/dist/{static-deploy-BP3MDXCP.js → chunk-3HQMVCYA.js} +89 -48
- package/dist/chunk-DKWG7MGO.js +93 -0
- package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
- package/dist/{chunk-I37DLT7K.js → chunk-RNAH5T4W.js} +14 -3
- package/dist/chunk-RQSJZWQC.js +452 -0
- package/dist/{chunk-NVUKSP5U.js → chunk-UPHQM6NZ.js} +12 -1
- package/dist/chunk-ZD4ND6X6.js +31 -0
- package/dist/chunk-ZK5JLUD4.js +94 -0
- package/dist/credentials.d.ts +61 -0
- package/dist/credentials.js +20 -0
- package/dist/delete-project-VENS2B44.js +118 -0
- package/dist/deploy.d.ts +149 -0
- package/dist/deploy.js +567 -0
- package/dist/{dev-7UZZGE4U.js → dev-QM26ONKS.js} +3095 -294
- package/dist/index.js +113 -28
- package/dist/init.d.ts +2 -1
- package/dist/init.js +42 -1
- package/dist/{keys-KEKO3EJ6.js → keys-JHLMEGRA.js} +49 -18
- package/dist/leaderboard-SYPSBPS3.js +352 -0
- package/dist/{login-OV2EFNTJ.js → login-2M73HBZT.js} +25 -1
- package/dist/{logs-5OUDPAIX.js → logs-2W7CPZO5.js} +42 -17
- package/dist/{migrate-UD245ULI.js → migrate-T3DZJREY.js} +44 -19
- package/dist/ratings-VG32WFDG.js +297 -0
- package/dist/{rollback-CLQVYFHW.js → rollback-SO74MVZV.js} +41 -17
- package/dist/{rooms-OJ3JLYHD.js → rooms-VI33P4RA.js} +73 -20
- package/dist/simulate.d.ts +215 -3
- package/dist/simulate.js +865 -64
- package/dist/static-deploy-KOWFKWZA.js +19 -0
- package/dist/status-HF3ZEKB7.js +219 -0
- package/dist/usage-4G23QXCH.js +213 -0
- package/dist/{whoami-S73O6KJF.js → whoami-KTMTQNHM.js} +21 -2
- package/package.json +24 -7
- package/dist/chunk-BPE452KF.js +0 -180
- package/dist/chunk-D7CDJRFF.js +0 -24
- package/dist/deploy-YVCVDVMS.js +0 -396
package/dist/simulate.d.ts
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
|
-
import { SimulationReport } from '@irtio/bots';
|
|
1
|
+
import { ScenarioDefinition, BotConditions, NetworkConditions, AssertionResult, RunEnd, SimulationReport, HitRow, TruthDiff, TickHealthReading } from '@irtio/bots';
|
|
2
2
|
import { AnySchema } from '@irtio/schema';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* D41: loading a scenario file, and reading the recorded authoritative timeline back off the dev
|
|
6
|
+
* server.
|
|
7
|
+
*
|
|
8
|
+
* Loading follows `loadProjectSchema`'s pattern exactly: esbuild the user's TypeScript to ESM,
|
|
9
|
+
* dynamic-import it with a cache-busting query, and shape-check the export. `bundleRoom` is the
|
|
10
|
+
* wrong tool here for the same reason it is wrong for a schema module, only more so: it enforces
|
|
11
|
+
* the room sandbox's fixed import menu, and a scenario's whole point is that it imports
|
|
12
|
+
* `@irtio/bots`.
|
|
13
|
+
*
|
|
14
|
+
* Reading follows the tick counters' pattern exactly: the dev server's own inspector routes, on
|
|
15
|
+
* the socket's port. A deployed tenant has no authoritative tap, so it has no timeline either,
|
|
16
|
+
* and this module says so rather than inventing one from what a client received.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A scenario that could not be loaded, or a timeline that could not be recorded. Both are "the
|
|
21
|
+
* run was not performed" rather than "the room failed": nothing was measured either way, and
|
|
22
|
+
* reporting a scenario as passing because its assertions never ran would be the worst outcome
|
|
23
|
+
* this command can produce.
|
|
24
|
+
*/
|
|
25
|
+
declare class ScenarioNotRunError extends Error {
|
|
26
|
+
readonly cause?: unknown | undefined;
|
|
27
|
+
readonly name = "ScenarioNotRunError";
|
|
28
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
29
|
+
}
|
|
30
|
+
interface LoadScenarioOptions {
|
|
31
|
+
readonly cwd: string;
|
|
32
|
+
readonly outDir: string;
|
|
33
|
+
/** The scenario file, absolute or relative to `cwd`. */
|
|
34
|
+
readonly file: string;
|
|
35
|
+
/** Redirects `@irtio/*` for the bundle; the monorepo's own `src/` by default. */
|
|
36
|
+
readonly irtioPackages?: Record<string, string> | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Bundles the scenario module and returns its default export. */
|
|
39
|
+
declare function loadScenario(options: LoadScenarioOptions): Promise<{
|
|
40
|
+
scenario: ScenarioDefinition;
|
|
41
|
+
file: string;
|
|
42
|
+
}>;
|
|
43
|
+
|
|
4
44
|
/**
|
|
5
45
|
* `irtio simulate`: point N real clients at a running room, play it randomly for a few
|
|
6
46
|
* seconds, and print a pass/fail line per built-in invariant.
|
|
@@ -19,14 +59,29 @@ interface SimulateArgs {
|
|
|
19
59
|
bots: number;
|
|
20
60
|
seconds: number;
|
|
21
61
|
cheat: boolean;
|
|
62
|
+
/** D42: bots named by `--cheat-bot`. Empty means "whatever `cheat` says". */
|
|
63
|
+
cheatBots?: number[];
|
|
64
|
+
/** D42: `--conditions`, applied to every bot. */
|
|
65
|
+
conditions?: NetworkConditions;
|
|
66
|
+
/** D42: `--conditions-bot <index>:<json>`, overriding `conditions` for those indices. */
|
|
67
|
+
conditionsPerBot?: Record<number, NetworkConditions>;
|
|
68
|
+
/** D43: `--truth`, take a save at the end of the run and diff it against the clients. */
|
|
69
|
+
truth?: boolean;
|
|
22
70
|
room?: string;
|
|
23
71
|
url?: string;
|
|
24
72
|
key?: string;
|
|
25
73
|
trace?: string;
|
|
74
|
+
scenario?: string;
|
|
26
75
|
mispredictionMax?: number;
|
|
27
76
|
snapsMax?: number;
|
|
28
77
|
correctionsMax?: number;
|
|
78
|
+
overrunsMax?: number;
|
|
79
|
+
/** D65: give every bot a bandwidth ledger and print the run's breakdown. */
|
|
80
|
+
profile?: boolean;
|
|
81
|
+
/** D65: rows in that table. Implies `profile`. */
|
|
82
|
+
profileTop?: number;
|
|
29
83
|
}
|
|
84
|
+
declare const USAGE = "usage: irtio simulate [options]\n\nDrives N real clients at a running room and checks the built-in invariants. Run it from a project\ndirectory so it can load irtio/schema.ts; without one it falls back to a relay run.\n\noptions:\n --bots <n> how many clients to spawn (default 5)\n --seconds <n> how long to play for (default 10)\n --room <code> join this room instead of creating one\n --url <ws://...> where to connect (default ws://localhost:7070)\n --key <projectKey> the project key to present\n --trace <path> write the frame trace here\n --scenario <file> run a scenario module and assert against the recorded timeline\n --cheat send illegal writes and expect corrections, from every bot\n --cheat-bot <index> only this bot cheats (repeatable)\n --conditions <json> inject network conditions into every bot, e.g.\n '{\"rttMs\":200,\"jitterMs\":20,\"loss\":0.02}'. Keys: rttMs, jitterMs,\n loss, duplicate, reorder, reorderMs. Loss, duplicate and reorder\n touch state frames only, so a join always completes\n --conditions-bot <i>:<json> conditions for one bot, overriding --conditions (repeatable)\n --truth save the room at the end and diff it against what each client\n received, within that client's visibility\n --misprediction-max <units> fail if one correction snaps a prediction further than this\n --snaps-max <n> fail above this many cap-exceeded reconciliations\n --corrections-max <perSec> fail above this correction rate per bot\n --overruns-max <n> fail above this many server tick overruns in the run window\n (default 0; the count is read from the server, and the run says\n so when it could not be read)\n --profile print where the run's bytes went, by collection and field, as the\n bots saw them\n --profile-top <n> rows in that table (default 12; implies --profile)\n -h, --help print this\n\nexit codes: 0 every invariant held, 1 something was measured and failed, 2 the run could not be\nperformed (nobody joined, or it passed its wall-clock ceiling).\n";
|
|
30
85
|
/** Hand-rolled, like `parseDevArgs`: the CLI has no argument-parsing dependency. */
|
|
31
86
|
declare function parseSimulateArgs(args: readonly string[]): SimulateArgs;
|
|
32
87
|
interface LoadSchemaOptions {
|
|
@@ -61,7 +116,14 @@ interface LoadedWorld {
|
|
|
61
116
|
* way a browser client would. The engine stays external — it must be the project's own copy,
|
|
62
117
|
* resolved at import time, exactly as a room bundle resolves it.
|
|
63
118
|
*/
|
|
64
|
-
|
|
119
|
+
/** `bugs.md` #21: a 2D world module, recognised rather than refused. There is nothing to predict
|
|
120
|
+
* from it in this release, so the caller drops the predicted-world path and runs anyway. */
|
|
121
|
+
interface UnpredictedWorld {
|
|
122
|
+
readonly unpredicted: 'matter2d';
|
|
123
|
+
readonly file: string;
|
|
124
|
+
}
|
|
125
|
+
declare function isUnpredictedWorld(world: LoadedWorld | UnpredictedWorld | undefined): world is UnpredictedWorld;
|
|
126
|
+
declare function loadProjectWorld(options: LoadSchemaOptions): Promise<LoadedWorld | UnpredictedWorld | undefined>;
|
|
65
127
|
/**
|
|
66
128
|
* The build-twice determinism check: builds two worlds from the shared `setup` and compares
|
|
67
129
|
* Rapier's own snapshots byte for byte. A builder that is not pure over synced inputs
|
|
@@ -70,6 +132,76 @@ declare function loadProjectWorld(options: LoadSchemaOptions): Promise<LoadedWor
|
|
|
70
132
|
* description, or `undefined` when the worlds agree.
|
|
71
133
|
*/
|
|
72
134
|
declare function checkWorldDeterminism(world: LoadedWorld): Promise<string | undefined>;
|
|
135
|
+
/**
|
|
136
|
+
* The dev server's JSON view, which is where a local run reads the server's own tick counters.
|
|
137
|
+
* `irtio dev` serves it on the socket's own port; nothing else does, and that is deliberate — a
|
|
138
|
+
* deployed tenant's counters belong to the control plane, not to whoever can reach the socket.
|
|
139
|
+
*/
|
|
140
|
+
declare function stateUrlFor(wsUrl: string): string | undefined;
|
|
141
|
+
interface RoomTickCounters {
|
|
142
|
+
readonly overruns?: number;
|
|
143
|
+
readonly maxTickMs?: number;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* `bugs.md` #16: which build of the room a run tested.
|
|
147
|
+
*
|
|
148
|
+
* A `simulate` report used to name the room code, the bot count and the URL, and nothing that
|
|
149
|
+
* identified the code under test. That is a false-green generator: edit `irtio/room.ts`, forget
|
|
150
|
+
* that an older `dev` is still holding the port, and the run measures the bundle you replaced.
|
|
151
|
+
* The information already existed on the inspector; nothing read it.
|
|
152
|
+
*
|
|
153
|
+
* Both fields matter and neither is redundant. The bundle hash catches an edit the server never
|
|
154
|
+
* picked up; the start time catches the case the hash cannot see, which is a *different process*
|
|
155
|
+
* that happens to be serving an identical bundle, and it is the field that tells a reader the
|
|
156
|
+
* server they thought they had restarted did not restart.
|
|
157
|
+
*/
|
|
158
|
+
interface BuildIdentity {
|
|
159
|
+
/** The bundle hash the server is serving, as `irtio dev` prints it at startup. */
|
|
160
|
+
readonly bundleHash?: string;
|
|
161
|
+
/** The schema hash behind that bundle, when the server knows one. */
|
|
162
|
+
readonly schemaHash?: string;
|
|
163
|
+
/** Epoch ms when the serving process booted. */
|
|
164
|
+
readonly startedAt?: number;
|
|
165
|
+
/** Where it was read from. */
|
|
166
|
+
readonly source?: string;
|
|
167
|
+
/** Why there is no identity, when there is none. Never both this and the fields above. */
|
|
168
|
+
readonly unavailable?: string;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Reads the serving build's identity off the dev inspector, or says why it could not.
|
|
172
|
+
*
|
|
173
|
+
* Deliberately shaped like `readTickCounters`: a deployed tenant does not serve `state.json`, so
|
|
174
|
+
* against staging this returns an `unavailable` sentence rather than a wrong answer. An unknown
|
|
175
|
+
* build is a fact worth printing, and printing nothing is what caused the bug.
|
|
176
|
+
*/
|
|
177
|
+
declare function readBuildIdentity(wsUrl: string): Promise<BuildIdentity>;
|
|
178
|
+
/** The one line `bugs.md` #16 asked for, beside the URL in the report header. */
|
|
179
|
+
declare function formatBuildIdentity(build: BuildIdentity): string;
|
|
180
|
+
/**
|
|
181
|
+
* Reads one room's tick counters off the dev server, or explains why it could not. Every failure
|
|
182
|
+
* path returns a sentence rather than a zero: the `tick-health` invariant has an `unavailable`
|
|
183
|
+
* state precisely so that "the room dropped no ticks" and "nobody asked the room" stay different
|
|
184
|
+
* answers.
|
|
185
|
+
*/
|
|
186
|
+
declare function readTickCounters(wsUrl: string, roomId: string): Promise<{
|
|
187
|
+
counters?: RoomTickCounters;
|
|
188
|
+
unavailable?: string;
|
|
189
|
+
source?: string;
|
|
190
|
+
}>;
|
|
191
|
+
/**
|
|
192
|
+
* The run-window reading: overruns are a lifetime counter on the server, so the window is the
|
|
193
|
+
* difference between a baseline taken as the bots came up and a reading taken as they went down.
|
|
194
|
+
* `maxTickMs` has no windowed form (it is a running maximum) and is reported as what it is.
|
|
195
|
+
*/
|
|
196
|
+
declare function tickHealthFrom(before: {
|
|
197
|
+
counters?: RoomTickCounters;
|
|
198
|
+
unavailable?: string;
|
|
199
|
+
source?: string;
|
|
200
|
+
}, after: {
|
|
201
|
+
counters?: RoomTickCounters;
|
|
202
|
+
unavailable?: string;
|
|
203
|
+
source?: string;
|
|
204
|
+
}): TickHealthReading;
|
|
73
205
|
interface RunSimulationOptions extends Partial<SimulateArgs> {
|
|
74
206
|
/** Project root; defaults to `process.cwd()`. */
|
|
75
207
|
readonly cwd?: string;
|
|
@@ -80,12 +212,86 @@ interface RunSimulationOptions extends Partial<SimulateArgs> {
|
|
|
80
212
|
* hardware can burst past the cursor-tuned default without anything being wrong.
|
|
81
213
|
*/
|
|
82
214
|
readonly correctionsPerSecMax?: number;
|
|
215
|
+
/** D36: initial-join bound, passed through to `spawnBots`. */
|
|
216
|
+
readonly joinTimeoutMs?: number;
|
|
217
|
+
/** D36: room-gone grace, passed through to `spawnBots`. */
|
|
218
|
+
readonly roomGoneGraceMs?: number;
|
|
219
|
+
/**
|
|
220
|
+
* D36: the hard wall-clock ceiling on the whole run. Default `seconds × 3 + 30 s`. Reaching it
|
|
221
|
+
* is a fatal run condition, not a clean end.
|
|
222
|
+
*/
|
|
223
|
+
readonly ceilingMs?: number;
|
|
83
224
|
/** @internal Test seam, same as `startDev`'s. */
|
|
84
225
|
readonly irtioPackages?: Record<string, string> | undefined;
|
|
85
226
|
}
|
|
227
|
+
/** D36's wall-clock ceiling: generous enough that no healthy run meets it. */
|
|
228
|
+
declare function ceilingFor(seconds: number): number;
|
|
229
|
+
/**
|
|
230
|
+
* D36: the run could not be performed at all — nobody joined, so there is no report to print and
|
|
231
|
+
* nothing was measured. `irtio simulate` turns this into exit code 2, which is the whole point of
|
|
232
|
+
* having a third code: "clean" and "violation" are both claims about a room, and this is not one.
|
|
233
|
+
*/
|
|
234
|
+
declare class RunNotPerformedError extends Error {
|
|
235
|
+
readonly cause?: unknown | undefined;
|
|
236
|
+
readonly name = "RunNotPerformedError";
|
|
237
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* D36: what ended the run. `scripts`/`duration` are the ordinary ends; `room-gone` and `ceiling`
|
|
241
|
+
* are the two fatal run conditions, and both still print the report and write the trace.
|
|
242
|
+
*/
|
|
243
|
+
type SimulationEnd = RunEnd | 'ceiling';
|
|
244
|
+
/** D42: the adversarial section. Present whenever anything was injected or anyone cheated. */
|
|
245
|
+
interface AdversarialRun {
|
|
246
|
+
/** Per bot: what was injected and what the wrapper counted. */
|
|
247
|
+
readonly conditions: readonly BotConditions[];
|
|
248
|
+
/** Bot indices that cheated. */
|
|
249
|
+
readonly cheated: readonly number[];
|
|
250
|
+
/** Corrections drawn per cheating bot, in the same order as `cheated`. */
|
|
251
|
+
readonly correctionsPerCheater: readonly number[];
|
|
252
|
+
}
|
|
253
|
+
/** D41: what a scenario run adds to the report. Absent when no `--scenario` ran. */
|
|
254
|
+
interface ScenarioRun {
|
|
255
|
+
readonly file: string;
|
|
256
|
+
/** Every assertion the scenario ran, in order. */
|
|
257
|
+
readonly assertions: readonly AssertionResult[];
|
|
258
|
+
readonly ok: boolean;
|
|
259
|
+
/** Recorded ticks the assertions could read. */
|
|
260
|
+
readonly ticks: number;
|
|
261
|
+
/** Ticks the recorder's caps evicted. Non-zero means the recording is a tail. */
|
|
262
|
+
readonly dropped: number;
|
|
263
|
+
/** Where the recording was written, so a failed run can be re-read without re-running it. */
|
|
264
|
+
readonly timelinePath: string;
|
|
265
|
+
}
|
|
266
|
+
/** D43: the truth-seam section, when a run asked for one. */
|
|
267
|
+
interface TruthRun {
|
|
268
|
+
/** The diff, when a save was taken and decoded. */
|
|
269
|
+
readonly diff?: TruthDiff;
|
|
270
|
+
/** The save generation the diff read. */
|
|
271
|
+
readonly saveId?: string;
|
|
272
|
+
/**
|
|
273
|
+
* The decoded save's state, in `inspectState` shape. Carried because it is the only place a
|
|
274
|
+
* caller can read what the server actually held without starting a room, which is the whole
|
|
275
|
+
* point of the decoder, and because a test asserting "the illegal write was accepted" has to
|
|
276
|
+
* read authoritative state rather than infer it from a correction that did not arrive.
|
|
277
|
+
*/
|
|
278
|
+
readonly saveState?: Record<string, unknown>;
|
|
279
|
+
/** Why there is no diff, when there is none. Never both this and `diff`. */
|
|
280
|
+
readonly error?: string;
|
|
281
|
+
}
|
|
86
282
|
interface SimulationRun {
|
|
87
283
|
readonly report: SimulationReport;
|
|
88
284
|
readonly tracePath: string;
|
|
285
|
+
/** `bugs.md` #16: which build of the room this run tested, or why that is not knowable. */
|
|
286
|
+
readonly build: BuildIdentity;
|
|
287
|
+
/** D41: the scenario section, when `--scenario` ran one. */
|
|
288
|
+
readonly scenario?: ScenarioRun;
|
|
289
|
+
/** D42: what was injected per bot, and which bots cheated. */
|
|
290
|
+
readonly adversarial?: AdversarialRun;
|
|
291
|
+
/** D42: one row per `bot.shot(...)`. Absent when nothing fired. */
|
|
292
|
+
readonly hits?: readonly HitRow[];
|
|
293
|
+
/** D43: the truth-seam verdict. Absent unless the run asked for it. */
|
|
294
|
+
readonly truth?: TruthRun;
|
|
89
295
|
/** `false` when there was no schema module and the run fell back to a relay simulation. */
|
|
90
296
|
readonly schema: boolean;
|
|
91
297
|
/** `true` when the bots predicted physics from a shared world-builder module (D22 part 2). */
|
|
@@ -93,6 +299,12 @@ interface SimulationRun {
|
|
|
93
299
|
/** Result of the build-twice world-builder check: the failure text, or `undefined` if it held
|
|
94
300
|
* (or did not apply). A failure also fails the run. */
|
|
95
301
|
readonly worldError?: string;
|
|
302
|
+
/** D36: what ended the run. */
|
|
303
|
+
readonly endedBy: SimulationEnd;
|
|
304
|
+
/** D36: the fatal run condition in one sentence, when one ended the run. */
|
|
305
|
+
readonly fatal?: string;
|
|
306
|
+
/** D36: `0` clean, `1` a violation was measured, `2` the run was not performed as asked. */
|
|
307
|
+
readonly exitCode: 0 | 1 | 2;
|
|
96
308
|
/** Everything that was printed, in order — the same lines `log` received. */
|
|
97
309
|
readonly output: readonly string[];
|
|
98
310
|
}
|
|
@@ -100,4 +312,4 @@ interface SimulationRun {
|
|
|
100
312
|
declare function runSimulation(options?: RunSimulationOptions): Promise<SimulationRun>;
|
|
101
313
|
declare function simulate(args: readonly string[]): Promise<void>;
|
|
102
314
|
|
|
103
|
-
export { type LoadSchemaOptions, type RunSimulationOptions, type SimulateArgs, type SimulationRun, checkWorldDeterminism, loadProjectSchema, loadProjectWorld, parseSimulateArgs, runSimulation, simulate };
|
|
315
|
+
export { type AdversarialRun, type BuildIdentity, type LoadSchemaOptions, RunNotPerformedError, type RunSimulationOptions, ScenarioNotRunError, type ScenarioRun, type SimulateArgs, type SimulationEnd, type SimulationRun, type TruthRun, USAGE, type UnpredictedWorld, ceilingFor, checkWorldDeterminism, formatBuildIdentity, isUnpredictedWorld, loadProjectSchema, loadProjectWorld, loadScenario, parseSimulateArgs, readBuildIdentity, readTickCounters, runSimulation, simulate, stateUrlFor, tickHealthFrom };
|