Skip to content

Commit 4eafd59

Browse files
authored
feat(bulk-import): repository list includes only "Left Overs" (#2465)
* feat(bulk-import): first implementation to add function to list all repositories for authenticated user Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): group number constants used in the function and name them Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): removed unnecessary comments and renamed variables Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add debug logging for page number extraction in listAllRepositoriesForAuthenticatedUser Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): remove unused ghApiName option from listAllRepositoriesForAuthenticatedUser Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): introduce AuthenticatedUserRepositoryList type for repository listing Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): update the type imports in utils.ts Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): move the listAllRepositoriesForAuthenticatedUser function to the bottom of the file Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): move documentation comments for listForAuthenticatedUser endpoint usage Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): make addGithubTokenRepositories use listAllRepositoriesForAuthenticatedUser Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(githubApiService): remove reqParams from addGithubTokenRepositories call to fetch all repositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): filter out already imported repositories in findAllRepositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): enhance findAllRepositories to filter out already imported repositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): remove unnecessary comments Signed-off-by: Dominik Augustín <daugusti@redhat.com> * refactor(findAllRepositories): simplify response formatting Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(listAllRepositoriesForAuthenticatedUser): use Number.parseInt instead of parseInt Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(githubApiService): return search parameter to addGithubTokenRepositories arguments Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(addGithubTokenRepositories): change the way search in repository names is done to filter on backend side Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(addGithubTokenRepositories): streamline repository filtering logic Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add AppInstallationRepositories type for installation accessible repositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add function to list all repositories accessible to installation Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): rewrite responses data concatenation to unshifting in listAllRepositoriesForAuthenticatedUser Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): adding github app repositories uses listAllRepositoriesAccessibleToInstallation if no search Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): refactor addGithubAppRepositories to use listAllRepositoriesAccessibleToInstallation for search functionality Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): streamline addGithubAppRepositories by consolidating repository fetching and filtering logic Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): normalize search queries to lowercase in addGithubAppRepositories and addGithubTokenRepositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): remove pagination variables from addGithubAppRepositories Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): remove ghConfig parameter from addGithubAppRepositories and related function calls Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): move sorting of repositories before slicing the repository list Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): safely access repository_selection from pageResponses in listAllRepositoriesAccessibleToInstallation Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): update mocks related to listReposAccessibleToInstallation Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): set default mock return value for listForAuthenticatedUser in GithubApiService tests' beforeEach hook Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): correct typo in test name Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): sort repositories before formatting response Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): refactored getAllPages function for paginated API responses Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add OctokitResponse type import for improved type handling Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): refactor repository listing functions to utilize getAllPages Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add types for authenticated user repository and app installation repositories responses Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): update types for authenticated user repository and app installation repositories responses Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): rename and refactor pagination functions for improved clarity and consistency Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): rename search variable to lowercaseSearch to better represent its content Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): add function to list all repositories for authenticated user from gitlab Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): refactor addGitlabTokenRepositories to use listAllRepositoriesForAuthenticatedUser for gitlab Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): remove unused parameters from addGithubTokenRepositories call Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): update findAllRepositories to use unique catalog URL locations Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): remove pageNumber parameter from getRepositoriesFromIntegrations calls Signed-off-by: Dominik Augustín <daugusti@redhat.com> * feat(bulk-import): optimize findAllRepositories to fetch imported and all repositories concurrently Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): change listAllRepositoriesForAuthenticatedUser to throw error on failure Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): add mock handler for catalog API locations in test fixtures Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): export CATALOG_API_LOCATIONS_LOCAL_ADDR for external use Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): add repository filtering tests Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): add repository filtering tests for GitLab integration Signed-off-by: Dominik Augustín <daugusti@redhat.com> * docs(bulk-import): added changeset file Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): update target URLs to use 'blob/master' for catalog-info.yaml Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): add test case for fetching all repositories with non-root catalog location Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): improve repository import logic to handle catalog URL instead of substrings Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): improve github pagination using octokit.paginate Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): simplify response handling in listAllRepositoriesAccessibleToInstallation Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): enhanced mocked pagination handling to return repositories array from response Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): update import statement for RestEndpointMethodTypes to use type syntax Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): remove unused types replaced with SCM types Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): fix inconsistencies after merge Signed-off-by: Dominik Augustín <daugusti@redhat.com> * test(bulk-import): add SCM tokens to repository requests in tests Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): removed unused pagination options Signed-off-by: Dominik Augustín <daugusti@redhat.com> * chore(bulk-import): moved the listAllRepositories functions for github and gitlab Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(bulk-import): removed slicing of repositories to return the whole array Signed-off-by: Dominik Augustín <daugusti@redhat.com> * fix(RepositoriesTable): introduced client-side pagination and search to the repositories table Signed-off-by: Dominik Augustín <daugusti@redhat.com> * docs(bulk-import): updated changeset file to include frontend changes Signed-off-by: Dominik Augustín <daugusti@redhat.com> * docs: updated x-scm-tokens parameter description Signed-off-by: Dominik Augustín <daugusti@redhat.com> --------- Signed-off-by: Dominik Augustín <daugusti@redhat.com>
1 parent ba41609 commit 4eafd59

