@irtio/cli 0.4.0 → 0.5.1

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.
@@ -0,0 +1,24 @@
1
+ // src/project-file.ts
2
+ import { existsSync } from "fs";
3
+ import { readFile } from "fs/promises";
4
+ import * as path from "path";
5
+ async function readIrtioJsonName(cwd, command) {
6
+ const file = path.join(cwd, "irtio.json");
7
+ if (!existsSync(file)) return void 0;
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(await readFile(file, "utf8"));
11
+ } catch (err) {
12
+ throw new Error(`${command}: ${file} is not valid JSON: ${String(err)}`);
13
+ }
14
+ const name = parsed?.name;
15
+ if (name === void 0) return void 0;
16
+ if (typeof name !== "string" || name.length === 0) {
17
+ throw new Error(`${command}: ${file} has a "name" that is not a non-empty string`);
18
+ }
19
+ return name;
20
+ }
21
+
22
+ export {
23
+ readIrtioJsonName
24
+ };
@@ -4,6 +4,9 @@ import {
4
4
  import {
5
5
  bundleRoom
6
6
  } from "./chunk-KRQUAEN2.js";
7
+ import {
8
+ readIrtioJsonName
9
+ } from "./chunk-D7CDJRFF.js";
7
10
  import {
8
11
  ApiClientError,
9
12
  createApiClient,
@@ -39,6 +42,14 @@ function parseDeployArgs(args) {
39
42
  parsed.project = value;
40
43
  break;
41
44
  }
45
+ case "--name": {
46
+ const value = args[++i];
47
+ if (value === void 0 || value === "") {
48
+ throw new Error("irtio deploy: --name needs a value");
49
+ }
50
+ parsed.name = value;
51
+ break;
52
+ }
42
53
  case "--url": {
43
54
  const value = args[++i];
44
55
  if (value === void 0) throw new Error("irtio deploy: --url needs a value");
@@ -193,9 +204,7 @@ async function runDeploy(options = {}) {
193
204
  else throw err;
194
205
  }
195
206
  if (!projectExists) {
196
- const defaultName = path.basename(cwd);
197
- const answer = (await ask(`project ${projectId} does not exist yet \u2014 name it [${defaultName}]: `)).trim();
198
- const name = answer.length > 0 ? answer : defaultName;
207
+ const name = options.name ?? await readIrtioJsonName(cwd, "irtio deploy") ?? path.basename(cwd);
199
208
  await client.post("/v1/projects", { name, id: projectId });
200
209
  log(pc.dim(`created project ${projectId} (${name})`));
201
210
  }
@@ -356,6 +365,7 @@ async function deploy(args, deps = {}) {
356
365
  allowBreaking: parsed.allowBreaking,
357
366
  log,
358
367
  ...parsed.project !== void 0 ? { project: parsed.project } : {},
368
+ ...parsed.name !== void 0 ? { name: parsed.name } : {},
359
369
  ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
360
370
  ...parsed.room !== void 0 ? { room: parsed.room } : {},
361
371
  ...parsed.strategy !== void 0 ? { strategy: parsed.strategy } : {},
package/dist/index.js CHANGED
@@ -8,8 +8,10 @@ usage: irtio <command>
8
8
 
9
9
  commands:
10
10
  init [dir] [--tick|--event] scaffold irtio/schema.ts, rpc.ts, room.ts,
11
- room.test.ts and irtio.json (--tick/--event
12
- skip the one question it otherwise asks)
11
+ [--name <name>] room.test.ts and irtio.json (--tick/--event
12
+ skip the one question it otherwise asks;
13
+ --name is the project name a first deploy
14
+ registers, default the directory)
13
15
  dev [--room <file>] [--port <n>] [--no-watch] bundle the room file and run it locally
14
16
  simulate [--bots <n>] [--seconds <n>] drive N real clients at a running room and
15
17
  [--room <code>] [--url <ws://\u2026>] check the built-in invariants
@@ -26,11 +28,13 @@ commands:
26
28
  deploy [--allow-breaking] [--project <id>] bundle, classify the schema change against the
27
29
  [--url <control>] [--room <file>] last deployment, and upload \u2014 refuses breaking
28
30
  [--strategy drain|migrate] changes unless a migration covers them;
29
- migrate moves LIVE rooms onto the new version
30
- now (drain, the default, lets them finish)
31
+ [--name <name>] migrate moves LIVE rooms onto the new version
32
+ now (drain, the default, lets them finish).
33
+ A project that does not exist yet is created
34
+ as --name, else irtio.json's "name"
31
35
  deploy --static <dir> [--project <id>] upload a BUILT client directory to the project's
32
36
  [--label <label>] [--url <control>] static site and print the playable link
33
- (<label>.irt-serve.com; no build step)
37
+ [--name <name>] (<label>.irt-serve.com; no build step)
34
38
  migrate create <name> scaffold irtio/migrations/<n>_<name>.ts with the
35
39
  live breaking changes quoted in its header
36
40
  rollback <version> [--project <id>] re-promote deployment <version> and bring rooms
@@ -79,16 +83,16 @@ switch (command) {
79
83
  }
80
84
  case "deploy": {
81
85
  if (args.includes("--static")) {
82
- const { staticDeploy } = await import("./static-deploy-LZXTDYNY.js");
86
+ const { staticDeploy } = await import("./static-deploy-BP3MDXCP.js");
83
87
  await staticDeploy(args);
84
88
  break;
85
89
  }
86
- const { deploy } = await import("./deploy-6OM3UYNL.js");
90
+ const { deploy } = await import("./deploy-YVCVDVMS.js");
87
91
  await deploy(args);
88
92
  break;
89
93
  }
90
94
  case "migrate": {
91
- const { migrate } = await import("./migrate-3R6VRW4J.js");
95
+ const { migrate } = await import("./migrate-UD245ULI.js");
92
96
  await migrate(args);
93
97
  break;
94
98
  }
package/dist/init.d.ts CHANGED
@@ -21,6 +21,8 @@ interface InitArgs {
21
21
  readonly mode?: RoomMode;
22
22
  /** `--physics`: a Rapier room with a shared world-builder and client prediction (D22). */
23
23
  readonly physics?: boolean;
24
+ /** `--name`: the project's display name. Defaults to the directory name. */
25
+ readonly name?: string;
24
26
  }
25
27
  interface RunInitOptions {
26
28
  /** Where the project lives. Defaults to `process.cwd()`. */
@@ -28,6 +30,8 @@ interface RunInitOptions {
28
30
  readonly dir?: string;
29
31
  readonly mode?: RoomMode;
30
32
  readonly physics?: boolean;
33
+ /** The project's display name, written to `irtio.json`. Defaults to the directory name. */
34
+ readonly name?: string;
31
35
  /**
32
36
  * Asks the one question. Injected so tests never touch a TTY; the default reads stdin when it
33
37
  * is a TTY and otherwise takes the default answer rather than hanging in a pipeline.
@@ -41,6 +45,8 @@ interface InitResult {
41
45
  /** Absolute path of the scaffolded project. */
42
46
  readonly dir: string;
43
47
  readonly project: string;
48
+ /** The name written to `irtio.json`; what the first deploy registers the project as. */
49
+ readonly name: string;
44
50
  readonly mode: RoomMode;
45
51
  /** Files written, relative to `dir`, in emit order. */
46
52
  readonly created: readonly string[];
package/dist/init.js CHANGED
@@ -15,8 +15,17 @@ var DEFAULT_TICK_RATE = 20;
15
15
  var DEFAULT_IDLE_MS = 3e4;
16
16
  function parseInitArgs(args) {
17
17
  const parsed = {};
18
- for (const arg of args) {
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
19
20
  switch (arg) {
21
+ case "--name": {
22
+ const value = args[++i];
23
+ if (value === void 0 || value === "") {
24
+ throw new Error("irtio init: --name needs a value");
25
+ }
26
+ parsed.name = value;
27
+ break;
28
+ }
20
29
  case "--physics":
21
30
  parsed.physics = true;
22
31
  break;
@@ -455,11 +464,13 @@ async function runInit(options = {}) {
455
464
  }
456
465
  const configFile = path.join(dir, "irtio.json");
457
466
  let project = options.projectId ?? generateProjectId();
467
+ let name = options.name ?? (path.basename(dir) || "project");
458
468
  if (existsSync(configFile)) {
459
469
  try {
460
470
  const existing = JSON.parse(await readFile(configFile, "utf8"));
461
471
  if (typeof existing.project === "string" && existing.project !== "")
462
472
  project = existing.project;
473
+ if (typeof existing.name === "string" && existing.name !== "") name = existing.name;
463
474
  } catch {
464
475
  }
465
476
  }
@@ -469,14 +480,14 @@ async function runInit(options = {}) {
469
480
  ["irtio/world.ts", WORLD_TEMPLATE],
470
481
  ["irtio/room.ts", physicsRoomTemplate()],
471
482
  ["irtio/room.test.ts", PHYSICS_ROOM_TEST_TEMPLATE],
472
- ["irtio.json", `${JSON.stringify({ project }, null, 2)}
483
+ ["irtio.json", `${JSON.stringify({ project, name }, null, 2)}
473
484
  `]
474
485
  ] : [
475
486
  ["irtio/schema.ts", schemaTemplate(project)],
476
487
  ["irtio/rpc.ts", RPC_TEMPLATE],
477
488
  ["irtio/room.ts", roomTemplate(mode)],
478
489
  ["irtio/room.test.ts", ROOM_TEST_TEMPLATE],
479
- ["irtio.json", `${JSON.stringify({ project }, null, 2)}
490
+ ["irtio.json", `${JSON.stringify({ project, name }, null, 2)}
480
491
  `]
481
492
  ];
482
493
  const created = [];
@@ -498,7 +509,7 @@ async function runInit(options = {}) {
498
509
  if (skipped.length > 0) {
499
510
  log(pc.yellow(`kept ${skipped.join(", ")} (already there \u2014 nothing was overwritten)`));
500
511
  }
501
- log(pc.dim(`project ${project} \xB7 ${mode} mode${physics ? " \xB7 physics" : ""}`));
512
+ log(pc.dim(`project ${project} (${name}) \xB7 ${mode} mode${physics ? " \xB7 physics" : ""}`));
502
513
  const installCommand = packageJson === "absent" ? `npm i ${wantedDeps.join(" ")}` : "npm install # the irtio packages were added to your package.json";
503
514
  for (const line of nextSteps(installCommand)) log(line);
504
515
  if (physics) {
@@ -511,7 +522,7 @@ async function runInit(options = {}) {
511
522
  log(" draw from room.render (predicted + interpolated), write intents via room.state.");
512
523
  log("");
513
524
  }
514
- return { dir, project, mode, created, skipped, packageJson };
525
+ return { dir, project, name, mode, created, skipped, packageJson };
515
526
  }
516
527
  async function init(args) {
517
528
  try {
@@ -519,7 +530,8 @@ async function init(args) {
519
530
  await runInit({
520
531
  ...parsed.dir !== void 0 ? { dir: parsed.dir } : {},
521
532
  ...parsed.mode !== void 0 ? { mode: parsed.mode } : {},
522
- ...parsed.physics !== void 0 ? { physics: parsed.physics } : {}
533
+ ...parsed.physics !== void 0 ? { physics: parsed.physics } : {},
534
+ ...parsed.name !== void 0 ? { name: parsed.name } : {}
523
535
  });
524
536
  } catch (err) {
525
537
  console.error(pc.red(err instanceof Error ? err.message : String(err)));
@@ -56,7 +56,12 @@ function resolveEntry(cwd, room) {
56
56
  async function readIrtioJsonProject(cwd) {
57
57
  const file = path.join(cwd, "irtio.json");
58
58
  if (!existsSync(file)) return void 0;
59
- const parsed = JSON.parse(await readFile(file, "utf8"));
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
+ }
60
65
  return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
61
66
  }
62
67
  async function scanDirectoryVersion(cwd) {
@@ -1,8 +1,12 @@
1
+ import {
2
+ readIrtioJsonName
3
+ } from "./chunk-D7CDJRFF.js";
1
4
  import {
2
5
  STATIC_LIMITS,
3
6
  staticPathProblem
4
7
  } from "./chunk-BPE452KF.js";
5
8
  import {
9
+ ApiClientError,
6
10
  createApiClient,
7
11
  isLoginRequired
8
12
  } from "./chunk-I37DLT7K.js";
@@ -44,6 +48,14 @@ function parseStaticDeployArgs(args) {
44
48
  parsed.label = value;
45
49
  break;
46
50
  }
51
+ case "--name": {
52
+ const value = args[++i];
53
+ if (value === void 0 || value === "") {
54
+ throw new Error("irtio deploy: --name needs a value");
55
+ }
56
+ parsed.name = value;
57
+ break;
58
+ }
47
59
  default:
48
60
  throw new Error(`irtio deploy --static: unknown option ${JSON.stringify(arg)}`);
49
61
  }
@@ -94,6 +106,17 @@ async function readIrtioJsonProject(cwd) {
94
106
  }
95
107
  return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
96
108
  }
109
+ async function ensureProject(client, projectId, cwd, explicitName, log) {
110
+ try {
111
+ await client.get(`/v1/projects/${projectId}`);
112
+ return;
113
+ } catch (err) {
114
+ if (!(err instanceof ApiClientError && err.status === 404)) throw err;
115
+ }
116
+ const name = explicitName ?? await readIrtioJsonName(cwd, "irtio deploy") ?? path.basename(cwd);
117
+ await client.post("/v1/projects", { name, id: projectId });
118
+ log(pc.dim(`created project ${projectId} (${name})`));
119
+ }
97
120
  async function runStaticDeploy(options) {
98
121
  const log = options.log ?? ((line) => console.log(line));
99
122
  const cwd = path.resolve(options.cwd ?? process.cwd());
@@ -132,6 +155,7 @@ async function runStaticDeploy(options) {
132
155
  }
133
156
  const controlUrl = await resolveControlUrlForUser(options.controlUrl);
134
157
  const client = options.client ?? await createApiClient(controlUrl);
158
+ await ensureProject(client, project, cwd, options.name, log);
135
159
  const begin = await client.post(
136
160
  `/v1/projects/${project}/static/begin`,
137
161
  options.label !== void 0 ? { label: options.label } : {}
@@ -181,6 +205,7 @@ async function staticDeploy(args, deps = {}) {
181
205
  ...parsed.project !== void 0 ? { project: parsed.project } : {},
182
206
  ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
183
207
  ...parsed.label !== void 0 ? { label: parsed.label } : {},
208
+ ...parsed.name !== void 0 ? { name: parsed.name } : {},
184
209
  ...deps.client !== void 0 ? { client: deps.client } : {},
185
210
  ...deps.cwd !== void 0 ? { cwd: deps.cwd } : {}
186
211
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irtio/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "irtio CLI: local dev server, room scaffolding, bot simulation, deploy, logs and rooms",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -29,12 +29,12 @@
29
29
  "esbuild": "^0.27.3",
30
30
  "picocolors": "^1.1.1",
31
31
  "ws": "^8.18.0",
32
- "@irtio/bots": "0.4.0",
33
- "@irtio/client": "0.4.0",
34
- "@irtio/protocol": "0.4.0",
35
- "@irtio/runtime": "0.4.0",
36
- "@irtio/schema": "0.4.0",
37
- "@irtio/server": "0.4.0"
32
+ "@irtio/client": "0.5.1",
33
+ "@irtio/protocol": "0.5.1",
34
+ "@irtio/bots": "0.5.1",
35
+ "@irtio/schema": "0.5.1",
36
+ "@irtio/runtime": "0.5.1",
37
+ "@irtio/server": "0.5.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@dimforge/rapier3d-compat": "0.20.0",