@irtio/cli 0.6.0 → 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 (28) hide show
  1. package/dist/api-keys-UTLYMZYN.js +222 -0
  2. package/dist/api.js +1 -1
  3. package/dist/{chunk-3HQMVCYA.js → chunk-BQBOBFBO.js} +4 -4
  4. package/dist/{chunk-RNAH5T4W.js → chunk-IDF46P7R.js} +2 -0
  5. package/dist/{chunk-DKWG7MGO.js → chunk-JL235KIE.js} +1 -1
  6. package/dist/{chunk-ZD4ND6X6.js → chunk-OCVALOGK.js} +1 -1
  7. package/dist/{chunk-RQSJZWQC.js → chunk-WFMRNGO5.js} +134 -105
  8. package/dist/{delete-project-VENS2B44.js → delete-project-MXUYNGAO.js} +2 -2
  9. package/dist/deploy.d.ts +3 -1
  10. package/dist/deploy.js +163 -5
  11. package/dist/{dev-QM26ONKS.js → dev-AUZ4OLA3.js} +139 -10
  12. package/dist/index.js +39 -15
  13. package/dist/init.js +2 -2
  14. package/dist/{keys-JHLMEGRA.js → keys-NXRIBJZP.js} +3 -3
  15. package/dist/{leaderboard-SYPSBPS3.js → leaderboard-ZBJBKETM.js} +65 -11
  16. package/dist/{login-2M73HBZT.js → login-3RVN5PPN.js} +2 -2
  17. package/dist/{logs-2W7CPZO5.js → logs-AT7G6YRH.js} +3 -3
  18. package/dist/{migrate-T3DZJREY.js → migrate-FMXTRVUV.js} +3 -3
  19. package/dist/{ratings-VG32WFDG.js → ratings-XBLX2MUW.js} +3 -3
  20. package/dist/{rollback-SO74MVZV.js → rollback-LI4TDLQA.js} +3 -3
  21. package/dist/{rooms-VI33P4RA.js → rooms-52Q5KBUS.js} +179 -20
  22. package/dist/simulate.d.ts +115 -10
  23. package/dist/simulate.js +270 -36
  24. package/dist/{static-deploy-KOWFKWZA.js → static-deploy-7UCYINJB.js} +5 -5
  25. package/dist/{status-HF3ZEKB7.js → status-JZGKH2P6.js} +3 -3
  26. package/dist/{usage-4G23QXCH.js → usage-7S447INI.js} +3 -3
  27. package/dist/{whoami-KTMTQNHM.js → whoami-UFSWPWK6.js} +2 -2
  28. package/package.json +7 -7