16 files changed

Lines changed: 817 additions & 273 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-bulk-import-backend': minor
3+
---
4+
5+
**BREAKING** Changes the behavior of the bulk-import backend plugin to return only repositories that are yet to be imported by filtering out the already imported ones. Therefore, the frontend will not display already imported repositories with status displayed as "Imported" anymore. The frontend fetches all repositories at once on the first page load and then all the pagination and search is done client-side.

workspaces/bulk-import/plugins/bulk-import-backend/__fixtures__/handlers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export const LOCAL_ADDR = `http://${localHostAndPort}`;
2121

2222
export const LOCAL_GITLAB_ADDR = `https://gitlab.com/api/v4`;
2323

24+
export const CATALOG_API_LOCATIONS_LOCAL_ADDR =
25+
/^https?:\/\/localhost:\d+\/api\/catalog\/locations$/;
26+
2427
export function loadTestFixture(filePathFromFixturesDir: string) {
2528
return require(`${__dirname}/${filePathFromFixturesDir}`);
2629
}
@@ -499,4 +502,8 @@ export const DEFAULT_TEST_HANDLERS: RestHandler<
499502
return res(ctx.status(404));
500503
},
501504
),
505+
506+
rest.get(CATALOG_API_LOCATIONS_LOCAL_ADDR, (_, res, ctx) =>
507+
res(ctx.status(200), ctx.json([])),
508+
),
502509
];

workspaces/bulk-import/plugins/bulk-import-backend/api-docs/Apis/OrganizationApi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Fetch Repositories in the specified GitHub organization, provided it is accessib
5252
| **sizePerIntegration** | **Integer**| the number of items per Integration to return per page | [optional] [default to 20] |
5353
| **search** | **String**| returns only the items that match the search string | [optional] [default to null] |
5454
| **approvalTool** | **String**| the approvalTool to use | [optional] [default to GIT] |
55-
| **x-scm-tokens** | **String**| **Required.** JSON-encoded map of SCM host URL to user OAuth token. Used to fetch repositories on behalf of the signed-in user. The value must be a JSON object whose keys are SCM integration base URLs and whose values are OAuth bearer tokens (e.g. `{"https://github.com":"ghp_xxx"}`). Requests that omit this header, supply an empty object, or exceed 4 KB are rejected with HTTP 401. | [required] [default to null] |
55+
| **x-scm-tokens** | **String**| Optional JSON-encoded map of SCM host URL to user authentication token. Used to fetch repositories on behalf of the user for each configured SCM host. The value must be a JSON string whose structure matches SCMTokenMap (keys are SCM base URLs, values are OAuth bearer tokens). | [optional] [default to null] |
5656

5757
### Return type
5858

workspaces/bulk-import/plugins/bulk-import-backend/api-docs/Apis/RepositoryApi.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Fetch Organization Repositories accessible by Backstage Github Integrations
2222
| **sizePerIntegration** | **Integer**| the number of items per Integration to return per page | [optional] [default to 20] |
2323
| **search** | **String**| returns only the items that match the search string | [optional] [default to null] |
2424
| **approvalTool** | **String**| the approvalTool to use | [optional] [default to GIT] |
25-
| **x-scm-tokens** | **String**| **Required.** JSON-encoded map of SCM host URL to user OAuth token. Used to fetch repositories on behalf of the signed-in user. The value must be a JSON object whose keys are SCM integration base URLs and whose values are OAuth bearer tokens (e.g. `{"https://github.com":"ghp_xxx"}`). Requests that omit this header, supply an empty object, or exceed 4 KB are rejected with HTTP 401. | [required] [default to null] |
25+
| **x-scm-tokens** | **String**| Optional JSON-encoded map of SCM host URL to user authentication token. Used to fetch repositories on behalf of the user for each configured SCM host. The value must be a JSON string whose structure matches SCMTokenMap (keys are SCM base URLs, values are OAuth bearer tokens). | [optional] [default to null] |
2626

2727
### Return type
2828

