Skip to content

Commit 54b8229

Browse files
committed
CI changes
1 parent 8eaebe9 commit 54b8229

File tree

3 files changed

+348
-187
lines changed

3 files changed

+348
-187
lines changed

.github/workflows/production-release.yml

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
name: Production Release - Publish to pub.dev
2727

2828
on:
29-
# Trigger when PR to master is merged
29+
# Trigger when PR to master is merged (legacy path; promotion flow now preferred)
3030
pull_request:
3131
types:
3232
- closed
@@ -51,6 +51,21 @@ on:
5151
type: boolean
5252
default: false
5353

54+
# Allow being called from other workflows
55+
workflow_call:
56+
inputs:
57+
version:
58+
required: true
59+
type: string
60+
skip_tests:
61+
required: false
62+
type: boolean
63+
default: false
64+
dry_run:
65+
required: false
66+
type: boolean
67+
default: false
68+
5469
# Ensure only one production release runs at a time
5570
concurrency:
5671
group: production-release
@@ -67,8 +82,8 @@ jobs:
6782
name: 🔍 Validate Release
6883
runs-on: ubuntu-latest
6984

70-
# Only run if PR was actually merged (not just closed)
71-
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
85+
# Run when manually dispatched, called by another workflow, or when a PR merge event happens
86+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event.pull_request.merged == true
7287

7388
outputs:
7489
version: ${{ steps.get-version.outputs.version }}
@@ -84,8 +99,8 @@ jobs:
8499
- name: 🔍 Validate release source
85100
id: validate
86101
run: |
87-
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
88-
echo "Manual workflow dispatch - skipping branch validation"
102+
if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.event_name }}" == "workflow_call" ]]; then
103+
echo "Manual/called run - skipping branch validation"
89104
echo "is_release_branch=true" >> $GITHUB_OUTPUT
90105
echo "is_valid=true" >> $GITHUB_OUTPUT
91106
else
@@ -109,9 +124,9 @@ jobs:
109124
- name: 📝 Get version from pubspec.yaml
110125
id: get-version
111126
run: |
112-
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
113-
VERSION="${{ github.event.inputs.version }}"
114-
echo "Using manual version: $VERSION"
127+
if [[ "${{ github.event_name }}" == "workflow_dispatch" || "${{ github.event_name }}" == "workflow_call" ]]; then
128+
VERSION="${{ inputs.version || github.event.inputs.version }}"
129+
echo "Using provided version: $VERSION"
115130
else
116131
# Extract version from pubspec.yaml
117132
VERSION=$(grep "^version:" pubspec.yaml | sed 's/version: //' | tr -d ' ')
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
name: Promote Release - Merge on QA Pass and Publish
2+
3+
on:
4+
pull_request:
5+
types: [labeled, synchronize, reopened, ready_for_review]
6+
branches:
7+
- master
8+
pull_request_review:
9+
types: [submitted]
10+
branches:
11+
- master
12+
13+
concurrency:
14+
group: promote-release-${{ github.event.pull_request.number || github.run_id }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
gate-and-merge:
19+
name: 🔐 Gate, Verify Checks, and Merge
20+
if: >-
21+
${ { github.event.pull_request.head.ref } } == '' || startsWith(github.event.pull_request.head.ref, 'releases/')
22+
runs-on: ubuntu-latest
23+
permissions:
24+
contents: write
25+
pull-requests: write
26+
checks: read
27+
statuses: read
28+
outputs:
29+
merged: ${{ steps.merge.outputs.merged }}
30+
version: ${{ steps.version.outputs.version }}
31+
steps:
32+
- name: 🧠 Evaluate conditions
33+
id: eval
34+
uses: actions/github-script@v7
35+
with:
36+
script: |
37+
const core = require('@actions/core');
38+
const pr = context.payload.pull_request || (await github.rest.pulls.get({owner: context.repo.owner, repo: context.repo.repo, pull_number: context.payload.pull_request?.number || context.issue.number})).data;
39+
if (!pr) core.setFailed('No PR context');
40+
const hasLabel = pr.labels.some(l => l.name === 'pass QA ready for deploy');
41+
if (!hasLabel) core.setFailed('Required label not present: pass QA ready for deploy');
42+
// Check approvals
43+
const reviews = await github.rest.pulls.listReviews({owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number});
44+
const approved = reviews.data.some(r => r.state === 'APPROVED');
45+
if (!approved) core.setFailed('No approval found on the PR');
46+
core.setOutput('pr_number', pr.number.toString());
47+
- name: ⏳ Wait for required status checks to pass
48+
uses: actions/github-script@v7
49+
with:
50+
script: |
51+
const prNumber = Number(core.getInput('pr_number', { required: false })) || ${{ steps.eval.outputs.pr_number || '0' }};
52+
const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber });
53+
const ref = pr.head.sha;
54+
const start = Date.now();
55+
const timeoutMs = 60*60*1000; // 60 minutes
56+
const sleep = ms => new Promise(r => setTimeout(r, ms));
57+
while (true) {
58+
const { data: combined } = await github.rest.repos.getCombinedStatusForRef({ owner: context.repo.owner, repo: context.repo.repo, ref });
59+
const checksOk = combined.state === 'success';
60+
if (checksOk) break;
61+
if (Date.now() - start > timeoutMs) throw new Error('Timeout waiting for status checks to pass');
62+
core.info(`Waiting for checks. Current state: ${combined.state}`);
63+
await sleep(15000);
64+
}
65+
- name: 📥 Checkout
66+
uses: actions/checkout@v4
67+
with:
68+
fetch-depth: 0
69+
- name: 🔀 Merge PR immediately
70+
id: merge
71+
uses: actions/github-script@v7
72+
with:
73+
script: |
74+
const prNumber = Number(${ { steps.eval.outputs.pr_number } });
75+
const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: prNumber });
76+
if (pr.merged) { core.setOutput('merged', 'true'); return; }
77+
const method = 'merge'; // use repo default merge method
78+
await github.rest.pulls.merge({ owner: context.repo.owner, repo: context.repo.repo, pull_number: pr.number, merge_method: method });
79+
core.setOutput('merged', 'true');
80+
- name: 📝 Read version from pubspec on master
81+
id: version
82+
run: |
83+
git fetch origin master:master
84+
git checkout master
85+
VER=$(grep '^version:' pubspec.yaml | sed 's/version: //' | tr -d ' ')
86+
echo "version=$VER" >> $GITHUB_OUTPUT
87+
88+
call-production:
89+
name: 🚀 Production Release
90+
needs: gate-and-merge
91+
if: needs.gate-and-merge.outputs.merged == 'true'
92+
uses: ./.github/workflows/production-release.yml
93+
with:
94+
version: ${{ needs.gate-and-merge.outputs.version }}
95+
skip_tests: false
96+
dry_run: false
97+
secrets: inherit

0 commit comments

Comments
 (0)