@@ -0,0 +1,222 @@
1
+ import {
2
+ readProjectConfig
3
+ } from "./chunk-JL235KIE.js";
4
+ import {
5
+ HelpRequested,
6
+ helpFor,
7
+ helpRequested
8
+ } from "./chunk-OCVALOGK.js";
9
+ import {
10
+ createApiClient,
11
+ isLoginRequired
12
+ } from "./chunk-IDF46P7R.js";
13
+ import {
14
+ resolveControlUrlForUser
15
+ } from "./chunk-UPHQM6NZ.js";
16
+
17
+ // src/api-keys.ts
18
+ import pc from "picocolors";
19
+ var SCOPES = ["rooms:read", "rooms:write"];
20
+ var USAGE = `usage: irtio api-keys mint --scopes <list> [--label <text>] [options]
21
+ irtio api-keys list [options]
22
+ irtio api-keys revoke <id> [options]
23
+
24
+ Scoped API keys let a script you run yourself \u2014 a nightly cleanup job, a support tool, your own
25
+ backend \u2014 reach this project's rooms over HTTP, and nothing else. A key belongs to one project,
26
+ carries an explicit list of what it may do there, never expires, and can be revoked.
27
+
28
+ A key is NOT a login. It cannot deploy, cannot read your usage or billing, cannot see your other
29
+ projects, and cannot mint or list keys, including itself.
30
+
31
+ subcommands:
32
+ mint --scopes <list> create a key and print it once. Scopes are comma-separated:
33
+ rooms:read (list and read rooms and their saves) and rooms:write
34
+ (also set retention, delete, and force-delete). Write implies read
35
+ list this project's keys, newest first, revoked ones included
36
+ revoke <id> stop a key working. The row stays, so the listing is still an audit
37
+
38
+ options:
39
+ --label <text> a note for the listing, e.g. "nightly cleanup"
40
+ --project <id> project id (default: the project file)
41
+ -c, --config <f> the project file to read (default irtio.json)
42
+ --url <control> control plane (default: your stored login)
43
+ -h, --help print this
44
+ `;
45
+ function parseApiKeysArgs(args) {
46
+ if (helpRequested(args)) throw helpFor(USAGE);
47
+ const first = args[0];
48
+ if (first !== "mint" && first !== "list" && first !== "revoke") {
49
+ throw new Error("irtio api-keys: expected mint, list or revoke");
50
+ }
51
+ const parsed = { sub: first };
52
+ let rest = args.slice(1);
53
+ if (first === "revoke") {
54
+ const id = rest[0];
55
+ if (id === void 0 || id.startsWith("-")) {
56
+ throw new Error("irtio api-keys revoke: needs a key id \u2014 run `irtio api-keys list`");
57
+ }
58
+ parsed.id = id;
59
+ rest = rest.slice(1);
60
+ }
61
+ for (let i = 0; i < rest.length; i++) {
62
+ const arg = rest[i];
63
+ switch (arg) {
64
+ case "--scopes": {
65
+ const value = rest[++i];
66
+ if (value === void 0) throw new Error("irtio api-keys: --scopes needs a value");
67
+ const scopes = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
68
+ for (const scope of scopes) {
69
+ if (!SCOPES.includes(scope)) {
70
+ throw new Error(
71
+ `irtio api-keys: ${JSON.stringify(scope)} is not a scope; the scopes are ${SCOPES.join(" and ")}`
72
+ );
73
+ }
74
+ }
75
+ if (scopes.length === 0) throw new Error("irtio api-keys: --scopes needs at least one");
76
+ parsed.scopes = scopes;
77
+ break;
78
+ }
79
+ case "--label": {
80
+ const value = rest[++i];
81
+ if (value === void 0) throw new Error("irtio api-keys: --label needs a value");
82
+ parsed.label = value;
83
+ break;
84
+ }
85
+ case "--project": {
86
+ const value = rest[++i];
87
+ if (value === void 0) throw new Error("irtio api-keys: --project needs a value");
88
+ parsed.project = value;
89
+ break;
90
+ }
91
+ case "-c":
92
+ case "--config": {
93
+ const value = rest[++i];
94
+ if (value === void 0 || value === "") {
95
+ throw new Error("irtio api-keys: --config needs a value");
96
+ }
97
+ parsed.config = value;
98
+ break;
99
+ }
100
+ case "--url": {
101
+ const value = rest[++i];
102
+ if (value === void 0) throw new Error("irtio api-keys: --url needs a value");
103
+ parsed.url = value;
104
+ break;
105
+ }
106
+ default:
107
+ throw new Error(`irtio api-keys: unknown option ${JSON.stringify(arg)}`);
108
+ }
109
+ }
110
+ if (parsed.sub === "mint" && parsed.scopes === void 0) {
111
+ throw new Error(
112
+ "irtio api-keys mint: --scopes is required \u2014 rooms:read to look, rooms:write to change things too"
113
+ );
114
+ }
115
+ return parsed;
116
+ }
117
+ function formatKeys(rows) {
118
+ if (rows.length === 0) {
119
+ return [
120
+ pc.dim("no API keys"),
121
+ pc.dim("mint one with: irtio api-keys mint --scopes rooms:read")
122
+ ];
123
+ }
124
+ const idWidth = Math.max(2, ...rows.map((r) => r.id.length));
125
+ const scopeWidth = Math.max(6, ...rows.map((r) => r.scopes.join(",").length));
126
+ const out = [
127
+ pc.dim(`${"ID".padEnd(idWidth)} ${"SCOPES".padEnd(scopeWidth)} LAST USED LABEL`)
128
+ ];
129
+ for (const r of rows) {
130
+ const used = r.lastUsed ?? "never";
131
+ const label = r.revokedAt ? pc.dim(`${r.label} (revoked)`) : r.label;
132
+ out.push(
133
+ `${r.id.padEnd(idWidth)} ${r.scopes.join(",").padEnd(scopeWidth)} ${pc.dim(used.padEnd(20))} ${label}`
134
+ );
135
+ }
136
+ return out;
137
+ }
138
+ async function runMint(options) {
139
+ const log = options.log ?? ((line) => console.log(line));
140
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
141
+ const client = options.client ?? await createApiClient(controlUrl);
142
+ const minted = await client.post(`/v1/projects/${options.project}/keys`, {
143
+ scopes: options.scopes,
144
+ ...options.label !== void 0 ? { label: options.label } : {}
145
+ });
146
+ log("");
147
+ log(pc.bold(minted.key));
148
+ log("");
149
+ log(pc.yellow("Copy this now. It will not be shown again, here or anywhere else."));
150
+ log(pc.dim(`id ${minted.id}, scopes ${minted.scopes.join(",")}`));
151
+ log(pc.dim("use it as: Authorization: Bearer <key>, or set IRT_API_KEY for the rooms commands"));
152
+ log(pc.dim(`revoke it with: irtio api-keys revoke ${minted.id}`));
153
+ return minted;
154
+ }
155
+ async function runList(options) {
156
+ const log = options.log ?? ((line) => console.log(line));
157
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
158
+ const client = options.client ?? await createApiClient(controlUrl);
159
+ const rows = await client.get(`/v1/projects/${options.project}/keys`);
160
+ for (const line of formatKeys(rows)) log(line);
161
+ return rows;
162
+ }
163
+ async function runRevoke(options) {
164
+ const log = options.log ?? ((line) => console.log(line));
165
+ const controlUrl = await resolveControlUrlForUser(options.controlUrl);
166
+ const client = options.client ?? await createApiClient(controlUrl);
167
+ await client.del(`/v1/projects/${options.project}/keys/${encodeURIComponent(options.id)}`);
168
+ log(pc.green(`revoked ${options.id}`));
169
+ log(pc.dim("anything using it now gets a 401. The row stays in the listing as a record"));
170
+ }
171
+ async function apiKeys(args, deps = {}) {
172
+ const log = deps.log ?? ((line) => console.log(line));
173
+ const errorLog = deps.errorLog ?? ((line) => console.error(line));
174
+ try {
175
+ const parsed = parseApiKeysArgs(args);
176
+ const config = await readProjectConfig(process.cwd(), "irtio api-keys", parsed.config);
177
+ const project = parsed.project ?? config.project;
178
+ if (project === void 0) {
179
+ throw new Error(
180
+ "irtio api-keys: no project id \u2014 pass --project or run this from a project with irtio.json"
181
+ );
182
+ }
183
+ const common = {
184
+ project,
185
+ log,
186
+ ...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
187
+ ...deps.client !== void 0 ? { client: deps.client } : {}
188
+ };
189
+ if (parsed.sub === "mint") {
190
+ await runMint({
191
+ ...common,
192
+ scopes: parsed.scopes,
193
+ ...parsed.label !== void 0 ? { label: parsed.label } : {}
194
+ });
195
+ } else if (parsed.sub === "revoke") {
196
+ await runRevoke({ ...common, id: parsed.id });
197
+ } else {
198
+ await runList(common);
199
+ }
200
+ } catch (err) {
201
+ if (err instanceof HelpRequested) {
202
+ log(err.usage);
203
+ return;
204
+ }
205
+ if (isLoginRequired(err)) {
206
+ errorLog(pc.red("not logged in"));
207
+ errorLog("run: irtio login");
208
+ process.exitCode = 1;
209
+ return;
210
+ }
211
+ errorLog(pc.red(err instanceof Error ? err.message : String(err)));
212
+ process.exitCode = 1;
213
+ }
214
+ }
215
+ export {
216
+ USAGE,
217
+ apiKeys,
218
+ parseApiKeysArgs,
219
+ runList,
220
+ runMint,
221
+ runRevoke
222
+ };
package/dist/api.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  createApiClient,
5
5
  createApiClientWithToken,
