@irtio/cli 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/api-keys-UTLYMZYN.js +222 -0
  2. package/dist/api.d.ts +52 -0
  3. package/dist/api.js +15 -0
  4. package/dist/bundle.js +1 -1
  5. package/dist/{chunk-GBNHBWES.js → chunk-BQBOBFBO.js} +10 -6
  6. package/dist/chunk-IDF46P7R.js +98 -0
  7. package/dist/{chunk-32QTPKVT.js → chunk-JL235KIE.js} +1 -1
  8. package/dist/chunk-OCVALOGK.js +31 -0
  9. package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
  10. package/dist/chunk-UPHQM6NZ.js +72 -0
  11. package/dist/chunk-WFMRNGO5.js +481 -0
  12. package/dist/chunk-ZK5JLUD4.js +94 -0
  13. package/dist/credentials.d.ts +61 -0
  14. package/dist/credentials.js +20 -0
  15. package/dist/delete-project-MXUYNGAO.js +118 -0
  16. package/dist/deploy.d.ts +151 -0
  17. package/dist/{deploy-3SABPL3T.js → deploy.js} +324 -44
  18. package/dist/{dev-QJOGXLKM.js → dev-AUZ4OLA3.js} +3066 -211
  19. package/dist/index.js +121 -25
  20. package/dist/init.d.ts +1 -1
  21. package/dist/init.js +20 -4
  22. package/dist/{keys-XBORZAPI.js → keys-NXRIBJZP.js} +8 -4
  23. package/dist/leaderboard-ZBJBKETM.js +406 -0
  24. package/dist/{login-3EXB4CGX.js → login-3RVN5PPN.js} +12 -5
  25. package/dist/{logs-2EOXLNWF.js → logs-AT7G6YRH.js} +9 -5
  26. package/dist/{migrate-WUO2GBMX.js → migrate-FMXTRVUV.js} +12 -8
  27. package/dist/ratings-XBLX2MUW.js +297 -0
  28. package/dist/{rollback-GA6UY772.js → rollback-LI4TDLQA.js} +9 -5
  29. package/dist/rooms-52Q5KBUS.js +411 -0
  30. package/dist/simulate.d.ts +254 -6
  31. package/dist/simulate.js +925 -64
  32. package/dist/{static-deploy-5TBH4VNA.js → static-deploy-7UCYINJB.js} +6 -4
  33. package/dist/status-JZGKH2P6.js +219 -0
  34. package/dist/usage-7S447INI.js +213 -0
  35. package/dist/{whoami-CI5D5RCC.js → whoami-UFSWPWK6.js} +8 -4
  36. package/package.json +23 -7
  37. package/dist/chunk-BPE452KF.js +0 -180
  38. package/dist/chunk-TV66QHFP.js +0 -167
  39. package/dist/rooms-B66LQIIF.js +0 -226
@@ -1,38 +1,45 @@
1
- import {
2
- clientImportsRoom
3
- } from "./chunk-TQU6345E.js";
4
- import {
5
- bundleRoom
6
- } from "./chunk-KRQUAEN2.js";
7
1
  import {
8
2
  logEnsured,
9
3
  runStaticDeploy
10
- } from "./chunk-GBNHBWES.js";
11
- import "./chunk-BPE452KF.js";
4
+ } from "./chunk-BQBOBFBO.js";
5
+ import {
6
+ clientImportsRoom
7
+ } from "./chunk-TQU6345E.js";
8
+ import "./chunk-WFMRNGO5.js";
12
9
  import {
13
10
  ensureProject,
14
11
  readProjectConfig
15
- } from "./chunk-32QTPKVT.js";
12
+ } from "./chunk-JL235KIE.js";
13
+ import {
14
+ bundleRoom
15
+ } from "./chunk-OTSFRVJN.js";
16
16
  import {
17
- ApiClientError,
18
17
  HelpRequested,
19
- createApiClient,
20
18
  helpFor,
21
- helpRequested,
22
- isLoginRequired,
19
+ helpRequested
20
+ } from "./chunk-OCVALOGK.js";
21
+ import {
22
+ ApiClientError,
23
+ createApiClient,
24
+ isLoginRequired
25
+ } from "./chunk-IDF46P7R.js";
26
+ import {
23
27
  resolveControlUrlForUser
24
- } from "./chunk-TV66QHFP.js";
28
+ } from "./chunk-UPHQM6NZ.js";
25
29
 
