pr-report-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/checks.d.mts.map +1 -1
- package/dist/checks.mjs +8 -8
- package/dist/checks.mjs.map +1 -1
- package/dist/code-owners.d.mts +24 -24
- package/dist/code-owners.d.mts.map +1 -1
- package/dist/code-owners.mjs +42 -42
- package/dist/code-owners.mjs.map +1 -1
- package/dist/merge-after.d.mts.map +1 -1
- package/dist/merge-after.mjs +13 -13
- package/dist/merge-after.mjs.map +1 -1
- package/dist/queue-order.d.mts +14 -1
- package/dist/queue-order.d.mts.map +1 -1
- package/dist/queue-order.mjs +34 -17
- package/dist/queue-order.mjs.map +1 -1
- package/dist/queue-owner.d.mts +17 -17
- package/dist/queue-owner.d.mts.map +1 -1
- package/dist/queue-owner.mjs +51 -51
- package/dist/queue-owner.mjs.map +1 -1
- package/dist/report.d.mts +5 -5
- package/dist/report.d.mts.map +1 -1
- package/dist/report.mjs +23 -23
- package/dist/report.mjs.map +1 -1
- package/package.json +5 -5
- package/src/checks.mts +10 -10
- package/src/code-owners.mts +81 -81
- package/src/merge-after.mts +23 -23
- package/src/queue-order.mts +49 -27
- package/src/queue-order.test.mts +37 -0
- package/src/queue-owner.mts +84 -84
- package/src/report.mts +43 -43
package/src/queue-order.test.mts
CHANGED
|
@@ -383,15 +383,52 @@ describe(orderCandidates, () => {
|
|
|
383
383
|
[2, 3, 1],
|
|
384
384
|
);
|
|
385
385
|
});
|
|
386
|
+
|
|
387
|
+
// Picked early, it is released, goes green and is set aside for a person,
|
|
388
|
+
// and the cycle it took went to nothing that could merge on its own.
|
|
389
|
+
test('puts one only a bypass merges with priority:low, unless labelled priority:high', () => {
|
|
390
|
+
assert.deepStrictEqual(
|
|
391
|
+
order(
|
|
392
|
+
[
|
|
393
|
+
facts({ number: 1 }),
|
|
394
|
+
facts({
|
|
395
|
+
number: 2,
|
|
396
|
+
labels: [
|
|
397
|
+
MERGE_QUEUED_LABEL,
|
|
398
|
+
PRIORITY_HIGH_LABEL,
|
|
399
|
+
PRIORITY_LOW_LABEL,
|
|
400
|
+
],
|
|
401
|
+
}),
|
|
402
|
+
facts({
|
|
403
|
+
number: 3,
|
|
404
|
+
labels: [MERGE_QUEUED_LABEL, PRIORITY_LOW_LABEL],
|
|
405
|
+
}),
|
|
406
|
+
facts({ number: 4, headRef: 'changeset-release/main' }),
|
|
407
|
+
facts({ number: 5, mergeState: 'DIRTY' }),
|
|
408
|
+
facts({ number: 6 }),
|
|
409
|
+
facts({ number: 7 }),
|
|
410
|
+
facts({
|
|
411
|
+
number: 8,
|
|
412
|
+
labels: [MERGE_QUEUED_LABEL, PRIORITY_HIGH_LABEL],
|
|
413
|
+
}),
|
|
414
|
+
],
|
|
415
|
+
[7],
|
|
416
|
+
[1, 2, 7, 8],
|
|
417
|
+
),
|
|
418
|
+
[8, 6, 5, 4, 1, 2, 3, 7],
|
|
419
|
+
);
|
|
420
|
+
});
|
|
386
421
|
});
|
|
387
422
|
|
|
388
423
|
const order = (
|
|
389
424
|
candidates: readonly QueueFacts[],
|
|
390
425
|
demoted: readonly number[] = [],
|
|
426
|
+
bypass: readonly number[] = [],
|
|
391
427
|
): readonly number[] =>
|
|
392
428
|
orderCandidates(candidates, (candidate) => candidate, {
|
|
393
429
|
defaultBranch: 'main',
|
|
394
430
|
demoted: (candidate) => demoted.includes(candidate.number),
|
|
431
|
+
bypass: (candidate) => bypass.includes(candidate.number),
|
|
395
432
|
}).map(({ number }) => number);
|
|
396
433
|
|
|
397
434
|
const facts = (
|
package/src/queue-owner.mts
CHANGED
|
@@ -64,87 +64,6 @@ export const SET_ASIDE_REASONS = [
|
|
|
64
64
|
|
|
65
65
|
export type SetAsideReason = (typeof SET_ASIDE_REASONS)[number];
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Whose a pull request set aside for `reason` is, while the comment stands
|
|
69
|
-
* at its head. A skill's reason is one a session can fix by pushing: a
|
|
70
|
-
* conflict to resolve, a failure to reproduce. A person's is one only a
|
|
71
|
-
* setting, a permission, a review or a merge by hand clears, and the comment
|
|
72
|
-
* says which. A `switch` with no default, so a reason added to
|
|
73
|
-
* {@link SET_ASIDE_REASONS} without an owner fails to compile.
|
|
74
|
-
*/
|
|
75
|
-
const setAsideOwner = (
|
|
76
|
-
reason: SetAsideReason,
|
|
77
|
-
): Readonly<{ owner: 'human' | 'skill'; why: string }> => {
|
|
78
|
-
switch (reason) {
|
|
79
|
-
case 'checks-failed':
|
|
80
|
-
case 'rebase-failed':
|
|
81
|
-
return { owner: 'skill', why: 'the skill fixes it' };
|
|
82
|
-
|
|
83
|
-
case 'already-in-base':
|
|
84
|
-
return {
|
|
85
|
-
owner: 'human',
|
|
86
|
-
why: 'every commit is already in the base; close it',
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
case 'arm-failed':
|
|
90
|
-
return {
|
|
91
|
-
owner: 'human',
|
|
92
|
-
why: 'auto-merge could not be armed; a setting or a permission holds it',
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
case 'awaiting-bypass':
|
|
96
|
-
return {
|
|
97
|
-
owner: 'human',
|
|
98
|
-
why: 'green, and only a ruleset bypass merges it',
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
case 'not-merging':
|
|
102
|
-
return {
|
|
103
|
-
owner: 'human',
|
|
104
|
-
why: 'green but not merging; something the worker cannot read holds it',
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
case 'push-failed':
|
|
108
|
-
return {
|
|
109
|
-
owner: 'human',
|
|
110
|
-
why: 'the push was refused; a setting or a permission holds it',
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
case 'unlabel-failed':
|
|
114
|
-
return {
|
|
115
|
-
owner: 'human',
|
|
116
|
-
why: '`skip-ci` could not be taken off; a permission holds it',
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
case 'watch-timeout':
|
|
120
|
-
return {
|
|
121
|
-
owner: 'human',
|
|
122
|
-
why: 'its checks ran past the watch; look at the run',
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
/** What {@link queueOwner} reads about one pull request. */
|
|
128
|
-
export type QueueOwnerInput = Readonly<{
|
|
129
|
-
number: number;
|
|
130
|
-
labels: readonly string[];
|
|
131
|
-
isDraft: boolean;
|
|
132
|
-
fromFork: boolean;
|
|
133
|
-
headRef: string;
|
|
134
|
-
headSha: string;
|
|
135
|
-
baseRef: string;
|
|
136
|
-
defaultBranch: string;
|
|
137
|
-
/** The open pull request whose branch this one is on, if any. */
|
|
138
|
-
stackedOn: number | undefined;
|
|
139
|
-
/** Whether it sits on a `Merge-After` cycle. */
|
|
140
|
-
inCycle: boolean;
|
|
141
|
-
/** This account's set-aside comment on it (`set-aside.mts`). */
|
|
142
|
-
setAside: SetAsideComment | undefined;
|
|
143
|
-
/** This account's claim comment on it (`claim.mts`). */
|
|
144
|
-
claim: ClaimComment | undefined;
|
|
145
|
-
nowEpochMs: number;
|
|
146
|
-
}>;
|
|
147
|
-
|
|
148
67
|
/**
|
|
149
68
|
* Who moves `pr` next, or `undefined` for a pull request the queue does not
|
|
150
69
|
* cover: neither `merge-queued` nor `auto-rebase` under `skip-ci`.
|
|
@@ -250,6 +169,90 @@ export const queueOwnerOfEntry = (
|
|
|
250
169
|
nowEpochMs: context.nowEpochMs,
|
|
251
170
|
});
|
|
252
171
|
|
|
172
|
+
export const isSetAsideReason = (reason: string): reason is SetAsideReason =>
|
|
173
|
+
KNOWN_REASONS.has(reason);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Whose a pull request set aside for `reason` is, while the comment stands
|
|
177
|
+
* at its head. A skill's reason is one a session can fix by pushing: a
|
|
178
|
+
* conflict to resolve, a failure to reproduce. A person's is one only a
|
|
179
|
+
* setting, a permission, a review or a merge by hand clears, and the comment
|
|
180
|
+
* says which. A `switch` with no default, so a reason added to
|
|
181
|
+
* {@link SET_ASIDE_REASONS} without an owner fails to compile.
|
|
182
|
+
*/
|
|
183
|
+
const setAsideOwner = (
|
|
184
|
+
reason: SetAsideReason,
|
|
185
|
+
): Readonly<{ owner: 'human' | 'skill'; why: string }> => {
|
|
186
|
+
switch (reason) {
|
|
187
|
+
case 'checks-failed':
|
|
188
|
+
case 'rebase-failed':
|
|
189
|
+
return { owner: 'skill', why: 'the skill fixes it' };
|
|
190
|
+
|
|
191
|
+
case 'already-in-base':
|
|
192
|
+
return {
|
|
193
|
+
owner: 'human',
|
|
194
|
+
why: 'every commit is already in the base; close it',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
case 'arm-failed':
|
|
198
|
+
return {
|
|
199
|
+
owner: 'human',
|
|
200
|
+
why: 'auto-merge could not be armed; a setting or a permission holds it',
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
case 'awaiting-bypass':
|
|
204
|
+
return {
|
|
205
|
+
owner: 'human',
|
|
206
|
+
why: 'green, and only a ruleset bypass merges it',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
case 'not-merging':
|
|
210
|
+
return {
|
|
211
|
+
owner: 'human',
|
|
212
|
+
why: 'green but not merging; something the worker cannot read holds it',
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
case 'push-failed':
|
|
216
|
+
return {
|
|
217
|
+
owner: 'human',
|
|
218
|
+
why: 'the push was refused; a setting or a permission holds it',
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
case 'unlabel-failed':
|
|
222
|
+
return {
|
|
223
|
+
owner: 'human',
|
|
224
|
+
why: '`skip-ci` could not be taken off; a permission holds it',
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
case 'watch-timeout':
|
|
228
|
+
return {
|
|
229
|
+
owner: 'human',
|
|
230
|
+
why: 'its checks ran past the watch; look at the run',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** What {@link queueOwner} reads about one pull request. */
|
|
236
|
+
export type QueueOwnerInput = Readonly<{
|
|
237
|
+
number: number;
|
|
238
|
+
labels: readonly string[];
|
|
239
|
+
isDraft: boolean;
|
|
240
|
+
fromFork: boolean;
|
|
241
|
+
headRef: string;
|
|
242
|
+
headSha: string;
|
|
243
|
+
baseRef: string;
|
|
244
|
+
defaultBranch: string;
|
|
245
|
+
/** The open pull request whose branch this one is on, if any. */
|
|
246
|
+
stackedOn: number | undefined;
|
|
247
|
+
/** Whether it sits on a `Merge-After` cycle. */
|
|
248
|
+
inCycle: boolean;
|
|
249
|
+
/** This account's set-aside comment on it (`set-aside.mts`). */
|
|
250
|
+
setAside: SetAsideComment | undefined;
|
|
251
|
+
/** This account's claim comment on it (`claim.mts`). */
|
|
252
|
+
claim: ClaimComment | undefined;
|
|
253
|
+
nowEpochMs: number;
|
|
254
|
+
}>;
|
|
255
|
+
|
|
253
256
|
/**
|
|
254
257
|
* The set-aside `comment` says, if it stands at `headSha` with no retry
|
|
255
258
|
* asked for. The base is not read: see the module comment.
|
|
@@ -264,9 +267,6 @@ const standingAt = (
|
|
|
264
267
|
? comment.setAside
|
|
265
268
|
: undefined;
|
|
266
269
|
|
|
267
|
-
export const isSetAsideReason = (reason: string): reason is SetAsideReason =>
|
|
268
|
-
KNOWN_REASONS.has(reason);
|
|
269
|
-
|
|
270
270
|
const KNOWN_REASONS: ReadonlySet<string> = new Set(SET_ASIDE_REASONS);
|
|
271
271
|
|
|
272
272
|
/**
|
package/src/report.mts
CHANGED
|
@@ -82,49 +82,6 @@ export const buildReport = ({
|
|
|
82
82
|
issuesLimit,
|
|
83
83
|
}) as const;
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
* Each open issue with how it stands for being started
|
|
87
|
-
* (`docs/issue-start-after.md`): its start state, or how far someone has
|
|
88
|
-
* got with it when it is startable (`progressOf`), and where each thing it
|
|
89
|
-
* names stands.
|
|
90
|
-
*/
|
|
91
|
-
const reportIssues = (
|
|
92
|
-
issues: readonly OpenIssue[],
|
|
93
|
-
pulls: readonly PullRequestFacts[],
|
|
94
|
-
references: ReadonlyMap<number, ReferenceStatus>,
|
|
95
|
-
): readonly ReportIssue[] => {
|
|
96
|
-
const listed = new Set(issues.map(({ number }) => number));
|
|
97
|
-
|
|
98
|
-
const open = new Set(pulls.map(({ number }) => number));
|
|
99
|
-
|
|
100
|
-
const statusOf = (number: number): ReferenceStatus =>
|
|
101
|
-
listed.has(number) || open.has(number)
|
|
102
|
-
? 'waiting'
|
|
103
|
-
: (references.get(number) ?? 'unknown');
|
|
104
|
-
|
|
105
|
-
const states = decideStartStates(issues, statusOf);
|
|
106
|
-
|
|
107
|
-
const worked = issuesInProgress(pulls);
|
|
108
|
-
|
|
109
|
-
return issues.map((issue) => ({
|
|
110
|
-
...issue,
|
|
111
|
-
start: withProgress(
|
|
112
|
-
states.get(issue.number) ?? 'free',
|
|
113
|
-
progressOf(issue.labels, worked.has(issue.number)),
|
|
114
|
-
),
|
|
115
|
-
named: issue.startAfter.map((number) => ({
|
|
116
|
-
number,
|
|
117
|
-
status: statusOf(number),
|
|
118
|
-
})),
|
|
119
|
-
}));
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export type DecidedEntries = Readonly<{
|
|
123
|
-
entries: readonly ReportEntry[];
|
|
124
|
-
roots: readonly TreeNode[];
|
|
125
|
-
cycles: readonly (readonly number[])[];
|
|
126
|
-
}>;
|
|
127
|
-
|
|
128
85
|
/**
|
|
129
86
|
* The part of a report that is decided rather than read: each open pull
|
|
130
87
|
* request's declared order, the stack it is in, its check verdict, and the
|
|
@@ -236,6 +193,49 @@ export const buildEntries = ({
|
|
|
236
193
|
return { entries, roots, cycles };
|
|
237
194
|
};
|
|
238
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Each open issue with how it stands for being started
|
|
198
|
+
* (`docs/issue-start-after.md`): its start state, or how far someone has
|
|
199
|
+
* got with it when it is startable (`progressOf`), and where each thing it
|
|
200
|
+
* names stands.
|
|
201
|
+
*/
|
|
202
|
+
const reportIssues = (
|
|
203
|
+
issues: readonly OpenIssue[],
|
|
204
|
+
pulls: readonly PullRequestFacts[],
|
|
205
|
+
references: ReadonlyMap<number, ReferenceStatus>,
|
|
206
|
+
): readonly ReportIssue[] => {
|
|
207
|
+
const listed = new Set(issues.map(({ number }) => number));
|
|
208
|
+
|
|
209
|
+
const open = new Set(pulls.map(({ number }) => number));
|
|
210
|
+
|
|
211
|
+
const statusOf = (number: number): ReferenceStatus =>
|
|
212
|
+
listed.has(number) || open.has(number)
|
|
213
|
+
? 'waiting'
|
|
214
|
+
: (references.get(number) ?? 'unknown');
|
|
215
|
+
|
|
216
|
+
const states = decideStartStates(issues, statusOf);
|
|
217
|
+
|
|
218
|
+
const worked = issuesInProgress(pulls);
|
|
219
|
+
|
|
220
|
+
return issues.map((issue) => ({
|
|
221
|
+
...issue,
|
|
222
|
+
start: withProgress(
|
|
223
|
+
states.get(issue.number) ?? 'free',
|
|
224
|
+
progressOf(issue.labels, worked.has(issue.number)),
|
|
225
|
+
),
|
|
226
|
+
named: issue.startAfter.map((number) => ({
|
|
227
|
+
number,
|
|
228
|
+
status: statusOf(number),
|
|
229
|
+
})),
|
|
230
|
+
}));
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export type DecidedEntries = Readonly<{
|
|
234
|
+
entries: readonly ReportEntry[];
|
|
235
|
+
roots: readonly TreeNode[];
|
|
236
|
+
cycles: readonly (readonly number[])[];
|
|
237
|
+
}>;
|
|
238
|
+
|
|
239
239
|
const NO_DECLARATIONS: IssueDeclarations = {
|
|
240
240
|
startAfter: new Map<number, readonly number[]>(),
|
|
241
241
|
open: new Set<number>(),
|