@irtio/cli 0.6.0 → 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.
- package/dist/api-keys-UTLYMZYN.js +222 -0
- package/dist/api.js +1 -1
- package/dist/{chunk-3HQMVCYA.js → chunk-BQBOBFBO.js} +4 -4
- package/dist/{chunk-RNAH5T4W.js → chunk-IDF46P7R.js} +2 -0
- package/dist/{chunk-DKWG7MGO.js → chunk-JL235KIE.js} +1 -1
- package/dist/{chunk-ZD4ND6X6.js → chunk-OCVALOGK.js} +1 -1
- package/dist/{chunk-RQSJZWQC.js → chunk-WFMRNGO5.js} +134 -105
- package/dist/{delete-project-VENS2B44.js → delete-project-MXUYNGAO.js} +2 -2
- package/dist/deploy.d.ts +3 -1
- package/dist/deploy.js +163 -5
- package/dist/{dev-QM26ONKS.js → dev-AUZ4OLA3.js} +139 -10
- package/dist/index.js +39 -15
- package/dist/init.js +2 -2
- package/dist/{keys-JHLMEGRA.js → keys-NXRIBJZP.js} +3 -3
- package/dist/{leaderboard-SYPSBPS3.js → leaderboard-ZBJBKETM.js} +65 -11
- package/dist/{login-2M73HBZT.js → login-3RVN5PPN.js} +2 -2
- package/dist/{logs-2W7CPZO5.js → logs-AT7G6YRH.js} +3 -3
- package/dist/{migrate-T3DZJREY.js → migrate-FMXTRVUV.js} +3 -3
- package/dist/{ratings-VG32WFDG.js → ratings-XBLX2MUW.js} +3 -3
- package/dist/{rollback-SO74MVZV.js → rollback-LI4TDLQA.js} +3 -3
- package/dist/{rooms-VI33P4RA.js → rooms-52Q5KBUS.js} +179 -20
- package/dist/simulate.d.ts +115 -10
- package/dist/simulate.js +270 -36
- package/dist/{static-deploy-KOWFKWZA.js → static-deploy-7UCYINJB.js} +5 -5
- package/dist/{status-HF3ZEKB7.js → status-JZGKH2P6.js} +3 -3
- package/dist/{usage-4G23QXCH.js → usage-7S447INI.js} +3 -3
- package/dist/{whoami-KTMTQNHM.js → whoami-UFSWPWK6.js} +2 -2
- package/package.json +7 -7
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
HelpRequested,
|
|
6
6
|
helpFor,
|
|
7
7
|
helpRequested
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient,
|
|
11
11
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveControlUrlForUser
|
|
15
15
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -33,6 +33,9 @@ delete remove a whole board, or one player's row with --player. Needs your log
|
|
|
33
33
|
|
|
34
34
|
options:
|
|
35
35
|
--limit <n> how many rows (default 10, max 100)
|
|
36
|
+
--period <key> read a past period (2026-09-03, 2026-W36, 2026-09, or a season
|
|
37
|
+
name). Default: whichever period the board is in now
|
|
38
|
+
--bucket <name> which cohort to read, on a board configured with buckets
|
|
36
39
|
--cursor <c> continue from a previous page's nextCursor
|
|
37
40
|
--all page through the whole board, following cursors
|
|
38
41
|
--around <player> show the rows around one player instead of the top, with their rank
|
|
@@ -63,6 +66,23 @@ function parseLeaderboardArgs(args) {
|
|
|
63
66
|
case "--all":
|
|
64
67
|
parsed.all = true;
|
|
65
68
|
break;
|
|
69
|
+
// ---- M6 lane B: boards ----
|
|
70
|
+
case "--period": {
|
|
71
|
+
const value = args[++i];
|
|
72
|
+
if (value === void 0 || value === "") {
|
|
73
|
+
throw new Error("irtio leaderboard: --period needs a period key, e.g. 2026-W36");
|
|
74
|
+
}
|
|
75
|
+
parsed.period = value;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "--bucket": {
|
|
79
|
+
const value = args[++i];
|
|
80
|
+
if (value === void 0 || value === "") {
|
|
81
|
+
throw new Error("irtio leaderboard: --bucket needs a bucket name, e.g. eu");
|
|
82
|
+
}
|
|
83
|
+
parsed.bucket = value;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
66
86
|
case "--yes":
|
|
67
87
|
parsed.yes = true;
|
|
68
88
|
break;
|
|
@@ -125,7 +145,12 @@ function parseLeaderboardArgs(args) {
|
|
|
125
145
|
}
|
|
126
146
|
function formatLeaderboard(body, around) {
|
|
127
147
|
const direction = body.direction === "lower" ? "lower is better" : "higher is better";
|
|
128
|
-
const
|
|
148
|
+
const scope = [
|
|
149
|
+
body.period === void 0 || body.period === "" ? void 0 : `period ${body.period}`,
|
|
150
|
+
body.bucket === void 0 || body.bucket === "" ? void 0 : `bucket ${body.bucket}`
|
|
151
|
+
].filter((part) => part !== void 0);
|
|
152
|
+
const header = [`board ${body.board}`, ...scope, direction].join(" \xB7 ");
|
|
153
|
+
const out = [pc.dim(header), ""];
|
|
129
154
|
if (body.entries.length === 0) {
|
|
130
155
|
out.push(
|
|
131
156
|
pc.dim(
|
|
@@ -150,20 +175,29 @@ async function runLeaderboard(options) {
|
|
|
150
175
|
const base = `${controlUrl}/v1/leaderboard/${encodeURIComponent(
|
|
151
176
|
options.project
|
|
152
177
|
)}/${encodeURIComponent(options.board)}`;
|
|
178
|
+
const scoped = () => {
|
|
179
|
+
const query = new URLSearchParams();
|
|
180
|
+
if (options.period !== void 0) query.set("period", options.period);
|
|
181
|
+
if (options.bucket !== void 0) query.set("bucket", options.bucket);
|
|
182
|
+
return query;
|
|
183
|
+
};
|
|
184
|
+
const withQuery = (url, query) => {
|
|
185
|
+
const q = query.toString();
|
|
186
|
+
return `${url}${q === "" ? "" : `?${q}`}`;
|
|
187
|
+
};
|
|
153
188
|
if (options.around !== void 0) {
|
|
154
189
|
const body2 = await readPublicJson(
|
|
155
190
|
fetchImpl,
|
|
156
|
-
`${base}/around/${encodeURIComponent(options.around)}
|
|
191
|
+
withQuery(`${base}/around/${encodeURIComponent(options.around)}`, scoped())
|
|
157
192
|
);
|
|
158
193
|
for (const line of formatLeaderboard(body2, options.around)) log(line);
|
|
159
194
|
return body2;
|
|
160
195
|
}
|
|
161
196
|
const pageUrl = (cursor) => {
|
|
162
|
-
const query =
|
|
197
|
+
const query = scoped();
|
|
163
198
|
if (options.limit !== void 0) query.set("limit", String(options.limit));
|
|
164
199
|
if (cursor !== void 0) query.set("cursor", cursor);
|
|
165
|
-
|
|
166
|
-
return `${base}/top${q === "" ? "" : `?${q}`}`;
|
|
200
|
+
return withQuery(`${base}/top`, query);
|
|
167
201
|
};
|
|
168
202
|
let body = await readPublicJson(fetchImpl, pageUrl(options.cursor));
|
|
169
203
|
const entries = [...body.entries];
|
|
@@ -210,10 +244,22 @@ function formatBoardList(boards) {
|
|
|
210
244
|
];
|
|
211
245
|
}
|
|
212
246
|
const nameWidth = Math.max(5, ...boards.map((b) => b.board.length));
|
|
213
|
-
const
|
|
247
|
+
const rotates = boards.some((b) => b.period !== void 0 || b.buckets === true);
|
|
248
|
+
const periodOf = (b) => b.period === void 0 ? "all-time" : `${b.period}${b.currentPeriod === void 0 ? "" : ` (${b.currentPeriod})`}`;
|
|
249
|
+
const periodWidth = rotates ? Math.max(6, ...boards.map((b) => periodOf(b).length)) : 0;
|
|
250
|
+
const head = rotates ? `${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} ${"PERIOD".padEnd(periodWidth)} DIRECTION` : `${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} DIRECTION`;
|
|
251
|
+
const out = [pc.dim(head)];
|
|
214
252
|
for (const b of boards) {
|
|
215
|
-
const
|
|
216
|
-
|
|
253
|
+
const notes = [
|
|
254
|
+
b.buckets === true ? "buckets" : void 0,
|
|
255
|
+
b.keep === void 0 ? void 0 : `keeps ${b.keep}`,
|
|
256
|
+
b.configured && b.rows === 0 ? "configured, no rows yet" : void 0
|
|
257
|
+
].filter((n) => n !== void 0);
|
|
258
|
+
const note = notes.length === 0 ? "" : pc.dim(` (${notes.join(", ")})`);
|
|
259
|
+
const scope = rotates ? `${periodOf(b).padEnd(periodWidth)} ` : "";
|
|
260
|
+
out.push(
|
|
261
|
+
`${b.board.padEnd(nameWidth)} ${String(b.rows).padStart(7)} ${scope}${b.direction}${note}`
|
|
262
|
+
);
|
|
217
263
|
}
|
|
218
264
|
return out;
|
|
219
265
|
}
|
|
@@ -312,6 +358,12 @@ async function leaderboard(args, deps = {}) {
|
|
|
312
358
|
if (parsed.cursor !== void 0 && parsed.all === true) {
|
|
313
359
|
throw new Error("irtio leaderboard: --cursor and --all are two ways to do the same thing");
|
|
314
360
|
}
|
|
361
|
+
if (parsed.period !== void 0 && parsed.action !== void 0) {
|
|
362
|
+
throw new Error("irtio leaderboard: --period is for a read, not for list or delete");
|
|
363
|
+
}
|
|
364
|
+
if (parsed.bucket !== void 0 && parsed.action !== void 0) {
|
|
365
|
+
throw new Error("irtio leaderboard: --bucket is for a read, not for list or delete");
|
|
366
|
+
}
|
|
315
367
|
if (parsed.around !== void 0 && (parsed.cursor !== void 0 || parsed.all === true)) {
|
|
316
368
|
throw new Error("irtio leaderboard: --around does not page; it is already a window");
|
|
317
369
|
}
|
|
@@ -323,6 +375,8 @@ async function leaderboard(args, deps = {}) {
|
|
|
323
375
|
...parsed.around !== void 0 ? { around: parsed.around } : {},
|
|
324
376
|
...parsed.cursor !== void 0 ? { cursor: parsed.cursor } : {},
|
|
325
377
|
...parsed.all === true ? { all: true } : {},
|
|
378
|
+
...parsed.period !== void 0 ? { period: parsed.period } : {},
|
|
379
|
+
...parsed.bucket !== void 0 ? { bucket: parsed.bucket } : {},
|
|
326
380
|
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {}
|
|
327
381
|
});
|
|
328
382
|
} catch (err) {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
HelpRequested,
|
|
6
6
|
helpFor,
|
|
7
7
|
helpRequested
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient,
|
|
11
11
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveControlUrlForUser
|
|
15
15
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
bundleRoom
|
|
6
6
|
} from "./chunk-OTSFRVJN.js";
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
HelpRequested,
|
|
9
9
|
helpFor,
|
|
10
10
|
helpRequested
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-OCVALOGK.js";
|
|
12
12
|
import {
|
|
13
13
|
createApiClient,
|
|
14
14
|
isLoginRequired
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IDF46P7R.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveControlUrlForUser
|
|
18
18
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
HelpRequested,
|
|
6
6
|
helpFor,
|
|
7
7
|
helpRequested
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient,
|
|
11
11
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveControlUrlForUser
|
|
15
15
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
HelpRequested,
|
|
6
6
|
helpFor,
|
|
7
7
|
helpRequested
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient,
|
|
11
11
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveControlUrlForUser
|
|
15
15
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readProjectConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
4
|
import {
|
|
5
5
|
HelpRequested,
|
|
6
6
|
helpFor,
|
|
7
7
|
helpRequested
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
9
|
import {
|
|
10
10
|
createApiClient,
|
|
11
11
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
13
|
import {
|
|
14
14
|
resolveControlUrlForUser
|
|
15
15
|
} from "./chunk-UPHQM6NZ.js";
|
|
@@ -19,27 +19,42 @@ import "fs";
|
|
|
19
19
|
import "fs/promises";
|
|
20
20
|
import "path";
|
|
21
21
|
import pc from "picocolors";
|
|
22
|
-
var USAGE = `usage: irtio rooms [options]
|
|
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]
|
|
23
25
|
irtio rooms saves <room> [options]
|
|
24
26
|
irtio rooms restore <room> --save <id> [options]
|
|
25
|
-
irtio rooms delete <room> [options]
|
|
27
|
+
irtio rooms delete <room> [--force] [options]
|
|
26
28
|
|
|
27
|
-
Lists a project's rooms, with the retention window each one
|
|
28
|
-
longer than the control plane's row-pruning window (30 days by
|
|
29
|
-
listing only and never a room's stored state.
|
|
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.
|
|
30
32
|
|
|
31
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
|
|
32
40
|
saves <room> list a room's save generations, newest first
|
|
33
41
|
restore <room> --save <id>
|
|
34
42
|
bring a room back from a save. This DISCARDS its current state, and
|
|
35
43
|
stops the tenant if it is live
|
|
36
44
|
delete <room> delete a room's stored state: its snapshot, its saves and its alarms.
|
|
37
|
-
This is permanent. Refused while the room is awake
|
|
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
|
|
38
52
|
|
|
39
53
|
options:
|
|
54
|
+
--force with delete: disconnect the room's clients first, then delete it
|
|
40
55
|
--project <id> project id (default: the project file)
|
|
41
56
|
-c, --config <f> the project file to read (default irtio.json)
|
|
42
|
-
--url <control> control plane (default: your stored login)
|
|
57
|
+
--url <control> control plane (default: your stored login, or IRT_API_KEY)
|
|
43
58
|
-h, --help print this
|
|
44
59
|
`;
|
|
45
60
|
function parseRoomsArgs(args) {
|
|
@@ -47,7 +62,7 @@ function parseRoomsArgs(args) {
|
|
|
47
62
|
const parsed = { sub: "list" };
|
|
48
63
|
let rest = args;
|
|
49
64
|
const first = args[0];
|
|
50
|
-
if (first === "saves" || first === "restore" || first === "delete") {
|
|
65
|
+
if (first === "saves" || first === "restore" || first === "delete" || first === "get" || first === "set") {
|
|
51
66
|
parsed.sub = first;
|
|
52
67
|
const room = args[1];
|
|
53
68
|
if (room === void 0 || room.startsWith("-")) {
|
|
@@ -65,6 +80,33 @@ function parseRoomsArgs(args) {
|
|
|
65
80
|
parsed.save = value;
|
|
66
81
|
break;
|
|
67
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 ----
|
|
68
110
|
case "--project": {
|
|
69
111
|
const value = rest[++i];
|
|
70
112
|
if (value === void 0) throw new Error("irtio rooms: --project needs a value");
|
|
@@ -94,6 +136,23 @@ function parseRoomsArgs(args) {
|
|
|
94
136
|
"irtio rooms restore: --save <id> is required \u2014 run `irtio rooms saves <room>` to see them"
|
|
95
137
|
);
|
|
96
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
|
+
}
|
|
97
156
|
return parsed;
|
|
98
157
|
}
|
|
99
158
|
function formatBytes(bytes) {
|
|
@@ -114,16 +173,60 @@ function formatAge(iso, now) {
|
|
|
114
173
|
function statusColor(status) {
|
|
115
174
|
return status === "active" || status === "running" ? pc.green(status) : status === "draining" ? pc.yellow(status) : pc.dim(status);
|
|
116
175
|
}
|
|
117
|
-
function formatTable(rows) {
|
|
176
|
+
function formatTable(rows, now) {
|
|
118
177
|
if (rows.length === 0) return [pc.dim("no rooms")];
|
|
119
178
|
const idWidth = Math.max(4, ...rows.map((r) => r.roomId.length));
|
|
120
|
-
const
|
|
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)];
|
|
121
182
|
for (const r of rows) {
|
|
122
183
|
const retention = r.retention ?? "forever";
|
|
184
|
+
const mark = r.retentionOverride ? "*" : "";
|
|
123
185
|
out.push(
|
|
124
|
-
`${r.roomId.padEnd(idWidth)} ${statusColor(r.status).padEnd(10)} ${retention
|
|
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)}` : "")
|
|
125
187
|
);
|
|
126
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)));
|
|
127
230
|
return out;
|
|
128
231
|
}
|
|
129
232
|
function formatSaves(rows, now) {
|
|
@@ -180,8 +283,16 @@ async function runDeleteRoom(options) {
|
|
|
180
283
|
log(
|
|
181
284
|
`${pc.yellow("deleting")} room ${pc.bold(options.room)}: its snapshot, its saves and its alarms`
|
|
182
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
|
+
}
|
|
183
293
|
const result = await client.del(
|
|
184
|
-
`/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}
|
|
294
|
+
`/v1/projects/${options.project}/rooms/${encodeURIComponent(options.room)}`,
|
|
295
|
+
options.force === true ? { force: "true" } : {}
|
|
185
296
|
);
|
|
186
297
|
log(pc.green(`deleted ${options.room}: ${result.objects} stored object(s) removed`));
|
|
187
298
|
log(pc.dim("player data and leaderboard scores are keyed to the player and are untouched"));
|
|
@@ -191,10 +302,40 @@ async function runRooms(options) {
|
|
|
191
302
|
const log = options.log ?? ((line) => console.log(line));
|
|
192
303
|
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
193
304
|
const client = options.client ?? await createApiClient(controlUrl);
|
|
194
|
-
const rows = await client.get(`/v1/projects/${options.project}/rooms
|
|
195
|
-
|
|
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);
|
|
196
310
|
return rows;
|
|
197
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
|
+
}
|
|
198
339
|
async function rooms(args, deps = {}) {
|
|
199
340
|
const log = deps.log ?? ((line) => console.log(line));
|
|
200
341
|
const errorLog = deps.errorLog ?? ((line) => console.error(line));
|
|
@@ -216,15 +357,31 @@ async function rooms(args, deps = {}) {
|
|
|
216
357
|
if (parsed.sub === "saves") {
|
|
217
358
|
await runSaves({ ...common, room: parsed.room });
|
|
218
359
|
} else if (parsed.sub === "delete") {
|
|
219
|
-
await runDeleteRoom({
|
|
360
|
+
await runDeleteRoom({
|
|
361
|
+
...common,
|
|
362
|
+
room: parsed.room,
|
|
363
|
+
...parsed.force === true ? { force: true } : {}
|
|
364
|
+
});
|
|
220
365
|
} else if (parsed.sub === "restore") {
|
|
221
366
|
await runRestore({
|
|
222
367
|
...common,
|
|
223
368
|
room: parsed.room,
|
|
224
369
|
saveId: parsed.save
|
|
225
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
|
+
});
|
|
226
379
|
} else {
|
|
227
|
-
await runRooms(
|
|
380
|
+
await runRooms({
|
|
381
|
+
...common,
|
|
382
|
+
...parsed.active !== void 0 ? { active: parsed.active } : {},
|
|
383
|
+
...parsed.idleLongerThan !== void 0 ? { idleLongerThan: parsed.idleLongerThan } : {}
|
|
384
|
+
});
|
|
228
385
|
}
|
|
229
386
|
} catch (err) {
|
|
230
387
|
if (err instanceof HelpRequested) {
|
|
@@ -246,7 +403,9 @@ export {
|
|
|
246
403
|
parseRoomsArgs,
|
|
247
404
|
rooms,
|
|
248
405
|
runDeleteRoom,
|
|
406
|
+
runGetRoom,
|
|
249
407
|
runRestore,
|
|
250
408
|
runRooms,
|
|
251
|
-
runSaves
|
|
409
|
+
runSaves,
|
|
410
|
+
runSetRoom
|
|
252
411
|
};
|