withruntime 0.21.0 → 0.33.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 (79) hide show
  1. package/COMPATIBILITY.md +64 -3
  2. package/E2B.md +1 -1
  3. package/README.md +36 -2
  4. package/README.src.md +35 -1
  5. package/compatibility-lock.json +3 -3
  6. package/dist/agents/browser.d.ts +12 -0
  7. package/dist/agents/browser.js +12 -0
  8. package/dist/agents/index.d.ts +7 -7
  9. package/dist/agents/index.js +7 -8
  10. package/dist/agents-cli.d.ts +1 -1
  11. package/dist/agents-cli.js +250 -3
  12. package/dist/agents-deploy.d.ts +186 -0
  13. package/dist/agents-deploy.js +752 -0
  14. package/dist/agents-file.d.ts +19 -0
  15. package/dist/agents-file.js +135 -0
  16. package/dist/apps/index.d.ts +10 -0
  17. package/dist/apps/index.js +11 -0
  18. package/dist/apps-cli.d.ts +3 -1
  19. package/dist/apps-cli.js +285 -38
  20. package/dist/blaxel/errors.js +2 -1
  21. package/dist/ci/index.d.ts +10 -0
  22. package/dist/ci/index.js +11 -0
  23. package/dist/ci-cli.d.ts +10 -0
  24. package/dist/ci-cli.js +343 -0
  25. package/dist/cli.d.ts +18 -7
  26. package/dist/cli.js +862 -99
  27. package/dist/daytona/errors.js +2 -1
  28. package/dist/e2b/errors.js +2 -1
  29. package/dist/e2b/filesystem.d.ts +1 -0
  30. package/dist/e2b/filesystem.js +1 -1
  31. package/dist/e2b/sandbox.d.ts +11 -2
  32. package/dist/e2b/sandbox.js +22 -6
  33. package/dist/errors.d.ts +11 -4
  34. package/dist/errors.js +27 -4
  35. package/dist/fly/index.d.ts +102 -0
  36. package/dist/fly/index.js +930 -0
  37. package/dist/index.d.ts +7 -3
  38. package/dist/keys.d.ts +6 -0
  39. package/dist/keys.js +10 -1
  40. package/dist/machine.d.ts +10 -6
  41. package/dist/machine.js +14 -6
  42. package/dist/modal/index.d.ts +34 -0
  43. package/dist/modal/index.js +128 -22
  44. package/dist/product-names.js +7 -0
  45. package/dist/products/agents.d.ts +101 -3
  46. package/dist/products/agents.js +49 -0
  47. package/dist/products/apps.d.ts +206 -10
  48. package/dist/products/apps.js +172 -1
  49. package/dist/products/ci.d.ts +332 -0
  50. package/dist/products/ci.js +101 -0
  51. package/dist/products/index.d.ts +8 -0
  52. package/dist/products/index.js +15 -0
  53. package/dist/products/jobs.d.ts +105 -5
  54. package/dist/products/jobs.js +47 -3
  55. package/dist/products/network.d.ts +13 -0
  56. package/dist/products/observability.d.ts +1 -1
  57. package/dist/products/plans.d.ts +131 -0
  58. package/dist/products/plans.js +24 -0
  59. package/dist/products/reservations.d.ts +108 -0
  60. package/dist/products/reservations.js +41 -0
  61. package/dist/products/resources.d.ts +2 -1
  62. package/dist/products/secrets.d.ts +109 -6
  63. package/dist/products/secrets.js +38 -2
  64. package/dist/proxy.d.ts +3 -3
  65. package/dist/proxy.js +20 -0
  66. package/dist/sandbox.d.ts +29 -1
  67. package/dist/sandbox.js +56 -3
  68. package/dist/snapshots.d.ts +1 -1
  69. package/dist/switched-off.d.ts +5 -0
  70. package/dist/switched-off.js +8 -0
  71. package/dist/tar.d.ts +1 -1
  72. package/dist/tar.js +19 -1
  73. package/dist/transport.d.ts +1 -1
  74. package/dist/transport.js +48 -9
  75. package/dist/types.d.ts +35 -4
  76. package/dist/vercel/errors.d.ts +1 -1
  77. package/dist/vercel/errors.js +2 -1
  78. package/dist/vercel/snapshot.js +4 -2
  79. package/package.json +23 -6
package/dist/apps-cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { me, named } from "./cli-name.js";
2
2
  import { RuntimeError } from "./errors.js";
3
3
  import { logStream } from "./terminal.js";
4
- import { apps, } from "./products/apps.js";
4
+ import { apps, functions, } from "./products/apps.js";
5
5
  import { builds, } from "./products/builds.js";
6
6
  import { containers, } from "./products/containers.js";
