Repository navigation
279 lines (252 loc) · 10.3 KB
/
Copy pathrelease.yml
File metadata and controls
279 lines (252 loc) · 10.3 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
name: Release
# 打 v* tag 时构建三平台产物并发布 Release;手动触发只构建、不发布。
# 未签名产物不会自动更新,也不伪装成已签名:Release 正文明确写清楚。
on:
push:
tags: ["v*"]
workflow_dispatch:
permissions:
contents: write
jobs:
preflight:
name: Preflight (tag / version)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v7
with:
node-version: "22.22.2"
# 手动触发没有 tag,版本以 package.json 为准;打 tag 时必须三者一致,
# 否则产物文件名、应用内版本和 Release 标题会各说各话。
- name: Verify tag matches package version
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
pkg_version="$(node -p "require('./package.json').version")"
conf_version="$(node -p "require('./src-tauri/tauri.conf.json').version || ''")"
if [ -z "$conf_version" ]; then
conf_version="$(awk '/^\[package\]/{inpkg=1;next} /^\[/{inpkg=0} inpkg && /^version[[:space:]]*=/{gsub(/["[:space:]]/,"",$3); print $3; exit}' src-tauri/Cargo.toml)"
fi
echo "tag=$tag package.json=$pkg_version tauri=$conf_version"
if [ "$tag" != "$pkg_version" ]; then
echo "::error::发布 tag 与 package.json version 不一致:tag=$tag package=$pkg_version"
exit 1
fi
if [ "$conf_version" != "$pkg_version" ]; then
echo "::error::构建版本与 package.json version 不一致:tauri/Cargo=$conf_version package=$pkg_version"
exit 1
fi
build:
needs: preflight
name: Build (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
bundle: nsis
artifact: workbench-windows-x64
- os: ubuntu-latest
bundle: deb
artifact: workbench-linux-amd64
- os: macos-latest
bundle: app
artifact: workbench-macos
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v7
with:
node-version: "22.22.2"
cache: npm
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux system deps
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \
libayatana-appindicator3-dev patchelf libssl-dev pkg-config
- run: npm ci
- name: Build bundle
run: npx tauri build --bundles ${{ matrix.bundle }}
- name: Collect assets
shell: bash
run: |
mkdir -p release-assets
shopt -s nullglob
for f in src-tauri/target/release/bundle/nsis/*-setup.exe \
src-tauri/target/release/bundle/deb/*.deb; do
cp "$f" release-assets/
done
if [ -d src-tauri/target/release/bundle/macos ]; then
for d in src-tauri/target/release/bundle/macos/*.app; do
(cd src-tauri/target/release/bundle/macos && zip -qry "$OLDPWD/release-assets/$(basename "$d" .app).zip" "$(basename "$d")")
done
fi
ls -la release-assets
# 打包步骤退出 0 不代表产物真的落地:bundle 目标写错、产物被清理、
# 打包器改名都会留下空目录。这里按平台断言「恰好一份、非空、版本号
# 出现在文件名里」,失败即非 0。
- name: Verify assets
shell: bash
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
echo "package version: $version"
expect_one() {
local pattern="$1" label="$2"
shopt -s nullglob
local matches=( $pattern )
shopt -u nullglob
if [ "${#matches[@]}" -ne 1 ]; then
echo "::error::期望恰好 1 个 ${label}(${pattern}),实际 ${#matches[@]} 个"
ls -la release-assets || true
exit 1
fi
FOUND="${matches[0]}"
local bytes
bytes=$(wc -c < "$FOUND" | tr -d ' ')
if [ "$bytes" -le 0 ]; then
echo "::error::${label} 是空文件:$FOUND"
exit 1
fi
echo "ok ${label}: $FOUND (${bytes} bytes)"
}
case "${{ matrix.bundle }}" in
nsis)
expect_one "release-assets/*-setup.exe" "NSIS 安装包"
case "$FOUND" in
*"$version"*) echo "ok 文件名包含版本号" ;;
*) echo "::error::NSIS 文件名不含版本 ${version}:${FOUND}"; exit 1 ;;
esac
;;
deb)
expect_one "release-assets/*.deb" "deb 包"
case "$FOUND" in
*"$version"*) echo "ok 文件名包含版本号" ;;
*) echo "::error::deb 文件名不含版本 ${version}:${FOUND}"; exit 1 ;;
esac
;;
app)
app="$(ls -d src-tauri/target/release/bundle/macos/*.app 2>/dev/null | head -1 || true)"
if [ -z "${app}" ] || [ ! -d "${app}" ]; then
echo "::error::.app 目录不存在"
ls -la src-tauri/target/release/bundle/macos 2>/dev/null || true
exit 1
fi
if [ ! -s "${app}/Contents/Info.plist" ]; then
echo "::error::缺少 Info.plist:${app}"
exit 1
fi
# 以 Info.plist 声明的 CFBundleExecutable 为准,确认主程序真的
# 存在且非空;只检查 .app 目录存在会把空壳当成功。
exe_name=$(awk '/<key>CFBundleExecutable<\/key>/{getline; gsub(/.*<string>|<\/string>.*/,""); print; exit}' "${app}/Contents/Info.plist")
if [ -z "${exe_name}" ]; then
echo "::error::Info.plist 缺少 CFBundleExecutable:${app}"
exit 1
fi
if [ ! -s "${app}/Contents/MacOS/${exe_name}" ]; then
echo "::error::主程序缺失或为空:${app}/Contents/MacOS/${exe_name}"
ls -la "${app}/Contents/MacOS" || true
exit 1
fi
# 注意:$app 之类的展开必须加花括号 —— macOS runner 的 bash 3.2
# 会把紧跟其后的全角字符(如 $app( )吞进变量名,set -u 下
# 直接 unbound 报错(v0.6.0 的 Release 就挂在第 63 行)。
echo "ok .app: ${app}(CFBundleExecutable=${exe_name})"
expect_one "release-assets/*.zip" "macOS zip"
unzip -l "${FOUND}" | grep -q "Contents/MacOS/" || {
echo "::error::zip 内缺少 Contents/MacOS/:${FOUND}"
exit 1
}
;;
*)
echo "::error::未知 bundle 类型:${{ matrix.bundle }}"
exit 1
;;
esac
# 收集目录里不允许出现 0 字节文件(上传后会变成看似正常的空包)。
for f in release-assets/*; do
[ -f "$f" ] || continue
if [ ! -s "$f" ]; then
echo "::error::空文件:$f"
exit 1
fi
done
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: release-assets/*
if-no-files-found: error
publish:
name: Publish release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
path: release-assets
merge-multiple: true
# 校验和只有在「条目数 = 产物数、每个产物都有条目、且能回溯校验」时
# 才有意义;否则一个漏条目的 SHA256SUMS.txt 会被当成完整清单发布。
- name: Generate and verify checksums
run: |
set -euo pipefail
cd release-assets
rm -f SHA256SUMS.txt
shopt -s nullglob
assets=()
for f in *; do
[ -f "$f" ] || continue
[ "$f" = "SHA256SUMS.txt" ] && continue
assets+=("$f")
done
shopt -u nullglob
if [ "${#assets[@]}" -ne 3 ]; then
echo "::error::期望 3 个平台产物,实际 ${#assets[@]} 个:${assets[*]:-<无>}"
ls -la
exit 1
fi
for f in "${assets[@]}"; do
if [ ! -s "$f" ]; then
echo "::error::空产物:$f"
exit 1
fi
done
sha256sum "${assets[@]}" > SHA256SUMS.txt
cat SHA256SUMS.txt
entries=$(wc -l < SHA256SUMS.txt | tr -d ' ')
if [ "$entries" -ne "${#assets[@]}" ]; then
echo "::error::SHA256SUMS 条目数 $entries 与产物数 ${#assets[@]} 不一致"
exit 1
fi
# Windows 版 sha256sum 会在文件名前加 `*`(二进制模式标记),
# Ubuntu 版不会;两种都要能解析出文件名。
listed=$(awk '{print $2}' SHA256SUMS.txt | sed 's/^\*//')
for f in "${assets[@]}"; do
if ! printf '%s\n' "$listed" | grep -Fxq "$f"; then
echo "::error::SHA256SUMS 缺少条目:$f"
exit 1
fi
done
sha256sum -c SHA256SUMS.txt
- uses: softprops/action-gh-release@v2
with:
files: release-assets/*
generate_release_notes: true
body: |
## Downloads
| Platform | File |
|----------|------|
| Windows 10/11 x64 | `Workbench_<version>_x64-setup.exe` (NSIS) |
| Linux x64 | `workbench_<version>_amd64.deb` |
| macOS | `Workbench.zip` (unsigned `.app`) |
These builds are **not code-signed**. Windows SmartScreen may ask for
"More info → Run anyway"; on macOS right-click the app and choose Open
the first time. Verify downloads against `SHA256SUMS.txt`.