-
Notifications
You must be signed in to change notification settings - Fork 1
161 lines (145 loc) · 5.81 KB
/
Copy pathrelease.yml
File metadata and controls
161 lines (145 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# Continuous release pipeline for Homeboy Action.
#
# Triggers on push to main (immediate release) and manual dispatch.
# Checks for releasable conventional commits since the last tag. If found:
# 1. Version bump + changelog generation
# 2. Tag + push + GitHub Release creation
# 3. Post-release hook moves the floating v2 tag
#
# No binary builds, no extensions needed — homeboy reads homeboy.json
# directly for version targets and hooks.
#
# No human input needed — version is computed from commit types:
# fix: → patch, feat: → minor, BREAKING CHANGE → major
# chore:/ci:/docs:/test: → no release
name: Release
permissions:
contents: write
issues: write
pull-requests: write
on:
push:
branches: [main]
workflow_dispatch:
inputs:
dry-run:
description: 'Preview the release without making changes'
type: boolean
default: false
jobs:
# ── Step 1: Check for releasable commits ──
check:
name: Check for releasable commits
runs-on: ubuntu-latest
outputs:
should-release: ${{ steps.check.outputs.should-release }}
tooling-identity: ${{ steps.release-check.outputs.tooling-identity }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Dry-run release check
id: release-check
uses: ./
with:
commands: release
release-dry-run: 'true'
release-skip-publish: 'true'
release-skip-github-release: 'true'
# The failed-SHA marker is keyed by the resolved tooling identity, not
# just the SHA, so a CI-side fix (new homeboy binary / extension) that
# changes the resolved tooling produces a fresh key and self-heals a
# previously-blocked SHA. See homeboy-action#257.
- name: Restore failure cache
id: failure-cache
uses: actions/cache/restore@v5
with:
path: ${{ runner.temp }}/homeboy-release-last-failed
key: release-last-failed-${{ github.ref_name }}-${{ github.sha }}-${{ steps.release-check.outputs.tooling-identity }}
- name: Decide whether to release
id: check
env:
# A manual dispatch is an explicit human "retry this now" — it must
# never be silently skipped by a stale failed-SHA marker. The marker
# only guards the unattended push path against retry loops.
IS_MANUAL_DISPATCH: ${{ github.event_name == 'workflow_dispatch' }}
run: |
HEAD_SHA="$(git rev-parse HEAD)"
FAILURE_MARKER="${RUNNER_TEMP}/homeboy-release-last-failed"
if [ "${IS_MANUAL_DISPATCH}" != "true" ] && [ -f "${FAILURE_MARKER}" ]; then
LAST_FAILED="$(tr -d '[:space:]' < "${FAILURE_MARKER}")"
if [ "${HEAD_SHA}" = "${LAST_FAILED}" ]; then
echo "::notice::HEAD ${HEAD_SHA:0:8} matches last failed release attempt with the same tooling — skipping until new commits or new tooling (dispatch manually to force)"
echo "should-release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
RELEASE_VERSION="${{ steps.release-check.outputs.release-version }}"
BUMP_TYPE="${{ steps.release-check.outputs.release-bump-type }}"
if [ -z "${RELEASE_VERSION}" ]; then
echo "should-release=false" >> "$GITHUB_OUTPUT"
echo "::notice::No releasable commits at HEAD ${HEAD_SHA:0:8}"
else
echo "should-release=true" >> "$GITHUB_OUTPUT"
echo "::notice::Release dry-run predicts v${RELEASE_VERSION} (${BUMP_TYPE})"
fi
# ── Step 2: Release ──
release:
name: Release
needs: check
if: needs.check.outputs.should-release == 'true'
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
continue-on-error: true
with:
app-id: ${{ secrets.HOMEBOY_APP_ID }}
private-key: ${{ secrets.HOMEBOY_APP_PRIVATE_KEY }}
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
- uses: ./
id: release
with:
commands: release
release-dry-run: ${{ inputs.dry-run || 'false' }}
release-skip-publish: 'true'
release-skip-github-release: 'true'
app-token: ${{ steps.app-token.outputs.token || '' }}
# ── Record failed SHA to prevent retry loops ──
record-failure:
name: Record failure
needs:
- check
- release
if: ${{ always() && needs.check.outputs.should-release == 'true' && needs.release.result == 'failure' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Save failed SHA
run: git rev-parse HEAD > "${RUNNER_TEMP}/homeboy-release-last-failed"
- name: Cache failed SHA
uses: actions/cache/save@v5
with:
path: ${{ runner.temp }}/homeboy-release-last-failed
# Keyed by tooling identity so a later tooling fix produces a new
# key and the prior failure no longer matches (homeboy-action#257).
key: release-last-failed-${{ github.ref_name }}-${{ github.sha }}-${{ needs.check.outputs.tooling-identity }}
# ── Clear failure cache on success ──
clear-failure:
name: Clear failure cache
needs: release
if: ${{ needs.release.result == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Clear failure cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh cache list --json id,key --jq '.[] | select(.key | startswith("release-last-failed-${{ github.ref_name }}-")) | .id' | while read -r id; do
gh cache delete "$id" 2>/dev/null || true
done