7
7
  export const APP_HELP = `runtime apps <command>
@@ -12,13 +12,26 @@ export const APP_HELP = `runtime apps <command>
12
12
 
13
13
  create [<name>] --vcpu <1-16> --memory <128-65536> --max-copies <1-1000>
14
14
  [--min-copies 0] [--concurrency 100] [--spares 2] [--port 8080]
15
- [--health-path /healthz] [--label key=value]...
16
- Make an App, waiting for its first deploy; prints its id
15
+ [--health-path /healthz] [--health-fail 60] [--stop-grace 30] [--cdn on|off]
16
+ [--volume <volumeId>:<path>] [--label key=value]...
17
+ Make an App, waiting for its first deploy; prints its id.
18
+ --health-fail: seconds a live copy may fail its health
19
+ check before it is replaced. --stop-grace: seconds a
20
+ stopped copy has after SIGTERM. --volume mounts one
21
+ volume of yours at a path under /data, /mnt, /state,
22
+ /storage or /volumes, in the App's one copy: it needs
23
+ --max-copies 1 (--min-copies is then 1, --spares 0), and
24
+ each deploy stops the old copy before the new one boots
17
25
  ls [--all] Your Apps: state, live version, copies; --all, deleted too
18
26
  get <app> One App, in full (an id or its name)
19
27
  update <app> [--name <name>] [--vcpu n] [--memory n] [--min-copies n] [--max-copies n]