6
6
  isLoginRequired
7
- } from "./chunk-RNAH5T4W.js";
7
+ } from "./chunk-IDF46P7R.js";
8
8
  import "./chunk-UPHQM6NZ.js";
9
9
  export {
10
10
  ApiClientError,
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  STATIC_LIMITS,
3
3
  staticPathProblem
4
- } from "./chunk-RQSJZWQC.js";
4
+ } from "./chunk-WFMRNGO5.js";
5
5
  import {
6
6
  ensureProject,
7
7
  readProjectConfig
8
- } from "./chunk-DKWG7MGO.js";
8
+ } from "./chunk-JL235KIE.js";
9
9
  import {
10
10
  HelpRequested,
11
11
  helpFor,
12
12
  helpRequested
13
- } from "./chunk-ZD4ND6X6.js";
13
+ } from "./chunk-OCVALOGK.js";
14
14
  import {
15
15
  createApiClient,
16
16
  isLoginRequired
17
- } from "./chunk-RNAH5T4W.js";
17
+ } from "./chunk-IDF46P7R.js";
18
18
  import {
19
19
  resolveControlUrlForUser
20
20
  } from "./chunk-UPHQM6NZ.js";
@@ -26,6 +26,8 @@ var NotLoggedInError = class extends Error {
26
26
  name = "NotLoggedInError";
27
27
  };
