@irtio/cli 0.5.1 → 0.5.2

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/index.js CHANGED
@@ -20,7 +20,9 @@ commands:
20
20
  [--misprediction-max <units>] fail the run if one correction snaps a
21
21
  [--snaps-max <n>] prediction further than this, if more than
22
22
  [--corrections-max <perSec>] <n> corrections fall outside the resim
23
- window, or above this correction rate/bot
23
+ [--overruns-max <n>] window, above this correction rate/bot, or
24
+ above this many server tick overruns.
25
+ exits 0 clean, 1 violation, 2 not performed
24
26
 
25
27
  login [--url <control>] sign in to a control plane via the browser
26
28
  whoami [--url <control>] print the identity the stored credential
@@ -28,13 +30,17 @@ commands:
28
30
  deploy [--allow-breaking] [--project <id>] bundle, classify the schema change against the
29
31
  [--url <control>] [--room <file>] last deployment, and upload \u2014 refuses breaking
30
32
  [--strategy drain|migrate] changes unless a migration covers them;
31
- [--name <name>] migrate moves LIVE rooms onto the new version
33
+ [--name <name>] [--no-static] migrate moves LIVE rooms onto the new version
32
34
  now (drain, the default, lets them finish).
33
35
  A project that does not exist yet is created
34
- as --name, else irtio.json's "name"
35
- deploy --static <dir> [--project <id>] upload a BUILT client directory to the project's
36
- [--label <label>] [--url <control>] static site and print the playable link
37
- [--name <name>] (<label>.irt-serve.com; no build step)
36
+ as --name, else irtio.json's "name".
37
+ With "static" in the project file the client
38
+ directory ships too; --no-static skips it
39
+ deploy --static [<dir>] [--project <id>] upload a BUILT client directory to the project's
40
+ [--label <label>] [--url <control>] static site and print the playable link, and
41
+ [--name <name>] nothing else (<label>.irt-serve.com; no build
42
+ step). Directory and label default to the
43
+ project file's "static"
38
44
  migrate create <name> scaffold irtio/migrations/<n>_<name>.ts with the
39
45
  live breaking changes quoted in its header
40
46
  rollback <version> [--project <id>] re-promote deployment <version> and bring rooms
@@ -54,6 +60,13 @@ commands:
54
60
  rooms saves <room> list a room's save generations, newest first
55
61
  rooms restore <room> --save <id> bring a room back from a save \u2014 DISCARDS its
56
62
  current state; stops the tenant if it is live
