Skip to content

Commit c102f6d

Browse files
committed
ci: replace bespoke staged-asset rollback pipeline with action-gh-release@v3 official rolling-release pattern (tag_name latest + overwrite)
1 parent 2e0c4e9 commit c102f6d

1 file changed

Lines changed: 23 additions & 206 deletions

File tree

.github/workflows/ci.yml

Lines changed: 23 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -96,216 +96,33 @@ jobs:
9696
with:
9797
name: extension-zip
9898
path: release
99-
- name: Publish asset with rollback, then move tag and verify
99+
- name: Verify artifact integrity and branch head
100100
env:
101101
GH_TOKEN: ${{ github.token }}
102-
shell: bash
103102
run: |
104103
set -euo pipefail
105-
VERSION=$(jq -r .version manifest.json)
106-
FIXED_NAME="MultiLangSwitcher-latest.zip"
107104
zip_file="$(ls release/MultiLangSwitcher-v*.zip)"
108-
expected_digest="$(cat release/digest.txt)"
109-
source_sha="$(cat release/source-sha.txt)"
110-
test "$source_sha" = "$GITHUB_SHA"
111-
test "$(sha256sum "$zip_file" | awk '{print $1}')" = "$expected_digest"
112-
113-
current_branch_sha() {
114-
gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/$GITHUB_REF_NAME" --jq '.object.sha'
115-
}
116-
117-
# concurrency 只能约束运行顺序;显式头检查同时拦截旧 run 的 Re-run。
118-
assert_current_head() {
119-
local current
120-
current="$(current_branch_sha)"
121-
if [ "$current" != "$GITHUB_SHA" ]; then
122-
echo "::error::$GITHUB_REF_NAME 已前进到 $current,拒绝用旧提交 $GITHUB_SHA 更新滚动 Release。"
123-
return 1
124-
fi
125-
}
126-
assert_current_head
127-
128-
release_api="repos/$GITHUB_REPOSITORY/releases/tags/latest"
129-
metadata_before="$RUNNER_TEMP/release-metadata-before.json"
130-
old_tag_sha=''
131-
release_id=''
132-
if gh api "$release_api" > "$RUNNER_TEMP/release-before.json" 2>/dev/null; then
133-
release_id="$(jq -r '.id' "$RUNNER_TEMP/release-before.json")"
134-
jq '{name, body, draft, prerelease}' "$RUNNER_TEMP/release-before.json" > "$metadata_before"
135-
old_tag_sha="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/latest" --jq '.object.sha' 2>/dev/null || true)"
136-
else
137-
jq -n '{name: "Latest (continuous)", body: "", draft: false, prerelease: true}' > "$metadata_before"
138-
fi
139-
140-
staged_name="staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-${GITHUB_SHA}.zip"
141-
rollback_name="rollback-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}.zip"
142-
staged_zip="release/$staged_name"
143-
cp "$zip_file" "$staged_zip"
144-
145-
asset_id_by_name() {
146-
test -n "$release_id" || return 0
147-
gh api "repos/$GITHUB_REPOSITORY/releases/$release_id" \
148-
| jq -r --arg name "$1" '[.assets[] | select(.name == $name) | .id][0] // empty'
149-
}
150-
151-
# 下载资产回本地做 sha256 比对(不依赖 GitHub 异步计算的 digest 字段,消除时序抖动)
152-
wait_for_asset_bytes() {
153-
local name="$1" expected_hex="$2" expected_id="${3:-}"
154-
local attempt asset_id
155-
for attempt in {1..15}; do
156-
asset_id="$(asset_id_by_name "$name" 2>/dev/null || true)"
157-
if [ -n "$asset_id" ] && { [ -z "$expected_id" ] || [ "$asset_id" = "$expected_id" ]; }; then
158-
rm -f "$RUNNER_TEMP/asset-check.bin"
159-
gh api -H 'Accept: application/octet-stream' \
160-
"repos/$GITHUB_REPOSITORY/releases/assets/$asset_id" > "$RUNNER_TEMP/asset-check.bin" 2>/dev/null || true
161-
if [ -s "$RUNNER_TEMP/asset-check.bin" ] && \
162-
[ "$(sha256sum "$RUNNER_TEMP/asset-check.bin" | awk '{print $1}')" = "$expected_hex" ]; then
163-
printf '%s\n' "$asset_id"
164-
return 0
165-
fi
166-
fi
167-
sleep 2
168-
done
169-
echo "::error::等待 Release 资产 $name 下载校验超时。" >&2
170-
return 1
171-
}
172-
173-
staged_id=''
174-
old_asset_id="$(asset_id_by_name "$FIXED_NAME")"
175-
test -z "$(asset_id_by_name "$staged_name")"
176-
test -z "$(asset_id_by_name "$rollback_name")"
177-
178-
rollback_publish() {
179-
local status="$1"
180-
local cleanup_id fixed_id old_name current_tag
181-
trap - ERR INT TERM
182-
set +e
183-
echo '::warning::滚动 Release 发布失败,正在恢复上一个已验证状态。'
184-
185-
cleanup_id="$staged_id"
186-
if [ -z "$cleanup_id" ]; then
187-
cleanup_id="$(asset_id_by_name "$staged_name")"
188-
fi
189-
if [ -n "$cleanup_id" ] && [ "$cleanup_id" != "$old_asset_id" ]; then
190-
gh api --method DELETE "repos/$GITHUB_REPOSITORY/releases/assets/$cleanup_id" >/dev/null
191-
fi
192-
193-
if [ -n "$old_asset_id" ]; then
194-
old_name="$(gh api "repos/$GITHUB_REPOSITORY/releases/assets/$old_asset_id" --jq '.name' 2>/dev/null)"
195-
if [ -n "$old_name" ] && [ "$old_name" != "$FIXED_NAME" ]; then
196-
fixed_id="$(asset_id_by_name "$FIXED_NAME")"
197-
if [ -n "$fixed_id" ] && [ "$fixed_id" != "$old_asset_id" ]; then
198-
gh api --method DELETE "repos/$GITHUB_REPOSITORY/releases/assets/$fixed_id" >/dev/null
199-
fi
200-
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/assets/$old_asset_id" \
201-
-f name="$FIXED_NAME" >/dev/null
202-
fi
203-
fi
204-
205-
if [ -n "$release_id" ]; then
206-
gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$release_id" \
207-
--input "$metadata_before" >/dev/null
208-
fi
209-
if [ -n "$old_tag_sha" ]; then
210-
gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/latest" \
211-
-f sha="$old_tag_sha" -F force=true >/dev/null
212-
current_tag="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/latest" --jq '.object.sha' 2>/dev/null)"
213-
if [ "$current_tag" != "$old_tag_sha" ]; then
214-
echo '::error::tag 回滚未恢复,需要人工检查。'
215-
fi
216-
fi
217-
exit "$status"
218-
}
219-
trap 'rollback_publish $?' ERR
220-
trap 'rollback_publish 130' INT
221-
trap 'rollback_publish 143' TERM
222-
223-
# 失败定位辅助:每个关键步骤出错时打印步骤名(此前裸失败时无报错行可查)
224-
step() {
225-
echo "::group::$*"
226-
local rc=0
227-
"$@" || rc=$?
228-
echo "::endgroup::"
229-
if [ "$rc" -ne 0 ]; then echo "::error::FAILED AT STEP: $* (exit $rc)"; fi
230-
return "$rc"
231-
}
232-
233-
# 首次发布:创建 prerelease;后续:上传暂存资产并校验摘要。
234-
if [ -z "$release_id" ]; then
235-
gh release create latest --repo "$GITHUB_REPOSITORY" \
236-
--title "Latest (continuous)" --prerelease --notes "滚动最新构建渠道。"
237-
release_id="$(gh api "$release_api" --jq '.id')"
238-
test -n "$release_id"
239-
fi
240-
241-
step gh release upload latest "$staged_zip" --repo "$GITHUB_REPOSITORY"
242-
# 上传后短暂沉降,降低资产列表/下载端点的可见性延迟
243-
sleep 5
244-
staged_id="$(wait_for_asset_bytes "$staged_name" "$expected_digest")" || {
245-
echo "::error::FAILED AT STEP: wait_for_asset_bytes staged"
246-
exit 1
247-
}
248-
249-
# 上传暂存资产可能耗时;在触碰旧固定资产前再核对一次分支头。
250-
assert_current_head
251-
252-
# 暂存资产完整后,旧固定资产改回滚名,新资产切换为固定名。
253-
if [ -n "$old_asset_id" ]; then
254-
step gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/assets/$old_asset_id" \
255-
-f name="$rollback_name" >/dev/null
256-
fi
257-
step gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/assets/$staged_id" \
258-
-f name="$FIXED_NAME" >/dev/null
259-
260-
notes="$RUNNER_TEMP/release-notes.md"
261-
{
262-
echo "自动构建的最新扩展包(滚动测试渠道,与正式版 Release 互不冲突)。"
263-
echo "- 版本:v${VERSION}"
264-
echo "- 提交:${GITHUB_SHA}"
265-
echo "- 分支:${GITHUB_REF_NAME}"
266-
} > "$notes"
267-
gh release edit latest --repo "$GITHUB_REPOSITORY" \
268-
--title "Latest v${VERSION} (${GITHUB_SHA::7})" \
269-
--notes-file "$notes" \
270-
--prerelease
271-
272-
# 新资产和发布说明就绪后才移动 tag。
273-
assert_current_head
274-
gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/latest" \
275-
-f sha="$GITHUB_SHA" -F force=true >/dev/null || \
276-
gh api --method POST "repos/$GITHUB_REPOSITORY/git/refs" \
277-
-f ref="refs/tags/latest" -f sha="$GITHUB_SHA" >/dev/null
278-
279-
actual_tag="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/latest" --jq '.object.sha')"
280-
test "$actual_tag" = "$GITHUB_SHA"
281-
282-
asset_id="$(wait_for_asset_bytes "$FIXED_NAME" "$expected_digest" "$staged_id")"
283-
test "$asset_id" = "$staged_id"
284-
gh api -H 'Accept: application/octet-stream' \
285-
"repos/$GITHUB_REPOSITORY/releases/assets/$asset_id" > "$RUNNER_TEMP/published.zip"
286-
test "$(sha256sum "$RUNNER_TEMP/published.zip" | awk '{print $1}')" = "$expected_digest"
287-
288-
# 若发布期间分支已前进,就在旧资产仍可恢复时主动回滚本次结果。
289-
assert_current_head
290-
291-
# 新状态已完整核验,之后只清理旧副本。
292-
trap - ERR INT TERM
293-
if [ -n "$old_asset_id" ]; then
294-
gh api --method DELETE "repos/$GITHUB_REPOSITORY/releases/assets/$old_asset_id" >/dev/null
295-
fi
296-
mapfile -t orphan_asset_ids < <(
297-
gh api "repos/$GITHUB_REPOSITORY/releases/$release_id" \
298-
| jq -r '.assets[] | select(.name | test("^(staging|rollback)-.*\\.zip$")) | .id'
299-
)
300-
for orphan_id in "${orphan_asset_ids[@]}"; do
301-
gh api --method DELETE "repos/$GITHUB_REPOSITORY/releases/assets/$orphan_id" >/dev/null
302-
done
303-
304-
final_count="$(
305-
gh api "repos/$GITHUB_REPOSITORY/releases/$release_id" \
306-
| jq --arg name "$FIXED_NAME" '[.assets[] | select(.name == $name)] | length'
307-
)"
308-
test "$final_count" = '1'
105+
test "$(cat release/digest.txt)" = "$(sha256sum "$zip_file" | awk '{print $1}')"
106+
test "$(cat release/source-sha.txt)" = "$GITHUB_SHA"
107+
# 过期 run(re-run 或并发)不接触线上 Release
108+
current="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/$GITHUB_REF_NAME" --jq '.object.sha')"
109+
test "$current" = "$GITHUB_SHA" || { echo "::error::$GITHUB_REF_NAME 已前进,拒绝用旧提交发布。"; exit 1; }
110+
cp "$zip_file" release/MultiLangSwitcher-latest.zip
111+
- name: Move latest tag to HEAD
112+
run: |
113+
git config user.name "github-actions[bot]"
114+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
115+
git tag -fa latest -m "Rolling latest build"
116+
git push -f origin latest
117+
- uses: softprops/action-gh-release@c12583777ecdfd3be55c69cf75464299dc01057e # v3
118+
with:
119+
tag_name: latest
120+
name: Latest (continuous)
121+
body: |
122+
自动构建的最新扩展包(滚动测试渠道,与正式版 Release 互不冲突)。
123+
提交:${{ github.sha }}
124+
prerelease: true
125+
files: release/MultiLangSwitcher-latest.zip
309126

310127
release:
311128
name: Publish tagged release
@@ -333,7 +150,7 @@ jobs:
333150
zip_file="$(ls release/MultiLangSwitcher-v*.zip)"
334151
test "$(sha256sum "$zip_file" | awk '{print $1}')" = "$(cat release/digest.txt)"
335152
test "$(cat release/source-sha.txt)" = "$GITHUB_SHA"
336-
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
153+
- uses: softprops/action-gh-release@c12583777ecdfd3be55c69cf75464299dc01057e # v3
337154
with:
338155
files: release/MultiLangSwitcher-v*.zip
339156
generate_release_notes: true

0 commit comments

Comments
 (0)