@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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readProjectConfig
|
|
3
|
+
} from "./chunk-DKWG7MGO.js";
|
|
4
|
+
import {
|
|
5
|
+
HelpRequested,
|
|
6
|
+
helpFor,
|
|
7
|
+
helpRequested
|
|
8
|
+
} from "./chunk-ZD4ND6X6.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient,
|
|
11
|
+
isLoginRequired
|
|
12
|
+
} from "./chunk-RNAH5T4W.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveControlUrlForUser
|
|
15
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
16
|
+
|
|
17
|
+
// src/leaderboard.ts
|
|
18
|
+
import pc from "picocolors";
|
|
19
|
+
var USAGE = `usage: irtio leaderboard <board> [options]
|
|
20
|
+
irtio leaderboard list [options]
|
|
21
|
+
irtio leaderboard delete <board> [--player <id>] --yes [options]
|
|
22
|
+
|
|
23
|
+
Prints a project's leaderboard. Reads are public: no login is needed, and this works against
|
|
24
|
+
any project's board, exactly as that project's own website would read it.
|
|
25
|
+
|
|
26
|
+
There is no submit command. Scores reach a board only from room code
|
|
27
|
+
(room.leaderboard.submit), which is what makes them server-authoritative.
|
|
28
|
+
|
|
29
|
+
list every board on the project, with its row count and direction. Needs your login,
|
|
30
|
+
because it is a view of a project rather than of one public ranking.
|
|
31
|
+
delete remove a whole board, or one player's row with --player. Needs your login and
|
|
32
|
+
--yes; without --yes it prints what would go and changes nothing.
|
|
33
|
+
|
|
34
|
+
options:
|
|
35
|
+
--limit <n> how many rows (default 10, max 100)
|
|
36
|
+
--cursor <c> continue from a previous page's nextCursor
|
|
37
|
+
--all page through the whole board, following cursors
|
|
38
|
+
--around <player> show the rows around one player instead of the top, with their rank
|
|
39
|
+
--player <id> with delete: remove one player's row instead of the whole board
|
|
40
|
+
--yes with delete: actually delete
|
|
41
|
+
--project <id> project id (default: the project file)
|
|
42
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
43
|
+
--url <control> control plane (default: your stored login)
|
|
44
|
+
-h, --help print this
|
|
45
|
+
`;
|
|
46
|
+
function parseLeaderboardArgs(args) {
|
|
47
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
48
|
+
const parsed = {};
|
|
49
|
+
if (args[0] === "list" || args[0] === "delete") {
|
|
50
|
+
parsed.action = args[0];
|
|
51
|
+
}
|
|
52
|
+
for (let i = parsed.action === void 0 ? 0 : 1; i < args.length; i++) {
|
|
53
|
+
const arg = args[i];
|
|
54
|
+
switch (arg) {
|
|
55
|
+
case "--cursor": {
|
|
56
|
+
const value = args[++i];
|
|
57
|
+
if (value === void 0 || value === "") {
|
|
58
|
+
throw new Error("irtio leaderboard: --cursor needs a value");
|
|
59
|
+
}
|
|
60
|
+
parsed.cursor = value;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "--all":
|
|
64
|
+
parsed.all = true;
|
|
65
|
+
break;
|
|
66
|
+
case "--yes":
|
|
67
|
+
parsed.yes = true;
|
|
68
|
+
break;
|
|
69
|
+
case "--player": {
|
|
70
|
+
const value = args[++i];
|
|
71
|
+
if (value === void 0 || value === "") {
|
|
72
|
+
throw new Error("irtio leaderboard: --player needs a player id");
|
|
73
|
+
}
|
|
74
|
+
parsed.player = value;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "--limit": {
|
|
78
|
+
const value = Number(args[++i]);
|
|
79
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
80
|
+
throw new Error("irtio leaderboard: --limit needs a whole number of at least 1");
|
|
81
|
+
}
|
|
82
|
+
parsed.limit = value;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "--around": {
|
|
86
|
+
const value = args[++i];
|
|
87
|
+
if (value === void 0 || value === "") {
|
|
88
|
+
throw new Error("irtio leaderboard: --around needs a player id");
|
|
89
|
+
}
|
|
90
|
+
parsed.around = value;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "--project": {
|
|
94
|
+
const value = args[++i];
|
|
95
|
+
if (value === void 0) throw new Error("irtio leaderboard: --project needs a value");
|
|
96
|
+
parsed.project = value;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "-c":
|
|
100
|
+
case "--config": {
|
|
101
|
+
const value = args[++i];
|
|
102
|
+
if (value === void 0 || value === "") {
|
|
103
|
+
throw new Error("irtio leaderboard: --config needs a value");
|
|
104
|
+
}
|
|
105
|
+
parsed.config = value;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "--url": {
|
|
109
|
+
const value = args[++i];
|
|
110
|
+
if (value === void 0) throw new Error("irtio leaderboard: --url needs a value");
|
|
111
|
+
parsed.url = value;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
default:
|
|
115
|
+
if (arg.startsWith("-")) {
|
|
116
|
+
throw new Error(`irtio leaderboard: unknown option ${JSON.stringify(arg)}`);
|
|
117
|
+
}
|
|
118
|
+
if (parsed.board !== void 0) {
|
|
119
|
+
throw new Error("irtio leaderboard: give exactly one board name");
|
|
120
|
+
}
|
|
121
|
+
parsed.board = arg;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return parsed;
|
|
125
|
+
}
|
|
126
|
+
function formatLeaderboard(body, around) {
|
|
127
|
+
const direction = body.direction === "lower" ? "lower is better" : "higher is better";
|
|
128
|
+
const out = [pc.dim(`board ${body.board} \xB7 ${direction}`), ""];
|
|
129
|
+
if (body.entries.length === 0) {
|
|
130
|
+
out.push(
|
|
131
|
+
pc.dim(
|
|
132
|
+
around !== void 0 ? `${around} has no score on this board` : "no scores on this board yet"
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
const rankWidth = Math.max(4, ...body.entries.map((e) => String(e.rank).length));
|
|
138
|
+
const idWidth = Math.max(6, ...body.entries.map((e) => e.playerId.length));
|
|
139
|
+
out.push(pc.dim(`${"RANK".padEnd(rankWidth)} ${"PLAYER".padEnd(idWidth)} SCORE`));
|
|
140
|
+
for (const entry of body.entries) {
|
|
141
|
+
const line = `${String(entry.rank).padEnd(rankWidth)} ${entry.playerId.padEnd(idWidth)} ${entry.score}`;
|
|
142
|
+
out.push(entry.playerId === around ? pc.bold(line) : line);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
async function runLeaderboard(options) {
|
|
147
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
148
|
+
const controlUrl = (await resolveControlUrlForUser(options.controlUrl)).replace(/\/+$/, "");
|
|
149
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
150
|
+
const base = `${controlUrl}/v1/leaderboard/${encodeURIComponent(
|
|
151
|
+
options.project
|
|
152
|
+
)}/${encodeURIComponent(options.board)}`;
|
|
153
|
+
if (options.around !== void 0) {
|
|
154
|
+
const body2 = await readPublicJson(
|
|
155
|
+
fetchImpl,
|
|
156
|
+
`${base}/around/${encodeURIComponent(options.around)}`
|
|
157
|
+
);
|
|
158
|
+
for (const line of formatLeaderboard(body2, options.around)) log(line);
|
|
159
|
+
return body2;
|
|
160
|
+
}
|
|
161
|
+
const pageUrl = (cursor) => {
|
|
162
|
+
const query = new URLSearchParams();
|
|
163
|
+
if (options.limit !== void 0) query.set("limit", String(options.limit));
|
|
164
|
+
if (cursor !== void 0) query.set("cursor", cursor);
|
|
165
|
+
const q = query.toString();
|
|
166
|
+
return `${base}/top${q === "" ? "" : `?${q}`}`;
|
|
167
|
+
};
|
|
168
|
+
let body = await readPublicJson(fetchImpl, pageUrl(options.cursor));
|
|
169
|
+
const entries = [...body.entries];
|
|
170
|
+
if (options.all === true) {
|
|
171
|
+
let next = body.nextCursor;
|
|
172
|
+
for (let page = 1; next !== void 0 && page < 100; page++) {
|
|
173
|
+
body = await readPublicJson(fetchImpl, pageUrl(next));
|
|
174
|
+
entries.push(...body.entries);
|
|
175
|
+
next = body.nextCursor;
|
|
176
|
+
}
|
|
177
|
+
if (next !== void 0) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"irtio leaderboard: --all stopped after a hundred pages. Read it in pieces with --cursor"
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const combined = { ...body, entries };
|
|
184
|
+
for (const line of formatLeaderboard(combined)) log(line);
|
|
185
|
+
if (options.all !== true && body.nextCursor !== void 0) {
|
|
186
|
+
log("");
|
|
187
|
+
log(pc.dim(`more rows: --cursor ${body.nextCursor}`));
|
|
188
|
+
}
|
|
189
|
+
return combined;
|
|
190
|
+
}
|
|
191
|
+
async function readPublicJson(fetchImpl, url) {
|
|
192
|
+
const res = await fetchImpl(url);
|
|
193
|
+
const text = await res.text().catch(() => "");
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
let message = `leaderboard read failed with status ${res.status}`;
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(text);
|
|
198
|
+
if (typeof parsed.message === "string") message = parsed.message;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
throw new Error(message);
|
|
202
|
+
}
|
|
203
|
+
return JSON.parse(text);
|
|
204
|
+
}
|
|
205
|
+
function formatBoardList(boards) {
|
|
206
|
+
if (boards.length === 0) {
|
|
207
|
+
return [
|
|
208
|
+
pc.dim("no boards on this project yet"),
|
|
209
|
+
pc.dim("scores reach a board from room code: room.leaderboard.submit")
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
const nameWidth = Math.max(5, ...boards.map((b) => b.board.length));
|
|
213
|
+
const out = [pc.dim(`${"BOARD".padEnd(nameWidth)} ${"ROWS".padStart(7)} DIRECTION`)];
|
|
214
|
+
for (const b of boards) {
|
|
215
|
+
const note = b.configured && b.rows === 0 ? pc.dim(" (configured, no rows yet)") : "";
|
|
216
|
+
out.push(`${b.board.padEnd(nameWidth)} ${String(b.rows).padStart(7)} ${b.direction}${note}`);
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
async function runBoardList(options) {
|
|
221
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
222
|
+
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
223
|
+
const client = options.client ?? await createApiClient(controlUrl);
|
|
224
|
+
const body = await client.get(
|
|
225
|
+
`/v1/projects/${encodeURIComponent(options.project)}/leaderboards`
|
|
226
|
+
);
|
|
227
|
+
for (const line of formatBoardList(body.boards)) log(line);
|
|
228
|
+
return body.boards;
|
|
229
|
+
}
|
|
230
|
+
async function runBoardDelete(options) {
|
|
231
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
232
|
+
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
233
|
+
const client = options.client ?? await createApiClient(controlUrl);
|
|
234
|
+
const path = `/v1/projects/${encodeURIComponent(options.project)}/leaderboard/${encodeURIComponent(
|
|
235
|
+
options.board
|
|
236
|
+
)}`;
|
|
237
|
+
if (!options.yes) {
|
|
238
|
+
if (options.player !== void 0) {
|
|
239
|
+
log(
|
|
240
|
+
`this would delete ${pc.bold(options.player)} from board ${pc.bold(options.board)} in project ${options.project}`
|
|
241
|
+
);
|
|
242
|
+
} else {
|
|
243
|
+
const { boards } = await client.get(
|
|
244
|
+
`/v1/projects/${encodeURIComponent(options.project)}/leaderboards`
|
|
245
|
+
);
|
|
246
|
+
const rows = boards.find((b) => b.board === options.board)?.rows ?? 0;
|
|
247
|
+
log(
|
|
248
|
+
`this would delete every row on board ${pc.bold(options.board)} in project ${options.project}: ${pc.bold(String(rows))} row(s)`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
log(pc.dim("nothing was deleted. This cannot be undone once it runs"));
|
|
252
|
+
log(
|
|
253
|
+
`to proceed: irtio leaderboard delete ${options.board}${options.player !== void 0 ? ` --player ${options.player}` : ""} --yes`
|
|
254
|
+
);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (options.player !== void 0) {
|
|
258
|
+
const body2 = await client.del(
|
|
259
|
+
`${path}/${encodeURIComponent(options.player)}`
|
|
260
|
+
);
|
|
261
|
+
log(
|
|
262
|
+
body2.deleted ? pc.green(`deleted ${options.player} from board ${options.board}`) : pc.dim(`${options.player} had no row on board ${options.board}; nothing to delete`)
|
|
263
|
+
);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const body = await client.del(path);
|
|
267
|
+
log(pc.green(`deleted ${body.deleted} row(s) from board ${options.board}`));
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
async function leaderboard(args, deps = {}) {
|
|
271
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
272
|
+
const errorLog = deps.errorLog ?? ((line) => console.error(line));
|
|
273
|
+
try {
|
|
274
|
+
const parsed = parseLeaderboardArgs(args);
|
|
275
|
+
const config = await readProjectConfig(process.cwd(), "irtio leaderboard", parsed.config);
|
|
276
|
+
const project = parsed.project ?? config.project;
|
|
277
|
+
if (project === void 0) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"irtio leaderboard: no project id. Pass --project or run this from a project with irtio.json"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const common = {
|
|
283
|
+
project,
|
|
284
|
+
log,
|
|
285
|
+
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
|
|
286
|
+
...deps.client !== void 0 ? { client: deps.client } : {}
|
|
287
|
+
};
|
|
288
|
+
if (parsed.action === "list") {
|
|
289
|
+
if (parsed.board !== void 0) {
|
|
290
|
+
throw new Error("irtio leaderboard list: takes no board name");
|
|
291
|
+
}
|
|
292
|
+
await runBoardList(common);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (parsed.action === "delete") {
|
|
296
|
+
if (parsed.board === void 0) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"irtio leaderboard delete: which board? e.g. irtio leaderboard delete scores --yes"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
await runBoardDelete({
|
|
302
|
+
...common,
|
|
303
|
+
board: parsed.board,
|
|
304
|
+
yes: parsed.yes === true,
|
|
305
|
+
...parsed.player !== void 0 ? { player: parsed.player } : {}
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (parsed.board === void 0) {
|
|
310
|
+
throw new Error("irtio leaderboard: which board? e.g. irtio leaderboard scores");
|
|
311
|
+
}
|
|
312
|
+
if (parsed.cursor !== void 0 && parsed.all === true) {
|
|
313
|
+
throw new Error("irtio leaderboard: --cursor and --all are two ways to do the same thing");
|
|
314
|
+
}
|
|
315
|
+
if (parsed.around !== void 0 && (parsed.cursor !== void 0 || parsed.all === true)) {
|
|
316
|
+
throw new Error("irtio leaderboard: --around does not page; it is already a window");
|
|
317
|
+
}
|
|
318
|
+
await runLeaderboard({
|
|
319
|
+
project,
|
|
320
|
+
board: parsed.board,
|
|
321
|
+
log,
|
|
322
|
+
...parsed.limit !== void 0 ? { limit: parsed.limit } : {},
|
|
323
|
+
...parsed.around !== void 0 ? { around: parsed.around } : {},
|
|
324
|
+
...parsed.cursor !== void 0 ? { cursor: parsed.cursor } : {},
|
|
325
|
+
...parsed.all === true ? { all: true } : {},
|
|
326
|
+
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {}
|
|
327
|
+
});
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (err instanceof HelpRequested) {
|
|
330
|
+
log(err.usage);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (isLoginRequired(err)) {
|
|
334
|
+
errorLog(pc.red("not logged in"));
|
|
335
|
+
errorLog("run: irtio login");
|
|
336
|
+
process.exitCode = 1;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
errorLog(pc.red(err instanceof Error ? err.message : String(err)));
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
export {
|
|
344
|
+
USAGE,
|
|
345
|
+
formatBoardList,
|
|
346
|
+
formatLeaderboard,
|
|
347
|
+
leaderboard,
|
|
348
|
+
parseLeaderboardArgs,
|
|
349
|
+
runBoardDelete,
|
|
350
|
+
runBoardList,
|
|
351
|
+
runLeaderboard
|
|
352
|
+
};
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HelpRequested,
|
|
3
|
+
helpFor,
|
|
4
|
+
helpRequested
|
|
5
|
+
} from "./chunk-ZD4ND6X6.js";
|
|
6
|
+
import "./chunk-RNAH5T4W.js";
|
|
1
7
|
import {
|
|
2
8
|
credentialsPath,
|
|
3
9
|
resolveControlUrl,
|
|
4
10
|
writeCredential
|
|
5
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
6
12
|
|
|
7
13
|
// src/login.ts
|
|
8
14
|
import { spawn } from "child_process";
|
|
@@ -10,7 +16,20 @@ import { randomBytes } from "crypto";
|
|
|
10
16
|
import { createServer } from "http";
|
|
11
17
|
import pc from "picocolors";
|
|
12
18
|
var LOGIN_TIMEOUT_MS = 3 * 60 * 1e3;
|
|
19
|
+
var USAGE = `usage: irtio login [--url <control>]
|
|
20
|
+
|
|
21
|
+
Signs in to a control plane through your browser and stores the credential it hands back.
|
|
22
|
+
|
|
23
|
+
options:
|
|
24
|
+
--url <control> the control plane to sign in to (default: the last one you used)
|
|
25
|
+
--token-file <file> store the credential in this file instead of the default location
|
|
26
|
+
(a global flag: pass it to later commands too, or set
|
|
27
|
+
IRT_CREDENTIALS_FILE). For agents and sandboxes that cannot
|
|
28
|
+
reach the home directory
|
|
29
|
+
-h, --help print this
|
|
30
|
+
`;
|
|
13
31
|
function parseLoginArgs(args) {
|
|
32
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
14
33
|
const parsed = {};
|
|
15
34
|
for (let i = 0; i < args.length; i++) {
|
|
16
35
|
const arg = args[i];
|
|
@@ -145,11 +164,16 @@ async function login(args) {
|
|
|
145
164
|
console.log(pc.dim(`control plane: ${result.controlUrl}`));
|
|
146
165
|
console.log(pc.dim(`credentials stored at ${result.file ?? credentialsPath()}`));
|
|
147
166
|
} catch (err) {
|
|
167
|
+
if (err instanceof HelpRequested) {
|
|
168
|
+
console.log(err.usage);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
148
171
|
console.error(pc.red(err instanceof Error ? err.message : String(err)));
|
|
149
172
|
process.exitCode = 1;
|
|
150
173
|
}
|
|
151
174
|
}
|
|
152
175
|
export {
|
|
176
|
+
USAGE,
|
|
153
177
|
defaultOpenBrowser,
|
|
154
178
|
login,
|
|
155
179
|
parseLoginArgs,
|
|
@@ -1,18 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readProjectConfig
|
|
3
|
+
} from "./chunk-DKWG7MGO.js";
|
|
4
|
+
import {
|
|
5
|
+
HelpRequested,
|
|
6
|
+
helpFor,
|
|
7
|
+
helpRequested
|
|
8
|
+
} from "./chunk-ZD4ND6X6.js";
|
|
1
9
|
import {
|
|
2
10
|
createApiClient,
|
|
3
11
|
isLoginRequired
|
|
4
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RNAH5T4W.js";
|
|
5
13
|
import {
|
|
6
14
|
resolveControlUrlForUser
|
|
7
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
8
16
|
|
|
9
17
|
// src/logs.ts
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
18
|
+
import "fs";
|
|
19
|
+
import "fs/promises";
|
|
20
|
+
import "path";
|
|
13
21
|
import pc from "picocolors";
|
|
14
22
|
var POLL_MS = 2e3;
|
|
23
|
+
var USAGE = `usage: irtio logs [options]
|
|
24
|
+
|
|
25
|
+
Tails a project's logs, oldest line first.
|
|
26
|
+
|
|
27
|
+
options:
|
|
28
|
+
--follow poll every 2s, threading the cursor so nothing repeats
|
|
29
|
+
--since <cursor> start after this cursor (a previous row's timestamp)
|
|
30
|
+
--room <id> narrow to one room's stream. __tenant is the tenant's own events
|
|
31
|
+
--project <id> project id (default: the project file)
|
|
32
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
33
|
+
--url <control> control plane (default: your stored login)
|
|
34
|
+
-h, --help print this
|
|
35
|
+
`;
|
|
15
36
|
function parseLogsArgs(args) {
|
|
37
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
16
38
|
const parsed = {
|
|
17
39
|
follow: false
|
|
18
40
|
};
|
|
@@ -40,6 +62,14 @@ function parseLogsArgs(args) {
|
|
|
40
62
|
parsed.project = value;
|
|
41
63
|
break;
|
|
42
64
|
}
|
|
65
|
+
case "-c":
|
|
66
|
+
case "--config": {
|
|
67
|
+
const value = args[++i];
|
|
68
|
+
if (value === void 0 || value === "")
|
|
69
|
+
throw new Error("irtio logs: --config needs a value");
|
|
70
|
+
parsed.config = value;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
43
73
|
case "--url": {
|
|
44
74
|
const value = args[++i];
|
|
45
75
|
if (value === void 0) throw new Error("irtio logs: --url needs a value");
|
|
@@ -81,23 +111,13 @@ async function runLogs(options) {
|
|
|
81
111
|
await new Promise((resolve) => setTimeout(resolve, options.pollMs ?? POLL_MS));
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
|
-
async function readIrtioJsonProject(cwd) {
|
|
85
|
-
const file = path.join(cwd, "irtio.json");
|
|
86
|
-
if (!existsSync(file)) return void 0;
|
|
87
|
-
let parsed;
|
|
88
|
-
try {
|
|
89
|
-
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
90
|
-
} catch {
|
|
91
|
-
throw new Error(`irtio logs: ${file} is not valid JSON`);
|
|
92
|
-
}
|
|
93
|
-
return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
|
|
94
|
-
}
|
|
95
114
|
async function logs(args, deps = {}) {
|
|
96
115
|
const log = deps.log ?? ((line) => console.log(line));
|
|
97
116
|
const errorLog = deps.errorLog ?? ((line) => console.error(line));
|
|
98
117
|
try {
|
|
99
118
|
const parsed = parseLogsArgs(args);
|
|
100
|
-
const
|
|
119
|
+
const config = await readProjectConfig(process.cwd(), "irtio logs", parsed.config);
|
|
120
|
+
const project = parsed.project ?? config.project;
|
|
101
121
|
if (project === void 0) {
|
|
102
122
|
throw new Error(
|
|
103
123
|
"irtio logs: no project id \u2014 pass --project or run this from a project with irtio.json"
|
|
@@ -113,6 +133,10 @@ async function logs(args, deps = {}) {
|
|
|
113
133
|
...deps.client !== void 0 ? { client: deps.client } : {}
|
|
114
134
|
});
|
|
115
135
|
} catch (err) {
|
|
136
|
+
if (err instanceof HelpRequested) {
|
|
137
|
+
log(err.usage);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
116
140
|
if (isLoginRequired(err)) {
|
|
117
141
|
errorLog(pc.red("not logged in"));
|
|
118
142
|
errorLog("run: irtio login");
|
|
@@ -124,6 +148,7 @@ async function logs(args, deps = {}) {
|
|
|
124
148
|
}
|
|
125
149
|
}
|
|
126
150
|
export {
|
|
151
|
+
USAGE,
|
|
127
152
|
logs,
|
|
128
153
|
parseLogsArgs,
|
|
129
154
|
runLogs
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readProjectConfig
|
|
3
|
+
} from "./chunk-DKWG7MGO.js";
|
|
1
4
|
import {
|
|
2
5
|
bundleRoom
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OTSFRVJN.js";
|
|
7
|
+
import {
|
|
8
|
+
HelpRequested,
|
|
9
|
+
helpFor,
|
|
10
|
+
helpRequested
|
|
11
|
+
} from "./chunk-ZD4ND6X6.js";
|
|
4
12
|
import {
|
|
5
13
|
createApiClient,
|
|
6
14
|
isLoginRequired
|
|
7
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-RNAH5T4W.js";
|
|
8
16
|
import {
|
|
9
17
|
resolveControlUrlForUser
|
|
10
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
11
19
|
|
|
12
20
|
// src/migrate.ts
|
|
13
21
|
import { existsSync } from "fs";
|
|
14
|
-
import { mkdir, mkdtemp,
|
|
22
|
+
import { mkdir, mkdtemp, readdir, writeFile } from "fs/promises";
|
|
15
23
|
import { tmpdir } from "os";
|
|
16
24
|
import * as path from "path";
|
|
17
25
|
import { pathToFileURL } from "url";
|
|
@@ -23,7 +31,18 @@ import {
|
|
|
23
31
|
import pc from "picocolors";
|
|
24
32
|
var MIGRATIONS_DIR = "irtio/migrations";
|
|
25
33
|
var ROOM_CANDIDATES = ["irtio/room.ts", "irtio/room.js", "room.ts"];
|
|
34
|
+
var USAGE = `usage: irtio migrate create <name> [-c <file>]
|
|
35
|
+
|
|
36
|
+
Scaffolds irtio/migrations/<n>_<name>.ts, with the breaking changes against the live deployment
|
|
37
|
+
quoted in its header so the transform has something to answer.
|
|
38
|
+
|
|
39
|
+
options:
|
|
40
|
+
<name> a short name for the migration, e.g. rename-hp
|
|
41
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
42
|
+
-h, --help print this
|
|
43
|
+
`;
|
|
26
44
|
function parseMigrateArgs(args) {
|
|
45
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
27
46
|
const [sub, name, ...rest] = args;
|
|
28
47
|
if (sub !== "create") {
|
|
29
48
|
throw new Error(
|
|
@@ -35,9 +54,20 @@ function parseMigrateArgs(args) {
|
|
|
35
54
|
"irtio migrate create: a name is required, e.g. irtio migrate create rename-hp"
|
|
36
55
|
);
|
|
37
56
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
let config;
|
|
58
|
+
for (let i = 0; i < rest.length; i++) {
|
|
59
|
+
const arg = rest[i];
|
|
60
|
+
if (arg === "-c" || arg === "--config") {
|
|
61
|
+
const value = rest[++i];
|
|
62
|
+
if (value === void 0 || value === "") {
|
|
63
|
+
throw new Error("irtio migrate create: --config needs a value");
|
|
64
|
+
}
|
|
65
|
+
config = value;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`irtio migrate create: unexpected extra argument ${JSON.stringify(arg)}`);
|
|
69
|
+
}
|
|
70
|
+
return { command: "create", args: config === void 0 ? { name } : { name, config } };
|
|
41
71
|
}
|
|
42
72
|
function resolveEntry(cwd, room) {
|
|
43
73
|
if (room !== void 0) {
|
|
@@ -53,17 +83,6 @@ function resolveEntry(cwd, room) {
|
|
|
53
83
|
`irtio migrate create: no room file found in ${cwd} (looked for: ${ROOM_CANDIDATES.join(", ")})`
|
|
54
84
|
);
|
|
55
85
|
}
|
|
56
|
-
async function readIrtioJsonProject(cwd) {
|
|
57
|
-
const file = path.join(cwd, "irtio.json");
|
|
58
|
-
if (!existsSync(file)) return void 0;
|
|
59
|
-
let parsed;
|
|
60
|
-
try {
|
|
61
|
-
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
62
|
-
} catch {
|
|
63
|
-
throw new Error(`irtio migrate: ${file} is not valid JSON`);
|
|
64
|
-
}
|
|
65
|
-
return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
|
|
66
|
-
}
|
|
67
86
|
async function scanDirectoryVersion(cwd) {
|
|
68
87
|
const dir = path.join(cwd, MIGRATIONS_DIR);
|
|
69
88
|
if (!existsSync(dir)) return 1;
|
|
@@ -120,7 +139,8 @@ async function runMigrateCreate(options) {
|
|
|
120
139
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
121
140
|
const log = options.log ?? ((line) => console.log(line));
|
|
122
141
|
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
123
|
-
const
|
|
142
|
+
const config = await readProjectConfig(cwd, "irtio migrate", options.config);
|
|
143
|
+
const projectId = options.project ?? config.project;
|
|
124
144
|
let version;
|
|
125
145
|
let versionSource;
|
|
126
146
|
let previousSchemaJson;
|
|
@@ -196,6 +216,10 @@ async function migrate(args, deps = {}) {
|
|
|
196
216
|
...deps.client !== void 0 ? { client: deps.client } : {}
|
|
197
217
|
});
|
|
198
218
|
} catch (err) {
|
|
219
|
+
if (err instanceof HelpRequested) {
|
|
220
|
+
log(err.usage);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
199
223
|
if (isLoginRequired(err)) {
|
|
200
224
|
errorLog(pc.red("not logged in"));
|
|
201
225
|
errorLog("run: irtio login");
|
|
@@ -207,6 +231,7 @@ async function migrate(args, deps = {}) {
|
|
|
207
231
|
}
|
|
208
232
|
}
|
|
209
233
|
export {
|
|
234
|
+
USAGE,
|
|
210
235
|
migrate,
|
|
211
236
|
parseMigrateArgs,
|
|
212
237
|
runMigrateCreate
|