@coderook/cli 0.36.0 → 0.38.1
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 +90 -22
- package/dist/cli/src/actions_command.js +254 -0
- package/dist/cli/src/api.js +32 -15
- package/dist/cli/src/build_command.js +8 -0
- package/dist/cli/src/cli.js +297 -44
- 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 +108 -9
- package/dist/cli/src/service_commands.js +121 -5
- package/dist/cli/src/verify_command.js +135 -0
- package/dist/cli/src/workspace_command.js +198 -0
- package/package.json +66 -66
- package/skills/coderook/SKILL.md +413 -371
- package/dist/cli/src/github_bridge.js +0 -403
|
@@ -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.38.1",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
211
|
-
cbx
|
|
212
|
-
cbx
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
}
|
package/dist/cli/src/api.js
CHANGED
|
@@ -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.
|
|
49
|
-
exports.
|
|
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 ||
|
|
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
|
-
/**
|
|
812
|
-
async function
|
|
813
|
-
|
|
814
|
-
|
|
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
|
-
/**
|
|
819
|
-
async function
|
|
820
|
-
await call(`/v1/
|
|
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:
|
|
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));
|