diff --git a/.github/workflows/release-patch-2-trigger.yml b/.github/workflows/release-patch-2-trigger.yml index 47a9ea40cd..842a662c4f 100644 --- a/.github/workflows/release-patch-2-trigger.yml +++ b/.github/workflows/release-patch-2-trigger.yml @@ -27,6 +27,11 @@ on: required: false type: 'boolean' default: false + force_skip_tests: + description: 'Select to skip the "Run Tests" step in testing. Prod releases should run tests' + required: false + type: 'boolean' + default: false jobs: trigger-patch-release: @@ -34,7 +39,7 @@ jobs: runs-on: 'ubuntu-latest' permissions: actions: 'write' - contents: 'read' + contents: 'write' pull-requests: 'write' steps: - name: 'Checkout' @@ -62,5 +67,6 @@ jobs: GITHUB_REPOSITORY_NAME: '${{ github.event.repository.name }}' GITHUB_EVENT_NAME: '${{ github.event_name }}' GITHUB_EVENT_PAYLOAD: '${{ toJSON(github.event) }}' + FORCE_SKIP_TESTS: '${{ github.event.inputs.force_skip_tests }}' run: | node scripts/releasing/patch-trigger.js diff --git a/docs/releases.md b/docs/releases.md index 1ca7439090..2ac8f6a0ab 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -191,6 +191,65 @@ Upon merging the pull request, the `Release: Patch (2) Trigger` workflow is auto This fully automated process ensures that patches are created and released consistently and reliably. +#### Troubleshooting: Older Branch Workflows + +**Issue**: If the patch trigger workflow fails with errors like "Resource not accessible by integration" or references to non-existent workflow files (e.g., `patch-release.yml`), this indicates the hotfix branch contains an outdated version of the workflow files. + +**Root Cause**: When a PR is merged, GitHub Actions runs the workflow definition from the **source branch** (the hotfix branch), not from the target branch (the release branch). If the hotfix branch was created from an older release branch that predates workflow improvements, it will use the old workflow logic. + +**Solutions**: + +**Option 1: Manual Trigger (Quick Fix)** +Manually trigger the updated workflow from the branch with the latest workflow code: + +```bash +# For a preview channel patch with tests skipped +gh workflow run release-patch-2-trigger.yml --ref \ + --field ref="hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234" \ + --field workflow_ref= \ + --field dry_run=false \ + --field force_skip_tests=true + +# For a stable channel patch +gh workflow run release-patch-2-trigger.yml --ref \ + --field ref="hotfix/v0.5.1/stable/cherry-pick-abc1234" \ + --field workflow_ref= \ + --field dry_run=false \ + --field force_skip_tests=false + +# Example using main branch (most common case) +gh workflow run release-patch-2-trigger.yml --ref main \ + --field ref="hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234" \ + --field workflow_ref=main \ + --field dry_run=false \ + --field force_skip_tests=true +``` + +**Note**: Replace `` with the branch containing the latest workflow improvements (usually `main`, but could be a feature branch if testing updates). + +**Option 2: Update the Hotfix Branch** +Merge the latest main branch into your hotfix branch to get the updated workflows: + +```bash +git checkout hotfix/v0.6.0-preview.2/preview/cherry-pick-abc1234 +git merge main +git push +``` + +Then close and reopen the PR to retrigger the workflow with the updated version. + +**Option 3: Direct Release Trigger** +Skip the trigger workflow entirely and directly run the release workflow: + +```bash +# Replace channel and release_ref with appropriate values +gh workflow run release-patch-3-release.yml --ref main \ + --field type="preview" \ + --field dry_run=false \ + --field force_skip_tests=true \ + --field release_ref="release/v0.6.0-preview.2" +``` + ### Docker We also run a Google cloud build called [release-docker.yml](../.gcp/release-docker.yml). Which publishes the sandbox docker to match your release. This will also be moved to GH and combined with the main release file once service account permissions are sorted out. diff --git a/scripts/releasing/patch-trigger.js b/scripts/releasing/patch-trigger.js index 7402cdebda..7434efdcf8 100644 --- a/scripts/releasing/patch-trigger.js +++ b/scripts/releasing/patch-trigger.js @@ -37,6 +37,11 @@ async function main() { type: 'boolean', default: false, }) + .option('force-skip-tests', { + description: 'Skip the "Run Tests" step in testing', + type: 'boolean', + default: false, + }) .example( '$0 --head-ref "hotfix/v0.5.3/preview/cherry-pick-abc1234" --test', 'Test channel detection logic', @@ -50,15 +55,6 @@ async function main() { const testMode = argv.test || process.env.TEST_MODE === 'true'; - // Initialize GitHub API client only if not in test mode - let github; - if (!testMode) { - const { Octokit } = await import('@octokit/rest'); - github = new Octokit({ - auth: process.env.GITHUB_TOKEN, - }); - } - const context = { eventName: process.env.GITHUB_EVENT_NAME || 'pull_request', repo: { @@ -72,6 +68,8 @@ async function main() { const headRef = argv.headRef || process.env.HEAD_REF; const body = argv.prBody || process.env.PR_BODY || ''; const isDryRun = argv.dryRun || body.includes('[DRY RUN]'); + const forceSkipTests = + argv.forceSkipTests || process.env.FORCE_SKIP_TESTS === 'true'; if (!headRef) { throw new Error( @@ -131,21 +129,26 @@ async function main() { // Try to find the original PR that requested this patch let originalPr = null; - if (!testMode && github) { + if (!testMode) { try { console.log('Looking for original PR using search...'); - // Use GitHub search to find the PR with a comment referencing the hotfix branch. - // This is much more efficient than listing PRs and their comments. - const query = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:all in:comments "Patch PR Created" "${headRef}"`; - const searchResults = await github.rest.search.issuesAndPullRequests({ - q: query, - sort: 'updated', - order: 'desc', - per_page: 1, - }); + const { execFileSync } = await import('node:child_process'); - if (searchResults.data.items.length > 0) { - originalPr = searchResults.data.items[0].number; + // Use gh CLI to search for PRs with comments referencing the hotfix branch + const query = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:all in:comments "Patch PR Created" "${headRef}"`; + + const result = execFileSync( + 'gh', + ['search', 'prs', '--json', 'number,title', '--limit', '1', query], + { + encoding: 'utf8', + env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN }, + }, + ); + + const searchResults = JSON.parse(result); + if (searchResults && searchResults.length > 0) { + originalPr = searchResults[0].number; console.log(`Found original PR: #${originalPr}`); } else { console.log('Could not find a matching original PR via search.'); @@ -160,23 +163,45 @@ async function main() { // Trigger the release workflow console.log(`Triggering release workflow: ${workflowId}`); - if (!testMode && github) { - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflowId, - ref: 'main', - inputs: { - type: channel, - dry_run: isDryRun.toString(), - release_ref: releaseRef, - original_pr: originalPr ? originalPr.toString() : '', - }, - }); + if (!testMode) { + try { + const { execFileSync } = await import('node:child_process'); + + const args = [ + 'workflow', + 'run', + workflowId, + '--ref', + 'main', + '--field', + `type=${channel}`, + '--field', + `dry_run=${isDryRun.toString()}`, + '--field', + `force_skip_tests=${forceSkipTests.toString()}`, + '--field', + `release_ref=${releaseRef}`, + '--field', + originalPr ? `original_pr=${originalPr.toString()}` : 'original_pr=', + ]; + + console.log(`Running command: gh ${args.join(' ')}`); + + execFileSync('gh', args, { + stdio: 'inherit', + env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN }, + }); + + console.log('✅ Workflow dispatch completed successfully'); + } catch (e) { + console.error('❌ Failed to dispatch workflow:', e.message); + throw e; + } } else { console.log('✅ Would trigger workflow with inputs:', { type: channel, dry_run: isDryRun.toString(), + force_skip_tests: forceSkipTests.toString(), release_ref: releaseRef, original_pr: originalPr ? originalPr.toString() : '', }); @@ -200,13 +225,46 @@ async function main() { **🔗 Track Progress:** - [View release workflow](https://github.com/${context.repo.owner}/${context.repo.repo}/actions)`; - if (!testMode && github) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: originalPr, - body: commentBody, - }); + if (!testMode) { + let tempDir; + try { + const { execFileSync } = await import('node:child_process'); + const { writeFileSync, mkdtempSync } = await import('node:fs'); + const { join } = await import('node:path'); + const { tmpdir } = await import('node:os'); + + // Create secure temporary directory and file + tempDir = mkdtempSync(join(tmpdir(), 'patch-trigger-')); + const tempFile = join(tempDir, 'comment.md'); + writeFileSync(tempFile, commentBody); + + execFileSync( + 'gh', + ['pr', 'comment', originalPr.toString(), '--body-file', tempFile], + { + stdio: 'inherit', + env: { ...process.env, GH_TOKEN: process.env.GITHUB_TOKEN }, + }, + ); + + console.log('✅ Comment posted successfully'); + } catch (e) { + console.error('❌ Failed to post comment:', e.message); + // Don't throw here since the main workflow dispatch succeeded + } finally { + // Clean up temp directory and all its contents + if (tempDir) { + try { + const { rmSync } = await import('node:fs'); + rmSync(tempDir, { recursive: true, force: true }); + } catch (cleanupError) { + console.warn( + '⚠️ Failed to clean up temp directory:', + cleanupError.message, + ); + } + } + } } else { console.log('✅ Would post comment:', commentBody); }