@pepys/source-github 0.4.1 → 0.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pepys/source-github",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "The pepys GitHub plug-in: pull requests, releases, commits and deploys",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,9 +19,9 @@
19
19
  "access": "public"
20
20
  },
21
21
  "dependencies": {
22
- "@pepys/api": "0.4.1",
23
- "@pepys/ingest": "0.4.1",
24
- "@pepys/kit": "0.4.1",
22
+ "@pepys/api": "0.4.3",
23
+ "@pepys/ingest": "0.4.3",
24
+ "@pepys/kit": "0.4.3",
25
25
  "@zgeoff/std": "0.4.3",
26
26
  "kysely": "0.29.8",
27
27
  "ts-pattern": "5.9.2",
@@ -31,7 +31,7 @@
31
31
  "@msw/data": "1.1.8",
32
32
  "@orpc/client": "2.0.0-beta.50",
33
33
  "@pepys/mock-network": "0.0.0",
34
- "@pepys/testing": "0.4.1",
34
+ "@pepys/testing": "0.4.3",
35
35
  "msw": "3.0.3",
36
36
  "tiny-invariant": "1.3.3"
37
37
  }
@@ -0,0 +1,20 @@
1
+ import { buildAppJWT } from './build-app-jwt.ts';
2
+
3
+ // The function that signs the App's JWT, or undefined when the key cannot sign. GitHub refuses a JWT
4
+ // ten minutes after its `iat`, and a pull can run for longer, so each call takes a JWT of its own.
5
+ export function findAppJWTSigner(
6
+ env: Readonly<Record<string, string>>,
7
+ now: () => number,
8
+ ): (() => string) | undefined {
9
+ const appID = env['PEPYS_GITHUB_APP_ID'] ?? '';
10
+ const privateKey = env['PEPYS_GITHUB_APP_PRIVATE_KEY'] ?? '';
11
+
12
+ try {
13
+ // a JWT of time 0 shows that the key signs, and reads no clock
14
+ buildAppJWT(appID, privateKey, 0);
15
+ } catch {
16
+ return undefined;
17
+ }
18
+
19
+ return () => buildAppJWT(appID, privateKey, now());
20
+ }
@@ -11,13 +11,17 @@ export type InstallationToken =
11
11
  const installationSchema = z.looseObject({ id: z.number().int() });
12
12
  const accessTokenSchema = z.looseObject({ token: z.string().min(1) });
13
13
 
