@detergent-software/conductor 0.0.0-stage → 1.0.0-dev.2
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 +31 -0
- package/README.md +131 -2
- package/build/claude.js +102 -0
- package/build/claude.js.map +1 -0
- package/build/cli.js +450 -0
- package/build/cli.js.map +1 -0
- package/build/client.js +64 -0
- package/build/client.js.map +1 -0
- package/build/conditions.js +198 -0
- package/build/conditions.js.map +1 -0
- package/build/config.js +74 -0
- package/build/config.js.map +1 -0
- package/build/daemon.js +127 -0
- package/build/daemon.js.map +1 -0
- package/build/errors.js +17 -0
- package/build/errors.js.map +1 -0
- package/build/jsonc.js +24 -0
- package/build/jsonc.js.map +1 -0
- package/build/lock.js +64 -0
- package/build/lock.js.map +1 -0
- package/build/log.js +43 -0
- package/build/log.js.map +1 -0
- package/build/manager.js +614 -0
- package/build/manager.js.map +1 -0
- package/build/paths.js +16 -0
- package/build/paths.js.map +1 -0
- package/build/profile.js +74 -0
- package/build/profile.js.map +1 -0
- package/build/registry.js +33 -0
- package/build/registry.js.map +1 -0
- package/build/resolve.js +28 -0
- package/build/resolve.js.map +1 -0
- package/build/run.js +52 -0
- package/build/run.js.map +1 -0
- package/build/server.js +227 -0
- package/build/server.js.map +1 -0
- package/build/spend.js +134 -0
- package/build/spend.js.map +1 -0
- package/build/startup.js +78 -0
- package/build/startup.js.map +1 -0
- package/build/store.js +528 -0
- package/build/store.js.map +1 -0
- package/build/tab.js +26 -0
- package/build/tab.js.map +1 -0
- package/build/token.js +58 -0
- package/build/token.js.map +1 -0
- package/build/upgrade.js +93 -0
- package/build/upgrade.js.map +1 -0
- package/build/wait.js +149 -0
- package/build/wait.js.map +1 -0
- package/build/watchers/ado.js +167 -0
- package/build/watchers/ado.js.map +1 -0
- package/build/watchers/github.js +82 -0
- package/build/watchers/github.js.map +1 -0
- package/build/watchers/health.js +42 -0
- package/build/watchers/health.js.map +1 -0
- package/build/watchers/index.js +226 -0
- package/build/watchers/index.js.map +1 -0
- package/build/watchers/linear-key.js +56 -0
- package/build/watchers/linear-key.js.map +1 -0
- package/build/watchers/linear.js +218 -0
- package/build/watchers/linear.js.map +1 -0
- package/build/watchers/npm.js +35 -0
- package/build/watchers/npm.js.map +1 -0
- package/build/watchers/source.js +94 -0
- package/build/watchers/source.js.map +1 -0
- package/build/worktrees.js +143 -0
- package/build/worktrees.js.map +1 -0
- package/package.json +56 -5
package/build/upgrade.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { posix, win32 } from 'node:path';
|
|
2
|
+
import { ConductorError } from "./errors.js";
|
|
3
|
+
import { outputOf } from "./run.js";
|
|
4
|
+
import { TASK_NAME } from "./startup.js";
|
|
5
|
+
export const PACKAGE = '@detergent-software/conductor';
|
|
6
|
+
export const TAGS = ['dev', 'latest'];
|
|
7
|
+
// A -dev.N version came from `dev` (develop); anything else from `latest` (main).
|
|
8
|
+
export function defaultTag(version) {
|
|
9
|
+
return /-dev\.\d+$/.test(version) ? 'dev' : 'latest';
|
|
10
|
+
}
|
|
11
|
+
// True when cliPath lies under npm's global node_modules. A linked build resolves to its checkout.
|
|
12
|
+
export function isGlobalInstall(cliPath, npmRoot, platform = process.platform) {
|
|
13
|
+
const path = platform === 'win32' ? win32 : posix;
|
|
14
|
+
const rel = path.relative(npmRoot, cliPath);
|
|
15
|
+
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
16
|
+
}
|
|
17
|
+
export function parseTag(tag) {
|
|
18
|
+
if (tag === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const known = TAGS.find((t) => t === tag);
|
|
21
|
+
if (known)
|
|
22
|
+
return known;
|
|
23
|
+
throw new ConductorError(`--tag takes ${TAGS.join(' or ')}, not "${tag}"`);
|
|
24
|
+
}
|
|
25
|
+
// Stop, install, restart. Refuses a linked build before it touches anything, does nothing when the
|
|
26
|
+
// tag's version is already installed, and starts the daemon again only if it stopped one: through
|
|
27
|
+
// the logon task when that runs this cli.js (a global install keeps the path), else detached.
|
|
28
|
+
export async function upgrade({ tag }, deps) {
|
|
29
|
+
const root = await deps.npmRoot();
|
|
30
|
+
if (!isGlobalInstall(deps.cliPath, root)) {
|
|
31
|
+
throw new ConductorError('this conduct is not an npm global install', [
|
|
32
|
+
`${deps.cliPath} is outside ${root}`,
|
|
33
|
+
'a linked build: pull, `pnpm -C apps/conductor build`, then `conduct stop` and `conduct serve`',
|
|
34
|
+
`another package manager: upgrade ${PACKAGE} with it, then \`conduct stop\` and \`conduct serve\``,
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
const from = deps.installedVersion();
|
|
38
|
+
const chosen = tag ?? defaultTag(from);
|
|
39
|
+
const spec = `${PACKAGE}@${chosen}`;
|
|
40
|
+
const target = await deps.npmView(spec);
|
|
41
|
+
if (target === from) {
|
|
42
|
+
deps.out(`already on ${from}, the newest on ${chosen}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const stopped = await deps.stopDaemon();
|
|
46
|
+
deps.out(`installing ${spec} (${target})`);
|
|
47
|
+
const installed = await deps.npmInstall(spec);
|
|
48
|
+
// Restart before reporting a failed install, so the daemon runs again on whatever npm left in
|
|
49
|
+
// place.
|
|
50
|
+
let restart = null;
|
|
51
|
+
let restartError = null;
|
|
52
|
+
if (stopped) {
|
|
53
|
+
try {
|
|
54
|
+
restart = await restartDaemon(deps);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
restartError = error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (installed.code !== 0) {
|
|
61
|
+
throw new ConductorError(`npm could not install ${spec}`, [
|
|
62
|
+
outputOf(installed, 1000),
|
|
63
|
+
...(restart ? [`started the conductor again ${restart}`] : []),
|
|
64
|
+
...(restartError ? [`could not start the conductor again: ${messageOf(restartError)}`] : []),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
if (restartError) {
|
|
68
|
+
throw new ConductorError(`installed ${deps.installedVersion()}, but the conductor did not start again`, [
|
|
69
|
+
messageOf(restartError),
|
|
70
|
+
'start it with `conduct serve`',
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
deps.out(`upgraded ${from} → ${deps.installedVersion()} (from ${chosen})`);
|
|
74
|
+
deps.out(restart ? `started the conductor ${restart}` : 'the conductor was not running; start it with `conduct serve`');
|
|
75
|
+
}
|
|
76
|
+
function messageOf(error) {
|
|
77
|
+
return error instanceof Error ? error.message : String(error);
|
|
78
|
+
}
|
|
79
|
+
async function restartDaemon(deps) {
|
|
80
|
+
if (deps.taskRunsCli()) {
|
|
81
|
+
try {
|
|
82
|
+
deps.runTask();
|
|
83
|
+
return `through the "${TASK_NAME}" logon task (pid ${await deps.awaitDaemon()})`;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// The task ignores a run while its last instance is still ending (MultipleInstances
|
|
87
|
+
// IgnoreNew), and still exits 0, so a daemon that doesn't answer is started directly.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
deps.serveDetached();
|
|
91
|
+
return `with conduct serve (pid ${await deps.awaitDaemon()})`;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=upgrade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"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"}
|
package/build/wait.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { basename, join, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { callApi, NotRunningError } from "./client.js";
|
|
5
|
+
import { formatCondition, fromVerbs, sourceOf } from "./conditions.js";
|
|
6
|
+
import { loadConfig } from "./config.js";
|
|
7
|
+
import { ConductorError } from "./errors.js";
|
|
8
|
+
import { fleetWorkerSchema } from "./manager.js";
|
|
9
|
+
import { PROFILE_FILE, readProfile } from "./profile.js";
|
|
10
|
+
import { run } from "./run.js";
|
|
11
|
+
import { projectSchema, waitSchema } from "./store.js";
|
|
12
|
+
import { waitLocally } from "./watchers/index.js";
|
|
13
|
+
import {} from "./watchers/source.js";
|
|
14
|
+
// Monitor ends a command after 30 minutes (ATK-273's spike), so a local wait gives up a minute before.
|
|
15
|
+
export const DEFAULT_TIMEOUT_MINUTES = 29;
|
|
16
|
+
// Exit code for a local wait that ran out of time: the condition doesn't hold yet. Arm it again.
|
|
17
|
+
export const TIMED_OUT = 2;
|
|
18
|
+
const fleetBody = z.object({ workers: z.array(fleetWorkerSchema) });
|
|
19
|
+
const projectsBody = z.object({ projects: z.array(projectSchema) });
|
|
20
|
+
const waitBody = z.object({ wait: waitSchema });
|
|
21
|
+
const errorBody = z.object({ details: z.array(z.string()).optional(), error: z.string() });
|
|
22
|
+
// `conduct wait <verb …> | <full form>`. In a worker's worktree with the daemon running, it
|
|
23
|
+
// registers the wait for that worker and returns at once: post the note, end the turn, and the
|
|
24
|
+
// daemon parks the session and resumes it when the condition holds. Otherwise it checks the
|
|
25
|
+
// condition itself and blocks until it holds (exit 0) or the timeout passes (exit 2), for a worker
|
|
26
|
+
// that runs it under Monitor. Returns the exit code.
|
|
27
|
+
export async function waitCommand(paths, args, options, io) {
|
|
28
|
+
const checkout = await gitToplevel(io.cwd);
|
|
29
|
+
const profile = checkout ? profileOf(checkout) : null;
|
|
30
|
+
if (!options.local) {
|
|
31
|
+
const worker = await daemonWorker(paths, checkout, options.worker);
|
|
32
|
+
if (worker === 'no-daemon') {
|
|
33
|
+
// Fall through to a local wait.
|
|
34
|
+
}
|
|
35
|
+
else if (worker === 'unreachable' || worker === null) {
|
|
36
|
+
io.err(worker === null
|
|
37
|
+
? 'the conductor runs, but no worker of its works in this checkout: waiting here instead'
|
|
38
|
+
: "the conductor didn't answer: waiting here instead");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// The worker's own worktree has its profile; `--worker` may run from another project's
|
|
42
|
+
// checkout, so it takes the worker's project profile as registered.
|
|
43
|
+
const known = options.worker === undefined ? (profile ?? (await registeredProfile(paths, worker.project))) : await registeredProfile(paths, worker.project);
|
|
44
|
+
const condition = fromVerbs(args, { item: options.item ?? worker.item, options, profile: known });
|
|
45
|
+
const { body, status } = await callApi(paths, {
|
|
46
|
+
body: { condition: formatCondition(condition), worker: worker.id },
|
|
47
|
+
method: 'POST',
|
|
48
|
+
path: '/waits',
|
|
49
|
+
});
|
|
50
|
+
if (status !== 201) {
|
|
51
|
+
const error = errorBody.safeParse(body);
|
|
52
|
+
throw new ConductorError(error.success ? error.data.error : `the API answered ${status}`, error.data?.details ?? []);
|
|
53
|
+
}
|
|
54
|
+
const { wait } = waitBody.parse(body);
|
|
55
|
+
io.out(`waiting on ${wait.condition} (wait ${wait.id} for ${worker.name}): end the turn; the conductor resumes this session when it holds`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const item = options.item ?? itemOf(checkout, profile);
|
|
60
|
+
const condition = fromVerbs(args, { item, options, profile });
|
|
61
|
+
const timeoutMinutes = options.timeoutMinutes ?? DEFAULT_TIMEOUT_MINUTES;
|
|
62
|
+
const { intervalMs, perMinute } = pace(paths, condition);
|
|
63
|
+
io.err(`waiting on ${formatCondition(condition)} here (no conductor), checking every ${Math.round(intervalMs / 1000)} s for up to ${timeoutMinutes} min`);
|
|
64
|
+
const resolution = await waitLocally({
|
|
65
|
+
condition,
|
|
66
|
+
intervalMs,
|
|
67
|
+
now: io.now,
|
|
68
|
+
perMinute,
|
|
69
|
+
profile,
|
|
70
|
+
report: io.err,
|
|
71
|
+
sleep: io.sleep,
|
|
72
|
+
sources: io.sources,
|
|
73
|
+
timeoutMs: timeoutMinutes * 60_000,
|
|
74
|
+
});
|
|
75
|
+
if (resolution === null) {
|
|
76
|
+
io.out(`still waiting on ${formatCondition(condition)} after ${timeoutMinutes} min`);
|
|
77
|
+
return TIMED_OUT;
|
|
78
|
+
}
|
|
79
|
+
io.out(`[conductor] event: ${resolution}`);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
// The fleet worker this checkout belongs to: `--worker` by name or item, else the one whose
|
|
83
|
+
// worktree is this checkout. 'no-daemon' when none runs; 'unreachable' when one may run but didn't
|
|
84
|
+
// answer (nothing was registered, so waiting here is safe).
|
|
85
|
+
async function daemonWorker(paths, checkout, ref) {
|
|
86
|
+
let workers;
|
|
87
|
+
try {
|
|
88
|
+
const { body, status } = await callApi(paths, { method: 'GET', path: '/fleet' });
|
|
89
|
+
if (status !== 200)
|
|
90
|
+
return 'unreachable';
|
|
91
|
+
workers = fleetBody.parse(body).workers;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof NotRunningError)
|
|
95
|
+
return 'no-daemon';
|
|
96
|
+
if (error instanceof ConductorError)
|
|
97
|
+
return 'unreachable';
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (ref !== undefined) {
|
|
101
|
+
const found = workers.filter((w) => w.name === ref).concat(workers.filter((w) => w.name !== ref && w.item === ref));
|
|
102
|
+
if (found.length === 0)
|
|
103
|
+
throw new ConductorError(`no worker "${ref}" in flight`, ['`conduct status` lists them']);
|
|
104
|
+
return found[0] ?? null;
|
|
105
|
+
}
|
|
106
|
+
if (checkout === null)
|
|
107
|
+
return null;
|
|
108
|
+
return workers.find((w) => w.worktree !== null && samePath(w.worktree, checkout)) ?? null;
|
|
109
|
+
}
|
|
110
|
+
async function gitToplevel(cwd) {
|
|
111
|
+
const result = await run('git', ['rev-parse', '--show-toplevel'], { cwd, timeoutMs: 30_000 });
|
|
112
|
+
return result.code === 0 && result.stdout.trim() ? resolve(result.stdout.trim()) : null;
|
|
113
|
+
}
|
|
114
|
+
// The item a checkout works: its worktree's name (`ATK-310`), or a key in its branch name.
|
|
115
|
+
function itemOf(checkout, profile) {
|
|
116
|
+
if (checkout === null)
|
|
117
|
+
return null;
|
|
118
|
+
const name = basename(checkout);
|
|
119
|
+
if (/^[A-Za-z]+-\d+$/.test(name))
|
|
120
|
+
return name.toUpperCase();
|
|
121
|
+
const team = profile?.tracker.team;
|
|
122
|
+
return team ? (new RegExp(`(?:^|[/_-])(${team}-\\d+)(?:$|[/_-])`, 'i').exec(name)?.[1]?.toUpperCase() ?? null) : null;
|
|
123
|
+
}
|
|
124
|
+
// How often and how hard a local wait may ask: the watchers' settings for its source.
|
|
125
|
+
function pace(paths, condition) {
|
|
126
|
+
const source = sourceOf(condition);
|
|
127
|
+
if (source === 'clock')
|
|
128
|
+
return { intervalMs: 30_000, perMinute: 1 };
|
|
129
|
+
const settings = loadConfig(paths.config).watchers[source];
|
|
130
|
+
return { intervalMs: settings.intervalSeconds * 1000, perMinute: settings.perMinute };
|
|
131
|
+
}
|
|
132
|
+
// A checkout without a profile can still wait on full forms that need none; a broken one is an error.
|
|
133
|
+
function profileOf(checkout) {
|
|
134
|
+
return existsSync(join(checkout, PROFILE_FILE)) ? readProfile(checkout).profile : null;
|
|
135
|
+
}
|
|
136
|
+
async function registeredProfile(paths, project) {
|
|
137
|
+
const { body, status } = await callApi(paths, { method: 'GET', path: '/projects' });
|
|
138
|
+
if (status !== 200)
|
|
139
|
+
return null;
|
|
140
|
+
return projectsBody.parse(body).projects.find((p) => p.name === project)?.profile ?? null;
|
|
141
|
+
}
|
|
142
|
+
function samePath(a, b) {
|
|
143
|
+
const norm = (p) => {
|
|
144
|
+
const full = resolve(p);
|
|
145
|
+
return process.platform === 'win32' ? full.toLowerCase() : full;
|
|
146
|
+
};
|
|
147
|
+
return norm(a) === norm(b);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wait.js","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ConductorError } from "../errors.js";
|
|
3
|
+
import { outputOf, run } from "../run.js";
|
|
4
|
+
import { getJson, perPass, perWait, spend, untilSpent, WaitProblem } from "./source.js";
|
|
5
|
+
// Azure DevOps' resource id, for `az account get-access-token`.
|
|
6
|
+
const ADO_RESOURCE = '499b84ac-1321-427f-aa17-267ca6975798';
|
|
7
|
+
// An az token lasts about an hour; a fresh one well before that.
|
|
8
|
+
const TOKEN_MAX_AGE_MS = 20 * 60_000;
|
|
9
|
+
const pullRequest = z.looseObject({
|
|
10
|
+
autoCompleteSetBy: z.unknown().optional(),
|
|
11
|
+
lastMergeSourceCommit: z.looseObject({ commitId: z.string() }).optional(),
|
|
12
|
+
pullRequestId: z.number(),
|
|
13
|
+
repository: z.looseObject({ project: z.looseObject({ id: z.string() }) }),
|
|
14
|
+
status: z.string(),
|
|
15
|
+
});
|
|
16
|
+
const evaluation = z.looseObject({
|
|
17
|
+
configuration: z
|
|
18
|
+
.looseObject({
|
|
19
|
+
isBlocking: z.boolean().optional(),
|
|
20
|
+
isEnabled: z.boolean().optional(),
|
|
21
|
+
type: z.looseObject({ displayName: z.string().optional() }).optional(),
|
|
22
|
+
})
|
|
23
|
+
.optional(),
|
|
24
|
+
context: z
|
|
25
|
+
.looseObject({
|
|
26
|
+
buildDefinitionName: z.string().optional(),
|
|
27
|
+
buildIsNotCurrent: z.boolean().optional(),
|
|
28
|
+
isExpired: z.boolean().optional(),
|
|
29
|
+
lastMergeSourceCommitId: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.nullish(),
|
|
32
|
+
status: z.string(),
|
|
33
|
+
});
|
|
34
|
+
const thread = z.looseObject({
|
|
35
|
+
comments: z
|
|
36
|
+
.array(z.looseObject({ commentType: z.string().optional(), content: z.string().nullish(), isDeleted: z.boolean().optional() }))
|
|
37
|
+
.optional(),
|
|
38
|
+
id: z.number(),
|
|
39
|
+
isDeleted: z.boolean().optional(),
|
|
40
|
+
properties: z.looseObject({ CodeReviewThreadType: z.unknown().optional() }).nullish(),
|
|
41
|
+
status: z.string().optional(),
|
|
42
|
+
});
|
|
43
|
+
const build = z.looseObject({ result: z.string().nullish(), status: z.string() });
|
|
44
|
+
const list = (item) => z.looseObject({ value: z.array(item) });
|
|
45
|
+
// ADO PRs (merged, checks, comment) and pipeline runs (done), over REST. The organization and
|
|
46
|
+
// project come from the waiting worker's profile. A pass reads each PR once, and its evaluations
|
|
47
|
+
// and threads only when a wait needs them.
|
|
48
|
+
export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken() } = {}) {
|
|
49
|
+
// The first try's request was taken from the budget by the caller; a retry with a renewed token
|
|
50
|
+
// takes one more.
|
|
51
|
+
const get = async (budget, base, path, version = '7.1') => {
|
|
52
|
+
const url = `${base}/_apis/${path}${path.includes('?') ? '&' : '?'}api-version=${version}`;
|
|
53
|
+
for (let renewed = false;; renewed = true) {
|
|
54
|
+
const { body, status } = await getJson(fetcher, url, { headers: { authorization: `Bearer ${await token(renewed)}` } });
|
|
55
|
+
// 203 is ADO's sign-in page for a token it doesn't take.
|
|
56
|
+
if ((status === 401 || status === 203) && !renewed) {
|
|
57
|
+
spend(budget);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (status === 404 || status === 400)
|
|
61
|
+
throw new WaitProblem(`Azure DevOps answered ${status} for ${path}`);
|
|
62
|
+
if (status !== 200)
|
|
63
|
+
throw new Error(`Azure DevOps answered ${status} for ${path}`);
|
|
64
|
+
return body;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
check: async (waits, budget) => {
|
|
69
|
+
const outcomes = new Map();
|
|
70
|
+
const ask = perPass(budget);
|
|
71
|
+
const once = (base, path, version) => ask(`${base} ${path}`, () => get(budget, base, path, version));
|
|
72
|
+
await untilSpent(async () => {
|
|
73
|
+
for (const wait of waits) {
|
|
74
|
+
const c = wait.condition;
|
|
75
|
+
if ((c.kind !== 'pr' || c.host !== 'ado') && c.kind !== 'run')
|
|
76
|
+
continue;
|
|
77
|
+
await perWait(outcomes, wait.id, async () => {
|
|
78
|
+
const base = baseOf(wait.profile);
|
|
79
|
+
if (c.kind === 'run') {
|
|
80
|
+
const b = build.parse(await once(base, `build/builds/${c.id}`));
|
|
81
|
+
return b.status === 'completed' ? `run ${c.pipeline} ${c.id} ${b.result ?? 'completed'}` : null;
|
|
82
|
+
}
|
|
83
|
+
const repo = encodeURIComponent(c.repo);
|
|
84
|
+
const pr = pullRequest.parse(await once(base, `git/repositories/${repo}/pullrequests/${c.number}`));
|
|
85
|
+
if (pr.status === 'completed')
|
|
86
|
+
return `pr ${c.number} merged`;
|
|
87
|
+
if (pr.status === 'abandoned')
|
|
88
|
+
return `pr ${c.number} abandoned`;
|
|
89
|
+
if (c.on === 'merged')
|
|
90
|
+
return null;
|
|
91
|
+
if (c.on === 'checks') {
|
|
92
|
+
const artifact = `vstfs:///CodeReview/CodeReviewId/${pr.repository.project.id}/${pr.pullRequestId}`;
|
|
93
|
+
const { value } = list(evaluation).parse(await once(base, `policy/evaluations?artifactId=${encodeURIComponent(artifact)}`, '7.1-preview.1'));
|
|
94
|
+
const verdict = checksVerdict(pr, value);
|
|
95
|
+
return verdict === null ? null : `pr ${c.number} ${verdict}`;
|
|
96
|
+
}
|
|
97
|
+
const { value } = list(thread).parse(await once(base, `git/repositories/${repo}/pullRequests/${c.number}/threads`));
|
|
98
|
+
const open = openReviewThreads(value, `${wait.profile?.skills.work ?? 'work-issue'} · `);
|
|
99
|
+
return open.length > 0 ? `pr ${c.number} comment: ${open.length} open thread${open.length === 1 ? '' : 's'}` : null;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return outcomes;
|
|
104
|
+
},
|
|
105
|
+
name: 'ado',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// The az CLI's token for Azure DevOps, kept for 20 minutes and renewed on a 401. `az` is a .cmd on
|
|
109
|
+
// Windows, so it runs through a shell; its arguments are constants.
|
|
110
|
+
export function azToken({ now = Date.now } = {}) {
|
|
111
|
+
let cached = null;
|
|
112
|
+
return async (fresh) => {
|
|
113
|
+
if (!fresh && cached && now() - cached.at < TOKEN_MAX_AGE_MS)
|
|
114
|
+
return cached.value;
|
|
115
|
+
const result = await run('az', ['account', 'get-access-token', '--resource', ADO_RESOURCE, '--query', 'accessToken', '-o', 'tsv'], {
|
|
116
|
+
shell: process.platform === 'win32',
|
|
117
|
+
timeoutMs: 60_000,
|
|
118
|
+
});
|
|
119
|
+
const value = result.stdout.trim();
|
|
120
|
+
// A ConductorError: a local wait can't go on without it, so it stops there (the daemon backs off).
|
|
121
|
+
if (result.code !== 0 || !value) {
|
|
122
|
+
throw new ConductorError('az account get-access-token failed', ['run az login', outputOf(result, 300)]);
|
|
123
|
+
}
|
|
124
|
+
cached = { at: now(), value };
|
|
125
|
+
return value;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// The PR's blocking builds as one verdict once none of them runs, or null while one does (or none
|
|
129
|
+
// has been evaluated yet). A path-filtered build the PR doesn't touch is `notApplicable`, which
|
|
130
|
+
// passes, as `land` reads it. Right after a push an evaluation can still describe the previous commit.
|
|
131
|
+
export function checksVerdict(pr, evaluations) {
|
|
132
|
+
const source = pr.lastMergeSourceCommit?.commitId;
|
|
133
|
+
const builds = evaluations.filter((e) => e.configuration?.isEnabled &&
|
|
134
|
+
e.configuration.isBlocking &&
|
|
135
|
+
e.configuration.type?.displayName === 'Build' &&
|
|
136
|
+
!e.context?.buildIsNotCurrent &&
|
|
137
|
+
(!source || !e.context?.lastMergeSourceCommitId || e.context.lastMergeSourceCommitId === source));
|
|
138
|
+
const finished = new Set(['approved', 'broken', 'notApplicable', 'rejected']);
|
|
139
|
+
const live = builds.filter((e) => !e.context?.isExpired);
|
|
140
|
+
if (builds.length === 0 || live.some((e) => !finished.has(e.status)))
|
|
141
|
+
return null;
|
|
142
|
+
if (live.length === 0)
|
|
143
|
+
return 'checks expired';
|
|
144
|
+
const failed = live.filter((e) => e.status === 'rejected' || e.status === 'broken').map((e) => e.context?.buildDefinitionName ?? 'a build');
|
|
145
|
+
return failed.length > 0 ? `checks failed: ${failed.join(', ')}` : 'checks passed';
|
|
146
|
+
}
|
|
147
|
+
// Threads a person opened (not a system update) that are still open and whose last word isn't a
|
|
148
|
+
// work-skill note: what a worker has to answer. Every comment here can carry the same identity, so
|
|
149
|
+
// the `<skill> · ` heading, not the author, marks the worker's own (work-issue's `land` reads them
|
|
150
|
+
// the same way).
|
|
151
|
+
export function openReviewThreads(threads, notePrefix) {
|
|
152
|
+
const done = new Set(['byDesign', 'closed', 'fixed', 'wontFix']);
|
|
153
|
+
return threads
|
|
154
|
+
.filter((t) => !t.isDeleted && !done.has(t.status ?? '') && !t.properties?.CodeReviewThreadType)
|
|
155
|
+
.filter((t) => {
|
|
156
|
+
const comments = (t.comments ?? []).filter((c) => !c.isDeleted && c.commentType !== 'system');
|
|
157
|
+
const last = comments.at(-1)?.content ?? '';
|
|
158
|
+
return comments.length > 0 && !last.replace(/^[#*\s]+/, '').startsWith(notePrefix);
|
|
159
|
+
})
|
|
160
|
+
.map((t) => t.id);
|
|
161
|
+
}
|
|
162
|
+
function baseOf(profile) {
|
|
163
|
+
if (profile?.codeHost.kind !== 'ado')
|
|
164
|
+
throw new WaitProblem("the project's profile has no Azure DevOps code host");
|
|
165
|
+
return `${profile.codeHost.organization.replace(/\/+$/, '')}/${encodeURIComponent(profile.codeHost.project)}`;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=ado.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ado.js","sourceRoot":"","sources":["../../src/watchers/ado.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { outputOf, run } from "../run.js";
|
|
3
|
+
import { perPass, perWait, untilSpent, WaitProblem } from "./source.js";
|
|
4
|
+
const pull = z.looseObject({
|
|
5
|
+
head: z.looseObject({ sha: z.string() }),
|
|
6
|
+
merged: z.boolean().optional(),
|
|
7
|
+
state: z.string(),
|
|
8
|
+
});
|
|
9
|
+
const checkRuns = z.looseObject({
|
|
10
|
+
check_runs: z.array(z.looseObject({ conclusion: z.string().nullish(), name: z.string(), status: z.string() })),
|
|
11
|
+
});
|
|
12
|
+
const user = z.looseObject({ login: z.string(), type: z.string().optional() }).nullish();
|
|
13
|
+
const comments = z.array(z.looseObject({ body: z.string().nullish(), created_at: z.string(), user }));
|
|
14
|
+
const reviews = z.array(z.looseObject({ body: z.string().nullish(), state: z.string(), submitted_at: z.string().nullish(), user }));
|
|
15
|
+
// Conclusions that don't fail a check.
|
|
16
|
+
const PASSING = new Set(['neutral', 'skipped', 'success']);
|
|
17
|
+
export function ghApi() {
|
|
18
|
+
return async (path) => {
|
|
19
|
+
const result = await run('gh', ['api', path], { timeoutMs: 60_000 });
|
|
20
|
+
if (result.code === 0)
|
|
21
|
+
return JSON.parse(result.stdout);
|
|
22
|
+
const said = outputOf(result, 300);
|
|
23
|
+
if (/HTTP 404|Not Found/i.test(said))
|
|
24
|
+
throw new WaitProblem(`GitHub answered 404 for ${path}`);
|
|
25
|
+
throw new Error(`gh api ${path} failed: ${said}`);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// GitHub PRs (merged, checks, comment), the shape ATK-250's code-host adapter will take. GitHub
|
|
29
|
+
// has no open/closed thread state to read cheaply, so `comment` means a person's comment or
|
|
30
|
+
// review made after the wait was registered; bots and the work skill's own notes don't count.
|
|
31
|
+
export function githubSource({ gh = ghApi() } = {}) {
|
|
32
|
+
return {
|
|
33
|
+
check: async (waits, budget) => {
|
|
34
|
+
const outcomes = new Map();
|
|
35
|
+
const ask = perPass(budget);
|
|
36
|
+
const once = (path) => ask(path, () => gh(path));
|
|
37
|
+
await untilSpent(async () => {
|
|
38
|
+
for (const wait of waits) {
|
|
39
|
+
const c = wait.condition;
|
|
40
|
+
if (c.kind !== 'pr' || c.host !== 'github')
|
|
41
|
+
continue;
|
|
42
|
+
await perWait(outcomes, wait.id, async () => {
|
|
43
|
+
const at = `pr ${c.repo}#${c.number}`;
|
|
44
|
+
const base = `repos/${c.repo}`;
|
|
45
|
+
const pr = pull.parse(await once(`${base}/pulls/${c.number}`));
|
|
46
|
+
if (pr.merged)
|
|
47
|
+
return `${at} merged`;
|
|
48
|
+
if (pr.state === 'closed')
|
|
49
|
+
return `${at} closed unmerged`;
|
|
50
|
+
if (c.on === 'merged')
|
|
51
|
+
return null;
|
|
52
|
+
if (c.on === 'checks') {
|
|
53
|
+
const { check_runs: runs } = checkRuns.parse(await once(`${base}/commits/${pr.head.sha}/check-runs?per_page=100`));
|
|
54
|
+
if (runs.length === 0 || runs.some((r) => r.status !== 'completed'))
|
|
55
|
+
return null;
|
|
56
|
+
const failed = runs.filter((r) => !PASSING.has(r.conclusion ?? '')).map((r) => r.name);
|
|
57
|
+
return failed.length > 0 ? `${at} checks failed: ${failed.join(', ')}` : `${at} checks passed`;
|
|
58
|
+
}
|
|
59
|
+
const note = `${wait.profile?.skills.work ?? 'work-issue'} · `;
|
|
60
|
+
const since = Date.parse(wait.createdAt);
|
|
61
|
+
const byPerson = (u, body, when) => u?.type !== 'Bot' && when != null && Date.parse(when) > since && !(body ?? '').replace(/^[#*\s]+/, '').startsWith(note);
|
|
62
|
+
// The comment lists come oldest first, so they're asked from the wait on (`since`), which
|
|
63
|
+
// keeps a new comment on the first page. Reviews can't be asked that way; a PR has few.
|
|
64
|
+
const after = `per_page=100&since=${encodeURIComponent(wait.createdAt)}`;
|
|
65
|
+
const found = [
|
|
66
|
+
...comments.parse(await once(`${base}/issues/${c.number}/comments?${after}`)).filter((x) => byPerson(x.user, x.body, x.created_at)),
|
|
67
|
+
...comments.parse(await once(`${base}/pulls/${c.number}/comments?${after}`)).filter((x) => byPerson(x.user, x.body, x.created_at)),
|
|
68
|
+
...reviews
|
|
69
|
+
.parse(await once(`${base}/pulls/${c.number}/reviews?per_page=100`))
|
|
70
|
+
.filter((x) => x.state !== 'PENDING' && byPerson(x.user, x.body, x.submitted_at)),
|
|
71
|
+
];
|
|
72
|
+
const first = found[0];
|
|
73
|
+
return first ? `${at} comment by ${first.user?.login ?? 'someone'}` : null;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return outcomes;
|
|
78
|
+
},
|
|
79
|
+
name: 'github',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/watchers/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getJson, perPass, perWait, untilSpent, WaitProblem } from "./source.js";
|
|
3
|
+
const healthBody = z.looseObject({ commit: z.string().optional(), version: z.string().optional() });
|
|
4
|
+
// An environment's /health at a version or a commit. A deploy restarts the app, so an error or a
|
|
5
|
+
// non-200 answer only means "not yet". One request per URL per pass.
|
|
6
|
+
export function healthSource({ fetch: fetcher = globalThis.fetch } = {}) {
|
|
7
|
+
return {
|
|
8
|
+
check: async (waits, budget) => {
|
|
9
|
+
const outcomes = new Map();
|
|
10
|
+
const ask = perPass(budget);
|
|
11
|
+
const read = (url) => ask(url, async () => {
|
|
12
|
+
try {
|
|
13
|
+
const answer = await getJson(fetcher, url, { timeoutMs: 15_000 });
|
|
14
|
+
return answer.status === 200 ? (healthBody.safeParse(answer.body).data ?? null) : null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
await untilSpent(async () => {
|
|
21
|
+
for (const wait of waits) {
|
|
22
|
+
const c = wait.condition;
|
|
23
|
+
if (c.kind !== 'health')
|
|
24
|
+
continue;
|
|
25
|
+
await perWait(outcomes, wait.id, async () => {
|
|
26
|
+
const url = wait.profile?.health?.[c.env];
|
|
27
|
+
if (!url)
|
|
28
|
+
throw new WaitProblem(`the project's profile has no health URL for "${c.env}"`);
|
|
29
|
+
const body = await read(url);
|
|
30
|
+
if (c.field === 'version')
|
|
31
|
+
return body?.version === c.value ? `health ${c.env} at version ${c.value}` : null;
|
|
32
|
+
const commit = body?.commit?.toLowerCase();
|
|
33
|
+
return commit?.startsWith(c.value.toLowerCase()) ? `health ${c.env} at commit ${commit.slice(0, 7)}` : null;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return outcomes;
|
|
38
|
+
},
|
|
39
|
+
name: 'health',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/watchers/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,OAAO,EAAgB,OAAO,EAAE,OAAO,EAAe,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5G,MAAM,UAAU,GAAG,CAAC,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAEpG,iGAAiG;AACjG,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;IACrE,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;YAC5C,MAAM,GAAG,GAAG,OAAO,CAAqC,MAAM,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,CAAC,GAAW,EAA+C,EAAE,CACxE,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE;gBAClB,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;oBAClE,OAAO,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBACzF,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;YACL,MAAM,UAAU,CAAC,KAAK,IAAI,EAAE;gBAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;oBACzB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;wBAAE,SAAS;oBAClC,MAAM,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;wBAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC1C,IAAI,CAAC,GAAG;4BAAE,MAAM,IAAI,WAAW,CAAC,gDAAgD,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;wBAC1F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;wBAC7B,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;4BAAE,OAAO,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;wBAC7G,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;wBAC3C,OAAO,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,cAAc,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;oBAC9G,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,EAAE,QAAQ;KACf,CAAC;AACJ,CAAC"}
|