@detergent-software/conductor 1.2.1-dev.2 → 1.3.0-dev.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/CHANGELOG.md +21 -0
- package/README.md +17 -0
- package/build/brief.js +2 -1
- package/build/brief.js.map +1 -1
- package/build/cli.js +4 -0
- package/build/cli.js.map +1 -1
- package/build/daemon.js +4 -0
- package/build/daemon.js.map +1 -1
- package/build/help.js +11 -0
- package/build/help.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +230 -0
- package/build/lib/providers/ado/code-host.js.map +1 -0
- package/build/lib/providers/ado/http.js +95 -0
- package/build/lib/providers/ado/http.js.map +1 -0
- package/build/lib/providers/ado/pipelines.js +185 -0
- package/build/lib/providers/ado/pipelines.js.map +1 -0
- package/build/lib/providers/ado/trigger.js +99 -0
- package/build/lib/providers/ado/trigger.js.map +1 -0
- package/build/lib/providers/errors.js +23 -0
- package/build/lib/providers/errors.js.map +1 -0
- package/build/lib/providers/github/actions.js +86 -0
- package/build/lib/providers/github/actions.js.map +1 -0
- package/build/lib/providers/github/code-host.js +201 -0
- package/build/lib/providers/github/code-host.js.map +1 -0
- package/build/lib/providers/github/gh.js +15 -0
- package/build/lib/providers/github/gh.js.map +1 -0
- package/build/lib/providers/index.js +15 -0
- package/build/lib/providers/index.js.map +1 -0
- package/build/lib/providers/notes.js +22 -0
- package/build/lib/providers/notes.js.map +1 -0
- package/build/lib/providers/npm/stage.js +62 -0
- package/build/lib/providers/npm/stage.js.map +1 -0
- package/build/lib/providers/types.js +8 -0
- package/build/lib/providers/types.js.map +1 -0
- package/build/lib/providers/verdicts/pr.js +90 -0
- package/build/lib/providers/verdicts/pr.js.map +1 -0
- package/build/lib/providers/verdicts/runs.js +73 -0
- package/build/lib/providers/verdicts/runs.js.map +1 -0
- package/build/lib/providers/verdicts/stage.js +14 -0
- package/build/lib/providers/verdicts/stage.js.map +1 -0
- package/build/notes.js +1 -1
- package/build/notes.js.map +1 -1
- package/build/profile.js +10 -0
- package/build/profile.js.map +1 -1
- package/build/train-cli.js +64 -0
- package/build/train-cli.js.map +1 -0
- package/build/train.js +342 -0
- package/build/train.js.map +1 -0
- package/build/watchers/ado-api.js +2 -31
- package/build/watchers/ado-api.js.map +1 -1
- package/build/watchers/ado.js +36 -119
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +21 -48
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/linear.js +2 -17
- package/build/watchers/linear.js.map +1 -1
- package/build/watchers/npm.js +1 -1
- package/build/watchers/npm.js.map +1 -1
- package/build/watchers/source.js +8 -2
- package/build/watchers/source.js.map +1 -1
- package/package.json +1 -1
package/build/train.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ConductorError } from "./errors.js";
|
|
3
|
+
import { checksSettled, githubChecks, githubRepoRef, noteHeading, repoName, } from "./lib/providers/index.js";
|
|
4
|
+
import { notePrefixes } from "./profile.js";
|
|
5
|
+
import { outputOf, run } from "./run.js";
|
|
6
|
+
import { ghApi } from "./watchers/github.js";
|
|
7
|
+
// The train phase; its `urgent` kind asks for a train now. `Train opened` is the note `conduct
|
|
8
|
+
// train` leaves on each member.
|
|
9
|
+
const TRAIN_PHASE = 'Train';
|
|
10
|
+
const OPENED_PHASE = 'Train opened';
|
|
11
|
+
const BRANCH_PREFIX = 'train/';
|
|
12
|
+
const TRAIN_TITLE = 'feat(registry): skill train ';
|
|
13
|
+
// A member line of a train PR's body: `- #531 …`.
|
|
14
|
+
const MEMBER_LINE = /^- #(\d+)\b/gm;
|
|
15
|
+
// A few gh calls per project each time: the open PRs, then each one's comments.
|
|
16
|
+
const CHECK_MS = 15 * 60_000;
|
|
17
|
+
const FIRST_CHECK_MS = 60_000;
|
|
18
|
+
const HOUR_MS = 60 * 60_000;
|
|
19
|
+
const pullSchema = z.looseObject({
|
|
20
|
+
base: z.looseObject({ ref: z.string() }),
|
|
21
|
+
body: z.string().nullish(),
|
|
22
|
+
head: z.looseObject({ ref: z.string(), sha: z.string() }),
|
|
23
|
+
html_url: z.string(),
|
|
24
|
+
number: z.number(),
|
|
25
|
+
title: z.string(),
|
|
26
|
+
});
|
|
27
|
+
const commentSchema = z.looseObject({ body: z.string().nullish(), created_at: z.string() });
|
|
28
|
+
const checkRunsSchema = z.looseObject({
|
|
29
|
+
check_runs: z.array(z.looseObject({
|
|
30
|
+
conclusion: z.string().nullish(),
|
|
31
|
+
details_url: z.string().nullish(),
|
|
32
|
+
html_url: z.string().nullish(),
|
|
33
|
+
name: z.string(),
|
|
34
|
+
status: z.string(),
|
|
35
|
+
})),
|
|
36
|
+
});
|
|
37
|
+
const fileSchema = z.looseObject({ filename: z.string(), previous_filename: z.string().nullish() });
|
|
38
|
+
// The train branch from the default branch, one merge per member, the PR, and a note on each member.
|
|
39
|
+
// A member GitHub won't merge (a conflict with the default branch or another member) stays out; when
|
|
40
|
+
// none merges, or the PR isn't opened, the branch is deleted, so nothing is left half built.
|
|
41
|
+
export async function buildTrain(gh, write, repo, plan, { now, prefix }) {
|
|
42
|
+
if (plan.take.length === 0)
|
|
43
|
+
throw new ConductorError('no member can ride a train', refusalLines(plan.refused));
|
|
44
|
+
const base = `repos/${repoName(repo)}`;
|
|
45
|
+
const branch = `${BRANCH_PREFIX}${stamp(now)}`;
|
|
46
|
+
const head = z
|
|
47
|
+
.looseObject({ object: z.looseObject({ sha: z.string() }) })
|
|
48
|
+
.parse(await gh(`${base}/git/ref/heads/${plan.base}`));
|
|
49
|
+
await write('POST', `${base}/git/refs`, { ref: `refs/heads/${branch}`, sha: head.object.sha });
|
|
50
|
+
const undo = () => write('DELETE', `${base}/git/refs/heads/${branch}`).catch(() => undefined);
|
|
51
|
+
const refused = [...plan.refused];
|
|
52
|
+
const take = [];
|
|
53
|
+
for (const m of plan.take) {
|
|
54
|
+
try {
|
|
55
|
+
await write('POST', `${base}/merges`, {
|
|
56
|
+
base: branch,
|
|
57
|
+
commit_message: `Merge #${m.number} into ${branch}: ${m.title}`,
|
|
58
|
+
head: m.headSha,
|
|
59
|
+
});
|
|
60
|
+
take.push(m);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
refused.push({ member: m, why: `GitHub refused its merge (${messageOf(error)})` });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (take.length === 0) {
|
|
67
|
+
await undo();
|
|
68
|
+
throw new ConductorError(`GitHub refused every merge into ${branch}, so the branch is deleted`, refusalLines(refused));
|
|
69
|
+
}
|
|
70
|
+
const built = { base: plan.base, refused, take };
|
|
71
|
+
let pr;
|
|
72
|
+
try {
|
|
73
|
+
pr = pullSchema.parse(await write('POST', `${base}/pulls`, {
|
|
74
|
+
base: plan.base,
|
|
75
|
+
body: trainBody(built),
|
|
76
|
+
head: branch,
|
|
77
|
+
title: `${TRAIN_TITLE}${stamp(now)} (${take.length} ${plural(take.length, 'publish', 'publishes')})`,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
await undo();
|
|
82
|
+
throw new ConductorError(`GitHub didn't open the train's PR, so ${branch} is deleted`, [messageOf(error)]);
|
|
83
|
+
}
|
|
84
|
+
// The train is open either way; a member without its note only lacks the link.
|
|
85
|
+
const unnoted = [];
|
|
86
|
+
for (const m of take) {
|
|
87
|
+
await write('POST', `${base}/issues/${m.number}/comments`, {
|
|
88
|
+
body: `## ${prefix}${OPENED_PHASE} · merge: ${pr.html_url}\n\nThis publish rides train #${pr.number}. Merging the train merges it: GitHub then shows this PR merged, and the train's merge commit is the one Build Registry runs on.`,
|
|
89
|
+
}).catch(() => unnoted.push(m.number));
|
|
90
|
+
}
|
|
91
|
+
return { branch, members: take.map((m) => m.number), number: pr.number, refused, unnoted, url: pr.html_url };
|
|
92
|
+
}
|
|
93
|
+
export function ghWrite() {
|
|
94
|
+
return async (method, path, fields = {}) => {
|
|
95
|
+
const args = ['api', '-X', method, path, ...Object.entries(fields).flatMap(([k, v]) => ['-f', `${k}=${v}`])];
|
|
96
|
+
const result = await run('gh', args, { timeoutMs: 60_000 });
|
|
97
|
+
if (result.code !== 0)
|
|
98
|
+
throw new Error(`gh api -X ${method} ${path} failed: ${outputOf(result, 300)}`);
|
|
99
|
+
return result.stdout.trim() === '' ? null : JSON.parse(result.stdout);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// The waiting members a train takes: open on the default branch, checks passed, and no path shared
|
|
103
|
+
// with a member taken before it. The rest are refused, each with why.
|
|
104
|
+
export async function planTrain(gh, repo, members) {
|
|
105
|
+
const base = `repos/${repoName(repo)}`;
|
|
106
|
+
const info = z.looseObject({ default_branch: z.string() }).parse(await gh(base));
|
|
107
|
+
const claimed = new Map();
|
|
108
|
+
const take = [];
|
|
109
|
+
const refused = [];
|
|
110
|
+
for (const member of [...members].filter((m) => m.train === null).sort((a, b) => a.number - b.number)) {
|
|
111
|
+
const refuse = (why) => void refused.push({ member, why });
|
|
112
|
+
if (member.base !== info.default_branch) {
|
|
113
|
+
refuse(`it targets ${member.base}, not ${info.default_branch}`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const runs = checkRunsSchema.parse(await gh(`${base}/commits/${member.headSha}/check-runs?per_page=100`));
|
|
117
|
+
const settled = checksSettled(githubChecks(runs.check_runs));
|
|
118
|
+
if (settled === null) {
|
|
119
|
+
refuse('its checks are still running');
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (settled.state !== 'passed') {
|
|
123
|
+
refuse(settled.state === 'failed' ? `its checks failed: ${settled.failed.join(', ')}` : 'its checks expired');
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const files = z.array(fileSchema).parse(await gh(`${base}/pulls/${member.number}/files?per_page=100`));
|
|
127
|
+
const paths = files.flatMap((f) => [f.filename, ...(f.previous_filename ? [f.previous_filename] : [])]);
|
|
128
|
+
const shared = paths.find((p) => claimed.has(p));
|
|
129
|
+
if (shared !== undefined) {
|
|
130
|
+
refuse(`it changes ${shared}, as #${claimed.get(shared)} does`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (const p of paths)
|
|
134
|
+
claimed.set(p, member.number);
|
|
135
|
+
take.push(member);
|
|
136
|
+
}
|
|
137
|
+
return { base: info.default_branch, refused, take };
|
|
138
|
+
}
|
|
139
|
+
// The open PRs that carry a Train note, and the open trains with the members they carry.
|
|
140
|
+
export async function readRoster(gh, repo, prefixes) {
|
|
141
|
+
const base = `repos/${repoName(repo)}`;
|
|
142
|
+
const open = z.array(pullSchema).parse(await gh(`${base}/pulls?state=open&per_page=100`));
|
|
143
|
+
const isTrain = (p) => p.head.ref.startsWith(BRANCH_PREFIX) && p.title.startsWith(TRAIN_TITLE);
|
|
144
|
+
const trains = open.filter(isTrain).map((p) => ({
|
|
145
|
+
members: [...(p.body ?? '').matchAll(MEMBER_LINE)].map((m) => Number(m[1])),
|
|
146
|
+
number: p.number,
|
|
147
|
+
url: p.html_url,
|
|
148
|
+
}));
|
|
149
|
+
const carriedBy = new Map(trains.flatMap((t) => t.members.map((n) => [n, t.number])));
|
|
150
|
+
const members = [];
|
|
151
|
+
for (const pr of open) {
|
|
152
|
+
if (isTrain(pr))
|
|
153
|
+
continue;
|
|
154
|
+
const comments = z.array(commentSchema).parse(await gh(`${base}/issues/${pr.number}/comments?per_page=100`));
|
|
155
|
+
const marks = comments.flatMap((c) => {
|
|
156
|
+
const heading = noteHeading(c.body ?? '', prefixes);
|
|
157
|
+
return heading?.phase === TRAIN_PHASE ? [{ at: c.created_at, heading }] : [];
|
|
158
|
+
});
|
|
159
|
+
const first = marks[0];
|
|
160
|
+
if (!first)
|
|
161
|
+
continue;
|
|
162
|
+
const urgent = marks.find((m) => m.heading.kind === 'urgent');
|
|
163
|
+
members.push({
|
|
164
|
+
base: pr.base.ref,
|
|
165
|
+
headSha: pr.head.sha,
|
|
166
|
+
markedAt: first.at,
|
|
167
|
+
number: pr.number,
|
|
168
|
+
title: pr.title,
|
|
169
|
+
train: carriedBy.get(pr.number) ?? null,
|
|
170
|
+
urgent: urgent ? (urgent.heading.line ?? 'urgent') : null,
|
|
171
|
+
url: pr.html_url,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return { members, trains };
|
|
175
|
+
}
|
|
176
|
+
export function refusalLines(refused) {
|
|
177
|
+
return refused.map((r) => `#${r.member.number} (${r.member.title}) stays out: ${r.why}`);
|
|
178
|
+
}
|
|
179
|
+
export function rosterLines(roster, settings, now) {
|
|
180
|
+
const lines = roster.trains.map((t) => `train #${t.number} waits on your merge: ${t.url} (${t.members.length} members)`);
|
|
181
|
+
const waiting = roster.members.filter((m) => m.train === null);
|
|
182
|
+
if (waiting.length === 0)
|
|
183
|
+
return [...lines, 'no publish waits for a train'];
|
|
184
|
+
const ask = trainAsk(roster.members, settings, now);
|
|
185
|
+
return [
|
|
186
|
+
...lines,
|
|
187
|
+
`${waiting.length} ${plural(waiting.length, 'publish waits', 'publishes wait')} for a train${ask ? ` (${ask})` : ` (the daemon asks at ${settings.minimum}, or after ${settings.maxHours} h)`}:`,
|
|
188
|
+
...waiting.map((m) => ` #${m.number} ${m.title}${m.urgent ? ` [urgent: ${m.urgent}]` : ''}, marked ${m.markedAt}`),
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
// Why Jason should run a train now, or null: an urgent member, enough members, or one that has
|
|
192
|
+
// waited too long. Members already on an open train don't count.
|
|
193
|
+
export function trainAsk(members, settings, now) {
|
|
194
|
+
const waiting = members.filter((m) => m.train === null);
|
|
195
|
+
if (waiting.length === 0)
|
|
196
|
+
return null;
|
|
197
|
+
const urgent = waiting.find((m) => m.urgent !== null);
|
|
198
|
+
if (urgent)
|
|
199
|
+
return `#${urgent.number} is urgent: ${urgent.urgent}`;
|
|
200
|
+
if (waiting.length >= settings.minimum)
|
|
201
|
+
return `the minimum of ${settings.minimum} is reached`;
|
|
202
|
+
const oldest = waiting.reduce((a, b) => (Date.parse(a.markedAt) <= Date.parse(b.markedAt) ? a : b));
|
|
203
|
+
const hours = (now.getTime() - Date.parse(oldest.markedAt)) / HOUR_MS;
|
|
204
|
+
// No running count in the text: the inbox item would be replaced, and pushed again, every hour.
|
|
205
|
+
return hours >= settings.maxHours ? `#${oldest.number} has waited ${settings.maxHours} h or more` : null;
|
|
206
|
+
}
|
|
207
|
+
// The train's repo from the profile, or a ConductorError that says what's missing.
|
|
208
|
+
export function trainRepo(profile, project) {
|
|
209
|
+
const settings = profile.train;
|
|
210
|
+
if (!settings)
|
|
211
|
+
throw new ConductorError(`${project}'s profile has no train`, ['add "train": { "repo": … }']);
|
|
212
|
+
const repo = githubRepoRef(settings.repo);
|
|
213
|
+
if (!repo)
|
|
214
|
+
throw new ConductorError(`train.repo ${settings.repo} is not a GitHub owner/repo`);
|
|
215
|
+
return { repo, settings };
|
|
216
|
+
}
|
|
217
|
+
// The daemon's side: for each project with a train, one `jason` item while publishes should ride a
|
|
218
|
+
// train, and one `merge` item per open train, each resolved once it no longer holds.
|
|
219
|
+
export function trainWatch(deps) {
|
|
220
|
+
const { inbox, log, store } = deps;
|
|
221
|
+
const gh = deps.gh ?? ghApi();
|
|
222
|
+
const now = deps.now ?? (() => new Date());
|
|
223
|
+
const failing = new Map();
|
|
224
|
+
let running = null;
|
|
225
|
+
let first;
|
|
226
|
+
let timer;
|
|
227
|
+
const ours = (project) => inbox.open().filter((i) => i.project === project && i.item === null && isTrainItem(i));
|
|
228
|
+
const sync = async (project, settings, repo) => {
|
|
229
|
+
const roster = await readRoster(gh, repo, notePrefixes(project.profile));
|
|
230
|
+
const wanted = new Map();
|
|
231
|
+
const ask = trainAsk(roster.members, settings, now());
|
|
232
|
+
if (ask !== null) {
|
|
233
|
+
const waiting = roster.members.filter((m) => m.train === null);
|
|
234
|
+
wanted.set('ask', {
|
|
235
|
+
commands: [`conduct train ${project.name}`],
|
|
236
|
+
condition: null,
|
|
237
|
+
item: null,
|
|
238
|
+
kind: 'jason',
|
|
239
|
+
links: [`https://github.com/${repoName(repo)}/pulls`],
|
|
240
|
+
project: project.name,
|
|
241
|
+
text: `${waiting.length} skill ${plural(waiting.length, 'publish waits', 'publishes wait')} for one merge (${ask}): ${waiting.map((m) => `#${m.number}`).join(', ')}`,
|
|
242
|
+
worker: null,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
for (const t of roster.trains) {
|
|
246
|
+
wanted.set(t.url, {
|
|
247
|
+
commands: [],
|
|
248
|
+
condition: null,
|
|
249
|
+
item: null,
|
|
250
|
+
kind: 'merge',
|
|
251
|
+
links: [t.url],
|
|
252
|
+
project: project.name,
|
|
253
|
+
text: `skill train #${t.number} (${t.members.length} ${plural(t.members.length, 'publish', 'publishes')}) waits on your merge, with a merge commit`,
|
|
254
|
+
worker: null,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const open = ours(project.name);
|
|
258
|
+
for (const item of open) {
|
|
259
|
+
const still = wanted.get(keyOf(item));
|
|
260
|
+
if (still?.text === item.text)
|
|
261
|
+
wanted.delete(keyOf(item));
|
|
262
|
+
else
|
|
263
|
+
inbox.resolve(item, still ? 'replaced by a newer train check' : 'the train check no longer holds');
|
|
264
|
+
}
|
|
265
|
+
for (const entry of wanted.values())
|
|
266
|
+
inbox.raise(entry);
|
|
267
|
+
};
|
|
268
|
+
const checkAll = async () => {
|
|
269
|
+
for (const project of store.listProjects()) {
|
|
270
|
+
const settings = project.enabled ? project.profile?.train : undefined;
|
|
271
|
+
const repo = githubRepoRef(settings?.repo);
|
|
272
|
+
if (!settings || !repo) {
|
|
273
|
+
for (const item of ours(project.name))
|
|
274
|
+
inbox.resolve(item, 'the project has no train now');
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
await sync(project, settings, repo);
|
|
279
|
+
failing.delete(project.name);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
283
|
+
if (failing.get(project.name) === message)
|
|
284
|
+
continue;
|
|
285
|
+
failing.set(project.name, message);
|
|
286
|
+
log.log('warn', 'the train check failed', { error: message, project: project.name });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
const check = () => {
|
|
291
|
+
running ??= checkAll().finally(() => (running = null));
|
|
292
|
+
return running;
|
|
293
|
+
};
|
|
294
|
+
return {
|
|
295
|
+
check,
|
|
296
|
+
// The first check comes a minute after the start, once the daemon has settled.
|
|
297
|
+
start: () => {
|
|
298
|
+
const safeCheck = () => {
|
|
299
|
+
check().catch((error) => log.log('error', 'the train check failed', { error: String(error) }));
|
|
300
|
+
};
|
|
301
|
+
first = setTimeout(safeCheck, FIRST_CHECK_MS);
|
|
302
|
+
first.unref();
|
|
303
|
+
timer = setInterval(safeCheck, CHECK_MS);
|
|
304
|
+
timer.unref();
|
|
305
|
+
},
|
|
306
|
+
stop: async () => {
|
|
307
|
+
clearTimeout(first);
|
|
308
|
+
clearInterval(timer);
|
|
309
|
+
timer = undefined;
|
|
310
|
+
await running;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
function isTrainItem(item) {
|
|
315
|
+
return ((item.kind === 'jason' && item.commands[0]?.startsWith('conduct train ') === true) ||
|
|
316
|
+
(item.kind === 'merge' && item.text.startsWith('skill train #')));
|
|
317
|
+
}
|
|
318
|
+
function keyOf(item) {
|
|
319
|
+
return item.kind === 'jason' ? 'ask' : (item.links[0] ?? '');
|
|
320
|
+
}
|
|
321
|
+
function messageOf(error) {
|
|
322
|
+
return error instanceof Error ? error.message : String(error);
|
|
323
|
+
}
|
|
324
|
+
function plural(n, one, many) {
|
|
325
|
+
return n === 1 ? one : many;
|
|
326
|
+
}
|
|
327
|
+
// UTC, to the second: train/20261014-154000.
|
|
328
|
+
function stamp(at) {
|
|
329
|
+
const iso = at.toISOString();
|
|
330
|
+
return `${iso.slice(0, 10).replace(/-/g, '')}-${iso.slice(11, 19).replace(/:/g, '')}`;
|
|
331
|
+
}
|
|
332
|
+
function trainBody(plan) {
|
|
333
|
+
return [
|
|
334
|
+
`${plan.take.length} registry ${plural(plan.take.length, 'publish', 'publishes')} in one merge (\`conduct train\`, ATK-285). Merge with a merge commit: each member below then shows as merged.`,
|
|
335
|
+
'',
|
|
336
|
+
...plan.take.map((m) => `- #${m.number} ${m.title}`),
|
|
337
|
+
...(plan.refused.length > 0
|
|
338
|
+
? ['', 'Left out:', '', ...plan.refused.map((r) => `* #${r.member.number}: ${r.why}`)]
|
|
339
|
+
: []),
|
|
340
|
+
].join('\n');
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=train.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"train.js","sourceRoot":"","sources":["../src/train.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
import { ConductorError } from "../errors.js";
|
|
2
|
+
import {} from "../lib/providers/index.js";
|
|
2
3
|
import { outputOf, run } from "../run.js";
|
|
4
|
+
export { adoRequest } from "../lib/providers/index.js";
|
|
3
5
|
// Azure DevOps' resource id, for `az account get-access-token`.
|
|
4
6
|
const ADO_RESOURCE = '499b84ac-1321-427f-aa17-267ca6975798';
|
|
5
7
|
// An az token lasts about an hour; a fresh one well before that.
|
|
6
8
|
const TOKEN_MAX_AGE_MS = 20 * 60_000;
|
|
7
|
-
const TIMEOUT_MS = 20_000;
|
|
8
|
-
// One request to Azure DevOps with the az token, tried again once with a renewed token when ADO
|
|
9
|
-
// refuses it: 401, or 203, its sign-in page for a token it doesn't take. Throws only when fetch
|
|
10
|
-
// does (network, timeout); every status is the caller's to read.
|
|
11
|
-
export async function adoRequest(fetcher, token, url, { body, contentType = 'application/json', method = 'GET', onRetry } = {}) {
|
|
12
|
-
for (let renewed = false;; renewed = true) {
|
|
13
|
-
const response = await fetcher(url, {
|
|
14
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
15
|
-
cache: 'no-store',
|
|
16
|
-
headers: {
|
|
17
|
-
authorization: `Bearer ${await token(renewed)}`,
|
|
18
|
-
...(body === undefined ? {} : { 'content-type': contentType }),
|
|
19
|
-
},
|
|
20
|
-
method,
|
|
21
|
-
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
22
|
-
});
|
|
23
|
-
if ((response.status === 401 || response.status === 203) && !renewed) {
|
|
24
|
-
onRetry?.();
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const text = await response.text();
|
|
28
|
-
let parsed;
|
|
29
|
-
try {
|
|
30
|
-
parsed = text ? JSON.parse(text) : null;
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
parsed = null;
|
|
34
|
-
}
|
|
35
|
-
return { body: parsed, status: response.status };
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
9
|
// The az CLI's token for Azure DevOps, kept for 20 minutes and renewed on a 401. `az` is a .cmd on
|
|
39
10
|
// Windows, so it runs through a shell; its arguments are constants.
|
|
40
11
|
export function azToken({ now = Date.now } = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ado-api.js","sourceRoot":"","sources":["../../src/watchers/ado-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"ado-api.js","sourceRoot":"","sources":["../../src/watchers/ado-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAiB,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAmB,UAAU,EAAiB,MAAM,2BAA2B,CAAC;AAEvF,gEAAgE;AAChE,MAAM,YAAY,GAAG,sCAAsC,CAAC;AAC5D,iEAAiE;AACjE,MAAM,gBAAgB,GAAG,EAAE,GAAG,MAAM,CAAC;AAErC,mGAAmG;AACnG,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,KAA6B,EAAE;IACrE,IAAI,MAAM,GAAyC,IAAI,CAAC;IACxD,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,GAAG,gBAAgB;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QAClF,MAAM,MAAM,GAAG,MAAM,GAAG,CACtB,IAAI,EACJ,CAAC,SAAS,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,CAAC,EAClG;YACE,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;YACnC,SAAS,EAAE,MAAM;SAClB,CACF,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACnC,mGAAmG;QACnG,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,IAAI,cAAc,CAAC,oCAAoC,EAAE,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1G,CAAC;QACD,MAAM,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
|
package/build/watchers/ado.js
CHANGED
|
@@ -1,91 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { adoCodeHost, adoHttp, adoPipelines, checksSettled, needsAnswer, } from "../lib/providers/index.js";
|
|
2
2
|
import { notePrefixes } from "../profile.js";
|
|
3
|
-
import {
|
|
3
|
+
import { azToken } from "./ado-api.js";
|
|
4
4
|
import { boardsWaits } from "./boards.js";
|
|
5
|
-
import {
|
|
6
|
-
import { perPass, perWait, spend, untilSpent, WaitProblem, } from "./source.js";
|
|
5
|
+
import { checksLine, perPass, perWait, spend, untilSpent, WaitProblem, } from "./source.js";
|
|
7
6
|
export { azToken } from "./ado-api.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
status: z.string(),
|
|
15
|
-
targetRefName: z.string().optional(),
|
|
16
|
-
});
|
|
17
|
-
const evaluation = z.looseObject({
|
|
18
|
-
configuration: z
|
|
19
|
-
.looseObject({
|
|
20
|
-
isBlocking: z.boolean().optional(),
|
|
21
|
-
isEnabled: z.boolean().optional(),
|
|
22
|
-
type: z.looseObject({ displayName: z.string().optional() }).optional(),
|
|
23
|
-
})
|
|
24
|
-
.optional(),
|
|
25
|
-
context: z
|
|
26
|
-
.looseObject({
|
|
27
|
-
buildDefinitionName: z.string().optional(),
|
|
28
|
-
buildIsNotCurrent: z.boolean().optional(),
|
|
29
|
-
isExpired: z.boolean().optional(),
|
|
30
|
-
lastMergeSourceCommitId: z.string().optional(),
|
|
31
|
-
})
|
|
32
|
-
.nullish(),
|
|
33
|
-
status: z.string(),
|
|
34
|
-
});
|
|
35
|
-
const thread = z.looseObject({
|
|
36
|
-
comments: z
|
|
37
|
-
.array(z.looseObject({
|
|
38
|
-
commentType: z.string().optional(),
|
|
39
|
-
content: z.string().nullish(),
|
|
40
|
-
isDeleted: z.boolean().optional(),
|
|
41
|
-
}))
|
|
42
|
-
.optional(),
|
|
43
|
-
id: z.number(),
|
|
44
|
-
isDeleted: z.boolean().optional(),
|
|
45
|
-
properties: z.looseObject({ CodeReviewThreadType: z.unknown().optional() }).nullish(),
|
|
46
|
-
status: z.string().optional(),
|
|
47
|
-
});
|
|
48
|
-
const build = z.looseObject({ result: z.string().nullish(), status: z.string() });
|
|
49
|
-
const list = (item) => z.looseObject({ value: z.array(item) });
|
|
50
|
-
// ADO PRs (merged, checks, comment) and pipeline runs (done), over REST, and the waits on ADO Boards
|
|
51
|
-
// items (approval, item), so all of them share one budget. The organization and project come from
|
|
52
|
-
// the waiting worker's profile. A pass reads each PR once, and its evaluations and threads only
|
|
53
|
-
// when a wait needs them. A PR wait whose condition doesn't hold yet reports the PR's merge state,
|
|
54
|
-
// so a conflict with the base branch, which no build runs on, can wake its worker (conflicts.ts).
|
|
7
|
+
// ADO PRs (merged, checks, comment) and pipeline runs (done), read through the providers' Azure
|
|
8
|
+
// Repos and Azure Pipelines adapters, and the waits on ADO Boards items (approval, item), so all of
|
|
9
|
+
// them share one budget. The organization and project come from the waiting worker's profile. A
|
|
10
|
+
// pass reads each PR once, and its evaluations and threads only when a wait needs them. A PR wait
|
|
11
|
+
// whose condition doesn't hold yet reports the PR's merge state, so a conflict with the base
|
|
12
|
+
// branch, which no build runs on, can wake its worker (conflicts.ts).
|
|
55
13
|
export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken(), } = {}) {
|
|
56
|
-
// The first try's request was taken from the budget by the caller; a retry with a renewed token
|
|
57
|
-
// takes one more.
|
|
58
|
-
const get = async (budget, base, path, version = '7.1') => {
|
|
59
|
-
const url = `${base}/_apis/${path}${path.includes('?') ? '&' : '?'}api-version=${version}`;
|
|
60
|
-
const { body, status } = await adoRequest(fetcher, token, url, { onRetry: () => spend(budget) });
|
|
61
|
-
if (status === 404 || status === 400)
|
|
62
|
-
throw new WaitProblem(`Azure DevOps answered ${status} for ${path}`);
|
|
63
|
-
if (status !== 200)
|
|
64
|
-
throw new Error(`Azure DevOps answered ${status} for ${path}`);
|
|
65
|
-
return body;
|
|
66
|
-
};
|
|
67
14
|
const boards = boardsWaits({ fetch: fetcher, token });
|
|
68
15
|
return {
|
|
69
16
|
check: async (waits, budget) => {
|
|
70
17
|
const outcomes = new Map();
|
|
71
|
-
|
|
72
|
-
const
|
|
18
|
+
// A retry with a renewed token takes one more request from the budget.
|
|
19
|
+
const http = perPassHttp(adoHttp({ fetch: fetcher, onRetry: () => spend(budget), token }), budget);
|
|
20
|
+
const host = adoCodeHost(http);
|
|
21
|
+
const ci = adoPipelines(http);
|
|
73
22
|
await untilSpent(async () => {
|
|
74
23
|
for (const wait of waits) {
|
|
75
24
|
const c = wait.condition;
|
|
76
25
|
if ((c.kind !== 'pr' || c.host !== 'ado') && c.kind !== 'run')
|
|
77
26
|
continue;
|
|
78
27
|
await perWait(outcomes, wait.id, async () => {
|
|
79
|
-
const
|
|
28
|
+
const repo = repoOf(wait.profile);
|
|
80
29
|
if (c.kind === 'run') {
|
|
81
|
-
const
|
|
82
|
-
return
|
|
30
|
+
const run = await ci.run({ id: c.id, repo });
|
|
31
|
+
return run.status === 'completed' ? `run ${c.pipeline} ${c.id} ${run.result ?? 'completed'}` : null;
|
|
83
32
|
}
|
|
84
|
-
const
|
|
85
|
-
const pr =
|
|
86
|
-
if (pr.
|
|
33
|
+
const ref = { number: c.number, repo: { ...repo, repository: c.repo } };
|
|
34
|
+
const pr = await host.pr(ref);
|
|
35
|
+
if (pr.state === 'merged')
|
|
87
36
|
return `pr ${c.number} merged`;
|
|
88
|
-
if (pr.
|
|
37
|
+
if (pr.state === 'closed')
|
|
89
38
|
return `pr ${c.number} abandoned`;
|
|
90
39
|
const merge = mergeOf(pr);
|
|
91
40
|
const found = (resolution) => resolution === null
|
|
@@ -99,13 +48,10 @@ export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken()
|
|
|
99
48
|
// No build runs on a conflicted PR, so its evaluations can only be stale: not asked.
|
|
100
49
|
if (merge?.conflicts)
|
|
101
50
|
return found(null);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const verdict = checksVerdict(pr, value);
|
|
105
|
-
return found(verdict === null ? null : `pr ${c.number} ${verdict}`);
|
|
51
|
+
const settled = checksSettled(await host.checks(ref));
|
|
52
|
+
return found(settled === null ? null : `pr ${c.number} ${checksLine(settled)}`);
|
|
106
53
|
}
|
|
107
|
-
const
|
|
108
|
-
const open = openReviewThreads(value, notePrefixes(wait.profile));
|
|
54
|
+
const open = (await host.threads(ref, { notePrefixes: notePrefixes(wait.profile) })).filter(needsAnswer);
|
|
109
55
|
return found(open.length > 0
|
|
110
56
|
? `pr ${c.number} comment: ${open.length} open thread${open.length === 1 ? '' : 's'}`
|
|
111
57
|
: null);
|
|
@@ -132,56 +78,27 @@ export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken()
|
|
|
132
78
|
name: 'ado',
|
|
133
79
|
};
|
|
134
80
|
}
|
|
135
|
-
// The PR's blocking builds as one verdict once none of them runs, or null while one does (or none
|
|
136
|
-
// has been evaluated yet). A path-filtered build the PR doesn't touch is `notApplicable`, which
|
|
137
|
-
// passes, as `land` reads it. Right after a push an evaluation can still describe the previous commit.
|
|
138
|
-
export function checksVerdict(pr, evaluations) {
|
|
139
|
-
const source = pr.lastMergeSourceCommit?.commitId;
|
|
140
|
-
const builds = evaluations.filter((e) => e.configuration?.isEnabled &&
|
|
141
|
-
e.configuration.isBlocking &&
|
|
142
|
-
e.configuration.type?.displayName === 'Build' &&
|
|
143
|
-
!e.context?.buildIsNotCurrent &&
|
|
144
|
-
(!source || !e.context?.lastMergeSourceCommitId || e.context.lastMergeSourceCommitId === source));
|
|
145
|
-
const finished = new Set(['approved', 'broken', 'notApplicable', 'rejected']);
|
|
146
|
-
const live = builds.filter((e) => !e.context?.isExpired);
|
|
147
|
-
if (builds.length === 0 || live.some((e) => !finished.has(e.status)))
|
|
148
|
-
return null;
|
|
149
|
-
if (live.length === 0)
|
|
150
|
-
return 'checks expired';
|
|
151
|
-
const failed = live
|
|
152
|
-
.filter((e) => e.status === 'rejected' || e.status === 'broken')
|
|
153
|
-
.map((e) => e.context?.buildDefinitionName ?? 'a build');
|
|
154
|
-
return failed.length > 0 ? `checks failed: ${failed.join(', ')}` : 'checks passed';
|
|
155
|
-
}
|
|
156
81
|
// An open PR's merge state, from what ADO last worked out: `conflicts`, or `succeeded` for a clean
|
|
157
82
|
// merge. Null while it works one out (`queued`, `notSet`), for a failure that isn't a conflict, or
|
|
158
83
|
// with no target branch to name in the wake's `git merge`.
|
|
159
|
-
|
|
160
|
-
if ((pr.
|
|
84
|
+
function mergeOf(pr) {
|
|
85
|
+
if ((pr.merge !== 'conflicts' && pr.merge !== 'clean') || !pr.base)
|
|
161
86
|
return null;
|
|
87
|
+
return { base: pr.base, commit: pr.sourceCommit, conflicts: pr.merge === 'conflicts' };
|
|
88
|
+
}
|
|
89
|
+
// The adapters' GETs, each URL asked once per pass and taken from the budget first.
|
|
90
|
+
function perPassHttp(http, budget) {
|
|
91
|
+
const json = perPass(budget);
|
|
92
|
+
const text = perPass(budget);
|
|
162
93
|
return {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
conflicts: pr.mergeStatus === 'conflicts',
|
|
94
|
+
json: (url) => json(url, () => http.json(url)),
|
|
95
|
+
text: (url) => text(url, () => http.text(url)),
|
|
166
96
|
};
|
|
167
97
|
}
|
|
168
|
-
|
|
169
|
-
// work-skill note: what a worker has to answer. Every comment here can carry the same identity, so
|
|
170
|
-
// a note heading (`noteHeading`, under any of `notePrefixes`), not the author, marks the
|
|
171
|
-
// worker's own (the work skill's `land` reads them the same way).
|
|
172
|
-
export function openReviewThreads(threads, notePrefixes) {
|
|
173
|
-
const done = new Set(['byDesign', 'closed', 'fixed', 'wontFix']);
|
|
174
|
-
return threads
|
|
175
|
-
.filter((t) => !t.isDeleted && !done.has(t.status ?? '') && !t.properties?.CodeReviewThreadType)
|
|
176
|
-
.filter((t) => {
|
|
177
|
-
const comments = (t.comments ?? []).filter((c) => !c.isDeleted && c.commentType !== 'system');
|
|
178
|
-
return comments.length > 0 && noteHeading(comments.at(-1)?.content ?? '', notePrefixes) === null;
|
|
179
|
-
})
|
|
180
|
-
.map((t) => t.id);
|
|
181
|
-
}
|
|
182
|
-
function baseOf(profile) {
|
|
98
|
+
function repoOf(profile) {
|
|
183
99
|
if (profile?.codeHost.kind !== 'ado')
|
|
184
100
|
throw new WaitProblem("the project's profile has no Azure DevOps code host");
|
|
185
|
-
|
|
101
|
+
const { organization, project, repository } = profile.codeHost;
|
|
102
|
+
return { host: 'ado', organization, project, repository };
|
|
186
103
|
}
|
|
187
104
|
//# sourceMappingURL=ado.js.map
|