toolroll 0.9.86 → 0.9.88
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/CHANGELOG.md +86 -0
- package/dist/acp-check.d.ts +9 -0
- package/dist/acp-check.js +40 -28
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -70
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +329 -183
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -1
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/design/kit.css +722 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +31 -1
- package/dist/discord-settings.js +8 -4
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.js +7 -3
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +141 -30
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +38 -4
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +23 -0
- package/dist/release.js +46 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +3 -0
- package/dist/scope.js +6 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +16 -16
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +24 -12
- package/dist/server/flows.js +64 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +70 -0
- package/dist/server/kit.d.ts +419 -0
- package/dist/server/kit.js +369 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +22 -190
- package/dist/server/render-pages.js +123 -575
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +35 -5
- package/dist/server/render-tasks.js +114 -85
- package/dist/server/route-table.js +20 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +85 -2
- package/dist/server/tasks.d.ts +8 -0
- package/dist/server/tasks.js +95 -40
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +112 -3
- package/dist/store.js +292 -20
- package/dist/surface.js +5 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.js +11 -7
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +7 -4
- package/package.json +1 -1
|
@@ -22,19 +22,19 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
|
|
|
22
22
|
export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
|
|
23
23
|
/** Chat reads only an admitted project. Keep the full source in the project UI. */
|
|
24
24
|
export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
|
|
25
|
-
|
|
26
|
-
content?: never;
|
|
25
|
+
history?: never;
|
|
27
26
|
revision: number;
|
|
28
27
|
stale: boolean;
|
|
29
28
|
notice: string;
|
|
30
29
|
decision?: never;
|
|
31
|
-
|
|
30
|
+
title?: never;
|
|
31
|
+
content?: never;
|
|
32
32
|
instructions?: never;
|
|
33
33
|
references?: never;
|
|
34
34
|
decisions?: never;
|
|
35
35
|
} | {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
revision?: never;
|
|
37
|
+
history?: never;
|
|
38
38
|
stale?: never;
|
|
39
39
|
decision: {
|
|
40
40
|
repo: string;
|
|
@@ -51,27 +51,27 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
51
51
|
id: number;
|
|
52
52
|
};
|
|
53
53
|
notice: string;
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
title?: never;
|
|
55
|
+
content?: never;
|
|
56
56
|
instructions?: never;
|
|
57
57
|
references?: never;
|
|
58
58
|
decisions?: never;
|
|
59
59
|
} | {
|
|
60
|
+
history?: never;
|
|
60
61
|
stale?: never;
|
|
61
62
|
decision?: never;
|
|
62
63
|
revision: number;
|
|
63
64
|
title: string;
|
|
64
65
|
content: string;
|
|
65
66
|
notice: string;
|
|
66
|
-
history?: never;
|
|
67
67
|
instructions?: never;
|
|
68
68
|
references?: never;
|
|
69
69
|
decisions?: never;
|
|
70
70
|
} | {
|
|
71
|
-
title?: never;
|
|
72
|
-
content?: never;
|
|
73
71
|
stale?: never;
|
|
74
72
|
decision?: never;
|
|
73
|
+
title?: never;
|
|
74
|
+
content?: never;
|
|
75
75
|
revision: number;
|
|
76
76
|
history: {
|
|
77
77
|
revision: number;
|
package/dist/publish.d.ts
CHANGED
|
@@ -131,3 +131,14 @@ export declare function sweepMerges(store: Store, options: {
|
|
|
131
131
|
clock?: () => Date;
|
|
132
132
|
exec?: PublishExec;
|
|
133
133
|
}): Promise<MergeReport>;
|
|
134
|
+
/** A conditional settle (store.settleObservation) and, only when it won, that head's checks' own timing (v146 ci_check),
|
|
135
|
+
* in one transaction: a stale, losing answer neither settles nor records a check. */
|
|
136
|
+
export declare function settleWithChecks(store: Store, observation: {
|
|
137
|
+
githubRepo: string;
|
|
138
|
+
prNumber: number;
|
|
139
|
+
headSha: string;
|
|
140
|
+
state: "passing" | "failing" | "running" | "none";
|
|
141
|
+
generation: number;
|
|
142
|
+
}, rollup: unknown, now: Date): {
|
|
143
|
+
won: boolean;
|
|
144
|
+
};
|
package/dist/publish.js
CHANGED
|
@@ -26,7 +26,7 @@ import { tmpdir } from "node:os";
|
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
import { run as execRun } from "./exec.js";
|
|
28
28
|
import { summarizeChecks } from "./pulls.js";
|
|
29
|
-
import { releasePublication } from "./store.js";
|
|
29
|
+
import { parseCiRollup, recordCiChecks, releasePublication } from "./store.js";
|
|
30
30
|
import { headWithin } from "./names.js";
|
|
31
31
|
export const BODY_TEMPLATE_VERSION = 2;
|
|
32
32
|
/** After this many failed attempts a publication stops retrying and pages instead. */
|
|
@@ -340,6 +340,8 @@ export async function observeChecks(store, options) {
|
|
|
340
340
|
if (!settled.won)
|
|
341
341
|
return;
|
|
342
342
|
won = true;
|
|
343
|
+
// The checks' own timing (v146), only with the observation that won.
|
|
344
|
+
recordCiChecks(store, publication.githubRepo, publication.prNumber, headOid, parseCiRollup(payload.statusCheckRollup), clock());
|
|
343
345
|
store.recordPublicationRemoteState(publication.id, remoteState, clock());
|
|
344
346
|
report.resolved += store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, null, clock());
|
|
345
347
|
// A closed PR moots its merge blocker and supersedes its intent —
|
|
@@ -361,7 +363,8 @@ export async function observeChecks(store, options) {
|
|
|
361
363
|
continue;
|
|
362
364
|
}
|
|
363
365
|
const state = summarizeChecks(payload.statusCheckRollup);
|
|
364
|
-
|
|
366
|
+
// Settled and, when it won, the checks' own timing recorded (v146) in one transaction: a losing generation writes nothing.
|
|
367
|
+
const settled = settleWithChecks(store, { githubRepo: publication.githubRepo, prNumber: publication.prNumber, headSha: headOid, state, generation }, payload.statusCheckRollup, clock());
|
|
365
368
|
if (!settled.won) {
|
|
366
369
|
// An out-of-order response: a newer observation already spoke.
|
|
367
370
|
// Nothing persists from this one (round-4 findings 24/25).
|
|
@@ -465,7 +468,7 @@ async function observeOne(store, exec, clock, githubRepo, prNumber) {
|
|
|
465
468
|
if (String(payload.state ?? "").toUpperCase() !== "OPEN")
|
|
466
469
|
return { ok: false, why: "pr-" + String(payload.state ?? "gone").toLowerCase() };
|
|
467
470
|
const state = summarizeChecks(payload.statusCheckRollup);
|
|
468
|
-
const settled = store
|
|
471
|
+
const settled = settleWithChecks(store, { githubRepo, prNumber, headSha, state, generation }, payload.statusCheckRollup, clock());
|
|
469
472
|
// A losing settle is NO observation (round-4 finding 24).
|
|
470
473
|
if (!settled.won)
|
|
471
474
|
return { ok: false, why: "stale-observation" };
|
|
@@ -707,3 +710,13 @@ export async function sweepMerges(store, options) {
|
|
|
707
710
|
}
|
|
708
711
|
return report;
|
|
709
712
|
}
|
|
713
|
+
/** A conditional settle (store.settleObservation) and, only when it won, that head's checks' own timing (v146 ci_check),
|
|
714
|
+
* in one transaction: a stale, losing answer neither settles nor records a check. */
|
|
715
|
+
export function settleWithChecks(store, observation, rollup, now) {
|
|
716
|
+
return store.transact(() => {
|
|
717
|
+
const settled = store.settleObservation(observation, now);
|
|
718
|
+
if (settled.won)
|
|
719
|
+
recordCiChecks(store, observation.githubRepo, observation.prNumber, observation.headSha, parseCiRollup(rollup), now);
|
|
720
|
+
return settled;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
@@ -21,4 +21,5 @@ export type PullRequestSettingsView = {
|
|
|
21
21
|
problem: string | null;
|
|
22
22
|
};
|
|
23
23
|
export declare function pullRequestSettingsHtml(view: PullRequestSettingsView): Html;
|
|
24
|
-
|
|
24
|
+
/** Kept for the console's style bundle (server/chrome.ts); the page itself is kit markup. */
|
|
25
|
+
export declare const PULL_REQUEST_SETTINGS_CSS = "";
|
package/dist/pull-request-ui.js
CHANGED
|
@@ -1,44 +1,55 @@
|
|
|
1
1
|
/** Settings → Projects → Pull requests: one project's pull-request setup, in plain words. Off shows what setup
|
|
2
2
|
* found (or what to fix) and one Turn on; on shows where pull requests go and how they merge. Every change takes
|
|
3
3
|
* the password; turning on restates the exact repository and branch that were checked. */
|
|
4
|
-
import { html,
|
|
4
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
5
|
+
import { acts, btn, disclose, formRow, infoLine, note, notice, select, settingsSection, switchInput } from "./server/kit.js";
|
|
6
|
+
import { kitSectionForm } from "./settings-kit.js";
|
|
5
7
|
const METHOD_WORDS = { squash: "Squash and merge", merge: "Merge commit", rebase: "Rebase and merge" };
|
|
8
|
+
const mono = (text) => html `<span class="k-mono">${text}</span>`;
|
|
6
9
|
export function pullRequestSettingsHtml(view) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
view.
|
|
11
|
-
|
|
12
|
-
];
|
|
13
|
-
const head = html `<p class="meta">${view.name} · <span class="mono">${view.repo}</span></p>${notes}`;
|
|
10
|
+
const head = joinHtml([
|
|
11
|
+
view.problem === null ? "" : notice(view.problem),
|
|
12
|
+
view.said === null ? "" : infoLine(view.said),
|
|
13
|
+
note(html `${view.name} · ${mono(view.repo)}`),
|
|
14
|
+
]);
|
|
14
15
|
const publishing = view.publishing;
|
|
15
16
|
if (publishing.on) {
|
|
16
|
-
const status =
|
|
17
|
+
const status = settingsSection({ id: "pull-requests", title: "Pull requests", state: " · On",
|
|
18
|
+
lead: html `${mono(publishing.githubRepo)} into ${mono(publishing.base)}`,
|
|
19
|
+
body: note(`${publishing.legacy ? "Every finished build opens a pull request (set up with publish grant)." : "Complete offers “Complete and open a pull request”."} Set up by ${publishing.grantedBy}.`) });
|
|
17
20
|
if (!view.canChange)
|
|
18
|
-
return
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
return joinHtml([head, status]);
|
|
22
|
+
const merging = settingsSection({ id: "merging", title: "Merging",
|
|
23
|
+
body: kitSectionForm({ proof: "pull-requests", action: "/settings/pull-requests", id: "merging-form", gate: true, hidden: { repo: view.repo, act: "settings" },
|
|
24
|
+
body: joinHtml([
|
|
25
|
+
formRow("Merge method", null, select({ id: "pr-method", name: "method", label: "Merge method", current: publishing.mergeMethod,
|
|
26
|
+
items: ["squash", "merge", "rebase"].map(method => [method, `${METHOD_WORDS[method]}${method === "squash" ? " (default)" : ""}`]) })),
|
|
27
|
+
formRow("Merge when checks pass", "Off by default. When on, a pull request merges by itself once its checks pass on the completed commit. Branches are deleted after merging.", switchInput({ id: "pr-when-green", name: "when-green", label: publishing.mergeWhenGreen ? "On" : "Off", on: publishing.mergeWhenGreen, attrs: { value: "1", "aria-label": "Merge when checks pass" } }), "inline"),
|
|
28
|
+
]) }) });
|
|
29
|
+
const off = disclose("Turn off pull requests", kitSectionForm({ proof: "pull-requests", action: "/settings/pull-requests", id: "pr-off-form", hidden: { repo: view.repo, act: "off" },
|
|
30
|
+
save: { label: "Turn off", variant: "danger" }, confirm: "Turn off pull requests", body: note("Nothing more is pushed. Open pull requests stay on GitHub.") }));
|
|
31
|
+
return joinHtml([head, status, merging, off]);
|
|
21
32
|
}
|
|
22
33
|
const check = view.check;
|
|
23
34
|
if (check === null)
|
|
24
|
-
return
|
|
35
|
+
return joinHtml([head, settingsSection({ id: "pull-requests", title: "Pull requests", state: " · Off", body: note("An approver can turn on pull requests.") })]);
|
|
25
36
|
// The checks ask git and GitHub, so they run when asked, never on opening the page (docs/plans/get-side-effects.md).
|
|
26
|
-
const checkButton = (label) => postForm("/settings/pull-requests/check",
|
|
27
|
-
if (check === "unchecked")
|
|
28
|
-
return
|
|
37
|
+
const checkButton = (label, variant) => postForm("/settings/pull-requests/check", acts(btn(label, { sm: true, type: "submit", variant })), { hidden: { repo: view.repo } });
|
|
38
|
+
if (check === "unchecked") {
|
|
39
|
+
return joinHtml([head, settingsSection({ id: "pull-requests", title: "Pull requests", state: " · Off",
|
|
40
|
+
lead: "Check this project's GitHub setup to see what turning on would do.", body: checkButton("Check setup", "primary") })]);
|
|
41
|
+
}
|
|
29
42
|
if (!check.ok) {
|
|
30
|
-
return
|
|
43
|
+
return joinHtml([head, settingsSection({ id: "pull-requests", title: "Pull requests", state: " · Can’t turn on yet",
|
|
44
|
+
body: joinHtml([notice(check.message), checkButton("Check again", "outline")]) })]);
|
|
31
45
|
}
|
|
32
46
|
const plan = check.plan;
|
|
33
|
-
return
|
|
47
|
+
return joinHtml([head, settingsSection({ id: "pull-requests", title: "Pull requests", state: " · Ready to turn on",
|
|
48
|
+
body: kitSectionForm({ proof: "pull-requests-on", action: "/settings/pull-requests", id: "pr-on-form", hidden: { repo: view.repo, act: "on", github: plan.githubRepo, base: plan.base },
|
|
49
|
+
save: { label: "Turn on pull requests" }, confirm: "Turn on pull requests",
|
|
50
|
+
body: html `<ul class="k-note"><li>Pull requests open on ${mono(plan.githubRepo)} into ${mono(plan.base)}${plan.account === null ? "" : `, as ${plan.account}`}.</li>\
|
|
51
|
+
<li>Complete offers “Complete and open a pull request”, from the exact completed commit. A flow's Pull request zone can open one too.</li>\
|
|
52
|
+
<li>Merging squashes and deletes the branch. A person merges with their password; a flow merges only when its zone says so, after a person approved the card.</li></ul>` }) })]);
|
|
34
53
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.pr-settings p, .pr-settings h2 { margin: 0; }
|
|
38
|
-
.pr-settings label { display: grid; gap: .25rem; }
|
|
39
|
-
.pr-settings label input[type=password], .pr-settings select { min-height: 44px; box-sizing: border-box; width: 100%; }
|
|
40
|
-
.pr-settings .pr-check { display: flex; align-items: center; gap: .5rem; min-height: 44px; }
|
|
41
|
-
.pr-settings button { min-height: 44px; justify-self: start; }
|
|
42
|
-
.pr-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
43
|
-
@media (max-width: 480px) { .pr-settings button { justify-self: stretch; } }
|
|
44
|
-
`;
|
|
54
|
+
/** Kept for the console's style bundle (server/chrome.ts); the page itself is kit markup. */
|
|
55
|
+
export const PULL_REQUEST_SETTINGS_CSS = "";
|
package/dist/recipe-ui.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type Recipe, type RecipeDocument, type WorkflowPreview } from "./recipes.js";
|
|
2
2
|
import { type Html } from "./html.js";
|
|
3
3
|
export declare function recipeFromForm(body: URLSearchParams): RecipeDocument;
|
|
4
|
-
|
|
4
|
+
/** "One-time work" (New flow): the open project's recipes and the starters as rows, each with one "Use this"; then
|
|
5
|
+
* Create a recipe, importing a shared one, and the work recipes made lately. */
|
|
6
|
+
export declare function recipeRowsHtml(starters: Recipe[], saved: Recipe[], project: string | null, projectName: string | null, revision: number, recent: {
|
|
5
7
|
name: string;
|
|
6
8
|
href: string;
|
|
7
9
|
state: string;
|
|
@@ -16,4 +18,4 @@ export declare function recipeDefinitionPreviewHtml(preview: WorkflowPreview, re
|
|
|
16
18
|
export declare function recipeRunHtml(recipe: Recipe, repo: string, revision: number, problem?: string | null, values?: URLSearchParams, saved?: boolean): Html;
|
|
17
19
|
export declare function recipeAnswersFromForm(body: URLSearchParams): Map<string, string>;
|
|
18
20
|
export declare function recipeScript(): string;
|
|
19
|
-
export declare const RECIPE_CSS = "\n.recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }\n.recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }\n.recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }\n.recipe-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1rem 0 2rem; }\n.recipe-card { border:1px solid var(--border); border-radius:var(--radius); padding:1.1rem; background:var(--card); display:flex; flex-direction:column; gap:.5rem; }\n.recipe-card h3 { font-size:1.05rem; margin:0; letter-spacing:-.015em; }\n.recipe-card p { color:var(--muted-foreground); margin:.2rem 0; font-size:.85rem; line-height:1.5; }\n.recipe-card-link { margin-top:auto; padding-top:.75rem; display:flex; justify-content:space-between; }\n.recipe-editor { max-width:52rem; }.recipe-editor h2 { margin-top:0; }\n.recipe-criterion { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); padding:.8rem; margin:.8rem 0; }\n.recipe-evidence { display:flex; flex-wrap:wrap; gap:.5rem 1rem; }.recipe-evidence label { display:flex; align-items:center; gap:.4rem; font-size:.8rem; }.recipe-evidence input { width:auto; margin:0; }\n.recipe-options { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }\n.recipe-preview-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(16rem,1fr); gap:1rem; }\n.recipe-steps { padding-left:1.4rem; }.recipe-steps li { padding:.4rem 0 .6rem .3rem; }.recipe-steps p { margin:.3rem 0 0; font-size:.85rem; color:var(--muted-foreground); }\n.recipe-launch { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }.recipe-launch form { margin:0 0 .6rem; }\n.recipe-history a { display:flex; justify-content:space-between; gap:1rem; padding:.6rem 0; border-bottom:1px solid var(--border); }.recipe-history span { color:var(--muted-foreground); }\n.recipe-error { border-color:var(--destructive); }\n.recipe-card[hidden] { display:none; }.recipe-search { max-width:28rem; }.recipe-question { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); margin:1rem 0; padding:.8rem; min-width:0; }.recipe-run { max-width:42rem; }.recipe-run button { margin-top:1rem; }\n@media(max-width:1000px){.recipe-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }}\n@media(max-width:760px){.recipe-grid,.recipe-options,.recipe-preview-grid,.recipe-launch { grid-template-columns:1fr; }.recipe-heading { margin-bottom:1rem; }.recipe-history a { flex-direction:column; gap:.2rem; }}\n";
|
|
21
|
+
export declare const RECIPE_CSS = "\n.fl-recipe-more { display:grid; gap:12px; padding-top:12px; }.fl-recipe-more .k-disclose[open] > form, .fl-recipe-more .k-disclose[open] > div { margin-top:12px; }.fl-recipe-import { display:grid; gap:12px; margin:0; }\n.fl-recipe-recent { display:grid; }.fl-recipe-recent a { display:flex; justify-content:space-between; gap:12px; min-height:40px; align-items:center; border-bottom:1px solid var(--so-line); color:var(--so-ink); text-decoration:none; font-size:13px; }\n.recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }\n.recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }\n.recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }\n.recipe-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1rem 0 2rem; }\n.recipe-card { border:1px solid var(--border); border-radius:var(--radius); padding:1.1rem; background:var(--card); display:flex; flex-direction:column; gap:.5rem; }\n.recipe-card h3 { font-size:1.05rem; margin:0; letter-spacing:-.015em; }\n.recipe-card p { color:var(--muted-foreground); margin:.2rem 0; font-size:.85rem; line-height:1.5; }\n.recipe-card-link { margin-top:auto; padding-top:.75rem; display:flex; justify-content:space-between; }\n.recipe-editor { max-width:52rem; }.recipe-editor h2 { margin-top:0; }\n.recipe-criterion { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); padding:.8rem; margin:.8rem 0; }\n.recipe-evidence { display:flex; flex-wrap:wrap; gap:.5rem 1rem; }.recipe-evidence label { display:flex; align-items:center; gap:.4rem; font-size:.8rem; }.recipe-evidence input { width:auto; margin:0; }\n.recipe-options { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }\n.recipe-preview-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(16rem,1fr); gap:1rem; }\n.recipe-steps { padding-left:1.4rem; }.recipe-steps li { padding:.4rem 0 .6rem .3rem; }.recipe-steps p { margin:.3rem 0 0; font-size:.85rem; color:var(--muted-foreground); }\n.recipe-launch { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }.recipe-launch form { margin:0 0 .6rem; }\n.recipe-history a { display:flex; justify-content:space-between; gap:1rem; padding:.6rem 0; border-bottom:1px solid var(--border); }.recipe-history span { color:var(--muted-foreground); }\n.recipe-error { border-color:var(--destructive); }\n.recipe-card[hidden] { display:none; }.recipe-search { max-width:28rem; }.recipe-question { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); margin:1rem 0; padding:.8rem; min-width:0; }.recipe-run { max-width:42rem; }.recipe-run button { margin-top:1rem; }\n@media(max-width:1000px){.recipe-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }}\n@media(max-width:760px){.recipe-grid,.recipe-options,.recipe-preview-grid,.recipe-launch { grid-template-columns:1fr; }.recipe-heading { margin-bottom:1rem; }.recipe-history a { flex-direction:column; gap:.2rem; }}\n";
|
package/dist/recipe-ui.js
CHANGED
|
@@ -3,6 +3,8 @@ import { composerSchedule, scheduleEditorHtml, scheduleEditorScript } from "./ta
|
|
|
3
3
|
import { parseRecipe, workflowSteps, recipeScheduleWords, RecipeError, RECIPE_INPUT_LIMIT } from "./recipes.js";
|
|
4
4
|
import { ACCEPTANCE_LIMITS } from "./scope.js";
|
|
5
5
|
import { html, joinHtml, postForm, replaceMarkup } from "./html.js";
|
|
6
|
+
import { templateRow } from "./flow-gallery-ui.js";
|
|
7
|
+
import { acts, btn, count, disclose, field, group, note, textarea } from "./server/kit.js";
|
|
6
8
|
const hidden = (name, value) => html `<input type="hidden" name="${name}" value="${value}">`;
|
|
7
9
|
const evidence = [["check", "Passing tests or commands"], ["changed-path", "Changed files"], ["screenshot", "Screenshot"], ["manual-review", "Reviewer assessment"]];
|
|
8
10
|
export function recipeFromForm(body) {
|
|
@@ -53,10 +55,16 @@ export function recipeFromForm(body) {
|
|
|
53
55
|
return parseRecipe({ format: "standing-orders-recipe", version: inputs.length ? 2 : 1, ...(inputs.length ? { inputs } : {}), name: body.get("name") ?? "", description: body.get("description") ?? "", goal: body.get("goal") ?? "", outOfScope: body.get("not") || null,
|
|
54
56
|
touches: (body.get("touches") ?? "").split(/[\n,]/).map(one => one.trim()).filter(Boolean), acceptance, planning: body.get("planning") ?? "auto", deliverable: body.get("deliverable") ?? "branch", schedule: schedule.schedule, costCeilingUsd: ceiling === "" ? null : Number(ceiling) });
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
/** "One-time work" (New flow): the open project's recipes and the starters as rows, each with one "Use this"; then
|
|
59
|
+
* Create a recipe, importing a shared one, and the work recipes made lately. */
|
|
60
|
+
export function recipeRowsHtml(starters, saved, project, projectName, revision, recent) {
|
|
61
|
+
const kind = (recipe) => `${recipe.document.deliverable === "report" ? "Report" : "Code change"} · ${recipe.document.schedule === null ? "runs once as a task" : "repeats on a schedule"}${recipe.document.version === 2 ? ` · ${recipe.document.inputs.length} question${recipe.document.inputs.length === 1 ? "" : "s"}` : ""}`;
|
|
62
|
+
const rowOf = (recipe, reusable) => templateRow({ name: recipe.document.name, promise: recipe.document.description, line: kind(recipe), attrs: html ` data-recipe-card data-recipe="${recipe.id}"`,
|
|
63
|
+
action: project === null ? "" : btn("Use this", { sm: true, href: `/recipes/${reusable ? "run" : "start"}?recipe=${recipe.id}&revision=${recipe.revision}`, attrs: { "aria-label": `Use ${recipe.document.name}` } }) });
|
|
64
|
+
const tools = project === null ? note("Open a project to use, save or import a recipe.")
|
|
65
|
+
: html `<p class="k-acts fl-recipe-acts">${btn("Create a recipe", { sm: true, href: "/recipes/new" })}</p>${disclose("Import a shared recipe", postForm("/recipes/import", html `${field({ id: "fl-recipe-json", label: "Recipe JSON", hint: "Paste exported recipe JSON. You review it in this project before any work is made.", control: textarea({ id: "fl-recipe-json", name: "document", rows: 6, attrs: { required: true, maxlength: 32768, spellcheck: "false" } }) })}${acts(btn("Review imported recipe", { type: "submit" }))}`, { projectRevision: String(revision), hidden: { repo: project }, attrs: { class: "fl-recipe-import" } }))}`;
|
|
66
|
+
const recentRows = recent.length === 0 ? "" : disclose(`Made from recipes lately (${recent.length})`, html `<div class="fl-recipe-recent">${recent.map(one => html `<a href="${one.href}"><span>${one.name}</span><span class="k-note">${one.state}</span></a>`)}</div>`);
|
|
67
|
+
return html `${group(projectName === null ? "One-time work" : `One-time work in ${projectName}`, count(saved.length + starters.length))}<div data-group="recipes">${saved.map(one => rowOf(one, true))}${starters.map(one => rowOf(one, false))}</div><div class="fl-recipe-more">${tools}${recentRows}</div>`;
|
|
60
68
|
}
|
|
61
69
|
function criterionHtml(index, criterion, values) {
|
|
62
70
|
const prefix = `criterion-${index}-`;
|
|
@@ -137,6 +145,8 @@ export function recipeScript() {
|
|
|
137
145
|
})();`;
|
|
138
146
|
}
|
|
139
147
|
export const RECIPE_CSS = `
|
|
148
|
+
.fl-recipe-more { display:grid; gap:12px; padding-top:12px; }.fl-recipe-more .k-disclose[open] > form, .fl-recipe-more .k-disclose[open] > div { margin-top:12px; }.fl-recipe-import { display:grid; gap:12px; margin:0; }
|
|
149
|
+
.fl-recipe-recent { display:grid; }.fl-recipe-recent a { display:flex; justify-content:space-between; gap:12px; min-height:40px; align-items:center; border-bottom:1px solid var(--so-line); color:var(--so-ink); text-decoration:none; font-size:13px; }
|
|
140
150
|
.recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }
|
|
141
151
|
.recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }
|
|
142
152
|
.recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }
|
|
@@ -189,3 +189,6 @@ export declare class ProtectionUnreadable extends Error {
|
|
|
189
189
|
}
|
|
190
190
|
/** Whether main's protection holds, as a yes/no (release-auto.ts sealAutoGreen): null when it can't be read. */
|
|
191
191
|
export declare function readUpToDateRequired(run: Exec, gh: string, repo: string, branch?: string): Promise<boolean | null>;
|
|
192
|
+
/** The release's step record (store.ts release_step, v146) through a store opened for each entry: a failure to open or
|
|
193
|
+
* write is swallowed, so recording never stops or changes the release. */
|
|
194
|
+
export declare function releaseProgressRecorder(open: () => Store, done: (store: Store) => void): NonNullable<ReleaseAdapters["progress"]>;
|
package/dist/release-adapters.js
CHANGED
|
@@ -19,7 +19,7 @@ import { PROTECTION_MINIMUM_CHECKS, PROTECTION_REPORT_ONLY_CHECKS } from "./rele
|
|
|
19
19
|
import { activeUpdateWork } from "./desktop-update-gate.js";
|
|
20
20
|
import { BEFORE_SWAP, gateOwner } from "./release-gate.js";
|
|
21
21
|
import { findCliLinks, switchCliLinks } from "./release-links.js";
|
|
22
|
-
import { databasePath, finishReleaseOperation, requireFreshReleaseApproval, openStore, releaseAuthorityOfTask, releaseCarries, releaseOperations, renewReleaseLease, reserveRelease, runReleaseOperation, Store, releaseIssuerGone, takeReleaseLease, } from "./store.js";
|
|
22
|
+
import { databasePath, finishReleaseOperation, requireFreshReleaseApproval, openStore, releaseAuthorityOfTask, releaseCarries, releaseOperations, renewReleaseLease, reserveRelease, runReleaseOperation, Store, recordReleaseStep, releaseIssuerGone, takeReleaseLease, } from "./store.js";
|
|
23
23
|
import { acquireDeploymentLock } from "./deploy-lock.js";
|
|
24
24
|
import { autoReleaseApprovalOf, liveReleaseOf } from "./scope.js";
|
|
25
25
|
import { recheckReleaseTask, releaseCheckStanding } from "./check-failure.js";
|
|
@@ -429,7 +429,7 @@ export function releaseAdapters(options) {
|
|
|
429
429
|
// A boundary's one bounded read of GitHub's nightly: plain exec, never the release's long retries (20 s in all).
|
|
430
430
|
githubReads(run, ciGh));
|
|
431
431
|
return {
|
|
432
|
-
...(existsSync(database) ? { release } : {}),
|
|
432
|
+
...(existsSync(database) ? { release, progress: releaseProgressRecorder(() => openStore(database), store => store.close()) } : {}),
|
|
433
433
|
now: options.now ?? (() => new Date()),
|
|
434
434
|
sleep: options.sleep ?? sleep,
|
|
435
435
|
say,
|
|
@@ -1471,3 +1471,23 @@ function mainTriggered(run) {
|
|
|
1471
1471
|
return bases.length > 0 && bases.every(base => base === "main");
|
|
1472
1472
|
return false;
|
|
1473
1473
|
}
|
|
1474
|
+
/** The release's step record (store.ts release_step, v146) through a store opened for each entry: a failure to open or
|
|
1475
|
+
* write is swallowed, so recording never stops or changes the release. */
|
|
1476
|
+
export function releaseProgressRecorder(open, done) {
|
|
1477
|
+
return entry => {
|
|
1478
|
+
let store = null;
|
|
1479
|
+
try {
|
|
1480
|
+
store = open();
|
|
1481
|
+
recordReleaseStep(store, entry);
|
|
1482
|
+
}
|
|
1483
|
+
catch { /* best effort: the release goes on */ }
|
|
1484
|
+
finally {
|
|
1485
|
+
if (store !== null) {
|
|
1486
|
+
try {
|
|
1487
|
+
done(store);
|
|
1488
|
+
}
|
|
1489
|
+
catch { /* best effort */ }
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
}
|
package/dist/release-cli.js
CHANGED
|
@@ -16,7 +16,8 @@ import { RELEASE_LIMITS, runRelease } from "./release.js";
|
|
|
16
16
|
import { processes, releaseAdapters } from "./release-adapters.js";
|
|
17
17
|
import { journals, releaseGate } from "./release-gate.js";
|
|
18
18
|
import { rescueDeployment } from "./deploy-rescue.js";
|
|
19
|
-
import { databasePath, openStore, revokeRelease } from "./store.js";
|
|
19
|
+
import { databasePath, elapsedSeconds, latestReleaseStepTask, openStore, releaseProgressFor, revokeRelease } from "./store.js";
|
|
20
|
+
import { dur } from "./design/kit.js";
|
|
20
21
|
import { carryRefusalWords, proveCarry, setAskEveryReleaseCommit, setUnsandboxedCarry } from "./release-carry.js";
|
|
21
22
|
import { authenticateApprover, machineActing, personProof, proveCredential } from "./scope.js";
|
|
22
23
|
import { allowAutoPublish, setReleaseApproval } from "./release-auto.js";
|
|
@@ -35,6 +36,12 @@ export const RELEASE_HELP = `toolroll release <branch> — release Toolroll itse
|
|
|
35
36
|
|
|
36
37
|
Run the same command to resume after a stop. Interrupted deployments recover their saved stage before retrying.
|
|
37
38
|
|
|
39
|
+
A branch whose name is one of this command's own words (status, nightly, carry…) is named with --branch <name>
|
|
40
|
+
or after --: \`toolroll release --branch status\`.
|
|
41
|
+
|
|
42
|
+
toolroll release status [<task>] — the steps of a release (the newest one recorded, or the release task named): each
|
|
43
|
+
step's state and how long it took, and where it stopped
|
|
44
|
+
|
|
38
45
|
toolroll release --release-gate <id> [--yes] — lift the update pause a deployment left before its swap
|
|
39
46
|
|
|
40
47
|
Shows why it is safe first: the pause is that deployment's, its journal stopped before the swap, and the
|
|
@@ -98,7 +105,7 @@ A runner's lease is returned with \`toolroll worker release <lease>\`.`;
|
|
|
98
105
|
/** A runner lease id (claim.ts mints randomUUID()). */
|
|
99
106
|
export const LEASE_SHAPED = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
100
107
|
export const LEASE_DEPRECATION = "`toolroll release <lease>` is now `toolroll worker release <lease>`; the old form goes in the next minor version.";
|
|
101
|
-
const VALUE_FLAGS = new Set(["--repo", "--limit", "--tap", "--release-gate", "--to", "--request", "--as", "--token", "--schedule", "--stage", "--lane-retire"]);
|
|
108
|
+
const VALUE_FLAGS = new Set(["--repo", "--limit", "--tap", "--release-gate", "--to", "--request", "--as", "--token", "--schedule", "--stage", "--lane-retire", "--branch"]);
|
|
102
109
|
const BARE_FLAGS = new Set(["--new", "--json", "--help", "--yes", "--no-carry", "--start", "--recover-deploy", "--github"]);
|
|
103
110
|
export async function runReleaseCommand(args, write, deps = {}) {
|
|
104
111
|
// The flag, not a secret's value that happens to read "--json".
|
|
@@ -112,9 +119,23 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
112
119
|
const positional = [];
|
|
113
120
|
const limits = {};
|
|
114
121
|
let stage, laneRetire, laneAlias;
|
|
122
|
+
// `--branch <name>` (or `-- <name>`) names the branch outright, so a branch called "status", "nightly" or "carry" is
|
|
123
|
+
// still a branch and never one of this command's own words.
|
|
124
|
+
let branchFlag, literal = false, branchFlags = 0;
|
|
125
|
+
// Only what follows `--` is literal: it never changes how the words before it read.
|
|
126
|
+
const literalArgs = [];
|
|
115
127
|
let repo, tap, gate, to, request, as, token, scheduleFlag;
|
|
116
128
|
for (let i = 0; i < args.length; i += 1) {
|
|
117
129
|
const raw = args[i];
|
|
130
|
+
// After `--`, every argument is a branch name, never a flag or a subcommand.
|
|
131
|
+
if (raw === "--" && !literal) {
|
|
132
|
+
literal = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (literal) {
|
|
136
|
+
literalArgs.push(raw);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
118
139
|
if (BARE_FLAGS.has(raw))
|
|
119
140
|
continue;
|
|
120
141
|
// `--lane-alias <lane-id> <new name>`: two values, neither a flag of this command.
|
|
@@ -157,6 +178,10 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
157
178
|
stage = value;
|
|
158
179
|
else if (arg === "--lane-retire")
|
|
159
180
|
laneRetire = value;
|
|
181
|
+
else if (arg === "--branch") {
|
|
182
|
+
branchFlag = value;
|
|
183
|
+
branchFlags += 1;
|
|
184
|
+
}
|
|
160
185
|
else {
|
|
161
186
|
const match = /^([a-z]+)=([0-9]+)$/.exec(value);
|
|
162
187
|
if (match === null || !(match[1] in RELEASE_LIMITS) || Number(match[2]) < 1)
|
|
@@ -174,12 +199,12 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
174
199
|
return 0;
|
|
175
200
|
}
|
|
176
201
|
if (gate !== undefined) {
|
|
177
|
-
if (positional.length > 0 || args.some(arg => ["--repo", "--limit", "--tap", "--new"].includes(arg)))
|
|
202
|
+
if (positional.length > 0 || branchFlag !== undefined || literalArgs.length > 0 || args.some(arg => ["--repo", "--limit", "--tap", "--new"].includes(arg)))
|
|
178
203
|
return refuse("usage", "--release-gate <id> takes no branch or release options.", 2);
|
|
179
204
|
return runGateRelease(gate, bare.has("--yes"), write, json, deps.gateWorld ?? installedGateWorld());
|
|
180
205
|
}
|
|
181
206
|
if (bare.has("--recover-deploy")) {
|
|
182
|
-
if (positional.length > 0 || args.some(arg => ["--repo", "--limit", "--tap", "--new", "--release-gate"].includes(arg.split("=")[0])))
|
|
207
|
+
if (positional.length > 0 || branchFlag !== undefined || literalArgs.length > 0 || args.some(arg => ["--repo", "--limit", "--tap", "--new", "--release-gate"].includes(arg.split("=")[0])))
|
|
183
208
|
return refuse("usage", "--recover-deploy takes only --stage <dir>, --yes and --json.", 2);
|
|
184
209
|
return runRescue(stage, bare.has("--yes"), write, json, deps.rescueWorld ?? installedRescueWorld(deps.cwd ?? process.cwd()));
|
|
185
210
|
}
|
|
@@ -194,15 +219,29 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
194
219
|
return refuse("usage", "Use one of --github, --lane-alias or --lane-retire, without --schedule or --start.", 2);
|
|
195
220
|
return runGithubNightlyCommand({ repo: repo === undefined ? undefined : resolve(deps.cwd ?? process.cwd(), repo), as, token, alias: laneAlias, retire: laneRetire }, write, json, deps);
|
|
196
221
|
}
|
|
197
|
-
|
|
222
|
+
const named = branchFlag !== undefined || literalArgs.length > 0;
|
|
223
|
+
// One branch, named one way: never a guess between two.
|
|
224
|
+
if (branchFlags > 1 || literalArgs.length > 1 || (branchFlag !== undefined && (positional.length > 0 || literalArgs.length > 0)) || (literalArgs.length > 0 && positional.length > 0)) {
|
|
225
|
+
return refuse("usage", "Name the branch once: `toolroll release <branch>`, `--branch <name>` or `-- <name>`, never two of them.", 2);
|
|
226
|
+
}
|
|
227
|
+
if (branchFlag !== undefined && branchFlag === "")
|
|
228
|
+
return refuse("usage", "--branch takes a branch name.", 2);
|
|
229
|
+
// `release status [<task>]`: the steps recorded for a release. A branch named "status" is released with
|
|
230
|
+
// `--branch status` (or `-- status`).
|
|
231
|
+
if (!named && positional[0] === "status" && positional.length <= 2) {
|
|
232
|
+
if (args.some(arg => VALUE_FLAGS.has(arg.split("=")[0]) || ["--new", "--no-carry", "--start"].includes(arg)))
|
|
233
|
+
return refuse("usage", "`toolroll release status [<task>]` takes only --json.", 2);
|
|
234
|
+
return runStatusCommand(positional[1], write, json, deps);
|
|
235
|
+
}
|
|
236
|
+
if (!named && positional[0] === "nightly" && positional.length === 1)
|
|
198
237
|
return runNightlyCommand({ repo: repo === undefined ? undefined : resolve(deps.cwd ?? process.cwd(), repo), as, token, schedule: scheduleFlag, start: bare.has("--start") }, write, json, deps);
|
|
199
|
-
if (["carry", "revoke", "ask-every-commit", "unsandboxed-carry", "auto-approve", "publish-ok"].includes(positional[0] ?? "") && positional.length === 2) {
|
|
238
|
+
if (!named && ["carry", "revoke", "ask-every-commit", "unsandboxed-carry", "auto-approve", "publish-ok"].includes(positional[0] ?? "") && positional.length === 2) {
|
|
200
239
|
return runAuthorityCommand(positional[0], positional[1], { to, request, as, token, repo: repo === undefined ? undefined : resolve(deps.cwd ?? process.cwd(), repo) }, write, json, deps);
|
|
201
240
|
}
|
|
202
|
-
if (positional.length !== 1)
|
|
241
|
+
if (!named && positional.length !== 1)
|
|
203
242
|
return refuse("usage", `Which branch? \`toolroll release <branch>\`.\n${RELEASE_HELP}`, 2);
|
|
204
|
-
const branch = positional[0];
|
|
205
|
-
if (LEASE_SHAPED.test(branch)) {
|
|
243
|
+
const branch = branchFlag ?? literalArgs[0] ?? positional[0];
|
|
244
|
+
if (!named && LEASE_SHAPED.test(branch)) {
|
|
206
245
|
const releaseOptions = args.filter(arg => VALUE_FLAGS.has(arg) || arg === "--new");
|
|
207
246
|
if (releaseOptions.length > 0)
|
|
208
247
|
return refuse("ambiguous", `${branch} looks like a lease id, but ${releaseOptions[0]} is a release option. Return a lease with \`toolroll worker release <lease>\`; a branch shaped like a lease id can't be released.`, 2);
|
|
@@ -519,3 +558,36 @@ async function runGithubNightlyCommand(flags, write, json, deps) {
|
|
|
519
558
|
store.close();
|
|
520
559
|
}
|
|
521
560
|
}
|
|
561
|
+
/** `toolroll release status [<task>]` (read-only): the release's recorded steps (store.ts release_step, v146). */
|
|
562
|
+
async function runStatusCommand(task, write, json, deps) {
|
|
563
|
+
const command = "release status";
|
|
564
|
+
const refuse = (reason, message, code) => { write(json ? checkedEnvelopeJson({ ok: false, command, reason, message }) : message); return code; };
|
|
565
|
+
const database = deps.database ?? databasePath(process.env, homedir());
|
|
566
|
+
if (!existsSync(database))
|
|
567
|
+
return refuse("no-plane", "This machine has no Toolroll database.", 1);
|
|
568
|
+
const store = openStore(database);
|
|
569
|
+
try {
|
|
570
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
571
|
+
const taskId = task ?? latestReleaseStepTask(store);
|
|
572
|
+
if (taskId === null)
|
|
573
|
+
return refuse("no-release", "No release has recorded its steps yet.", 1);
|
|
574
|
+
const progress = releaseProgressFor(store, taskId);
|
|
575
|
+
if (progress === null)
|
|
576
|
+
return refuse("no-release", `No release steps are recorded for ${taskId}.`, 1);
|
|
577
|
+
const steps = progress.steps.map(one => ({ ...one, seconds: elapsedSeconds(one.startedAt, one.endedAt, now) }));
|
|
578
|
+
if (json) {
|
|
579
|
+
write(checkedEnvelopeJson({ ok: true, command, task: taskId, release: { ...progress, steps } }));
|
|
580
|
+
return 0;
|
|
581
|
+
}
|
|
582
|
+
const word = { running: "running", waiting: "waiting", done: "done", failed: "failed" };
|
|
583
|
+
write(`Release ${progress.version} (${progress.tag})${progress.pr === null ? "" : ` · pull request #${progress.pr}`} · task ${taskId}`);
|
|
584
|
+
for (const one of steps)
|
|
585
|
+
write(` ${one.step.padEnd(9)} ${word[one.state].padEnd(8)} ${one.seconds === null ? "–" : dur(one.seconds)}${one.detail === null ? "" : ` ${one.detail}`}`);
|
|
586
|
+
if (progress.stopped !== null)
|
|
587
|
+
write(`Stopped at ${progress.stopped.step}: ${progress.stopped.message}`);
|
|
588
|
+
return 0;
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
store.close();
|
|
592
|
+
}
|
|
593
|
+
}
|
package/dist/release.d.ts
CHANGED
|
@@ -48,6 +48,15 @@ export declare const REQUIRED_PR_CHECKS_WORKFLOW: Readonly<Record<typeof REQUIRE
|
|
|
48
48
|
export declare const PROTECTION_REPORT_ONLY_CHECKS: readonly string[];
|
|
49
49
|
/** The checks main's protection must require at minimum: every required check that isn't report-only. */
|
|
50
50
|
export declare const PROTECTION_MINIMUM_CHECKS: readonly string[];
|
|
51
|
+
/**
|
|
52
|
+
* Pull-request checks a release never waits for or fails on, by exact name and the workflow file that defines them:
|
|
53
|
+
* youiyoux's UI drift check (src/design/KIT.md), which fails a pull request that adds design drift but says nothing
|
|
54
|
+
* about whether a build is safe to release, and the scripted journeys on a pull request (journeys.yml,
|
|
55
|
+
* docs/plans/ci-speed.md item 4), advisory while they settle in CI: the release check runs every journey itself. Only a
|
|
56
|
+
* GitHub Actions run of that name from that workflow is left out; the same name from anywhere else is judged like any
|
|
57
|
+
* other check. Never a required check.
|
|
58
|
+
*/
|
|
59
|
+
export declare const ADVISORY_PR_CHECKS: Readonly<Record<string, string>>;
|
|
51
60
|
/**
|
|
52
61
|
* Main's protection as the plane's gh reads it (release-adapters.ts readMainProtection): `effective` only when one layer
|
|
53
62
|
* (classic protection or one active ruleset) requires up-to-date branches, binds every PROTECTION_MINIMUM_CHECKS check to GitHub Actions,
|
|
@@ -393,6 +402,18 @@ export type AutoReleaseAdapter = {
|
|
|
393
402
|
export type ReleaseAdapters = {
|
|
394
403
|
/** One approval per release (release-once.md); absent on a plane that can't carry. */
|
|
395
404
|
release?: ReleaseAuthorityAdapter;
|
|
405
|
+
/** Where each step's start, end and outcome are recorded (store.ts release_step, v146) for the Release view and
|
|
406
|
+
* `toolroll release status`. Best effort: a failure here never stops or changes the release. */
|
|
407
|
+
progress?: (entry: {
|
|
408
|
+
taskId: string;
|
|
409
|
+
tag: string;
|
|
410
|
+
version: string;
|
|
411
|
+
pr: number | null;
|
|
412
|
+
step: ReleaseStep;
|
|
413
|
+
state: "running" | "done" | "failed" | "waiting";
|
|
414
|
+
at: Date;
|
|
415
|
+
detail?: string | null;
|
|
416
|
+
}) => void;
|
|
396
417
|
now(): Date;
|
|
397
418
|
sleep(ms: number): Promise<void>;
|
|
398
419
|
say(line: string): void;
|
|
@@ -731,4 +752,6 @@ export declare function checkFailedWords(input: {
|
|
|
731
752
|
}): string;
|
|
732
753
|
/** Whether GitHub's workflow-run `path` names exactly `file` (it may carry the ref it ran from, after an `@`). */
|
|
733
754
|
export declare function workflowIs(path: string | null | undefined, file: string): boolean;
|
|
755
|
+
/** How a person names this branch to `toolroll release` again. */
|
|
756
|
+
export declare function releaseBranchArg(branch: string): string;
|
|
734
757
|
export {};
|