feat(release): 完善版本服务与镜像发布
This commit is contained in:
1 parent
593112e733
commit
ea67453b32
33 files changed
+1546
-180
No files matched your search
+3
-5
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生成应用内在线升级读取的版本清单 appcast.json。
|
||||
|
||||
Scripts/appcast.py <版本> <build> <下载地址前缀> <Apple 芯片 zip> <Apple 芯片 dmg> <Intel zip> <Intel dmg> > dist/appcast.json
|
||||
Scripts/appcast.py <版本> <build> <下载地址前缀> <Apple 芯片 zip> <Apple 芯片 dmg> > dist/appcast.json
|
||||
|
||||
顶层的 url / sha256 / size / dmg 是 Apple 芯片版:0.3.0 只有 Apple 芯片版,也只认顶层字段。
|
||||
Intel 版放在 intel 字段里,0.3.1 起的 Intel 版应用读它。
|
||||
清单只发布 Apple Silicon 安装包。
|
||||
|
||||
更新摘要取自 CHANGELOG.md 中该版本的条目:每条取冒号或句号之前的部分(过短时带上冒号后的内容),最多 10 条。
|
||||
"""
|
||||
@@ -63,7 +62,7 @@ def asset(base: str, zip_path: str, dmg_path: str) -> dict:
|
||||
|
||||
|
||||
def main() -> None:
|
||||
version, build, base, arm_zip, arm_dmg, intel_zip, intel_dmg = sys.argv[1:8]
|
||||
version, build, base, arm_zip, arm_dmg = sys.argv[1:6]
|
||||
root = os.path.join(os.path.dirname(__file__), "..")
|
||||
with open(os.path.join(root, "CHANGELOG.md"), encoding="utf-8") as handle:
|
||||
date, notes = release_notes(handle.read(), version)
|
||||
@@ -75,7 +74,6 @@ def main() -> None:
|
||||
**asset(base, arm_zip, arm_dmg),
|
||||
"notes": notes,
|
||||
"changelog": "https://getopenstats.com/#changelog",
|
||||
"intel": asset(base, intel_zip, intel_dmg),
|
||||
}
|
||||
json.dump(feed, sys.stdout, ensure_ascii=False, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Copyright (C) 2026 ysicing
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
compile="$(task --dry compile SIGN_ID= 2>&1)"
|
||||
grep -q -- "-destination 'generic/platform=macOS' ARCHS=arm64 ONLY_ACTIVE_ARCH=NO" <<< "$compile"
|
||||
if grep -q 'x86_64' <<< "$compile"; then
|
||||
echo "默认构建不应包含 x86_64" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q -- '^ -quiet' <<< "$compile"; then
|
||||
echo "默认构建不应使用会产生 SwiftCompile 伪错误的 -quiet" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
printf 'zip' > "$WORK/XStats-0.6.1-AppleSilicon.zip"
|
||||
printf 'dmg' > "$WORK/XStats-0.6.1-AppleSilicon.dmg"
|
||||
python3 Scripts/appcast.py 0.6.1 1 https://example.test \
|
||||
"$WORK/XStats-0.6.1-AppleSilicon.zip" "$WORK/XStats-0.6.1-AppleSilicon.dmg" > "$WORK/appcast.json"
|
||||
python3 - "$WORK/appcast.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
feed = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
assert "intel" not in feed, feed
|
||||
assert feed["url"].endswith("-AppleSilicon.zip"), feed
|
||||
assert feed["dmg"].endswith("-AppleSilicon.dmg"), feed
|
||||
PY
|
||||
@@ -2,7 +2,7 @@
|
||||
# 本机只保留一个 XStats:退出正在运行的旧版 → 删除 /Applications 里的旧版 → 把刚编译的新版移进去 → 启动。
|
||||
# 用移动而不是复制,编译目录里不留第二份;其他位置被系统登记的同名应用会取消登记并提示。
|
||||
#
|
||||
# Scripts/install_local.sh build/DerivedData/Build/Products/Release/XStats.app
|
||||
# Scripts/install_local.sh build/DerivedData-arm64/Build/Products/Release/XStats.app
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2026 ysicing
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
"""把发布清单提交给 XStats API。"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_ENDPOINT = "https://getopenstats.com/api/v1/releases/current"
|
||||
|
||||
|
||||
def publish(appcast: Path, endpoint: str, token: str) -> None:
|
||||
if not token:
|
||||
raise ValueError("XSTATS_RELEASE_TOKEN 不能为空")
|
||||
body = appcast.read_bytes()
|
||||
manifest = json.loads(body)
|
||||
if not isinstance(manifest, dict) or not manifest.get("version"):
|
||||
raise ValueError(f"{appcast} 不是有效的版本清单")
|
||||
request = urllib.request.Request(
|
||||
endpoint,
|
||||
data=body,
|
||||
method="PUT",
|
||||
headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "XStats-Release",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
if response.status != 204:
|
||||
raise RuntimeError(f"发布接口返回 {response.status}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit(f"用法:{sys.argv[0]} <appcast.json>")
|
||||
endpoint = os.environ.get("XSTATS_API_URL", DEFAULT_ENDPOINT)
|
||||
token = os.environ.get("XSTATS_RELEASE_TOKEN", "")
|
||||
try:
|
||||
publish(Path(sys.argv[1]), endpoint, token)
|
||||
except Exception as error:
|
||||
raise SystemExit(f"发布版本到 API 失败:{error}") from error
|
||||
print(f"✅ 版本清单已发布到 {endpoint}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2026 ysicing
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
import publish_api
|
||||
|
||||
|
||||
class PublishAPITest(unittest.TestCase):
|
||||
def test_sends_manifest_with_bearer_token(self) -> None:
|
||||
received = {}
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_PUT(self) -> None:
|
||||
received["path"] = self.path
|
||||
received["authorization"] = self.headers.get("Authorization")
|
||||
received["content_type"] = self.headers.get("Content-Type")
|
||||
received["body"] = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, *_: object) -> None:
|
||||
pass
|
||||
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "appcast.json"
|
||||
path.write_text('{"version":"2026.09.21.03"}', encoding="utf-8")
|
||||
publish_api.publish(path, f"http://127.0.0.1:{server.server_port}/api/v1/releases/current", "release-secret")
|
||||
finally:
|
||||
server.shutdown()
|
||||
thread.join()
|
||||
server.server_close()
|
||||
|
||||
self.assertEqual(received["path"], "/api/v1/releases/current")
|
||||
self.assertEqual(received["authorization"], "Bearer release-secret")
|
||||
self.assertEqual(received["content_type"], "application/json")
|
||||
self.assertEqual(received["body"], {"version": "2026.09.21.03"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+13
-18
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# 发布已公证的版本:Apple 芯片版与 Intel 版的 DMG、在线升级包上传到官网 /download/,最后更新版本清单 appcast.json(已安装的应用据此提示升级),
|
||||
# 发布已公证的 Apple Silicon 版 DMG、在线升级包到官网 /download/,最后更新静态清单并提交版本 API(已安装的应用据此提示升级),
|
||||
# cask 提交到 gentpan/homebrew-tap,并校验线上文件。
|
||||
#
|
||||
# ./Scripts/release.sh # 先打包、公证
|
||||
@@ -12,35 +12,30 @@ KEY="${SITE_KEY:-$HOME/.ssh/gentpan.pem}"
|
||||
ROOT="${SITE_ROOT:-/var/www/getopenstats.com}"
|
||||
TAP="${TAP:-gentpan/homebrew-tap}"
|
||||
VERSION="$(sed -nE 's/^ *MARKETING_VERSION: *"?([0-9.]+)"?.*/\1/p' project.yml | head -1)"
|
||||
CHIPS=(AppleSilicon Intel)
|
||||
APPCAST="dist/appcast.json"
|
||||
CASK="dist/xstats.rb"
|
||||
SSH=(ssh -i "$KEY" -o BatchMode=yes "$HOST")
|
||||
|
||||
for chip in "${CHIPS[@]}"; do
|
||||
for ext in dmg zip; do
|
||||
file="dist/XStats-${VERSION}-${chip}.${ext}"
|
||||
[ -f "$file" ] || { echo "缺少 $file,先运行 ./Scripts/release.sh" >&2; exit 1; }
|
||||
done
|
||||
for ext in dmg zip; do
|
||||
file="dist/XStats-${VERSION}-AppleSilicon.${ext}"
|
||||
[ -f "$file" ] || { echo "缺少 $file,先运行 ./Scripts/release.sh" >&2; exit 1; }
|
||||
done
|
||||
for file in "$APPCAST" "$CASK"; do
|
||||
[ -f "$file" ] || { echo "缺少 $file,先运行 ./Scripts/release.sh" >&2; exit 1; }
|
||||
done
|
||||
python3 - "$APPCAST" "$VERSION" "dist/XStats-${VERSION}-AppleSilicon.zip" "dist/XStats-${VERSION}-Intel.zip" <<'PY' \
|
||||
python3 - "$APPCAST" "$VERSION" "dist/XStats-${VERSION}-AppleSilicon.zip" <<'PY' \
|
||||
|| { echo "$APPCAST 与升级包不一致" >&2; exit 1; }
|
||||
import hashlib, json, sys
|
||||
feed = json.load(open(sys.argv[1]))
|
||||
digest = lambda path: hashlib.sha256(open(path, "rb").read()).hexdigest()
|
||||
assert feed["version"] == sys.argv[2] and feed["notes"], feed
|
||||
assert feed["sha256"] == digest(sys.argv[3]) and feed["url"].endswith("-AppleSilicon.zip"), feed
|
||||
assert feed["intel"]["sha256"] == digest(sys.argv[4]) and feed["intel"]["url"].endswith("-Intel.zip"), feed
|
||||
assert "intel" not in feed, feed
|
||||
PY
|
||||
grep -q "version \"${VERSION}\"" "$CASK" || { echo "$CASK 的版本不是 ${VERSION}" >&2; exit 1; }
|
||||
for chip in "${CHIPS[@]}"; do
|
||||
dmg="dist/XStats-${VERSION}-${chip}.dmg"
|
||||
xcrun stapler validate "$dmg" >/dev/null || { echo "$dmg 没有装订公证票据" >&2; exit 1; }
|
||||
grep -q "$(shasum -a 256 "$dmg" | cut -d' ' -f1)" "$CASK" || { echo "$CASK 里的 sha256 与 $dmg 不一致" >&2; exit 1; }
|
||||
done
|
||||
dmg="dist/XStats-${VERSION}-AppleSilicon.dmg"
|
||||
xcrun stapler validate "$dmg" >/dev/null || { echo "$dmg 没有装订公证票据" >&2; exit 1; }
|
||||
grep -q "$(shasum -a 256 "$dmg" | cut -d' ' -f1)" "$CASK" || { echo "$CASK 里的 sha256 与 $dmg 不一致" >&2; exit 1; }
|
||||
|
||||
# 上传:先传临时名再改名,下载中途不会拿到半个文件
|
||||
"${SSH[@]}" "sudo install -d -o \$(id -un) -m 755 $ROOT/download"
|
||||
@@ -54,12 +49,12 @@ upload() {
|
||||
[ "$online" = "$(shasum -a 256 "$1" | cut -d' ' -f1)" ] || { echo "线上 ${name} 校验不一致:$online" >&2; exit 1; }
|
||||
echo "✅ https://getopenstats.com/download/${name}"
|
||||
}
|
||||
for chip in "${CHIPS[@]}"; do
|
||||
upload "dist/XStats-${VERSION}-${chip}.dmg"
|
||||
upload "dist/XStats-${VERSION}-${chip}.zip"
|
||||
done
|
||||
upload "dist/XStats-${VERSION}-AppleSilicon.dmg"
|
||||
upload "dist/XStats-${VERSION}-AppleSilicon.zip"
|
||||
# 版本清单最后发布:安装包都已就位后,已安装的应用才会看到新版本
|
||||
upload "$APPCAST"
|
||||
# 新版客户端从 API 读取同一份清单;Token 只从环境变量读取,不出现在命令参数里。
|
||||
python3 Scripts/publish_api.py "$APPCAST"
|
||||
|
||||
# Homebrew tap
|
||||
WORK="$(mktemp -d)"
|
||||
|
||||
+66
-81
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# 发布一个可分发的版本:Apple 芯片版与 Intel 版各自 Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订)
|
||||
# 发布一个可分发的 Apple Silicon 版本:Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订)
|
||||
# → 在线升级包 → 版本清单 → Homebrew cask。
|
||||
#
|
||||
# ./Scripts/release.sh
|
||||
@@ -19,10 +19,7 @@ DIST="${DIST:-dist}"
|
||||
NOTARY_PROFILE="${NOTARY_PROFILE:-GiantAccel}"
|
||||
SIGN_ID="${SIGN_ID:-$(security find-identity -v -p codesigning 2>/dev/null \
|
||||
| grep 'Developer ID Application' | head -1 | sed -E 's/.*"(.*)".*/\1/' || true)}"
|
||||
# 编译架构与文件名里的芯片名:XStats-2026.09.21.01-AppleSilicon.dmg、XStats-2026.09.21.01-Intel.dmg
|
||||
ARCHS=(arm64 x86_64)
|
||||
chip() { [ "$1" = arm64 ] && echo AppleSilicon || echo Intel; }
|
||||
app_for() { echo "build/DerivedData-$1/Build/Products/Release/XStats.app"; }
|
||||
APP="build/DerivedData-arm64/Build/Products/Release/XStats.app"
|
||||
|
||||
# 在线升级的更新摘要取自下一版本的更新日志:发版前把 “## 未发布” 改成 “## 版本 · 日期”
|
||||
NEXT_VERSION="$(./Scripts/version.sh next)"
|
||||
@@ -35,7 +32,7 @@ if [ -z "$SIGN_ID" ]; then
|
||||
fi
|
||||
TEAM_ID="$(echo "$SIGN_ID" | sed -nE 's/.*\(([A-Z0-9]+)\)$/\1/p')"
|
||||
|
||||
# 两种芯片共用一个公开版本号和内部构建号:先推进一次,各自构建时不再推进
|
||||
# 发布前推进一次公开版本号和内部构建号
|
||||
./Scripts/version.sh build >/dev/null
|
||||
VERSION="$(sed -nE 's/^ *MARKETING_VERSION: *"?([0-9.]+)"?.*/\1/p' project.yml | head -1)"
|
||||
BUILD="$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: *"?([0-9]+)"?.*/\1/p' project.yml | head -1)"
|
||||
@@ -52,94 +49,86 @@ trap 'rm -rf "$WORK"' EXIT
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"
|
||||
|
||||
for arch in "${ARCHS[@]}"; do
|
||||
APP="$(app_for "$arch")"
|
||||
NAME="XStats-${VERSION}-$(chip "$arch")"
|
||||
echo
|
||||
echo "==== $(chip "$arch")(${arch})===="
|
||||
NAME="XStats-${VERSION}-AppleSilicon"
|
||||
echo
|
||||
echo "==== Apple Silicon(arm64)===="
|
||||
|
||||
# ---- 构建 ------------------------------------------------------------------
|
||||
# ---- 构建 ------------------------------------------------------------------
|
||||
|
||||
rm -rf "$APP"
|
||||
task build CONFIG=Release INSTALL=0 BUMP=0 ARCH="$arch" SIGN_ID="$SIGN_ID"
|
||||
rm -rf "$APP"
|
||||
task build CONFIG=Release INSTALL=0 BUMP=0 SIGN_ID="$SIGN_ID"
|
||||
|
||||
for binary in "$APP/Contents/MacOS/XStats" "$APP/Contents/MacOS/XStatsHelper" \
|
||||
"$APP/Contents/PlugIns/XStatsWidget.appex/Contents/MacOS/XStatsWidget"; do
|
||||
[ "$(lipo -archs "$binary")" = "$arch" ] \
|
||||
|| { echo "error: $binary 的架构是 $(lipo -archs "$binary"),应当只有 $arch" >&2; exit 1; }
|
||||
done
|
||||
codesign --verify --deep --strict --verbose=2 "$APP"
|
||||
for binary in "$APP" "$APP/Contents/MacOS/XStatsHelper" "$APP/Contents/PlugIns/XStatsWidget.appex"; do
|
||||
details="$(codesign -dvv "$binary" 2>&1)"
|
||||
echo "$details" | grep -q "TeamIdentifier=${TEAM_ID}" \
|
||||
|| { echo "error: $binary 未使用团队 ${TEAM_ID} 签名" >&2; exit 1; }
|
||||
echo "$details" | grep -q "Timestamp=" \
|
||||
|| { echo "error: $binary 缺少安全时间戳" >&2; exit 1; }
|
||||
echo "$details" | grep -Eq "flags=.*runtime" \
|
||||
|| { echo "error: $binary 未启用 Hardened Runtime" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# ---- 公证 App --------------------------------------------------------------
|
||||
|
||||
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
|
||||
# ditto 而不是 zip:保留包内的符号链接与扩展属性
|
||||
ditto -c -k --keepParent "$APP" "$WORK/$NAME-notarize.zip"
|
||||
notarize "$WORK/$NAME-notarize.zip"
|
||||
xcrun stapler staple "$APP"
|
||||
xcrun stapler validate "$APP"
|
||||
# 拒绝发布 Gatekeeper 在用户机器上仍会拒绝的包(Intel 版的签名与公证票据同样能在本机校验)
|
||||
if ! spctl -a -vv "$APP" 2>&1 | grep -q accepted; then
|
||||
echo "error: Gatekeeper 仍然拒绝该 App,停止发布。" >&2
|
||||
spctl -a -vv "$APP" || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- DMG -------------------------------------------------------------------
|
||||
|
||||
rm -rf "$WORK/dmg"
|
||||
mkdir -p "$WORK/dmg"
|
||||
ditto "$APP" "$WORK/dmg/XStats.app"
|
||||
ln -s /Applications "$WORK/dmg/Applications"
|
||||
hdiutil create -volname "XStats ${VERSION}" -srcfolder "$WORK/dmg" -ov -format UDZO "$DIST/$NAME.dmg" >/dev/null
|
||||
codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$NAME.dmg"
|
||||
|
||||
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
|
||||
notarize "$DIST/$NAME.dmg"
|
||||
xcrun stapler staple "$DIST/$NAME.dmg"
|
||||
spctl -a -t open --context context:primary-signature -vv "$DIST/$NAME.dmg"
|
||||
fi
|
||||
|
||||
# ---- 在线升级 ----------------------------------------------------------------
|
||||
|
||||
# 应用内升级下载已装订票据的 .app 压缩包
|
||||
ditto -c -k --keepParent "$APP" "$DIST/$NAME.zip"
|
||||
for binary in "$APP/Contents/MacOS/XStats" "$APP/Contents/MacOS/XStatsHelper" \
|
||||
"$APP/Contents/PlugIns/XStatsWidget.appex/Contents/MacOS/XStatsWidget"; do
|
||||
[ "$(lipo -archs "$binary")" = arm64 ] \
|
||||
|| { echo "error: $binary 的架构是 $(lipo -archs "$binary"),应当只有 arm64" >&2; exit 1; }
|
||||
done
|
||||
codesign --verify --deep --strict --verbose=2 "$APP"
|
||||
for binary in "$APP" "$APP/Contents/MacOS/XStatsHelper" "$APP/Contents/PlugIns/XStatsWidget.appex"; do
|
||||
details="$(codesign -dvv "$binary" 2>&1)"
|
||||
echo "$details" | grep -q "TeamIdentifier=${TEAM_ID}" \
|
||||
|| { echo "error: $binary 未使用团队 ${TEAM_ID} 签名" >&2; exit 1; }
|
||||
echo "$details" | grep -q "Timestamp=" \
|
||||
|| { echo "error: $binary 缺少安全时间戳" >&2; exit 1; }
|
||||
echo "$details" | grep -Eq "flags=.*runtime" \
|
||||
|| { echo "error: $binary 未启用 Hardened Runtime" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# 版本清单:顶层是 Apple 芯片版(0.3.0 只认顶层字段),intel 字段是 Intel 版
|
||||
# ---- 公证 App --------------------------------------------------------------
|
||||
|
||||
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
|
||||
# ditto 而不是 zip:保留包内的符号链接与扩展属性
|
||||
ditto -c -k --keepParent "$APP" "$WORK/$NAME-notarize.zip"
|
||||
notarize "$WORK/$NAME-notarize.zip"
|
||||
xcrun stapler staple "$APP"
|
||||
xcrun stapler validate "$APP"
|
||||
if ! spctl -a -vv "$APP" 2>&1 | grep -q accepted; then
|
||||
echo "error: Gatekeeper 仍然拒绝该 App,停止发布。" >&2
|
||||
spctl -a -vv "$APP" || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- DMG -------------------------------------------------------------------
|
||||
|
||||
rm -rf "$WORK/dmg"
|
||||
mkdir -p "$WORK/dmg"
|
||||
ditto "$APP" "$WORK/dmg/XStats.app"
|
||||
ln -s /Applications "$WORK/dmg/Applications"
|
||||
hdiutil create -volname "XStats ${VERSION}" -srcfolder "$WORK/dmg" -ov -format UDZO "$DIST/$NAME.dmg" >/dev/null
|
||||
codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$NAME.dmg"
|
||||
|
||||
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
|
||||
notarize "$DIST/$NAME.dmg"
|
||||
xcrun stapler staple "$DIST/$NAME.dmg"
|
||||
spctl -a -t open --context context:primary-signature -vv "$DIST/$NAME.dmg"
|
||||
fi
|
||||
|
||||
# ---- 在线升级 ----------------------------------------------------------------
|
||||
|
||||
# 应用内升级下载已装订票据的 .app 压缩包
|
||||
ditto -c -k --keepParent "$APP" "$DIST/$NAME.zip"
|
||||
|
||||
# 版本清单只包含 Apple Silicon 安装包
|
||||
python3 Scripts/appcast.py "$VERSION" "$BUILD" "$DOWNLOAD_BASE" \
|
||||
"$DIST/XStats-${VERSION}-AppleSilicon.zip" "$DIST/XStats-${VERSION}-AppleSilicon.dmg" \
|
||||
"$DIST/XStats-${VERSION}-Intel.zip" "$DIST/XStats-${VERSION}-Intel.dmg" > "$DIST/appcast.json"
|
||||
"$DIST/XStats-${VERSION}-AppleSilicon.zip" "$DIST/XStats-${VERSION}-AppleSilicon.dmg" > "$DIST/appcast.json"
|
||||
|
||||
# ---- Homebrew cask ------------------------------------------------------------
|
||||
|
||||
SHA_ARM="$(shasum -a 256 "$DIST/XStats-${VERSION}-AppleSilicon.dmg" | cut -d' ' -f1)"
|
||||
SHA_INTEL="$(shasum -a 256 "$DIST/XStats-${VERSION}-Intel.dmg" | cut -d' ' -f1)"
|
||||
cat > "$DIST/xstats.rb" <<CASK
|
||||
cask "xstats" do
|
||||
arch arm: "AppleSilicon", intel: "Intel"
|
||||
|
||||
version "${VERSION}"
|
||||
sha256 arm: "${SHA_ARM}",
|
||||
intel: "${SHA_INTEL}"
|
||||
sha256 "${SHA_ARM}"
|
||||
|
||||
url "${DOWNLOAD_BASE}/XStats-#{version}-#{arch}.dmg"
|
||||
url "${DOWNLOAD_BASE}/XStats-#{version}-AppleSilicon.dmg"
|
||||
name "XStats"
|
||||
desc "Menu bar system monitor with fan control, keep-awake and cleanup"
|
||||
homepage "https://github.com/ysicing/xstats"
|
||||
|
||||
# 应用内置在线升级,brew upgrade 默认不再重复升级
|
||||
auto_updates true
|
||||
depends_on arch: :arm64
|
||||
depends_on macos: :sonoma
|
||||
|
||||
app "XStats.app"
|
||||
@@ -151,15 +140,11 @@ cask "xstats" do
|
||||
end
|
||||
CASK
|
||||
|
||||
# 本机只保留一份:把本机芯片的发布版装到 /Applications,另一种芯片的编译产物由安装脚本一并清掉
|
||||
native="$(uname -m)"
|
||||
./Scripts/install_local.sh "$(app_for "$native")"
|
||||
# 本机只保留一份:把发布版装到 /Applications
|
||||
./Scripts/install_local.sh "$APP"
|
||||
|
||||
echo
|
||||
for arch in "${ARCHS[@]}"; do
|
||||
NAME="XStats-${VERSION}-$(chip "$arch")"
|
||||
echo "✅ ${DIST}/${NAME}.dmg SHA-256 $(shasum -a 256 "$DIST/$NAME.dmg" | cut -d' ' -f1)"
|
||||
done
|
||||
echo "✅ ${DIST}/${NAME}.dmg SHA-256 $(shasum -a 256 "$DIST/$NAME.dmg" | cut -d' ' -f1)"
|
||||
echo " 在线升级:${DIST}/*.zip · ${DIST}/appcast.json"
|
||||
echo " Homebrew cask:${DIST}/xstats.rb"
|
||||
if [ "${SKIP_NOTARIZE:-0}" = "1" ]; then
|
||||
|
||||
Reference in new issue
Block a user