workspaces/bulk-import/plugins/bulk-import-backend/src/github/githubApiService.test.ts

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ const octokit = {
2323
paginate: async (fn: any) => {
2424
const res = await fn();
2525
if (res) {
26+
if (Array.isArray(res?.data?.repositories)) {
27+
return res.data.repositories;
28+
}
2629
return res.data;
2730
}
2831
return [];
2932
},
30-
apps: {
31-
listReposAccessibleToInstallation: jest.fn().mockReturnValue({ data: [] }),
32-
},
3333
rest: {
34+
apps: {
35+
listReposAccessibleToInstallation: jest.fn(),
36+
},
3437
repos: {
3538
listForAuthenticatedUser: jest.fn(),
3639
listForOrg: jest.fn(),
@@ -134,6 +137,16 @@ describe('GithubApiService tests', () => {
134137
},
135138
},
136139
});
140+
octokit.rest.apps.listReposAccessibleToInstallation.mockReturnValue({
141+
data: {
142+
repositories: [],
143+
total_count: 0,
144+
repository_selection: 'all',
145+
},
146+
});
147+
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({
148+
data: [],
149+
});
137150
octokit.rest.repos.listForOrg.mockReturnValue({ data: [] });
138151
octokit.rest.users.getByUsername.mockReturnValue({
139152
data: {
@@ -211,9 +224,12 @@ describe('GithubApiService tests', () => {
211224
type: 'User',
212225
},
213226
});
214-
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({ data: [] });
215-
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
216-
data: ghRepos,
227+
octokit.rest.apps.listReposAccessibleToInstallation.mockReturnValue({
228+
data: {
229+
repositories: ghRepos,
230+
total_count: ghRepos.length,
231+
repository_selection: 'all',
232+
},
217233
});
218234

219235
const result = await githubApiService.getRepositoriesFromIntegrations();
@@ -239,28 +255,36 @@ describe('GithubApiService tests', () => {
239255
);
240256
});
241257

242-
it('returns an a list of unique repositories and no errors', async () => {
243-
octokit.apps.listReposAccessibleToInstallation
258+
it('returns a list of unique repositories and no errors', async () => {
259+
octokit.rest.apps.listReposAccessibleToInstallation
244260
.mockReturnValueOnce({
245-
data: ghRepos,
261+
data: {
262+
repositories: ghRepos,
263+
total_count: ghRepos.length,
264+
repository_selection: 'all',
265+
},
246266
})
247267
.mockReturnValue({
248-
data: [
249-
{
250-
name: 'B',
251-
full_name: 'backstage/B',
252-
url: 'https://api.github.com/repos/backstage/B',
253-
html_url: 'https://github.com/backstage/B',
254-
default_branch: 'main',
255-
},
256-
{
257-
name: 'C',
258-
full_name: 'backstage/C',
259-
url: 'https://api.github.com/repos/backstage/C',
260-
html_url: 'https://github.com/backstage/C',
261-
default_branch: 'default',
262-
},
263-
],
268+
data: {
269+
repositories: [
270+
{
271+
name: 'B',
272+
full_name: 'backstage/B',
273+
url: 'https://api.github.com/repos/backstage/B',
274+
html_url: 'https://github.com/backstage/B',
275+
default_branch: 'main',
276+
},
277+
{
278+
name: 'C',
279+
full_name: 'backstage/C',
280+
url: 'https://api.github.com/repos/backstage/C',
281+
html_url: 'https://github.com/backstage/C',
282+
default_branch: 'default',
283+
},
284+
],
285+
total_count: 2,
286+
repository_selection: 'all',
287+
},
264288
});
265289

266290
const result = await githubApiService.getRepositoriesFromIntegrations();
@@ -311,14 +335,18 @@ describe('GithubApiService tests', () => {
311335
throw customError;
312336
},
313337
);
314-
octokit.apps.listReposAccessibleToInstallation
338+
octokit.rest.apps.listReposAccessibleToInstallation
315339
.mockImplementationOnce(async () => {
316340
const unauthorizedError = new Error('Bad credentials');
317341
unauthorizedError.name = '401 Unauthorized';
318342
throw unauthorizedError;
319343
})
320344
.mockReturnValue({
321-
data: ghRepos,
345+
data: {
346+
repositories: ghRepos,
347+
total_count: ghRepos.length,
348+
repository_selection: 'all',
349+
},
322350
});
323351

324352
const result = await githubApiService.getRepositoriesFromIntegrations();
@@ -351,9 +379,6 @@ describe('GithubApiService tests', () => {
351379
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({
352380
data: ghRepos,
353381
});
354-
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
355-
data: [],
356-
});
357382

358383
const result = await githubApiService.getRepositoriesFromIntegrations();
359384

@@ -384,13 +409,11 @@ describe('GithubApiService tests', () => {
384409
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({
385410
data: ghRepos,
386411
});
387-
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
412+
octokit.rest.apps.listReposAccessibleToInstallation.mockReturnValue({
388413
data: [],
389414
});
390415

391416
const result = await githubApiService.getRepositoriesFromIntegrations(
392-
undefined,
393-
undefined,
394417
undefined,
395418
{ 'https://github.com': 'user-oauth-token' },
396419
);
@@ -403,8 +426,6 @@ describe('GithubApiService tests', () => {
403426

404427
it('returns empty repositories when userTokens is provided but no host matches an integration', async () => {
405428
const result = await githubApiService.getRepositoriesFromIntegrations(
406-
undefined,
407-
undefined,
408429
undefined,
409430
{ 'https://some-other-host.com': 'user-oauth-token' },
410431
);
@@ -418,7 +439,7 @@ describe('GithubApiService tests', () => {
418439
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({
419440
data: ghRepos,
420441
});
421-
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
442+
octokit.rest.apps.listReposAccessibleToInstallation.mockReturnValue({
422443
data: [],
423444
});
424445

@@ -433,13 +454,11 @@ describe('GithubApiService tests', () => {
433454
octokit.rest.repos.listForAuthenticatedUser.mockReturnValue({
434455
data: ghRepos,
435456
});
436-
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
457+
octokit.rest.apps.listReposAccessibleToInstallation.mockReturnValue({
437458
data: [],
438459
});
439460

440461
const result = await githubApiService.getRepositoriesFromIntegrations(
441-
undefined,
442-
undefined,
443462
undefined,
444463
{},
445464
);

workspaces/bulk-import/plugins/bulk-import-backend/src/github/githubApiService.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -335,13 +335,10 @@ export class GithubApiService implements GitApiService {
335335
},
336336
octokit,
337337
credential,
338-
ghConfig,
339338
repositories,
340339
dataFetchErrors,
341340
{
342341
search,
343-
pageNumber,
344-
pageSize,
345342
},
346343
);
347344
} else {
@@ -356,8 +353,6 @@ export class GithubApiService implements GitApiService {
356353
dataFetchErrors,
357354
{
358355
search,
359-
pageNumber,
360-
pageSize,
361356
},
362357
);
363358
}
@@ -383,8 +378,6 @@ export class GithubApiService implements GitApiService {
383378
*/
384379
async getRepositoriesFromIntegrations(
385380
search?: string,
386-
pageNumber: number = DefaultPageNumber,
387-
pageSize: number = DefaultPageSize,
388381
userTokens?: Record<string, string>,
389382
): Promise<GithubRepositoryResponse> {
390383
const repositories = new Map<string, GithubRepository>();
@@ -404,7 +397,7 @@ export class GithubApiService implements GitApiService {
404397
userCredential,
405398
repositories,
406399
dataFetchErrors,
407-
{ search, pageNumber, pageSize },
400+
{ search },
408401
),
409402
);
410403
const repoList = Array.from(repositories.values());
@@ -429,13 +422,10 @@ export class GithubApiService implements GitApiService {
429422
},
430423
octokit,
431424
credential,
432-
ghConfig,
433425
repositories,
434426
dataFetchErrors,
435427
{
436428
search,
437-
pageNumber,
438-
pageSize,
439429
},
440430
)
441431
: await addGithubTokenRepositories(
@@ -448,8 +438,6 @@ export class GithubApiService implements GitApiService {
448438
dataFetchErrors,
449439
{
450440
search,
451-
pageNumber,
452-
pageSize,
453441
},
454442
);
455443
this.logger.debug(
@@ -463,7 +451,7 @@ export class GithubApiService implements GitApiService {
463451
},
464452
);
465453

466-
return this.buildRepositoryResponse(repositories, result, pageSize);
454+
return this.buildRepositoryResponse(repositories, result, DefaultPageSize);
467455
}
468456

469457
async filterLocationsAccessibleFromIntegrations(

workspaces/bulk-import/plugins/bulk-import-backend/src/github/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import type {
1919
GithubCredentialsProvider,
2020
} from '@backstage/integration';
2121

22+
import { type RestEndpointMethodTypes } from '@octokit/rest';
23+
2224
export type {
2325
SCMFetchError as GithubFetchError,
2426
SCMOrganization as GithubOrganization,
@@ -64,3 +66,15 @@ export interface ExtendedGithubCredentialsProvider extends GithubCredentialsProv
6466
host: string;
6567
}) => Promise<ExtendedGithubCredentials[]>;
6668
}
69+
70+
export type AuthenticatedUserRepositoryResponse =
71+
RestEndpointMethodTypes['repos']['listForAuthenticatedUser']['response'];
72+
73+
export type AuthenticatedUserRepositoryList =
74+
AuthenticatedUserRepositoryResponse['data'];
75+
76+
export type AppInstallationRepositoriesResponse =
77+
RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response'];
78+
79+
export type AppInstallationRepositories =
80+
AppInstallationRepositoriesResponse['data'];

0 commit comments

Comments
 (0)