@framers/agentos-ext-github 0.5.0 → 0.7.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 +15 -8
- package/dist/__tests__/app.test.js +297 -1
- package/dist/__tests__/app.test.js.map +1 -1
- package/dist/__tests__/documents.test.js +48 -1
- package/dist/__tests__/documents.test.js.map +1 -1
- package/dist/app/credential.d.ts +26 -2
- package/dist/app/credential.d.ts.map +1 -1
- package/dist/app/credential.js +35 -3
- package/dist/app/credential.js.map +1 -1
- package/dist/app/installations.d.ts +72 -4
- package/dist/app/installations.d.ts.map +1 -1
- package/dist/app/installations.js +138 -13
- package/dist/app/installations.js.map +1 -1
- package/dist/app/reconcile.d.ts +20 -7
- package/dist/app/reconcile.d.ts.map +1 -1
- package/dist/app/reconcile.js +36 -14
- package/dist/app/reconcile.js.map +1 -1
- package/dist/documents/index.d.ts +7 -4
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +7 -4
- package/dist/documents/index.js.map +1 -1
- package/dist/documents/tree.d.ts +26 -2
- package/dist/documents/tree.d.ts.map +1 -1
- package/dist/documents/tree.js +47 -7
- package/dist/documents/tree.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +4 -4
package/dist/app/credential.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview A GitHub App's credential: the app's JWT, signed RS256 from its private key, and installation tokens
|
|
3
|
-
* minted with it, narrowed to the repositories asked for
|
|
3
|
+
* minted with it, narrowed to the repositories asked for, or to the metadata of every repository of the installation,
|
|
4
|
+
* and held in memory until near their end.
|
|
4
5
|
* @module agentos-ext-github/app/credential
|
|
5
6
|
*/
|
|
6
7
|
/** The app's identity: its client id (the JWT's issuer) and its private key in PEM. */
|
|
@@ -23,7 +24,10 @@ export interface InstallationTokenOptions {
|
|
|
23
24
|
* @param nowSeconds The time to issue it at, in Unix seconds; now when absent.
|
|
24
25
|
*/
|
|
25
26
|
export declare function appJwt(identity: GitHubAppIdentity, nowSeconds?: number): string;
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Installation tokens, minted narrowed to the repositories asked for, or to the metadata of every repository of the
|
|
29
|
+
* installation, and held in memory until near their end.
|
|
30
|
+
*/
|
|
27
31
|
export declare class GitHubAppCredential {
|
|
28
32
|
private readonly identity;
|
|
29
33
|
private readonly fetchImpl;
|
|
@@ -49,5 +53,25 @@ export declare class GitHubAppCredential {
|
|
|
49
53
|
* repository with a token for every repository the installation reaches.
|
|
50
54
|
*/
|
|
51
55
|
installationToken(installationId: number, repositoryIds: readonly number[], options?: InstallationTokenOptions): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* An installation token that reads the metadata of every repository the installation reaches and nothing else
|
|
58
|
+
* (`permissions: { metadata: 'read' }`, no repository named): enough to list the installation's repositories
|
|
59
|
+
* (`repositoriesOfInstallation`), never to read a file. Held as `installationToken` holds its tokens. A refusal
|
|
60
|
+
* throws `GitHubApiError` (404 for an installation GitHub no longer has), and a request that gets no answer throws
|
|
61
|
+
* `GitHubNoAnswer`.
|
|
62
|
+
*
|
|
63
|
+
* @param installationId The installation the token acts for.
|
|
64
|
+
* @param options The caller's signal and the request's deadline.
|
|
65
|
+
*/
|
|
66
|
+
metadataToken(installationId: number, options?: InstallationTokenOptions): Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Drops every token held for an installation, so the next call mints again: for a held token GitHub refused, as it
|
|
69
|
+
* refuses every token of an installation it no longer has, whose new token request then answers 404.
|
|
70
|
+
*
|
|
71
|
+
* @param installationId The installation whose held tokens are dropped.
|
|
72
|
+
*/
|
|
73
|
+
forget(installationId: number): void;
|
|
74
|
+
/** The token held under `key` while it has more than five minutes left, or a new one minted with `body` and held. */
|
|
75
|
+
private minted;
|
|
52
76
|
}
|
|
53
77
|
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../../src/app/credential.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../../src/app/credential.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,uFAAuF;AACvF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,uEAAuE;AACvE,MAAM,WAAW,wBAAwB;IACvC,gGAAgG;IAChG,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,wGAAwG;IACxG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,iBAAiB,EAAE,UAAU,SAAgC,GAAG,MAAM,CAKtG;AAED;;;GAGG;AACH,qBAAa,mBAAmB;IAQlB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAAqB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAAwB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2D;IAEhF;;;;OAIG;gBAC0B,QAAQ,EAAE,iBAAiB,EAAmB,SAAS,GAAE,OAAO,KAAa,EAAmB,OAAO,SAAkB;IAEtJ,2CAA2C;IAC3C,GAAG,IAAI,MAAM;IAIb;;;;;;;;;;OAUG;IACG,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ1I;;;;;;;;;OASG;IACG,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,MAAM,CAAC;IAIpG;;;;;OAKG;IACH,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAIpC,qHAAqH;YACvG,MAAM;CAoBrB"}
|
package/dist/app/credential.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview A GitHub App's credential: the app's JWT, signed RS256 from its private key, and installation tokens
|
|
3
|
-
* minted with it, narrowed to the repositories asked for
|
|
3
|
+
* minted with it, narrowed to the repositories asked for, or to the metadata of every repository of the installation,
|
|
4
|
+
* and held in memory until near their end.
|
|
4
5
|
* @module agentos-ext-github/app/credential
|
|
5
6
|
*/
|
|
6
7
|
import { createSign } from 'node:crypto';
|
|
@@ -23,7 +24,10 @@ export function appJwt(identity, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
|
23
24
|
const signature = createSign('RSA-SHA256').update(`${head}.${body}`).sign(identity.privateKey);
|
|
24
25
|
return `${head}.${body}.${base64url(signature)}`;
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Installation tokens, minted narrowed to the repositories asked for, or to the metadata of every repository of the
|
|
29
|
+
* installation, and held in memory until near their end.
|
|
30
|
+
*/
|
|
27
31
|
export class GitHubAppCredential {
|
|
28
32
|
identity;
|
|
29
33
|
fetchImpl;
|
|
@@ -59,6 +63,34 @@ export class GitHubAppCredential {
|
|
|
59
63
|
throw new TypeError('An installation token needs at least one repository id: with none, GitHub mints one for every repository of the installation');
|
|
60
64
|
}
|
|
61
65
|
const key = `${installationId}:${[...repositoryIds].sort((a, b) => a - b).join(',')}`;
|
|
66
|
+
return this.minted(installationId, key, { repository_ids: repositoryIds }, options);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* An installation token that reads the metadata of every repository the installation reaches and nothing else
|
|
70
|
+
* (`permissions: { metadata: 'read' }`, no repository named): enough to list the installation's repositories
|
|
71
|
+
* (`repositoriesOfInstallation`), never to read a file. Held as `installationToken` holds its tokens. A refusal
|
|
72
|
+
* throws `GitHubApiError` (404 for an installation GitHub no longer has), and a request that gets no answer throws
|
|
73
|
+
* `GitHubNoAnswer`.
|
|
74
|
+
*
|
|
75
|
+
* @param installationId The installation the token acts for.
|
|
76
|
+
* @param options The caller's signal and the request's deadline.
|
|
77
|
+
*/
|
|
78
|
+
async metadataToken(installationId, options = {}) {
|
|
79
|
+
return this.minted(installationId, `${installationId}:metadata`, { permissions: { metadata: 'read' } }, options);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Drops every token held for an installation, so the next call mints again: for a held token GitHub refused, as it
|
|
83
|
+
* refuses every token of an installation it no longer has, whose new token request then answers 404.
|
|
84
|
+
*
|
|
85
|
+
* @param installationId The installation whose held tokens are dropped.
|
|
86
|
+
*/
|
|
87
|
+
forget(installationId) {
|
|
88
|
+
for (const key of this.held.keys())
|
|
89
|
+
if (key.startsWith(`${installationId}:`))
|
|
90
|
+
this.held.delete(key);
|
|
91
|
+
}
|
|
92
|
+
/** The token held under `key` while it has more than five minutes left, or a new one minted with `body` and held. */
|
|
93
|
+
async minted(installationId, key, body, options) {
|
|
62
94
|
const now = Date.now();
|
|
63
95
|
const held = this.held.get(key);
|
|
64
96
|
if (held !== undefined && now < held.expiresAt - RENEW_BEFORE_MS)
|
|
@@ -70,7 +102,7 @@ export class GitHubAppCredential {
|
|
|
70
102
|
const answer = objectOf(await githubFetch(this.fetchImpl, `${this.apiBase}/app/installations/${installationId}/access_tokens`, {
|
|
71
103
|
token: this.jwt(),
|
|
72
104
|
method: 'POST',
|
|
73
|
-
body: JSON.stringify(
|
|
105
|
+
body: JSON.stringify(body),
|
|
74
106
|
signal: options.signal,
|
|
75
107
|
timeoutMs: options.timeoutMs,
|
|
76
108
|
}), 'an installation token');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../../src/app/credential.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../../src/app/credential.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview What a user token reaches (the person, the app's installations they can access, and the repositories
|
|
3
|
-
* of an installation they can read)
|
|
4
|
-
*
|
|
3
|
+
* of an installation they can read), the app's own installations, listed, read one at a time and removed with its JWT,
|
|
4
|
+
* each with when it was suspended, and the repositories an installation reaches, listed with an installation token, and
|
|
5
|
+
* those of them a caller holds that it no longer reaches. Each function takes `{ fetchImpl, apiBase, timeoutMs }` last:
|
|
6
|
+
* `fetch`, GitHub's API and a 30-second deadline a call when absent.
|
|
5
7
|
* @module agentos-ext-github/app/installations
|
|
6
8
|
*/
|
|
7
9
|
import type { GitHubAppCredential } from './credential.js';
|
|
@@ -21,12 +23,26 @@ export interface GitHubInstallation {
|
|
|
21
23
|
id: number;
|
|
22
24
|
account: GitHubAccount | null;
|
|
23
25
|
}
|
|
26
|
+
/** One installation as the app's own calls answer it (`listInstallations`, `getInstallation`): with when it was suspended. */
|
|
27
|
+
export interface AppInstallation extends GitHubInstallation {
|
|
28
|
+
/**
|
|
29
|
+
* When the installation was suspended (GitHub's `suspended_at`, an ISO 8601 time), or null while it is not. GitHub
|
|
30
|
+
* blocks a suspended installation's access to the API and its webhook events until it is unsuspended.
|
|
31
|
+
*/
|
|
32
|
+
suspendedAt: string | null;
|
|
33
|
+
}
|
|
24
34
|
/** A repository a user token can read in an installation. */
|
|
25
35
|
export interface UserRepository {
|
|
26
36
|
id: number;
|
|
27
37
|
fullName: string;
|
|
28
38
|
defaultBranch: string;
|
|
29
39
|
}
|
|
40
|
+
/** A repository an installation reaches, as its own listing answers it. */
|
|
41
|
+
export interface InstallationRepository {
|
|
42
|
+
id: number;
|
|
43
|
+
fullName: string;
|
|
44
|
+
defaultBranch: string;
|
|
45
|
+
}
|
|
30
46
|
/** 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
47
|
export declare function accountOf(value: unknown): GitHubAccount;
|
|
32
48
|
/** The person a user token belongs to (`GET /user`): the GitHub id and login a link is made by. */
|
|
@@ -38,8 +54,60 @@ export declare function installationsOfUser(token: string, { fetchImpl, apiBase,
|
|
|
38
54
|
* across every page: those the person owns, collaborates on or reaches through an organization.
|
|
39
55
|
*/
|
|
40
56
|
export declare function repositoriesOfUser(token: string, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<UserRepository[]>;
|
|
41
|
-
/**
|
|
42
|
-
|
|
57
|
+
/**
|
|
58
|
+
* An installation's repositories changed while each of {@link LISTING_READS} reads of its listing was read (its pages
|
|
59
|
+
* disagreed on GitHub's `total_count`), so no read is whole. The message names nothing of the installation.
|
|
60
|
+
*/
|
|
61
|
+
export declare class GitHubListingChanged extends Error {
|
|
62
|
+
constructor();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Every repository an installation reaches (`GET /installation/repositories`), across every page, read with an
|
|
66
|
+
* installation token that reads their metadata and nothing else (`credential.metadataToken`): the whole of what the
|
|
67
|
+
* app may read through the installation, whoever asks, so a repository its owner took off the app since a link was
|
|
68
|
+
* made is missing from it. The token is minted with the credential's own fetch; the pages are read with `fetchImpl`. A
|
|
69
|
+
* held token GitHub refuses (401, as it refuses every token of an installation it no longer has) is dropped and one
|
|
70
|
+
* new token asked for, once. A refused token request throws `GitHubApiError` (404 for an installation GitHub no longer
|
|
71
|
+
* has), so a lost installation never reads as one that reaches no repository. The listing is read a page at a time, and
|
|
72
|
+
* a repository added to or taken off the installation between two pages changes GitHub's `total_count` and can move
|
|
73
|
+
* another one past a page's end: a read whose pages disagree on that count is read again, up to three reads, and then
|
|
74
|
+
* {@link GitHubListingChanged} is thrown. An addition and a removal between the same two pages leave the count as it
|
|
75
|
+
* was and can still move one past a page's end, so {@link repositoriesLost} reads the listing twice before it answers a
|
|
76
|
+
* repository lost.
|
|
77
|
+
*/
|
|
78
|
+
export declare function repositoriesOfInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<InstallationRepository[]>;
|
|
79
|
+
/**
|
|
80
|
+
* Which of the repositories `held` an installation no longer reaches: those missing from its listing
|
|
81
|
+
* (`repositoriesOfInstallation`, each read one whose pages agree on GitHub's count) read twice, the second listing read
|
|
82
|
+
* only when the first misses one. An addition and a removal between the same two pages of a listing leave the count as
|
|
83
|
+
* it was and can move a repository the installation still reaches past a page's end, so a repository missing from one
|
|
84
|
+
* listing alone is not answered lost; one missing from both is. Answers in the order of `held`, each id once, and makes
|
|
85
|
+
* no request for an empty `held`. A refused listing, and one that kept changing (`GitHubListingChanged`), throws, so a
|
|
86
|
+
* failure never answers a repository lost.
|
|
87
|
+
*
|
|
88
|
+
* @param credential The app's credential, whose metadata token the listings are read with.
|
|
89
|
+
* @param installationId The installation.
|
|
90
|
+
* @param held The repositories the caller holds for the installation.
|
|
91
|
+
* @param options Where calls go.
|
|
92
|
+
*/
|
|
93
|
+
export declare function repositoriesLost(credential: GitHubAppCredential, installationId: number, held: readonly number[], options?: GitHubApiOptions): Promise<number[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Every installation of the app (`GET /app/installations`, with its JWT), across every page, each with when GitHub
|
|
96
|
+
* suspended it. The listing is read a page at a time, so an installation removed while it is read can move another one
|
|
97
|
+
* past a page's end, and that one is missing from the answer: `reconcileInstallations` asks for each linked
|
|
98
|
+
* installation it misses by its own address (`getInstallation`) before it answers one gone.
|
|
99
|
+
*/
|
|
100
|
+
export declare function listInstallations(credential: GitHubAppCredential, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<AppInstallation[]>;
|
|
101
|
+
/**
|
|
102
|
+
* One installation of the app (`GET /app/installations/{installation_id}`, with its JWT), with when GitHub suspended
|
|
103
|
+
* it, or null when GitHub no longer has it (404). Any other refusal throws {@link GitHubApiError}, and a call that gets
|
|
104
|
+
* no answer throws `GitHubNoAnswer`, so an installation is never answered gone on a failure.
|
|
105
|
+
*
|
|
106
|
+
* @param credential The app's credential.
|
|
107
|
+
* @param installationId The installation.
|
|
108
|
+
* @param options Where calls go.
|
|
109
|
+
*/
|
|
110
|
+
export declare function getInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<AppInstallation | null>;
|
|
43
111
|
/**
|
|
44
112
|
* Uninstalls the app from an account (`DELETE /app/installations/{installation_id}`, with its JWT). Answers without an
|
|
45
113
|
* error on a 2xx (202 at the pinned API version, where GitHub has taken the removal and does it in the background; 204
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview What a user token reaches (the person, the app's installations they can access, and the repositories
|
|
3
|
-
* of an installation they can read)
|
|
4
|
-
*
|
|
3
|
+
* of an installation they can read), the app's own installations, listed, read one at a time and removed with its JWT,
|
|
4
|
+
* each with when it was suspended, and the repositories an installation reaches, listed with an installation token, and
|
|
5
|
+
* those of them a caller holds that it no longer reaches. Each function takes `{ fetchImpl, apiBase, timeoutMs }` last:
|
|
6
|
+
* `fetch`, GitHub's API and a 30-second deadline a call when absent.
|
|
5
7
|
* @module agentos-ext-github/app/installations
|
|
6
8
|
*/
|
|
7
|
-
import { arrayOf, discardBody, GITHUB_API_BASE, GitHubApiError, githubFetch, githubSend, numberIn, objectOf, pages, stringIn, } from './http.js';
|
|
9
|
+
import { arrayOf, discardBody, GITHUB_API_BASE, GitHubApiError, githubFetch, githubPages, githubSend, numberIn, objectOf, pages, stringIn, } from './http.js';
|
|
8
10
|
/** 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
11
|
export function accountOf(value) {
|
|
10
12
|
const account = objectOf(value, "an installation's account");
|
|
@@ -20,6 +22,22 @@ function installationOf(value) {
|
|
|
20
22
|
account: fields.account === null || fields.account === undefined ? null : accountOf(fields.account),
|
|
21
23
|
};
|
|
22
24
|
}
|
|
25
|
+
/** An installation as the app's own calls answer it: `suspended_at` a time, or null when GitHub sends none. */
|
|
26
|
+
function appInstallationOf(value) {
|
|
27
|
+
const fields = objectOf(value, 'an installation');
|
|
28
|
+
return { ...installationOf(fields), suspendedAt: typeof fields.suspended_at === 'string' ? fields.suspended_at : null };
|
|
29
|
+
}
|
|
30
|
+
/** The repositories of a page of a repository listing: each one's id, full name and default branch. */
|
|
31
|
+
function repositoriesOfPage(page) {
|
|
32
|
+
return arrayOf(objectOf(page, 'a page of repositories').repositories, 'a page of repositories').map((value) => {
|
|
33
|
+
const repository = objectOf(value, 'a repository');
|
|
34
|
+
return {
|
|
35
|
+
id: numberIn(repository, 'id', 'a repository'),
|
|
36
|
+
fullName: stringIn(repository, 'full_name', 'a repository'),
|
|
37
|
+
defaultBranch: stringIn(repository, 'default_branch', 'a repository'),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
23
41
|
/** The person a user token belongs to (`GET /user`): the GitHub id and login a link is made by. */
|
|
24
42
|
export async function userOfToken(token, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
25
43
|
const user = objectOf(await githubFetch(fetchImpl, `${apiBase}/user`, { token, timeoutMs }), 'a user');
|
|
@@ -34,18 +52,125 @@ export async function installationsOfUser(token, { fetchImpl = fetch, apiBase =
|
|
|
34
52
|
* across every page: those the person owns, collaborates on or reaches through an organization.
|
|
35
53
|
*/
|
|
36
54
|
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 },
|
|
38
|
-
const repository = objectOf(value, 'a repository');
|
|
39
|
-
return {
|
|
40
|
-
id: numberIn(repository, 'id', 'a repository'),
|
|
41
|
-
fullName: stringIn(repository, 'full_name', 'a repository'),
|
|
42
|
-
defaultBranch: stringIn(repository, 'default_branch', 'a repository'),
|
|
43
|
-
};
|
|
44
|
-
}));
|
|
55
|
+
return pages(fetchImpl, `${apiBase}/user/installations/${installationId}/repositories?per_page=100`, { token, timeoutMs }, repositoriesOfPage);
|
|
45
56
|
}
|
|
46
|
-
/**
|
|
57
|
+
/** The reads of an installation's repositories made before {@link GitHubListingChanged} is thrown. */
|
|
58
|
+
const LISTING_READS = 3;
|
|
59
|
+
/**
|
|
60
|
+
* An installation's repositories changed while each of {@link LISTING_READS} reads of its listing was read (its pages
|
|
61
|
+
* disagreed on GitHub's `total_count`), so no read is whole. The message names nothing of the installation.
|
|
62
|
+
*/
|
|
63
|
+
export class GitHubListingChanged extends Error {
|
|
64
|
+
constructor() {
|
|
65
|
+
super("The installation's repositories changed while their listing was read");
|
|
66
|
+
this.name = 'GitHubListingChanged';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reads `GET /installation/repositories` once, every page, keeping each page's `total_count`: a repository added to or
|
|
71
|
+
* taken off the installation between two pages changes it, and the read is not settled.
|
|
72
|
+
*/
|
|
73
|
+
async function readInstallationListing(fetchImpl, url, token, timeoutMs) {
|
|
74
|
+
const repositories = [];
|
|
75
|
+
let count = null;
|
|
76
|
+
let settled = true;
|
|
77
|
+
for await (const page of githubPages(fetchImpl, url, { token, timeoutMs })) {
|
|
78
|
+
const total = numberIn(objectOf(page, 'a page of repositories'), 'total_count', 'a page of repositories');
|
|
79
|
+
if (count !== null && total !== count)
|
|
80
|
+
settled = false;
|
|
81
|
+
count = total;
|
|
82
|
+
repositories.push(...repositoriesOfPage(page));
|
|
83
|
+
}
|
|
84
|
+
return { repositories, settled };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Every repository an installation reaches (`GET /installation/repositories`), across every page, read with an
|
|
88
|
+
* installation token that reads their metadata and nothing else (`credential.metadataToken`): the whole of what the
|
|
89
|
+
* app may read through the installation, whoever asks, so a repository its owner took off the app since a link was
|
|
90
|
+
* made is missing from it. The token is minted with the credential's own fetch; the pages are read with `fetchImpl`. A
|
|
91
|
+
* held token GitHub refuses (401, as it refuses every token of an installation it no longer has) is dropped and one
|
|
92
|
+
* new token asked for, once. A refused token request throws `GitHubApiError` (404 for an installation GitHub no longer
|
|
93
|
+
* has), so a lost installation never reads as one that reaches no repository. The listing is read a page at a time, and
|
|
94
|
+
* a repository added to or taken off the installation between two pages changes GitHub's `total_count` and can move
|
|
95
|
+
* another one past a page's end: a read whose pages disagree on that count is read again, up to three reads, and then
|
|
96
|
+
* {@link GitHubListingChanged} is thrown. An addition and a removal between the same two pages leave the count as it
|
|
97
|
+
* was and can still move one past a page's end, so {@link repositoriesLost} reads the listing twice before it answers a
|
|
98
|
+
* repository lost.
|
|
99
|
+
*/
|
|
100
|
+
export async function repositoriesOfInstallation(credential, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
101
|
+
const url = `${apiBase}/installation/repositories?per_page=100`;
|
|
102
|
+
const read = async () => {
|
|
103
|
+
try {
|
|
104
|
+
return await readInstallationListing(fetchImpl, url, await credential.metadataToken(installationId, { timeoutMs }), timeoutMs);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!(error instanceof GitHubApiError) || error.status !== 401)
|
|
108
|
+
throw error;
|
|
109
|
+
credential.forget(installationId);
|
|
110
|
+
return readInstallationListing(fetchImpl, url, await credential.metadataToken(installationId, { timeoutMs }), timeoutMs);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
for (let attempt = 1; attempt <= LISTING_READS; attempt += 1) {
|
|
114
|
+
const listing = await read();
|
|
115
|
+
if (listing.settled)
|
|
116
|
+
return listing.repositories;
|
|
117
|
+
}
|
|
118
|
+
throw new GitHubListingChanged();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Which of the repositories `held` an installation no longer reaches: those missing from its listing
|
|
122
|
+
* (`repositoriesOfInstallation`, each read one whose pages agree on GitHub's count) read twice, the second listing read
|
|
123
|
+
* only when the first misses one. An addition and a removal between the same two pages of a listing leave the count as
|
|
124
|
+
* it was and can move a repository the installation still reaches past a page's end, so a repository missing from one
|
|
125
|
+
* listing alone is not answered lost; one missing from both is. Answers in the order of `held`, each id once, and makes
|
|
126
|
+
* no request for an empty `held`. A refused listing, and one that kept changing (`GitHubListingChanged`), throws, so a
|
|
127
|
+
* failure never answers a repository lost.
|
|
128
|
+
*
|
|
129
|
+
* @param credential The app's credential, whose metadata token the listings are read with.
|
|
130
|
+
* @param installationId The installation.
|
|
131
|
+
* @param held The repositories the caller holds for the installation.
|
|
132
|
+
* @param options Where calls go.
|
|
133
|
+
*/
|
|
134
|
+
export async function repositoriesLost(credential, installationId, held, options = {}) {
|
|
135
|
+
const missingFrom = (listed, among) => {
|
|
136
|
+
const reached = new Set(listed.map((repository) => repository.id));
|
|
137
|
+
return among.filter((id) => !reached.has(id));
|
|
138
|
+
};
|
|
139
|
+
const asked = [...new Set(held)];
|
|
140
|
+
if (asked.length === 0)
|
|
141
|
+
return [];
|
|
142
|
+
const first = missingFrom(await repositoriesOfInstallation(credential, installationId, options), asked);
|
|
143
|
+
if (first.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
return missingFrom(await repositoriesOfInstallation(credential, installationId, options), first);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Every installation of the app (`GET /app/installations`, with its JWT), across every page, each with when GitHub
|
|
149
|
+
* suspended it. The listing is read a page at a time, so an installation removed while it is read can move another one
|
|
150
|
+
* past a page's end, and that one is missing from the answer: `reconcileInstallations` asks for each linked
|
|
151
|
+
* installation it misses by its own address (`getInstallation`) before it answers one gone.
|
|
152
|
+
*/
|
|
47
153
|
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(
|
|
154
|
+
return pages(fetchImpl, `${apiBase}/app/installations?per_page=100`, { token: credential.jwt(), timeoutMs }, (page) => arrayOf(page, 'a page of installations').map(appInstallationOf));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* One installation of the app (`GET /app/installations/{installation_id}`, with its JWT), with when GitHub suspended
|
|
158
|
+
* it, or null when GitHub no longer has it (404). Any other refusal throws {@link GitHubApiError}, and a call that gets
|
|
159
|
+
* no answer throws `GitHubNoAnswer`, so an installation is never answered gone on a failure.
|
|
160
|
+
*
|
|
161
|
+
* @param credential The app's credential.
|
|
162
|
+
* @param installationId The installation.
|
|
163
|
+
* @param options Where calls go.
|
|
164
|
+
*/
|
|
165
|
+
export async function getInstallation(credential, installationId, { fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs } = {}) {
|
|
166
|
+
try {
|
|
167
|
+
return appInstallationOf(await githubFetch(fetchImpl, `${apiBase}/app/installations/${installationId}`, { token: credential.jwt(), timeoutMs }));
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (error instanceof GitHubApiError && error.status === 404)
|
|
171
|
+
return null;
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
49
174
|
}
|
|
50
175
|
/**
|
|
51
176
|
* Uninstalls the app from an account (`DELETE /app/installations/{installation_id}`, with its JWT). Answers without an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installations.js","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"installations.js","sourceRoot":"","sources":["../../src/app/installations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,OAAO,EACP,WAAW,EACX,eAAe,EACf,cAAc,EACd,WAAW,EACX,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,GAET,MAAM,WAAW,CAAC;AA2CnB,oIAAoI;AACpI,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,2BAA2B,CAAC,CAAC;IAC7D,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,2BAA2B,CAAC;QAC3G,EAAE,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,2BAA2B,CAAC;KACzD,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAClD,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC;QAC7C,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;KACpG,CAAC;AACJ,CAAC;AAED,+GAA+G;AAC/G,SAAS,iBAAiB,CAAC,KAAc;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAClD,OAAO,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1H,CAAC;AAED,uGAAuG;AACvG,SAAS,kBAAkB,CAAC,IAAa;IACvC,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5G,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;QACnD,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,CAAC;YAC9C,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE,WAAW,EAAE,cAAc,CAAC;YAC3D,aAAa,EAAE,QAAQ,CAAC,UAAU,EAAE,gBAAgB,EAAE,cAAc,CAAC;SACtE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAa,EAAE,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IACjI,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,WAAW,CAAC,SAAS,EAAE,GAAG,OAAO,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvG,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,sGAAsG;AACtG,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,OAAO,KAAK,CAAC,SAAS,EAAE,GAAG,OAAO,kCAAkC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CACnG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAChH,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,cAAsB,EACtB,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,OAAO,KAAK,CAAC,SAAS,EAAE,GAAG,OAAO,uBAAuB,cAAc,4BAA4B,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,kBAAkB,CAAC,CAAC;AACjJ,CAAC;AAED,sGAAsG;AACtG,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB;;;GAGG;AACH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C;QACE,KAAK,CAAC,sEAAsE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAQD;;;GAGG;AACH,KAAK,UAAU,uBAAuB,CAAC,SAAuB,EAAE,GAAW,EAAE,KAAa,EAAE,SAA6B;IACvH,MAAM,YAAY,GAA6B,EAAE,CAAC;IAClD,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QAC3E,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC,EAAE,aAAa,EAAE,wBAAwB,CAAC,CAAC;QAC1G,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;YAAE,OAAO,GAAG,KAAK,CAAC;QACvD,KAAK,GAAG,KAAK,CAAC;QACd,YAAY,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,UAA+B,EAC/B,cAAsB,EACtB,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,MAAM,GAAG,GAAG,GAAG,OAAO,yCAAyC,CAAC;IAChE,MAAM,IAAI,GAAG,KAAK,IAAkC,EAAE;QACpD,IAAI,CAAC;YACH,OAAO,MAAM,uBAAuB,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC,aAAa,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QACjI,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,cAAc,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;gBAAE,MAAM,KAAK,CAAC;YAC5E,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YAClC,OAAO,uBAAuB,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC,aAAa,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAC3H,CAAC;IACH,CAAC,CAAC;IACF,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,aAAa,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,YAAY,CAAC;IACnD,CAAC;IACD,MAAM,IAAI,oBAAoB,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAA+B,EAC/B,cAAsB,EACtB,IAAuB,EACvB,UAA4B,EAAE;IAE9B,MAAM,WAAW,GAAG,CAAC,MAAyC,EAAE,KAAwB,EAAY,EAAE;QACpG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QACnE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,0BAA0B,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IACxG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,WAAW,CAAC,MAAM,0BAA0B,CAAC,UAAU,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;AACnG,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAA+B,EAC/B,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,OAAO,KAAK,CAAC,SAAS,EAAE,GAAG,OAAO,iCAAiC,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CACpH,OAAO,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAChE,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAA+B,EAC/B,cAAsB,EACtB,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,IAAI,CAAC;QACH,OAAO,iBAAiB,CAAC,MAAM,WAAW,CAAC,SAAS,EAAE,GAAG,OAAO,sBAAsB,cAAc,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACnJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,cAAc,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAA+B,EAC/B,cAAsB,EACtB,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,SAAS,KAAuB,EAAE;IAElF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,GAAG,OAAO,sBAAsB,cAAc,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;QACvJ,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,cAAc,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO;QACpE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/app/reconcile.d.ts
CHANGED
|
@@ -60,8 +60,17 @@ export interface DeliveriesOutcome {
|
|
|
60
60
|
}
|
|
61
61
|
/** What the reconcile found and did. */
|
|
62
62
|
export interface ReconcileOutcome {
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Installations the app no longer has: their links and documents go. Each one missing from the installations listing
|
|
65
|
+
* is asked for by its own address, and answered gone only when GitHub answers that 404.
|
|
66
|
+
*/
|
|
64
67
|
gone: number[];
|
|
68
|
+
/**
|
|
69
|
+
* The linked installations GitHub has suspended, each once, in the order of the links: none of their repositories is
|
|
70
|
+
* read, since GitHub blocks a suspended installation's access to the API, and a linked installation not here nor in
|
|
71
|
+
* `gone` is not suspended, whatever a missed `suspend` or `unsuspend` delivery would have said.
|
|
72
|
+
*/
|
|
73
|
+
suspended: number[];
|
|
65
74
|
/** Repositories to compare by tree, as a push to their default branch is. */
|
|
66
75
|
moved: MovedRepository[];
|
|
67
76
|
/** Deliveries asked for again. */
|
|
@@ -78,8 +87,11 @@ export interface ReconcileOutcome {
|
|
|
78
87
|
retryAt: number | null;
|
|
79
88
|
}
|
|
80
89
|
/**
|
|
81
|
-
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is
|
|
82
|
-
*
|
|
90
|
+
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is asked
|
|
91
|
+
* for by its own address (`GET /app/installations/{installation_id}`, since the listing is read a page at a time and an
|
|
92
|
+
* installation removed meanwhile can move another past a page's end) and answered gone when GitHub answers that 404; an
|
|
93
|
+
* installation GitHub has suspended is answered suspended, its repositories not read; each repository of the other
|
|
94
|
+
* links has its default branch's head read by the repository's id
|
|
83
95
|
* (`/repositories/<id>/branches/<branch>`, never by its name, which can come to mean another repository) with an
|
|
84
96
|
* installation token narrowed to it, and is answered moved when the head differs from the one last read, or unreadable
|
|
85
97
|
* with GitHub's status; and the app's failed deliveries since `since` are asked for again
|
|
@@ -95,10 +107,11 @@ export interface ReconcileOutcome {
|
|
|
95
107
|
*
|
|
96
108
|
* A call GitHub refuses for a rate limit ends the reconcile: no call follows it (GitHub may ban an integration that
|
|
97
109
|
* keeps calling while it is rate limited), `retryAt` says when to run it again, and the outcome holds what was found
|
|
98
|
-
* before the refusal. The gone installations are always whole, since they
|
|
99
|
-
* whose head read was refused for a rate limit is not unreadable, and the repositories after
|
|
100
|
-
* step wait for the next run. A rate limit on `GET /app/installations` itself
|
|
101
|
-
* `retryAt`, as any refusal of
|
|
110
|
+
* before the refusal. The gone and the suspended installations are always whole, since they are settled before any
|
|
111
|
+
* head is read; a repository whose head read was refused for a rate limit is not unreadable, and the repositories after
|
|
112
|
+
* it and the deliveries step wait for the next run. A rate limit on `GET /app/installations` itself, or on an
|
|
113
|
+
* installation's own address, throws `GitHubApiError` with its `retryAt`, as any other refusal of those calls does
|
|
114
|
+
* (an installation's own address answering 404 apart), before anything is found.
|
|
102
115
|
*
|
|
103
116
|
* @param credential The app's credential.
|
|
104
117
|
* @param links The links to check.
|
|
@@ -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,EAYL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAGnB,2GAA2G;AAC3G,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,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;;;GAGG;AACH,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;;;OAGG;IACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B
|
|
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,EAYL,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAGnB,2GAA2G;AAC3G,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,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;;;GAGG;AACH,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;;;OAGG;IACH,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,EAAE,MAAM,EAAE,CAAC;IACf;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,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;IAC9B;;;;OAIG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAyFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;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,CA8C3B"}
|
package/dist/app/reconcile.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module agentos-ext-github/app/reconcile
|
|
5
5
|
*/
|
|
6
6
|
import { arrayOf, discardBody, GITHUB_API_BASE, GitHubApiError, githubFetch, githubPages, githubSend, numberIn, objectOf, repositoryPath, stringIn, } from './http.js';
|
|
7
|
-
import { listInstallations } from './installations.js';
|
|
7
|
+
import { getInstallation, listInstallations } from './installations.js';
|
|
8
8
|
/** Epoch milliseconds before which a call is not made again after GitHub refused it for a rate limit. */
|
|
9
9
|
function rateLimitEnd(error) {
|
|
10
10
|
// A rate limit always carries its time; a minute on, GitHub's own floor, should one ever come without it.
|
|
@@ -84,8 +84,11 @@ async function redeliverFailures(credential, since, fetchImpl, apiBase, timeoutM
|
|
|
84
84
|
return { redelivered, deliveries: { listingStatus: null, refused }, retryAt: null };
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is
|
|
88
|
-
*
|
|
87
|
+
* Reconciles a GitHub App's links with GitHub: an installation that `GET /app/installations` no longer lists is asked
|
|
88
|
+
* for by its own address (`GET /app/installations/{installation_id}`, since the listing is read a page at a time and an
|
|
89
|
+
* installation removed meanwhile can move another past a page's end) and answered gone when GitHub answers that 404; an
|
|
90
|
+
* installation GitHub has suspended is answered suspended, its repositories not read; each repository of the other
|
|
91
|
+
* links has its default branch's head read by the repository's id
|
|
89
92
|
* (`/repositories/<id>/branches/<branch>`, never by its name, which can come to mean another repository) with an
|
|
90
93
|
* installation token narrowed to it, and is answered moved when the head differs from the one last read, or unreadable
|
|
91
94
|
* with GitHub's status; and the app's failed deliveries since `since` are asked for again
|
|
@@ -101,25 +104,44 @@ async function redeliverFailures(credential, since, fetchImpl, apiBase, timeoutM
|
|
|
101
104
|
*
|
|
102
105
|
* A call GitHub refuses for a rate limit ends the reconcile: no call follows it (GitHub may ban an integration that
|
|
103
106
|
* keeps calling while it is rate limited), `retryAt` says when to run it again, and the outcome holds what was found
|
|
104
|
-
* before the refusal. The gone installations are always whole, since they
|
|
105
|
-
* whose head read was refused for a rate limit is not unreadable, and the repositories after
|
|
106
|
-
* step wait for the next run. A rate limit on `GET /app/installations` itself
|
|
107
|
-
* `retryAt`, as any refusal of
|
|
107
|
+
* before the refusal. The gone and the suspended installations are always whole, since they are settled before any
|
|
108
|
+
* head is read; a repository whose head read was refused for a rate limit is not unreadable, and the repositories after
|
|
109
|
+
* it and the deliveries step wait for the next run. A rate limit on `GET /app/installations` itself, or on an
|
|
110
|
+
* installation's own address, throws `GitHubApiError` with its `retryAt`, as any other refusal of those calls does
|
|
111
|
+
* (an installation's own address answering 404 apart), before anything is found.
|
|
108
112
|
*
|
|
109
113
|
* @param credential The app's credential.
|
|
110
114
|
* @param links The links to check.
|
|
111
115
|
* @param options The window's start, and where calls go.
|
|
112
116
|
*/
|
|
113
117
|
export async function reconcileInstallations(credential, links, { since, fetchImpl = fetch, apiBase = GITHUB_API_BASE, timeoutMs }) {
|
|
114
|
-
const listed = new
|
|
118
|
+
const listed = new Map((await listInstallations(credential, { fetchImpl, apiBase, timeoutMs })).map((installation) => [installation.id, installation]));
|
|
115
119
|
const gone = [];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
const suspended = [];
|
|
121
|
+
const settled = new Set();
|
|
122
|
+
for (const link of links) {
|
|
123
|
+
const installationId = link.installationId;
|
|
124
|
+
if (settled.has(installationId))
|
|
125
|
+
continue;
|
|
126
|
+
settled.add(installationId);
|
|
127
|
+
let installation = listed.get(installationId);
|
|
128
|
+
if (installation === undefined) {
|
|
129
|
+
// Missing from a listing read a page at a time is not gone: an installation removed meanwhile moves another past
|
|
130
|
+
// a page's end. GitHub's 404 at the installation's own address is.
|
|
131
|
+
const found = await getInstallation(credential, installationId, { fetchImpl, apiBase, timeoutMs });
|
|
132
|
+
if (found === null) {
|
|
133
|
+
gone.push(installationId);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
installation = found;
|
|
137
|
+
}
|
|
138
|
+
if (installation.suspendedAt !== null)
|
|
139
|
+
suspended.push(installationId);
|
|
140
|
+
}
|
|
119
141
|
const moved = [];
|
|
120
142
|
const unreadable = [];
|
|
121
143
|
for (const link of links) {
|
|
122
|
-
if (
|
|
144
|
+
if (gone.includes(link.installationId) || suspended.includes(link.installationId))
|
|
123
145
|
continue;
|
|
124
146
|
for (const repository of link.repositories) {
|
|
125
147
|
try {
|
|
@@ -134,13 +156,13 @@ export async function reconcileInstallations(credential, links, { since, fetchIm
|
|
|
134
156
|
if (!(error instanceof GitHubApiError))
|
|
135
157
|
throw error;
|
|
136
158
|
if (error.rateLimited) {
|
|
137
|
-
return { gone, moved, redelivered: [], unreadable, deliveries: { listingStatus: null, refused: [] }, retryAt: rateLimitEnd(error) };
|
|
159
|
+
return { gone, suspended, moved, redelivered: [], unreadable, deliveries: { listingStatus: null, refused: [] }, retryAt: rateLimitEnd(error) };
|
|
138
160
|
}
|
|
139
161
|
unreadable.push({ installationId: link.installationId, repositoryId: repository.id, status: error.status });
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
164
|
}
|
|
143
165
|
const { redelivered, deliveries, retryAt } = await redeliverFailures(credential, since.getTime(), fetchImpl, apiBase, timeoutMs);
|
|
144
|
-
return { gone, moved, redelivered, unreadable, deliveries, retryAt };
|
|
166
|
+
return { gone, suspended, moved, redelivered, unreadable, deliveries, retryAt };
|
|
145
167
|
}
|
|
146
168
|
//# sourceMappingURL=reconcile.js.map
|