2025-09-18 16:31:39 -07:00
#!/usr/bin/env node
/ * *
* @ license
* Copyright 2025 Google LLC
* SPDX - License - Identifier : Apache - 2.0
* /
/ * *
* Script for commenting on the original PR after patch creation ( step 1 ) .
* Handles parsing create - patch - pr . js output and creating appropriate feedback .
* /
import yargs from 'yargs' ;
import { hideBin } from 'yargs/helpers' ;
async function main ( ) {
const argv = await yargs ( hideBin ( process . argv ) )
. option ( 'original-pr' , {
description : 'The original PR number to comment on' ,
type : 'number' ,
demandOption : ! process . env . GITHUB _ACTIONS ,
} )
. option ( 'exit-code' , {
description : 'Exit code from patch creation step' ,
type : 'number' ,
demandOption : ! process . env . GITHUB _ACTIONS ,
} )
. option ( 'commit' , {
description : 'The commit SHA being patched' ,
type : 'string' ,
demandOption : ! process . env . GITHUB _ACTIONS ,
} )
. option ( 'channel' , {
description : 'The channel (stable or preview)' ,
type : 'string' ,
choices : [ 'stable' , 'preview' ] ,
demandOption : ! process . env . GITHUB _ACTIONS ,
} )
. option ( 'repository' , {
description : 'The GitHub repository (owner/repo format)' ,
type : 'string' ,
demandOption : ! process . env . GITHUB _ACTIONS ,
} )
. option ( 'run-id' , {
description : 'The GitHub workflow run ID' ,
type : 'string' ,
} )
2025-11-06 18:30:20 -05:00
. option ( 'environment' , {
choices : [ 'prod' , 'dev' ] ,
type : 'string' ,
default : process . env . ENVIRONMENT || 'prod' ,
} )
2025-09-18 16:31:39 -07:00
. option ( 'test' , {
description : 'Test mode - validate logic without GitHub API calls' ,
type : 'boolean' ,
default : false ,
} )
. example (
'$0 --original-pr 8655 --exit-code 0 --commit abc1234 --channel preview --repository google-gemini/gemini-cli --test' ,
'Test success comment' ,
)
. example (
'$0 --original-pr 8655 --exit-code 1 --commit abc1234 --channel stable --repository google-gemini/gemini-cli --test' ,
'Test failure comment' ,
)
. help ( )
. alias ( 'help' , 'h' ) . argv ;
const testMode = argv . test || process . env . TEST _MODE === 'true' ;
2025-09-18 19:18:27 -07:00
// GitHub CLI is available in the workflow environment
const hasGitHubCli = ! testMode ;
2025-09-18 16:31:39 -07:00
// Get inputs from CLI args or environment
const originalPr = argv . originalPr || process . env . ORIGINAL _PR ;
const exitCode =
argv . exitCode !== undefined
? argv . exitCode
: parseInt ( process . env . EXIT _CODE || '1' ) ;
const commit = argv . commit || process . env . COMMIT ;
const channel = argv . channel || process . env . CHANNEL ;
2025-11-06 18:30:20 -05:00
const environment = argv . environment ;
2025-09-18 16:31:39 -07:00
const repository =
argv . repository || process . env . REPOSITORY || 'google-gemini/gemini-cli' ;
const runId = argv . runId || process . env . GITHUB _RUN _ID || '0' ;
2025-09-19 04:02:50 -07:00
// Validate required parameters
if ( ! runId || runId === '0' ) {
console . warn (
'Warning: No valid GitHub run ID found, workflow links may not work correctly' ,
) ;
}
2025-09-18 16:31:39 -07:00
if ( ! originalPr ) {
console . log ( 'No original PR specified, skipping comment' ) ;
return ;
}
console . log (
` Analyzing patch creation result for PR ${ originalPr } (exit code: ${ exitCode } ) ` ,
) ;
2025-09-18 19:18:27 -07:00
const [ _owner , _repo ] = repository . split ( '/' ) ;
2025-09-18 16:31:39 -07:00
const npmTag = channel === 'stable' ? 'latest' : 'preview' ;
if ( testMode ) {
console . log ( '\n🧪 TEST MODE - No API calls will be made' ) ;
console . log ( '\n📋 Inputs:' ) ;
console . log ( ` - Original PR: ${ originalPr } ` ) ;
console . log ( ` - Exit Code: ${ exitCode } ` ) ;
console . log ( ` - Commit: ${ commit } ` ) ;
console . log ( ` - Channel: ${ channel } → npm tag: ${ npmTag } ` ) ;
console . log ( ` - Repository: ${ repository } ` ) ;
console . log ( ` - Run ID: ${ runId } ` ) ;
}
let commentBody ;
let logContent = '' ;
2025-09-18 19:00:18 -07:00
// Get log content from environment variable or generate mock content for testing
if ( testMode && ! process . env . LOG _CONTENT ) {
// Create mock log content for testing only if LOG_CONTENT is not provided
if ( exitCode === 0 ) {
logContent = ` Creating hotfix branch hotfix/v0.5.3/ ${ channel } /cherry-pick- ${ commit . substring ( 0 , 7 ) } from release/v0.5.3 ` ;
2025-09-18 16:31:39 -07:00
} else {
2025-09-18 19:00:18 -07:00
logContent = 'Error: Failed to create patch' ;
2025-09-18 16:31:39 -07:00
}
2025-09-18 19:00:18 -07:00
} else {
// Use log content from environment variable
logContent = process . env . LOG _CONTENT || '' ;
2025-09-18 16:31:39 -07:00
}
2025-09-19 04:02:50 -07:00
if (
logContent . includes (
'Failed to create release branch due to insufficient GitHub App permissions' ,
)
) {
// GitHub App permission error - extract manual commands
const manualCommandsMatch = logContent . match (
2025-09-19 04:07:26 -07:00
/📋 Please run these commands manually to create the branch:[\s\S]*?```bash\s*([\s\S]*?)\s*```/ ,
2025-09-19 04:02:50 -07:00
) ;
let manualCommands = '' ;
if ( manualCommandsMatch ) {
manualCommands = manualCommandsMatch [ 1 ] . trim ( ) ;
}
2026-03-10 13:15:04 -04:00
commentBody = ` 🔒 **[Step 2/4] GitHub App Permission Issue**
2025-09-19 04:02:50 -07:00
The patch creation failed due to insufficient GitHub App permissions for creating workflow files .
* * 📝 Manual Action Required : * *
$ {
manualCommands
? ` Please run these commands manually to create the release branch:
\ ` \` \` bash
$ { manualCommands }
\ ` \` \`
After running these commands , you can re - run the patch workflow . `
: 'Please check the workflow logs for manual commands to run.'
}
* * 🔗 Links : * *
- [ View workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
} else if ( logContent . includes ( 'already has an open PR' ) ) {
2025-09-18 16:31:39 -07:00
// Branch exists with existing PR
const prMatch = logContent . match ( /Found existing PR #(\d+): (.*)/ ) ;
if ( prMatch ) {
const [ , prNumber , prUrl ] = prMatch ;
2026-03-10 13:15:04 -04:00
commentBody = ` ℹ ️ **[Step 2/4] Patch PR already exists!**
2025-09-18 16:31:39 -07:00
A patch PR for this change already exists : [ # $ { prNumber } ] ( $ { prUrl } ) .
* * 📝 Next Steps : * *
1. Review and approve the existing patch PR
2. If it ' s incorrect , close it and run the patch command again
* * 🔗 Links : * *
- [ View existing patch PR # $ { prNumber } ] ( $ { prUrl } ) ` ;
}
} else if ( logContent . includes ( 'exists but has no open PR' ) ) {
// Branch exists but no PR
const branchMatch = logContent . match ( /Hotfix branch (.*) already exists/ ) ;
if ( branchMatch ) {
const [ , branch ] = branchMatch ;
2026-03-10 13:15:04 -04:00
commentBody = ` ℹ ️ **[Step 2/4] Patch branch exists but no PR found!**
2025-09-18 16:31:39 -07:00
A patch branch [ \ ` ${ branch } \` ](https://github.com/ ${ repository } /tree/ ${ branch } ) exists but has no open PR.
* * 🔍 Issue : * * This might indicate an incomplete patch process .
* * 📝 Next Steps : * *
1. Delete the branch : \ ` git branch -D ${ branch } \`
2. Run the patch command again
* * 🔗 Links : * *
- [ View branch on GitHub ] ( https : //github.com/${repository}/tree/${branch})`;
}
} else if ( exitCode === 0 ) {
// Success - extract branch info
const branchMatch = logContent . match ( /Creating hotfix branch (.*) from/ ) ;
if ( branchMatch ) {
const [ , branch ] = branchMatch ;
if ( testMode ) {
// Mock PR info for testing
const mockPrNumber = Math . floor ( Math . random ( ) * 1000 ) + 8000 ;
const mockPrUrl = ` https://github.com/ ${ repository } /pull/ ${ mockPrNumber } ` ;
2025-09-18 17:33:08 -07:00
const hasConflicts =
logContent . includes ( 'Cherry-pick has conflicts' ) ||
logContent . includes ( '[CONFLICTS]' ) ;
2026-03-10 13:15:04 -04:00
commentBody = ` 🚀 **[Step 2/4] Patch PR Created!**
2025-09-18 16:31:39 -07:00
* * 📋 Patch Details : * *
2025-11-06 18:30:20 -05:00
- * * Environment * * : \ ` ${ environment } \`
2025-09-18 16:31:39 -07:00
- * * Channel * * : \ ` ${ channel } \` → will publish to npm tag \` ${ npmTag } \`
- * * Commit * * : \ ` ${ commit } \`
- * * Hotfix Branch * * : [ \ ` ${ branch } \` ](https://github.com/ ${ repository } /tree/ ${ branch } )
2025-09-18 17:33:08 -07:00
- * * Hotfix PR * * : [ # $ { mockPrNumber } ] ( $ { mockPrUrl } ) $ { hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : '' }
2025-09-18 16:31:39 -07:00
* * 📝 Next Steps : * *
2025-09-18 17:33:08 -07:00
1. $ { hasConflicts ? '⚠️ **Resolve conflicts** in the hotfix PR first' : 'Review and approve the hotfix PR' } : [ # $ { mockPrNumber } ] ( $ { mockPrUrl } ) $ { hasConflicts ? '\n2. **Test your changes** after resolving conflicts' : '' }
$ { hasConflicts ? '3' : '2' } . Once merged , the patch release will automatically trigger
$ { hasConflicts ? '4' : '3' } . You ' ll receive updates here when the release completes
2025-09-18 16:31:39 -07:00
* * 🔗 Track Progress : * *
2026-03-10 13:15:04 -04:00
- [ View hotfix PR # $ { mockPrNumber } ] ( $ { mockPrUrl } )
- [ This patch creation workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
2025-09-18 19:18:27 -07:00
} else if ( hasGitHubCli ) {
// Find the actual PR for the new branch using gh CLI
2025-09-18 16:31:39 -07:00
try {
2025-09-18 19:18:27 -07:00
const { spawnSync } = await import ( 'node:child_process' ) ;
const result = spawnSync (
'gh' ,
[
'pr' ,
'list' ,
'--head' ,
branch ,
'--state' ,
'open' ,
'--json' ,
'number,title,url' ,
'--limit' ,
'1' ,
] ,
{ encoding : 'utf8' } ,
) ;
if ( result . error ) {
throw result . error ;
}
if ( result . status !== 0 ) {
throw new Error (
` gh pr list failed with status ${ result . status } : ${ result . stderr } ` ,
) ;
}
const prListOutput = result . stdout ;
const prList = JSON . parse ( prListOutput ) ;
if ( prList . length > 0 ) {
const pr = prList [ 0 ] ;
2025-09-18 17:33:08 -07:00
const hasConflicts =
logContent . includes ( 'Cherry-pick has conflicts' ) ||
pr . title . includes ( '[CONFLICTS]' ) ;
2026-03-10 13:15:04 -04:00
commentBody = ` 🚀 **[Step 2/4] Patch PR Created!**
2025-09-18 16:31:39 -07:00
* * 📋 Patch Details : * *
2025-11-06 18:30:20 -05:00
- * * Environment * * : \ ` ${ environment } \`
2025-09-18 16:31:39 -07:00
- * * Channel * * : \ ` ${ channel } \` → will publish to npm tag \` ${ npmTag } \`
- * * Commit * * : \ ` ${ commit } \`
- * * Hotfix Branch * * : [ \ ` ${ branch } \` ](https://github.com/ ${ repository } /tree/ ${ branch } )
2025-09-18 19:18:27 -07:00
- * * Hotfix PR * * : [ # $ { pr . number } ] ( $ { pr . url } ) $ { hasConflicts ? '\n- **⚠️ Status**: Cherry-pick conflicts detected - manual resolution required' : '' }
2025-09-18 16:31:39 -07:00
* * 📝 Next Steps : * *
2025-09-18 19:18:27 -07:00
1. $ { hasConflicts ? '⚠️ **Resolve conflicts** in the hotfix PR first' : 'Review and approve the hotfix PR' } : [ # $ { pr . number } ] ( $ { pr . url } ) $ { hasConflicts ? '\n2. **Test your changes** after resolving conflicts' : '' }
2025-09-18 17:33:08 -07:00
$ { hasConflicts ? '3' : '2' } . Once merged , the patch release will automatically trigger
$ { hasConflicts ? '4' : '3' } . You ' ll receive updates here when the release completes
2025-09-18 16:31:39 -07:00
* * 🔗 Track Progress : * *
2026-03-10 13:15:04 -04:00
- [ View hotfix PR # $ { pr . number } ] ( $ { pr . url } )
- [ This patch creation workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
2025-09-18 16:31:39 -07:00
} else {
// Fallback if PR not found yet
2026-03-10 13:15:04 -04:00
commentBody = ` 🚀 **[Step 2/4] Patch PR Created!**
2025-09-18 16:31:39 -07:00
The patch release PR for this change has been created on branch [ \ ` ${ branch } \` ](https://github.com/ ${ repository } /tree/ ${ branch } ).
* * 📝 Next Steps : * *
1. Review and approve the patch PR
2. Once merged , the patch release will automatically trigger
* * 🔗 Links : * *
2026-03-10 13:15:04 -04:00
- [ View all patch PRs ] ( https : //github.com/${repository}/pulls?q=is%3Apr+is%3Aopen+label%3Apatch)
- [ This patch creation workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
2025-09-18 16:31:39 -07:00
}
} catch ( error ) {
console . log ( 'Error finding PR for branch:' , error . message ) ;
// Fallback
2026-03-10 13:15:04 -04:00
commentBody = ` 🚀 **[Step 2/4] Patch PR Created!**
2025-09-18 16:31:39 -07:00
The patch release PR for this change has been created .
* * 🔗 Links : * *
2026-03-10 13:15:04 -04:00
- [ View all patch PRs ] ( https : //github.com/${repository}/pulls?q=is%3Apr+is%3Aopen+label%3Apatch)
- [ This patch creation workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
2025-09-18 16:31:39 -07:00
}
}
}
} else {
// Failure
2026-03-10 13:15:04 -04:00
commentBody = ` ❌ **[Step 2/4] Patch creation failed!**
2025-09-18 16:31:39 -07:00
There was an error creating the patch release .
* * 🔍 Troubleshooting : * *
- Check the workflow logs for detailed error information
- Verify the commit SHA is valid and accessible
- Ensure you have permissions to create branches and PRs
* * 🔗 Links : * *
2025-09-19 00:36:12 -07:00
- [ View workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
2025-09-18 16:31:39 -07:00
}
if ( ! commentBody ) {
2026-03-10 13:15:04 -04:00
commentBody = ` ❌ **[Step 2/4] Patch creation failed!**
2025-09-18 16:31:39 -07:00
No output was generated during patch creation .
* * 🔗 Links : * *
- [ View workflow run ] ( https : //github.com/${repository}/actions/runs/${runId})`;
}
if ( testMode ) {
console . log ( '\n💬 Would post comment:' ) ;
console . log ( '----------------------------------------' ) ;
console . log ( commentBody ) ;
console . log ( '----------------------------------------' ) ;
console . log ( '\n✅ Comment generation working correctly!' ) ;
2025-09-18 19:18:27 -07:00
} else if ( hasGitHubCli ) {
const { spawnSync } = await import ( 'node:child_process' ) ;
const { writeFileSync , unlinkSync } = await import ( 'node:fs' ) ;
const { join } = await import ( 'node:path' ) ;
// Write comment to temporary file to avoid shell escaping issues
const tmpFile = join ( process . cwd ( ) , ` comment- ${ Date . now ( ) } .md ` ) ;
writeFileSync ( tmpFile , commentBody ) ;
try {
const result = spawnSync (
'gh' ,
[ 'pr' , 'comment' , originalPr . toString ( ) , '--body-file' , tmpFile ] ,
{
stdio : 'inherit' ,
} ,
) ;
if ( result . error ) {
throw result . error ;
}
if ( result . status !== 0 ) {
throw new Error ( ` gh pr comment failed with status ${ result . status } ` ) ;
}
console . log ( ` Successfully commented on PR ${ originalPr } ` ) ;
} finally {
// Clean up temp file
try {
unlinkSync ( tmpFile ) ;
} catch ( _e ) {
// Ignore cleanup errors
}
}
2025-09-18 16:31:39 -07:00
} else {
2025-09-18 19:18:27 -07:00
console . log ( 'No GitHub CLI available' ) ;
2025-09-18 16:31:39 -07:00
}
}
main ( ) . catch ( ( error ) => {
console . error ( 'Error commenting on PR:' , error ) ;
process . exit ( 1 ) ;
} ) ;