@shipfox/api-integration-github 12.5.0 → 12.6.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.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/dist/api/client.d.ts.map +1 -1
  4. package/dist/api/client.js +8 -5
  5. package/dist/api/client.js.map +1 -1
  6. package/dist/api/github-octokit.d.ts +7 -0
  7. package/dist/api/github-octokit.d.ts.map +1 -0
  8. package/dist/api/github-octokit.js +32 -0
  9. package/dist/api/github-octokit.js.map +1 -0
  10. package/dist/api/installation-token-provider.d.ts.map +1 -1
  11. package/dist/api/installation-token-provider.js +4 -2
  12. package/dist/api/installation-token-provider.js.map +1 -1
  13. package/dist/config.d.ts +1 -0
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +8 -0
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/agent-tools.d.ts.map +1 -1
  18. package/dist/core/agent-tools.js +124 -78
  19. package/dist/core/agent-tools.js.map +1 -1
  20. package/dist/metrics/instance.d.ts +1 -0
  21. package/dist/metrics/instance.d.ts.map +1 -1
  22. package/dist/metrics/instance.js +19 -0
  23. package/dist/metrics/instance.js.map +1 -1
  24. package/dist/tsconfig.test.tsbuildinfo +1 -1
  25. package/package.json +1 -1
  26. package/src/api/client.test.ts +25 -5
  27. package/src/api/client.ts +22 -5
  28. package/src/api/github-octokit.test.ts +115 -0
  29. package/src/api/github-octokit.ts +49 -0
  30. package/src/api/installation-token-provider.test.ts +44 -5
  31. package/src/api/installation-token-provider.ts +5 -2
  32. package/src/config.ts +5 -0
  33. package/src/core/agent-tools.test.ts +261 -74
  34. package/src/core/agent-tools.ts +191 -80
  35. package/src/metrics/instance.ts +25 -0
  36. package/test/env.ts +1 -0
  37. package/test/fixtures/github-installation-token.ts +8 -0
  38. package/test/index.ts +4 -0
  39. package/tsconfig.build.tsbuildinfo +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-integration-github",
3
3
  "license": "MIT",
4
- "version": "12.5.0",
4
+ "version": "12.6.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -1,6 +1,12 @@
1
1
  import {GithubIntegrationProviderError} from '#core/errors.js';
2
+ import {
3
+ GITHUB_STATEFUL_INSTALLATION_TOKEN,
4
+ GITHUB_STATELESS_INSTALLATION_TOKEN,
5
+ } from '#test/index.js';
2
6
  import {createGithubApiClient, mapGithubError} from './client.js';
3
7
 
