@coderook/cli 0.36.0 → 0.37.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.
@@ -2,7 +2,7 @@
2
2
  "name": "coderook",
3
3
  "displayName": "CodeRook",
4
4
  "description": "Save, browse and restore whole-snapshot versions of a project on CodeRook, with cbx or plain git, from Claude Code.",
5
- "version": "0.36.0",
5
+ "version": "0.37.0",
6
6
  "author": {
7
7
  "name": "ACCA Gaming Productions",
8
8
  "url": "https://coderook.com"
package/README.md CHANGED
@@ -169,7 +169,7 @@ Once a folder has a history, `status`, `diff`, `log`, `switch`, `merge`,
169
169
  unsaved change. `--force` discards those changes. Files the history has never
170
170
  held are never touched.
171
171
 
172
- ## Proposals
172
+ ## Proposals and pull requests
173
173
 
174
174
  A line gets back to main by being proposed. The proposal is checked and
175
175
  reviewed before it lands, and it follows the line: saving onto it afterwards
@@ -198,33 +198,51 @@ Every proposal also has a `coderook/malware` check: what the line adds or
198
198
  changes is scanned before it can land, and a finding names the file and stops
199
199
  the proposal being applied.
200
200
 
201
- ### Reviewing proposals on GitHub
201
+ A **protected** line refuses saves made straight onto it — from `cbx submit`,
202
+ the desktop application and `git push` alike — so the way in is a line of your
203
+ own and a proposal. It also cannot be put back on an earlier save, and a merge
204
+ cannot land on it over a conflict or a failing check.
202
205
 
203
- CodeRabbit, Blacksmith and most CI only connect to GitHub, so `cbx github`
204
- keeps a GitHub repository as a mirror: each open proposal becomes a pull
205
- request there, whatever is installed on it reviews and builds it, and the
206
- results come back to the proposal as checks (`github/<check>`,
207
- `review/<reviewer>`).
206
+ ### Pull requests from the terminal
208
207
 
209
208
  ```
210
- cbx github setup my-name/my-game-mirror token, secret, workflow
211
- cbx submit save the workflow into the project
212
- cbx github sync --repo my-name/my-game-mirror mirror once from here
209
+ cbx pr list [--all] [--mine] [--label bug] this project's pull requests
210
+ cbx pr view M-3 description, reviews, checks, conversation, what blocks it
211
+ cbx pr files M-3 what it changes
212
+ cbx pr comment M-3 -m "…" [--line src/app.ts:42]
213
+ cbx pr approve M-3 or: cbx pr request-changes M-3 -m "…"
214
+ cbx pr ask M-3 sam,priya ask people to review it
215
+ cbx pr label M-3 bug,ui
216
+ cbx pr ready M-3 or: draft, title, retarget, close, reopen
217
+ cbx pr merge M-3
213
218
  ```
214
219
 
215
- `setup` makes a token that reaches this project only and can only read it and
216
- report checks, stores it as the repository's `CODEROOK_TOKEN` secret (with the
217
- GitHub CLI; otherwise it prints it once), and writes
218
- `.github/workflows/coderook-bridge.yml`, which then runs on GitHub every ten
219
- minutes. Add `CODEROOK_BRIDGE_PUSH`, a fine-grained GitHub token with Contents
220
- and Pull requests read and write: GitHub's own workflow token cannot start
221
- other workflows, so CI would never run. Results are advice unless named in
222
- `CODEROOK_REQUIRED` (or `--required`). Use a repository nothing else pushes to.
220
+ An approval stops counting when new saves land on the line, and a draft
221
+ cannot be merged until it is marked ready. The website's Pull requests tab
222
+ does the same, with comments on lines and suggested changes.
223
223
 
224
- A **protected** line refuses saves made straight onto it — from `cbx submit`,
225
- the desktop application and `git push` alike — so the way in is a line of your
226
- own and a proposal. It also cannot be put back on an earlier save, and a merge
227
- cannot land on it over a conflict or a failing check.
224
+ ## Keeping files private
225
+
226
+ ```
227
+ cbx private add "design/" "*.psd" a folder anywhere, a file type
228
+ cbx private list
229
+ cbx private remove "*.psd"
230
+ ```
231
+
232
+ In a public project, everybody outside it is served every version without
233
+ those files: the file list, zips, git clone, Pages and search leave them out.
234
+ Members see everything. Only the owner or an admin can change the list.
235
+
236
+ ### Signed releases and dependencies
237
+
238
+ ```
239
+ cbx release v2.0 --draft --generate-notes nothing public until: cbx release publish --version 41
240
+ cbx release v2.1.0-beta.1 --prerelease --channel beta
241
+ cbx verify sam/editor v2.0.0 ./setup.exe check a download against the signed manifest
242
+ cbx deps known vulnerabilities in the lockfiles (OSV)
243
+ cbx pr merge M-3 --squash
244
+ cbx pr auto-merge M-3
245
+ ```
228
246
 