14
- // The token of the App's installation on one repository, `owner/name`, from the App's JWT.
14
+ // The token of the App's installation on one repository, `owner/name`. Each call signs its own JWT.
15
15
  export async function readInstallationToken(
16
16
  repository: string,
17
- appJWT: string,
17
+ signAppJWT: () => string,
18
18
  call: GithubCall,
19
19
  ): Promise<InstallationToken> {
20
- const installation = await requestGithubAPI(`/repos/${repository}/installation`, appJWT, call);
20
+ const installation = await requestGithubAPI(
21
+ `/repos/${repository}/installation`,
22
+ signAppJWT(),
23
+ call,
24
+ );
21
25
 
22
26
  if (installation.status === 404) {
23
27
  return { kind: 'not-installed' };
@@ -31,7 +35,7 @@ export async function readInstallationToken(
31
35
 
32
36
  const created = await requestGithubAPI(
33
37
  `/app/installations/${parsedInstallation.data.id}/access_tokens`,
34
- appJWT,
38
+ signAppJWT(),
35
39
  call,
36
40
  'POST',
37
41
  );
@@ -2,7 +2,7 @@ import { planAppends } from '@pepys/ingest';
2
2
  import type { PendingFact, PullContext, Pulled } from '@pepys/kit';
3
3
  import { z } from 'zod';
4
4
  import { buildGithubFact } from '../build-github-fact.ts';
5
- import { findAppJWT } from '../github-app/find-app-jwt.ts';
5
+ import { findAppJWTSigner } from '../github-app/find-app-jwt-signer.ts';
6
6
  import { pickPullFailure } from '../github-app/pick-pull-failure.ts';
7
7
  import { requestGithubAPI } from '../github-app/request-github-api.ts';
8
8
  import type { GithubAnswer, GithubCall } from '../github-app/request-github-api.ts';
@@ -19,15 +19,15 @@ export async function pullFailedDeliveries(
19
19
  cursor: string | null,
20
20
  context: PullContext,
21
21
  ): Promise<Pulled> {
22
- const appJWT = findAppJWT(context.env, context.now());
22
+ const signAppJWT = findAppJWTSigner(context.env, context.now);
23
23
 
24
- if (appJWT === undefined) {
24
+ if (signAppJWT === undefined) {
25
25
  return { kind: 'rejected', reason: 'the GitHub App private key cannot sign a token' };
26
26
  }
27
27
 
28
28
  const seenID = cursor === null ? 0n : BigInt(cursor);
29
29
 
30
- const listed = await readDeliveriesAfter(seenID, appJWT, context);
30
+ const listed = await readDeliveriesAfter(seenID, signAppJWT, context);
31
31
 
32
32
  if (listed.kind !== 'listed') {
33
33
  return pickPullFailure(listed.status);
@@ -36,7 +36,7 @@ export async function pullFailedDeliveries(
36
36
  const facts: PendingFact[] = [];
37
37
 
38
38
  for (const id of collectFailedIDs(listed.items)) {
39
- const answer = await requestGithubAPI(`/app/hook/deliveries/${id}`, appJWT, context);
39
+ const answer = await requestGithubAPI(`/app/hook/deliveries/${id}`, signAppJWT(), context);
40
40
 
41
41
  const detail = deliveryDetailSchema.safeParse(answer.body);
42
42
 
@@ -89,14 +89,14 @@ type DeliveryList = { kind: 'listed'; items: DeliveryItem[] } | { kind: 'failed'
89
89
  // GitHub lists the newest delivery first, so the pages stop at the first one already seen.
90
90
  async function readDeliveriesAfter(
91
91
  seenID: bigint,
92
- appJWT: string,
92
+ signAppJWT: () => string,
93
93
  call: GithubCall,
94
94
  ): Promise<DeliveryList> {
95
95
  const items: DeliveryItem[] = [];
96
96
  let url: string | undefined = '/app/hook/deliveries?per_page=100';
97
97
 
98
98
  while (url !== undefined) {
99
- const answer: GithubAnswer = await requestGithubAPI(url, appJWT, call);
99
+ const answer: GithubAnswer = await requestGithubAPI(url, signAppJWT(), call);
100
100
 
101
101
  const page = deliveryListSchema.safeParse(parseDeliveryList(answer.text));
102
102
 
@@ -2,7 +2,7 @@ import type { PendingFact, PullContext, Pulled } from '@pepys/kit';
2
2
  import { z } from 'zod';
3
3
  import { buildGithubFact } from '../build-github-fact.ts';
4
4
  import type { GithubFactOrigin } from '../build-github-fact.ts';
5
- import { findAppJWT } from '../github-app/find-app-jwt.ts';
5
+ import { findAppJWTSigner } from '../github-app/find-app-jwt-signer.ts';
6
6
  import { pickPullFailure } from '../github-app/pick-pull-failure.ts';
7
7
  import { readInstallationToken } from '../github-app/read-installation-token.ts';
8
8
  import type { InstallationToken } from '../github-app/read-installation-token.ts';
@@ -24,9 +24,9 @@ export async function pullMarkedObjects(
24
24
  return { kind: 'batch', batch: { facts: [] }, cursor: 'resynced', isDone: true };
25
25
  }
26
26
 
27
- const appJWT = findAppJWT(context.env, context.now());
27
+ const signAppJWT = findAppJWTSigner(context.env, context.now);
28
28
 
29
- if (appJWT === undefined) {
29
+ if (signAppJWT === undefined) {
30
30
  return { kind: 'rejected', reason: 'the GitHub App private key cannot sign a token' };
31
31
  }
32
32
 
@@ -35,7 +35,7 @@ export async function pullMarkedObjects(
35
35
  const facts: PendingFact[] = [];
36
36
 
37
37
  for (const target of targets) {
38
- const token = await readCachedToken(tokens, target.repository, appJWT, context);
38
+ const token = await readCachedToken(tokens, target.repository, signAppJWT, context);
39
39
 
40
40
  if (token.kind === 'failed') {
41
41
  return pickPullFailure(token.status);
@@ -63,7 +63,7 @@ export async function pullMarkedObjects(
63
63
  async function readCachedToken(
64
64
  tokens: Map<string, InstallationToken>,
65
65
  repository: string,
66
- appJWT: string,
66
+ signAppJWT: () => string,
67
67
  call: GithubCall,
68
68
  ): Promise<InstallationToken> {
69
69
  const known = tokens.get(repository);
@@ -72,7 +72,7 @@ async function readCachedToken(
72
72
  return known;
73
73
  }
74
74
 
75
- const token = await readInstallationToken(repository, appJWT, call);
75
+ const token = await readInstallationToken(repository, signAppJWT, call);
76
76
 
77
77
  tokens.set(repository, token);
78
78
 
@@ -1,16 +0,0 @@
1
- import { buildAppJWT } from './build-app-jwt.ts';
2
-
3
- export function findAppJWT(
4
- env: Readonly<Record<string, string>>,
5
- nowMs: number,
6
- ): string | undefined {
7
- try {
8
- return buildAppJWT(
9
- env['PEPYS_GITHUB_APP_ID'] ?? '',
10
- env['PEPYS_GITHUB_APP_PRIVATE_KEY'] ?? '',
11
- nowMs,
12
- );
13
- } catch {
14
- return undefined;
15
- }
16
- }