26
30
  // src/deploy.ts
27
- import { existsSync } from "fs";
31
+ import { existsSync, readdirSync } from "fs";
28
32
  import { mkdir, readFile, readdir, rm } from "fs/promises";
29
33
  import * as path from "path";
30
34
  import { createInterface } from "readline/promises";
31
35
  import { pathToFileURL } from "url";
36
+ import { DEFAULT_ROOM_TYPE, ROOM_TYPE_RE } from "@irtio/protocol";
32
37
  import { diffSchemas, schemaFromCanonical } from "@irtio/schema";
33
38
  import pc from "picocolors";
34
39
  var ROOM_CANDIDATES = ["irtio/room.ts", "irtio/room.js", "room.ts"];
40
+ var SCHEMA_CANDIDATES = ["irtio/schema.ts", "irtio/schema.js"];
35
41
  var MIGRATIONS_DIR = "irtio/migrations";
42
+ var ROOM_TYPES_DIR = "irtio/rooms";
36
43
  var USAGE = `usage: irtio deploy [options]
37
44
  irtio deploy --static [<dir>] [options]
38
45
 
@@ -139,15 +146,62 @@ function findRoomEntry(cwd, room) {
139
146
  }
140
147
  return void 0;
141
148
  }
149
+ function findSchemaEntry(cwd) {
150
+ for (const candidate of SCHEMA_CANDIDATES) {
151
+ const file = path.resolve(cwd, candidate);
152
+ if (existsSync(file)) return file;
153
+ }
154
+ return void 0;
155
+ }
142
156
  function resolveEntry(cwd, room) {
143
157
  const entry = findRoomEntry(cwd, room);
144
158
  if (entry !== void 0) return entry;
145
159
  throw new Error(
146
160
  `irtio deploy: no room file found in ${cwd}
147
161
  looked for: ${ROOM_CANDIDATES.join(", ")}