229
247
  ## Who can reach a project
230
248
 
@@ -463,6 +481,56 @@ artifacts or with `cbx attach <file>`, so a release made by pushing a tag
463
481
  simply has none until something adds them — which is what a release without a
464
482
  build has always looked like.
465
483
 
484
+ ## Actions
485
+
486
+ ```
487
+ cbx runs what has run, and how it went
488
+ cbx logs 12 --follow watch a run as it happens
489
+ cbx cancel 12 stop a run that is waiting or working
490
+ cbx runner run this project's work on this machine
491
+ ```
492
+
493
+ CodeRook's own runners give every account 1,000 minutes a month; your own
494
+ machine has no limit. A run nobody picks up within a day is ended with the
495
+ reason.
496
+
497
+ ### More from Actions
498
+
499
+ ```
500
+ cbx rerun 12 run it again, against the same save
501
+ cbx approve 14 a run waiting for approval
502
+ cbx actions check check .rook/workflows/*.yml before saving
503
+ cbx actions set Nightly --schedule "0 3 * * *" --timeout 30
504
+ cbx actions set Deploy --approval on --concurrency deploy --cancel-old
505
+ cbx secret set DEPLOY_TOKEN asked for at a hidden prompt; never shown again
506
+ cbx secret list
507
+ ```
508
+
509
+ Every run gets `CODEROOK_TOKEN`, `CODEROOK_API` and `CODEROOK_PROJECT`, so
510
+ `cbx issue comment`, `cbx pr label` or `cbx release` inside a workflow need no
511
+ setup. Secrets reach only runs started by people who can change the project.
512
+
513
+ ## Issues
514
+
515
+ ```
516
+ cbx issue view 12
517
+ cbx issue comment 12 -m "Fixed in v41"
518
+ cbx issue close 12 --duplicate-of 9 or --reason not-planned
519
+ cbx issue label 12 bug,ui --remove to take them off
520
+ cbx issue assign 12 sam or none
521
+ cbx issue labels --starter
522
+ ```
523
+
524
+ ## Forks and security reports
525
+
526
+ ```
527
+ cbx fork sam/editor your own copy
528
+ cbx fork offer fix-export --title "…" offer a line back as a pull request
529
+ cbx fork sync the original's latest, on "upstream"
530
+ cbx security report sam/editor --title "…" --severity high
531
+ cbx security list | view S-3 | reply S-3 -m "…" | mark S-3 fixed
532
+ ```
533
+
466
534
  ## Releases and app downloads
467
535
 