20
- [--concurrency n] [--spares n] [--port n] [--health-path /path] [--label key=value]...
21
- Change its settings
28
+ [--concurrency n] [--spares n] [--port n] [--health-path /path] [--health-fail n]
29
+ [--stop-grace n] [--volume <volumeId>:<path> | none] [--cdn on|off]
30
+ [--label key=value]...
31
+ Change its settings; --volume none takes the volume
32
+ away, only before its first deploy;
33
+ --cdn off sends visitors straight to Runtime's routers,
34
+ with nothing cached (on: Cloudflare's CDN in front)
22
35
  rm <app> Delete it and stop its copies
23
36
  deploy <app> (--image <image> | --build <buildId>) [--env KEY=VALUE]... [--keep-env]
24
37
  [--port 8080] [--health-path /healthz] [--no-wait]
@@ -32,12 +45,76 @@ export const APP_HELP = `runtime apps <command>
32
45
  that one (--wait: until it ends)
33
46
  versions <app> Its versions; the live and previous ones keep saved copies
34
47
  copies <app> Its copies now: version, region, state
48
+ logs <app> [--follow] [--since 1h] [--version n] [--copy <copyId>] [--stream stdout|stderr]
49
+ [--limit 100] [--raw] What its copies printed: the newest lines since --since
50
+ (a time, or 30s, 15m, 2h, 7d; kept 14 days), or with
51
+ --follow every line as it comes, until Ctrl-C. On a
52
+ terminal control characters show as \\uXXXX unless --raw
53
+ exec <app> [--copy <id>] [--root] [--cwd <dir>] [--env K=V]... [--timeout <ms>] [--trace-files <file.json>] -- <command>
54
+ Run a command in its live copy
55
+ cp <file> <app>:/<path> [--copy <id>] Copy a file onto its live copy
56
+ cp <app>:/<path> <file> [--copy <id>] Copy a file off it
57
+ exec and cp go to a serving copy of the live version unless --copy names
58
+ one. A copy holds the App's secrets, so only an owner's or admin's key
59
+ reaches into one. A file written to a copy lasts as long as that copy; a
60
+ deploy replaces its copies.
35
61
  rollback <app> [--no-wait] Put the previous version live again
62
+ restart <app> [--no-wait] Start the live version afresh, as a deploy of its own;
63
+ with a volume, the old copy stops first and the
64
+ downtime is said
36
65
 
37
66
  Examples
38
67
  runtime apps create web --vcpu 1 --memory 512 --max-copies 3
39
68
  runtime apps deploy web --image web:latest --env NODE_ENV=production
69
+ runtime apps logs web --follow
40
70
  runtime apps rollback web
71
+ runtime apps create db --vcpu 1 --memory 1024 --max-copies 1 --volume <volumeId>:/data
72
+ `;
73
+ export const FUNCTION_HELP = `runtime functions <command>
74
+
75
+ A Function serves your HTTP handler from an image or a build, at its own
76
+ address. Copies start when calls come, up to --max-copies, and sleep when
77
+ they stop: an idle Function has no copy awake.
78
+ In a private beta: outside it, each command says how to ask to join.
79
+
80
+ create [<name>] --vcpu <1-16> --memory <128-65536> --max-copies <1-1000>
81
+ [--concurrency 100] [--spares 2] [--port 8080]
82
+ [--health-path /healthz] [--health-fail 60] [--label key=value]...
83
+ Make a Function, waiting for its first deploy; prints its id.
84
+ --health-fail: seconds a live copy may fail its health
85
+ check before it is replaced
86
+ ls [--all] Your Functions: state, live version, copies; --all, deleted too
87
+ get <function> One Function, in full (an id or its name)
88
+ update <function> [--name <name>] [--vcpu n] [--memory n] [--max-copies n]
89
+ [--concurrency n] [--spares n] [--port n] [--health-path /path] [--health-fail n]
90
+ [--label key=value]...
91
+ Change its settings
92
+ rm <function> Delete it and stop its copies
93
+ deploy <function> (--image <image> | --build <buildId>) [--env KEY=VALUE]... [--keep-env]
94
+ [--port 8080] [--health-path /healthz] [--no-wait]
95
+ A new version. Waits until it is live, or says why it
96
+ failed; --no-wait prints the deploy's id at once.
97
+ --image: an image of yours (id, name, name:tag);
98
+ --build: a build that succeeded (runtime builds).
99
+ --env KEY alone sends KEY's value from your environment.
100
+ --keep-env keeps the last deploy's environment, sealed
101
+ deploys <function> [<deployId>] [--wait] Each version's deploy: state and address; with an id,
102
+ that one (--wait: until it ends)
103
+ versions <function> Its versions; the live and previous ones keep saved copies
104
+ copies <function> Its copies now: version, region, state
105
+ logs <function> [--follow] [--since 1h] [--version n] [--copy <copyId>] [--stream stdout|stderr]
106
+ [--limit 100] [--raw] What its copies printed: the newest lines since --since
107
+ (a time, or 30s, 15m, 2h, 7d; kept 14 days), or with
108
+ --follow every line as it comes, until Ctrl-C. On a
109
+ terminal control characters show as \\uXXXX unless --raw
110
+ rollback <function> [--no-wait] Put the previous version live again
111
+ restart <function> [--no-wait] Start the live version afresh, as a deploy of its own
112
+
113
+ Examples
114
+ runtime functions create resize --vcpu 1 --memory 512 --max-copies 10
115
+ runtime functions deploy resize --image resize:latest
116
+ runtime functions logs resize --follow
117
+ runtime functions rollback resize
41
118
  `;
42
119
  export const BUILD_HELP = `runtime builds <command>
43
120
 
@@ -105,31 +182,82 @@ Examples
105
182
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
106
183
  const when = (ms) => (ms == null ? "-" : new Date(ms).toISOString());
107
184
  const ENDED = ["succeeded", "failed", "cancelled", "superseded"];
108
- /** An App's id from its id or its name: a name is looked up among the Apps
109
- * not deleted, and must be one App's alone. */
110
- async function appIdOf(api, target, kit) {
185
+ const seconds = (ms) => `${(ms / 1000).toFixed(1)} s`;
186
+ /** A stop-then-start's downtime, in a person's words. */
187
+ const downtime = (d) => d ? `down ${seconds(d.totalMs)} (stop ${seconds(d.stopMs)}, boot ${seconds(d.bootMs)})` : "-";
188
+ const APP = {
189
+ api: apps,
190
+ command: "apps",
191
+ one: "App",
192
+ many: "Apps",
193
+ help: APP_HELP,
194
+ minCopies: true,
195
+ example: "web --vcpu 1 --memory 512 --max-copies 2",
196
+ };
197
+ const FUNCTION = {
198
+ // The same calls under /v1/functions; the API answers minCopies 0.
199
+ api: functions,
200
+ command: "functions",
201
+ one: "Function",
202
+ many: "Functions",
203
+ help: FUNCTION_HELP,
204
+ minCopies: false,
205
+ example: "resize --vcpu 1 --memory 512 --max-copies 10",
206
+ };
207
+ /** An App's or Function's id from its id or its name: a name is looked up
208
+ * among those not deleted, and must be one's alone. */
209
+ async function appIdOf(api, target, kit, kind) {
111
210
  if (UUID.test(target))
112
211
  return target;
113
212
  const found = (await api.list({ name: target, state: ["waiting", "live", "failed"] })).data;
114
213
  if (found.length === 1)
115
214
  return found[0].id;
116
215
  throw kit.usage(found.length
117
- ? `${found.length} Apps are named ${target}; give one's id (${me} apps ls).`
118
- : `No App is named ${target}. Your Apps: ${me} apps ls`);
216
+ ? `${found.length} ${kind.many} are named ${target}; give one's id (${me} ${kind.command} ls).`
217
+ : `No ${kind.one} is named ${target}. Your ${kind.many}: ${me} ${kind.command} ls`);
218
+ }
219
+ /** `--cdn on` or `--cdn off`. */
220
+ function onOff(value, kit) {
221
+ if (value === "on")
222
+ return true;
223
+ if (value === "off")
224
+ return false;
225
+ throw kit.usage(`--cdn takes on or off, not ${value}.`);
119
226
  }
