@irtio/cli 0.5.2 → 0.7.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.
Files changed (39) hide show
  1. package/dist/api-keys-UTLYMZYN.js +222 -0
  2. package/dist/api.d.ts +52 -0
  3. package/dist/api.js +15 -0
  4. package/dist/bundle.js +1 -1
  5. package/dist/{chunk-GBNHBWES.js → chunk-BQBOBFBO.js} +10 -6
  6. package/dist/chunk-IDF46P7R.js +98 -0
  7. package/dist/{chunk-32QTPKVT.js → chunk-JL235KIE.js} +1 -1
  8. package/dist/chunk-OCVALOGK.js +31 -0
  9. package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
  10. package/dist/chunk-UPHQM6NZ.js +72 -0
  11. package/dist/chunk-WFMRNGO5.js +481 -0
  12. package/dist/chunk-ZK5JLUD4.js +94 -0
  13. package/dist/credentials.d.ts +61 -0
  14. package/dist/credentials.js +20 -0
  15. package/dist/delete-project-MXUYNGAO.js +118 -0
  16. package/dist/deploy.d.ts +151 -0
  17. package/dist/{deploy-3SABPL3T.js → deploy.js} +324 -44
  18. package/dist/{dev-QJOGXLKM.js → dev-AUZ4OLA3.js} +3066 -211
  19. package/dist/index.js +121 -25
  20. package/dist/init.d.ts +1 -1
  21. package/dist/init.js +20 -4
  22. package/dist/{keys-XBORZAPI.js → keys-NXRIBJZP.js} +8 -4
  23. package/dist/leaderboard-ZBJBKETM.js +406 -0
  24. package/dist/{login-3EXB4CGX.js → login-3RVN5PPN.js} +12 -5
  25. package/dist/{logs-2EOXLNWF.js → logs-AT7G6YRH.js} +9 -5
  26. package/dist/{migrate-WUO2GBMX.js → migrate-FMXTRVUV.js} +12 -8
  27. package/dist/ratings-XBLX2MUW.js +297 -0
  28. package/dist/{rollback-GA6UY772.js → rollback-LI4TDLQA.js} +9 -5
  29. package/dist/rooms-52Q5KBUS.js +411 -0
  30. package/dist/simulate.d.ts +254 -6
  31. package/dist/simulate.js +925 -64
  32. package/dist/{static-deploy-5TBH4VNA.js → static-deploy-7UCYINJB.js} +6 -4
  33. package/dist/status-JZGKH2P6.js +219 -0
  34. package/dist/usage-7S447INI.js +213 -0
  35. package/dist/{whoami-CI5D5RCC.js → whoami-UFSWPWK6.js} +8 -4
  36. package/package.json +23 -7
  37. package/dist/chunk-BPE452KF.js +0 -180
  38. package/dist/chunk-TV66QHFP.js +0 -167
  39. package/dist/rooms-B66LQIIF.js +0 -226
