Repository navigation
646 lines (599 loc) · 31.2 KB
/
Copy pathrelease.yml
File metadata and controls
646 lines (599 loc) · 31.2 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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# git-sc のリリース。Run workflow 1 回で、版の更新 (JST の yy.m.counter) からコミットとタグ、ビルド、
# Release の作成、Homebrew の tap (owayo/homebrew-git-sc) の formula の更新、winget-pkgs (owayo.git-sc)
# への更新の PR までを行う。dry_run は次の版を計算するだけで、build 以降は走らない。
#
# 配る添付は既定の feature でビルドする (Apple Intelligence の apple-ai は含めない。ソースからの
# make install だけが macOS で有効にする)。
#
# build の matrix は ci.yml の build と同じ組にする (dry_run では build が走らないので、配布物を
# 作れることは ci.yml の build で確かめる)。
name: Release
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry run (compute the next version only; no commit, tag or release)"
type: boolean
default: false
# 書き込みはジョブ単位で与える (版のコミットとタグの push、Release の作成)
permissions:
contents: read
# 同時に 2 つのリリースを走らせない (同じ番号を 2 回採る)。走っているものは止めない
concurrency:
group: release
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
BINARY_NAME: git-sc
jobs:
prepare-release:
name: Prepare release
runs-on: ubuntu-latest
permissions:
contents: write
# push できるジョブなので、mise.toml に cargo update で使わないツールを足したら
# env の MISE_DISABLE_TOOLS で外す (今の mise.toml は rust だけ)
outputs:
version: ${{ steps.bump.outputs.version }}
tag: ${{ steps.bump.outputs.tag }}
steps:
# 本番は既定のブランチからだけ出す (ほかのブランチから出すと、既定のブランチに無いコミットにタグが付く)。
# dry_run はどのブランチでも動くので、workflow の変更をブランチで試せる
- name: Check the branch
if: ${{ !inputs.dry_run }}
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
if [ "${GITHUB_REF}" != "refs/heads/${DEFAULT_BRANCH}" ]; then
echo "::error::Run the release from ${DEFAULT_BRANCH} (this run is on ${GITHUB_REF})"
exit 1
fi
- uses: actions/checkout@v7
# cargo update を mise.toml の版の Rust で実行する (無いとランナーに最初から入っている Rust で動く)
- uses: jdx/mise-action@v4
with:
minimum_release_age: 14d
cache: false
- name: Bump the version
id: bump
run: |
set -euo pipefail
# 版は yy.m.counter。年は 2 桁 (Windows の MSI は major が 255 以下)。月はゼロ埋めしない
# (SemVer は数値の先頭の 0 を禁じ、Cargo は 26.08.100 を拒む)。連番は 100 から (桁がそろう)。
# 採番は JST で切る。ランナーは UTC なので、素の date では JST の月初 0〜9 時のリリースが
# 前の月の連番になる (実例: 9 月最初のリリースが v26.8.113 になった)
YEAR=$(TZ=Asia/Tokyo date +%y)
MONTH=$(TZ=Asia/Tokyo date +%-m)
CURRENT=$(awk -F '"' '/^version = /{ print $2; exit }' Cargo.toml)
echo "Current version: ${CURRENT}"
COUNTER=100
if [[ "${CURRENT}" =~ ^([0-9]{2,4})\.([0-9]{1,2})\.([0-9]+)$ ]]; then
# 4 桁の年 (2026.9.105) の版からも続けて採番する
if [ "${BASH_REMATCH[1]#20}" = "${YEAR}" ] && [ "${BASH_REMATCH[2]}" = "${MONTH}" ]; then
COUNTER=$((BASH_REMATCH[3] + 1))
fi
fi
VERSION="${YEAR}.${MONTH}.${COUNTER}"
TAG="v${VERSION}"
echo "Next version: ${VERSION}"
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" > /dev/null; then
echo "::error::Tag ${TAG} already exists. Check the version in Cargo.toml against the existing tags"
exit 1
fi
# [package] の version (ファイルで最初の `version = ` の行) だけを書き換える (依存の version は触らない)
sed -i "0,/^version = \"[^\"]*\"/s//version = \"${VERSION}\"/" Cargo.toml
test "$(awk -F '"' '/^version = /{ print $2; exit }' Cargo.toml)" = "${VERSION}"
# Cargo.lock は自分のパッケージの版だけを合わせる (lockfile を作り直すと全依存が最新へ
# 解決し直され、CI で確かめていない版が配布物に入る)
cargo update --workspace
git diff --stat
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
- name: Commit and tag
if: ${{ !inputs.dry_run }}
env:
TAG: ${{ steps.bump.outputs.tag }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Cargo.toml Cargo.lock
git commit -m "Release ${TAG}"
git tag -a "${TAG}" -m "Release ${TAG}"
# ブランチとタグを 1 回で送る。--atomic で、ブランチが先に進んでいて片方だけが通ることを防ぐ
git push --atomic origin "HEAD:refs/heads/${DEFAULT_BRANCH}" "refs/tags/${TAG}"
- name: Dry run
if: ${{ inputs.dry_run }}
env:
TAG: ${{ steps.bump.outputs.tag }}
run: |
echo "Dry run: would release ${TAG} (no commit, tag or release)"
git diff -- Cargo.toml Cargo.lock
build:
name: Build (${{ matrix.target }})
needs: prepare-release
if: ${{ !inputs.dry_run }}
runs-on: ${{ matrix.os }}
outputs:
# 添付の数 (matrix の行ごとに 1 つ)。release のジョブが、集めた添付の数と照らす
assets: ${{ strategy.job-total }}
strategy:
fail-fast: false
matrix:
# ci.yml の build と同じ組。Linux ARM64 は arm64 のランナーでそのままビルドする (cross を使わない)
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
# Windows のランナーは既定で LF を CRLF に変えて checkout する。ci.yml の build と同じく LF のまま取り、
# include_str! で埋め込むファイルの改行を CI で確かめたものとそろえる
- name: Keep LF line endings
run: git config --global core.autocrlf false
- uses: actions/checkout@v7
with:
ref: ${{ needs.prepare-release.outputs.tag }}
persist-credentials: false
# ビルドのキャッシュ (rust-cache) は使わない。配布物を作るジョブがキャッシュを読むと、キャッシュを
# 書けるほかのジョブの汚染が配布物に入る。Rust の版と mise.toml の [env] は mise-action が渡す
- uses: jdx/mise-action@v4
with:
minimum_release_age: 14d
cache: false
# mise.toml の rust はホストのツールチェーンと components だけを入れるので、配布のターゲットを足す
- name: Add the Rust target
run: rustup target add ${{ matrix.target }}
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }}
# 添付の名前は <bin>-<target>.tar.gz (Windows は .zip)。中身はバイナリだけを最上位に置く
# (Homebrew の formula と winget のマニフェストがこの形を前提にする)
- name: Package
id: package
shell: bash
env:
TARGET: ${{ matrix.target }}
TAR_GZ: ${{ env.BINARY_NAME }}-${{ matrix.target }}.tar.gz
ZIP: ${{ env.BINARY_NAME }}-${{ matrix.target }}.zip
run: |
set -euo pipefail
cd "target/${TARGET}/release"
if [ "${RUNNER_OS}" = "Windows" ]; then
7z a "../../../${ZIP}" "${BINARY_NAME}.exe"
echo "asset=${ZIP}" >> "$GITHUB_OUTPUT"
else
tar -czf "../../../${TAR_GZ}" "${BINARY_NAME}"
echo "asset=${TAR_GZ}" >> "$GITHUB_OUTPUT"
fi
# archive: false で添付のファイルをそのまま上げる (artifact の名前はファイル名になる)。zip を展開させない
# ため、release のジョブは skip-decompress: true で落とす
- uses: actions/upload-artifact@v7
with:
path: ${{ steps.package.outputs.asset }}
archive: false
if-no-files-found: error
release:
name: Release
needs: [prepare-release, build]
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
assets: ${{ steps.files.outputs.assets }}
steps:
# 添付を build が上げた形 (tar.gz / zip) のまま集める。download-artifact@v8 は content-type が zip の
# artifact を展開するので、skip-decompress が無いと Windows の .zip が展開されて <bin>.exe だけが残る。
# skip-decompress はほかの artifact も zip の塊のまま落とすので、pattern で添付だけに絞る
- uses: actions/download-artifact@v8
with:
path: release
pattern: ${{ env.BINARY_NAME }}-*
merge-multiple: true
skip-decompress: true
- name: Write SHA256SUMS
id: files
env:
EXPECTED: ${{ needs.build.outputs.assets }}
run: |
set -euo pipefail
cd release
# 添付は build の target ごとに 1 つ: <bin>-<target>.tar.gz か Windows の <bin>-<target>.zip の、空でない
# 通常のファイル。展開された .exe・ディレクトリ・隠しファイルが混ざったり数が合わなかったりしたら、
# Release を作る前に止める
shopt -s nullglob dotglob
files=(*)
for f in "${files[@]}"; do
case "${f}" in
"${BINARY_NAME}"-*-windows-*.zip | "${BINARY_NAME}"-*.tar.gz) ;;
*)
echo "::error::Unexpected entry in release/: ${f} (expected ${BINARY_NAME}-<target>.tar.gz or .zip)"
exit 1
;;
esac
if [ ! -f "${f}" ] || [ -L "${f}" ] || [ ! -s "${f}" ]; then
echo "::error::${f} in release/ is not a non-empty regular file"
exit 1
fi
done
if [ "${#files[@]}" -ne "${EXPECTED}" ]; then
echo "::error::release/ has ${#files[@]} assets, expected ${EXPECTED} (one per build target)"
exit 1
fi
sha256sum -- "${files[@]}" > SHA256SUMS
cat SHA256SUMS
echo "assets=$(printf '%s ' *)" >> "$GITHUB_OUTPUT"
# タイトルはタグ名だけ。本文は導入の手順で、変更点は generate_release_notes が後ろに足す
- name: Create the release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.prepare-release.outputs.tag }}
name: ${{ needs.prepare-release.outputs.tag }}
body: |
## Install
### Homebrew (macOS / Linux)
```bash
brew install ${{ github.repository_owner }}/git-sc/git-sc
```
### winget (Windows)
```powershell
winget install owayo.git-sc
```
Open a new terminal afterwards so that the updated `PATH` takes effect.
### Download
Download `${{ env.BINARY_NAME }}-<target>.tar.gz` for your platform from the assets below (`.zip` on Windows), extract it and put `${{ env.BINARY_NAME }}` on your `PATH`. `SHA256SUMS` lists the checksums of all assets.
```bash
curl -fLO https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/${{ env.BINARY_NAME }}-aarch64-apple-darwin.tar.gz
tar -xzf ${{ env.BINARY_NAME }}-aarch64-apple-darwin.tar.gz
```
generate_release_notes: true
files: release/*
fail_on_unmatched_files: true
# 公開した添付が release/ と名前・大きさ・状態まで一致するか (アップロードの取りこぼしを見つける)。
# GitHub が添付の SHA-256 (digest) を返していれば、それも照らす
- name: Verify the published assets
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.prepare-release.outputs.tag }}
run: |
set -euo pipefail
gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json assets > "${RUNNER_TEMP}/assets.json"
find release -type f -printf '%f %s uploaded\n' | LC_ALL=C sort > "${RUNNER_TEMP}/expected.txt"
jq -r '.assets[] | "\(.name) \(.size) \(.state)"' "${RUNNER_TEMP}/assets.json" | LC_ALL=C sort > "${RUNNER_TEMP}/published.txt"
if ! diff -u "${RUNNER_TEMP}/expected.txt" "${RUNNER_TEMP}/published.txt"; then
echo "::error::The published assets differ from the files in release/"
exit 1
fi
jq -r '.assets[] | select(.digest != null) | "\(.digest | ltrimstr("sha256:")) \(.name)"' "${RUNNER_TEMP}/assets.json" > "${RUNNER_TEMP}/published.sha256"
if [ -s "${RUNNER_TEMP}/published.sha256" ]; then
(cd release && sha256sum --check --strict --quiet "${RUNNER_TEMP}/published.sha256")
else
echo "::warning::GitHub returned no asset digests; skipped the SHA-256 comparison"
fi
# ---- Homebrew ------------------------------------------------------------------------------
# tap (homebrew-git-sc) の formula を、この版の添付の tar.gz を直接指す形に書き直す (bottle も
# ソースからのビルドも使わない)。tap は別のリポジトリなので GITHUB_TOKEN では push できず、
# GitHub App のトークンを使う (App の Client ID は Variables の APP_CLIENT_ID、秘密鍵は Secrets の PRIVATE_KEY)。
# GITHUB_TOKEN で作った Release は on: release のワークフローを起動しないので、同じワークフローの後続にする
update-homebrew:
name: Update the Homebrew tap
needs: [prepare-release, release]
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
permissions:
contents: read
env:
TAG: ${{ needs.prepare-release.outputs.tag }}
outputs:
# updated / unchanged / not-configured (空ならこのジョブが途中で落ちた)
state: ${{ steps.formula.outputs.state || steps.app.outputs.state }}
tap: ${{ github.repository_owner }}/homebrew-git-sc
steps:
# App の設定が無ければリリース全体を落とさずに飛ばす (jobs.<id>.if では secrets を読めない)。
# 値そのものは渡さず、有無だけを式で渡す
- name: Check the GitHub App settings
id: app
env:
HAS_CLIENT_ID: ${{ vars.APP_CLIENT_ID != '' }}
HAS_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY != '' }}
run: |
if [ "${HAS_CLIENT_ID}" = "true" ] && [ "${HAS_PRIVATE_KEY}" = "true" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::The GitHub App for the tap is not configured (variable APP_CLIENT_ID and secret PRIVATE_KEY); skipped the Homebrew tap"
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "state=not-configured" >> "$GITHUB_OUTPUT"
fi
# 全部の添付を取り直さず、公開した SHA256SUMS から formula が指す 4 つの sha256 を読む
- name: Read the checksums
id: sums
if: steps.app.outputs.enabled == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release download "${TAG}" --repo "${GITHUB_REPOSITORY}" --pattern SHA256SUMS --dir "${RUNNER_TEMP}"
for target in aarch64-apple-darwin x86_64-apple-darwin aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu; do
asset="${BINARY_NAME}-${target}.tar.gz"
sum=$(awk -v f="${asset}" '$2 == f { print $1 }' "${RUNNER_TEMP}/SHA256SUMS")
if [ "${#sum}" -ne 64 ]; then
echo "::error::SHA256SUMS has no entry for ${asset}"
exit 1
fi
echo "${target//-/_}=${sum}" >> "$GITHUB_OUTPUT"
done
# App のトークンは tap を触る直前に作る (トークンに手が届く時間を短くする)。対象は tap だけ、権限は contents: write だけ
- name: Create a token for the tap
id: app-token
if: steps.app.outputs.enabled == 'true'
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: homebrew-git-sc
permission-contents: write
- uses: actions/checkout@v7
if: steps.app.outputs.enabled == 'true'
with:
repository: ${{ github.repository_owner }}/homebrew-git-sc
token: ${{ steps.app-token.outputs.token }}
path: tap
# formula は添付の tar.gz を OS と CPU ごとに直接指す (bottle もソースの url も持たない)。
# 変化が無ければ push しない (再実行しても壊れない)
- name: Update the formula
id: formula
if: steps.app.outputs.enabled == 'true'
working-directory: tap
env:
HOMEPAGE: ${{ github.server_url }}/${{ github.repository }}
URL: ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}
SHA_MACOS_ARM: ${{ steps.sums.outputs.aarch64_apple_darwin }}
SHA_MACOS_INTEL: ${{ steps.sums.outputs.x86_64_apple_darwin }}
SHA_LINUX_ARM: ${{ steps.sums.outputs.aarch64_unknown_linux_gnu }}
SHA_LINUX_INTEL: ${{ steps.sums.outputs.x86_64_unknown_linux_gnu }}
run: |
set -euo pipefail
# Homebrew のクラス名の規則 (Formulary.class_s): 先頭と - _ . の直後を大文字にしてつなぐ (git-sc → GitSc)
CLASS=$(printf '%s' "${BINARY_NAME}" | sed -E 's/(^|[-_.])([a-z0-9])/\U\2/g')
mkdir -p Formula
# シェルが展開するのは ${...} だけ (Ruby の #{...} はそのまま残る)。版は URL の /download/v<版>/ から
# Homebrew が読むので version は書かない (書くと brew audit --strict が冗長と指摘する)
cat > Formula/git-sc.rb <<FORMULA
class ${CLASS} < Formula
desc "AI-powered smart commit message generator for coding agents"
homepage "${HOMEPAGE}"
license "MIT"
on_macos do
if Hardware::CPU.arm?
url "${URL}/${BINARY_NAME}-aarch64-apple-darwin.tar.gz"
sha256 "${SHA_MACOS_ARM}"
else
url "${URL}/${BINARY_NAME}-x86_64-apple-darwin.tar.gz"
sha256 "${SHA_MACOS_INTEL}"
end
end
on_linux do
if Hardware::CPU.arm?
url "${URL}/${BINARY_NAME}-aarch64-unknown-linux-gnu.tar.gz"
sha256 "${SHA_LINUX_ARM}"
else
url "${URL}/${BINARY_NAME}-x86_64-unknown-linux-gnu.tar.gz"
sha256 "${SHA_LINUX_INTEL}"
end
end
def install
bin.install "${BINARY_NAME}"
end
test do
assert_match version.to_s, shell_output("#{bin}/${BINARY_NAME} --version")
end
end
FORMULA
# 壊れた Ruby (desc の中の引用符、数字で始まるクラス名など) を tap に push しない
ruby -c Formula/git-sc.rb
# 新しく作った formula も差分に入るよう、add してから比べる
git add Formula/git-sc.rb
if git diff --cached --quiet; then
echo "The formula already points at ${TAG}"
echo "state=unchanged" >> "$GITHUB_OUTPUT"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "${BINARY_NAME} ${TAG}"
git push
echo "state=updated" >> "$GITHUB_OUTPUT"
# ---- winget --------------------------------------------------------------------------------
# winget-pkgs へ更新の PR を出す。トークンは classic PAT (public_repo) を Secrets の WINGET_TOKEN に置く。
# PR を出す前に、fork (<owner>/winget-pkgs) を microsoft/winget-pkgs に同期する。upstream が .github/workflows/ を
# 変えた後の同期には Workflows の書き込み権限が要り、public_repo の PAT では winget-releaser の同期が UpdateRef の
# 権限不足で落ちる。同期だけを、fork にだけ入れた GitHub App のトークンで先に行う (App の Client ID は Variables の
# WINGET_SYNC_APP_CLIENT_ID、秘密鍵は Secrets の WINGET_SYNC_APP_PRIVATE_KEY)。PAT に workflow の scope は
# 足さない (漏れたときに、持ち主の全 public リポジトリの workflow を書き換えられる)。
# winget-releaser は「既に winget-pkgs にある版の更新」しかできないので、初版は手で出した
# (PackageIdentifier は owayo.git-sc。大文字小文字を区別し、変えると別のパッケージになる)
publish-winget:
name: Publish to winget
needs: [prepare-release, release]
if: ${{ !inputs.dry_run }}
runs-on: ubuntu-latest
env:
WINGET_IDENTIFIER: owayo.git-sc
outputs:
submitted: ${{ steps.submit.outcome == 'success' }}
# no-token / no-sync-app / not-registered / registered / unknown (提出しなかった理由を release-summary に渡す)
state: ${{ steps.registration.outputs.state || steps.token.outputs.state }}
steps:
# トークンか、fork を同期する App の設定が無ければ、リリース全体を落とさずに飛ばす (jobs.<id>.if では secrets を
# 読めない)。値そのものは渡さず、有無だけを式で渡す
- name: Check the winget token
id: token
env:
HAS_TOKEN: ${{ secrets.WINGET_TOKEN != '' }}
HAS_SYNC_CLIENT_ID: ${{ vars.WINGET_SYNC_APP_CLIENT_ID != '' }}
HAS_SYNC_PRIVATE_KEY: ${{ secrets.WINGET_SYNC_APP_PRIVATE_KEY != '' }}
run: |
if [ "${HAS_TOKEN}" != "true" ]; then
echo "::warning::The secret WINGET_TOKEN is not set; skipped the winget-pkgs submission"
echo "available=false" >> "$GITHUB_OUTPUT"
echo "state=no-token" >> "$GITHUB_OUTPUT"
elif [ "${HAS_SYNC_CLIENT_ID}" != "true" ] || [ "${HAS_SYNC_PRIVATE_KEY}" != "true" ]; then
echo "::warning::The GitHub App for the winget fork is not configured (variable WINGET_SYNC_APP_CLIENT_ID and secret WINGET_SYNC_APP_PRIVATE_KEY); skipped the winget-pkgs submission"
echo "available=false" >> "$GITHUB_OUTPUT"
echo "state=no-sync-app" >> "$GITHUB_OUTPUT"
else
echo "available=true" >> "$GITHUB_OUTPUT"
fi
# 初版のマニフェストはモデレータの承認待ちで数日かかることがあり、その間 winget-releaser は
# "Package ... does not exist in the winget-pkgs repository" で落ちる。未登録はこの期間の正常な状態なので、
# 提出の前に登録を確かめて飛ばす。continue-on-error は PAT の失効や installers-regex の誤りまで緑にするので使わない
- name: Check the winget-pkgs registration
id: registration
if: steps.token.outputs.available == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# winget-releaser と同じ判定のパス: manifests/<頭文字の小文字>/<Publisher>/<Package>
INITIAL=$(printf '%s' "${WINGET_IDENTIFIER}" | cut -c1 | tr '[:upper:]' '[:lower:]')
MANIFEST_DIR="manifests/${INITIAL}/${WINGET_IDENTIFIER//.//}"
# 接続の失敗でステップごと落ちないよう、HTTP の状態だけで判断する
http_status() {
curl --silent --output /dev/null --write-out '%{http_code}' "$@" || true
}
STATUS=$(http_status \
--header "Authorization: Bearer ${GH_TOKEN}" \
--header "Accept: application/vnd.github+json" \
"https://api.github.com/repos/microsoft/winget-pkgs/contents/${MANIFEST_DIR}")
# API が使えないとき (rate limit など) は、winget-releaser と同じ web のパスで確かめ直す
if [ "${STATUS}" != "200" ] && [ "${STATUS}" != "404" ]; then
STATUS=$(http_status "https://github.com/microsoft/winget-pkgs/tree/master/${MANIFEST_DIR}")
fi
case "${STATUS}" in
200)
echo "registered=true" >> "$GITHUB_OUTPUT"
echo "state=registered" >> "$GITHUB_OUTPUT"
;;
404)
echo "::notice::${WINGET_IDENTIFIER} is not in winget-pkgs yet (the first manifest is waiting for a moderator); skipped the submission"
echo "registered=false" >> "$GITHUB_OUTPUT"
echo "state=not-registered" >> "$GITHUB_OUTPUT"
;;
*)
# 判定できないときは提出を試す (飛ばす側に倒すと、判定が失敗し続けたとき更新が黙って止まる)
echo "::warning::Could not check the winget-pkgs registration (HTTP ${STATUS}); submitting anyway"
echo "registered=true" >> "$GITHUB_OUTPUT"
echo "state=unknown" >> "$GITHUB_OUTPUT"
;;
esac
# App のトークンは同期の直前に作る (トークンに手が届く時間を短くする)。対象は fork だけ、権限は contents と
# workflows の write だけ (upstream の workflow の変更を fork に取り込むのに workflows: write が要る)
- name: Create a token for the winget fork
id: sync-token
if: steps.registration.outputs.registered == 'true'
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.WINGET_SYNC_APP_CLIENT_ID }}
private-key: ${{ secrets.WINGET_SYNC_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: winget-pkgs
permission-contents: write
permission-workflows: write
# fork の master を upstream の master に追いつかせる (追いついていれば何も書かない)。先に同期しておけば、
# winget-releaser が提出の前に行う同期は書くものが無く、PAT の権限のままで通る
- name: Sync the winget-pkgs fork
if: steps.registration.outputs.registered == 'true'
env:
GH_TOKEN: ${{ steps.sync-token.outputs.token }}
run: |
set -euo pipefail
gh api -X POST "repos/${GITHUB_REPOSITORY_OWNER}/winget-pkgs/merge-upstream" \
-f branch=master --jq .message
- name: Submit the manifest to winget-pkgs
id: submit
if: steps.registration.outputs.registered == 'true'
uses: vedantmgoyal9/winget-releaser@v2
with:
identifier: ${{ env.WINGET_IDENTIFIER }}
version: ${{ needs.prepare-release.outputs.version }}
# workflow_dispatch では既定値 (release イベントのタグ) が使えないので明示する
release-tag: ${{ needs.prepare-release.outputs.tag }}
# 既定値は exe / msi / msix / appx だけで zip を拾わない
installers-regex: '-pc-windows-msvc\.zip$'
max-versions-to-keep: 5
token: ${{ secrets.WINGET_TOKEN }}
# ---- 結果のまとめ ------------------------------------------------------------------------
# 途中のジョブが落ちても、飛ばされても (dry_run を含む) 結果を書く
release-summary:
name: Release summary
needs: [prepare-release, build, release, update-homebrew, publish-winget]
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Write the summary
env:
DRY_RUN: ${{ inputs.dry_run }}
TAG: ${{ needs.prepare-release.outputs.tag }}
PREPARE_RESULT: ${{ needs.prepare-release.result }}
BUILD_RESULT: ${{ needs.build.result }}
RELEASE_RESULT: ${{ needs.release.result }}
ASSETS: ${{ needs.release.outputs.assets }}
HOMEBREW_RESULT: ${{ needs.update-homebrew.result }}
HOMEBREW_STATE: ${{ needs.update-homebrew.outputs.state }}
HOMEBREW_TAP: ${{ needs.update-homebrew.outputs.tap }}
WINGET_RESULT: ${{ needs.publish-winget.result }}
WINGET_SUBMITTED: ${{ needs.publish-winget.outputs.submitted }}
WINGET_STATE: ${{ needs.publish-winget.outputs.state }}
run: |
set -euo pipefail
{
if [ "${DRY_RUN}" = "true" ]; then
echo "## Dry run: ${TAG:-no version}"
echo
echo "prepare-release: ${PREPARE_RESULT}. Nothing was committed, tagged or released, and the build did not run (ci.yml builds every target)."
exit 0
fi
echo "## ${TAG:-no version}"
echo
echo "| Job | Result | Note |"
echo "|---|---|---|"
echo "| prepare-release | ${PREPARE_RESULT} | |"
echo "| build | ${BUILD_RESULT} | |"
echo "| release | ${RELEASE_RESULT} | |"
if [ -n "${HOMEBREW_RESULT:-}" ]; then
case "${HOMEBREW_STATE:-}" in
updated) note="updated ${HOMEBREW_TAP}" ;;
unchanged) note="${HOMEBREW_TAP} already had this version" ;;
not-configured) note="skipped: the GitHub App is not configured" ;;
*) note="not updated" ;;
esac
echo "| update-homebrew | ${HOMEBREW_RESULT} | ${note} |"
fi
if [ -n "${WINGET_RESULT:-}" ]; then
case "${WINGET_SUBMITTED:-}/${WINGET_STATE:-}" in
true/*) note="submitted a pull request to microsoft/winget-pkgs" ;;
*/no-token) note="skipped: the secret WINGET_TOKEN is not set" ;;
*/no-sync-app) note="skipped: the GitHub App for the winget fork is not configured" ;;
*/not-registered) note="skipped: the first manifest is not merged into winget-pkgs yet" ;;
*) note="not submitted" ;;
esac
echo "| publish-winget | ${WINGET_RESULT} | ${note} |"
fi
if [ -n "${ASSETS:-}" ]; then
echo
echo "Assets of ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}:"
echo
read -r -a assets <<< "${ASSETS}"
for asset in "${assets[@]}"; do
echo "- \`${asset}\`"
done
fi
} >> "$GITHUB_STEP_SUMMARY"