28
28
  async function createApiClient(controlUrl) {
29
+ const apiKey = process.env.IRT_API_KEY?.trim();
30
+ if (apiKey) return createApiClientWithToken(controlUrl, apiKey);
29
31
  const credential = await readCredential(controlUrl);
30
32
  if (!credential) throw new NotLoggedInError(controlUrl);
31
33
  return createApiClientWithToken(controlUrl, credential.token);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ApiClientError
3
- } from "./chunk-RNAH5T4W.js";
3
+ } from "./chunk-IDF46P7R.js";
4
4
 
5
5
  // src/project-file.ts
6
6
  import { existsSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ApiClientError
3
- } from "./chunk-RNAH5T4W.js";
3
+ } from "./chunk-IDF46P7R.js";
4
4
 
5
5
  // src/help.ts
6
6
  function helpRequested(args) {
@@ -1,14 +1,105 @@
1
- // ../store/dist/index.js
2
- import {
3
- createHash,
4
- createHmac,
5
- createPublicKey,
6
- generateKeyPairSync,
7
- sign,
8
- verify
9
- } from "crypto";
1
+ // ../store/src/static.ts
2
+ var STATIC_LIMITS = {
3
+ /** Bytes per file. Big enough for a wasm build or a texture atlas; a video does not belong. */
4
+ maxFileBytes: 32 * 1024 * 1024,
5
+ /** Files per deploy. */
6
+ maxFiles: 2e3,
7
+ /** Total bytes per deploy. */
8
+ maxTotalBytes: 256 * 1024 * 1024
9
+ };
10
+ function staticPathProblem(path2) {
11
+ if (path2.length === 0 || path2.length > 512) return "path must be 1-512 characters";
12
+ if (path2.startsWith("/")) return "path must be relative (no leading slash)";
13
+ if (path2.includes("\\")) return "path must use forward slashes";
14
+ if (/[\x00-\x1f\x7f]/.test(path2)) return "path must not contain control characters";
15
+ for (const segment of path2.split("/")) {
16
+ if (segment === "" || segment === "." || segment === "..") {
17
+ return "path segments must be non-empty and not . or ..";
18
+ }
19
+ }
20
+ return void 0;
21
+ }
22
+
23
+ // ../store/src/disk.ts
10
24
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
11
25
  import * as path from "path";
26
+ var DiskStore = class {
27
+ constructor(dir) {
28
+ this.dir = dir;
29
+ }
30
+ dir;
31
+ fileFor(key) {
32
+ if (!/^[A-Za-z0-9_.@/-]+$/.test(key) || key.includes("..")) {
33
+ throw new Error(`invalid object store key ${JSON.stringify(key)}`);
34
+ }
35
+ return path.join(this.dir, `${key}.snap`);
36
+ }
37
+ async put(key, bytes) {
38
+ const file = this.fileFor(key);
39
+ await mkdir(path.dirname(file), { recursive: true });
40
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
41
+ await writeFile(tmp, bytes);
42
+ for (let attempt = 0; ; attempt++) {
43
+ try {
44
+ await rename(tmp, file);
45
+ return;
46
+ } catch (err) {
47
+ const code = err.code;
48
+ if ((code === "EPERM" || code === "EBUSY") && attempt < 10) {
49
+ await new Promise((resolve) => setTimeout(resolve, 5 * (attempt + 1)));
50
+ continue;
51
+ }
52
+ throw err;
53
+ }
54
+ }
55
+ }
56
+ async get(key) {
57
+ try {
58
+ return new Uint8Array(await readFile(this.fileFor(key)));
59
+ } catch (err) {
60
+ if (err.code === "ENOENT") return void 0;
61
+ throw err;
62
+ }
63
+ }
64
+ async delete(key) {
65
+ await rm(this.fileFor(key), { force: true });
66
+ }
67
+ async list(prefix) {
68
+ const out = [];
69
+ const walk = async (dir, rel) => {
70
+ let entries;
71
+ try {
72
+ entries = await readdir(dir, { withFileTypes: true });
73
+ } catch (err) {
74
+ if (err.code === "ENOENT") return;
75
+ throw err;
76
+ }
77
+ for (const e of entries) {
78
+ const r = rel ? `${rel}/${e.name}` : e.name;
79
+ if (e.isDirectory()) await walk(path.join(dir, e.name), r);
80
+ else if (e.name.endsWith(".snap")) out.push(r.slice(0, -".snap".length));
81
+ }
82
+ };
83
+ await walk(this.dir, "");
84
+ return out.filter((k) => k.startsWith(prefix)).sort();
85
+ }
86
+ async sizes(prefix) {
87
+ const keys = await this.list(prefix);
88
+ const out = [];
89
+ for (const key of keys) {
90
+ try {
91
+ const st = await stat(this.fileFor(key));
92
+ out.push({ key, size: st.size, lastModified: Math.floor(st.mtimeMs) });
93
+ } catch (err) {
94
+ if (err.code === "ENOENT") continue;
95
+ throw err;
96
+ }
97
+ }
98
+ return out;
99
+ }
100
+ };
101
+
102
+ // ../store/src/kv.ts
12
103
  var KV_LIMITS = {
13
104
  /** Max UTF-8 bytes in one value. */
14
105
  valueBytes: 16 * 1024,
@@ -31,6 +122,16 @@ var KV_ERRORS = {
31
122
  forbidden: "E_KV_FORBIDDEN",
32
123
  unavailable: "E_KV_UNAVAILABLE"
33
124
  };
125
+
126
+ // ../store/src/identity.ts
127
+ import {
128
+ createHash,
129
+ createHmac,
130
+ createPublicKey,
131
+ generateKeyPairSync,
132
+ sign,
133
+ verify
134
+ } from "crypto";
34
135
  var IRT_IDENTITY_ISSUER = "irt";
35
136
  var ASSERTION_TYP = "IRTA";
36
137
  var ASSERTION_ALG = "EdDSA";
@@ -131,6 +232,8 @@ function parseIdentityKeys(value) {
131
232
  }
132
233
  return out.slice(0, 2);
133
234
  }
235
+
236
+ // ../store/src/leaderboard.ts
134
237
  var LEADERBOARD_ERRORS = {
135
238
  badBoard: "E_LB_BAD_BOARD",
136
239
  badScore: "E_LB_BAD_SCORE",
@@ -141,8 +244,21 @@ var LEADERBOARD_ERRORS = {
141
244
  /** D62-a: a `top` cursor that is not a cursor for the board and direction it arrived on. Its
142
245
  * own code because the only thing to do about it is start again from the first page, which no
143
246
  * other code in this table implies. */
144
- badCursor: "E_LB_BAD_CURSOR"
247
+ badCursor: "E_LB_BAD_CURSOR",
248
+ // ---- M6 lane B: boards (D69) ----
249
+ /** A `?period=` that is not shaped like a period key. */
250
+ badPeriod: "E_LB_BAD_PERIOD",
251
+ /** A bucket name that breaks the board-name rules it shares. */
252
+ badBucket: "E_LB_BAD_BUCKET",
253
+ /** A bucketed board read or written without a bucket. Refused rather than merged or defaulted:
254
+ * a read that silently answered every cohort at once would be a ranking nobody asked for. */
255
+ bucketRequired: "E_LB_BUCKET_REQUIRED",
256
+ /** A bucket on a board that declares none. Refused rather than ignored, because a submit whose
257
+ * bucket was quietly dropped would put a cohort's scores on the wrong ranking. */
258
+ noBuckets: "E_LB_NO_BUCKETS"
145
259
  };
260
+
261
+ // ../store/src/rating.ts
146
262
  var RATING_LIMITS = {
147
263
  /** Max players named in one report. A report is one rating period and decomposes into pairs,
148
264
  * so the work is quadratic in this number; 64 is `MAX_QUEUE_SIZE`, which is the largest party
@@ -272,6 +388,8 @@ function ratingValueProblem(value) {
272
388
  }
273
389
  return void 0;
274
390
  }
391
+
392
+ // ../store/src/saves.ts
275
393
  var SAVES_SEGMENT = "/saves/";
276
394
  var SAVE_ID_TIME_DIGITS = 17;
277
395
  var DEFAULT_SAVE_RETAIN = 10;
@@ -332,101 +450,12 @@ async function pruneSaves(store, saves, retain, log) {
332
450
  }
333
451
  return deleted;
334
452
  }
335
- var STATIC_LIMITS = {
336
- /** Bytes per file. Big enough for a wasm build or a texture atlas; a video does not belong. */
337
- maxFileBytes: 32 * 1024 * 1024,
338
- /** Files per deploy. */
339
- maxFiles: 2e3,
340
- /** Total bytes per deploy. */
341
- maxTotalBytes: 256 * 1024 * 1024
342
- };
343
- function staticPathProblem(path2) {
344
- if (path2.length === 0 || path2.length > 512) return "path must be 1-512 characters";
345
- if (path2.startsWith("/")) return "path must be relative (no leading slash)";
346
- if (path2.includes("\\")) return "path must use forward slashes";
347
- if (/[\x00-\x1f\x7f]/.test(path2)) return "path must not contain control characters";
348
- for (const segment of path2.split("/")) {
349
- if (segment === "" || segment === "." || segment === "..") {
350
- return "path segments must be non-empty and not . or ..";
351
- }
352
- }
353
- return void 0;
354
- }
355
- var DiskStore = class {
356
- constructor(dir) {
357
- this.dir = dir;
358
- }
359
- dir;
360
- fileFor(key) {
361
- if (!/^[A-Za-z0-9_.@/-]+$/.test(key) || key.includes("..")) {
362
- throw new Error(`invalid object store key ${JSON.stringify(key)}`);
363
- }
364
- return path.join(this.dir, `${key}.snap`);
365
- }
366
- async put(key, bytes) {
367
- const file = this.fileFor(key);
368
- await mkdir(path.dirname(file), { recursive: true });
369
- const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
370
- await writeFile(tmp, bytes);
371
- for (let attempt = 0; ; attempt++) {
372
- try {
373
- await rename(tmp, file);
374
- return;
375
- } catch (err) {
376
- const code = err.code;
377
- if ((code === "EPERM" || code === "EBUSY") && attempt < 10) {
378
- await new Promise((resolve) => setTimeout(resolve, 5 * (attempt + 1)));
379
- continue;
380
- }
381
- throw err;
382
- }
383
- }
384
- }
385
- async get(key) {
386
- try {
387
- return new Uint8Array(await readFile(this.fileFor(key)));
388
- } catch (err) {
389
- if (err.code === "ENOENT") return void 0;
390
- throw err;
391
- }
392
- }
393
- async delete(key) {
394
- await rm(this.fileFor(key), { force: true });
395
- }
396
- async list(prefix) {
397
- const out = [];
398
- const walk = async (dir, rel) => {
399
- let entries;
400
- try {
401
- entries = await readdir(dir, { withFileTypes: true });
402
- } catch (err) {
403
- if (err.code === "ENOENT") return;
404
- throw err;
405
- }
406
- for (const e of entries) {
407
- const r = rel ? `${rel}/${e.name}` : e.name;
408
- if (e.isDirectory()) await walk(path.join(dir, e.name), r);
409
- else if (e.name.endsWith(".snap")) out.push(r.slice(0, -".snap".length));
410
- }
411
- };
412
- await walk(this.dir, "");
413
- return out.filter((k) => k.startsWith(prefix)).sort();
414
- }
415
- async sizes(prefix) {
416
- const keys = await this.list(prefix);
417
- const out = [];
418
- for (const key of keys) {
419
- try {
420
- const st = await stat(this.fileFor(key));
421
- out.push({ key, size: st.size, lastModified: Math.floor(st.mtimeMs) });
422
- } catch (err) {
423
- if (err.code === "ENOENT") continue;
424
- throw err;
425
- }
426
- }
427
- return out;
428
- }
429
- };
453
+
454
+ // ../store/src/s3.ts
455
+ import { createHash as createHash3 } from "crypto";
456
+
457
+ // ../store/src/sigv4.ts
458
+ import { createHash as createHash2, createHmac as createHmac2 } from "crypto";
430
459
 
431
460
  export {
432
461
  PLAYER_ISSUER_RE,
@@ -2,11 +2,11 @@ import {
2
2
  HelpRequested,
3
3
  helpFor,
4
4
  helpRequested
5
- } from "./chunk-ZD4ND6X6.js";
5
+ } from "./chunk-OCVALOGK.js";
6
6
  import {
7
7
  createApiClient,
8
8
  isLoginRequired
9
- } from "./chunk-RNAH5T4W.js";
9
+ } from "./chunk-IDF46P7R.js";
10
10
  import {
11
11
  resolveControlUrlForUser
12
12
  } from "./chunk-UPHQM6NZ.js";
package/dist/deploy.d.ts CHANGED
@@ -90,6 +90,8 @@ interface RunDeployResult {
90
90
  /** The room entry, or undefined when the project has none. `--room` naming a missing file still
91
91
  * throws: that is a typo, not a project without a room. */
92
92
  declare function findRoomEntry(cwd: string, room: string | undefined): string | undefined;
93
+ /** D70: the schema entry of a schema-only project, or `undefined` when it has none. */
94
+ declare function findSchemaEntry(cwd: string): string | undefined;
93
95
  /** Every room type a project declares, resolved to entry files. */
94
96
  interface RoomTypeEntries {
95
97
  /** The entry `resolveEntry` would have picked: what a plain roomId gets. */
@@ -146,4 +148,4 @@ declare function deploy(args: readonly string[], deps?: {
146
148
  irtioPackages?: BundleOptions['irtioPackages'];
147
149
  }): Promise<void>;
148
150
 
149
- export { type DeployArgs, DeployRefusedError, type RoomTypeEntries, type RunDeployOptions, type RunDeployResult, USAGE, deploy, findRoomEntry, findRoomTypes, parseDeployArgs, runDeploy };
151
+ export { type DeployArgs, DeployRefusedError, type RoomTypeEntries, type RunDeployOptions, type RunDeployResult, USAGE, deploy, findRoomEntry, findRoomTypes, findSchemaEntry, parseDeployArgs, runDeploy };