@detergent-software/conductor 1.0.0-dev.19 → 1.0.0-dev.20
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 +11 -0
- package/README.md +34 -6
- package/build/conditions.js +14 -7
- package/build/conditions.js.map +1 -1
- package/build/daemon.js +16 -6
- package/build/daemon.js.map +1 -1
- package/build/inbox.js +3 -2
- package/build/inbox.js.map +1 -1
- package/build/notes.js +4 -4
- package/build/notes.js.map +1 -1
- package/build/profile.js +23 -0
- package/build/profile.js.map +1 -1
- package/build/queue.js +6 -1
- package/build/queue.js.map +1 -1
- package/build/rule-actions.js +1 -1
- package/build/rule-actions.js.map +1 -1
- package/build/scheduler.js +2 -2
- package/build/scheduler.js.map +1 -1
- package/build/store.js.map +1 -1
- package/build/trackers.js +12 -0
- package/build/trackers.js.map +1 -0
- package/build/wait.js +2 -2
- package/build/wait.js.map +1 -1
- package/build/watchers/ado-api.js +55 -0
- package/build/watchers/ado-api.js.map +1 -0
- package/build/watchers/ado.js +30 -43
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/boards-queue.js +120 -0
- package/build/watchers/boards-queue.js.map +1 -0
- package/build/watchers/boards-writes.js +83 -0
- package/build/watchers/boards-writes.js.map +1 -0
- package/build/watchers/boards.js +267 -0
- package/build/watchers/boards.js.map +1 -0
- package/build/watchers/linear-labels.js.map +1 -1
- package/build/watchers/linear-queue.js +3 -0
- package/build/watchers/linear-queue.js.map +1 -1
- package/build/watchers/linear.js +1 -1
- package/build/watchers/linear.js.map +1 -1
- package/package.json +1 -1
package/build/watchers/ado.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ConductorError } from "../errors.js";
|
|
3
2
|
import { notePrefixes } from "../profile.js";
|
|
4
|
-
import {
|
|
3
|
+
import { adoRequest, azToken } from "./ado-api.js";
|
|
4
|
+
import { boardsWaits } from "./boards.js";
|
|
5
5
|
import { noteHeading } from "./linear.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const ADO_RESOURCE = '499b84ac-1321-427f-aa17-267ca6975798';
|
|
9
|
-
// An az token lasts about an hour; a fresh one well before that.
|
|
10
|
-
const TOKEN_MAX_AGE_MS = 20 * 60_000;
|
|
6
|
+
import { perPass, perWait, spend, untilSpent, WaitProblem } from "./source.js";
|
|
7
|
+
export { azToken } from "./ado-api.js";
|
|
11
8
|
const pullRequest = z.looseObject({
|
|
12
9
|
autoCompleteSetBy: z.unknown().optional(),
|
|
13
10
|
lastMergeSourceCommit: z.looseObject({ commitId: z.string() }).optional(),
|
|
@@ -44,28 +41,23 @@ const thread = z.looseObject({
|
|
|
44
41
|
});
|
|
45
42
|
const build = z.looseObject({ result: z.string().nullish(), status: z.string() });
|
|
46
43
|
const list = (item) => z.looseObject({ value: z.array(item) });
|
|
47
|
-
// ADO PRs (merged, checks, comment) and pipeline runs (done), over REST
|
|
48
|
-
//
|
|
49
|
-
//
|
|
44
|
+
// ADO PRs (merged, checks, comment) and pipeline runs (done), over REST, and the waits on ADO Boards
|
|
45
|
+
// items (approval, item), so all of them share one budget. The organization and project come from
|
|
46
|
+
// the waiting worker's profile. A pass reads each PR once, and its evaluations and threads only
|
|
47
|
+
// when a wait needs them.
|
|
50
48
|
export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken() } = {}) {
|
|
51
49
|
// The first try's request was taken from the budget by the caller; a retry with a renewed token
|
|
52
50
|
// takes one more.
|
|
53
51
|
const get = async (budget, base, path, version = '7.1') => {
|
|
54
52
|
const url = `${base}/_apis/${path}${path.includes('?') ? '&' : '?'}api-version=${version}`;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
if (status === 404 || status === 400)
|
|
63
|
-
throw new WaitProblem(`Azure DevOps answered ${status} for ${path}`);
|
|
64
|
-
if (status !== 200)
|
|
65
|
-
throw new Error(`Azure DevOps answered ${status} for ${path}`);
|
|
66
|
-
return body;
|
|
67
|
-
}
|
|
53
|
+
const { body, status } = await adoRequest(fetcher, token, url, { onRetry: () => spend(budget) });
|
|
54
|
+
if (status === 404 || status === 400)
|
|
55
|
+
throw new WaitProblem(`Azure DevOps answered ${status} for ${path}`);
|
|
56
|
+
if (status !== 200)
|
|
57
|
+
throw new Error(`Azure DevOps answered ${status} for ${path}`);
|
|
58
|
+
return body;
|
|
68
59
|
};
|
|
60
|
+
const boards = boardsWaits({ fetch: fetcher, token });
|
|
69
61
|
return {
|
|
70
62
|
check: async (waits, budget) => {
|
|
71
63
|
const outcomes = new Map();
|
|
@@ -102,31 +94,26 @@ export function adoSource({ fetch: fetcher = globalThis.fetch, token = azToken()
|
|
|
102
94
|
});
|
|
103
95
|
}
|
|
104
96
|
});
|
|
97
|
+
try {
|
|
98
|
+
await untilSpent(() => boards.check(waits, budget, outcomes));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
// A Boards failure (ADO throttling it, a 5xx) mustn't throw away this pass's PR and run
|
|
102
|
+
// outcomes or back them off: with any of them here, it is the Boards waits' problem this pass,
|
|
103
|
+
// and they stay open. Alone, the source fails and backs off as for any API.
|
|
104
|
+
const boardWait = (w) => (w.condition.kind === 'approval' || w.condition.kind === 'item') && w.condition.tracker === 'ado-boards';
|
|
105
|
+
if (waits.every(boardWait))
|
|
106
|
+
throw error;
|
|
107
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
108
|
+
for (const wait of waits)
|
|
109
|
+
if (boardWait(wait) && !outcomes.has(wait.id))
|
|
110
|
+
outcomes.set(wait.id, { error: message });
|
|
111
|
+
}
|
|
105
112
|
return outcomes;
|
|
106
113
|
},
|
|
107
114
|
name: 'ado',
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
|
-
// The az CLI's token for Azure DevOps, kept for 20 minutes and renewed on a 401. `az` is a .cmd on
|
|
111
|
-
// Windows, so it runs through a shell; its arguments are constants.
|
|
112
|
-
export function azToken({ now = Date.now } = {}) {
|
|
113
|
-
let cached = null;
|
|
114
|
-
return async (fresh) => {
|
|
115
|
-
if (!fresh && cached && now() - cached.at < TOKEN_MAX_AGE_MS)
|
|
116
|
-
return cached.value;
|
|
117
|
-
const result = await run('az', ['account', 'get-access-token', '--resource', ADO_RESOURCE, '--query', 'accessToken', '-o', 'tsv'], {
|
|
118
|
-
shell: process.platform === 'win32',
|
|
119
|
-
timeoutMs: 60_000,
|
|
120
|
-
});
|
|
121
|
-
const value = result.stdout.trim();
|
|
122
|
-
// A ConductorError: a local wait can't go on without it, so it stops there (the daemon backs off).
|
|
123
|
-
if (result.code !== 0 || !value) {
|
|
124
|
-
throw new ConductorError('az account get-access-token failed', ['run az login', outputOf(result, 300)]);
|
|
125
|
-
}
|
|
126
|
-
cached = { at: now(), value };
|
|
127
|
-
return value;
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
117
|
// The PR's blocking builds as one verdict once none of them runs, or null while one does (or none
|
|
131
118
|
// has been evaluated yet). A path-filtered build the PR doesn't touch is `notApplicable`, which
|
|
132
119
|
// passes, as `land` reads it. Right after a push an evaluation can still describe the previous commit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ado.js","sourceRoot":"","sources":["../../src/watchers/ado.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ado.js","sourceRoot":"","sources":["../../src/watchers/ado.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ConductorError } from "../errors.js";
|
|
3
|
+
import { boardsAsk, CLOSED_TYPES, expectOk, OPEN_TYPES, readWorkItems, requireBoard, stateType, tagsOf, workItemId } from "./boards.js";
|
|
4
|
+
// The scheduler's read of an ADO Boards queue (design note, section 12): the saved query the
|
|
5
|
+
// profile names is the conducted queue, in its own order (by default Priority, then Backlog
|
|
6
|
+
// Priority). Its items, the in-flight ones and their open Predecessors are read in batches.
|
|
7
|
+
// "B has Predecessor A" is stored on B as a Dependency-Reverse link to A.
|
|
8
|
+
const PREDECESSOR = 'System.LinkTypes.Dependency-Reverse';
|
|
9
|
+
const savedQuery = z.looseObject({ id: z.string(), isFolder: z.boolean().optional(), name: z.string() });
|
|
10
|
+
const queryRun = z.looseObject({ queryType: z.string(), workItems: z.array(z.looseObject({ id: z.number() })).nullish() });
|
|
11
|
+
// The Area of an Area Path: its path below the root, with `/` between levels (as an Area label
|
|
12
|
+
// group's sub-Areas are written); null at the root. A path outside the root keeps all of it.
|
|
13
|
+
export function areaOfPath(path, root) {
|
|
14
|
+
const levels = (p) => p.split('\\').filter((l) => l !== '');
|
|
15
|
+
const mine = levels(path);
|
|
16
|
+
const top = levels(root);
|
|
17
|
+
const below = top.every((l, i) => mine[i]?.toLowerCase() === l.toLowerCase()) ? mine.slice(top.length) : mine;
|
|
18
|
+
return below.length > 0 ? below.join('/') : null;
|
|
19
|
+
}
|
|
20
|
+
export function boardsQueue(options = {}) {
|
|
21
|
+
// A saved query's id and name, by board and the profile's path or id.
|
|
22
|
+
const queries = new Map();
|
|
23
|
+
const findQuery = async (ask, board) => {
|
|
24
|
+
const key = `${board.base} ${board.query}`;
|
|
25
|
+
const known = queries.get(key);
|
|
26
|
+
if (known)
|
|
27
|
+
return known;
|
|
28
|
+
const answer = await ask(`wit/queries/${encodeURIComponent(board.query)}`);
|
|
29
|
+
if (answer.status === 404)
|
|
30
|
+
throw new ConductorError(`Azure DevOps Boards has no saved query "${board.query}" in ${board.project}`);
|
|
31
|
+
const query = savedQuery.parse(expectOk(answer, `the saved query "${board.query}"`));
|
|
32
|
+
if (query.isFolder)
|
|
33
|
+
throw new ConductorError(`"${board.query}" is a query folder: tracker.query names a saved query`);
|
|
34
|
+
queries.set(key, query);
|
|
35
|
+
return query;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
read: async (profile, inFlight, budget) => {
|
|
39
|
+
const board = requireBoard(profile);
|
|
40
|
+
const ask = boardsAsk(options, board, budget);
|
|
41
|
+
const query = await findQuery(ask, board);
|
|
42
|
+
const answer = await ask(`wit/wiql/${query.id}`);
|
|
43
|
+
// A query deleted and saved again has a new id: look it up again next time.
|
|
44
|
+
if (answer.status === 404)
|
|
45
|
+
queries.delete(`${board.base} ${board.query}`);
|
|
46
|
+
const ran = queryRun.parse(expectOk(answer, `the saved query "${query.name}"`));
|
|
47
|
+
if (ran.queryType !== 'flat') {
|
|
48
|
+
throw new ConductorError(`the saved query "${query.name}" is a ${ran.queryType} query: the queue is a flat list of work items`);
|
|
49
|
+
}
|
|
50
|
+
const queued = (ran.workItems ?? []).map((w) => w.id);
|
|
51
|
+
const flying = inFlight.flatMap((item) => workItemId(item, board) ?? []);
|
|
52
|
+
const items = new Map();
|
|
53
|
+
const load = async (ids) => {
|
|
54
|
+
const missing = [...new Set(ids)].filter((id) => !items.has(id));
|
|
55
|
+
for (const w of await readWorkItems(ask, missing, { relations: true }))
|
|
56
|
+
items.set(w.id, w);
|
|
57
|
+
};
|
|
58
|
+
await load([...queued, ...flying]);
|
|
59
|
+
// A Predecessor outside the query still blocks an open item, so it is read too.
|
|
60
|
+
const open = [...items.values()].filter((w) => OPEN_TYPES.has(stateType(String(w.fields['System.State'] ?? ''), board)));
|
|
61
|
+
await load(open.flatMap(predecessors));
|
|
62
|
+
return boardsSnapshot(board, query.name, queued, flying, items);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// The snapshot from the query's ids (in its order), the in-flight ids and every item read. Open
|
|
67
|
+
// query items are the queue, in one tracker project named after the query, as are in-flight items
|
|
68
|
+
// a query on state has dropped (so the project isn't read as ready to release while they're
|
|
69
|
+
// worked); an open Predecessor outside both is listed without a project, so it holds its Area and
|
|
70
|
+
// blocks.
|
|
71
|
+
export function boardsSnapshot(board, queryName, queued, flying, items) {
|
|
72
|
+
const type = (w) => stateType(String(w.fields['System.State'] ?? ''), board);
|
|
73
|
+
const open = new Set([...items.values()].filter((w) => OPEN_TYPES.has(type(w))).map((w) => w.id));
|
|
74
|
+
const rank = new Map(queued.map((id, i) => [id, i]));
|
|
75
|
+
const listed = [...new Set([...queued, ...items.keys()])].filter((id) => open.has(id));
|
|
76
|
+
const issues = listed.flatMap((id) => {
|
|
77
|
+
const w = items.get(id);
|
|
78
|
+
if (!w)
|
|
79
|
+
return [];
|
|
80
|
+
const priority = Number(w.fields['Microsoft.VSTS.Common.Priority']);
|
|
81
|
+
const inQuery = rank.has(id);
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
area: areaOfPath(String(w.fields['System.AreaPath'] ?? ''), board.areaRoot),
|
|
85
|
+
blockedBy: predecessors(w)
|
|
86
|
+
.filter((p) => open.has(p))
|
|
87
|
+
.map((p) => keyOf(board, p)),
|
|
88
|
+
createdAt: typeof w.fields['System.CreatedDate'] === 'string' ? w.fields['System.CreatedDate'] : null,
|
|
89
|
+
id: keyOf(board, id),
|
|
90
|
+
// ADO tags ignore case and keep the org's spelling, so the human tag is spelt as the scheduler looks for it.
|
|
91
|
+
labels: tagsOf(w.fields['System.Tags']).map((t) => (t.toLowerCase() === board.humanLabel.toLowerCase() ? board.humanLabel : t)),
|
|
92
|
+
priority: Number.isInteger(priority) ? priority : null,
|
|
93
|
+
...(inQuery ? { rank: rank.get(id) } : {}),
|
|
94
|
+
stateType: type(w),
|
|
95
|
+
title: typeof w.fields['System.Title'] === 'string' ? w.fields['System.Title'] : null,
|
|
96
|
+
trackerProject: inQuery || flying.includes(id) ? queryName : null,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
areaLabels: [...new Set(issues.flatMap((i) => i.area ?? []))].sort(),
|
|
102
|
+
closed: flying.filter((id) => {
|
|
103
|
+
const w = items.get(id);
|
|
104
|
+
return w !== undefined && CLOSED_TYPES.has(type(w));
|
|
105
|
+
}).map((id) => keyOf(board, id)),
|
|
106
|
+
issues,
|
|
107
|
+
projects: [{ name: queryName, priority: board.priority, sortOrder: 0 }],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function keyOf(board, id) {
|
|
111
|
+
return `${board.key}-${id}`;
|
|
112
|
+
}
|
|
113
|
+
// The ids of a work item's Predecessors.
|
|
114
|
+
function predecessors(w) {
|
|
115
|
+
return (w.relations ?? []).flatMap((r) => {
|
|
116
|
+
const id = r.rel === PREDECESSOR ? /\/workItems\/(\d+)$/i.exec(r.url)?.[1] : undefined;
|
|
117
|
+
return id ? [Number(id)] : [];
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=boards-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boards-queue.js","sourceRoot":"","sources":["../../src/watchers/boards-queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ConductorError } from "../errors.js";
|
|
3
|
+
import { NotePostError } from "../notes.js";
|
|
4
|
+
import { adoMessage, boardsAsk, expectOk, requireBoard, tagsOf, workItemId } from "./boards.js";
|
|
5
|
+
// The conductor's writes to ADO Boards: phase notes as discussion comments, and a rule's tag edit.
|
|
6
|
+
// Neither takes from the watchers' budget, as Linear's writes don't.
|
|
7
|
+
const created = z.looseObject({ id: z.number() });
|
|
8
|
+
// A note as a markdown discussion comment. A refused request (4xx, or 203, ADO's sign-in page for
|
|
9
|
+
// a token it doesn't take) posted nothing; a lost answer or a 5xx may have posted it.
|
|
10
|
+
export function boardsNotes(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
comment: async (item, body, profile) => {
|
|
13
|
+
const board = requireBoard(profile);
|
|
14
|
+
const id = itemId(item, board, (message) => new NotePostError(message, 'no'));
|
|
15
|
+
const ask = boardsAsk(options, board, null);
|
|
16
|
+
let answer;
|
|
17
|
+
try {
|
|
18
|
+
answer = await ask(`wit/workItems/${id}/comments?format=markdown`, { body: { text: body }, method: 'POST', version: '7.1-preview.4' });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
// No az token: nothing was sent.
|
|
23
|
+
if (error instanceof ConductorError)
|
|
24
|
+
throw new NotePostError(message, 'no', error.details);
|
|
25
|
+
throw new NotePostError(`Azure DevOps unreachable: ${message}`, 'unknown');
|
|
26
|
+
}
|
|
27
|
+
if (answer.status === 203 || (answer.status >= 400 && answer.status < 500)) {
|
|
28
|
+
throw new NotePostError(`Azure DevOps refused the comment on ${item} (${answer.status}${adoMessage(answer.body)})`, 'no');
|
|
29
|
+
}
|
|
30
|
+
const comment = answer.status >= 200 && answer.status < 300 ? created.safeParse(answer.body).data : undefined;
|
|
31
|
+
if (!comment)
|
|
32
|
+
throw new NotePostError(`Azure DevOps' answer to the comment on ${item} didn't read (${answer.status})`, 'unknown');
|
|
33
|
+
return { id: String(comment.id), url: `${board.base}/_workitems/edit/${id}` };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const tagged = z.looseObject({ fields: z.record(z.string(), z.unknown()), rev: z.number() });
|
|
38
|
+
// A tag added to or removed from a work item: a patch of `System.Tags` alone, refused by ADO when
|
|
39
|
+
// the item changed since it was read (`test /rev`), so a concurrent edit isn't overwritten.
|
|
40
|
+
export function boardsLabels(options = {}) {
|
|
41
|
+
return {
|
|
42
|
+
label: async (item, label, op, profile) => {
|
|
43
|
+
const board = requireBoard(profile);
|
|
44
|
+
const id = itemId(item, board, (message) => new ConductorError(message));
|
|
45
|
+
const ask = boardsAsk(options, board, null);
|
|
46
|
+
const read = await readTags(ask, id, item);
|
|
47
|
+
const has = read.tags.some((t) => t.toLowerCase() === label.toLowerCase());
|
|
48
|
+
if (has === (op === 'add'))
|
|
49
|
+
return 'already';
|
|
50
|
+
const tags = op === 'add' ? [...read.tags, label] : read.tags.filter((t) => t.toLowerCase() !== label.toLowerCase());
|
|
51
|
+
// `add` on System.Tags merges into the tags there (removing nothing), so a list that replaces
|
|
52
|
+
// them is a `replace`, which needs the field to exist.
|
|
53
|
+
const patch = [
|
|
54
|
+
{ op: 'test', path: '/rev', value: read.rev },
|
|
55
|
+
{ op: read.tags.length > 0 ? 'replace' : 'add', path: '/fields/System.Tags', value: tags.join('; ') },
|
|
56
|
+
];
|
|
57
|
+
const answer = await ask(`wit/workitems/${id}`, { body: patch, contentType: 'application/json-patch+json', method: 'PATCH' });
|
|
58
|
+
const failed = `Azure DevOps didn't ${op} the tag "${label}" on ${item}`;
|
|
59
|
+
if (answer.status < 200 || answer.status > 299)
|
|
60
|
+
throw new ConductorError(`${failed} (${answer.status}${adoMessage(answer.body)})`);
|
|
61
|
+
// A patch ADO takes without changing anything still answers 200, so the answer's tags decide.
|
|
62
|
+
const after = tagged.safeParse(answer.body).data;
|
|
63
|
+
const now = tagsOf(after?.fields['System.Tags']).some((t) => t.toLowerCase() === label.toLowerCase());
|
|
64
|
+
if (!after || now !== (op === 'add'))
|
|
65
|
+
throw new ConductorError(`${failed}: its tags are "${String(after?.fields['System.Tags'] ?? '')}"`);
|
|
66
|
+
return op === 'add' ? 'added' : 'removed';
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function itemId(item, board, error) {
|
|
71
|
+
const id = workItemId(item, board);
|
|
72
|
+
if (id === null)
|
|
73
|
+
throw error(`${item} isn't an item of the board keyed ${board.key}, such as ${board.key}-1234`);
|
|
74
|
+
return id;
|
|
75
|
+
}
|
|
76
|
+
async function readTags(ask, id, item) {
|
|
77
|
+
const answer = await ask(`wit/workitems/${id}?fields=System.Tags`);
|
|
78
|
+
if (answer.status === 404)
|
|
79
|
+
throw new ConductorError(`Azure DevOps Boards has no work item ${id} (${item})`);
|
|
80
|
+
const w = tagged.parse(expectOk(answer, `work item ${id}`));
|
|
81
|
+
return { rev: w.rev, tags: tagsOf(w.fields['System.Tags']) };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=boards-writes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boards-writes.js","sourceRoot":"","sources":["../../src/watchers/boards-writes.ts"],"names":[],"mappings":"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"}
|