@@ -0,0 +1,411 @@
1
+ import {
2
+ readProjectConfig
3
+ } from "./chunk-JL235KIE.js";
4
+ import {
5
+ HelpRequested,
6
+ helpFor,
7
+ helpRequested
8
+ } from "./chunk-OCVALOGK.js";
9
+ import {
10
+ createApiClient,
11
+ isLoginRequired
12
+ } from "./chunk-IDF46P7R.js";
13
+ import {
14
+ resolveControlUrlForUser
15
+ } from "./chunk-UPHQM6NZ.js";
16
+
17
+ // src/rooms.ts
18
+ import "fs";
19
+ import "fs/promises";
20
+ import "path";
21
+ import pc from "picocolors";
22
+ var USAGE = `usage: irtio rooms [--active|--idle] [--idle-longer-than <d>] [options]
23
+ irtio rooms get <room> [options]
24
+ irtio rooms set <room> --retention <d|forever|clear> [options]
25
+ irtio rooms saves <room> [options]
26
+ irtio rooms restore <room> --save <id> [options]
27
+ irtio rooms delete <room> [--force] [options]
28
+
29
+ Lists a project's rooms, with the retention window in force for each one and when it will be
30
+ deleted. Rows not reported for longer than the control plane's row-pruning window (30 days by
31
+ default) are pruned; that prunes the listing only and never a room's stored state.
32
+
33
+ subcommands:
34
+ get <room> one room in full: its type and size class, how many save generations
35
+ it holds, how big its stored state is, and the exact deletion time
36
+ set <room> --retention <d|forever|clear>
37
+ override the retention window for THIS room. A duration like 30m, 6h
38
+ or 7d keeps it that long past its last activity; forever keeps it
39
+ indefinitely; clear goes back to what its type declares
40
+ saves <room> list a room's save generations, newest first
41
+ restore <room> --save <id>
42
+ bring a room back from a save. This DISCARDS its current state, and
43
+ stops the tenant if it is live
44
+ delete <room> delete a room's stored state: its snapshot, its saves and its alarms.
45
+ This is permanent. Refused while the room is awake unless --force
46
+
47
+ list filters:
48
+ --active only rooms that are awake in a live tenant
49
+ --idle only rooms that are not
50
+ --idle-longer-than <d>
51
+ only rooms whose last report is older than a duration like 30m, 6h or 7d
52
+
53
+ options:
54
+ --force with delete: disconnect the room's clients first, then delete it
55
+ --project <id> project id (default: the project file)
56
+ -c, --config <f> the project file to read (default irtio.json)
57
+ --url <control> control plane (default: your stored login, or IRT_API_KEY)
58
+ -h, --help print this
59
+ `;
60
+ function parseRoomsArgs(args) {
61
+ if (helpRequested(args)) throw helpFor(USAGE);
62
+ const parsed = { sub: "list" };
63
+ let rest = args;
64
+ const first = args[0];
65
+ if (first === "saves" || first === "restore" || first === "delete" || first === "get" || first === "set") {
66
+ parsed.sub = first;
67
+ const room = args[1];
68
+ if (room === void 0 || room.startsWith("-")) {
69
+ throw new Error(`irtio rooms ${first}: needs a room id`);
70
+ }
71
+ parsed.room = room;
72
+ rest = args.slice(2);
73
+ }
74
+ for (let i = 0; i < rest.length; i++) {
75
+ const arg = rest[i];
76
+ switch (arg) {
77
+ case "--save": {
78
+ const value = rest[++i];
79
+ if (value === void 0) throw new Error("irtio rooms: --save needs a value");
80
+ parsed.save = value;
81
+ break;
82
+ }
83
+ // ---- M6 lane A: rooms API ----
84
+ case "--active":
85
+ parsed.active = true;
86
+ break;
87
+ case "--idle":
88
+ parsed.active = false;
89
+ break;
90
+ case "--idle-longer-than": {
91
+ const value = rest[++i];
92
+ if (value === void 0) {
93
+ throw new Error("irtio rooms: --idle-longer-than needs a duration like 30m, 6h or 7d");
94
+ }
95
+ parsed.idleLongerThan = value;
96
+ break;
97
+ }
98
+ case "--retention": {
99
+ const value = rest[++i];
100
+ if (value === void 0) {
101
+ throw new Error("irtio rooms: --retention needs a duration, forever, or clear");
102
+ }
103
+ parsed.retention = value;
104
+ break;
105
+ }
106
+ case "--force":
107
+ parsed.force = true;
108
+ break;
109
+ // ---- end M6 lane A ----
110
+ case "--project": {
111
+ const value = rest[++i];
112
+ if (value === void 0) throw new Error("irtio rooms: --project needs a value");
113
+ parsed.project = value;
114
+ break;
115
+ }
116
+ case "-c":
117
+ case "--config": {
118
+ const value = args[++i];
119
+ if (value === void 0 || value === "")
120
+ throw new Error("irtio rooms: --config needs a value");
121
+ parsed.config = value;
122
+ break;
123
+ }
124
+ case "--url": {
125
+ const value = rest[++i];
126
+ if (value === void 0) throw new Error("irtio rooms: --url needs a value");
127
+ parsed.url = value;
128
+ break;
129
+ }
130
+ default:
131
+ throw new Error(`irtio rooms: unknown option ${JSON.stringify(arg)}`);
132
+ }
133
+ }
134
+ if (parsed.sub === "restore" && parsed.save === void 0) {
135
+ throw new Error(
136
+ "irtio rooms restore: --save <id> is required \u2014 run `irtio rooms saves <room>` to see them"
137
+ );
138
+ }
139
+ if (parsed.sub === "set" && parsed.retention === void 0) {
140
+ throw new Error(
141
+ "irtio rooms set: --retention <d|forever|clear> is required. A duration like 7d keeps this room that long past its last activity, forever keeps it indefinitely, and clear goes back to what its type declares"
142
+ );
143
+ }
144
+ if (parsed.active !== void 0 && parsed.sub !== "list") {
145
+ throw new Error("irtio rooms: --active and --idle are filters for the listing");
146
+ }
147
+ if (parsed.idleLongerThan !== void 0 && parsed.sub !== "list") {
148
+ throw new Error("irtio rooms: --idle-longer-than is a filter for the listing");
149
+ }
150
+ if (parsed.force === true && parsed.sub !== "delete") {
151
+ throw new Error("irtio rooms: --force belongs to `irtio rooms delete`");
152
+ }
153
+ if (parsed.retention !== void 0 && parsed.sub !== "set") {
154
+ throw new Error("irtio rooms: --retention belongs to `irtio rooms set`");
155
+ }
156
+ return parsed;
157
+ }
158
+ function formatBytes(bytes) {
159
+ if (bytes < 1024) return `${bytes} B`;
160
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
161
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
162
+ }
163
+ function formatAge(iso, now) {
164
+ const ms = now - Date.parse(iso);
165
+ if (!Number.isFinite(ms) || ms < 0) return "\u2014";
166
+ const mins = Math.floor(ms / 6e4);
167
+ if (mins < 1) return "just now";
168
+ if (mins < 60) return `${mins}m ago`;
169
+ const hours = Math.floor(mins / 60);
170
+ if (hours < 24) return `${hours}h ago`;
171
+ return `${Math.floor(hours / 24)}d ago`;
172
+ }
173
+ function statusColor(status) {
174
+ return status === "active" || status === "running" ? pc.green(status) : status === "draining" ? pc.yellow(status) : pc.dim(status);
175
+ }
176
+ function formatTable(rows, now) {
177
+ if (rows.length === 0) return [pc.dim("no rooms")];
178
+ const idWidth = Math.max(4, ...rows.map((r) => r.roomId.length));
179
+ const sharded = rows.some((r) => (r.shard ?? 0) > 0);
180
+ const head = `${"ROOM".padEnd(idWidth)} STATUS RETAIN DELETES LAST SEEN` + (sharded ? " SHARD" : "");
181
+ const out = [pc.dim(head)];
182
+ for (const r of rows) {
183
+ const retention = r.retention ?? "forever";
184
+ const mark = r.retentionOverride ? "*" : "";
185
+ out.push(
186
+ `${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${`${retention}${mark}`.padEnd(6)} ${formatDeadline(r.deletesAt, now).padEnd(13)} ${pc.dim(r.lastSeen)}` + (sharded ? ` ${String(r.shard ?? 0)}` : "")
187
+ );
188
+ }
189
+ if (rows.some((r) => r.retentionOverride)) {
190
+ out.push(pc.dim("* set on this room, not declared by its type"));
191
+ }
192
+ return out;
193
+ }
194
+ function formatDeadline(deletesAt, now) {
195
+ if (!deletesAt) return pc.dim("never");
196
+ const ms = Date.parse(deletesAt) - now;
197
+ if (!Number.isFinite(ms)) return pc.dim("never");
198
+ if (ms <= 0) return pc.yellow("next sweep");
199
+ const mins = Math.floor(ms / 6e4);
200
+ if (mins < 60) return `in ${mins}m`;
201
+ const hours = Math.floor(mins / 60);
202
+ if (hours < 48) return `in ${hours}h`;
203
+ return `in ${Math.floor(hours / 24)}d`;
204
+ }
205
+ function formatDetail(room, now) {
206
+ const line = (label, value) => `${pc.dim(label.padEnd(12))}${value}`;
207
+ const out = [
208
+ line("room", pc.bold(room.roomId)),
209
+ line("status", statusColor(room.status)),
210
+ line("type", room.type ?? pc.dim("not a room id"))
211
+ ];
212
+ out.push(line("class", room.class ?? pc.dim("not declared (billed as small)")));
213
+ if (room.clients !== null && room.clients !== void 0) {
214
+ const max = room.maxClients ? `/${room.maxClients}` : "";
215
+ out.push(line("clients", `${room.clients}${max}`));
216
+ }
217
+ out.push(line("retention", room.retention ?? "forever"));
218
+ if (room.retentionOverride) {
219
+ out.push(line("", pc.dim(`set on this room (${room.retentionOverride}), not by its type`)));
220
+ }
221
+ out.push(line("deletes", formatDeadline(room.deletesAt, now)));
222
+ out.push(line("saves", String(room.saves)));
223
+ out.push(
224
+ line(
225
+ "state",
226
+ room.stateBytes === void 0 ? pc.dim("unknown while the room is awake; read it after it sleeps") : formatBytes(room.stateBytes)
227
+ )
228
+ );
229
+ out.push(line("last seen", pc.dim(room.lastSeen)));
230
+ return out;
231
+ }
232
+ function formatSaves(rows, now) {
233
+ if (rows.length === 0) {
234
+ return [pc.dim("no saves"), pc.dim("a room writes one when its code calls room.save()")];
235
+ }
236
+ const idWidth = Math.max(7, ...rows.map((r) => r.saveId.length));
237
+ const out = [pc.dim(`${"SAVE ID".padEnd(idWidth)} AGE SIZE DEPLOY`)];
238
+ for (const r of rows) {
239
+ out.push(
240
+ `${r.saveId.padEnd(idWidth)} ${formatAge(r.createdAt, now).padEnd(10)} ${formatBytes(r.bytes).padEnd(8)} ${pc.dim(`v${r.version}`)}`
241
+ );
242
+ }
243
+ return out;
244
+ }
245
+ async function runSaves(options) {
246
+ const log = options.log ?? ((line) => console.log(line));
247
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
248
+ const client = options.client ?? await createApiClient(controlUrl);
249
+ const rows = await client.get(
250
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}/saves`
251
+ );
252
+ for (const line of formatSaves(rows, options.now ?? Date.now())) log(line);
253
+ return rows;
254
+ }
255
+ async function runRestore(options) {
256
+ const log = options.log ?? ((line) => console.log(line));
257
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
258
+ const client = options.client ?? await createApiClient(controlUrl);
259
+ log(
260
+ `${pc.yellow("discarding")} the current state of room ${pc.bold(options.room)} and restoring save ${pc.bold(options.saveId)}`
261
+ );
262
+ const result = await client.post(
263
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}/restore`,
264
+ { saveId: options.saveId }
265
+ );
266
+ if (result.applied === "stopped") {
267
+ log(pc.green(`restored: ${options.room} was live, so its tenant was stopped`));
268
+ log(
269
+ pc.dim(
270
+ "the room comes back from the save on the next join; every other room in this project was stopped too and comes back from its own snapshot, unchanged"
271
+ )
272
+ );
273
+ } else {
274
+ log(pc.green(`queued: ${options.room} was not running`));
275
+ log(pc.dim("the restore is applied at the next placement \u2014 the next join starts it"));
276
+ }
277
+ return result;
278
+ }
279
+ async function runDeleteRoom(options) {
280
+ const log = options.log ?? ((line) => console.log(line));
281
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
282
+ const client = options.client ?? await createApiClient(controlUrl);
283
+ log(
284
+ `${pc.yellow("deleting")} room ${pc.bold(options.room)}: its snapshot, its saves and its alarms`
285
+ );
286
+ if (options.force === true) {
287
+ log(
288
+ pc.yellow(
289
+ "forced: if the room is awake, every client in it is disconnected first and sees E_ROOM_DELETED. They cannot rejoin the same room; a fresh one starts empty"
290
+ )
291
+ );
292
+ }
293
+ const result = await client.del(
294
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`,
295
+ options.force === true ? { force: "true" } : {}
296
+ );
297
+ log(pc.green(`deleted ${options.room}: ${result.objects} stored object(s) removed`));
298
+ log(pc.dim("player data and leaderboard scores are keyed to the player and are untouched"));
299
+ return result;
300
+ }
301
+ async function runRooms(options) {
302
+ const log = options.log ?? ((line) => console.log(line));
303
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
304
+ const client = options.client ?? await createApiClient(controlUrl);
305
+ const rows = await client.get(`/v1/projects/${options.project}/rooms`, {
306
+ ...options.active !== void 0 ? { active: String(options.active) } : {},
307
+ ...options.idleLongerThan !== void 0 ? { "idle-longer-than": options.idleLongerThan } : {}
308
+ });
309
+ for (const line of formatTable(rows, options.now ?? Date.now())) log(line);
310
+ return rows;
311
+ }
312
+ async function runGetRoom(options) {
313
+ const log = options.log ?? ((line) => console.log(line));
314
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
315
+ const client = options.client ?? await createApiClient(controlUrl);
316
+ const room = await client.get(
317
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`
318
+ );
319
+ for (const line of formatDetail(room, options.now ?? Date.now())) log(line);
320
+ return room;
321
+ }
322
+ async function runSetRoom(options) {
323
+ const log = options.log ?? ((line) => console.log(line));
324
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
325
+ const client = options.client ?? await createApiClient(controlUrl);
326
+ const retention = options.retention === "clear" ? null : options.retention;
327
+ const room = await client.patch(
328
+ `/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`,
329
+ { retention }
330
+ );
331
+ log(
332
+ retention === null ? pc.green(
333
+ `${options.room}: override cleared, back to its type's window (${room.retention ?? "forever"})`
334
+ ) : pc.green(`${options.room}: retention set to ${retention}`)
335
+ );
336
+ for (const line of formatDetail(room, options.now ?? Date.now())) log(line);
337
+ return room;
338
+ }
339
+ async function rooms(args, deps = {}) {
340
+ const log = deps.log ?? ((line) => console.log(line));
341
+ const errorLog = deps.errorLog ?? ((line) => console.error(line));
342
+ try {
343
+ const parsed = parseRoomsArgs(args);
344
+ const config = await readProjectConfig(process.cwd(), "irtio rooms", parsed.config);
345
+ const project = parsed.project ?? config.project;
346
+ if (project === void 0) {
347
+ throw new Error(
348
+ "irtio rooms: no project id \u2014 pass --project or run this from a project with irtio.json"
349
+ );
350
+ }
351
+ const common = {
352
+ project,
353
+ log,
354
+ ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
355
+ ...deps.client !== void 0 ? { client: deps.client } : {}
356
+ };
357
+ if (parsed.sub === "saves") {
358
+ await runSaves({ ...common, room: parsed.room });
359
+ } else if (parsed.sub === "delete") {
360
+ await runDeleteRoom({
361
+ ...common,
362
+ room: parsed.room,
363
+ ...parsed.force === true ? { force: true } : {}
364
+ });
365
+ } else if (parsed.sub === "restore") {
366
+ await runRestore({
367
+ ...common,
368
+ room: parsed.room,
369
+ saveId: parsed.save
370
+ });
371
+ } else if (parsed.sub === "get") {
372
+ await runGetRoom({ ...common, room: parsed.room });
373
+ } else if (parsed.sub === "set") {
374
+ await runSetRoom({
375
+ ...common,
376
+ room: parsed.room,
377
+ retention: parsed.retention
378
+ });
379
+ } else {
380
+ await runRooms({
381
+ ...common,
382
+ ...parsed.active !== void 0 ? { active: parsed.active } : {},
383
+ ...parsed.idleLongerThan !== void 0 ? { idleLongerThan: parsed.idleLongerThan } : {}
384
+ });
385
+ }
386
+ } catch (err) {
387
+ if (err instanceof HelpRequested) {
388
+ log(err.usage);
389
+ return;
390
+ }
391
+ if (isLoginRequired(err)) {
392
+ errorLog(pc.red("not logged in"));
393
+ errorLog("run: irtio login");
394
+ process.exitCode = 1;
395
+ return;
396
+ }
397
+ errorLog(pc.red(err instanceof Error ? err.message : String(err)));
398
+ process.exitCode = 1;
399
+ }
400
+ }
401
+ export {
402
+ USAGE,
403
+ parseRoomsArgs,
404
+ rooms,
405
+ runDeleteRoom,
406
+ runGetRoom,
407
+ runRestore,
408
+ runRooms,
409
+ runSaves,
410
+ runSetRoom
411
+ };