@@ -2,7 +2,18 @@ import { createAppAuth } from '@octokit/auth-app'
22import { Octokit } from '@octokit/rest'
33import * as mod from './github'
44import { config } from './config'
5- import { GetResponseTypeFromEndpointMethod } from '@octokit/types'
5+
6+ export interface OperationError {
7+ user : string
8+ operation : 'add' | 'remove'
9+ message : string
10+ status ?: number
11+ }
12+
13+ export interface OperationResult {
14+ success : string [ ]
15+ errors : OperationError [ ]
16+ }
617
718export function getAuthenticatedOctokit ( ) : Octokit {
819 return new Octokit ( {
@@ -57,47 +68,85 @@ export async function getUserIdFromUsername(username: string): Promise<number> {
5768 return user . data . id
5869}
5970
60- export async function addUsersToGitHubOrg ( users : Set < string > ) : Promise < void > {
71+ export async function addUsersToGitHubOrg ( users : Set < string > ) : Promise < OperationResult > {
72+ const result : OperationResult = { success : [ ] , errors : [ ] }
6173 for ( const user of users ) {
62- await mod . addUserToGitHubOrg ( user )
74+ const outcome = await mod . addUserToGitHubOrg ( user )
75+ if ( outcome === true ) {
76+ result . success . push ( user )
77+ } else if ( outcome !== false && 'error' in outcome ) {
78+ result . errors . push ( outcome . error )
79+ }
6380 }
81+ return result
6482}
6583
6684// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
67- export async function addUserToGitHubOrg (
68- user : string ,
69- ) : Promise < GetResponseTypeFromEndpointMethod < typeof octokit . orgs . createInvitation > | boolean > {
85+ export async function addUserToGitHubOrg ( user : string ) : Promise < { error : OperationError } | boolean > {
7086 const octokit = mod . getAuthenticatedOctokit ( )
7187 if ( config . ignoredUsers . includes ( user . toLowerCase ( ) ) ) {
7288 console . log ( `Ignoring add for ${ user } ` )
7389 return false
7490 }
75- const userId = await mod . getUserIdFromUsername ( user )
76- console . log ( `Inviting ${ user } (${ userId } to ${ config . githubOrg } )` )
77- return await octokit . orgs . createInvitation ( {
78- org : config . githubOrg ,
79- invitee_id : userId ,
80- } )
91+ try {
92+ const userId = await mod . getUserIdFromUsername ( user )
93+ console . log ( `Inviting ${ user } (${ userId } ) to ${ config . githubOrg } ` )
94+ await octokit . orgs . createInvitation ( {
95+ org : config . githubOrg ,
96+ invitee_id : userId ,
97+ } )
98+ return true
99+ } catch ( error ) {
100+ const status = error ?. status || error ?. response ?. status
101+ let message = error ?. message || String ( error )
102+ if ( status === 422 ) {
103+ message = `Validation failed: ${ message } (user may already be invited, or org is at max capacity)`
104+ } else if ( status === 404 ) {
105+ message = `User not found: ${ user } `
106+ } else if ( status === 403 ) {
107+ message = `Permission denied or rate limited`
108+ }
109+ console . error ( `Error adding ${ user } : ${ message } ` )
110+ return { error : { user, operation : 'add' , message, status } }
111+ }
81112}
82113
83- export async function removeUsersFromGitHubOrg ( users : Set < string > ) : Promise < void > {
114+ export async function removeUsersFromGitHubOrg ( users : Set < string > ) : Promise < OperationResult > {
115+ const result : OperationResult = { success : [ ] , errors : [ ] }
84116 for ( const user of users ) {
85- await mod . removeUserFromGitHubOrg ( user )
117+ const outcome = await mod . removeUserFromGitHubOrg ( user )
118+ if ( outcome === true ) {
119+ result . success . push ( user )
120+ } else if ( outcome !== false && 'error' in outcome ) {
121+ result . errors . push ( outcome . error )
122+ }
86123 }
124+ return result
87125}
88126
89127// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
90- export async function removeUserFromGitHubOrg (
91- user : string ,
92- ) : Promise < GetResponseTypeFromEndpointMethod < typeof octokit . orgs . removeMembershipForUser > | boolean > {
128+ export async function removeUserFromGitHubOrg ( user : string ) : Promise < { error : OperationError } | boolean > {
93129 const octokit = mod . getAuthenticatedOctokit ( )
94130 if ( config . ignoredUsers . includes ( user . toLowerCase ( ) ) ) {
95131 console . log ( `Ignoring remove for ${ user } ` )
96132 return false
97133 }
98- console . log ( `Removing user/invitation ${ user } from ${ config . githubOrg } ` )
99- return octokit . orgs . removeMembershipForUser ( {
100- org : config . githubOrg ,
101- username : user ,
102- } )
134+ try {
135+ console . log ( `Removing user/invitation ${ user } from ${ config . githubOrg } ` )
136+ await octokit . orgs . removeMembershipForUser ( {
137+ org : config . githubOrg ,
138+ username : user ,
139+ } )
140+ return true
141+ } catch ( error ) {
142+ const status = error ?. status || error ?. response ?. status
143+ let message = error ?. message || String ( error )
144+ if ( status === 404 ) {
145+ message = `User not found or not a member: ${ user } `
146+ } else if ( status === 403 ) {
147+ message = `Permission denied or rate limited`
148+ }
149+ console . error ( `Error removing ${ user } : ${ message } ` )
150+ return { error : { user, operation : 'remove' , message, status } }
151+ }
103152}
0 commit comments