468
536
  ```
@@ -0,0 +1,254 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.commandSecret = commandSecret;
7
+ exports.commandCheckWorkflows = commandCheckWorkflows;
8
+ exports.commandSetWorkflow = commandSetWorkflow;
9
+ exports.commandApprove = commandApprove;
10
+ /**
11
+ * The newer parts of Actions from the terminal:
12
+ *
13
+ * cbx secret list | set NAME | remove NAME a project's secrets
14
+ * cbx actions check [files…] check workflow files before saving them
15
+ * cbx actions set <action> --schedule … --timeout … --approval on|off --concurrency …
16
+ * cbx approve <run> let a run that waits for approval start
17
+ *
18
+ * A secret's value is never printed and never taken as an argument (it would
19
+ * sit in shell history): it is read from standard input, from another
20
+ * environment variable with --from-env, or typed at a hidden prompt.
21
+ */
22
+ const promises_1 = require("node:fs/promises");
23
+ const node_path_1 = __importDefault(require("node:path"));
24
+ const api_js_1 = require("./api.js");
25
+ const colour_js_1 = require("./colour.js");
26
+ const project_commands_js_1 = require("./project_commands.js");
27
+ function projectFlag(parsed) {
28
+ const named = parsed.flags.get("project");
29
+ return typeof named === "string" ? named : undefined;
30
+ }
31
+ /** Read a secret without it appearing anywhere: piped in, from another variable, or typed hidden. */
32
+ async function readSecretValue(name, parsed) {
33
+ const fromEnv = parsed.flags.get("from-env");
34
+ if (typeof fromEnv === "string" && fromEnv.trim()) {
35
+ const value = process.env[fromEnv.trim()];
36
+ if (!value) {
37
+ console.error((0, colour_js_1.red)(`The environment variable ${fromEnv.trim()} is empty or not set.`));
38
+ return null;
39
+ }
40
+ return value;
41
+ }
42
+ if (!process.stdin.isTTY) {
43
+ const chunks = [];
44
+ for await (const chunk of process.stdin)
45
+ chunks.push(Buffer.from(chunk));
46
+ return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
47
+ }
48
+ process.stdout.write(`Value for ${name} (hidden; Enter to finish): `);
49
+ return new Promise((resolve) => {
50
+ const input = process.stdin;
51
+ let value = "";
52
+ input.setRawMode(true);
53
+ input.resume();
54
+ input.setEncoding("utf8");
55
+ const done = (result) => {
56
+ input.setRawMode(false);
57
+ input.pause();
58
+ input.removeListener("data", onData);
59
+ process.stdout.write("\n");
60
+ resolve(result);
61
+ };
62
+ const onData = (key) => {
63
+ for (const character of key) {
64
+ if (character === "\r" || character === "\n")
65
+ return done(value);
66
+ if (character === "\u0003")
67
+ return done(null); // Ctrl+C
68
+ if (character === "\u007f" || character === "\b")
69
+ value = value.slice(0, -1);
70
+ else
71
+ value += character;
72
+ }
73
+ };
74
+ input.on("data", onData);
75
+ });
76
+ }
77
+ async function commandSecret(parsed) {
78
+ const sub = parsed.positional[0] ?? "list";
79
+ const project = await (0, project_commands_js_1.resolveProject)(projectFlag(parsed));
80
+ if (!project)
81
+ return 1;
82
+ const base = `/v1/repositories/${encodeURIComponent(project.id)}/secrets`;
83
+ if (sub === "list") {
84
+ const { secrets } = await (0, api_js_1.callApi)(base);
85
+ if (!secrets.length) {
86
+ console.log(`${(0, colour_js_1.bold)(project.slug)} has no secrets.`);
87
+ console.log((0, colour_js_1.dim)(" Add one: cbx secret set DEPLOY_TOKEN (you are asked for the value; it is never shown)"));
88
+ return 0;
89
+ }
90
+ console.log((0, colour_js_1.bold)(`Secrets in ${project.slug}`) + (0, colour_js_1.dim)(" — values are never shown"));
91
+ for (const secret of secrets) {
92
+ console.log(` ${(0, colour_js_1.accent)(secret.name.padEnd(28))} ${(0, colour_js_1.dim)(`set ${new Date(secret.updatedAt).toLocaleString()}${secret.updatedBy ? ` by ${secret.updatedBy}` : ""}`)}`);
93
+ }
94
+ return 0;
95
+ }
96
+ const name = (parsed.positional[1] ?? "").trim().toUpperCase();
97
+ if (!name) {
98
+ console.error((0, colour_js_1.red)(`Name the secret: cbx secret ${sub} DEPLOY_TOKEN`));
99
+ return 1;
100
+ }
101
+ if (sub === "set") {
102
+ const value = await readSecretValue(name, parsed);
103
+ if (value === null)
104
+ return 1;
105
+ if (!value) {
106
+ console.error((0, colour_js_1.red)("Nothing was entered, so nothing was saved."));
107
+ return 1;
108
+ }
109
+ await (0, api_js_1.callApi)(`${base}/${encodeURIComponent(name)}`, { method: "PUT", body: { value } });
110
+ console.log(`${(0, colour_js_1.green)("Saved.")} Runs started by people who can change ${project.slug} get ${(0, colour_js_1.accent)(name)} as an environment variable.`);
111
+ console.log((0, colour_js_1.dim)(" It is replaced by *** in run logs. A run from outside the project never gets it."));
112
+ return 0;
113
+ }
114
+ if (sub === "remove") {
115
+ await (0, api_js_1.callApi)(`${base}/${encodeURIComponent(name)}`, { method: "DELETE" });
116
+ console.log(`${(0, colour_js_1.green)("Removed")} ${name}.`);
117
+ return 0;
118
+ }
119
+ console.error((0, colour_js_1.red)(`cbx secret has no "${sub}". `) + "Try: list, set, remove");
120
+ return 1;
121
+ }
122
+ const EVENT_WORDS = {
123
+ manual: "by hand",
124
+ version_saved: "on save",
125
+ merge_opened: "on pull requests",
126
+ merge_applied: "when merged",
127
+ issue_opened: "issue opened",
128
+ issue_labeled: "issue labelled",
129
+ issue_commented: "issue comment",
130
+ release_published: "on release",
131
+ schedule: "on a schedule",
132
+ };
133
+ /** `cbx actions check [files…]`: what each file would make, and what is wrong with it. */
134
+ async function commandCheckWorkflows(parsed) {
135
+ let files = parsed.positional.slice(1);
136
+ if (!files.length) {
137
+ const folder = node_path_1.default.join(process.cwd(), ".rook", "workflows");
138
+ try {
139
+ files = (await (0, promises_1.readdir)(folder)).filter((name) => /\.ya?ml$/.test(name)).map((name) => node_path_1.default.join(folder, name));
140
+ }
141
+ catch {
142
+ files = [];
143
+ }
144
+ }
145
+ if (!files.length) {
146
+ console.log("No workflow files here. They live in .rook/workflows/*.yml");
147
+ console.log((0, colour_js_1.dim)(" See: https://coderook.com/docs#workflow-files"));
148
+ return 0;
149
+ }
150
+ const project = await (0, project_commands_js_1.resolveProject)(projectFlag(parsed));
151
+ if (!project)
152
+ return 1;
153
+ let failed = false;
154
+ for (const file of files) {
155
+ const relative = node_path_1.default.relative(process.cwd(), file).split(node_path_1.default.sep).join("/");
156
+ const text = await (0, promises_1.readFile)(file, "utf8");
157
+ const answer = await (0, api_js_1.callApi)(`/v1/repositories/${encodeURIComponent(project.id)}/workflow-files/check`, {
158
+ method: "POST",
159
+ body: { path: relative.startsWith(".rook/") ? relative : `.rook/workflows/${node_path_1.default.basename(file)}`, text },
160
+ });
161
+ console.log(answer.ok ? `${(0, colour_js_1.green)("✓")} ${(0, colour_js_1.bold)(relative)}` : `${(0, colour_js_1.red)("×")} ${(0, colour_js_1.bold)(relative)}`);
162
+ for (const error of answer.errors)
163
+ console.log(` ${(0, colour_js_1.red)(error)}`);
164
+ for (const workflow of answer.workflows) {
165
+ const details = [
166
+ workflow.on.map((event) => EVENT_WORDS[event] ?? event).join(", "),
167
+ workflow.schedule ? `"${workflow.schedule}" UTC` : null,
168
+ workflow.runsOn !== "hosted" ? `on ${workflow.runsOn}` : null,
169
+ workflow.matrix ? `${Object.values(workflow.matrix).reduce((all, values) => all * values.length, 1)} runs (matrix)` : null,
170
+ workflow.needs.length ? `after ${workflow.needs.join(", ")}` : null,
171
+ workflow.needsApproval ? "needs approval" : null,
172
+ workflow.timeoutMinutes ? `${workflow.timeoutMinutes} min limit` : null,
173
+ ].filter(Boolean);
174
+ console.log(` ${(0, colour_js_1.accent)(workflow.name)} ${(0, colour_js_1.dim)(details.join(" · "))}`);
175
+ }
176
+ failed ||= !answer.ok;
177
+ }
178
+ if (!failed)
179
+ console.log((0, colour_js_1.dim)("\nSave them to the project's main line and they take effect."));
180
+ return failed ? 1 : 0;
181
+ }
182
+ /** `cbx actions set <action> …`: the options a workflow made on the website can have. */
183
+ async function commandSetWorkflow(parsed) {
184
+ const named = (parsed.positional[1] ?? "").trim();
185
+ if (!named) {
186
+ console.error((0, colour_js_1.red)('Which action? cbx actions set "Nightly build" --schedule "0 3 * * *"'));
187
+ return 1;
188
+ }
189
+ const project = await (0, project_commands_js_1.resolveProject)(projectFlag(parsed));
190
+ if (!project)
191
+ return 1;
192
+ const target = (await (0, api_js_1.workflows)(project.id)).find((one) => one.name.toLowerCase() === named.toLowerCase());
193
+ if (!target) {
194
+ console.error((0, colour_js_1.red)(`${project.name} has no action called ${named}.`));
195
+ return 1;
196
+ }
197
+ const change = {};
198
+ const schedule = parsed.flags.get("schedule");
199
+ if (typeof schedule === "string") {
200
+ change.schedule = schedule === "off" ? null : schedule;
201
+ if (schedule !== "off")
202
+ change.trigger = "schedule";
203
+ }
204
+ const timeout = parsed.flags.get("timeout");
205
+ if (typeof timeout === "string")
206
+ change.timeoutMinutes = timeout === "off" ? null : Number(timeout);
207
+ const approval = parsed.flags.get("approval");
208
+ if (typeof approval === "string")
209
+ change.needsApproval = approval === "on";
210
+ const concurrency = parsed.flags.get("concurrency");
211
+ if (typeof concurrency === "string")
212
+ change.concurrencyGroup = concurrency === "off" ? null : concurrency;
213
+ if (parsed.flags.has("cancel-old"))
214
+ change.cancelInProgress = true;
215
+ if (parsed.flags.has("keep-old"))
216
+ change.cancelInProgress = false;
217
+ if (!Object.keys(change).length) {
218
+ console.error((0, colour_js_1.red)("Nothing to change. ") + (0, colour_js_1.dim)("Options: --schedule, --timeout, --approval on|off, --concurrency, --cancel-old"));
219
+ return 1;
220
+ }
221
+ await (0, api_js_1.changeWorkflow)(project.id, target.id, change);
222
+ console.log(`${(0, colour_js_1.green)("Saved.")} ${target.name}:`);
223
+ if ("schedule" in change)
224
+ console.log(` ${change.schedule ? `runs on "${change.schedule}" (UTC)` : "no schedule"}`);
225
+ if ("timeoutMinutes" in change)
226
+ console.log(` ${change.timeoutMinutes ? `stopped after ${change.timeoutMinutes} minutes` : "no time limit of its own"}`);
227
+ if ("needsApproval" in change)
228
+ console.log(` ${change.needsApproval ? "each run waits for approval (cbx approve)" : "runs start without approval"}`);
229
+ if ("concurrencyGroup" in change)
230
+ console.log(` ${change.concurrencyGroup ? `one at a time in "${change.concurrencyGroup}"` : "no concurrency group"}`);
231
+ if ("cancelInProgress" in change)
232
+ console.log(` ${change.cancelInProgress ? "a new run cancels the group's older ones" : "a new run waits its turn"}`);
233
+ return 0;
234
+ }
235
+ /** `cbx approve <run>`: start a run that is waiting for approval. */
236
+ async function commandApprove(parsed) {
237
+ const asked = parsed.positional[0];
238
+ const number = Number((asked ?? "").replace(/^#/, ""));
239
+ if (!asked || !Number.isInteger(number) || number < 1) {
240
+ console.error((0, colour_js_1.red)("Which run? Try: cbx approve 12"));
241
+ return 1;
242
+ }
243
+ const project = await (0, project_commands_js_1.resolveProject)(parsed.positional[1] ?? projectFlag(parsed));
244
+ if (!project)
245
+ return 1;
246
+ const run = (await (0, api_js_1.runs)(project.id)).find((one) => one.number === number);
247
+ if (!run) {
248
+ console.error((0, colour_js_1.red)(`${project.name} has no run #${number}.`));
249
+ return 1;
250
+ }
251
+ await (0, api_js_1.callApi)(`/v1/repositories/${encodeURIComponent(project.id)}/runs/${encodeURIComponent(run.id)}/approve`, { method: "POST" });
252
+ console.log(`${(0, colour_js_1.accent)(`#${number}`)} approved. It is queued and starts when a runner takes it.`);
253
+ return 0;
254
+ }
@@ -43,10 +43,13 @@ exports.setWatch = setWatch;
43
43
  exports.workflows = workflows;
