1+ #!/usr/bin/env tsx
2+
3+ /**
4+ * Clean up GitHub deployments that are not successful
5+ * This script removes all deployments with status other than 'success'
6+ *
7+ * Usage: tsx scripts/git/cleanup-deployments.ts [options]
8+ * Options:
9+ * --dry-run Show what would be deleted without actually deleting
10+ * --keep-last Keep the most recent deployment regardless of status
11+ * --verbose Show detailed output
12+ */
13+
14+ import { execSync } from 'child_process' ;
15+
16+ interface Deployment {
17+ id : number ;
18+ environment : string ;
19+ created_at : string ;
20+ updated_at : string ;
21+ }
22+
23+ interface DeploymentStatus {
24+ state : string ;
25+ created_at : string ;
26+ }
27+
28+ // Parse command line arguments
29+ const args = process . argv . slice ( 2 ) ;
30+ const options = {
31+ dryRun : args . includes ( '--dry-run' ) ,
32+ keepLast : args . includes ( '--keep-last' ) ,
33+ verbose : args . includes ( '--verbose' )
34+ } ;
35+
36+ // Colors for terminal output
37+ const colors = {
38+ reset : '\x1b[0m' ,
39+ bright : '\x1b[1m' ,
40+ red : '\x1b[31m' ,
41+ green : '\x1b[32m' ,
42+ yellow : '\x1b[33m' ,
43+ blue : '\x1b[34m' ,
44+ cyan : '\x1b[36m' ,
45+ gray : '\x1b[90m'
46+ } ;
47+
48+ function log ( message : string , color : string = colors . reset ) {
49+ console . log ( `${ color } ${ message } ${ colors . reset } ` ) ;
50+ }
51+
52+ function verbose ( message : string ) {
53+ if ( options . verbose ) {
54+ log ( message , colors . gray ) ;
55+ }
56+ }
57+
58+ function executeCommand ( command : string ) : string {
59+ try {
60+ return execSync ( command , { encoding : 'utf-8' } ) . trim ( ) ;
61+ } catch ( error : any ) {
62+ if ( error . status !== 0 ) {
63+ throw new Error ( `Command failed: ${ command } \n${ error . message } ` ) ;
64+ }
65+ return '' ;
66+ }
67+ }
68+
69+ async function getDeployments ( ) : Promise < Deployment [ ] > {
70+ try {
71+ verbose ( 'Fetching deployments...' ) ;
72+ const result = executeCommand (
73+ 'gh api repos/josedacosta/mcp-jetbrains-code-inspections/deployments --paginate'
74+ ) ;
75+ return JSON . parse ( result ) ;
76+ } catch ( error ) {
77+ log ( 'Error fetching deployments' , colors . red ) ;
78+ throw error ;
79+ }
80+ }
81+
82+ async function getDeploymentStatus ( deploymentId : number ) : Promise < string > {
83+ try {
84+ const result = executeCommand (
85+ `gh api repos/josedacosta/mcp-jetbrains-code-inspections/deployments/${ deploymentId } /statuses`
86+ ) ;
87+ const statuses : DeploymentStatus [ ] = JSON . parse ( result ) ;
88+
89+ // Return the most recent status or 'pending' if no status exists
90+ return statuses . length > 0 ? statuses [ 0 ] . state : 'pending' ;
91+ } catch ( error ) {
92+ verbose ( `Could not fetch status for deployment ${ deploymentId } , assuming pending` ) ;
93+ return 'pending' ;
94+ }
95+ }
96+
97+ async function deleteDeployment ( deploymentId : number ) : Promise < boolean > {
98+ if ( options . dryRun ) {
99+ log ( ` [DRY RUN] Would delete deployment ${ deploymentId } ` , colors . cyan ) ;
100+ return true ;
101+ }
102+
103+ try {
104+ executeCommand (
105+ `gh api -X DELETE repos/josedacosta/mcp-jetbrains-code-inspections/deployments/${ deploymentId } `
106+ ) ;
107+ return true ;
108+ } catch ( error ) {
109+ log ( ` Failed to delete deployment ${ deploymentId } ` , colors . red ) ;
110+ verbose ( ` Error: ${ error } ` ) ;
111+ return false ;
112+ }
113+ }
114+
115+ async function main ( ) {
116+ log ( '\n🧹 GitHub Deployments Cleanup Script' , colors . bright ) ;
117+ log ( '=' . repeat ( 40 ) , colors . gray ) ;
118+
119+ if ( options . dryRun ) {
120+ log ( '🔍 Running in DRY RUN mode (no changes will be made)' , colors . yellow ) ;
121+ }
122+
123+ try {
124+ // Check if gh CLI is installed and authenticated
125+ try {
126+ executeCommand ( 'gh auth status' ) ;
127+ } catch ( error ) {
128+ log ( '\n❌ GitHub CLI is not authenticated' , colors . red ) ;
129+ log ( ' Run: gh auth login' , colors . yellow ) ;
130+ process . exit ( 1 ) ;
131+ }
132+
133+ // Get all deployments
134+ const deployments = await getDeployments ( ) ;
135+
136+ if ( deployments . length === 0 ) {
137+ log ( '\n✅ No deployments found' , colors . green ) ;
138+ return ;
139+ }
140+
141+ log ( `\n📊 Found ${ deployments . length } deployment(s)` , colors . blue ) ;
142+
143+ // Statistics
144+ let successCount = 0 ;
145+ let deletedCount = 0 ;
146+ let failedCount = 0 ;
147+ let skippedCount = 0 ;
148+
149+ // Process each deployment
150+ for ( let i = 0 ; i < deployments . length ; i ++ ) {
151+ const deployment = deployments [ i ] ;
152+ const isLast = i === 0 ; // Deployments are returned in reverse chronological order
153+
154+ verbose ( `\nChecking deployment ${ deployment . id } ...` ) ;
155+
156+ // Skip the last deployment if keepLast option is set
157+ if ( isLast && options . keepLast ) {
158+ log ( `\n📌 Keeping last deployment ${ deployment . id } (--keep-last option)` , colors . yellow ) ;
159+ skippedCount ++ ;
160+ continue ;
161+ }
162+
163+ // Get deployment status
164+ const status = await getDeploymentStatus ( deployment . id ) ;
165+
166+ // Format deployment info
167+ const deploymentInfo = `Deployment ${ deployment . id } [${ deployment . environment } ] - Status: ${ status } ` ;
168+
169+ if ( status === 'success' ) {
170+ log ( `\n✅ ${ deploymentInfo } ` , colors . green ) ;
171+ successCount ++ ;
172+ } else {
173+ log ( `\n❌ ${ deploymentInfo } ` , colors . yellow ) ;
174+
175+ // Delete non-successful deployment
176+ const deleted = await deleteDeployment ( deployment . id ) ;
177+
178+ if ( deleted ) {
179+ if ( ! options . dryRun ) {
180+ log ( ` ✓ Deleted successfully` , colors . green ) ;
181+ }
182+ deletedCount ++ ;
183+ } else {
184+ failedCount ++ ;
185+ }
186+ }
187+ }
188+
189+ // Print summary
190+ log ( '\n' + '=' . repeat ( 40 ) , colors . gray ) ;
191+ log ( '📈 Summary:' , colors . bright ) ;
192+ log ( ` • Total deployments: ${ deployments . length } ` , colors . blue ) ;
193+ log ( ` • Successful: ${ successCount } ` , colors . green ) ;
194+ log ( ` • Deleted: ${ deletedCount } ` , colors . yellow ) ;
195+
196+ if ( failedCount > 0 ) {
197+ log ( ` • Failed to delete: ${ failedCount } ` , colors . red ) ;
198+ }
199+
200+ if ( skippedCount > 0 ) {
201+ log ( ` • Skipped: ${ skippedCount } ` , colors . cyan ) ;
202+ }
203+
204+ if ( options . dryRun ) {
205+ log ( '\n💡 This was a dry run. Run without --dry-run to actually delete deployments' , colors . yellow ) ;
206+ } else if ( deletedCount > 0 ) {
207+ log ( '\n✨ Cleanup completed successfully!' , colors . green ) ;
208+ } else {
209+ log ( '\n✨ No deployments needed cleanup' , colors . green ) ;
210+ }
211+
212+ } catch ( error : any ) {
213+ log ( '\n❌ An error occurred:' , colors . red ) ;
214+ log ( error . message , colors . red ) ;
215+ process . exit ( 1 ) ;
216+ }
217+ }
218+
219+ // Run the script
220+ main ( ) . catch ( ( error ) => {
221+ log ( 'Unexpected error:' , colors . red ) ;
222+ console . error ( error ) ;
223+ process . exit ( 1 ) ;
224+ } ) ;
0 commit comments