Sync nightly → production #21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync nightly → production | |
| on: | |
| schedule: | |
| # Run weekly on Sundays at midnight UTC | |
| - cron: '0 0 * * 0' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| concurrency: | |
| group: sync-nightly-production | |
| cancel-in-progress: true | |
| jobs: | |
| create-and-merge: | |
| name: Create PR and merge into production | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create or update PR and merge when possible | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const head = 'nightly'; | |
| const base = 'production'; | |
| const title = `chore: sync ${head} → ${base} (${new Date().toISOString().split('T')[0]})`; | |
| const body = `Automated sync of ${head} into ${base} by workflow.`; | |
| // Look for an existing open PR from nightly -> production | |
| const { data: existing } = await github.rest.pulls.list({ | |
| owner, | |
| repo, | |
| state: 'open', | |
| head: `${owner}:${head}`, | |
| base, | |
| }); | |
| let pr; | |
| if (existing.length > 0) { | |
| pr = existing[0]; | |
| core.info(`Found existing PR #${pr.number}`); | |
| } else { | |
| const { data: created } = await github.rest.pulls.create({ | |
| owner, | |
| repo, | |
| title, | |
| head, | |
| base, | |
| body, | |
| maintainer_can_modify: true, | |
| }); | |
| pr = created; | |
| core.info(`Created PR #${pr.number}`); | |
| } | |
| // Wait for GitHub to compute mergeability (it may be null initially) | |
| let refreshed = pr; | |
| for (let i = 0; i < 10; i++) { | |
| const { data } = await github.rest.pulls.get({ owner, repo, pull_number: pr.number }); | |
| refreshed = data; | |
| if (refreshed.mergeable !== null) break; | |
| await new Promise((r) => setTimeout(r, 2000)); | |
| } | |
| if (!refreshed.mergeable) { | |
| core.setOutput('pr-number', String(pr.number)); | |
| core.info(`PR #${pr.number} is not mergeable (state=${refreshed.mergeable_state}). Leaving PR open for manual resolution.`); | |
| return; | |
| } | |
| // Merge the PR | |
| try { | |
| const mergeRes = await github.rest.pulls.merge({ | |
| owner, | |
| repo, | |
| pull_number: pr.number, | |
| merge_method: 'merge', | |
| }); | |
| core.info(`Merged PR #${pr.number} — sha: ${mergeRes.data.sha}`); | |
| core.setOutput('pr-number', String(pr.number)); | |
| } catch (err) { | |
| core.setFailed(`Failed to merge PR #${pr.number}: ${err.message}`); | |
| } |