@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.
Files changed (38) hide show
  1. package/dist/api.d.ts +52 -0
  2. package/dist/api.js +15 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/{static-deploy-BP3MDXCP.js → chunk-3HQMVCYA.js} +89 -48
  5. package/dist/chunk-DKWG7MGO.js +93 -0
  6. package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
  7. package/dist/{chunk-I37DLT7K.js → chunk-RNAH5T4W.js} +14 -3
  8. package/dist/chunk-RQSJZWQC.js +452 -0
  9. package/dist/{chunk-NVUKSP5U.js → chunk-UPHQM6NZ.js} +12 -1
  10. package/dist/chunk-ZD4ND6X6.js +31 -0
  11. package/dist/chunk-ZK5JLUD4.js +94 -0
  12. package/dist/credentials.d.ts +61 -0
  13. package/dist/credentials.js +20 -0
  14. package/dist/delete-project-VENS2B44.js +118 -0
  15. package/dist/deploy.d.ts +149 -0
  16. package/dist/deploy.js +567 -0
  17. package/dist/{dev-7UZZGE4U.js → dev-QM26ONKS.js} +3095 -294
  18. package/dist/index.js +113 -28
  19. package/dist/init.d.ts +2 -1
  20. package/dist/init.js +42 -1
  21. package/dist/{keys-KEKO3EJ6.js → keys-JHLMEGRA.js} +49 -18
  22. package/dist/leaderboard-SYPSBPS3.js +352 -0
  23. package/dist/{login-OV2EFNTJ.js → login-2M73HBZT.js} +25 -1
  24. package/dist/{logs-5OUDPAIX.js → logs-2W7CPZO5.js} +42 -17
  25. package/dist/{migrate-UD245ULI.js → migrate-T3DZJREY.js} +44 -19
  26. package/dist/ratings-VG32WFDG.js +297 -0
  27. package/dist/{rollback-CLQVYFHW.js → rollback-SO74MVZV.js} +41 -17
  28. package/dist/{rooms-OJ3JLYHD.js → rooms-VI33P4RA.js} +73 -20
  29. package/dist/simulate.d.ts +215 -3
  30. package/dist/simulate.js +865 -64
  31. package/dist/static-deploy-KOWFKWZA.js +19 -0
  32. package/dist/status-HF3ZEKB7.js +219 -0
  33. package/dist/usage-4G23QXCH.js +213 -0
  34. package/dist/{whoami-S73O6KJF.js → whoami-KTMTQNHM.js} +21 -2
  35. package/package.json +24 -7
  36. package/dist/chunk-BPE452KF.js +0 -180
  37. package/dist/chunk-D7CDJRFF.js +0 -24
  38. 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-NVUKSP5U.js";
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-I37DLT7K.js";
12
+ } from "./chunk-RNAH5T4W.js";
5
13
  import {
6
14
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
15
+ } from "./chunk-UPHQM6NZ.js";
8
16
 
9
17
  // src/logs.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import * as path from "path";
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 project = parsed.project ?? await readIrtioJsonProject(process.cwd());
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-KRQUAEN2.js";
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-I37DLT7K.js";
15
+ } from "./chunk-RNAH5T4W.js";
8
16
  import {
9
17
  resolveControlUrlForUser
10
- } from "./chunk-NVUKSP5U.js";
18
+ } from "./chunk-UPHQM6NZ.js";
11
19
 
12
20
  // src/migrate.ts
13
21
  import { existsSync } from "fs";
14
- import { mkdir, mkdtemp, readFile, readdir, writeFile } from "fs/promises";
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
- if (rest.length > 0)
39
- throw new Error(`irtio migrate create: unexpected extra argument ${JSON.stringify(rest[0])}`);
40
- return { command: "create", args: { name } };
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 projectId = options.project ?? await readIrtioJsonProject(cwd);
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