diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 13a0d8a8848..b3238e040fc 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -53,6 +53,7 @@ on: - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' + - 'update_pull_request_branches' - 'validate' run_url: description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' @@ -62,7 +63,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate)' required: false type: string default: '' @@ -157,7 +158,7 @@ jobs: await main(); run_operation: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim permissions: actions: write @@ -213,6 +214,47 @@ jobs: id: record run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + update_pull_request_branches: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout actions folder + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + actions + persist-credentials: false + + - name: Setup Scripts + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Update pull request branches + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); + await main(); + apply_safe_outputs: if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} runs-on: ubuntu-slim diff --git a/actions/setup/js/run_operation_update_upgrade.cjs b/actions/setup/js/run_operation_update_upgrade.cjs index c0dc3768d31..1da45d2774d 100644 --- a/actions/setup/js/run_operation_update_upgrade.cjs +++ b/actions/setup/js/run_operation_update_upgrade.cjs @@ -45,7 +45,8 @@ function formatTimestamp(date) { } /** - * Run 'gh aw update', 'gh aw upgrade', 'gh aw disable', or 'gh aw enable', + * Run maintenance operations handled by run_operation: + * - 'gh aw update', 'gh aw upgrade', 'gh aw disable', 'gh aw enable' * creating a pull request when needed for update/upgrade operations. * * For update/upgrade: runs with --no-compile so lock files are not modified. diff --git a/actions/setup/js/update_pull_request_branches.cjs b/actions/setup/js/update_pull_request_branches.cjs new file mode 100644 index 00000000000..1a66c0f19ca --- /dev/null +++ b/actions/setup/js/update_pull_request_branches.cjs @@ -0,0 +1,184 @@ +// @ts-check +/// + +const { getErrorMessage } = require("./error_helpers.cjs"); +const { withRetry, isTransientError, sleep } = require("./error_recovery.cjs"); +const { fetchAndLogRateLimit } = require("./github_rate_limit_logger.cjs"); +const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); + +const LIST_PULL_REQUESTS_PER_PAGE = 100; +const UPDATE_DELAY_MS = 1000; + +/** + * @param {string} owner + * @param {string} repo + * @returns {Promise} + */ +async function listOpenPullRequests(owner, repo) { + const pulls = await github.paginate(github.rest.pulls.list, { + owner, + repo, + state: "open", + per_page: LIST_PULL_REQUESTS_PER_PAGE, + }); + + return pulls.map(pr => pr.number).filter(number => Number.isInteger(number)); +} + +/** + * @param {string} owner + * @param {string} repo + * @param {number[]} pullNumbers + * @returns {Promise} + */ +async function filterMergeablePullRequests(owner, repo, pullNumbers) { + const mergeable = []; + const baseRepository = `${owner}/${repo}`.toLowerCase(); + + for (const pullNumber of pullNumbers) { + const { data: pull } = await withRetry( + () => + github.rest.pulls.get({ + owner, + repo, + pull_number: pullNumber, + }), + { + maxRetries: 2, + initialDelayMs: 500, + maxDelayMs: 2000, + jitterMs: 0, + shouldRetry: isTransientError, + }, + `fetch pull request #${pullNumber}` + ); + + const headRepositoryRaw = pull?.head?.repo?.full_name; + const headRepository = headRepositoryRaw?.toLowerCase() ?? ""; + const isSameRepository = headRepository === baseRepository; + const isMergeable = pull?.state === "open" && pull?.mergeable === true && pull?.draft !== true && isSameRepository; + if (isMergeable) { + mergeable.push(pullNumber); + continue; + } + + let skipReason = "not_mergeable"; + if (!isSameRepository) { + skipReason = headRepository ? "head_repository_mismatch" : "head_repository_missing"; + } + core.info(`Skipping PR #${pullNumber}: reason=${skipReason}, mergeable=${String(pull?.mergeable)}, state=${pull?.state || "unknown"}, draft=${String(Boolean(pull?.draft))}, head_repo=${headRepository || "unknown"}`); + } + + return mergeable; +} + +/** + * @param {unknown} error + * @returns {boolean} + */ +function isNonFatalUpdateBranchError(error) { + if (typeof error === "object" && error !== null && "status" in error && error.status === 422) { + return true; + } + + const message = getErrorMessage(error).toLowerCase(); + return message.includes("update branch failed") || message.includes("head branch is not behind"); +} + +/** + * @param {string} owner + * @param {string} repo + * @param {number} pullNumber + * @returns {Promise} + */ +async function updatePullRequestBranch(owner, repo, pullNumber) { + await withRetry( + () => + github.rest.pulls.updateBranch({ + owner, + repo, + pull_number: pullNumber, + }), + { + maxRetries: 2, + initialDelayMs: 1000, + maxDelayMs: 10000, + shouldRetry: isTransientError, + }, + `update branch for pull request #${pullNumber}` + ); +} + +/** + * @param {string} owner + * @param {string} repo + * @param {number} pullNumber + * @param {string} runUrl + * @returns {Promise} + */ +async function addMaintenanceUpdateComment(owner, repo, pullNumber, runUrl) { + const body = `🛠️ Agentic Maintenance updated this pull request branch.\n\n[View workflow run](${runUrl})`; + await github.rest.issues.createComment({ + owner, + repo, + issue_number: pullNumber, + body, + }); +} + +/** + * Update all mergeable PR branches. + * @returns {Promise} + */ +async function main() { + const owner = context.repo.owner; + const repo = context.repo.repo; + const runUrl = buildWorkflowRunUrl(context, context.repo); + + core.info(`Updating pull request branches in ${owner}/${repo}`); + core.info(`Run URL: ${runUrl}`); + await fetchAndLogRateLimit(github, "update_pull_request_branches_start"); + + const openPullRequests = await listOpenPullRequests(owner, repo); + core.info(`Found ${openPullRequests.length} open pull request(s)`); + if (openPullRequests.length === 0) return; + + const mergeablePullRequests = await filterMergeablePullRequests(owner, repo, openPullRequests); + core.info(`Found ${mergeablePullRequests.length} mergeable pull request(s)`); + if (mergeablePullRequests.length === 0) return; + + let updatedCount = 0; + let skippedCount = 0; + let failedCount = 0; + + for (let i = 0; i < mergeablePullRequests.length; i++) { + const pullNumber = mergeablePullRequests[i]; + try { + core.info(`Updating branch for PR #${pullNumber}`); + await updatePullRequestBranch(owner, repo, pullNumber); + await addMaintenanceUpdateComment(owner, repo, pullNumber, runUrl); + updatedCount++; + } catch (error) { + if (isNonFatalUpdateBranchError(error)) { + skippedCount++; + core.warning(`Skipping PR #${pullNumber}: ${getErrorMessage(error)}`); + } else { + failedCount++; + core.error(`Failed to update branch for PR #${pullNumber}: ${getErrorMessage(error)}`); + } + } + + if (i < mergeablePullRequests.length - 1) { + await sleep(UPDATE_DELAY_MS); + } + } + + await fetchAndLogRateLimit(github, "update_pull_request_branches_end"); + core.notice(`update_pull_request_branches completed: updated=${updatedCount}, skipped=${skippedCount}, failed=${failedCount}`); +} + +module.exports = { + main, + filterMergeablePullRequests, + isNonFatalUpdateBranchError, +}; diff --git a/actions/setup/js/update_pull_request_branches.test.cjs b/actions/setup/js/update_pull_request_branches.test.cjs new file mode 100644 index 00000000000..23a314820bf --- /dev/null +++ b/actions/setup/js/update_pull_request_branches.test.cjs @@ -0,0 +1,143 @@ +// @ts-check +import { describe, it, expect, beforeEach, vi } from "vitest"; + +vi.mock("./github_rate_limit_logger.cjs", () => ({ + fetchAndLogRateLimit: vi.fn().mockResolvedValue(undefined), +})); + +const moduleUnderTest = await import("./update_pull_request_branches.cjs"); + +describe("update_pull_request_branches", () => { + /** @type {any} */ + let mockCore; + /** @type {any} */ + let mockGithub; + /** @type {any} */ + let mockContext; + + beforeEach(() => { + vi.clearAllMocks(); + + mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + notice: vi.fn(), + }; + mockGithub = { + paginate: vi.fn(), + graphql: vi.fn(), + rest: { + issues: { + createComment: vi.fn(), + }, + pulls: { + list: vi.fn(), + get: vi.fn(), + updateBranch: vi.fn(), + }, + }, + }; + mockContext = { + runId: 123, + serverUrl: "https://github.com", + repo: { + owner: "owner", + repo: "repo", + }, + }; + + global.core = mockCore; + global.github = mockGithub; + global.context = mockContext; + }); + + it("updates only mergeable pull requests", async () => { + mockGithub.paginate.mockResolvedValue([{ number: 1 }, { number: 2 }, { number: 3 }]); + mockGithub.rest.pulls.get.mockImplementation(async ({ pull_number }) => { + if (pull_number === 1) return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + if (pull_number === 2) return { data: { state: "open", mergeable: false, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + }); + mockGithub.rest.pulls.updateBranch.mockResolvedValue({ data: {} }); + + await moduleUnderTest.main(); + + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenCalledTimes(2); + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenNthCalledWith(1, { + owner: "owner", + repo: "repo", + pull_number: 1, + }); + expect(mockGithub.rest.pulls.updateBranch).toHaveBeenNthCalledWith(2, { + owner: "owner", + repo: "repo", + pull_number: 3, + }); + expect(mockGithub.rest.issues.createComment).toHaveBeenCalledTimes(2); + expect(mockGithub.rest.issues.createComment).toHaveBeenNthCalledWith(1, { + owner: "owner", + repo: "repo", + issue_number: 1, + body: expect.stringContaining("[View workflow run](https://github.com/owner/repo/actions/runs/123)"), + }); + expect(mockGithub.rest.issues.createComment).toHaveBeenNthCalledWith(2, { + owner: "owner", + repo: "repo", + issue_number: 3, + body: expect.stringContaining("[View workflow run](https://github.com/owner/repo/actions/runs/123)"), + }); + }); + + it("continues on non-fatal updateBranch failures", async () => { + mockGithub.paginate.mockResolvedValue([{ number: 7 }]); + mockGithub.rest.pulls.get.mockResolvedValue({ data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } }); + const err = new Error("Update branch failed"); + // @ts-ignore + err.status = 422; + mockGithub.rest.pulls.updateBranch.mockRejectedValue(err); + + await expect(moduleUnderTest.main()).resolves.not.toThrow(); + expect(mockCore.warning).toHaveBeenCalledWith(expect.stringContaining("Skipping PR #7")); + expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled(); + }); + + it("ignores draft pull requests when filtering mergeable pull requests", async () => { + mockGithub.rest.pulls.get.mockImplementation(async ({ pull_number }) => { + if (pull_number === 1) return { data: { state: "open", mergeable: true, draft: true, head: { repo: { full_name: "owner/repo" } } } }; + if (pull_number === 2) return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + return { data: { state: "open", mergeable: false, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + }); + + const result = await moduleUnderTest.filterMergeablePullRequests("owner", "repo", [1, 2, 3]); + + expect(result).toEqual([2]); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Skipping PR #1")); + }); + + it("ignores fork pull requests that cannot be updated by repository token", async () => { + mockGithub.rest.pulls.get.mockImplementation(async ({ pull_number }) => { + if (pull_number === 1) return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "fork-owner/repo" } } } }; + return { data: { state: "open", mergeable: true, draft: false, head: { repo: { full_name: "owner/repo" } } } }; + }); + + const result = await moduleUnderTest.filterMergeablePullRequests("owner", "repo", [1, 2]); + + expect(result).toEqual([2]); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("reason=head_repository_mismatch")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("head_repo=fork-owner/repo")); + }); + + it("logs explicit reason when head repository is unavailable", async () => { + mockGithub.rest.pulls.get.mockResolvedValue({ + data: { state: "open", mergeable: true, draft: false, head: { repo: null } }, + }); + + const result = await moduleUnderTest.filterMergeablePullRequests("owner", "repo", [11]); + + expect(result).toEqual([]); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("reason=head_repository_missing")); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("head_repo=unknown")); + }); +}); diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index 852f3fd78d3..51a3991a18e 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -282,9 +282,10 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { yaml := string(content) operationSkipCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == ''` - operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'validate'` + operationRunCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate'` applySafeOutputsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs'` createLabelsCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels'` + updatePullRequestBranchesCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches'` activityReportCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report'` closeAgenticWorkflowIssuesCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues'` cleanCacheMemoriesCondition := `github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories'` @@ -356,6 +357,23 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { } } + // update_pull_request_branches job should be triggered when operation == 'update_pull_request_branches' + updatePullRequestBranchesIdx := strings.Index(yaml, "\n update_pull_request_branches:") + if updatePullRequestBranchesIdx == -1 { + t.Errorf("Job update_pull_request_branches not found in generated workflow") + } else { + updatePullRequestBranchesSection := yaml[updatePullRequestBranchesIdx : updatePullRequestBranchesIdx+runOpSectionSearchRange] + if !strings.Contains(updatePullRequestBranchesSection, updatePullRequestBranchesCondition) { + t.Errorf("Job update_pull_request_branches should have the activation condition %q in:\n%s", updatePullRequestBranchesCondition, updatePullRequestBranchesSection) + } + if !strings.Contains(updatePullRequestBranchesSection, "pull-requests: write") { + t.Errorf("Job update_pull_request_branches should include pull-requests: write permission in:\n%s", updatePullRequestBranchesSection) + } + if !strings.Contains(updatePullRequestBranchesSection, "contents: write") { + t.Errorf("Job update_pull_request_branches should include contents: write permission in:\n%s", updatePullRequestBranchesSection) + } + } + // validate_workflows job should be triggered when operation == 'validate' validateCondition := `(github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate'` validateIdx := strings.Index(yaml, "\n validate_workflows:") @@ -454,6 +472,11 @@ func TestGenerateMaintenanceWorkflow_OperationJobConditions(t *testing.T) { t.Error("workflow_dispatch operation choices should include 'clean_cache_memories'") } + // Verify update_pull_request_branches is an option in the operation choices + if !strings.Contains(yaml, "- 'update_pull_request_branches'") { + t.Error("workflow_dispatch operation choices should include 'update_pull_request_branches'") + } + // Verify validate is an option in the operation choices if !strings.Contains(yaml, "- 'validate'") { t.Error("workflow_dispatch operation choices should include 'validate'") diff --git a/pkg/workflow/maintenance_workflow_yaml.go b/pkg/workflow/maintenance_workflow_yaml.go index 3dd0362056d..bda1644251e 100644 --- a/pkg/workflow/maintenance_workflow_yaml.go +++ b/pkg/workflow/maintenance_workflow_yaml.go @@ -62,6 +62,7 @@ on: - 'activity_report' - 'close_agentic_workflows_issues' - 'clean_cache_memories' + - 'update_pull_request_branches' - 'validate' run_url: description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' @@ -71,7 +72,7 @@ on: workflow_call: inputs: operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate)' + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate)' required: false type: string default: '' @@ -196,8 +197,8 @@ jobs: `) // Add unified run_operation job for all dispatch operations except those with dedicated jobs - // (safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, validate) - runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity_report", "close_agentic_workflows_issues", "clean_cache_memories", "validate") + // (safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate) + runOperationCondition := buildRunOperationCondition("safe_outputs", "create_labels", "activity_report", "close_agentic_workflows_issues", "clean_cache_memories", "update_pull_request_branches", "validate") yaml.WriteString(` run_operation: if: ${{ ` + RenderCondition(runOperationCondition) + ` }} @@ -251,6 +252,55 @@ jobs: run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" `) + // Add update_pull_request_branches job for workflow_dispatch with operation == 'update_pull_request_branches' + yaml.WriteString(` + update_pull_request_branches: + if: ${{ ` + RenderCondition(buildDispatchOperationCondition("update_pull_request_branches")) + ` }} + runs-on: ` + runsOnValue + ` + permissions: + contents: write + pull-requests: write + steps: +`) + + // Add checkout step only in dev/script mode (for local action paths) + if actionMode == ActionModeDev || actionMode == ActionModeScript { + yaml.WriteString(" - name: Checkout actions folder\n") + yaml.WriteString(" uses: " + getActionPin("actions/checkout") + "\n") + yaml.WriteString(" with:\n") + yaml.WriteString(" sparse-checkout: |\n") + yaml.WriteString(" actions\n") + yaml.WriteString(" persist-credentials: false\n\n") + } + + yaml.WriteString(` - name: Setup Scripts + uses: ` + setupActionRef + ` + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Update pull request branches + uses: ` + getCachedActionPinFromResolver("actions/github-script", resolver) + ` + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); + await main(); +`) + // Add apply_safe_outputs job for workflow_dispatch with operation == 'safe_outputs' yaml.WriteString(` apply_safe_outputs: