Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Check release safety contract
run: ./script/test_release_contract.sh
- name: Run Swift tests
run: swift test --package-path TokenStepSwift
- name: Run usage collector fixture checks
Expand Down
126 changes: 109 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,60 @@ on:
workflow_dispatch:
inputs:
version:
description: "Release version, for example 0.1.0"
description: "Release version, for example 0.2.13"
required: true
type: string

permissions:
contents: write

concurrency:
group: tokenstep-release
cancel-in-progress: false

jobs:
release:
runs-on: macos-15
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Validate release request
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid release version: $VERSION" >&2
exit 2
fi
if [[ "$GITHUB_REF" != "refs/heads/main" ]]; then
echo "Releases must run from main, got $GITHUB_REF" >&2
exit 2
fi
git fetch origin main --tags
if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then
echo "Workflow checkout is not the current origin/main." >&2
exit 1
fi
if [[ ! -f "docs/RELEASE_NOTES_$VERSION.md" ]]; then
echo "Missing docs/RELEASE_NOTES_$VERSION.md" >&2
exit 1
fi
grep -F "# TokenStep $VERSION" "docs/RELEASE_NOTES_$VERSION.md"
grep -F "TokenStep-$VERSION.dmg" README.md
grep -F "VERSION=\"\${TOKENSTEP_VERSION:-$VERSION}\"" script/build_swiftui_and_run.sh
if gh release view "v$VERSION" >/dev/null 2>&1; then
echo "Release v$VERSION already exists." >&2
exit 1
fi
if git ls-remote --exit-code origin "refs/tags/v$VERSION" >/dev/null 2>&1; then
echo "Tag v$VERSION already exists." >&2
exit 1
fi

- name: Import Developer ID certificate
env:
Expand All @@ -34,7 +76,7 @@ jobs:
security list-keychain -d user -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

- name: Build, sign, notarize, and package
- name: Build, sign, notarize, and verify locally
env:
TOKENSTEP_VERSION: ${{ inputs.version }}
CODE_SIGN_IDENTITY: ${{ secrets.CODE_SIGN_IDENTITY }}
Expand All @@ -43,22 +85,72 @@ jobs:
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: ./script/package_release.sh --notarize

- name: Create GitHub Release
- name: Create verified draft release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
NOTES_FILE="docs/RELEASE_NOTES_$VERSION.md"
if [[ -f "$NOTES_FILE" ]]; then
gh release create "v$VERSION" \
"release/TokenStep-$VERSION.dmg" \
"release/TokenStep-$VERSION.zip" \
--title "TokenStep $VERSION" \
--notes-file "$NOTES_FILE"
else
gh release create "v$VERSION" \
"release/TokenStep-$VERSION.dmg" \
"release/TokenStep-$VERSION.zip" \
--title "TokenStep $VERSION" \
--notes "TokenStep $VERSION"
fi
set -euo pipefail
gh release create "v$VERSION" \
"release/TokenStep-$VERSION.dmg" \
"release/TokenStep-$VERSION.zip" \
"release/TokenStep-$VERSION-SHA256SUMS.txt" \
--draft \
--latest=false \
--fail-on-no-commits \
--target "$GITHUB_SHA" \
--title "TokenStep $VERSION" \
--notes-file "docs/RELEASE_NOTES_$VERSION.md"

- name: Download and verify draft assets
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
DOWNLOAD_DIR="$RUNNER_TEMP/tokenstep-release-download"
mkdir -p "$DOWNLOAD_DIR"
gh release download "v$VERSION" --dir "$DOWNLOAD_DIR"
(
cd "$DOWNLOAD_DIR"
shasum -a 256 --check --strict "TokenStep-$VERSION-SHA256SUMS.txt"
)
TOKENSTEP_EXPECTED_TEAM_ID="$APPLE_TEAM_ID" \
./script/verify_release_artifacts.sh \
"$DOWNLOAD_DIR/TokenStep-$VERSION.dmg" \
"$DOWNLOAD_DIR/TokenStep-$VERSION.zip" \
"$VERSION"
./script/verify_update_installer.sh \
"$DOWNLOAD_DIR/TokenStep-$VERSION.dmg" \
"$VERSION" \
"TokenStepSwift/dist/TokenStep.app/Contents/Helpers/TokenStepHelper"

- name: Publish verified release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: gh release edit "v$VERSION" --draft=false --latest