8
+ const GITHUB_INSTALLATION_TOKEN_PATTERN = /^ghs_[A-Za-z0-9._-]{36,}$/u;
9
+
4
10
  const {createInstallationAccessTokenMock, RequestErrorMock} = vi.hoisted(() => {
5
11
  class RequestErrorMock extends Error {
6
12
  constructor(
@@ -22,6 +28,9 @@ vi.mock('octokit', () => ({
22
28
  };
23
29
  },
24
30
  Octokit: {
31
+ plugin() {
32
+ return this;
33
+ },
25
34
  defaults(options: unknown) {
26
35
  return {defaults: options};
27
36
  },
@@ -62,7 +71,10 @@ describe('OctokitGithubApiClient.createInstallationAccessToken', () => {
62
71
 
63
72
  it('mints a repository-scoped, read-only installation token', async () => {
64
73
  createInstallationAccessTokenMock.mockResolvedValue({
65
- data: {token: 'ghs_installationtoken', expires_at: '2026-06-10T12:00:00.000Z'},
74
+ data: {
75
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
76
+ expires_at: '2026-06-10T12:00:00.000Z',
77
+ },
66
78
  });
67
79
  const client = createGithubApiClient();
68
80
 
@@ -72,9 +84,11 @@ describe('OctokitGithubApiClient.createInstallationAccessToken', () => {
72
84
  });
73
85
 
74
86
  expect(result).toEqual({
75
- token: 'ghs_installationtoken',
87
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
76
88
  expiresAt: new Date('2026-06-10T12:00:00.000Z'),
77
89
  });
90
+ expect(GITHUB_STATELESS_INSTALLATION_TOKEN).toMatch(GITHUB_INSTALLATION_TOKEN_PATTERN);
91
+ expect(GITHUB_STATELESS_INSTALLATION_TOKEN.slice(4).split('.')).toHaveLength(3);
78
92
  expect(createInstallationAccessTokenMock).toHaveBeenCalledWith({
79
93
  installation_id: 1,
80
94
  repository_ids: [42],
@@ -82,18 +96,24 @@ describe('OctokitGithubApiClient.createInstallationAccessToken', () => {
82
96
  });
83
97
  });
84
98
 
85
- it('mints a repository-scoped write installation token when requested', async () => {
99
+ it('passes through a stateful repository-scoped write token', async () => {
86
100
  createInstallationAccessTokenMock.mockResolvedValue({
87
- data: {token: 'ghs_installationtoken', expires_at: '2026-06-10T12:00:00.000Z'},
101
+ data: {
102
+ token: GITHUB_STATEFUL_INSTALLATION_TOKEN,
103
+ expires_at: '2026-06-10T12:00:00.000Z',
104
+ },
88
105
  });
89
106
  const client = createGithubApiClient();
90
107
 
91
- await client.createInstallationAccessToken({
108
+ const result = await client.createInstallationAccessToken({
92
109
  installationId: 1,
93
110
  repositoryId: 42,
94
111
  permissions: {contents: 'write'},
95
112
  });
96
113
 
114
+ expect(result.token).toBe(GITHUB_STATEFUL_INSTALLATION_TOKEN);
115
+ expect(GITHUB_STATEFUL_INSTALLATION_TOKEN).toMatch(GITHUB_INSTALLATION_TOKEN_PATTERN);
116
+ expect(GITHUB_STATEFUL_INSTALLATION_TOKEN).not.toContain('.');
97
117
  expect(createInstallationAccessTokenMock).toHaveBeenCalledWith({
98
118
  installation_id: 1,
99
119
  repository_ids: [42],
package/src/api/client.ts CHANGED
@@ -4,6 +4,11 @@ import ky, {HTTPError, TimeoutError} from 'ky';
4
4
  import {App, Octokit, RequestError} from 'octokit';
5
5
  import {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#config.js';
6
6
  import {GithubIntegrationProviderError} from '#core/errors.js';
7
+ import {recordInstallationTokenFormat} from '#metrics/index.js';
8
+ import {
9
+ getGithubInstallationOctokit,
10
+ githubInstallationTokenFormatPlugin,
11
+ } from './github-octokit.js';
7
12
 
8
13
  const NEXT_PAGE_RE = /[?&]page=(\d+)/;
9
14
  const TRAILING_SLASHES_RE = /\/+$/;
@@ -186,7 +191,9 @@ class OctokitGithubApiClient implements GithubApiClient {
186
191
  limit: number;
187
192
  cursor?: string | undefined;
188
193
  }): Promise<GithubRepositoryPage> {
189
- const octokit = await this.getApp().getInstallationOctokit(input.installationId);
194
+ const octokit = await mapGithubError(() =>
195
+ getGithubInstallationOctokit(this.getApp(), input.installationId),
196
+ );
190
197
  const page = cursorToPage(input.cursor);
191
198
  const response = await mapGithubError(() =>
192
199
  octokit.rest.apps.listReposAccessibleToInstallation({
@@ -205,7 +212,9 @@ class OctokitGithubApiClient implements GithubApiClient {
205
212
  installationId: number;
206
213
  repositoryId: number;
207
214
  }): Promise<GithubRepository> {
208
- const octokit = await this.getApp().getInstallationOctokit(input.installationId);
215
+ const octokit = await mapGithubError(() =>
216
+ getGithubInstallationOctokit(this.getApp(), input.installationId),
217
+ );
209
218
  const response = await mapGithubError(() =>
210
219
  octokit.request('GET /repositories/{repository_id}', {
211
220
  repository_id: input.repositoryId,
@@ -223,7 +232,9 @@ class OctokitGithubApiClient implements GithubApiClient {
223
232
  limit: number;
224
233
  cursor?: string | undefined;
225
234
  }): Promise<GithubFilePage> {
226
- const octokit = await this.getApp().getInstallationOctokit(input.installationId);
235
+ const octokit = await mapGithubError(() =>
236
+ getGithubInstallationOctokit(this.getApp(), input.installationId),
237
+ );
227
238
  const repository = await this.getRepository({
228
239
  installationId: input.installationId,
229
240
  repositoryId: input.repositoryId,
@@ -303,7 +314,9 @@ class OctokitGithubApiClient implements GithubApiClient {
303
314
  ref: string;
304
315
  path: string;
305
316
  }): Promise<GithubFileContent> {
306
- const octokit = await this.getApp().getInstallationOctokit(input.installationId);
317
+ const octokit = await mapGithubError(() =>
318
+ getGithubInstallationOctokit(this.getApp(), input.installationId),
319
+ );
307
320
  const repository = await this.getRepository({
308
321
  installationId: input.installationId,
309
322
  repositoryId: input.repositoryId,
@@ -363,6 +376,8 @@ class OctokitGithubApiClient implements GithubApiClient {
363
376
  );
364
377
  }
365
378
 
379
+ recordInstallationTokenFormat(response.data.token);
380
+
366
381
  const expiresAt = new Date(response.data.expires_at);
367
382
  if (Number.isNaN(expiresAt.getTime())) {
368
383
  throw new GithubIntegrationProviderError(
@@ -383,7 +398,9 @@ class OctokitGithubApiClient implements GithubApiClient {
383
398
  this.app = new App({
384
399
  appId: config.GITHUB_APP_ID,
385
400
  privateKey: normalizedGithubPrivateKey(),
386
- Octokit: Octokit.defaults({baseUrl: normalizedGithubApiBaseUrl()}),
401
+ Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({
402
+ baseUrl: normalizedGithubApiBaseUrl(),
403
+ }),
387
404
  });
388
405
  }
389
406
  return this.app;
@@ -0,0 +1,115 @@
1
+ import {generateKeyPairSync} from 'node:crypto';
2
+ import {once} from 'node:events';
3
+ import {createServer} from 'node:http';
4
+ import {App, Octokit} from 'octokit';
5
+ import {
6
+ GITHUB_STATEFUL_INSTALLATION_TOKEN,
7
+ GITHUB_STATELESS_INSTALLATION_TOKEN,
8
+ } from '#test/index.js';
9
+ import {
10
+ createGithubInstallationTokenFormatPlugin,
11
+ getGithubInstallationOctokit,
12
+ } from './github-octokit.js';
13
+
14
+ const {privateKey} = generateKeyPairSync('rsa', {
15
+ modulusLength: 2048,
16
+ publicKeyEncoding: {type: 'spki', format: 'pem'},
17
+ privateKeyEncoding: {type: 'pkcs8', format: 'pem'},
18
+ });
19
+ const BEARER_AUTHORIZATION = /^bearer /iu;
20
+
21
+ describe('GitHub installation Octokit', () => {
22
+ it.each([
23
+ {
24
+ format: 'stateless',
25
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
26
+ override: 'enabled' as const,
27
+ authorization: `bearer ${GITHUB_STATELESS_INSTALLATION_TOKEN}`,
28
+ },
29
+ {
30
+ format: 'stateful',
31
+ token: GITHUB_STATEFUL_INSTALLATION_TOKEN,
32
+ override: 'disabled' as const,
33
+ authorization: `token ${GITHUB_STATEFUL_INSTALLATION_TOKEN}`,
34
+ },
35
+ {
36
+ format: 'stateful without an override',
37
+ token: GITHUB_STATEFUL_INSTALLATION_TOKEN,
38
+ override: undefined,
39
+ authorization: `token ${GITHUB_STATEFUL_INSTALLATION_TOKEN}`,
40
+ },
41
+ ])('requests and authenticates with a $format token', async ({
42
+ token,
43
+ override,
44
+ authorization,
45
+ }) => {
46
+ const calls: Array<{
47
+ method: string | undefined;
48
+ path: string | undefined;
49
+ authorization: string | undefined;
50
+ tokenFormatOverride: string | undefined;
51
+ }> = [];
52
+ const server = createServer((request, response) => {
53
+ calls.push({
54
+ method: request.method,
55
+ path: request.url,
56
+ authorization: request.headers.authorization,
57
+ tokenFormatOverride: request.headers['x-github-stateless-s2s-token'] as string | undefined,
58
+ });
59
+
60
+ if (request.method === 'POST' && request.url === '/app/installations/123/access_tokens') {
61
+ response.writeHead(201, {'content-type': 'application/json'}).end(
62
+ JSON.stringify({
63
+ token,
64
+ expires_at: '2099-01-01T00:00:00.000Z',
65
+ permissions: {metadata: 'read'},
66
+ repository_selection: 'all',
67
+ }),
68
+ );
69
+ return;
70
+ }
71
+
72
+ if (request.method === 'GET' && request.url === '/installation/repositories') {
73
+ response
74
+ .writeHead(200, {'content-type': 'application/json'})
75
+ .end(JSON.stringify({total_count: 0, repositories: []}));
76
+ return;
77
+ }
78
+
79
+ response.writeHead(404, {'content-type': 'application/json'}).end('{"message":"Not Found"}');
80
+ });
81
+ server.listen({host: '127.0.0.1', port: 0});
82
+ await once(server, 'listening');
83
+ const address = server.address();
84
+ if (!address || typeof address === 'string') throw new Error('Expected TCP server address.');
85
+ const baseUrl = `http://127.0.0.1:${address.port}`;
86
+
87
+ try {
88
+ const InstallationTokenOctokit = Octokit.plugin(
89
+ createGithubInstallationTokenFormatPlugin(override),
90
+ ).defaults({baseUrl});
91
+ const app = new App({appId: 1, privateKey, Octokit: InstallationTokenOctokit});
92
+ const octokit = await getGithubInstallationOctokit(app, 123, baseUrl);
93
+
94
+ await octokit.rest.apps.listReposAccessibleToInstallation();
95
+
96
+ expect(calls).toEqual([
97
+ {
98
+ method: 'POST',
99
+ path: '/app/installations/123/access_tokens',
100
+ authorization: expect.stringMatching(BEARER_AUTHORIZATION),
101
+ tokenFormatOverride: override,
102
+ },
103
+ {
104
+ method: 'GET',
105
+ path: '/installation/repositories',
106
+ authorization,
107
+ tokenFormatOverride: undefined,
108
+ },
109
+ ]);
110
+ } finally {
111
+ server.close();
112
+ await once(server, 'close');
113
+ }
114
+ });
115
+ });
@@ -0,0 +1,49 @@
1
+ import {type App, Octokit} from 'octokit';
2
+ import {config, normalizedGithubApiBaseUrl} from '#config.js';
3
+ import {GithubIntegrationProviderError} from '#core/errors.js';
4
+ import {recordInstallationTokenFormat} from '#metrics/index.js';
5
+
6
+ const INSTALLATION_TOKEN_REQUEST_PATH = /\/app\/installations\/[^/]+\/access_tokens(?:\?|$)/u;
7
+ type GithubInstallationTokenFormatOverride = 'enabled' | 'disabled' | undefined;
8
+
9
+ export function createGithubInstallationTokenFormatPlugin(
10
+ override: GithubInstallationTokenFormatOverride,
11
+ ): Parameters<typeof Octokit.plugin>[0] {
12
+ return (octokit) => {
13
+ octokit.hook.before('request', (options) => {
14
+ if (
15
+ !override ||
16
+ options.method !== 'POST' ||
17
+ !INSTALLATION_TOKEN_REQUEST_PATH.test(options.url)
18
+ ) {
19
+ return;
20
+ }
21
+
22
+ options.headers['x-github-stateless-s2s-token'] = override;
23
+ });
24
+ };
25
+ }
26
+
27
+ export const githubInstallationTokenFormatPlugin: Parameters<typeof Octokit.plugin>[0] =
28
+ createGithubInstallationTokenFormatPlugin(config.GITHUB_INSTALLATION_TOKEN_FORMAT_OVERRIDE);
29
+
30
+ export async function getGithubInstallationOctokit(
31
+ app: App,
32
+ installationId: number,
33
+ baseUrl = normalizedGithubApiBaseUrl(),
34
+ ): Promise<Octokit> {
35
+ const authentication = (await app.octokit.auth({
36
+ type: 'installation',
37
+ installationId,
38
+ })) as {token?: unknown};
39
+
40
+ if (typeof authentication.token !== 'string') {
41
+ throw new GithubIntegrationProviderError(
42
+ 'malformed-provider-response',
43
+ 'GitHub installation authentication did not include a token',
44
+ );
45
+ }
46
+
47
+ recordInstallationTokenFormat(authentication.token);
48
+ return new Octokit({auth: authentication.token, baseUrl});
49
+ }
@@ -1,9 +1,15 @@
1
1
  import type {GetIntegrationConnectionByIdFn} from '@shipfox/api-integration-spi';
2
2
  import {GithubIntegrationProviderError} from '#core/errors.js';
3
- import {githubInstallationFactory} from '#test/index.js';
3
+ import {
4
+ GITHUB_STATEFUL_INSTALLATION_TOKEN,
5
+ GITHUB_STATELESS_INSTALLATION_TOKEN,
6
+ githubInstallationFactory,
7
+ } from '#test/index.js';
4
8
  import {encodeInstallationTokenEnvelope} from './installation-token-envelope.js';
5
9
  import {createGithubInstallationTokenProvider} from './installation-token-provider.js';
6
10
 
11
+ const GITHUB_INSTALLATION_TOKEN_PATTERN = /^ghs_[A-Za-z0-9._-]{36,}$/u;
12
+
7
13
  const {appOptions, createInstallationAccessTokenMock, RequestErrorMock} = vi.hoisted(() => ({
8
14
  appOptions: [] as unknown[],
9
15
  createInstallationAccessTokenMock: vi.fn(),
@@ -28,6 +34,9 @@ vi.mock('octokit', () => ({
28
34
  }
29
35
  },
30
36
  Octokit: {
37
+ plugin() {
38
+ return this;
39
+ },
31
40
  defaults(options: unknown) {
32
41
  return {defaults: options};
33
42
  },
@@ -47,26 +56,49 @@ describe('GithubInstallationTokenProvider', () => {
47
56
 
48
57
  it('mints a broad installation token on a cache miss', async () => {
49
58
  createInstallationAccessTokenMock.mockResolvedValue({
50
- data: {token: 'ghs_installationtoken', expires_at: '2026-06-10T12:00:00.000Z'},
59
+ data: {
60
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
61
+ expires_at: '2026-06-10T12:00:00.000Z',
62
+ },
51
63
  });
52
64
  const provider = createGithubInstallationTokenProvider();
53
65
 
54
66
  const result = await provider.getInstallationAccessToken(1);
55
67
 
56
68
  expect(result).toEqual({
57
- token: 'ghs_installationtoken',
69
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
58
70
  expiresAt: new Date('2026-06-10T12:00:00.000Z'),
59
71
  });
72
+ expect(GITHUB_STATELESS_INSTALLATION_TOKEN).toMatch(GITHUB_INSTALLATION_TOKEN_PATTERN);
73
+ expect(GITHUB_STATELESS_INSTALLATION_TOKEN.slice(4).split('.')).toHaveLength(3);
60
74
  expect(createInstallationAccessTokenMock).toHaveBeenCalledWith({
61
75
  installation_id: 1,
62
76
  });
63
77
  });
64
78
 
79
+ it('passes through a stateful broad installation token', async () => {
80
+ createInstallationAccessTokenMock.mockResolvedValue({
81
+ data: {
82
+ token: GITHUB_STATEFUL_INSTALLATION_TOKEN,
83
+ expires_at: '2026-06-10T12:00:00.000Z',
84
+ },
85
+ });
86
+ const provider = createGithubInstallationTokenProvider();
87
+
88
+ const result = await provider.getInstallationAccessToken(1);
89
+
90
+ expect(result.token).toBe(GITHUB_STATEFUL_INSTALLATION_TOKEN);
91
+ expect(GITHUB_STATEFUL_INSTALLATION_TOKEN).not.toContain('.');
92
+ });
93
+
65
94
  it('returns a cached token without a second mint', async () => {
66
95
  vi.useFakeTimers();
67
96
  vi.setSystemTime(new Date('2026-06-10T11:00:00.000Z'));
68
97
  createInstallationAccessTokenMock.mockResolvedValue({
69
- data: {token: 'ghs_installationtoken', expires_at: '2026-06-10T12:00:00.000Z'},
98
+ data: {
99
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
100
+ expires_at: '2026-06-10T12:00:00.000Z',
101
+ },
70
102
  });
71
103
  const provider = createGithubInstallationTokenProvider();
72
104
 
@@ -151,7 +183,10 @@ describe('GithubInstallationTokenProvider', () => {
151
183
  updatedAt: new Date(),
152
184
  });
153
185
  createInstallationAccessTokenMock.mockResolvedValue({
154
- data: {token: 'ghs_installationtoken', expires_at: '2026-06-10T12:00:00.000Z'},
186
+ data: {
187
+ token: GITHUB_STATELESS_INSTALLATION_TOKEN,
188
+ expires_at: '2026-06-10T12:00:00.000Z',
189
+ },
155
190
  });
156
191
  const provider = createGithubInstallationTokenProvider({
157
192
  getIntegrationConnectionById,
@@ -174,6 +209,10 @@ describe('GithubInstallationTokenProvider', () => {
174
209
  const second = await provider.getInstallationAccessToken(installationId);
175
210
 
176
211
  expect(first).toEqual(second);
212
+ expect(first.token).toBe(GITHUB_STATELESS_INSTALLATION_TOKEN);
213
+ expect(values.get(`${workspaceId}:${installationId}`)).toContain(
214
+ GITHUB_STATELESS_INSTALLATION_TOKEN,
215
+ );
177
216
  expect(lockCalls).toBe(1);
178
217
  expect(createInstallationAccessTokenMock).toHaveBeenCalledTimes(1);
179
218
  });
@@ -4,8 +4,9 @@ import {config, normalizedGithubApiBaseUrl, normalizedGithubPrivateKey} from '#c
4
4
  import {GithubIntegrationProviderError} from '#core/errors.js';
5
5
  import {withInstallationTokenLock} from '#db/installation-token-lock.js';
6
6
  import {getGithubInstallationByInstallationId} from '#db/installations.js';
7
- import {recordInstallationTokenLookup} from '#metrics/index.js';
7
+ import {recordInstallationTokenFormat, recordInstallationTokenLookup} from '#metrics/index.js';
8
8
  import {type GithubInstallationAccessToken, mapGithubError} from './client.js';
9
+ import {githubInstallationTokenFormatPlugin} from './github-octokit.js';
9
10
  import {
10
11
  githubInstallationTokenNamespace,
11
12
  TOKEN_REFRESH_MARGIN_MS,
@@ -66,6 +67,8 @@ class OctokitGithubInstallationTokenProvider implements GithubInstallationTokenP
66
67
  );
67
68
  }
68
69
 
70
+ recordInstallationTokenFormat(response.data.token);
71
+
69
72
  const expiresAt = new Date(response.data.expires_at);
70
73
  if (Number.isNaN(expiresAt.getTime())) {
71
74
  throw new GithubIntegrationProviderError(
@@ -86,7 +89,7 @@ class OctokitGithubInstallationTokenProvider implements GithubInstallationTokenP
86
89
  this.app = new App({
87
90
  appId: config.GITHUB_APP_ID,
88
91
  privateKey: normalizedGithubPrivateKey(),
89
- Octokit: Octokit.defaults({
92
+ Octokit: Octokit.plugin(githubInstallationTokenFormatPlugin).defaults({
90
93
  baseUrl: normalizedGithubApiBaseUrl(),
91
94
  throttle: {
92
95
  onRateLimit: (
package/src/config.ts CHANGED
@@ -29,6 +29,11 @@ export const config = createConfig({
29
29
  desc: 'Base URL used for GitHub REST API requests. Set this only for GitHub Enterprise Server or a compatible test server.',
30
30
  default: 'https://api.github.com',
31
31
  }),
32
+ GITHUB_INSTALLATION_TOKEN_FORMAT_OVERRIDE: str({
33
+ desc: 'Temporary GitHub installation token format override. Set this to enabled to request stateless tokens, disabled to request stateful tokens, or leave it unset to follow the GitHub rollout.',
34
+ choices: ['enabled', 'disabled'] as const,
35
+ default: undefined,
36
+ }),
32
37
  GITHUB_INSTALL_STATE_SECRET: str({
33
38
  desc: 'Secret used to sign the state token that protects the GitHub App install flow. Required.',
34
39
  }),