diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 04fb7df..0289441 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -38,6 +38,11 @@ jobs:
exit 1
fi
+ if [[ "$(git cat-file -t "refs/tags/${RELEASE_TAG}" 2>/dev/null || true)" != "tag" ]]; then
+ echo "::error title=Invalid release tag::Tag ${RELEASE_TAG} must be an annotated tag."
+ exit 1
+ fi
+
if ! TAG_COMMIT=$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}" 2>/dev/null); then
echo "::error title=Invalid release tag::Tag ${RELEASE_TAG} does not exist or does not resolve to a commit."
exit 1
@@ -63,6 +68,16 @@ jobs:
exit 1
fi
+ if ! git rev-parse --verify "origin/main^{commit}" >/dev/null 2>&1; then
+ echo "::error title=Missing main branch::Unable to resolve origin/main from the release checkout."
+ exit 1
+ fi
+
+ if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then
+ echo "::error title=Release tag not on main::Tag ${RELEASE_TAG} commit ${TAG_COMMIT} is not contained in origin/main."
+ exit 1
+ fi
+
VERSION="${RELEASE_TAG#v}"
{
echo "tag=$RELEASE_TAG"
@@ -233,10 +248,12 @@ jobs:
- name: Generate Checksums
run: |
- echo "### SHA-256 Checksums" > checksums.txt
- echo '```' >> checksums.txt
- shasum -a 256 ListenBar-macOS-universal.zip >> checksums.txt
- echo '```' >> checksums.txt
+ {
+ echo "### SHA-256 Checksums"
+ echo '```'
+ shasum -a 256 ListenBar-macOS-universal.zip
+ echo '```'
+ } > checksums.txt
{
echo "## ListenBar ${{ steps.release_tag.outputs.tag }}"
@@ -253,7 +270,11 @@ jobs:
- name: Download Sparkle tools
run: |
- curl -fsSL -o Sparkle-2.9.4.tar.xz https://github.com/sparkle-project/Sparkle/releases/download/2.9.4/Sparkle-2.9.4.tar.xz
+ curl -fsSL \
+ "https://github.com/sparkle-project/Sparkle/releases/download/2.9.4/Sparkle-2.9.4.tar.xz" \
+ -o Sparkle-2.9.4.tar.xz
+ echo "ce89daf967db1e1893ed3ebd67575ed82d3902563e3191ca92aaec9164fbdef9 Sparkle-2.9.4.tar.xz" \
+ | shasum -a 256 --check
mkdir -p sparkle-tools
tar -xf Sparkle-2.9.4.tar.xz -C sparkle-tools --strip-components=1
@@ -282,7 +303,7 @@ jobs:
subject-path: ListenBar-macOS-universal.zip
- name: Release
- uses: softprops/action-gh-release@v1
+ uses: softprops/action-gh-release@v2
with:
files: |
ListenBar-macOS-universal.zip
diff --git a/README.md b/README.md
index 640805c..667f330 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -42,7 +42,7 @@
- 🍎 **Truly native.** ListenBar's app business code is 100% Swift, built with SwiftUI and The Composable Architecture (TCA). It uses `MenuBarExtra` and `LSUIElement` instead of an Electron runtime or embedded WebView.
- 🪶 **Ultra-lightweight software.** ListenBar stays focused on inspecting local listeners without shipping an entire browser engine.
-- 🎨 **At home on macOS.** The interface automatically follows Light and Dark Mode. Native SwiftUI menu controls adopt the system-provided Liquid Glass appearance where appropriate on macOS 26, while macOS 14 and macOS 15 retain their own native styling. ListenBar does not simulate Liquid Glass with custom visual effects. Releases are built with Xcode 26.2.
+- 🎨 **At home on macOS.** The interface automatically follows Light and Dark Mode. Native SwiftUI menu controls adopt the system-provided Liquid Glass appearance where appropriate on macOS 26, while macOS 14 and macOS 15 retain their own native styling. ListenBar does not simulate Liquid Glass with custom visual effects. Releases are built with Xcode 26.5.
## 📦 Installation
@@ -103,6 +103,13 @@ Only bypass Gatekeeper when you obtained the app from this repository's official
Release assets include a SHA-256 checksum, and Sparkle updates are protected by an EdDSA signature in `appcast.xml`. Releases published after this workflow change also include a GitHub Artifact Attestation for `ListenBar-macOS-universal.zip`; `v0.4.0` and earlier releases predate this attestation support.
+Production releases must use an annotated `vX.Y.Z` tag whose commit is already contained in the protected `main` branch:
+
+```bash
+git tag -a vX.Y.Z -m "ListenBar vX.Y.Z"
+git push origin vX.Y.Z
+```
+
After downloading an attested release zip, verify its build provenance with GitHub CLI:
```bash
@@ -128,11 +135,11 @@ The `--ref` value and `release_tag` input must name the same tag. You can also d
## 🧪 Development and tests
-The project currently contains **159 XCTest test methods** covering reducer behavior, settings persistence, ignored listener identities and filtering, login item management, port parsing and grouping, process metadata, menu presentation, screenshot fixtures, and Sparkle configuration.
+The XCTest suite covers reducer behavior, settings persistence, ignored listener identities and filtering, login item management, port parsing and grouping, process metadata, menu presentation, screenshot fixtures, and Sparkle configuration.
-Requirements: Xcode 26, [Homebrew](https://brew.sh/), [just](https://github.com/casey/just), [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and [Tuist](https://tuist.dev/).
+Requirements: Xcode 26.5, [Homebrew](https://brew.sh/), [just](https://github.com/casey/just), [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and [Tuist](https://tuist.dev/).
-Install the development tools manually, then enable the repository-managed Git hook:
+Install the development tools manually, then enable the repository-managed Git hook and run the complete local validation:
```bash
brew install just swiftformat tuist
@@ -143,20 +150,6 @@ just check
`just setup` only checks the installed tools and configures `core.hooksPath`; it never installs or upgrades software. If Homebrew no longer provides SwiftFormat 0.62.1, install that exact version from the [official release](https://github.com/nicklockwood/SwiftFormat/releases/tag/0.62.1).
-Before each commit, the hook formats staged Swift files. When formatting changes are required, the commit stops so you can review the diff, stage the updated files, and retry. Partially staged Swift files are not modified automatically; stage or stash the remaining edits, or run `just format` manually. Run `just --list` to see all available development commands.
+`just check` is the local acceptance entry point. It checks Swift formatting and runs the complete XCTest suite with the repository-managed build and test parameters.
-To run the test command directly:
-
-```bash
-tuist generate --no-open
-xcodebuild test \
- -project ListenBar.xcodeproj \
- -scheme ListenBar \
- -destination 'platform=macOS' \
- -testLanguage zh-Hans \
- -skipPackagePluginValidation \
- -skipMacroValidation \
- CODE_SIGN_IDENTITY='' \
- CODE_SIGNING_ALLOWED=NO \
- CODE_SIGNING_REQUIRED=NO
-```
+Before each commit, the hook formats staged Swift files. When formatting changes are required, the commit stops so you can review the diff, stage the updated files, and retry. Partially staged Swift files are not modified automatically; stage or stash the remaining edits, or run `just format` manually. Run `just --list` to see all available development commands.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 18dc3a1..4bccaf9 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -14,7 +14,7 @@
-
+
@@ -42,7 +42,7 @@
- 🍎 **真正原生。** ListenBar 的 App 业务代码 100% 使用 Swift 编写,并采用 SwiftUI 和 The Composable Architecture(TCA)架构。它基于 `MenuBarExtra` 与 `LSUIElement` 构建,不包含 Electron 运行时,也没有嵌入 WebView。
- 🪶 **超轻量软件。** ListenBar 专注于检查本机监听端口,无需附带完整的浏览器引擎。
-- 🎨 **自然融入 macOS。** 界面会自动适配浅色与暗色模式。在 macOS 26 上,原生 SwiftUI 菜单控件会在适用位置呈现系统提供的 Liquid Glass 外观;macOS 14 与 macOS 15 则保持各自的原生系统样式。ListenBar 不使用自定义视觉效果模拟 Liquid Glass,发布版本使用 Xcode 26.2 构建。
+- 🎨 **自然融入 macOS。** 界面会自动适配浅色与暗色模式。在 macOS 26 上,原生 SwiftUI 菜单控件会在适用位置呈现系统提供的 Liquid Glass 外观;macOS 14 与 macOS 15 则保持各自的原生系统样式。ListenBar 不使用自定义视觉效果模拟 Liquid Glass,发布版本使用 Xcode 26.5 构建。
## 📦 安装
@@ -103,6 +103,13 @@ brew upgrade listenbar
Release assets 包含 SHA-256 checksum,Sparkle 更新则由 `appcast.xml` 中的 EdDSA 签名保护。本次 workflow 变更后发布的版本还会为 `ListenBar-macOS-universal.zip` 提供 GitHub Artifact Attestation;`v0.4.0` 及更早版本发布时尚未支持这项能力,因此不包含 attestation。
+正式发布必须使用 annotated `vX.Y.Z` tag,且该 tag 指向的提交必须已经包含在受保护的 `main` 分支中:
+
+```bash
+git tag -a vX.Y.Z -m "ListenBar vX.Y.Z"
+git push origin vX.Y.Z
+```
+
下载带有 attestation 的发布 zip 后,可使用 GitHub CLI 验证其构建来源:
```bash
@@ -128,11 +135,11 @@ gh workflow run release.yml \
## 🧪 开发与测试
-项目当前包含 **159 个 XCTest 测试方法**,覆盖 reducer 行为、配置持久化、监听项忽略身份与过滤、登录项管理、端口解析与分组、进程元数据、菜单呈现、截图 fixture 和 Sparkle 配置。
+XCTest 测试套件覆盖 reducer 行为、配置持久化、监听项忽略身份与过滤、登录项管理、端口解析与分组、进程元数据、菜单呈现、截图 fixture 和 Sparkle 配置。
-环境要求:Xcode 26、[Homebrew](https://brew.sh/)、[just](https://github.com/casey/just)、[SwiftFormat](https://github.com/nicklockwood/SwiftFormat) 和 [Tuist](https://tuist.dev/)。
+环境要求:Xcode 26.5、[Homebrew](https://brew.sh/)、[just](https://github.com/casey/just)、[SwiftFormat](https://github.com/nicklockwood/SwiftFormat) 和 [Tuist](https://tuist.dev/)。
-请手动安装开发工具,然后启用仓库管理的 Git hook:
+请手动安装开发工具,然后启用仓库管理的 Git hook 并运行完整的本地验收:
```bash
brew install just swiftformat tuist
@@ -143,20 +150,6 @@ just check
`just setup` 只检查已安装的工具并配置 `core.hooksPath`,不会安装或升级任何软件。如果 Homebrew 不再提供 SwiftFormat 0.62.1,请从[官方 Release](https://github.com/nicklockwood/SwiftFormat/releases/tag/0.62.1)手动安装该精确版本。
-每次提交前,hook 会格式化已暂存的 Swift 文件。如果产生格式变化,本次提交会中止,便于你检查 diff、重新暂存并再次提交。对于部分暂存的 Swift 文件,hook 不会自动修改;请先暂存或 stash 其余改动,或手动运行 `just format`。运行 `just --list` 可以查看所有开发命令。
+`just check` 是本地验收入口。它会检查 Swift 格式,并使用仓库维护的构建与测试参数运行完整 XCTest 测试套件。
-如需直接运行测试命令:
-
-```bash
-tuist generate --no-open
-xcodebuild test \
- -project ListenBar.xcodeproj \
- -scheme ListenBar \
- -destination 'platform=macOS' \
- -testLanguage zh-Hans \
- -skipPackagePluginValidation \
- -skipMacroValidation \
- CODE_SIGN_IDENTITY='' \
- CODE_SIGNING_ALLOWED=NO \
- CODE_SIGNING_REQUIRED=NO
-```
+每次提交前,hook 会格式化已暂存的 Swift 文件。如果产生格式变化,本次提交会中止,便于你检查 diff、重新暂存并再次提交。对于部分暂存的 Swift 文件,hook 不会自动修改;请先暂存或 stash 其余改动,或手动运行 `just format`。运行 `just --list` 可以查看所有开发命令。