44
44
  exports.changeWorkflow = changeWorkflow;
45
45
  exports.runs = runs;
46
+ exports.rerunRun = rerunRun;
47
+ exports.cancelRun = cancelRun;
46
48
  exports.runLogs = runLogs;
47
49
  exports.tokens = tokens;
48
- exports.createToken = createToken;
49
- exports.reportMergeCheck = reportMergeCheck;
50
+ exports.getPrivatePaths = getPrivatePaths;
51
+ exports.putPrivatePaths = putPrivatePaths;
52
+ exports.callApi = callApi;
50
53
  exports.revokeToken = revokeToken;
51
54
  exports.deleteProject = deleteProject;
52
55
  exports.projectLabels = projectLabels;
@@ -332,7 +335,10 @@ async function findProject(reference) {
332
335
  return [];
333
336
  throw error;
334
337
  });
335
- const ownersMatch = (project) => project.slug.toLowerCase() === bare || project.name.toLowerCase() === bare;
338
+ const ownersMatch = (project) => project.slug.toLowerCase() === bare ||
339
+ project.name.toLowerCase() === bare ||
340
+ /* By id too: a run is handed its project's id as CODEROOK_PROJECT. */
341
+ project.id.toLowerCase() === trimmed.toLowerCase();
336
342
  if (!owner) {
337
343
  const mine = all.find(ownersMatch);
338
344
  if (mine)
@@ -620,12 +626,12 @@ async function versions(repositoryId) {
620
626
  * — pushing the same tag twice renames the same version to the same thing
621
627
  * rather than making a second release.
622
628
  */
623
- async function markRelease(repositoryId, versionId, name, notes) {
629
+ async function markRelease(repositoryId, versionId, name, notes, options = {}) {
624
630
  const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}` +
625
631
  `/versions/${encodeURIComponent(versionId)}/release`,
626
632
  // `call` serialises the body itself; handing it a string would send a
627
633
  // JSON-encoded string where the service expects an object.
628
- { method: "PUT", body: { name, ...(notes ? { notes } : {}) } });
634
+ { method: "PUT", body: { name, ...(notes ? { notes } : {}), ...options } });
629
635
  return {
630
636
  name: String(body.release?.name ?? name),
631
637
  versionId: String(body.release?.versionId ?? versionId),
@@ -792,6 +798,14 @@ async function runs(repositoryId, filter = {}) {
792
798
  };
793
799
  });
794
800
  }
801
+ /** Stop a run that is waiting or working. */
802
+ /** Start a finished run again: same action, same save, same pull request. */
803
+ async function rerunRun(repositoryId, runId) {
804
+ return call(`/v1/repositories/${encodeURIComponent(repositoryId)}/runs/${encodeURIComponent(runId)}/rerun`, { method: "POST" });
805
+ }
806
+ async function cancelRun(repositoryId, runId) {
807
+ await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
808
+ }
795
809
  /** Everything a run printed, in the order it printed it. */
796
810
  async function runLogs(repositoryId, runId) {
797
811
  const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/runs/` +
@@ -808,19 +822,22 @@ async function tokens() {
808
822
  createdAt: String(row.createdAt ?? ""),
809
823
  }));
810
824
  }