120
- /** The flags an App's settings are given with, as the API names them. */
121
- function settings(args, kit) {
227
+ /** --volume <volumeId>:<path>, or none (an update's: no volume). */
228
+ function volume(args, kit) {
229
+ const given = kit.flag(args, "volume");
230
+ if (given === undefined)
231
+ return {};
232
+ if (given === "none")
233
+ return { volume: null };
234
+ const at = given.indexOf(":");
235
+ const volumeId = given.slice(0, at);
236
+ const path = given.slice(at + 1);
237
+ if (at < 0 || !UUID.test(volumeId) || !path.startsWith("/"))
238
+ throw kit.usage(`--volume takes a volume's id and where it mounts: --volume <volumeId>:/data (${me} volumes ls)`);
239
+ return { volume: { volumeId, path } };
240
+ }
241
+ /** The flags an App's settings are given with, as the API names them. A
242
+ * Function's leave out --min-copies, --stop-grace and --volume, which its
243
+ * parse does not take. */
244
+ function settings(args, kit, kind) {
122
245
  const { integer, flag } = kit;
123
246
  const number = (name, field) => integer(args, name) === undefined ? {} : { [field]: integer(args, name) };
124
247
  return {
125
248
  ...number("vcpu", "vcpu"),
126
249
  ...number("memory", "memoryMiB"),
127
- ...number("min-copies", "minCopies"),
250
+ ...(kind.minCopies ? number("min-copies", "minCopies") : {}),
128
251
  ...number("max-copies", "maxCopies"),
129
252
  ...number("concurrency", "concurrency"),
130
253
  ...number("spares", "spares"),
131
254
  ...number("port", "port"),
132
255
  ...(flag(args, "health-path") === undefined ? {} : { healthPath: flag(args, "health-path") }),
256
+ ...(flag(args, "cdn") === undefined ? {} : { cdn: onOff(flag(args, "cdn"), kit) }),
257
+ ...number("health-fail", "healthFailSeconds"),
258
+ ...(kind.minCopies
259
+ ? { ...number("stop-grace", "stopGraceSeconds"), ...volume(args, kit) }
260
+ : {}),
133
261
  };
134
262
  }
135
263
  const SETTINGS = [
@@ -141,8 +269,26 @@ const SETTINGS = [
141
269
  "spares",
142
270
  "port",
143
271
  "health-path",
272
+ "cdn",
273
+ "health-fail",
274
+ "stop-grace",
275
+ "volume",
144
276
  "label",
145
277
  ];
278
+ const settingsOf = (kind) => kind.minCopies
279
+ ? SETTINGS
280
+ : SETTINGS.filter((name) => !["min-copies", "stop-grace", "volume"].includes(name));
281
+ /** A version's copies replaced while live: how many, and why. */
282
+ function replaced(list) {
283
+ if (!list?.length)
284
+ return "-";
285
+ const health = list.filter((r) => r.reason === "health").length;
286
+ const why = [
287
+ health ? `${health} health` : "",
288
+ list.length - health ? `${list.length - health} exited` : "",
289
+ ];
290
+ return `${list.length} (${why.filter(Boolean).join(", ")})`;
291
+ }
146
292
  /** A deploy that ended badly, said with its own words, hint and last output. */
147
293
  function deployFailure(deploy) {
148
294
  if (!deploy.error)
@@ -156,29 +302,40 @@ function deployFailure(deploy) {
156
302
  details: { deploy },
157
303
  });
158
304
  }
159
- export async function appsCommand(argv, env, out, client, kit) {
305
+ export function appsCommand(argv, env, out, client, kit) {
306
+ return copiesCommand(APP, argv, env, out, client, kit);
307
+ }
308
+ export function functionsCommand(argv, env, out, client, kit) {
309
+ return copiesCommand(FUNCTION, argv, env, out, client, kit);
310
+ }
311
+ async function copiesCommand(kind, argv, env, out, client, kit) {
160
312
  const { parse, flag, has, need, pairs, envPairs, table, usage } = kit;
161
313
  const [verb, ...rest] = argv;
162
314
  const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
163
315
  if (!verb || verb === "help" || verb === "--help") {
164
- print(named(APP_HELP), { usage: named(APP_HELP) });
316
+ print(named(kind.help), { usage: named(kind.help) });
165
317
  return 0;
166
318
  }
167
- const api = apps((await client()).transport);
168
- const idOf = (target) => appIdOf(api, need(target, "an App's id or name"), kit);
319
+ const api = kind.api((await client()).transport);
320
+ const { command, one, many } = kind;
321
+ const idOf = (target) => appIdOf(api, need(target, `${one === "App" ? "an" : "a"} ${one}'s id or name`), kit, kind);
169
322
  switch (verb) {
170
323
  case "create": {
171
- const args = parse(rest, [], SETTINGS);
324
+ const args = parse(rest, [], settingsOf(kind));
172
325
  if (args.positional.length > 1)
173
- throw usage(`Give one name: ${me} apps create web --vcpu 1 --memory 512 --max-copies 2`);
174
- const given = settings(args, kit);
326
+ throw usage(`Give one name: ${me} ${command} create ${kind.example}`);
327
+ const given = settings(args, kit, kind);
328
+ if (given.volume === null)
329
+ throw usage(`--volume none is for update: leave --volume out to make an App without one.`);
175
330
  for (const [option, field] of [
176
331
  ["vcpu", "vcpu"],
177
332
  ["memory", "memoryMiB"],
178
333
  ["max-copies", "maxCopies"],
179
334
  ])
180
335
  if (given[field] === undefined)
181
- throw usage(`Give --${option}: ${me} apps create ${args.positional[0] ?? "web"} --vcpu 1 --memory 512 --max-copies 2`);
336
+ throw usage(`Give --${option}: ${me} ${command} create ${args.positional[0]
337
+ ? `${args.positional[0]} ${kind.example.replace(/^\S+ /, "")}`
338
+ : kind.example}`);
182
339
  const input = {
183
340
  ...given,
184
341
  ...(args.positional[0] ? { name: args.positional[0] } : {}),
@@ -186,7 +343,7 @@ export async function appsCommand(argv, env, out, client, kit) {
186
343
  };
187
344
  const app = await api.create(input);
188
345
  if (!out.json)
189
- out.error(`Created ${app.name ?? app.id}, waiting for its first version. Deploy one: ${me} apps deploy ${app.name ?? app.id} --image <image>`);
346
+ out.error(`Created ${app.name ?? app.id}, waiting for its first version. Deploy one: ${me} ${command} deploy ${app.name ?? app.id} --image <image>`);
190
347
  print(app.id, app);
191
348
  return 0;
192
349
  }
@@ -201,18 +358,21 @@ export async function appsCommand(argv, env, out, client, kit) {
201
358
  })).toArray(Infinity);
202
359
  print(all.length
203
360
  ? table([
204
- ["ID", "NAME", "STATE", "LIVE", "COPIES", "SIZE", "CREATED"],
361
+ ["ID", "NAME", "ADDRESS", "STATE", "LIVE", "COPIES", "SIZE", "CREATED"],
205
362
  ...all.map((a) => [
206
363
  a.id,
207
364
  a.name ?? "-",
208
- a.state,
365
+ a.url ?? "-",
366
+ // A copy waiting for room: its place in its region's line.
367
+ a.line ? `${a.state} (in line, place ${a.line.place})` : a.state,
209
368
  a.liveVersion === null ? "-" : `v${a.liveVersion}`,
210
- `${a.minCopies}-${a.maxCopies}`,
369
+ // A Function's copies all sleep when idle: up to its maximum.
370
+ kind.minCopies ? `${a.minCopies}-${a.maxCopies}` : `0-${a.maxCopies}`,
211
371
  `${a.vcpu} vCPU, ${a.memoryMiB} MiB`,
212
372
  when(a.createdAt),
213
373
  ]),
214
374
  ])
215
- : `No Apps. Make one: ${me} apps create web --vcpu 1 --memory 512 --max-copies 2`, all);
375
+ : `No ${many}. Make one: ${me} ${command} create ${kind.example}`, all);
216
376
  return 0;
217
377
  }
218
378
  case "get": {
@@ -221,15 +381,15 @@ export async function appsCommand(argv, env, out, client, kit) {
221
381
  return 0;
222
382
  }
223
383
  case "update": {
224
- const args = parse(rest, [], [...SETTINGS, "name"]);
384
+ const args = parse(rest, [], [...settingsOf(kind), "name"]);
225
385
  const id = await idOf(args.positional[0]);
226
386
  const change = {
227
- ...settings(args, kit),
387
+ ...settings(args, kit, kind),
228
388
  ...(flag(args, "name") === undefined ? {} : { name: flag(args, "name") }),
229
389
  ...(args.flags.has("label") ? { labels: pairs(args, "label") } : {}),
230
390
  };
231
391
  if (!Object.keys(change).length)
232
- throw usage(`Give a setting to change, such as ${me} apps update ${args.positional[0]} --max-copies 4`);
392
+ throw usage(`Give a setting to change, such as ${me} ${command} update ${args.positional[0]} --max-copies 4`);
233
393
  const app = await api.update(id, change);
234
394
  print(`${app.name ?? app.id} updated.`, app);
235
395
  return 0;
@@ -265,8 +425,8 @@ export async function appsCommand(argv, env, out, client, kit) {
265
425
  if (failed && !out.json)
266
426
  throw failed;
267
427
  print(deploy.state === "live"
268
- ? `Version ${deploy.version} is live at ${deploy.url}`
269
- : `Version ${deploy.version}: ${deploy.state}. Follow it: ${me} apps deploys ${args.positional[0]} ${deploy.id} --wait`, deploy);
428
+ ? `Version ${deploy.version} is live at ${deploy.url}${deploy.downtime ? `, ${downtime(deploy.downtime)}` : ""}`
429
+ : `Version ${deploy.version}: ${deploy.state}. Follow it: ${me} ${command} deploys ${args.positional[0]} ${deploy.id} --wait`, deploy);
270
430
  return failed ? 1 : 0;
271
431
  }
272
432
  case "deploys": {
@@ -289,16 +449,21 @@ export async function appsCommand(argv, env, out, client, kit) {
289
449
  const deploys = await Promise.all(versions.map((v) => api.getDeploy(id, v.deploy)));
290
450
  print(deploys.length
291
451
  ? table([
292
- ["VERSION", "DEPLOY", "STATE", "FROM", "ADDRESS"],
452
+ ["VERSION", "DEPLOY", "STATE", "FROM", "DOWNTIME", "ADDRESS"],
293
453
  ...deploys.map((d, i) => [
294
454
  `v${d.version}${versions[i].live ? " (live)" : versions[i].previous ? " (previous)" : ""}`,
295
455
  d.id,
296
456
  d.state,
297
- versions[i].build ? `build ${versions[i].build}` : "image",
457
+ d.restartOf != null
458
+ ? `restart of v${d.restartOf}`
459
+ : versions[i].build
460
+ ? `build ${versions[i].build}`
461
+ : "image",
462
+ downtime(d.downtime),
298
463
  d.url,
299
464
  ]),
300
465
  ])
301
- : `No deploys yet. Deploy one: ${me} apps deploy ${args.positional[0]} --image <image>`, deploys);
466
+ : `No deploys yet. Deploy one: ${me} ${command} deploy ${args.positional[0]} --image <image>`, deploys);
302
467
  return 0;
303
468
  }
304
469
  case "versions": {
@@ -306,14 +471,16 @@ export async function appsCommand(argv, env, out, client, kit) {
306
471
  const versions = (await api.versions(id)).sort((a, b) => b.version - a.version);
307
472
  print(versions.length
308
473
  ? table([
309
- ["VERSION", "LIVE", "PREVIOUS", "SAVED", "DEPLOY", "BUILD"],
474
+ ["VERSION", "LIVE", "PREVIOUS", "SAVED", "BOOT", "DEPLOY", "BUILD", "REPLACED"],
310
475
  ...versions.map((v) => [
311
- `v${v.version}`,
476
+ `v${v.version}${v.restartOf == null ? "" : ` (restart of v${v.restartOf})`}`,
312
477
  v.live ? "yes" : "-",
313
478
  v.previous ? "yes" : "-",
314
479
  v.saved ? "yes" : "-",
480
+ v.boot ?? "-",
315
481
  v.deploy,
316
482
  v.build ?? "-",
483
+ replaced(v.replacements),
317
484
  ]),
318
485
  ])
319
486
  : "No versions yet.", versions);
@@ -327,7 +494,68 @@ export async function appsCommand(argv, env, out, client, kit) {
327
494
  ["COPY", "VERSION", "REGION", "STATE", "SINCE"],
328
495
  ...copies.map((c) => [c.id, `v${c.version}`, c.region, c.state, when(c.since)]),
329
496
  ])
330
- : "No copies awake. A request starts one.", copies);
497
+ : `No copies awake. ${one === "App" ? "A request" : "A call"} starts one.`, copies);
498
+ return 0;
499
+ }
500
+ case "exec": {
501
+ const args = parse(rest, ["root"], ["cwd", "env", "timeout", "copy", "trace-files"]);
502
+ const command = args.rest ?? args.positional.slice(1);
503
+ if (!command.length)
504
+ throw usage(`Give the command after --: ${me} apps exec <app> -- ls -la`);
505
+ const copy = await api.copy(await idOf(args.positional[0]), {
506
+ ...(flag(args, "copy") ? { copy: flag(args, "copy") } : {}),
507
+ });
508
+ return kit.execute(copy, command, args, out, env);
509
+ }
510
+ case "cp": {
511
+ const args = parse(rest, [], ["copy"]);
512
+ const [from, to] = args.positional;
513
+ await kit.copyFile(need(from, "a source"), need(to, "a destination"), async (app) => api.copy(await idOf(app), {
514
+ ...(flag(args, "copy") ? { copy: flag(args, "copy") } : {}),
515
+ }), "<app>");
516
+ print(`Copied ${from} to ${to}.`, { from, to });
517
+ return 0;
518
+ }
519
+ case "logs": {
520
+ const args = parse(rest, ["f", "follow", "raw"], ["since", "version", "copy", "stream", "limit"]);
521
+ const id = await idOf(args.positional[0]);
522
+ const named = flag(args, "stream");
523
+ const stream = ["stdout", "stderr"].find((s) => s === named);
524
+ if (named !== undefined && stream === undefined)
525
+ throw usage(`--stream is stdout or stderr: ${me} ${command} logs ${args.positional[0]} --stream stderr`);
526
+ const version = kit.integer(args, "version");
527
+ const limit = kit.integer(args, "limit");
528
+ const query = {
529
+ ...(flag(args, "since") === undefined ? {} : { since: flag(args, "since") }),
530
+ ...(version === undefined ? {} : { version }),
531
+ ...(flag(args, "copy") === undefined ? {} : { copy: flag(args, "copy") }),
532
+ ...(stream === undefined ? {} : { stream }),
533
+ ...(limit === undefined ? {} : { limit }),
534
+ };
535
+ const raw = has(args, "raw");
536
+ const [stdout, stderr] = [logStream(process.stdout, raw), logStream(process.stderr, raw)];
537
+ const write = (line) => (line.stream === "stderr" ? stderr : stdout)(line.text.endsWith("\n") ? line.text : `${line.text}\n`);
538
+ if (!has(args, "f") && !has(args, "follow")) {
539
+ const page = await api.logs(id, query);
540
+ if (out.json)
541
+ out.write(JSON.stringify(page));
542
+ else if (page.lines.length)
543
+ page.lines.forEach(write);
544
+ else
545
+ out.error(`[no lines ${query.since ? `since ${query.since}` : "in the last hour"}; try --since 1d, or --follow]`);
546
+ if (!out.json && page.hasMore)
547
+ out.error(`[older lines are kept: --since further back, or --limit up to 500]`);
548
+ return 0;
549
+ }
550
+ // Until Ctrl-C: the SDK reconnects across the server's two-minute calls.
551
+ for await (const event of api.logs(id, { ...query, follow: true }, { timeoutMs: 0 })) {
552
+ if (out.json)
553
+ out.write(JSON.stringify(event));
554
+ else if (event.type === "line")
555
+ write(event);
556
+ else
557
+ out.error("[some lines were dropped before they were read; going on]");
558
+ }
331
559
  return 0;
332
560
  }
