withruntime 0.23.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.
- package/COMPATIBILITY.md +64 -3
- package/E2B.md +1 -1
- package/README.md +36 -2
- package/README.src.md +35 -1
- package/compatibility-lock.json +3 -3
- package/dist/agents/browser.d.ts +12 -0
- package/dist/agents/browser.js +12 -0
- package/dist/agents/index.d.ts +7 -7
- package/dist/agents/index.js +7 -8
- package/dist/agents-cli.d.ts +1 -1
- package/dist/agents-cli.js +250 -3
- package/dist/agents-deploy.d.ts +186 -0
- package/dist/agents-deploy.js +752 -0
- package/dist/agents-file.d.ts +19 -0
- package/dist/agents-file.js +135 -0
- package/dist/apps/index.d.ts +10 -0
- package/dist/apps/index.js +11 -0
- package/dist/apps-cli.d.ts +3 -1
- package/dist/apps-cli.js +285 -38
- package/dist/blaxel/errors.js +2 -1
- package/dist/ci/index.d.ts +10 -0
- package/dist/ci/index.js +11 -0
- package/dist/ci-cli.d.ts +10 -0
- package/dist/ci-cli.js +343 -0
- package/dist/cli.d.ts +18 -7
- package/dist/cli.js +775 -85
- package/dist/daytona/errors.js +2 -1
- package/dist/e2b/errors.js +2 -1
- package/dist/e2b/filesystem.d.ts +1 -0
- package/dist/e2b/filesystem.js +1 -1
- package/dist/e2b/sandbox.d.ts +11 -2
- package/dist/e2b/sandbox.js +22 -6
- package/dist/errors.d.ts +11 -4
- package/dist/errors.js +27 -4
- package/dist/fly/index.d.ts +102 -0
- package/dist/fly/index.js +930 -0
- package/dist/index.d.ts +7 -3
- package/dist/machine.d.ts +10 -6
- package/dist/machine.js +14 -6
- package/dist/modal/index.d.ts +34 -0
- package/dist/modal/index.js +128 -22
- package/dist/products/agents.d.ts +101 -3
- package/dist/products/agents.js +49 -0
- package/dist/products/apps.d.ts +206 -10
- package/dist/products/apps.js +172 -1
- package/dist/products/ci.d.ts +332 -0
- package/dist/products/ci.js +101 -0
- package/dist/products/index.d.ts +8 -0
- package/dist/products/index.js +15 -0
- package/dist/products/jobs.d.ts +105 -5
- package/dist/products/jobs.js +47 -3
- package/dist/products/network.d.ts +13 -0
- package/dist/products/observability.d.ts +1 -1
- package/dist/products/plans.d.ts +131 -0
- package/dist/products/plans.js +24 -0
- package/dist/products/reservations.d.ts +108 -0
- package/dist/products/reservations.js +41 -0
- package/dist/products/resources.d.ts +2 -1
- package/dist/products/secrets.d.ts +90 -6
- package/dist/products/secrets.js +8 -2
- package/dist/proxy.d.ts +3 -3
- package/dist/proxy.js +20 -0
- package/dist/sandbox.d.ts +29 -1
- package/dist/sandbox.js +56 -3
- package/dist/snapshots.d.ts +1 -1
- package/dist/switched-off.d.ts +5 -0
- package/dist/switched-off.js +8 -0
- package/dist/tar.d.ts +1 -1
- package/dist/tar.js +19 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +48 -9
- package/dist/types.d.ts +35 -4
- package/dist/vercel/errors.d.ts +1 -1
- package/dist/vercel/errors.js +2 -1
- package/dist/vercel/snapshot.js +4 -2
- package/package.json +23 -6
package/dist/ci-cli.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { me, named } from "./cli-name.js";
|
|
2
|
+
import { ci, } from "./products/ci.js";
|
|
3
|
+
export const CI_HELP = `runtime ci <command>
|
|
4
|
+
|
|
5
|
+
CI/CD checks each push and pull request of a connected GitHub repository in
|
|
6
|
+
sandboxes, as the steps its config file names. A fork's pull request waits
|
|
7
|
+
until a writer re-runs it on GitHub. In a private beta: outside it, each
|
|
8
|
+
command says how to ask to join.
|
|
9
|
+
|
|
10
|
+
connect <owner/name> [--branch main] [--config runtime.yml] [--installation <id>]
|
|
11
|
+
[--trust <app-slug>[bot]]... [--name <name>]
|
|
12
|
+
Connect a repository; prints its id. Takes a full key.
|
|
13
|
+
--trust runs that App's pushes as its own from the start
|
|
14
|
+
repos [ls] [--all] Connected repositories, with the Apps each trusts;
|
|
15
|
+
--all: disconnected ones too
|
|
16
|
+
repos trust <repoId | owner/name> <app-slug>[bot]...
|
|
17
|
+
Run that App's pushes as the repository's own code,
|
|
18
|
+
keeping the Apps it trusts already
|
|
19
|
+
repos untrust <repoId | owner/name> <app-slug>[bot]...
|
|
20
|
+
Stop trusting those Apps, keeping the rest
|
|
21
|
+
disconnect <repoId | owner/name> Stop checking it; its checks still going are cancelled
|
|
22
|
+
ls [--repository <repoId>] [--branch <name>] [--commit <sha>] [--pr <number>]
|
|
23
|
+
[--state <state>]... [--limit 20] Checks, newest first, with their jobs by state
|
|
24
|
+
get <checkId> One check and its jobs in plan order
|
|
25
|
+
logs <jobId> A job's output, its last 256 KiB, and its sandboxes
|
|
26
|
+
tests <repoId> [--quarantined] Its test files: time, recent outcomes, quarantine
|
|
27
|
+
flakes [--repository <repoId>] [--all] Flaky tests: each quarantined (or still gating), its
|
|
28
|
+
owner and 48-hour deadline; --all: ended ones too
|
|
29
|
+
flake <flakeId> One flaky test: its proof, owner, and recent runs
|
|
30
|
+
flake prove <flakeId> Its 100 proof runs again at main's next commit
|
|
31
|
+
flake hand <flakeId> <login | org/team> Hand it to someone else
|
|
32
|
+
flake delete <flakeId> --reason <why> Say why its test is deleted; it ends once main
|
|
33
|
+
no longer holds the file
|
|
34
|
+
|
|
35
|
+
A failed job's sandbox may be kept paused: \`runtime sandboxes wake <id>\`, then
|
|
36
|
+
\`runtime sandboxes shell <id>\` to look around where it failed.
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
runtime ci connect acme/web --config .runtime/ci.yml
|
|
40
|
+
runtime ci repos trust acme/web acme-bot[bot]
|
|
41
|
+
runtime ci ls --commit $(git rev-parse HEAD)
|
|
42
|
+
runtime ci logs <jobId>
|
|
43
|
+
`;
|
|
44
|
+
const when = (ms) => (ms === null ? "-" : new Date(ms).toISOString());
|
|
45
|
+
const took = (from, to) => from === null || to === null ? "-" : `${((to - from) / 1000).toFixed(1)}s`;
|
|
46
|
+
const counted = (c) => Object.entries(c)
|
|
47
|
+
.map(([state, n]) => `${n} ${state}`)
|
|
48
|
+
.join(", ") || "-";
|
|
49
|
+
/** Where a check's machines came from and how soon, in a line. */
|
|
50
|
+
const machined = (m) => {
|
|
51
|
+
if (!m)
|
|
52
|
+
return null;
|
|
53
|
+
const from = [
|
|
54
|
+
m.reused && `${m.reused} its pull request's own`,
|
|
55
|
+
m.pooled && `${m.pooled} pooled`,
|
|
56
|
+
m.forked && `${m.forked} forked in ${m.forks}`,
|
|
57
|
+
m.created && `${m.created} created`,
|
|
58
|
+
].filter(Boolean);
|
|
59
|
+
const s = (ms) => (ms === null ? "-" : `${(ms / 1000).toFixed(2)}s`);
|
|
60
|
+
return `machines: ${from.join(", ") || "none"}; first in ${s(m.firstMs)}, first test at ${s(m.firstTestMs)}${m.lineMs ? `, ${s(m.lineMs)} in line` : ""}${m.kept ? `; ${m.kept} kept for the next push` : ""}`;
|
|
61
|
+
};
|
|
62
|
+
const where = (c) => c.pullRequest !== null ? `#${c.pullRequest}` : (c.branch ?? c.event);
|
|
63
|
+
const STATES = ["queued", "running", "passed", "failed", "cancelled"];
|
|
64
|
+
const ownerOf = (f) => f.owner === null
|
|
65
|
+
? "nobody found"
|
|
66
|
+
: `@${f.owner.login}${f.owner.pullRequest === null ? "" : ` (#${f.owner.pullRequest})`}`;
|
|
67
|
+
export async function ciCommand(argv, out, client, kit) {
|
|
68
|
+
const { parse, flag, has, need, table, usage, unknown } = kit;
|
|
69
|
+
const [verb, ...rest] = argv;
|
|
70
|
+
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
71
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
72
|
+
print(named(CI_HELP), { usage: named(CI_HELP) });
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
const api = ci((await client()).transport);
|
|
76
|
+
const whole = (value, what) => {
|
|
77
|
+
if (value === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (!/^[1-9][0-9]{0,8}$/.test(value))
|
|
80
|
+
throw usage(`${what} takes a whole number: ${value}`);
|
|
81
|
+
return Number(value);
|
|
82
|
+
};
|
|
83
|
+
const apps = (words) => {
|
|
84
|
+
if (!words.length)
|
|
85
|
+
throw usage(`Name an App by its login, such as acme-bot[bot].`);
|
|
86
|
+
for (const word of words)
|
|
87
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,38}\[bot\]$/.test(word))
|
|
88
|
+
throw usage(`${word} is not an App's login: name it <app-slug>[bot], as GitHub shows its pushes (quote it in your shell: '${word.replace(/\[bot\]$/, "")}[bot]').`);
|
|
89
|
+
return words;
|
|
90
|
+
};
|
|
91
|
+
const trusted = (r) => r.trustedApps?.join(", ") || "none";
|
|
92
|
+
const disconnect = async (idOrName) => {
|
|
93
|
+
const found = await api.repositories.find(idOrName);
|
|
94
|
+
const repo = await api.repositories.disconnect(found?.id ?? idOrName);
|
|
95
|
+
print(`${repo.repository} disconnected; its checks that ended stay.`, repo);
|
|
96
|
+
return 0;
|
|
97
|
+
};
|
|
98
|
+
switch (verb) {
|
|
99
|
+
case "connect": {
|
|
100
|
+
const args = parse(rest, [], ["branch", "config", "installation", "trust", "name"]);
|
|
101
|
+
const repository = need(args.positional[0], "the repository, such as acme/web");
|
|
102
|
+
const installation = whole(flag(args, "installation"), "--installation");
|
|
103
|
+
const trust = args.flags.get("trust") ?? [];
|
|
104
|
+
const input = {
|
|
105
|
+
repository,
|
|
106
|
+
...(flag(args, "branch") ? { productionBranch: flag(args, "branch") } : {}),
|
|
107
|
+
...(flag(args, "config") ? { configPath: flag(args, "config") } : {}),
|
|
108
|
+
...(installation === undefined ? {} : { installation }),
|
|
109
|
+
...(trust.length ? { trustedApps: apps(trust) } : {}),
|
|
110
|
+
...(flag(args, "name") ? { name: flag(args, "name") } : {}),
|
|
111
|
+
};
|
|
112
|
+
const repo = await api.repositories.connect(input);
|
|
113
|
+
if (!out.json)
|
|
114
|
+
out.error(`Connected ${repo.repository}: each push and pull request is checked as ${repo.configPath} says; ${repo.productionBranch} is its production branch; it trusts ${trusted(repo)}. Its checks: ${me} ci ls --repository ${repo.id}`);
|
|
115
|
+
print(repo.id, repo);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
case "repos":
|
|
119
|
+
case "repositories": {
|
|
120
|
+
const [action, ...more] = rest;
|
|
121
|
+
if (action === "trust" || action === "untrust") {
|
|
122
|
+
const [which, ...logins] = more;
|
|
123
|
+
const id = need(which, "the repository's id or owner/name");
|
|
124
|
+
const listed = apps(logins);
|
|
125
|
+
const repo = action === "trust"
|
|
126
|
+
? await api.repositories.trust(id, listed)
|
|
127
|
+
: await api.repositories.untrust(id, listed);
|
|
128
|
+
print(`${repo.repository} trusts ${trusted(repo)}.`, repo);
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
if (action === "connect")
|
|
132
|
+
return ciCommand(["connect", ...more], out, client, kit);
|
|
133
|
+
if (action === "disconnect" || action === "rm")
|
|
134
|
+
return disconnect(need(more[0], "the repository's id or owner/name"));
|
|
135
|
+
const listing = action === "ls" || action === "list" ? more : rest;
|
|
136
|
+
if (listing.length && !listing[0].startsWith("-"))
|
|
137
|
+
throw unknown("ci repos command", listing[0], [
|
|
138
|
+
"ls",
|
|
139
|
+
"connect",
|
|
140
|
+
"trust",
|
|
141
|
+
"untrust",
|
|
142
|
+
"disconnect",
|
|
143
|
+
]);
|
|
144
|
+
const args = parse(listing, ["all"], []);
|
|
145
|
+
const all = await (await api.repositories.list({
|
|
146
|
+
limit: 100,
|
|
147
|
+
...(has(args, "all") ? {} : { state: ["connected"] }),
|
|
148
|
+
})).toArray(Infinity);
|
|
149
|
+
print(all.length
|
|
150
|
+
? table([
|
|
151
|
+
["ID", "REPOSITORY", "BRANCH", "CONFIG", "TRUSTED APPS", "STATE"],
|
|
152
|
+
...all.map((r) => [
|
|
153
|
+
r.id,
|
|
154
|
+
r.repository,
|
|
155
|
+
r.productionBranch,
|
|
156
|
+
r.configPath,
|
|
157
|
+
trusted(r),
|
|
158
|
+
r.state,
|
|
159
|
+
]),
|
|
160
|
+
])
|
|
161
|
+
: `No repositories this key lists. Connect one: ${me} ci connect acme/web`, all);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
case "disconnect":
|
|
165
|
+
return disconnect(need(rest[0], "the repository's id or owner/name"));
|
|
166
|
+
case "ls":
|
|
167
|
+
case "list": {
|
|
168
|
+
const args = parse(rest, [], ["repository", "branch", "commit", "pr", "state", "limit"]);
|
|
169
|
+
const commit = flag(args, "commit");
|
|
170
|
+
if (commit !== undefined && !/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(commit))
|
|
171
|
+
throw usage(`--commit takes a full sha in lower case: ${me} ci ls --commit $(git rev-parse HEAD)`);
|
|
172
|
+
const states = args.flags.get("state");
|
|
173
|
+
for (const state of states ?? [])
|
|
174
|
+
if (!STATES.includes(state))
|
|
175
|
+
throw unknown("state", state, STATES);
|
|
176
|
+
const pullRequest = whole(flag(args, "pr"), "--pr");
|
|
177
|
+
const limit = whole(flag(args, "limit"), "--limit") ?? 20;
|
|
178
|
+
const page = await api.checks.list({
|
|
179
|
+
limit: Math.min(limit, 100),
|
|
180
|
+
...(flag(args, "repository") ? { repositoryId: flag(args, "repository") } : {}),
|
|
181
|
+
...(flag(args, "branch") ? { branch: flag(args, "branch") } : {}),
|
|
182
|
+
...(commit ? { commit } : {}),
|
|
183
|
+
...(pullRequest === undefined ? {} : { pullRequest }),
|
|
184
|
+
...(states ? { state: states } : {}),
|
|
185
|
+
});
|
|
186
|
+
const all = await page.toArray(limit);
|
|
187
|
+
print(all.length
|
|
188
|
+
? table([
|
|
189
|
+
["ID", "STATE", "REPOSITORY", "WHERE", "COMMIT", "JOBS", "TOOK", "CREATED"],
|
|
190
|
+
...all.map((c) => [
|
|
191
|
+
c.id,
|
|
192
|
+
c.waiting ? "waiting" : c.state,
|
|
193
|
+
c.repository,
|
|
194
|
+
where(c),
|
|
195
|
+
c.commit.slice(0, 12),
|
|
196
|
+
counted(c.counts),
|
|
197
|
+
took(c.startedAt, c.endedAt),
|
|
198
|
+
when(c.createdAt),
|
|
199
|
+
]),
|
|
200
|
+
])
|
|
201
|
+
: "No checks.", all);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
case "get": {
|
|
205
|
+
const check = await api.checks.get(need(rest[0], "a check's id"));
|
|
206
|
+
const job = (j) => [
|
|
207
|
+
j.id,
|
|
208
|
+
j.parts > 1 ? `${j.name} (${j.part}/${j.parts})` : j.name,
|
|
209
|
+
j.state,
|
|
210
|
+
j.ms === null ? "-" : `${(j.ms / 1000).toFixed(1)}s`,
|
|
211
|
+
j.exitCode === null ? "-" : String(j.exitCode),
|
|
212
|
+
j.sandboxIds.join(",") || "-",
|
|
213
|
+
];
|
|
214
|
+
print([
|
|
215
|
+
`${check.repository} ${check.commit.slice(0, 12)} ${where(check)}: ${check.waiting ? "waiting for a writer to re-run it on GitHub" : check.state}${check.reason ? ` (${check.reason})` : ""}`,
|
|
216
|
+
...(check.machines ? [machined(check.machines)] : []),
|
|
217
|
+
check.jobs.length
|
|
218
|
+
? table([["JOB", "NAME", "STATE", "TOOK", "EXIT", "SANDBOXES"], ...check.jobs.map(job)])
|
|
219
|
+
: "No jobs yet.",
|
|
220
|
+
].join("\n"), check);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
case "logs":
|
|
224
|
+
case "job": {
|
|
225
|
+
const job = await api.jobs.get(need(rest[0], "a job's id (runtime ci get <checkId>)"));
|
|
226
|
+
if (out.json) {
|
|
227
|
+
out.write(JSON.stringify(job));
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
out.write(job.log.endsWith("\n") || !job.log ? job.log : `${job.log}\n`);
|
|
231
|
+
if (job.review)
|
|
232
|
+
out.write(reviewed(job.review));
|
|
233
|
+
out.error(`[${job.name}${job.parts > 1 ? ` ${job.part}/${job.parts}` : ""} ${job.state}${job.exitCode === null ? "" : `, exit code ${job.exitCode}`}${job.reason ? `, ${job.reason}` : ""}]`);
|
|
234
|
+
if (job.state === "failed" && job.sandboxIds.length)
|
|
235
|
+
out.error(`Its sandbox may be kept paused, to look into: ${me} sandboxes wake ${job.sandboxIds[0]}, then ${me} sandboxes shell ${job.sandboxIds[0]}`);
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
case "flakes": {
|
|
239
|
+
const args = parse(rest, ["all"], ["repository"]);
|
|
240
|
+
const repositoryId = flag(args, "repository");
|
|
241
|
+
const all = await (await api.flakes.list({
|
|
242
|
+
limit: 100,
|
|
243
|
+
...(repositoryId ? { repositoryId } : {}),
|
|
244
|
+
...(has(args, "all") ? {} : { state: ["quarantined", "gating"] }),
|
|
245
|
+
})).toArray(Infinity);
|
|
246
|
+
print(all.length
|
|
247
|
+
? table([
|
|
248
|
+
["ID", "FILE", "STATE", "OWNER", "DUE"],
|
|
249
|
+
...all.map((f) => [
|
|
250
|
+
f.id,
|
|
251
|
+
f.file,
|
|
252
|
+
f.state,
|
|
253
|
+
ownerOf(f),
|
|
254
|
+
f.endedAt !== null ? "-" : `${when(f.dueAt)}${f.overdue ? " (overdue)" : ""}`,
|
|
255
|
+
]),
|
|
256
|
+
])
|
|
257
|
+
: "No flaky tests.", all);
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
case "flake": {
|
|
261
|
+
const [sub, ...more] = rest;
|
|
262
|
+
if (sub === "prove" || sub === "hand" || sub === "delete") {
|
|
263
|
+
const args = parse(more, [], ["reason"]);
|
|
264
|
+
const id = need(args.positional[0], "a flake's id (runtime ci flakes)");
|
|
265
|
+
const change = sub === "prove"
|
|
266
|
+
? { prove: true }
|
|
267
|
+
: sub === "hand"
|
|
268
|
+
? { owner: need(args.positional[1], "a GitHub login, or org/team") }
|
|
269
|
+
: { deleteReason: need(flag(args, "reason"), "--reason <why its test is deleted>") };
|
|
270
|
+
const f = await api.flakes.update(id, change);
|
|
271
|
+
print(sub === "prove"
|
|
272
|
+
? `${f.file}: its 100 proof runs come at main's next commit.\n`
|
|
273
|
+
: sub === "hand"
|
|
274
|
+
? `${f.file}: handed to ${ownerOf(f)}.\n`
|
|
275
|
+
: `${f.file}: it ends once main no longer holds the file.\n`, f);
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
const f = await api.flakes.get(need(sub, "a flake's id (runtime ci flakes)"));
|
|
279
|
+
const runs = f.runs
|
|
280
|
+
.slice(0, 20)
|
|
281
|
+
.map((r) => `${r.passed ? "pass" : "FAIL"} ${r.attempt}`)
|
|
282
|
+
.join(", ");
|
|
283
|
+
print([
|
|
284
|
+
`${f.file}: ${f.state}${f.gates ? ` (still gates: ${f.gates.words})` : ""}`,
|
|
285
|
+
` owner ${ownerOf(f)}, due ${when(f.dueAt)}${f.overdue ? " (overdue)" : ""}`,
|
|
286
|
+
` proof passed and failed under key ${f.proof.key.slice(0, 12)} at ${f.proof.commit.slice(0, 9)}`,
|
|
287
|
+
` runs ${runs || "-"}`,
|
|
288
|
+
...f.failures
|
|
289
|
+
.slice(0, 5)
|
|
290
|
+
.map((x) => ` failed ${x.jobId} at ${x.commit.slice(0, 9)}: ${x.message ?? ""} (${me} ci logs ${x.jobId})`),
|
|
291
|
+
...(f.deleteReason ? [` delete ${f.deleteReason}`] : []),
|
|
292
|
+
"",
|
|
293
|
+
].join("\n"), f);
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
case "tests": {
|
|
297
|
+
const args = parse(rest, ["quarantined"], []);
|
|
298
|
+
const repositoryId = need(args.positional[0], "a repository's id (runtime ci repos)");
|
|
299
|
+
const all = await (await api.tests.list(repositoryId, {
|
|
300
|
+
limit: 100,
|
|
301
|
+
...(has(args, "quarantined") ? { quarantined: true } : {}),
|
|
302
|
+
})).toArray(Infinity);
|
|
303
|
+
print(all.length
|
|
304
|
+
? table([
|
|
305
|
+
["FILE", "TOOK", "RECENT", "QUARANTINED"],
|
|
306
|
+
...all.map((t) => [
|
|
307
|
+
t.file,
|
|
308
|
+
`${(t.ms / 1000).toFixed(1)}s`,
|
|
309
|
+
t.runs
|
|
310
|
+
.slice(-5)
|
|
311
|
+
.map((r) => r.outcome)
|
|
312
|
+
.join(" "),
|
|
313
|
+
t.quarantinedSince === null ? "-" : when(t.quarantinedSince),
|
|
314
|
+
]),
|
|
315
|
+
])
|
|
316
|
+
: "No test files yet.", all);
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw unknown("ci command", verb, [
|
|
321
|
+
"connect",
|
|
322
|
+
"repos",
|
|
323
|
+
"disconnect",
|
|
324
|
+
"ls",
|
|
325
|
+
"get",
|
|
326
|
+
"logs",
|
|
327
|
+
"tests",
|
|
328
|
+
"flakes",
|
|
329
|
+
"flake",
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
332
|
+
/** A review's verdict in words: each finding with its place and fix, then
|
|
333
|
+
* the notes, then who reviewed. */
|
|
334
|
+
function reviewed(r) {
|
|
335
|
+
const at = (f) => (f.line > 0 ? `${f.file}:${f.line}` : f.file);
|
|
336
|
+
return [
|
|
337
|
+
`\n${r.verdict === "pass" ? "Passed review" : `Blocked: ${r.findings.length} finding(s)`}. ${r.summary}`,
|
|
338
|
+
...r.findings.map((f) => ` ${f.kind} at ${at(f)}: ${f.what}\n Fix: ${f.fix}`),
|
|
339
|
+
...r.notes.map((n) => ` note at ${at(n)}: ${n.what}`),
|
|
340
|
+
`Reviewed by agent ${r.agent} (${r.model}) on ${r.account}${r.cut ? ", diff cut short" : ""}: session ${r.session}`,
|
|
341
|
+
"",
|
|
342
|
+
].join("\n");
|
|
343
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type Sandbox } from "./sandbox.js";
|
|
2
3
|
import { RuntimeError } from "./errors.js";
|
|
3
4
|
import type { OutOfMemory, Usage } from "./types.js";
|
|
4
5
|
import type { SwitchProvider } from "./products/switching.js";
|
|
@@ -31,6 +32,9 @@ declare const kit: {
|
|
|
31
32
|
table: (rows: string[][]) => string;
|
|
32
33
|
usage: (message: string) => RuntimeError;
|
|
33
34
|
unknown: (what: string, word: string, choices: readonly string[]) => RuntimeError;
|
|
35
|
+
/** `sandboxes exec`'s run and output, for any machine that execs. */
|
|
36
|
+
execute: (machine: Pick<Sandbox, "id" | "exec">, command: string[], args: Args, out: Out, env: NodeJS.ProcessEnv) => Promise<number>;
|
|
37
|
+
copyFile: (from: string, to: string, use: (id: string) => Promise<Pick<Sandbox, "files">>, side: string) => Promise<void>;
|
|
34
38
|
};
|
|
35
39
|
export type CliKit = typeof kit;
|
|
36
40
|
export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out): Promise<number>;
|
|
@@ -46,8 +50,9 @@ export declare function usageCsv(u: Usage): string;
|
|
|
46
50
|
* command: the API's message and hint, on standard error. A bare exit -9 (or
|
|
47
51
|
* 137) said nothing of memory until 9 October 2026. */
|
|
48
52
|
export declare function outOfMemoryLines(memory: OutOfMemory | undefined): string | undefined;
|
|
49
|
-
/** How to move a rival's code across, when one import
|
|
50
|
-
|
|
53
|
+
/** How to move a rival's code across, when one import (or, for Fly's
|
|
54
|
+
* Machines, one address) does it. `from` is the name the caller gave. */
|
|
55
|
+
export declare function dropInLines(provider: SwitchProvider | undefined, from?: string): string[];
|
|
51
56
|
/** A secret's value, from standard input only, so it never sits in shell
|
|
52
57
|
* history or a process list. Piped: everything, less one trailing newline.
|
|
53
58
|
* A terminal: one line, not shown. */
|
|
@@ -62,16 +67,22 @@ export declare function cleanSecret(raw: string): string;
|
|
|
62
67
|
* (ARCHITECTURE.md section 10, "Per-path rules on an egress secret") and says
|
|
63
68
|
* which one it refused. */
|
|
64
69
|
export declare function parseSecretRules(values: string[]): SecretRule[];
|
|
65
|
-
/** `--only-label role=hub,tier=x --only-sandbox <id>`: a
|
|
66
|
-
* hold every pair of any one --only-label gets the
|
|
67
|
-
* --only-sandbox
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
+
/** `--only-label role=hub,tier=x --only-sandbox <id> --only-app <id>`: a
|
|
71
|
+
* sandbox whose labels hold every pair of any one --only-label gets the
|
|
72
|
+
* secret, and so does each --only-sandbox and every copy of each --only-app.
|
|
73
|
+
* `--all-sandboxes`: every sandbox again. Nothing: undefined, so a
|
|
74
|
+
* replacement keeps the scope it has. */
|
|
75
|
+
export declare function parseSecretScope(labels: string[] | undefined, sandboxes: string[] | undefined, all: boolean, apps?: string[]): SecretScope | undefined;
|
|
70
76
|
/** Who gets a secret, in a few words. */
|
|
71
77
|
export declare const scopeText: (only: SecretScope | undefined) => string;
|
|
72
78
|
export declare function secretsCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out,
|
|
73
79
|
/** Tests give the value here; the CLI reads standard input. */
|
|
74
80
|
read?: () => Promise<string>): Promise<number>;
|
|
81
|
+
/** A booking's time on the command line: an ISO time, `now`, or HH:MM on this
|
|
82
|
+
* machine's clock. HH:MM is the next such time at or after `after` (the
|
|
83
|
+
* window's start for `--to`, so an end before the start is the next day, and
|
|
84
|
+
* `now` for `--from`: today, or tomorrow once it has passed). */
|
|
85
|
+
export declare function bookingTime(name: string, value: string, now: number, after?: number): Date;
|
|
75
86
|
export declare function imageCommand(argv: string[], env: NodeJS.ProcessEnv, out: Out,
|
|
76
87
|
/** Tests give the registry secret here; the CLI reads standard input. */
|
|
77
88
|
read?: () => Promise<string>): Promise<number>;
|