63
+
64
+ every command takes --help (or -h) and prints its own usage, which is the authority: the lines
65
+ above are a map, and the command's own help is generated next to the code that parses it.
66
+
67
+ every command that reads the project file takes -c/--config <file> to read a different one
68
+ (dev, deploy, deploy --static, migrate create, rollback, logs, rooms, keys). Default irtio.json;
69
+ the named file must exist. That is the test.irtio.json / prod.irtio.json pattern.
57
70
  `;
58
71
  switch (command) {
59
72
  case "init": {
@@ -62,7 +75,7 @@ switch (command) {
62
75
  break;
63
76
  }
64
77
  case "dev": {
65
- const { dev } = await import("./dev-7UZZGE4U.js");
78
+ const { dev } = await import("./dev-QJOGXLKM.js");
66
79
  await dev(args);
67
80
  break;
68
81
  }
@@ -72,47 +85,47 @@ switch (command) {
72
85
  break;
73
86
  }
74
87
  case "login": {
75
- const { login } = await import("./login-OV2EFNTJ.js");
88
+ const { login } = await import("./login-3EXB4CGX.js");
76
89
  await login(args);
77
90
  break;
78
91
  }
79
92
  case "whoami": {
80
- const { whoami } = await import("./whoami-S73O6KJF.js");
93
+ const { whoami } = await import("./whoami-CI5D5RCC.js");
81
94
  await whoami(args);
82
95
  break;
83
96
  }
84
97
  case "deploy": {
85
98
  if (args.includes("--static")) {
86
- const { staticDeploy } = await import("./static-deploy-BP3MDXCP.js");
99
+ const { staticDeploy } = await import("./static-deploy-5TBH4VNA.js");
87
100
  await staticDeploy(args);
88
101
  break;
89
102
  }
90
- const { deploy } = await import("./deploy-YVCVDVMS.js");
103
+ const { deploy } = await import("./deploy-3SABPL3T.js");
91
104
  await deploy(args);
92
105
  break;
93
106
  }
94
107
  case "migrate": {
95
- const { migrate } = await import("./migrate-UD245ULI.js");
108
+ const { migrate } = await import("./migrate-WUO2GBMX.js");
96
109
  await migrate(args);
97
110
  break;
98
111
  }
99
112
  case "rollback": {
100
- const { rollback } = await import("./rollback-CLQVYFHW.js");
113
+ const { rollback } = await import("./rollback-GA6UY772.js");
101
114
  await rollback(args);
102
115
  break;
103
116
  }
104
117
  case "keys": {
105
- const { keys } = await import("./keys-KEKO3EJ6.js");
118
+ const { keys } = await import("./keys-XBORZAPI.js");
106
119
  await keys(args);
107
120
  break;
108
121
  }
109
122
  case "logs": {
110
- const { logs } = await import("./logs-5OUDPAIX.js");
123
+ const { logs } = await import("./logs-2EOXLNWF.js");
111
124
  await logs(args);
112
125
  break;
113
126
  }
114
127
  case "rooms": {
115
- const { rooms } = await import("./rooms-OJ3JLYHD.js");
128
+ const { rooms } = await import("./rooms-B66LQIIF.js");
116
129
  await rooms(args);
117
130
  break;
118
131
  }
package/dist/init.d.ts CHANGED
@@ -59,6 +59,7 @@ interface InitResult {
59
59
  * `irtio init [dir] [--tick|--event]`. Hand-rolled like `parseDevArgs`: the CLI has no
60
60
  * argument-parsing dependency and four flags do not earn one.
61
61
  */
62
+ declare const USAGE = "usage: irtio init [dir] [options]\n\nScaffolds irtio/schema.ts, irtio/rpc.ts, irtio/room.ts, irtio/room.test.ts and irtio.json.\nNothing that already exists is ever overwritten.\n\noptions:\n [dir] where to scaffold (default: here)\n --tick a tick room, without asking\n --event an event room, without asking\n --physics scaffold a physics room and its shared world builder\n --name <name> the project name a first deploy registers (default: the directory)\n -h, --help print this\n";
62
63
  declare function parseInitArgs(args: readonly string[]): InitArgs;
63
64
  /** `p_` + 16 hex characters: the project id *is* the public key, so it is not a secret. */
64
65
  declare function generateProjectId(): string;
@@ -70,4 +71,4 @@ declare function runInit(options?: RunInitOptions): Promise<InitResult>;
70
71
  /** Thin on purpose: parse, run, and turn a thrown message into an exit code. */
71
72
  declare function init(args: readonly string[]): Promise<void>;
72
73
 
73
- export { INIT_DEPENDENCIES, type InitArgs, type InitResult, type RoomMode, type RunInitOptions, generateProjectId, init, parseInitArgs, runInit };
74
+ export { INIT_DEPENDENCIES, type InitArgs, type InitResult, type RoomMode, type RunInitOptions, USAGE, generateProjectId, init, parseInitArgs, runInit };
package/dist/init.js CHANGED
@@ -1,3 +1,9 @@
1
+ import {
2
+ HelpRequested,
3
+ helpFor,
4
+ helpRequested
5
+ } from "./chunk-TV66QHFP.js";
6
+
1
7
  // src/init.ts
2
8
  import { randomBytes } from "crypto";
3
9
  import { existsSync } from "fs";
@@ -13,7 +19,21 @@ var INIT_DEPENDENCIES = [
13
19
  ];
14
20
  var DEFAULT_TICK_RATE = 20;
15
21
  var DEFAULT_IDLE_MS = 3e4;
22
+ var USAGE = `usage: irtio init [dir] [options]
23
+
24
+ Scaffolds irtio/schema.ts, irtio/rpc.ts, irtio/room.ts, irtio/room.test.ts and irtio.json.
25
+ Nothing that already exists is ever overwritten.
26
+
27
+ options:
28
+ [dir] where to scaffold (default: here)
29
+ --tick a tick room, without asking
30
+ --event an event room, without asking
31
+ --physics scaffold a physics room and its shared world builder
32
+ --name <name> the project name a first deploy registers (default: the directory)
33
+ -h, --help print this
34
+ `;
16
35
  function parseInitArgs(args) {
36
+ if (helpRequested(args)) throw helpFor(USAGE);
17
37
  const parsed = {};
18
38
  for (let i = 0; i < args.length; i++) {
19
39
  const arg = args[i];
@@ -534,12 +554,17 @@ async function init(args) {
534
554
  ...parsed.name !== void 0 ? { name: parsed.name } : {}
535
555
  });
536
556
  } catch (err) {
557
+ if (err instanceof HelpRequested) {
558
+ console.log(err.usage);
559
+ return;
560
+ }
537
561
  console.error(pc.red(err instanceof Error ? err.message : String(err)));
538
562
  process.exitCode = 1;
539
563
  }
540
564
  }
541
565
  export {
542
566
  INIT_DEPENDENCIES,
567
+ USAGE,
543
568
  generateProjectId,
544
569
  init,
545
570
  parseInitArgs,
@@ -1,14 +1,16 @@
1
1
  import {
2
- createApiClient,
3
- isLoginRequired
4
- } from "./chunk-I37DLT7K.js";
2
+ readProjectConfig
3
+ } from "./chunk-32QTPKVT.js";
5
4
  import {
5
+ createApiClient,
6
+ helpRequested,
7
+ isLoginRequired,
6
8
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
9
+ } from "./chunk-TV66QHFP.js";
8
10
 
9
11
  // src/keys.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
+ import "fs";
13
+ import "fs/promises";
12
14
  import * as path from "path";
13
15
  import pc from "picocolors";
14
16
 
@@ -22,17 +24,6 @@ function signJwt(secret, claims) {
22
24
  }
23
25
 
24
26
  // src/keys.ts
25
- async function readIrtioJsonProject(cwd) {
26
- const file = path.join(cwd, "irtio.json");
27
- if (!existsSync(file)) return void 0;
28
- let parsed;
29
- try {
30
- parsed = JSON.parse(await readFile(file, "utf8"));
31
- } catch {
32
- throw new Error(`irtio keys: ${file} is not valid JSON`);
33
- }
34
- return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
35
- }
36
27
  function flagValue(args, flag) {
37
28
  const i = args.indexOf(flag);
38
29
  if (i === -1) return void 0;
@@ -40,11 +31,15 @@ function flagValue(args, flag) {
40
31
  if (value === void 0) throw new Error(`irtio keys: ${flag} needs a value`);
41
32
  return value;
42
33
  }
34
+ function configFlag(args) {
35
+ return flagValue(args, "--config") ?? flagValue(args, "-c");
36
+ }
43
37
  async function resolveProject(args) {
44
- const project = flagValue(args, "--project") ?? await readIrtioJsonProject(process.cwd());
38
+ const config = await readProjectConfig(process.cwd(), "irtio keys", configFlag(args));
39
+ const project = flagValue(args, "--project") ?? config.project;
45
40
  if (project === void 0) {
46
41
  throw new Error(
47
- "irtio keys: no project id \u2014 pass --project or run this from a project with irtio.json"
42
+ `irtio keys: no project id \u2014 pass --project or run this from a project with ${path.basename(config.file)}`
48
43
  );
49
44
  }
50
45
  return project;
@@ -88,9 +83,40 @@ async function runJwtSecretRetire(options) {
88
83
  log(pc.green(`retired the oldest active secret; ${result.active} remain(s) active`));
89
84
  log(pc.dim("tokens signed with the retired secret stop verifying at the next placement"));
90
85
  }
86
+ var USAGE = `usage: irtio keys jwt-secret [retire] [options]
87
+ irtio keys jwt-mint --secret <s> --sub <playerId> [options]
88
+
89
+ jwt-secret mints a signing secret for the project. It is printed ONCE and never again. Running it
90
+ a second time starts a rotation, where both secrets verify; retire finishes one by dropping the
91
+ oldest.
92
+
93
+ jwt-mint signs a test token locally, with the documented recipe. The secret you pass never leaves
94
+ this machine.
95
+
96
+ jwt-secret options:
97
+ retire retire the oldest active secret for this issuer
98
+ --issuer <label> the issuer to mint or retire under
99
+ --project <id> project id (default: the project file)
100
+ -c, --config <f> the project file to read (default irtio.json)
101
+ --url <control> control plane (default: your stored login)
102
+
103
+ jwt-mint options:
104
+ --secret <s> the signing secret (required)
105
+ --sub <playerId> the player the token is for (required)
106
+ --iss <label> the issuer claim
107
+ --room <id> restrict the token to one room
108
+ --role <r> the role to join as
109
+ --ttl <sec> lifetime in seconds (default 3600)
110
+
111
+ -h, --help print this
112
+ `;
91
113
  async function keys(args, deps = {}) {
92
114
  const log = deps.log ?? ((line) => console.log(line));
93
115
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
116
+ if (helpRequested(args)) {
117
+ log(USAGE);
118
+ return;
119
+ }
94
120
  try {
95
121
  const sub = args[0];
96
122
  const rest = args.slice(1);
@@ -133,7 +159,7 @@ async function keys(args, deps = {}) {
133
159
  const room = flagValue(rest, "--room");
134
160
  const role = flagValue(rest, "--role");
135
161
  const iss = flagValue(rest, "--iss") ?? DEFAULT_ISSUER;
136
- const project = flagValue(rest, "--project") ?? await readIrtioJsonProject(process.cwd());
162
+ const project = flagValue(rest, "--project") ?? (await readProjectConfig(process.cwd(), "irtio keys", configFlag(rest))).project;
137
163
  if (project === void 0) {
138
164
  throw new Error(
139
165
  "irtio keys jwt-mint: --project is required (aud must name the project the token is for)"
@@ -168,6 +194,7 @@ async function keys(args, deps = {}) {
168
194
  }
169
195
  export {
170
196
  DEFAULT_ISSUER,
197
+ USAGE,
171
198
  keys,
172
199
  runJwtSecret,
173
200
  runJwtSecretRetire
@@ -1,8 +1,11 @@
1
1
  import {
2
+ HelpRequested,
2
3
  credentialsPath,
4
+ helpFor,
5
+ helpRequested,
3
6
  resolveControlUrl,
4
7
  writeCredential
5
- } from "./chunk-NVUKSP5U.js";
8
+ } from "./chunk-TV66QHFP.js";
6
9
 
7
10
  // src/login.ts
8
11
  import { spawn } from "child_process";
@@ -10,7 +13,16 @@ import { randomBytes } from "crypto";
10
13
  import { createServer } from "http";
11
14
  import pc from "picocolors";
12
15
  var LOGIN_TIMEOUT_MS = 3 * 60 * 1e3;
16
+ var USAGE = `usage: irtio login [--url <control>]
17
+
18
+ Signs in to a control plane through your browser and stores the credential it hands back.
19
+
20
+ options:
21
+ --url <control> the control plane to sign in to (default: the last one you used)
22
+ -h, --help print this
23
+ `;
13
24
  function parseLoginArgs(args) {
25
+ if (helpRequested(args)) throw helpFor(USAGE);
14
26
  const parsed = {};
15
27
  for (let i = 0; i < args.length; i++) {
16
28
  const arg = args[i];
@@ -145,11 +157,16 @@ async function login(args) {
145
157
  console.log(pc.dim(`control plane: ${result.controlUrl}`));
146
158
  console.log(pc.dim(`credentials stored at ${result.file ?? credentialsPath()}`));
147
159
  } catch (err) {
160
+ if (err instanceof HelpRequested) {
161
+ console.log(err.usage);
162
+ return;
163
+ }
148
164
  console.error(pc.red(err instanceof Error ? err.message : String(err)));
149
165
  process.exitCode = 1;
150
166
  }
151
167
  }
152
168
  export {
169
+ USAGE,
153
170
  defaultOpenBrowser,
154
171
  login,
155
172
  parseLoginArgs,
@@ -1,18 +1,36 @@
1
1
  import {
2
- createApiClient,
3
- isLoginRequired
4
- } from "./chunk-I37DLT7K.js";
2
+ readProjectConfig
3
+ } from "./chunk-32QTPKVT.js";
5
4
  import {
5
+ HelpRequested,
6
+ createApiClient,
7
+ helpFor,
8
+ helpRequested,
9
+ isLoginRequired,
6
10
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
11
+ } from "./chunk-TV66QHFP.js";
8
12
 
9
13
  // src/logs.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import * as path from "path";
14
+ import "fs";
15
+ import "fs/promises";
16
+ import "path";
13
17
  import pc from "picocolors";
14
18
  var POLL_MS = 2e3;
19
+ var USAGE = `usage: irtio logs [options]
20
+
21
+ Tails a project's logs, oldest line first.
22
+
23
+ options:
24
+ --follow poll every 2s, threading the cursor so nothing repeats
25
+ --since <cursor> start after this cursor (a previous row's timestamp)
26
+ --room <id> narrow to one room's stream. __tenant is the tenant's own events
27
+ --project <id> project id (default: the project file)
28
+ -c, --config <f> the project file to read (default irtio.json)
29
+ --url <control> control plane (default: your stored login)
30
+ -h, --help print this
31
+ `;
15
32
  function parseLogsArgs(args) {
33
+ if (helpRequested(args)) throw helpFor(USAGE);
16
34
  const parsed = {
17
35
  follow: false
18
36
  };
@@ -40,6 +58,14 @@ function parseLogsArgs(args) {
40
58
  parsed.project = value;
41
59
  break;
42
60
  }
61
+ case "-c":
62
+ case "--config": {
63
+ const value = args[++i];
64
+ if (value === void 0 || value === "")
65
+ throw new Error("irtio logs: --config needs a value");
66
+ parsed.config = value;
67
+ break;
68
+ }
43
69
  case "--url": {
44
70
  const value = args[++i];
45
71
  if (value === void 0) throw new Error("irtio logs: --url needs a value");
@@ -81,23 +107,13 @@ async function runLogs(options) {
81
107
  await new Promise((resolve) => setTimeout(resolve, options.pollMs ?? POLL_MS));
82
108
  }
83
109
  }
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
110
  async function logs(args, deps = {}) {
96
111
  const log = deps.log ?? ((line) => console.log(line));
97
112
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
98
113
  try {
99
114
  const parsed = parseLogsArgs(args);
100
- const project = parsed.project ?? await readIrtioJsonProject(process.cwd());
115
+ const config = await readProjectConfig(process.cwd(), "irtio logs", parsed.config);
116
+ const project = parsed.project ?? config.project;
101
117
  if (project === void 0) {
102
118
  throw new Error(
103
119
  "irtio logs: no project id \u2014 pass --project or run this from a project with irtio.json"
@@ -113,6 +129,10 @@ async function logs(args, deps = {}) {
113
129
  ...deps.client !== void 0 ? { client: deps.client } : {}
114
130
  });
115
131
  } catch (err) {
132
+ if (err instanceof HelpRequested) {
133
+ log(err.usage);
134
+ return;
135
+ }
116
136
  if (isLoginRequired(err)) {
117
137
  errorLog(pc.red("not logged in"));
118
138
  errorLog("run: irtio login");
@@ -124,6 +144,7 @@ async function logs(args, deps = {}) {
124
144
  }
125
145
  }
126
146
  export {
147
+ USAGE,
127
148
  logs,
128
149
  parseLogsArgs,
129
150
  runLogs
@@ -2,16 +2,20 @@ import {
2
2
  bundleRoom
3
3
  } from "./chunk-KRQUAEN2.js";
4
4
  import {
5
- createApiClient,
6
- isLoginRequired
7
- } from "./chunk-I37DLT7K.js";
5
+ readProjectConfig
6
+ } from "./chunk-32QTPKVT.js";
8
7
  import {
8
+ HelpRequested,
9
+ createApiClient,
10
+ helpFor,
11
+ helpRequested,
12
+ isLoginRequired,
9
13
  resolveControlUrlForUser
10
- } from "./chunk-NVUKSP5U.js";
14
+ } from "./chunk-TV66QHFP.js";
11
15
 
12
16
  // src/migrate.ts
13
17
  import { existsSync } from "fs";
14
- import { mkdir, mkdtemp, readFile, readdir, writeFile } from "fs/promises";
18
+ import { mkdir, mkdtemp, readdir, writeFile } from "fs/promises";
15
19
  import { tmpdir } from "os";
16
20
  import * as path from "path";
17
21
  import { pathToFileURL } from "url";
@@ -23,7 +27,18 @@ import {
23
27
  import pc from "picocolors";
24
28
  var MIGRATIONS_DIR = "irtio/migrations";
25
29
  var ROOM_CANDIDATES = ["irtio/room.ts", "irtio/room.js", "room.ts"];
30
+ var USAGE = `usage: irtio migrate create <name> [-c <file>]
31
+
32
+ Scaffolds irtio/migrations/<n>_<name>.ts, with the breaking changes against the live deployment
33
+ quoted in its header so the transform has something to answer.
34
+
35
+ options:
36
+ <name> a short name for the migration, e.g. rename-hp
37
+ -c, --config <f> the project file to read (default irtio.json)
38
+ -h, --help print this
39
+ `;
26
40
  function parseMigrateArgs(args) {
41
+ if (helpRequested(args)) throw helpFor(USAGE);
27
42
  const [sub, name, ...rest] = args;
28
43
  if (sub !== "create") {
29
44
  throw new Error(
@@ -35,9 +50,20 @@ function parseMigrateArgs(args) {
35
50
  "irtio migrate create: a name is required, e.g. irtio migrate create rename-hp"
36
51
  );
37
52
  }
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 } };
53
+ let config;
54
+ for (let i = 0; i < rest.length; i++) {
55
+ const arg = rest[i];
56
+ if (arg === "-c" || arg === "--config") {
57
+ const value = rest[++i];
58
+ if (value === void 0 || value === "") {
59
+ throw new Error("irtio migrate create: --config needs a value");
60
+ }
61
+ config = value;
62
+ continue;
63
+ }
64
+ throw new Error(`irtio migrate create: unexpected extra argument ${JSON.stringify(arg)}`);
65
+ }
66
+ return { command: "create", args: config === void 0 ? { name } : { name, config } };
41
67
  }
42
68
  function resolveEntry(cwd, room) {
43
69
  if (room !== void 0) {
@@ -53,17 +79,6 @@ function resolveEntry(cwd, room) {
53
79
  `irtio migrate create: no room file found in ${cwd} (looked for: ${ROOM_CANDIDATES.join(", ")})`
54
80
  );
55
81
  }
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
82
  async function scanDirectoryVersion(cwd) {
68
83
  const dir = path.join(cwd, MIGRATIONS_DIR);
69
84
  if (!existsSync(dir)) return 1;
@@ -120,7 +135,8 @@ async function runMigrateCreate(options) {
120
135
  const cwd = path.resolve(options.cwd ?? process.cwd());
121
136
  const log = options.log ?? ((line) => console.log(line));
122
137
  const controlUrl = await resolveControlUrlForUser(options.controlUrl);
123
- const projectId = options.project ?? await readIrtioJsonProject(cwd);
138
+ const config = await readProjectConfig(cwd, "irtio migrate", options.config);
139
+ const projectId = options.project ?? config.project;
124
140
  let version;
125
141
  let versionSource;
126
142
  let previousSchemaJson;
@@ -196,6 +212,10 @@ async function migrate(args, deps = {}) {
196
212
  ...deps.client !== void 0 ? { client: deps.client } : {}
197
213
  });
198
214
  } catch (err) {
215
+ if (err instanceof HelpRequested) {
216
+ log(err.usage);
217
+ return;
218
+ }
199
219
  if (isLoginRequired(err)) {
200
220
  errorLog(pc.red("not logged in"));
201
221
  errorLog("run: irtio login");
@@ -207,6 +227,7 @@ async function migrate(args, deps = {}) {
207
227
  }
208
228
  }
209
229
  export {
230
+ USAGE,
210
231
  migrate,
211
232
  parseMigrateArgs,
212
233
  runMigrateCreate
@@ -1,17 +1,34 @@
1
1
  import {
2
- createApiClient,
3
- isLoginRequired
4
- } from "./chunk-I37DLT7K.js";
2
+ readProjectConfig
3
+ } from "./chunk-32QTPKVT.js";
5
4
  import {
5
+ HelpRequested,
6
+ createApiClient,
7
+ helpFor,
8
+ helpRequested,
9
+ isLoginRequired,
6
10
  resolveControlUrlForUser
7
- } from "./chunk-NVUKSP5U.js";
11
+ } from "./chunk-TV66QHFP.js";
8
12
 
9
13
  // src/rollback.ts
10
- import { existsSync } from "fs";
11
- import { readFile } from "fs/promises";
12
- import * as path from "path";
14
+ import "fs";
15
+ import "fs/promises";
16
+ import "path";
13
17
  import pc from "picocolors";
18
+ var USAGE = `usage: irtio rollback <version> [options]
19
+
20
+ Re-promotes deployment <version> and brings rooms that migrated past it back to their
21
+ pre-migration state. This DISCARDS whatever those rooms wrote since their migration.
22
+
23
+ options:
24
+ <version> the deployment version to go back to
25
+ --project <id> project id (default: the project file)
26
+ -c, --config <f> the project file to read (default irtio.json)
27
+ --url <control> control plane (default: your stored login)
28
+ -h, --help print this
29
+ `;
14
30
  function parseRollbackArgs(args) {
31
+ if (helpRequested(args)) throw helpFor(USAGE);
15
32
  const parsed = {};
16
33
  for (let i = 0; i < args.length; i++) {
17
34
  const arg = args[i];
@@ -22,6 +39,14 @@ function parseRollbackArgs(args) {
22
39
  parsed.project = value;
23
40
  break;
24
41
  }
42
+ case "-c":
43
+ case "--config": {
44
+ const value = args[++i];
45
+ if (value === void 0 || value === "")
46
+ throw new Error("irtio rollback: --config needs a value");
47
+ parsed.config = value;
48
+ break;
49
+ }
25
50
  case "--url": {
26
51
  const value = args[++i];
27
52
  if (value === void 0) throw new Error("irtio rollback: --url needs a value");
@@ -75,23 +100,13 @@ async function runRollback(options) {
75
100
  }
76
101
  return result;
77
102
  }
78
- async function readIrtioJsonProject(cwd) {
79
- const file = path.join(cwd, "irtio.json");
80
- if (!existsSync(file)) return void 0;
81
- let parsed;
82
- try {
83
- parsed = JSON.parse(await readFile(file, "utf8"));
84
- } catch {
85
- throw new Error(`irtio rollback: ${file} is not valid JSON`);
86
- }
87
- return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
88
- }
89
103
  async function rollback(args, deps = {}) {
90
104
  const log = deps.log ?? ((line) => console.log(line));
91
105
  const errorLog = deps.errorLog ?? ((line) => console.error(line));
92
106
  try {
93
107
  const parsed = parseRollbackArgs(args);
94
- const project = parsed.project ?? await readIrtioJsonProject(process.cwd());
108
+ const config = await readProjectConfig(process.cwd(), "irtio rollback", parsed.config);
109
+ const project = parsed.project ?? config.project;
95
110
  if (project === void 0) {
96
111
  throw new Error(
97
112
  "irtio rollback: no project id \u2014 pass --project or run this from a project with irtio.json"
@@ -105,6 +120,10 @@ async function rollback(args, deps = {}) {
105
120
  ...deps.client !== void 0 ? { client: deps.client } : {}
106
121
  });
107
122
  } catch (err) {
123
+ if (err instanceof HelpRequested) {
124
+ log(err.usage);
125
+ return;
126
+ }
108
127
  if (isLoginRequired(err)) {
109
128
  errorLog(pc.red("not logged in"));
110
129
  errorLog("run: irtio login");
@@ -116,6 +135,7 @@ async function rollback(args, deps = {}) {
116
135
  }
117
136
  }
118
137
  export {
138
+ USAGE,
119
139
  parseRollbackArgs,
120
140
  rollback,
121
141
  runRollback