162
+ or ${SCHEMA_CANDIDATES.join(" / ")}, to deploy a schema with no room code
148
163
  pass --room <file> if it lives elsewhere`
149
164
  );
150
165
  }
166
+ function findRoomTypes(cwd, room) {
167
+ const dir = path.resolve(cwd, ROOM_TYPES_DIR);
168
+ const types = {};
169
+ if (existsSync(dir)) {
170
+ for (const name of readdirSync(dir).sort()) {
171
+ const ext = path.extname(name);
172
+ if (ext !== ".ts" && ext !== ".js") continue;
173
+ const type = name.slice(0, -ext.length);
174
+ if (!ROOM_TYPE_RE.test(type)) {
175
+ throw new Error(
176
+ `irtio deploy: ${path.join(ROOM_TYPES_DIR, name)} is not a usable room type name
177
+ a type name is lowercase letters, digits, "_" or "-", starting with a letter or digit, at most 24 characters
178
+ rename the file, or move it out of ${ROOM_TYPES_DIR}/ if it is not a room entry`
179
+ );
180
+ }
181
+ types[type] = path.join(dir, name);
182
+ }
183
+ }
184
+ const explicit = findRoomEntry(cwd, room);
185
+ if (explicit !== void 0) {
186
+ if (room === void 0 && types[DEFAULT_ROOM_TYPE] !== void 0) {
187
+ throw new Error(
188
+ `irtio deploy: this project names its default room type twice, in ${ROOM_CANDIDATES[0]} and in ${ROOM_TYPES_DIR}/default.ts
189
+ keep one of them: a plain roomId resolves to exactly one definition`
190
+ );
191
+ }
192
+ return { default: explicit, types: { ...types, [DEFAULT_ROOM_TYPE]: explicit } };
193
+ }
194
+ const fallback = types[DEFAULT_ROOM_TYPE];
195
+ if (fallback !== void 0) return { default: fallback, types };
196
+ if (Object.keys(types).length > 0) {
197
+ throw new Error(
198
+ `irtio deploy: ${ROOM_TYPES_DIR}/ declares ${Object.keys(types).join(", ")} but no default room type, and a roomId with no type prefix has to resolve to one
199
+ add ${ROOM_TYPES_DIR}/default.ts, or keep the single entry at ${ROOM_CANDIDATES[0]}`
200
+ );
201
+ }
202
+ const entry = resolveEntry(cwd, room);
203
+ return { default: entry, types: { [DEFAULT_ROOM_TYPE]: entry } };
204
+ }
151
205
  async function warnIfClientImportsRoom(cwd, entry, config, log) {
152
206
  const client = config.client;
153
207
  if (client === void 0) return;
@@ -162,10 +216,17 @@ async function warnIfClientImportsRoom(cwd, entry, config, log) {
162
216
  }
163
217
  async function loadBundledSchema(file) {
164
218
  const mod = await import(pathToFileURL(file).href);
219
+ const config = mod.default.config;
165
220
  return {
166
221
  project: mod.default.schema.project,
167
222
  canonical: mod.default.schema.canonical,
168
- schema: mod.default.schema
223
+ schema: mod.default.schema,
224
+ memoryMb: typeof config?.memoryMb === "number" ? config.memoryMb : void 0,
225
+ maxAwake: typeof config?.maxAwake === "number" ? config.maxAwake : void 0,
226
+ // The declared string, verbatim and unparsed. `defineRoom` already refused anything the
227
+ // grammar does not accept, and control refuses it again; the CLI's job is to carry what the
228
+ // bundle says rather than to have an opinion of its own about a duration.
229
+ retention: typeof config?.retention === "string" ? config.retention : void 0
169
230
  };
170
231
  }
171
232
  function printBreaking(log, breaking, hint) {
@@ -183,13 +244,162 @@ async function findMigrationFile(cwd, version) {
183
244
  return match ? path.join(dir, match) : void 0;
184
245
  }
185
246
  var DEPLOY_BUILD_DIR = path.join(".irtio", "deploy");
247
+ function isSchemaOnly(cwd, room) {
248
+ if (room !== void 0) return false;
249
+ if (findRoomEntry(cwd, void 0) !== void 0) return false;
250
+ if (existsSync(path.resolve(cwd, ROOM_TYPES_DIR))) return false;
251
+ return findSchemaEntry(cwd) !== void 0;
252
+ }
253
+ async function loadDeployedSchema(entry, outDir, irtioPackages) {
254
+ const bundled = await bundleRoom({
255
+ entry,
256
+ outDir,
257
+ verify: false,
258
+ ...irtioPackages !== void 0 ? { irtioPackages } : {}
259
+ });
260
+ const mod = await import(pathToFileURL(bundled.file).href);
261
+ const schema = mod.schema ?? mod.default;
262
+ if (!schema || typeof schema.canonical !== "string" || typeof schema.hash !== "string") {
263
+ throw new Error(
264
+ `irtio deploy: ${path.basename(entry)} does not export a schema
265
+ it must export the value defineSchema(...) returned, as \`schema\` or as the default`
266
+ );
267
+ }
268
+ return schema;
269
+ }
270
+ async function runSchemaOnlyDeploy(options, cwd, log, ask, controlUrl, config) {
271
+ const entry = findSchemaEntry(cwd);
272
+ const outDir = path.join(cwd, DEPLOY_BUILD_DIR);
273
+ await mkdir(outDir, { recursive: true });
274
+ const schema = await loadDeployedSchema(entry, outDir, options.irtioPackages);
275
+ log(pc.green(`bundled ${path.relative(cwd, entry)} (schema ${schema.hash.slice(0, 12)})`));
276
+ const client = options.client ?? await createApiClient(controlUrl);
277
+ const projectId = options.project ?? config.project ?? schema.project;
278
+ if (projectId === void 0) {
279
+ throw new Error(
280
+ 'irtio deploy: no project id \u2014 pass --project, add "project" to irtio.json, or set it in defineSchema(...)'
281
+ );
282
+ }
283
+ const ensured = await ensureProject(client, projectId, {
284
+ cwd,
285
+ config,
286
+ command: "irtio deploy",
287
+ ...options.name !== void 0 ? { name: options.name } : {}
288
+ });
289
+ logEnsured(log, projectId, ensured);
290
+ const deployments = await client.get(`/v1/projects/${projectId}/deployments`);
291
+ const newestOf = (list) => list.reduce(
292
+ (best, d) => best === void 0 || d.version > best.version ? d : best,
293
+ void 0
294
+ );
295
+ const previous = newestOf(deployments.filter((d) => d.rolledBackAt == null));
296
+ const newest = newestOf(deployments);
297
+ const nextVersion = (newest?.version ?? 0) + 1;
298
+ let allowBreaking = options.allowBreaking ?? false;
299
+ let migrationFile;
300
+ if (previous !== void 0) {
301
+ const changes = diffSchemas(schemaFromCanonical(previous.schemaJson), schema);
302
+ const breaking = changes.filter((c) => c.kind === "breaking");
303
+ const additive = changes.filter((c) => c.kind === "additive");
304
+ if (breaking.length > 0) {
305
+ if (!allowBreaking) {
306
+ const hint = "run: irtio migrate create <name>";
307
+ printBreaking(log, breaking, hint);
308
+ throw new DeployRefusedError({ changes: breaking, hint });
309
+ }
310
+ migrationFile = await findMigrationFile(cwd, nextVersion);
311
+ if (migrationFile === void 0) {
312
+ const hint = `--allow-breaking needs a migration for v${nextVersion}. Run: irtio migrate create <name>`;
313
+ printBreaking(log, breaking, hint);
314
+ throw new DeployRefusedError({ changes: breaking, hint });
315
+ }
316
+ log(pc.yellow(`${breaking.length} breaking change(s), proceeding with --allow-breaking:`));
317
+ for (const change of breaking) log(` ${change.message}`);
318
+ } else {
319
+ allowBreaking = false;
320
+ }
321
+ if (additive.length > 0) {
322
+ log(pc.cyan(`${additive.length} additive change(s):`));
323
+ for (const change of additive) log(` ${change.message}`);
324
+ }
325
+ if (changes.length === 0) log(pc.dim("no schema changes"));
326
+ } else {
327
+ log(pc.dim("no previous deployment, so this is the first deploy for this project"));
328
+ }
329
+ let migrationKey;
330
+ if (migrationFile !== void 0) {
331
+ const migrationBundle = await bundleRoom({
332
+ entry: migrationFile,
333
+ outDir,
334
+ verify: false,
335
+ ...options.irtioPackages !== void 0 ? { irtioPackages: options.irtioPackages } : {}
336
+ });
337
+ const uploaded = await client.postBytes(
338
+ `/v1/projects/${projectId}/artifacts`,
339
+ new Uint8Array(await readFile(migrationBundle.file))
340
+ );
341
+ migrationKey = uploaded.key;
342
+ log(pc.green(`bundled and uploaded migration ${path.basename(migrationFile)}`));
343
+ }
344
+ let origin;
345
+ if (previous === void 0) {
346
+ const answer = (await ask("production origin (localhost is always allowed) \u2014 leave blank to skip: ")).trim();
347
+ if (answer.length > 0) origin = answer;
348
+ }
349
+ let created;
350
+ try {
351
+ created = await client.post(`/v1/projects/${projectId}/deployments`, {
352
+ // No `bundleHash` and no `bundleKey`: that absence IS the schema-only deploy, and it is
353
+ // what makes control write a row `isRelayOnly` reads as relay-only.
354
+ schemaJson: schema.canonical,
355
+ schemaHash: schema.hash,
356
+ ...migrationKey !== void 0 ? { migrationKey } : {},
357
+ ...allowBreaking ? { allowBreaking: true } : {},
358
+ ...origin !== void 0 ? { origin } : {}
359
+ });
360
+ } catch (err) {
361
+ if (err instanceof ApiClientError && err.code === "E_BREAKING_SCHEMA" && err.changes) {
362
+ const changes = err.changes;
363
+ const hint = err.hint ?? "run: irtio migrate create <name>";
364
+ printBreaking(log, changes, hint);
365
+ throw new DeployRefusedError({ changes, hint });
366
+ }
367
+ if (isLoginRequired(err)) throw err;
368
+ throw err;
369
+ }
370
+ await rm(outDir, { recursive: true, force: true }).catch(() => {
371
+ });
372
+ log(pc.green(`deployed v${created.version}`));
373
+ log(pc.dim("schema-only deploy: this project runs as a relay tenant"));
374
+ log(pc.dim(`reachable at ${created.url}`));
375
+ const result = {
376
+ project: projectId,
377
+ version: created.version,
378
+ url: created.url,
379
+ draining: created.draining,
380
+ ...created.applied !== void 0 ? { applied: created.applied } : {}
381
+ };
382
+ if (config.static === void 0 || options.noStatic === true) return result;
383
+ const site = await runStaticDeploy({
384
+ cwd,
385
+ ...options.config !== void 0 ? { config: options.config } : {},
386
+ ...options.controlUrl !== void 0 ? { controlUrl: options.controlUrl } : {},
387
+ ...options.client !== void 0 ? { client: options.client } : {},
388
+ log
389
+ });
390
+ return { ...result, site };
391
+ }
186
392
  async function runDeploy(options = {}) {
187
393
  const cwd = path.resolve(options.cwd ?? process.cwd());
188
394
  const log = options.log ?? ((line) => console.log(line));
189
395
  const ask = options.ask ?? defaultAsk;
190
396
  const controlUrl = await resolveControlUrlForUser(options.controlUrl);
191
397
  const config = await readProjectConfig(cwd, "irtio deploy", options.config);
192
- const entry = resolveEntry(cwd, options.room);
398
+ if (isSchemaOnly(cwd, options.room)) {
399
+ return runSchemaOnlyDeploy(options, cwd, log, ask, controlUrl, config);
400
+ }
401
+ const roomTypes = findRoomTypes(cwd, options.room);
402
+ const entry = roomTypes.default;
193
403
  await warnIfClientImportsRoom(cwd, entry, config, log);
194
404
  const outDir = path.join(cwd, DEPLOY_BUILD_DIR);
195
405
  await mkdir(outDir, { recursive: true });
@@ -228,25 +438,25 @@ async function runDeploy(options = {}) {
228
438
  const newest = newestOf(deployments);
229
439
  let allowBreaking = options.allowBreaking ?? false;
230
440
  let migrationFile;
441
+ let schemaChange = "none";
231
442
  const nextVersion = (newest?.version ?? 0) + 1;
232
443
  if (previous !== void 0) {
233
444
  const oldSchema = schemaFromCanonical(previous.schemaJson);
234
445
  const changes = diffSchemas(oldSchema, bundled.schema);
235
446
  const breaking = changes.filter((c) => c.kind === "breaking");
236
447
  const additive = changes.filter((c) => c.kind === "additive");
448
+ schemaChange = breaking.length > 0 ? "breaking" : additive.length > 0 ? "additive" : "none";
237
449
  if (breaking.length > 0) {
238
450
  if (!allowBreaking) {
239
- printBreaking(log, breaking, "run: irtio migrate create <name>");
240
- throw new DeployRefusedError();
451
+ const hint = "run: irtio migrate create <name>";
452
+ printBreaking(log, breaking, hint);
453
+ throw new DeployRefusedError({ changes: breaking, hint });
241
454
  }
242
455
  migrationFile = await findMigrationFile(cwd, nextVersion);
243
456
  if (migrationFile === void 0) {
244
- printBreaking(
245
- log,
246
- breaking,
247
- `--allow-breaking needs a migration for v${nextVersion} \u2014 run: irtio migrate create <name>`
248
- );
249
- throw new DeployRefusedError();
457
+ const hint = `--allow-breaking needs a migration for v${nextVersion}. Run: irtio migrate create <name>`;
458
+ printBreaking(log, breaking, hint);
459
+ throw new DeployRefusedError({ changes: breaking, hint });
250
460
  }
251
461
  log(pc.yellow(`${breaking.length} breaking change(s), proceeding with --allow-breaking:`));
252
462
  for (const change of breaking) log(` ${change.message}`);
@@ -259,7 +469,7 @@ async function runDeploy(options = {}) {
259
469
  }
260
470
  if (changes.length === 0) log(pc.dim("no schema changes"));
261
471
  } else {
262
- log(pc.dim("no previous deployment \u2014 first deploy for this project"));
472
+ log(pc.dim("no previous deployment, so this is the first deploy for this project"));
263
473
  }
264
474
  let migrationKey;
265
475
  if (migrationFile !== void 0) {
@@ -283,6 +493,45 @@ async function runDeploy(options = {}) {
283
493
  roomBytes
284
494
  );
285
495
  log(pc.green("uploaded bundle"));
496
+ const extraRooms = {};
497
+ for (const [type, typeEntry] of Object.entries(roomTypes.types)) {
498
+ if (type === DEFAULT_ROOM_TYPE) continue;
499
+ let typeResult;
500
+ try {
501
+ typeResult = await bundleRoom({
502
+ entry: typeEntry,
503
+ outDir,
504
+ ...options.irtioPackages !== void 0 ? { irtioPackages: options.irtioPackages } : {}
505
+ });
506
+ } catch (err) {
507
+ throw new Error(
508
+ `irtio deploy: room type "${type}": ${err instanceof Error ? err.message : String(err)}`
509
+ );
510
+ }
511
+ if (typeResult.schemaHash === void 0) {
512
+ throw new Error(`irtio deploy: room type "${type}" bundled without a schema hash`);
513
+ }
514
+ const typeSchema = await loadBundledSchema(typeResult.file);
515
+ const uploaded = await client.postBytes(
516
+ `/v1/projects/${projectId}/artifacts`,
517
+ new Uint8Array(await readFile(typeResult.file))
518
+ );
519
+ extraRooms[type] = {
520
+ bundleHash: typeResult.hash,
521
+ bundleKey: uploaded.key,
522
+ schemaJson: typeSchema.canonical,
523
+ schemaHash: typeResult.schemaHash,
524
+ // M5 part 3.5: this type's sizing declarations travel with its bundle, so control and the
525
+ // host agent can size the VM before it boots. Spread, never null: a type that declares
526
+ // neither sends neither, and the env generation of a project that has never declared
527
+ // anything is unchanged.
528
+ ...typeSchema.memoryMb !== void 0 ? { memoryMb: typeSchema.memoryMb } : {},
529
+ ...typeSchema.maxAwake !== void 0 ? { maxAwake: typeSchema.maxAwake } : {},
530
+ ...typeSchema.retention !== void 0 ? { retention: typeSchema.retention } : {}
531
+ };
532
+ log(pc.green(`bundled and uploaded room type ${type} (${typeResult.hash.slice(0, 12)})`));
533
+ }
534
+ const hasExtraTypes = Object.keys(extraRooms).length > 0;
286
535
  let origin;
287
536
  if (previous === void 0) {
288
537
  const answer = (await ask("production origin (localhost is always allowed) \u2014 leave blank to skip: ")).trim();
@@ -290,17 +539,35 @@ async function runDeploy(options = {}) {
290
539
  }
291
540
  const strategy = options.strategy ?? "drain";
292
541
  if (strategy === "migrate") {
293
- log(pc.yellow("strategy: migrate \u2014 live rooms move onto the new version now."));
294
- log(
295
- pc.yellow(
296
- "Schema unchanged: connected clients see a short gap and a resync on the same socket."
297
- )
298
- );
299
- log(
300
- pc.yellow(
301
- "Schema changed at all: EVERY connected client is disconnected with E_SCHEMA_MISMATCH and must reload."
302
- )
542
+ log(pc.yellow("strategy: migrate. Live rooms move onto the new version now."));
543
+ const physics = bundled.schema.collections.some(
544
+ (c) => c.physics !== void 0
303
545
  );
546
+ if (schemaChange === "breaking") {
547
+ log(
548
+ pc.yellow(
549
+ "Breaking schema change: EVERY connected client is disconnected with E_SCHEMA_MISMATCH and must reload."
550
+ )
551
+ );
552
+ } else {
553
+ log(
554
+ pc.yellow(
555
+ schemaChange === "none" ? "Schema unchanged: connected clients see a short gap and a resync on the same socket." : "Additive schema change: connected clients see a short gap and a resync on the same socket, on the new schema."
556
+ )
557
+ );
558
+ if (physics) {
559
+ log(
560
+ pc.yellow(
561
+ "This schema has physics collections. A change touching one of those is treated as breaking for now, so those clients reload."
562
+ )
563
+ );
564
+ }
565
+ log(
566
+ pc.dim(
567
+ "Clients built before irtio 0.6 are still disconnected with E_SCHEMA_MISMATCH on any schema change."
568
+ )
569
+ );
570
+ }
304
571
  }
305
572
  let response;
306
573
  try {
@@ -309,6 +576,13 @@ async function runDeploy(options = {}) {
309
576
  bundleKey: uploadedRoom.key,
310
577
  schemaJson: bundled.canonical,
311
578
  schemaHash: result.schemaHash,
579
+ // M5 part 3.5: the default type's declarations, top-level beside its schema for the reason
580
+ // `DeploymentSpec` states — the default type is not in the `rooms` map, and putting it there
581
+ // would give every single-room project a `rooms` key and cold-boot the fleet.
582
+ ...bundled.memoryMb !== void 0 ? { memoryMb: bundled.memoryMb } : {},
583
+ ...bundled.maxAwake !== void 0 ? { maxAwake: bundled.maxAwake } : {},
584
+ ...bundled.retention !== void 0 ? { retention: bundled.retention } : {},
585
+ ...hasExtraTypes ? { rooms: extraRooms } : {},
312
586
  ...migrationKey !== void 0 ? { migrationKey } : {},
313
587
  ...allowBreaking ? { allowBreaking: true } : {},
314
588
  ...origin !== void 0 ? { origin } : {},
@@ -324,12 +598,10 @@ async function runDeploy(options = {}) {
324
598
  };
325
599
  } catch (err) {
326
600
  if (err instanceof ApiClientError && err.code === "E_BREAKING_SCHEMA" && err.changes) {
327
- printBreaking(
328
- log,
329
- err.changes,
330
- err.hint ?? "run: irtio migrate create <name>"
331
- );
332
- throw new DeployRefusedError();
601
+ const changes = err.changes;
602
+ const hint = err.hint ?? "run: irtio migrate create <name>";
603
+ printBreaking(log, changes, hint);
604
+ throw new DeployRefusedError({ changes, hint });
333
605
  }
334
606
  throw err;
335
607
  }
@@ -376,8 +648,14 @@ async function runDeploy(options = {}) {
376
648
  }
377
649
  var DeployRefusedError = class extends Error {
378
650
  name = "DeployRefusedError";
379
- constructor() {
651
+ /** The breaking changes that caused the refusal, as the classifier reported them. */
652
+ changes;
653
+ /** The remedy, verbatim: normally `run: irtio migrate create <name>`. */
654
+ hint;
655
+ constructor(reason = {}) {
380
656
  super("deploy refused");
657
+ this.changes = reason.changes ?? [];
658
+ this.hint = reason.hint;
381
659
  }
382
660
  };
383
661
  async function deploy(args, deps = {}) {
@@ -388,7 +666,7 @@ async function deploy(args, deps = {}) {
388
666
  const cwd = path.resolve(deps.cwd ?? process.cwd());
389
667
  const config = await readProjectConfig(cwd, "irtio deploy", parsed.config);
390
668
  if (parsed.room === void 0 && parsed.noStatic !== true && config.static !== void 0 && findRoomEntry(cwd, void 0) === void 0) {
391
- log(pc.dim("no room file \u2014 deploying the static site only"));
669
+ log(pc.dim("no room file, so deploying the static site only"));
392
670
  await runStaticDeploy({
393
671
  cwd,
394
672
  log,
@@ -440,6 +718,8 @@ export {
440
718
  USAGE,
441
719
  deploy,
442
720
  findRoomEntry,
721
+ findRoomTypes,
722
+ findSchemaEntry,
443
723
  parseDeployArgs,
444
724
  runDeploy
445
725
  };