811
- /** Make a token. Limited to one project when `repositoryId` is given; shown once. */
812
- async function createToken(input) {
813
- return call("/v1/account/tokens", {
814
- method: "POST",
815
- body: input,
816
- });
825
+ /** The project's private paths: patterns only its own people see (members only). */
826
+ async function getPrivatePaths(repositoryId) {
827
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/private-paths`);
828
+ return body.patterns ?? [];
817
829
  }
818
- /** Say how a check on a proposal went; the same name again replaces it. */
819
- async function reportMergeCheck(mergeTrackId, name, input) {
820
- await call(`/v1/merge-tracks/${mergeTrackId}/checks/${encodeURIComponent(name)}`, {
830
+ /** Replace them. The project's owner or an admin only. */
831
+ async function putPrivatePaths(repositoryId, patterns) {
832
+ const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/private-paths`, {
821
833
  method: "PUT",
822
- body: input,
834
+ body: { patterns },
823
835
  });
836
+ return body.patterns ?? [];
837
+ }
838
+ /** A signed-in request to any route, for commands with many of them (pr_command.ts). */
839
+ async function callApi(route, options = {}) {
840
+ return call(route, options);
824
841
  }
825
842
  async function revokeToken(tokenId) {
826
843
  await call(`/v1/account/tokens/${encodeURIComponent(tokenId)}`, {
@@ -832,6 +832,14 @@ async function commandBuild(parsed) {
832
832
  const host = thisHost();
833
833
  console.log(dim(`Checking what this ${host.os} ${host.arch} machine can build with…`));
834
834
  const tools = await detectTools(host);
835
+ /*
836
+ Docker only when asked for. Every target is reached natively, by
837
+ cross-compiling or through WSL where it can be; a container is the last
838
+ resort somebody chooses with --docker, never one picked for them because
839
+ Docker Desktop happened to be running.
840
+ */
841
+ if (!parsed.flags.has("docker"))
842
+ tools.docker = false;
835
843
  const plan = planBuild(project, host, tools, targets);
836
844
  console.log(`${bold(project.name)} ${dim(`(${project.preset})`)}`);
837
845
  const width = Math.max(...plan.map((one) => one.target.length));