- name: Verify public release state and links
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
[[ "$(gh release view "v$VERSION" --json isDraft --jq .isDraft)" == "false" ]]
[[ "$(gh release view "v$VERSION" --json isPrerelease --jq .isPrerelease)" == "false" ]]
ASSET_NAMES="$(gh release view "v$VERSION" --json assets --jq '.assets[].name')"
for expected in \
"TokenStep-$VERSION.dmg" \
"TokenStep-$VERSION.zip" \
"TokenStep-$VERSION-SHA256SUMS.txt"; do
grep -Fx "$expected" <<<"$ASSET_NAMES"
done
curl --fail --silent --show-error --location --head --retry 5 \
"https://github.com/Backtthefuture/TokenStep/releases/download/v$VERSION/TokenStep-$VERSION.dmg" \
>/dev/null
curl --fail --silent --show-error --location --head --retry 5 \
"https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-$VERSION.dmg" \
>/dev/null
gh release view "v$VERSION" --json tagName,publishedAt,url,assets
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ TokenStep 是一个 macOS 菜单栏 App,用来本地统计你在 Codex、Claud

下载最新版 DMG,打开后把 `TokenStep.app` 拖进「应用程序」即可使用:

[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.12.dmg)
[下载 TokenStep 最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.13.dmg)

也可以从 Release 页面查看所有版本:

Expand All @@ -32,6 +32,10 @@ TokenStep 已使用 Developer ID 签名并通过 Apple 公证。首次打开时

Windows版本由十七做了移植,欢迎大家前往使用:https://github.com/canyexuanfan/TokenStep-Windows/releases

## 0.2.13 更新:修复自动更新的 Apple 公证门禁

0.2.13 修复了 0.2.12 因发布包遗漏 Apple 公证票据而无法自动安装的问题。新版已经过 Developer ID 签名、Apple 公证、App/DMG 票据装订、macOS 分发策略检查和隔离更新安装验证;不需要关闭 Gatekeeper 或更改系统安全设置。发布流程也改为“先草稿上传、回下载验收,再公开”,未公证产物无法进入公开 Release。完整说明见 [0.2.13 发布说明](docs/RELEASE_NOTES_0.2.13.md)。

## 0.2.12 更新:找回特洛伊火海的火焰动效

0.2.12 找回了 0.2.11 中意外丢失的「特洛伊火海」火焰动效:火焰 shader 改由场景更新循环驱动的自定义时钟供能,不再依赖在常驻渲染架构下会冻结的内置时间变量。火焰在每次打开浮层时持续燃烧,多次开关后依然保持动画,0.2.11 修复的开浮层闪烁不会回归。完整说明见 [0.2.12 发布说明](docs/RELEASE_NOTES_0.2.12.md)。
Expand Down Expand Up @@ -146,7 +150,7 @@ TokenStep 第一次从“更换配色”升级为完整的**主题皮肤包系
- 新增奥德赛弓箭阶梯 Logo;用量采用骨金、冷金或余烬橙,绿色只保留给同步成功等状态反馈。
- 关闭排行榜后浮层会自动收短,多来源额度则使用紧凑布局完整展示。

打开 `设置 → 通用 → 主题皮肤包` 即可切换。完整说明见 [0.2.4 发布说明](docs/RELEASE_NOTES_0.2.4.md),或直接[下载已签名并通过 Apple 公证的最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.11.dmg)。
打开 `设置 → 通用 → 主题皮肤包` 即可切换。完整说明见 [0.2.4 发布说明](docs/RELEASE_NOTES_0.2.4.md),或直接[下载已签名并通过 Apple 公证的最新版](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.13.dmg)。

## TokenStep 适合谁?

Expand Down Expand Up @@ -205,7 +209,7 @@ TokenStep 默认只做本地统计。

## 安装方式

