@coderook/cli 0.35.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +100 -1
- package/dist/cli/src/actions_command.js +254 -0
- package/dist/cli/src/api.js +36 -3
- package/dist/cli/src/build_command.js +8 -0
- package/dist/cli/src/cli.js +256 -2
- package/dist/cli/src/community_command.js +136 -0
- package/dist/cli/src/config.js +2 -1
- package/dist/cli/src/issue_command.js +186 -0
- package/dist/cli/src/pr_command.js +318 -0
- package/dist/cli/src/private_command.js +65 -0
- package/dist/cli/src/project_commands.js +2 -1
- package/dist/cli/src/runner.js +73 -8
- package/dist/cli/src/service_commands.js +121 -5
- package/dist/cli/src/verify_command.js +135 -0
- package/package.json +1 -1
- package/skills/coderook/SKILL.md +51 -0
|
@@ -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.
|
|
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
|
|
@@ -194,11 +194,56 @@ Tests whenever a line is proposed or saved onto, and its result is a required
|
|
|
194
194
|
check. `--on-save` runs an action on every save, and `--by-hand` only when
|
|
195
195
|
somebody starts it.
|
|
196
196
|
|
|
197
|
+
Every proposal also has a `coderook/malware` check: what the line adds or
|
|
198
|
+
changes is scanned before it can land, and a finding names the file and stops
|
|
199
|
+
the proposal being applied.
|
|
200
|
+
|
|
197
201
|
A **protected** line refuses saves made straight onto it — from `cbx submit`,
|
|
198
202
|
the desktop application and `git push` alike — so the way in is a line of your
|
|
199
203
|
own and a proposal. It also cannot be put back on an earlier save, and a merge
|
|
200
204
|
cannot land on it over a conflict or a failing check.
|
|
201
205
|
|
|
206
|
+
### Pull requests from the terminal
|
|
207
|
+
|
|
208
|
+
```
|
|
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
|
|
218
|
+
```
|
|
219
|
+
|
|
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
|
+
|
|
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
|
+
```
|
|
246
|
+
|
|
202
247
|
## Who can reach a project
|
|
203
248
|
|
|
204
249
|
cbx visibility # what it is now, and what each level means
|
|
@@ -436,6 +481,56 @@ artifacts or with `cbx attach <file>`, so a release made by pushing a tag
|
|
|
436
481
|
simply has none until something adds them — which is what a release without a
|
|
437
482
|
build has always looked like.
|
|
438
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
|
+
|
|
439
534
|
## Releases and app downloads
|
|
440
535
|
|
|
441
536
|
```
|
|
@@ -452,6 +547,10 @@ no need for a second project that only holds releases. `--downloads private`
|
|
|
452
547
|
takes them back. Without a release name it changes an existing release and
|
|
453
548
|
leaves its notes alone.
|
|
454
549
|
|
|
550
|
+
Every attached file is scanned for malware before anybody outside the project
|
|
551
|
+
can download it, usually within a minute; one that fails is never offered, and
|
|
552
|
+
whoever attached it and the owner are told what was found.
|
|
553
|
+
|
|
455
554
|
`cbx releases` marks each one `public`, `downloads public` or `private`, and
|
|
456
555
|
for a public project prints the addresses an app or a script reads, which
|
|
457
556
|
need no account:
|
|
@@ -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
|
+
}
|
package/dist/cli/src/api.js
CHANGED
|
@@ -43,8 +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;
|
|
50
|
+
exports.getPrivatePaths = getPrivatePaths;
|
|
51
|
+
exports.putPrivatePaths = putPrivatePaths;
|
|
52
|
+
exports.callApi = callApi;
|
|
48
53
|
exports.revokeToken = revokeToken;
|
|
49
54
|
exports.deleteProject = deleteProject;
|
|
50
55
|
exports.projectLabels = projectLabels;
|
|
@@ -330,7 +335,10 @@ async function findProject(reference) {
|
|
|
330
335
|
return [];
|
|
331
336
|
throw error;
|
|
332
337
|
});
|
|
333
|
-
const ownersMatch = (project) => project.slug.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();
|
|
334
342
|
if (!owner) {
|
|
335
343
|
const mine = all.find(ownersMatch);
|
|
336
344
|
if (mine)
|
|
@@ -618,12 +626,12 @@ async function versions(repositoryId) {
|
|
|
618
626
|
* — pushing the same tag twice renames the same version to the same thing
|
|
619
627
|
* rather than making a second release.
|
|
620
628
|
*/
|
|
621
|
-
async function markRelease(repositoryId, versionId, name, notes) {
|
|
629
|
+
async function markRelease(repositoryId, versionId, name, notes, options = {}) {
|
|
622
630
|
const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}` +
|
|
623
631
|
`/versions/${encodeURIComponent(versionId)}/release`,
|
|
624
632
|
// `call` serialises the body itself; handing it a string would send a
|
|
625
633
|
// JSON-encoded string where the service expects an object.
|
|
626
|
-
{ method: "PUT", body: { name, ...(notes ? { notes } : {}) } });
|
|
634
|
+
{ method: "PUT", body: { name, ...(notes ? { notes } : {}), ...options } });
|
|
627
635
|
return {
|
|
628
636
|
name: String(body.release?.name ?? name),
|
|
629
637
|
versionId: String(body.release?.versionId ?? versionId),
|
|
@@ -790,6 +798,14 @@ async function runs(repositoryId, filter = {}) {
|
|
|
790
798
|
};
|
|
791
799
|
});
|
|
792
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
|
+
}
|
|
793
809
|
/** Everything a run printed, in the order it printed it. */
|
|
794
810
|
async function runLogs(repositoryId, runId) {
|
|
795
811
|
const body = await call(`/v1/repositories/${encodeURIComponent(repositoryId)}/runs/` +
|
|
@@ -806,6 +822,23 @@ async function tokens() {
|
|
|
806
822
|
createdAt: String(row.createdAt ?? ""),
|
|
807
823
|
}));
|
|
808
824
|
}
|
|
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 ?? [];
|
|
829
|
+
}
|
|
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`, {
|
|
833
|
+
method: "PUT",
|
|
834
|
+
body: { patterns },
|
|
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);
|
|
841
|
+
}
|
|
809
842
|
async function revokeToken(tokenId) {
|
|
810
843
|
await call(`/v1/account/tokens/${encodeURIComponent(tokenId)}`, {
|
|
811
844
|
method: "DELETE",
|
|
@@ -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));
|