333
561
  case "rollback": {
@@ -345,8 +573,23 @@ export async function appsCommand(argv, env, out, client, kit) {
345
573
  : `Rolling back to version ${deploy.version}: ${deploy.state}.`, deploy);
346
574
  return failed ? 1 : 0;
347
575
  }
576
+ case "restart": {
577
+ const args = parse(rest, ["no-wait"], []);
578
+ const id = await idOf(args.positional[0]);
579
+ const deploy = await api.restart(id, {
580
+ wait: !has(args, "no-wait"),
581
+ timeoutMs: 15 * 60_000,
582
+ });
583
+ const failed = deployFailure(deploy);
584
+ if (failed && !out.json)
585
+ throw failed;
586
+ print(deploy.state === "live"
587
+ ? `Version ${deploy.version} restarted, live at ${deploy.url}${deploy.downtime ? `, ${downtime(deploy.downtime)}` : ""}`
588
+ : `Restarting version ${deploy.version}: ${deploy.state}. Follow it: ${me} ${command} deploys ${args.positional[0]} ${deploy.id} --wait`, deploy);
589
+ return failed ? 1 : 0;
590
+ }
348
591
  }
349
- throw kit.unknown("apps command", verb, [
592
+ throw kit.unknown(`${command} command`, verb, [
350
593
  "create",
351
594
  "ls",
352
595
  "get",
@@ -356,7 +599,11 @@ export async function appsCommand(argv, env, out, client, kit) {
356
599
  "deploys",
357
600
  "versions",
358
601
  "copies",
602
+ "exec",
603
+ "cp",
604
+ "logs",
359
605
  "rollback",
606
+ "restart",
360
607
  ]);
361
608
  }
362
609
  /** A repository as connect takes it: GitHub's owner/name, a github.com
@@ -1,4 +1,5 @@
1
1
  import { RuntimeError } from "../errors.js";
2
+ import { productSwitchedOff } from "../switched-off.js";
2
3
  /* Blaxel's error classes by the same names, so a
3
4
  `catch (e) { if (e instanceof ResponseError) ... }` or an
4
5
  `isGatewayError(e)` written for Blaxel keeps working. One made from a
@@ -143,7 +144,7 @@ export function translate(error) {
143
144
  return Object.assign(new CredentialsError("No Runtime key found. A Blaxel key (BL_API_KEY) is never sent anywhere: set RUNTIME_API_KEY to a " +
144
145
  "Runtime key (https://withruntime.com/account/keys), put a Runtime key (rtcloud_...) in BL_API_KEY, " +
145
146
  "or run `npx withruntime login` once."), { cause: error });
146
- if (error.status === 503 && error.code.endsWith("_unavailable"))
147
+ if (productSwitchedOff(error))
147
148
  return new NotSupportedError(error.code.replace(/_unavailable$/, ""), error.hint ?? "", error.message);
148
149
  if (error.status < 200 || error.status > 599)
149
150
  return error;
@@ -0,0 +1,10 @@
1
+ /** `withruntime/ci`: CI/CD alone. Each push and pull request of a connected
2
+ * GitHub repository is checked in sandboxes. In a private beta.
3
+ *
4
+ * import { ci } from "withruntime/ci";
5
+ * const client = ci(); // RUNTIME_API_KEY, or this machine's saved connection
6
+ */
7
+ import { Runtime, type RuntimeOptions } from "../client.js";
8
+ export type * from "../products/ci.js";
9
+ /** `runtime.ci` of a client made with `options`. */
10
+ export declare function ci(options?: RuntimeOptions): Runtime["ci"];
@@ -0,0 +1,11 @@
1
+ /** `withruntime/ci`: CI/CD alone. Each push and pull request of a connected
2
+ * GitHub repository is checked in sandboxes. In a private beta.
3
+ *
4
+ * import { ci } from "withruntime/ci";
5
+ * const client = ci(); // RUNTIME_API_KEY, or this machine's saved connection
6
+ */
7
+ import { Runtime } from "../client.js";
8
+ /** `runtime.ci` of a client made with `options`. */
9
+ export function ci(options) {
10
+ return new Runtime(options).ci;
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { CliKit } from "./cli.js";
2
+ import type { Runtime } from "./client.js";
3
+ type Out = {
4
+ json: boolean;
5
+ write: (text: string) => void;
6
+ error: (text: string) => void;
7
+ };
8
+ export declare const CI_HELP = "runtime ci <command>\n\n CI/CD checks each push and pull request of a connected GitHub repository in\n sandboxes, as the steps its config file names. A fork's pull request waits\n until a writer re-runs it on GitHub. In a private beta: outside it, each\n command says how to ask to join.\n\n connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]\n [--trust <app-slug>[bot]]... [--name <name>]\n Connect a repository; prints its id. Takes a full key.\n --trust runs that App's pushes as its own from the start\n repos [ls] [--all] Connected repositories, with the Apps each trusts;\n --all: disconnected ones too\n repos trust <repoId | owner/name> <app-slug>[bot]...\n Run that App's pushes as the repository's own code,\n keeping the Apps it trusts already\n repos untrust <repoId | owner/name> <app-slug>[bot]...\n Stop trusting those Apps, keeping the rest\n disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled\n ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]\n [--state <state>]... [--limit 20] Checks, newest first, with their jobs by state\n get <checkId> One check and its jobs in plan order\n logs <jobId> A job's output, its last 256 KiB, and its sandboxes\n tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine\n flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its\n owner and 48-hour deadline; --all: ended ones too\n flake <flakeId> One flaky test: its proof, owner, and recent runs\n flake prove <flakeId> Its 100 proof runs again at main's next commit\n flake hand <flakeId> <login | org/team> Hand it to someone else\n flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main\n no longer holds the file\n\n A failed job's sandbox may be kept paused: `runtime sandboxes wake <id>`, then\n `runtime sandboxes shell <id>` to look around where it failed.\n\nExamples\n runtime ci connect acme/web --config .runtime/ci.yml\n runtime ci repos trust acme/web acme-bot[bot]\n runtime ci ls --commit $(git rev-parse HEAD)\n runtime ci logs <jobId>\n";
9
+ export declare function ciCommand(argv: string[], out: Out, client: () => Promise<Runtime>, kit: CliKit): Promise<number>;
10
+ export {};