@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.
- package/dist/api-keys-UTLYMZYN.js +222 -0
- package/dist/api.d.ts +52 -0
- package/dist/api.js +15 -0
- package/dist/bundle.js +1 -1
- package/dist/{chunk-GBNHBWES.js → chunk-BQBOBFBO.js} +10 -6
- package/dist/chunk-IDF46P7R.js +98 -0
- package/dist/{chunk-32QTPKVT.js → chunk-JL235KIE.js} +1 -1
- package/dist/chunk-OCVALOGK.js +31 -0
- package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
- package/dist/chunk-UPHQM6NZ.js +72 -0
- package/dist/chunk-WFMRNGO5.js +481 -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-MXUYNGAO.js +118 -0
- package/dist/deploy.d.ts +151 -0
- package/dist/{deploy-3SABPL3T.js → deploy.js} +324 -44
- package/dist/{dev-QJOGXLKM.js → dev-AUZ4OLA3.js} +3066 -211
- package/dist/index.js +121 -25
- package/dist/init.d.ts +1 -1
- package/dist/init.js +20 -4
- package/dist/{keys-XBORZAPI.js → keys-NXRIBJZP.js} +8 -4
- package/dist/leaderboard-ZBJBKETM.js +406 -0
- package/dist/{login-3EXB4CGX.js → login-3RVN5PPN.js} +12 -5
- package/dist/{logs-2EOXLNWF.js → logs-AT7G6YRH.js} +9 -5
- package/dist/{migrate-WUO2GBMX.js → migrate-FMXTRVUV.js} +12 -8
- package/dist/ratings-XBLX2MUW.js +297 -0
- package/dist/{rollback-GA6UY772.js → rollback-LI4TDLQA.js} +9 -5
- package/dist/rooms-52Q5KBUS.js +411 -0
- package/dist/simulate.d.ts +254 -6
- package/dist/simulate.js +925 -64
- package/dist/{static-deploy-5TBH4VNA.js → static-deploy-7UCYINJB.js} +6 -4
- package/dist/status-JZGKH2P6.js +219 -0
- package/dist/usage-7S447INI.js +213 -0
- package/dist/{whoami-CI5D5RCC.js → whoami-UFSWPWK6.js} +8 -4
- package/package.json +23 -7
- package/dist/chunk-BPE452KF.js +0 -180
- package/dist/chunk-TV66QHFP.js +0 -167
- 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-
|
|
11
|
-
import
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
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
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
|
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
|
};
|