@nanocollective/roster 0.1.0-alpha.53 → 0.1.0-alpha.54
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/dist/cli.js
CHANGED
|
@@ -5728,6 +5728,17 @@ async function act(req) {
|
|
|
5728
5728
|
await run3("gh", ["issue", "reopen", n, "--repo", repo], { encoding: "utf8" });
|
|
5729
5729
|
return { ok: true, action };
|
|
5730
5730
|
}
|
|
5731
|
+
if (action === "approve") {
|
|
5732
|
+
const args = ["pr", "review", n, "--repo", repo, "--approve"];
|
|
5733
|
+
if (req.body?.trim()) args.push("--body", req.body.trim());
|
|
5734
|
+
try {
|
|
5735
|
+
await run3("gh", args, { encoding: "utf8" });
|
|
5736
|
+
} catch (err) {
|
|
5737
|
+
const line = errText(err).split("\n").map((l) => l.trim()).find((l) => l && !l.startsWith("Command failed"));
|
|
5738
|
+
throw new Error(`Not approved: ${line ?? "GitHub refused it without saying why."}`);
|
|
5739
|
+
}
|
|
5740
|
+
return { ok: true, action };
|
|
5741
|
+
}
|
|
5731
5742
|
if (action === "merge") {
|
|
5732
5743
|
const how = req.mergeMethod ?? await mergeMethodFor(repo);
|
|
5733
5744
|
const flag = MERGE_FLAG[how];
|
|
@@ -5787,6 +5798,10 @@ function whyNotMerged(ghError, pr) {
|
|
|
5787
5798
|
return `Not merged: checks failed (${list(failing)}).`;
|
|
5788
5799
|
if (pr?.mergeStateStatus === "BLOCKED" && running.length)
|
|
5789
5800
|
return `Not merged: checks are still running (${list(running)}). Merge again once they pass.`;
|
|
5801
|
+
if (pr?.mergeStateStatus === "BLOCKED" && pr.reviewDecision === "REVIEW_REQUIRED")
|
|
5802
|
+
return "Not merged: the branch rules need an approving review first. Press Approve, then Merge.";
|
|
5803
|
+
if (pr?.mergeStateStatus === "BLOCKED" && pr.reviewDecision === "CHANGES_REQUESTED")
|
|
5804
|
+
return "Not merged: a reviewer has asked for changes. Open it to see what.";
|
|
5790
5805
|
if (pr?.mergeStateStatus === "BLOCKED")
|
|
5791
5806
|
return "Not merged: the branch rules require something first, such as a review. Open it in GitHub to see what.";
|
|
5792
5807
|
if (pr?.mergeStateStatus === "BEHIND")
|
|
@@ -5804,7 +5819,15 @@ async function prState(repo, n) {
|
|
|
5804
5819
|
try {
|
|
5805
5820
|
const { stdout } = await run3(
|
|
5806
5821
|
"gh",
|
|
5807
|
-
[
|
|
5822
|
+
[
|
|
5823
|
+
"pr",
|
|
5824
|
+
"view",
|
|
5825
|
+
n,
|
|
5826
|
+
"--repo",
|
|
5827
|
+
repo,
|
|
5828
|
+
"--json",
|
|
5829
|
+
"mergeStateStatus,statusCheckRollup,reviewDecision"
|
|
5830
|
+
],
|
|
5808
5831
|
{ encoding: "utf8" }
|
|
5809
5832
|
);
|
|
5810
5833
|
return JSON.parse(stdout);
|
|
@@ -6020,7 +6043,7 @@ var LIGHT = `number title url state createdAt updatedAt closedAt body
|
|
|
6020
6043
|
closer: timelineItems(last:1, itemTypes:[CLOSED_EVENT]) {
|
|
6021
6044
|
nodes { ... on ClosedEvent { actor { login } } }
|
|
6022
6045
|
}`;
|
|
6023
|
-
var LIGHT_PR = `${LIGHT} isDraft mergeable
|
|
6046
|
+
var LIGHT_PR = `${LIGHT} isDraft mergeable reviewDecision
|
|
6024
6047
|
commits(last:1) { nodes { commit { statusCheckRollup { state } } } }`;
|
|
6025
6048
|
var QUERY = `
|
|
6026
6049
|
query($owner:String!, $name:String!) {
|
|
@@ -6053,7 +6076,7 @@ query($owner:String!, $name:String!, $number:Int!) {
|
|
|
6053
6076
|
timelineItems(last:100, itemTypes:${ISSUE_TYPES}) { nodes { ${TIMELINE_COMMON} } }
|
|
6054
6077
|
}
|
|
6055
6078
|
... on PullRequest {
|
|
6056
|
-
number title body url state createdAt updatedAt isDraft mergeable ${ROW_EXTRAS}
|
|
6079
|
+
number title body url state createdAt updatedAt isDraft mergeable reviewDecision ${ROW_EXTRAS}
|
|
6057
6080
|
author { login }
|
|
6058
6081
|
${REACTIONS}
|
|
6059
6082
|
labels(first:12) { nodes { name } }
|
|
@@ -6079,6 +6102,7 @@ async function fetchInbox(repos) {
|
|
|
6079
6102
|
item.draft = n.isDraft;
|
|
6080
6103
|
item.checks = rollup(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state);
|
|
6081
6104
|
item.mergeable = n.mergeable;
|
|
6105
|
+
if (n.reviewDecision) item.reviewDecision = n.reviewDecision;
|
|
6082
6106
|
items.push(item);
|
|
6083
6107
|
}
|
|
6084
6108
|
const cutoff = Date.now() - CLOSED_DAYS * 864e5;
|
|
@@ -6293,6 +6317,7 @@ async function fetchThread(repo, number, kind) {
|
|
|
6293
6317
|
item.draft = n.isDraft;
|
|
6294
6318
|
item.checks = rollup(n.commits?.nodes?.[0]?.commit?.statusCheckRollup?.state);
|
|
6295
6319
|
item.mergeable = n.mergeable;
|
|
6320
|
+
if (n.reviewDecision) item.reviewDecision = n.reviewDecision;
|
|
6296
6321
|
}
|
|
6297
6322
|
return item;
|
|
6298
6323
|
}
|
package/docs/portal.md
CHANGED
|
@@ -96,6 +96,9 @@ Where the portal opens. An Ask box at the top, then these, in order.
|
|
|
96
96
|
- a `review` has **Approve**.
|
|
97
97
|
- a `chore` has **Done**, which closes it.
|
|
98
98
|
- a pull request has **Merge**, unless its checks fail, are still running, or it conflicts.
|
|
99
|
+
Where the branch rules require an approving review, it also has **Approve**, and Merge waits
|
|
100
|
+
until you have pressed it. Approving is your review, given on purpose; Merge never gives it
|
|
101
|
+
for you.
|
|
99
102
|
|
|
100
103
|
Answers go out as you, with the staff member's `@handle`, so they act on them straight away.
|
|
101
104
|
Once you have had the last word, the card leaves Needs you for Your requests, since it is
|
package/package.json
CHANGED
|
@@ -357,6 +357,11 @@ jobs:
|
|
|
357
357
|
PUBLIC_TOKEN: ${{ steps.public.outputs.token }}
|
|
358
358
|
with:
|
|
359
359
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN || secrets.AGENT_TOKEN }}
|
|
360
|
+
# The action refuses to run for a bot unless it is named here. A peer's ask is opened
|
|
361
|
+
# by that peer's App, so on a peer run, and only then, the one bot that opened it is
|
|
362
|
+
# allowed. The caller has already checked it is an App, not this staff member's own,
|
|
363
|
+
# and that the issue carries a from- label. Every other run allows no bots.
|
|
364
|
+
allowed_bots: ${{ inputs.trigger == 'peer' && github.actor || '' }}
|
|
360
365
|
# These repos are private and single-user, so the usual reason to hide a run's output does
|
|
361
366
|
# not apply, and hiding it turns any failure into "is_error: true" with no message.
|
|
362
367
|
# Registered secrets are still masked by the runner.
|
|
@@ -209,8 +209,31 @@ function needCard(e) {
|
|
|
209
209
|
actions.append(button("Done", true, () => done(e, note, actions)));
|
|
210
210
|
} else if (kind === "merge") {
|
|
211
211
|
const blocked = item.checks === "failing" || item.mergeable === "CONFLICTING";
|
|
212
|
-
const
|
|
213
|
-
merge
|
|
212
|
+
const needsReview = item.reviewDecision === "REVIEW_REQUIRED";
|
|
213
|
+
const merge = button("Merge", !needsReview, () => merge_(e, note, actions));
|
|
214
|
+
merge.disabled = blocked || item.checks === "pending" || needsReview;
|
|
215
|
+
/* The branch rules want an approving review. Approve is the person's review, given on
|
|
216
|
+
purpose; Merge waits for it rather than approving on their behalf. */
|
|
217
|
+
if (needsReview) {
|
|
218
|
+
const approve = button("Approve", true, async () => {
|
|
219
|
+
approve.disabled = true;
|
|
220
|
+
note.className = "meta";
|
|
221
|
+
note.textContent = "Approving…";
|
|
222
|
+
try {
|
|
223
|
+
await post({ action: "approve", repo: item.repo, number: item.number });
|
|
224
|
+
item.reviewDecision = "APPROVED";
|
|
225
|
+
note.textContent = "Approved.";
|
|
226
|
+
approve.remove();
|
|
227
|
+
merge.className = "ghbtn primary";
|
|
228
|
+
merge.disabled = blocked || item.checks === "pending";
|
|
229
|
+
} catch (err) {
|
|
230
|
+
note.textContent = String(err.message || err);
|
|
231
|
+
note.className = "meta err";
|
|
232
|
+
approve.disabled = false;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
actions.append(approve);
|
|
236
|
+
}
|
|
214
237
|
actions.append(merge);
|
|
215
238
|
}
|
|
216
239
|
card.append(actions);
|
|
@@ -234,7 +257,9 @@ function checksLine(item) {
|
|
|
234
257
|
pending: "Checks are running.",
|
|
235
258
|
none: "No checks.",
|
|
236
259
|
};
|
|
237
|
-
const conflict =
|
|
260
|
+
const conflict =
|
|
261
|
+
(item.mergeable === "CONFLICTING" ? " Conflicts with its base." : "") +
|
|
262
|
+
(item.reviewDecision === "REVIEW_REQUIRED" ? " Needs your approval to merge." : "");
|
|
238
263
|
return el("p", {
|
|
239
264
|
className: "homedue" + (item.checks === "failing" || conflict ? " late" : ""),
|
|
240
265
|
textContent: (words[item.checks] ?? "") + conflict,
|
|
@@ -800,6 +800,23 @@ function inboxScreen(m, opts) {
|
|
|
800
800
|
openThread({ repo: item.repo, number: item.number, kind: item.kind });
|
|
801
801
|
} catch (e) { failed(e); }
|
|
802
802
|
};
|
|
803
|
+
/* The branch rules want an approving review before a merge. Approve is your review,
|
|
804
|
+
given on purpose; Merge never gives it for you, so it waits until there is one. */
|
|
805
|
+
if (item.reviewDecision === "REVIEW_REQUIRED" && item.mergeable !== "CONFLICTING") {
|
|
806
|
+
merge.disabled = true;
|
|
807
|
+
merge.title = "Approve it first: the branch rules need an approving review";
|
|
808
|
+
const approve = el("button", { className: "ghbtn primary", textContent: "Approve" });
|
|
809
|
+
approve.onclick = async () => {
|
|
810
|
+
busy(true, "approving…");
|
|
811
|
+
try {
|
|
812
|
+
await post({ action: "approve", repo: item.repo, number: item.number });
|
|
813
|
+
item.reviewDecision = "APPROVED";
|
|
814
|
+
openThread({ repo: item.repo, number: item.number, kind: item.kind });
|
|
815
|
+
} catch (e) { failed(e); }
|
|
816
|
+
};
|
|
817
|
+
buttons.push(approve);
|
|
818
|
+
row.append(approve);
|
|
819
|
+
}
|
|
803
820
|
buttons.push(merge);
|
|
804
821
|
row.append(merge);
|
|
805
822
|
}
|