1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.11.dmg)。
1. 下载 [TokenStep 最新版 DMG](https://github.com/Backtthefuture/TokenStep/releases/latest/download/TokenStep-0.2.13.dmg)。
2. 打开 DMG。
3. 把 `TokenStep.app` 拖到「应用程序」。
4. 启动 TokenStep。
Expand Down Expand Up @@ -262,18 +266,10 @@ TokenStepSwift/dist/TokenStep.app

## 发布打包

Developer ID 签名:

```bash
TOKENSTEP_VERSION=0.2.11 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
./script/package_release.sh
```

签名 + Apple 公证:
公开发布强制执行 Developer ID 签名、Apple 公证、票据装订、系统分发检查和隔离安装验证。不再生成可被误上传的“仅签名、未公证”发布包:

```bash
TOKENSTEP_VERSION=0.2.11 \
TOKENSTEP_VERSION=0.2.13 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
./script/package_release.sh --notarize
Expand All @@ -284,11 +280,12 @@ TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
```text
release/TokenStep-<version>.zip
release/TokenStep-<version>.dmg
release/TokenStep-<version>-SHA256SUMS.txt
```

维护者说明见 [docs/RELEASE.md](docs/RELEASE.md)。

0.2.11 发布说明见 [docs/RELEASE_NOTES_0.2.11.md](docs/RELEASE_NOTES_0.2.11.md);0.2.10 发布说明见 [docs/RELEASE_NOTES_0.2.10.md](docs/RELEASE_NOTES_0.2.10.md);0.2.9 发布说明见 [docs/RELEASE_NOTES_0.2.9.md](docs/RELEASE_NOTES_0.2.9.md);0.2.8 发布说明见 [docs/RELEASE_NOTES_0.2.8.md](docs/RELEASE_NOTES_0.2.8.md);0.2.7 发布说明见 [docs/RELEASE_NOTES_0.2.7.md](docs/RELEASE_NOTES_0.2.7.md);引力边界实现说明见 [docs/INTERSTELLAR_THEME_PACK_0.2.7.md](docs/INTERSTELLAR_THEME_PACK_0.2.7.md);0.2.6 更新闭环说明见 [docs/RELEASE_NOTES_0.2.6.md](docs/RELEASE_NOTES_0.2.6.md);0.2.4 奥德赛主题包说明见 [docs/ODYSSEY_THEME_PACK_0.2.4.md](docs/ODYSSEY_THEME_PACK_0.2.4.md)。
0.2.13 发布说明见 [docs/RELEASE_NOTES_0.2.13.md](docs/RELEASE_NOTES_0.2.13.md);0.2.12 发布说明见 [docs/RELEASE_NOTES_0.2.12.md](docs/RELEASE_NOTES_0.2.12.md);0.2.11 发布说明见 [docs/RELEASE_NOTES_0.2.11.md](docs/RELEASE_NOTES_0.2.11.md);0.2.10 发布说明见 [docs/RELEASE_NOTES_0.2.10.md](docs/RELEASE_NOTES_0.2.10.md);0.2.9 发布说明见 [docs/RELEASE_NOTES_0.2.9.md](docs/RELEASE_NOTES_0.2.9.md);0.2.8 发布说明见 [docs/RELEASE_NOTES_0.2.8.md](docs/RELEASE_NOTES_0.2.8.md);0.2.7 发布说明见 [docs/RELEASE_NOTES_0.2.7.md](docs/RELEASE_NOTES_0.2.7.md);引力边界实现说明见 [docs/INTERSTELLAR_THEME_PACK_0.2.7.md](docs/INTERSTELLAR_THEME_PACK_0.2.7.md);0.2.6 更新闭环说明见 [docs/RELEASE_NOTES_0.2.6.md](docs/RELEASE_NOTES_0.2.6.md);0.2.4 奥德赛主题包说明见 [docs/ODYSSEY_THEME_PACK_0.2.4.md](docs/ODYSSEY_THEME_PACK_0.2.4.md)。

## 开源协议

Expand Down
48 changes: 25 additions & 23 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,13 @@ Check local signing identities:
security find-identity -p codesigning -v
```

## Build and Sign
## Build Without Publishing

```bash
TOKENSTEP_VERSION=0.1.0 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
./script/package_release.sh
TOKENSTEP_VERSION=0.2.13 ./script/build_swiftui_and_run.sh --no-launch
```

This creates:

```text
release/TokenStep-0.1.0.zip
release/TokenStep-0.1.0.dmg
```
This produces a local development app only. It does not create anything under `release/` and must not be uploaded as a public build.

## Configure Notarization

Expand All @@ -41,10 +34,10 @@ xcrun notarytool store-credentials tokenstep-notary \
--password "app-specific-password"
```

Then release with notarization:
Every public package is notarized. The release script has no sign-only public mode:

```bash
TOKENSTEP_VERSION=0.1.0 \
TOKENSTEP_VERSION=0.2.13 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
./script/package_release.sh --notarize
Expand All @@ -53,7 +46,7 @@ TOKENSTEP_NOTARY_PROFILE="tokenstep-notary" \
Alternatively, pass credentials through environment variables:

```bash
TOKENSTEP_VERSION=0.1.0 \
TOKENSTEP_VERSION=0.2.13 \
CODE_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)" \
APPLE_ID="you@example.com" \
APPLE_TEAM_ID="TEAMID" \
Expand All @@ -65,21 +58,30 @@ Do not commit Apple credentials to the repository.

## Validate

After notarization:
The packaging command already runs all of these gates and fails before producing a publishable checksum file if any gate fails:

```bash
spctl -a -vv TokenStepSwift/dist/TokenStep.app
spctl -a -vv -t install release/TokenStep-0.1.0.dmg
xcrun stapler validate TokenStepSwift/dist/TokenStep.app
xcrun stapler validate release/TokenStep-0.1.0.dmg
./script/verify_release_artifacts.sh \
release/TokenStep-0.2.13.dmg \
release/TokenStep-0.2.13.zip \
0.2.13

./script/verify_update_installer.sh \
release/TokenStep-0.2.13.dmg \
0.2.13 \
TokenStepSwift/dist/TokenStep.app/Contents/Helpers/TokenStepHelper
```

`verify_release_artifacts.sh` requires valid code signatures, stapled notarization tickets, `syspolicy_check distribution`, Gatekeeper assessment, exact version, and the expected Team ID. This remains authoritative even if the maintainer Mac has Gatekeeper assessments disabled.

## Publish to GitHub

1. Create a GitHub Release for the version tag.
2. Upload the notarized DMG.
3. Upload the ZIP as a fallback artifact.
4. Include a short changelog and supported clients.
1. Merge the release commit to `main` and wait for CI.
2. Run the repository's `Release` workflow from `main` with the exact version.
3. The workflow creates a draft and uploads the notarized DMG, ZIP, and checksum file.
4. The workflow downloads the draft assets, checks their hashes, reruns distribution and isolated-installer verification, and only then publishes the release as Latest.

Do not manually upload artifacts that did not pass this workflow. A failed post-upload check must leave the release as a draft, never as a public release.

## GitHub Actions Release

Expand All @@ -93,6 +95,6 @@ The repository includes a manual Release workflow. Configure these repository se
- `APPLE_TEAM_ID`: Apple Developer Team ID
- `APPLE_APP_PASSWORD`: app-specific password for notarization

Then run the `Release` workflow manually with a version number such as `0.1.0`.
Then run the `Release` workflow manually from `main` with a version number such as `0.2.13`.

Apple's official overview is here: [Notarizing macOS software before distribution](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution).
17 changes: 17 additions & 0 deletions docs/RELEASE_NOTES_0.2.13.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TokenStep 0.2.13

TokenStep 0.2.13 restores automatic updates for users blocked by the missing Apple notarization ticket in the 0.2.12 release artifacts.

## Fixed

- Rebuild the current TokenStep app as version 0.2.13 with Developer ID signing, Apple notarization, and stapled tickets on both the app and DMG.
- Users on 0.2.11 can install this update without disabling Gatekeeper or changing macOS security settings.
- Keep the Trojan Inferno flame-animation and popover-flash fixes from 0.2.12 unchanged.

## Release safety

- Public packaging now requires explicit version, signing identity, notarization credentials, release notes, and an available macOS distribution-policy checker.
- Every app, ZIP, and DMG must pass code-signature, notarization-ticket, Gatekeeper, version, Team ID, and isolated update-installer verification.
- GitHub Releases are created as drafts, downloaded again, checksum-verified, and only then published as Latest.

Token totals, costs, quotas, rankings, collectors, settings, and theme behavior are unchanged.
2 changes: 1 addition & 1 deletion script/build_swiftui_and_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ HELPER_EXECUTABLE="$BUILD_DIR/$HELPER_NAME"
ICON_FILE="$ROOT_DIR/TokenUsageMenuApp/assets/TokenStepIcon.icns"
ODYSSEY_ASSET_DIR="$ROOT_DIR/TokenUsageMenuApp/assets/odyssey"
INTERSTELLAR_ASSET_DIR="$ROOT_DIR/TokenUsageMenuApp/assets/interstellar"
VERSION="${TOKENSTEP_VERSION:-0.2.12}"
VERSION="${TOKENSTEP_VERSION:-0.2.13}"
LAUNCH=true
VERIFY=false

Expand Down
Loading
Loading