@framers/agentos-ext-github 0.2.1 → 0.3.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/README.md +49 -15
- package/dist/__tests__/app.test.d.ts +1 -1
- package/dist/__tests__/app.test.js +306 -2
- package/dist/__tests__/app.test.js.map +1 -1
- package/dist/__tests__/documents.test.js +28 -1
- package/dist/__tests__/documents.test.js.map +1 -1
- package/dist/__tests__/entry.test.d.ts +6 -0
- package/dist/__tests__/entry.test.d.ts.map +1 -0
- package/dist/__tests__/entry.test.js +17 -0
- package/dist/__tests__/entry.test.js.map +1 -0
- package/dist/__tests__/events.test.d.ts +8 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.js +304 -0
- package/dist/__tests__/events.test.js.map +1 -0
- package/dist/app/credential.d.ts +4 -3
- package/dist/app/credential.d.ts.map +1 -1
- package/dist/app/credential.js +8 -4
- package/dist/app/credential.js.map +1 -1
- package/dist/app/events.d.ts +142 -0
- package/dist/app/events.d.ts.map +1 -0
- package/dist/app/events.js +182 -0
- package/dist/app/events.js.map +1 -0
- package/dist/app/http.d.ts +82 -13
- package/dist/app/http.d.ts.map +1 -1
- package/dist/app/http.js +151 -23
- package/dist/app/http.js.map +1 -1
- package/dist/app/installations.d.ts +8 -6
- package/dist/app/installations.d.ts.map +1 -1
- package/dist/app/installations.js +21 -15
- package/dist/app/installations.js.map +1 -1
- package/dist/app/reconcile.d.ts +25 -1
- package/dist/app/reconcile.d.ts.map +1 -1
- package/dist/app/reconcile.js +63 -26
- package/dist/app/reconcile.js.map +1 -1
- package/dist/app/userFlow.d.ts +10 -3
- package/dist/app/userFlow.d.ts.map +1 -1
- package/dist/app/userFlow.js +43 -4
- package/dist/app/userFlow.js.map +1 -1
- package/dist/app/webhook.d.ts +4 -1
- package/dist/app/webhook.d.ts.map +1 -1
- package/dist/app/webhook.js +6 -1
- package/dist/app/webhook.js.map +1 -1
- package/dist/documents/GitHubDocumentsConnector.d.ts +7 -4
- package/dist/documents/GitHubDocumentsConnector.d.ts.map +1 -1
- package/dist/documents/GitHubDocumentsConnector.js +39 -18
- package/dist/documents/GitHubDocumentsConnector.js.map +1 -1
- package/dist/documents/index.d.ts +12 -7
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +11 -6
- package/dist/documents/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/manifest.json +4 -2
- package/package.json +5 -4
package/dist/app/http.js
CHANGED
|
@@ -1,21 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview GitHub's REST API over an injected `fetch`: one call with the headers every call
|
|
3
|
-
* of a listing followed through its `Link` header, and the small readers that turn an answer into
|
|
4
|
-
* answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of
|
|
2
|
+
* @fileoverview GitHub's REST API over an injected `fetch`: one call under a deadline with the headers every call
|
|
3
|
+
* carries, the pages of a listing followed through its `Link` header, and the small readers that turn an answer into
|
|
4
|
+
* typed fields. An answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of
|
|
5
|
+
* the answer's body and which says whether GitHub refused for a rate limit and when to try again; a call that gets no
|
|
6
|
+
* answer throws {@link GitHubNoAnswer}. An answer's body is read under a byte cap.
|
|
5
7
|
* @module agentos-ext-github/app/http
|
|
6
8
|
*/
|
|
9
|
+
import { readCapped, ReadCapExceeded } from '@framers/agentos-ext-account-sources';
|
|
7
10
|
/** The REST API version every call pins (`X-GitHub-Api-Version`). */
|
|
8
11
|
export const GITHUB_API_VERSION = '2026-03-10';
|
|
9
12
|
/** GitHub's REST API. */
|
|
10
13
|
export const GITHUB_API_BASE = 'https://api.github.com';
|
|
11
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* How long one call may take when its caller sets no other deadline, its answer's body included: 30 seconds. GitHub
|
|
16
|
+
* ends a request it takes more than 10 seconds to process.
|
|
17
|
+
*/
|
|
18
|
+
export const GITHUB_TIMEOUT_MS = 30_000;
|
|
19
|
+
/** The largest JSON answer read: 16 MiB, above the 7 MB at which GitHub cuts a recursive tree. */
|
|
20
|
+
export const GITHUB_MAX_ANSWER_BYTES = 16_777_216;
|
|
21
|
+
/** How much of a refused call's body is read to tell a rate limit from another 403. */
|
|
22
|
+
const REFUSAL_READ_BYTES = 16_384;
|
|
23
|
+
/** The wait after a rate limit that names no time: GitHub asks for at least a minute. */
|
|
24
|
+
const RATE_LIMIT_WAIT_MS = 60_000;
|
|
25
|
+
/**
|
|
26
|
+
* GitHub answered a status outside 2xx. The message holds the status alone, never the answer's body. `rateLimited`
|
|
27
|
+
* and `retryAt` follow GitHub's rules for a refused call
|
|
28
|
+
* (https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#exceeding-the-rate-limit):
|
|
29
|
+
* `retry-after` first, then `x-ratelimit-reset` when `x-ratelimit-remaining` is 0, else a minute for a rate limit
|
|
30
|
+
* that names no time.
|
|
31
|
+
*/
|
|
12
32
|
export class GitHubApiError extends Error {
|
|
13
33
|
status;
|
|
14
|
-
/**
|
|
15
|
-
|
|
34
|
+
/** True when GitHub refused the call for a rate limit. */
|
|
35
|
+
rateLimited;
|
|
36
|
+
/** Epoch milliseconds before which the call should not be made again, or null when GitHub asked for no wait. */
|
|
37
|
+
retryAt;
|
|
38
|
+
/**
|
|
39
|
+
* @param status The HTTP status GitHub answered.
|
|
40
|
+
* @param timing Whether it was a rate limit and when to try again; neither when absent.
|
|
41
|
+
*/
|
|
42
|
+
constructor(status, timing = {}) {
|
|
16
43
|
super(`GitHub answered ${status}`);
|
|
17
44
|
this.status = status;
|
|
18
45
|
this.name = 'GitHubApiError';
|
|
46
|
+
this.rateLimited = timing.rateLimited ?? false;
|
|
47
|
+
this.retryAt = timing.retryAt ?? null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** A call that got no answer from GitHub, or whose answer's body stopped before its end. Names no address or cause. */
|
|
51
|
+
export class GitHubNoAnswer extends Error {
|
|
52
|
+
reason;
|
|
53
|
+
/** @param reason Why the call got no answer. */
|
|
54
|
+
constructor(reason) {
|
|
55
|
+
super(`GitHub gave no answer: ${reason}`);
|
|
56
|
+
this.reason = reason;
|
|
57
|
+
this.name = 'GitHubNoAnswer';
|
|
19
58
|
}
|
|
20
59
|
}
|
|
21
60
|
/** The headers of a call: the bearer, the media type, the pinned API version and the pack's user agent. */
|
|
@@ -36,25 +75,114 @@ export async function discardBody(response) {
|
|
|
36
75
|
// The body is not needed; a failed cancel changes nothing for the caller.
|
|
37
76
|
}
|
|
38
77
|
}
|
|
39
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* The deadline of one call: its own timeout joined with the caller's signal, so the call and the reading of its body
|
|
80
|
+
* end at whichever comes first.
|
|
81
|
+
*
|
|
82
|
+
* @param signal The caller's signal, if any.
|
|
83
|
+
* @param timeoutMs How long the call may take; {@link GITHUB_TIMEOUT_MS} when absent.
|
|
84
|
+
*/
|
|
85
|
+
export function callDeadline(signal, timeoutMs = GITHUB_TIMEOUT_MS) {
|
|
86
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
87
|
+
return {
|
|
88
|
+
signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]),
|
|
89
|
+
noAnswer: () => new GitHubNoAnswer(signal?.aborted === true ? 'cancelled' : timeout.aborted ? 'timeout' : 'network'),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** True when a refused call's body names a rate limit, read up to {@link REFUSAL_READ_BYTES}; the body is never kept. */
|
|
93
|
+
async function bodyNamesRateLimit(response) {
|
|
94
|
+
let fields;
|
|
95
|
+
try {
|
|
96
|
+
fields = JSON.parse(new TextDecoder().decode(await readCapped(response, REFUSAL_READ_BYTES)));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// An unreadable or oversized refusal is told by its status and headers alone.
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (typeof fields !== 'object' || fields === null || Array.isArray(fields))
|
|
103
|
+
return false;
|
|
104
|
+
const { message, documentation_url: documentation } = fields;
|
|
105
|
+
return (typeof message === 'string' && /rate limit/i.test(message)) || (typeof documentation === 'string' && /rate-limit/i.test(documentation));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The time a `retry-after` header gives, as delay-seconds or an HTTP date, in epoch milliseconds, or null when it
|
|
109
|
+
* gives none that can be read. The wait is kept whole, however long: GitHub asks that no call be made before it ends.
|
|
110
|
+
* A date already past answers `now`.
|
|
111
|
+
*/
|
|
112
|
+
function retryAfterAt(header, now) {
|
|
113
|
+
const value = (header ?? '').trim();
|
|
114
|
+
if (/^\d+$/.test(value)) {
|
|
115
|
+
const at = now + Number(value) * 1000;
|
|
116
|
+
return Number.isSafeInteger(at) ? at : null;
|
|
117
|
+
}
|
|
118
|
+
const date = value === '' ? Number.NaN : Date.parse(value);
|
|
119
|
+
return Number.isFinite(date) ? Math.max(date, now) : null;
|
|
120
|
+
}
|
|
121
|
+
/** The refusal an answer outside 2xx reads as: its status, whether it was a rate limit, and when to try again. */
|
|
122
|
+
async function refusalOf(response) {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const retryAfter = retryAfterAt(response.headers.get('retry-after'), now);
|
|
125
|
+
const exhausted = response.headers.get('x-ratelimit-remaining')?.trim() === '0';
|
|
126
|
+
const reset = (response.headers.get('x-ratelimit-reset') ?? '').trim();
|
|
127
|
+
let rateLimited = response.status === 429 || (response.status === 403 && (retryAfter !== null || exhausted));
|
|
128
|
+
if (!rateLimited && response.status === 403)
|
|
129
|
+
rateLimited = await bodyNamesRateLimit(response);
|
|
130
|
+
else
|
|
131
|
+
await discardBody(response);
|
|
132
|
+
let retryAt = retryAfter;
|
|
133
|
+
if (retryAt === null && rateLimited) {
|
|
134
|
+
retryAt = exhausted && /^\d+$/.test(reset) ? Math.max(Number(reset) * 1000, now + 1000) : now + RATE_LIMIT_WAIT_MS;
|
|
135
|
+
}
|
|
136
|
+
return new GitHubApiError(response.status, { rateLimited, retryAt });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Sends one call under its deadline and the caller's signal. An answer outside 2xx is read only as far as telling a
|
|
140
|
+
* rate limit needs and thrown as {@link GitHubApiError}; a call that gets no answer throws {@link GitHubNoAnswer}.
|
|
141
|
+
*/
|
|
40
142
|
export async function githubSend(fetchImpl, url, request) {
|
|
41
143
|
const headers = githubHeaders(request.token, request.accept);
|
|
42
144
|
if (request.body !== undefined)
|
|
43
145
|
headers['content-type'] = 'application/json';
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
146
|
+
const deadline = callDeadline(request.signal, request.timeoutMs);
|
|
147
|
+
let response;
|
|
148
|
+
try {
|
|
149
|
+
response = await fetchImpl(url, { method: request.method ?? 'GET', headers, body: request.body, signal: deadline.signal });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// The fetch's own error would name the address or its cause; the reason is all a caller acts on.
|
|
153
|
+
throw deadline.noAnswer();
|
|
48
154
|
}
|
|
49
|
-
|
|
155
|
+
if (!response.ok)
|
|
156
|
+
throw await refusalOf(response);
|
|
157
|
+
return {
|
|
158
|
+
response,
|
|
159
|
+
async bytes(maxBytes) {
|
|
160
|
+
try {
|
|
161
|
+
return await readCapped(response, maxBytes);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof ReadCapExceeded)
|
|
165
|
+
throw error;
|
|
166
|
+
throw deadline.noAnswer();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
50
170
|
}
|
|
51
|
-
/** An answer's JSON, or `undefined` when it has no body (a 202 or a 204). */
|
|
52
|
-
async function answerOf(
|
|
53
|
-
|
|
54
|
-
|
|
171
|
+
/** An answer's JSON, read under {@link GITHUB_MAX_ANSWER_BYTES}, or `undefined` when it has no body (a 202 or a 204). */
|
|
172
|
+
async function answerOf(answer) {
|
|
173
|
+
let bytes;
|
|
174
|
+
try {
|
|
175
|
+
bytes = await answer.bytes(GITHUB_MAX_ANSWER_BYTES);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof ReadCapExceeded)
|
|
179
|
+
throw new Error('GitHub answered a body past the size cap');
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
if (bytes.byteLength === 0)
|
|
55
183
|
return undefined;
|
|
56
184
|
try {
|
|
57
|
-
return JSON.parse(
|
|
185
|
+
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
58
186
|
}
|
|
59
187
|
catch {
|
|
60
188
|
// The parser's message would quote the body.
|
|
@@ -64,7 +192,7 @@ async function answerOf(response) {
|
|
|
64
192
|
/**
|
|
65
193
|
* One call to `url` (an address under the API's base), answering its JSON, or `undefined` for an answer without a
|
|
66
194
|
* body. Sends `Authorization: Bearer <token>`, `Accept: application/vnd.github+json` unless told otherwise,
|
|
67
|
-
* `X-GitHub-Api-Version` and `User-Agent
|
|
195
|
+
* `X-GitHub-Api-Version` and `User-Agent`, and ends at its deadline.
|
|
68
196
|
*/
|
|
69
197
|
export async function githubFetch(fetchImpl, url, request) {
|
|
70
198
|
return answerOf(await githubSend(fetchImpl, url, request));
|
|
@@ -77,16 +205,16 @@ export function nextPage(link) {
|
|
|
77
205
|
return match === null ? null : match[1];
|
|
78
206
|
}
|
|
79
207
|
/**
|
|
80
|
-
* Each page of a listing as its JSON, following `Link: <...>; rel="next"
|
|
81
|
-
* first is refused, so the bearer never leaves the API it was sent to.
|
|
208
|
+
* Each page of a listing as its JSON, following `Link: <...>; rel="next"`, each page under a deadline of its own. A
|
|
209
|
+
* next page on another origin than the first is refused, so the bearer never leaves the API it was sent to.
|
|
82
210
|
*/
|
|
83
211
|
export async function* githubPages(fetchImpl, url, request) {
|
|
84
212
|
const origin = new URL(url).origin;
|
|
85
213
|
let next = url;
|
|
86
214
|
while (next !== null) {
|
|
87
|
-
const
|
|
88
|
-
const link = response.headers.get('link');
|
|
89
|
-
yield await answerOf(
|
|
215
|
+
const answer = await githubSend(fetchImpl, next, request);
|
|
216
|
+
const link = answer.response.headers.get('link');
|
|
217
|
+
yield await answerOf(answer);
|
|
90
218
|
next = nextPage(link);
|
|
91
219
|
if (next !== null && new URL(next).origin !== origin)
|
|
92
220
|
throw new Error('GitHub named a next page on another origin');
|
package/dist/app/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview What a user token reaches (the person, the app's installations they can access, and the repositories
|
|
3
3
|
* of an installation they can read) and the app's own installations, listed and removed with its JWT. Each function
|
|
4
|
-
* takes `{ fetchImpl, apiBase }` last: `fetch
|
|
4
|
+
* takes `{ fetchImpl, apiBase, timeoutMs }` last: `fetch`, GitHub's API and a 30-second deadline a call when absent.
|
|
5
5
|
* @module agentos-ext-github/app/installations
|
|
6
6
|
*/
|
|
7
7
|
import type { GitHubAppCredential } from './credential.js';
|
|
@@ -27,22 +27,24 @@ export interface UserRepository {
|
|
|
27
27
|
fullName: string;
|
|
28
28
|
defaultBranch: string;
|
|
29
29
|
}
|
|
30
|
+
/** An installation's account, as GitHub answers it and as a webhook event names it: a login, or an enterprise's slug, and an id. */
|
|
31
|
+
export declare function accountOf(value: unknown): GitHubAccount;
|
|
30
32
|
/** The person a user token belongs to (`GET /user`): the GitHub id and login a link is made by. */
|
|
31
|
-
export declare function userOfToken(token: string, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubUser>;
|
|
33
|
+
export declare function userOfToken(token: string, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubUser>;
|
|
32
34
|
/** The app's installations a user token can access (`GET /user/installations`), across every page. */
|
|
33
|
-
export declare function installationsOfUser(token: string, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
|
|
35
|
+
export declare function installationsOfUser(token: string, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
|
|
34
36
|
/**
|
|
35
37
|
* The repositories of an installation a user token can read (`GET /user/installations/{installation_id}/repositories`),
|
|
36
38
|
* across every page: those the person owns, collaborates on or reaches through an organization.
|
|
37
39
|
*/
|
|
38
|
-
export declare function repositoriesOfUser(token: string, installationId: number, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<UserRepository[]>;
|
|
40
|
+
export declare function repositoriesOfUser(token: string, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<UserRepository[]>;
|
|
39
41
|
/** Every installation of the app (`GET /app/installations`, with its JWT), across every page. */
|
|
40
|
-
export declare function listInstallations(credential: GitHubAppCredential, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
|
|
42
|
+
export declare function listInstallations(credential: GitHubAppCredential, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
|
|
41
43
|
/**
|
|
42
44
|
* Uninstalls the app from an account (`DELETE /app/installations/{installation_id}`, with its JWT). Answers without an
|
|
43
45
|
* error on 202 (GitHub has taken the removal and does it in the background at the pinned API version), on 204 (the
|
|
44
46
|
* answer of earlier versions) and on 404 (the installation is already gone); the answer's body is never read. Any
|
|
45
47
|
* other status throws {@link GitHubApiError}.
|
|
46
48
|
*/
|
|
47
|
-
export declare function uninstallInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<void>;
|
|
49
|
+
export declare function uninstallInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<void>;
|
|
48
50
|
//# sourceMappingURL=installations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAEnB,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAEnB,0CAA0C;AAC1C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,oIAAoI;AACpI,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAMvD;AAUD,mGAAmG;AACnG,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,GAAE,gBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAGxJ;AAED,sGAAsG;AACtG,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,GAAE,gBAAqB,GACjF,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAI/B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,GAAE,gBAAqB,GACjF,OAAO,CAAC,cAAc,EAAE,CAAC,CAW3B;AAED,iGAAiG;AACjG,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,mBAAmB,EAC/B,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,GAAE,gBAAqB,GACjF,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAI/B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,mBAAmB,EAC/B,cAAc,EAAE,MAAM,EACtB,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,GAAE,gBAAqB,GACjF,OAAO,CAAC,IAAI,CAAC,CAQf"}
|
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview What a user token reaches (the person, the app's installations they can access, and the repositories
|
|
3
3
|
* of an installation they can read) and the app's own installations, listed and removed with its JWT. Each function
|
|
4
|
-
* takes `{ fetchImpl, apiBase }` last: `fetch
|
|
4
|
+
* takes `{ fetchImpl, apiBase, timeoutMs }` last: `fetch`, GitHub's API and a 30-second deadline a call when absent.
|
|
5
5
|
* @module agentos-ext-github/app/installations
|
|
6
6
|
*/
|
|
7
7
|
import { arrayOf, discardBody, GITHUB_API_BASE, GitHubApiError, githubFetch, githubSend, numberIn, objectOf, pages, stringIn, } from './http.js';
|
|
8
|
+
/** An installation's account, as GitHub answers it and as a webhook event names it: a login, or an enterprise's slug, and an id. */
|
|
9
|
+
export function accountOf(value) {
|
|
10
|
+
const account = objectOf(value, "an installation's account");
|
|
11
|
+
return {
|
|
12
|
+
login: stringIn(account, typeof account.login === 'string' ? 'login' : 'slug', "an installation's account"),
|
|
13
|
+
id: numberIn(account, 'id', "an installation's account"),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
8
16
|
function installationOf(value) {
|
|
9
17
|
const fields = objectOf(value, 'an installation');
|
|
10
|
-
const account = fields.account === null || fields.account === undefined ? null : objectOf(fields.account, "an installation's account");
|
|
11
18
|
return {
|
|
12
19
|
id: numberIn(fields, 'id', 'an installation'),
|
|
13
|
-
account: account === null
|
|
14
|
-
? null
|
|
15
|
-
: { login: stringIn(account, typeof account.login === 'string' ? 'login' : 'slug', "an installation's account"), id: numberIn(account, 'id', "an installation's account") },
|
|
20
|
+
account: fields.account === null || fields.account === undefined ? null : accountOf(fields.account),
|
|
16
21
|
};
|
|
17
22
|
}
|
|
18
23
|
/** The person a user token belongs to (`GET /user`): the GitHub id and login a link is made by. */
|
|
19
|
-
export async function userOfToken(token, { fetchImpl = fetch, apiBase = GITHUB_API_BASE } = {}) {
|
|
20
|
-
const user = objectOf(await githubFetch(fetchImpl, `${apiBase}/user`, { token }), 'a user');
|
|
24
|
+
export async function userOfToken(token, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
25
|
+
const user = objectOf(await githubFetch(fetchImpl, `${apiBase}/user`, { token, timeoutMs }), 'a user');
|
|
21
26
|
return { id: numberIn(user, 'id', 'a user'), login: stringIn(user, 'login', 'a user') };
|
|
22
27
|
}
|
|
23
28
|
/** The app's installations a user token can access (`GET /user/installations`), across every page. */
|
|
24
|
-
export async function installationsOfUser(token, { fetchImpl = fetch, apiBase = GITHUB_API_BASE } = {}) {
|
|
25
|
-
return pages(fetchImpl, `${apiBase}/user/installations?per_page=100`, { token }, (page) => arrayOf(objectOf(page, 'a page of installations').installations, 'a page of installations').map(installationOf));
|
|
29
|
+
export async function installationsOfUser(token, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
30
|
+
return pages(fetchImpl, `${apiBase}/user/installations?per_page=100`, { token, timeoutMs }, (page) => arrayOf(objectOf(page, 'a page of installations').installations, 'a page of installations').map(installationOf));
|
|
26
31
|
}
|
|
27
32
|
/**
|
|
28
33
|
* The repositories of an installation a user token can read (`GET /user/installations/{installation_id}/repositories`),
|
|
29
34
|
* across every page: those the person owns, collaborates on or reaches through an organization.
|
|
30
35
|
*/
|
|
31
|
-
export async function repositoriesOfUser(token, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE } = {}) {
|
|
32
|
-
return pages(fetchImpl, `${apiBase}/user/installations/${installationId}/repositories?per_page=100`, { token }, (page) => arrayOf(objectOf(page, 'a page of repositories').repositories, 'a page of repositories').map((value) => {
|
|
36
|
+
export async function repositoriesOfUser(token, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
37
|
+
return pages(fetchImpl, `${apiBase}/user/installations/${installationId}/repositories?per_page=100`, { token, timeoutMs }, (page) => arrayOf(objectOf(page, 'a page of repositories').repositories, 'a page of repositories').map((value) => {
|
|
33
38
|
const repository = objectOf(value, 'a repository');
|
|
34
39
|
return {
|
|
35
40
|
id: numberIn(repository, 'id', 'a repository'),
|
|
@@ -39,8 +44,8 @@ export async function repositoriesOfUser(token, installationId, { fetchImpl = fe
|
|
|
39
44
|
}));
|
|
40
45
|
}
|
|
41
46
|
/** Every installation of the app (`GET /app/installations`, with its JWT), across every page. */
|
|
42
|
-
export async function listInstallations(credential, { fetchImpl = fetch, apiBase = GITHUB_API_BASE } = {}) {
|
|
43
|
-
return pages(fetchImpl, `${apiBase}/app/installations?per_page=100`, { token: credential.jwt() }, (page) => arrayOf(page, 'a page of installations').map(installationOf));
|
|
47
|
+
export async function listInstallations(credential, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
48
|
+
return pages(fetchImpl, `${apiBase}/app/installations?per_page=100`, { token: credential.jwt(), timeoutMs }, (page) => arrayOf(page, 'a page of installations').map(installationOf));
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
46
51
|
* Uninstalls the app from an account (`DELETE /app/installations/{installation_id}`, with its JWT). Answers without an
|
|
@@ -48,9 +53,10 @@ export async function listInstallations(credential, { fetchImpl = fetch, apiBase
|
|
|
48
53
|
* answer of earlier versions) and on 404 (the installation is already gone); the answer's body is never read. Any
|
|
49
54
|
* other status throws {@link GitHubApiError}.
|
|
50
55
|
*/
|
|
51
|
-
export async function uninstallInstallation(credential, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE } = {}) {
|
|
56
|
+
export async function uninstallInstallation(credential, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
52
57
|
try {
|
|
53
|
-
|
|
58
|
+
const answer = await githubSend(fetchImpl, `${apiBase}/app/installations/${installationId}`, { token: credential.jwt(), method: 'DELETE', timeoutMs });
|
|
59
|
+
await discardBody(answer.response);
|
|
54
60
|
}
|
|
55
61
|
catch (error) {
|
|
56
62
|
if (error instanceof GitHubApiError && error.status === 404)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installations.js","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,OAAO,EACP,WAAW,EACX,eAAe,EACf,cAAc,EACd,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,GAET,MAAM,WAAW,CAAC;AA2BnB,SAAS,
|
|
1
|
+
{"version":3,"file":"installations.js","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"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"}
|
package/dist/app/reconcile.d.ts
CHANGED
|
@@ -35,6 +35,21 @@ export interface UnreadableRepository {
|
|
|
35
35
|
repositoryId: number;
|
|
36
36
|
status: number;
|
|
37
37
|
}
|
|
38
|
+
/** A redelivery GitHub refused: the delivery asked for again, and GitHub's status. */
|
|
39
|
+
export interface RefusedRedelivery {
|
|
40
|
+
id: number;
|
|
41
|
+
status: number;
|
|
42
|
+
}
|
|
43
|
+
/** How the deliveries step went, beside the deliveries it asked for again. */
|
|
44
|
+
export interface DeliveriesOutcome {
|
|
45
|
+
/**
|
|
46
|
+
* GitHub's status when it answered the deliveries listing outside 2xx, in which case no delivery was asked for
|
|
47
|
+
* again; null when the listing was read.
|
|
48
|
+
*/
|
|
49
|
+
listingStatus: number | null;
|
|
50
|
+
/** Redeliveries GitHub refused; the others were still asked for. */
|
|
51
|
+
refused: RefusedRedelivery[];
|
|
52
|
+
}
|
|
38
53
|
/** What the reconcile found and did. */
|
|
39
54
|
export interface ReconcileOutcome {
|
|
40
55
|
/** Installations the app no longer has: their links and documents go. */
|
|
@@ -45,6 +60,8 @@ export interface ReconcileOutcome {
|
|
|
45
60
|
redelivered: number[];
|
|
46
61
|
/** Repositories whose head GitHub would not answer. */
|
|
47
62
|
unreadable: UnreadableRepository[];
|
|
63
|
+
/** GitHub's refusals in the deliveries step, which never cost the lists above. */
|
|
64
|
+
deliveries: DeliveriesOutcome;
|
|
48
65
|
}
|
|
49
66
|
/**
|
|
50
67
|
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is
|
|
@@ -53,9 +70,16 @@ export interface ReconcileOutcome {
|
|
|
53
70
|
* status; and the app's failed deliveries since `since` are asked for again
|
|
54
71
|
* (`GET /app/hook/deliveries`, `POST /app/hook/deliveries/{delivery_id}/attempts`).
|
|
55
72
|
*
|
|
73
|
+
* A delivery whose newest attempt the receiving route answered with a 4xx other than 408 and 429 is not asked for
|
|
74
|
+
* again: such a refusal (a body past the route's size cap, a 413) answers the same on every attempt, and each new
|
|
75
|
+
* attempt would bring the delivery into the next run's window again, while a 408 or a 429 is a transient the route
|
|
76
|
+
* itself asks to be sent again. GitHub's refusals in the deliveries step never cost the lists computed before it: a
|
|
77
|
+
* deliveries listing GitHub answers outside 2xx is answered as `deliveries.listingStatus`, with no delivery asked for
|
|
78
|
+
* again, and a redelivery it refuses as an entry of `deliveries.refused`, the others still asked for.
|
|
79
|
+
*
|
|
56
80
|
* @param credential The app's credential.
|
|
57
81
|
* @param links The links to check.
|
|
58
82
|
* @param options The window's start, and where calls go.
|
|
59
83
|
*/
|
|
60
|
-
export declare function reconcileInstallations(credential: GitHubAppCredential, links: readonly ReconcileLink[], { since, fetchImpl, apiBase }: ReconcileOptions): Promise<ReconcileOutcome>;
|
|
84
|
+
export declare function reconcileInstallations(credential: GitHubAppCredential, links: readonly ReconcileLink[], { since, fetchImpl, apiBase, timeoutMs }: ReconcileOptions): Promise<ReconcileOutcome>;
|
|
61
85
|
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/app/reconcile.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAGnB,2GAA2G;AAC3G,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC9C;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,iFAAiF;IACjF,KAAK,EAAE,IAAI,CAAC;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8HAA8H;AAC9H,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,6EAA6E;IAC7E,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uDAAuD;IACvD,UAAU,EAAE,oBAAoB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/app/reconcile.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAWL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAGnB,2GAA2G;AAC3G,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC9C;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD,iFAAiF;IACjF,KAAK,EAAE,IAAI,CAAC;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8HAA8H;AAC9H,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oEAAoE;IACpE,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,6EAA6E;IAC7E,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,kCAAkC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uDAAuD;IACvD,UAAU,EAAE,oBAAoB,EAAE,CAAC;IACnC,kFAAkF;IAClF,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAkFD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,EAAE,KAAK,EAAE,SAAiB,EAAE,OAAyB,EAAE,SAAS,EAAE,EAAE,gBAAgB,GACnF,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
|
package/dist/app/reconcile.js
CHANGED
|
@@ -5,46 +5,76 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { arrayOf, discardBody, GITHUB_API_BASE, GitHubApiError, githubFetch, githubPages, githubSend, numberIn, objectOf, stringIn, } from './http.js';
|
|
7
7
|
import { listInstallations } from './installations.js';
|
|
8
|
+
/** The client refusals a retry may change: a timeout (408) and a rate limit (429). */
|
|
9
|
+
const TRANSIENT_CLIENT_STATUSES = new Set([408, 429]);
|
|
8
10
|
function deliveryOf(value) {
|
|
9
11
|
const fields = objectOf(value, 'a delivery');
|
|
10
|
-
const status = numberIn(fields, 'status_code', 'a delivery');
|
|
11
12
|
return {
|
|
12
13
|
id: numberIn(fields, 'id', 'a delivery'),
|
|
13
14
|
guid: stringIn(fields, 'guid', 'a delivery'),
|
|
14
15
|
deliveredAt: Date.parse(stringIn(fields, 'delivered_at', 'a delivery')),
|
|
15
|
-
|
|
16
|
+
statusCode: numberIn(fields, 'status_code', 'a delivery'),
|
|
16
17
|
};
|
|
17
18
|
}
|
|
19
|
+
/** True when the route took the attempt (a 2xx). */
|
|
20
|
+
function delivered(attempt) {
|
|
21
|
+
return attempt.statusCode >= 200 && attempt.statusCode < 300;
|
|
22
|
+
}
|
|
23
|
+
/** True when the route refused the attempt with a 4xx a retry would not change: any but 408 and 429. */
|
|
24
|
+
function refusedByRoute(attempt) {
|
|
25
|
+
return attempt.statusCode >= 400 && attempt.statusCode < 500 && !TRANSIENT_CLIENT_STATUSES.has(attempt.statusCode);
|
|
26
|
+
}
|
|
18
27
|
/**
|
|
19
28
|
* Asks again for each event of the window that no attempt delivered: the newest failed attempt of each delivery guid
|
|
20
|
-
* with no successful attempt since `since
|
|
21
|
-
* that reaches past the window.
|
|
29
|
+
* with no successful attempt since `since`, unless the route refused that newest attempt with a 4xx other than 408 and
|
|
30
|
+
* 429. GitHub lists deliveries newest first, so the pages stop at the first one that reaches past the window. A
|
|
31
|
+
* listing GitHub answers outside 2xx asks for nothing again and answers GitHub's status, since a guid's other attempts
|
|
32
|
+
* may lie on the pages not read; a redelivery GitHub refuses is answered with its status, and the others are still
|
|
33
|
+
* asked for.
|
|
22
34
|
*/
|
|
23
|
-
async function redeliverFailures(credential, since, fetchImpl, apiBase) {
|
|
35
|
+
async function redeliverFailures(credential, since, fetchImpl, apiBase, timeoutMs) {
|
|
24
36
|
const attemptsByGuid = new Map();
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
try {
|
|
38
|
+
for await (const page of githubPages(fetchImpl, `${apiBase}/app/hook/deliveries?per_page=100`, { token: credential.jwt(), timeoutMs })) {
|
|
39
|
+
const deliveries = arrayOf(page, 'a page of deliveries').map(deliveryOf);
|
|
40
|
+
for (const delivery of deliveries) {
|
|
41
|
+
if (!(delivery.deliveredAt >= since))
|
|
42
|
+
continue;
|
|
43
|
+
const attempts = attemptsByGuid.get(delivery.guid);
|
|
44
|
+
if (attempts === undefined)
|
|
45
|
+
attemptsByGuid.set(delivery.guid, [delivery]);
|
|
46
|
+
else
|
|
47
|
+
attempts.push(delivery);
|
|
48
|
+
}
|
|
49
|
+
if (deliveries.length === 0 || deliveries.some((delivery) => !(delivery.deliveredAt >= since)))
|
|
50
|
+
break;
|
|
35
51
|
}
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (!(error instanceof GitHubApiError))
|
|
55
|
+
throw error;
|
|
56
|
+
return { redelivered: [], deliveries: { listingStatus: error.status, refused: [] } };
|
|
38
57
|
}
|
|
39
58
|
const redelivered = [];
|
|
59
|
+
const refused = [];
|
|
40
60
|
for (const attempts of attemptsByGuid.values()) {
|
|
41
|
-
if (attempts.some(
|
|
61
|
+
if (attempts.some(delivered))
|
|
42
62
|
continue;
|
|
43
63
|
const latest = attempts.reduce((newest, attempt) => (attempt.deliveredAt > newest.deliveredAt ? attempt : newest));
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
if (refusedByRoute(latest))
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const answer = await githubSend(fetchImpl, `${apiBase}/app/hook/deliveries/${latest.id}/attempts`, { token: credential.jwt(), method: 'POST', timeoutMs });
|
|
68
|
+
await discardBody(answer.response);
|
|
69
|
+
redelivered.push(latest.id);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!(error instanceof GitHubApiError))
|
|
73
|
+
throw error;
|
|
74
|
+
refused.push({ id: latest.id, status: error.status });
|
|
75
|
+
}
|
|
46
76
|
}
|
|
47
|
-
return redelivered;
|
|
77
|
+
return { redelivered, deliveries: { listingStatus: null, refused } };
|
|
48
78
|
}
|
|
49
79
|
/**
|
|
50
80
|
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is
|
|
@@ -53,12 +83,19 @@ async function redeliverFailures(credential, since, fetchImpl, apiBase) {
|
|
|
53
83
|
* status; and the app's failed deliveries since `since` are asked for again
|
|
54
84
|
* (`GET /app/hook/deliveries`, `POST /app/hook/deliveries/{delivery_id}/attempts`).
|
|
55
85
|
*
|
|
86
|
+
* A delivery whose newest attempt the receiving route answered with a 4xx other than 408 and 429 is not asked for
|
|
87
|
+
* again: such a refusal (a body past the route's size cap, a 413) answers the same on every attempt, and each new
|
|
88
|
+
* attempt would bring the delivery into the next run's window again, while a 408 or a 429 is a transient the route
|
|
89
|
+
* itself asks to be sent again. GitHub's refusals in the deliveries step never cost the lists computed before it: a
|
|
90
|
+
* deliveries listing GitHub answers outside 2xx is answered as `deliveries.listingStatus`, with no delivery asked for
|
|
91
|
+
* again, and a redelivery it refuses as an entry of `deliveries.refused`, the others still asked for.
|
|
92
|
+
*
|
|
56
93
|
* @param credential The app's credential.
|
|
57
94
|
* @param links The links to check.
|
|
58
95
|
* @param options The window's start, and where calls go.
|
|
59
96
|
*/
|
|
60
|
-
export async function reconcileInstallations(credential, links, { since, fetchImpl = fetch, apiBase = GITHUB_API_BASE }) {
|
|
61
|
-
const listed = new Set((await listInstallations(credential, { fetchImpl, apiBase })).map((installation) => installation.id));
|
|
97
|
+
export async function reconcileInstallations(credential, links, { since, fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs }) {
|
|
98
|
+
const listed = new Set((await listInstallations(credential, { fetchImpl, apiBase, timeoutMs })).map((installation) => installation.id));
|
|
62
99
|
const gone = [];
|
|
63
100
|
const moved = [];
|
|
64
101
|
const unreadable = [];
|
|
@@ -71,7 +108,7 @@ export async function reconcileInstallations(credential, links, { since, fetchIm
|
|
|
71
108
|
for (const repository of link.repositories) {
|
|
72
109
|
try {
|
|
73
110
|
const token = await credential.installationToken(link.installationId, [repository.id]);
|
|
74
|
-
const branch = objectOf(await githubFetch(fetchImpl, `${apiBase}/repos/${repository.fullName}/branches/${encodeURIComponent(repository.defaultBranch)}`, { token }), 'a branch');
|
|
111
|
+
const branch = objectOf(await githubFetch(fetchImpl, `${apiBase}/repos/${repository.fullName}/branches/${encodeURIComponent(repository.defaultBranch)}`, { token, timeoutMs }), 'a branch');
|
|
75
112
|
const head = stringIn(objectOf(branch.commit, "a branch's commit"), 'sha', "a branch's commit");
|
|
76
113
|
if (head !== repository.headCommit)
|
|
77
114
|
moved.push({ installationId: link.installationId, repositoryId: repository.id, head });
|
|
@@ -83,7 +120,7 @@ export async function reconcileInstallations(credential, links, { since, fetchIm
|
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
122
|
}
|
|
86
|
-
const redelivered = await redeliverFailures(credential, since.getTime(), fetchImpl, apiBase);
|
|
87
|
-
return { gone, moved, redelivered, unreadable };
|
|
123
|
+
const { redelivered, deliveries } = await redeliverFailures(credential, since.getTime(), fetchImpl, apiBase, timeoutMs);
|
|
124
|
+
return { gone, moved, redelivered, unreadable, deliveries };
|
|
88
125
|
}
|
|
89
126
|
//# sourceMappingURL=reconcile.js.map
|