Skip to content

Commit 2a8aa23

Browse files
committed
release: add crates, npm, and homebrew publish pipeline
1 parent 66fd2c9 commit 2a8aa23

File tree

8 files changed

+371
-7
lines changed

8 files changed

+371
-7
lines changed

.github/workflows/release.yml

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ permissions:
1313

1414
env:
1515
CARGO_TERM_COLOR: always
16+
HOMEBREW_TAP_REPO: hanbu97/homebrew-tap
1617

1718
jobs:
1819
build:
@@ -55,6 +56,8 @@ jobs:
5556
cp "target/${{ matrix.target }}/release/tu" "dist/${ASSET_NAME}/"
5657
cp README.md LICENSE "dist/${ASSET_NAME}/"
5758
tar -C dist -czf "dist/${ASSET_NAME}.tar.gz" "${ASSET_NAME}"
59+
cp "target/${{ matrix.target }}/release/tu" "dist/${ASSET_NAME}"
60+
chmod +x "dist/${ASSET_NAME}"
5861
rm -rf "dist/${ASSET_NAME}"
5962
6063
- name: Package archive (Windows)
@@ -66,6 +69,7 @@ jobs:
6669
Copy-Item "target/${{ matrix.target }}/release/tu.exe" "dist/$assetName/"
6770
Copy-Item "README.md","LICENSE" "dist/$assetName/"
6871
Compress-Archive -Path "dist/$assetName/*" -DestinationPath "dist/$assetName.zip" -Force
72+
Copy-Item "target/${{ matrix.target }}/release/tu.exe" "dist/$assetName.exe"
6973
Remove-Item "dist/$assetName" -Recurse -Force
7074
7175
- name: Upload build artifact
@@ -91,7 +95,9 @@ jobs:
9195
run: |
9296
set -euxo pipefail
9397
cd dist
94-
sha256sum *.tar.gz *.zip > SHA256SUMS.txt
98+
shopt -s nullglob
99+
files=( *.tar.gz *.zip tu-* tu-*.exe )
100+
sha256sum "${files[@]}" > SHA256SUMS.txt
95101
96102
- name: Upload release bundle
97103
uses: actions/upload-artifact@v4
@@ -117,3 +123,124 @@ jobs:
117123
with:
118124
files: dist/*
119125
generate_release_notes: true
126+
127+
publish-crates:
128+
name: Publish crates.io
129+
runs-on: ubuntu-latest
130+
needs: checksums
131+
if: startsWith(github.ref, 'refs/tags/v') && secrets.CRATES_IO_TOKEN != ''
132+
steps:
133+
- name: Checkout
134+
uses: actions/checkout@v4
135+
136+
- name: Install Rust toolchain
137+
uses: dtolnay/rust-toolchain@stable
138+
139+
- name: Verify Cargo version matches tag
140+
run: |
141+
set -euxo pipefail
142+
tag="${GITHUB_REF_NAME#v}"
143+
cargo_version=$(grep -E '^version\\s*=\\s*\"' Cargo.toml | head -n1 | sed -E 's/.*\"([^\"]+)\".*/\\1/')
144+
test "$tag" = "$cargo_version"
145+
146+
- name: Publish crate
147+
run: cargo publish --locked --token "${{ secrets.CRATES_IO_TOKEN }}"
148+
149+
publish-npm:
150+
name: Publish npm package
151+
runs-on: ubuntu-latest
152+
needs: publish
153+
if: startsWith(github.ref, 'refs/tags/v') && secrets.NPM_TOKEN != ''
154+
steps:
155+
- name: Checkout
156+
uses: actions/checkout@v4
157+
158+
- name: Setup Node
159+
uses: actions/setup-node@v4
160+
with:
161+
node-version: "20"
162+
registry-url: "https://registry.npmjs.org"
163+
164+
- name: Verify npm package version matches tag
165+
run: |
166+
set -euxo pipefail
167+
tag="${GITHUB_REF_NAME#v}"
168+
npm_version=$(node -p "require('./npm/tu/package.json').version")
169+
test "$tag" = "$npm_version"
170+
171+
- name: Publish npm package
172+
working-directory: npm/tu
173+
run: npm publish --access public
174+
env:
175+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
176+
177+
publish-homebrew-tap:
178+
name: Publish Homebrew tap formula
179+
runs-on: ubuntu-latest
180+
needs: checksums
181+
if: startsWith(github.ref, 'refs/tags/v') && secrets.HOMEBREW_TAP_PAT != ''
182+
steps:
183+
- name: Download release bundle
184+
uses: actions/download-artifact@v4
185+
with:
186+
name: tu-release-bundle
187+
path: dist
188+
189+
- name: Generate Formula
190+
run: |
191+
set -euxo pipefail
192+
version="${GITHUB_REF_NAME#v}"
193+
linux_sha=$(sha256sum "dist/tu-${GITHUB_REF_NAME}-x86_64-unknown-linux-gnu.tar.gz" | awk '{print $1}')
194+
mac_arm_sha=$(sha256sum "dist/tu-${GITHUB_REF_NAME}-aarch64-apple-darwin.tar.gz" | awk '{print $1}')
195+
mac_x64_sha=$(sha256sum "dist/tu-${GITHUB_REF_NAME}-x86_64-apple-darwin.tar.gz" | awk '{print $1}')
196+
197+
mkdir -p out/Formula
198+
cat > out/Formula/tu.rb <<RUBY
199+
class Tu < Formula
200+
desc "Blazing-fast Rust token usage tracker for Codex and Claude Code"
201+
homepage "https://github.com/hanbu97/tokenusage"
202+
license "MIT"
203+
version "${version}"
204+
205+
on_macos do
206+
if Hardware::CPU.arm?
207+
url "https://github.com/hanbu97/tokenusage/releases/download/v${version}/tu-v${version}-aarch64-apple-darwin.tar.gz"
208+
sha256 "${mac_arm_sha}"
209+
else
210+
url "https://github.com/hanbu97/tokenusage/releases/download/v${version}/tu-v${version}-x86_64-apple-darwin.tar.gz"
211+
sha256 "${mac_x64_sha}"
212+
end
213+
end
214+
215+
on_linux do
216+
url "https://github.com/hanbu97/tokenusage/releases/download/v${version}/tu-v${version}-x86_64-unknown-linux-gnu.tar.gz"
217+
sha256 "${linux_sha}"
218+
end
219+
220+
def install
221+
bin.install "tu"
222+
prefix.install "README.md", "LICENSE"
223+
end
224+
225+
test do
226+
assert_match "tokenusage", shell_output("#{bin}/tu --version")
227+
end
228+
end
229+
RUBY
230+
231+
- name: Update tap repository
232+
run: |
233+
set -euxo pipefail
234+
git clone "https://x-access-token:${{ secrets.HOMEBREW_TAP_PAT }}@github.com/${HOMEBREW_TAP_REPO}.git" tap
235+
mkdir -p tap/Formula
236+
cp out/Formula/tu.rb tap/Formula/tu.rb
237+
cd tap
238+
git config user.name "github-actions[bot]"
239+
git config user.email "github-actions[bot]@users.noreply.github.com"
240+
git add Formula/tu.rb
241+
if git diff --cached --quiet; then
242+
echo "No formula changes."
243+
exit 0
244+
fi
245+
git commit -m "chore: update tu formula for ${GITHUB_REF_NAME}"
246+
git push origin HEAD:main

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
/target
2+
npm/tu/*.tgz
3+
npm/tu/vendor/

README.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,29 @@ This project is built for people searching for:
3636

3737
## Install
3838

39-
### From source
39+
### cargo (crates.io)
4040

4141
```bash
42-
cargo install --path . --bin tu --force
42+
cargo install tokenusage --bin tu
4343
```
4444

45-
### From crates.io (after publish)
45+
### Homebrew
4646

4747
```bash
48-
cargo install tokenusage --bin tu
48+
brew tap hanbu97/tap
49+
brew install tu
50+
```
51+
52+
### npm
53+
54+
```bash
55+
npm install -g @hanbu97/tokenusage
56+
```
57+
58+
### From source
59+
60+
```bash
61+
cargo install --path . --bin tu --force
4962
```
5063

5164
## Quick Start
@@ -234,14 +247,31 @@ cargo check
234247

235248
## Release (Maintainers)
236249

237-
Tag-based release is enabled in GitHub Actions.
250+
Tag-based release is enabled in GitHub Actions (`.github/workflows/release.yml`).
251+
252+
On tag push (`vX.Y.Z`), the pipeline:
253+
- builds and uploads release assets (Linux/macOS/Windows)
254+
- publishes GitHub Release
255+
- optionally publishes to crates.io / npm / Homebrew tap (if secrets are configured)
256+
257+
Required repository secrets for full multi-channel publish:
258+
- `CRATES_IO_TOKEN`
259+
- `NPM_TOKEN`
260+
- `HOMEBREW_TAP_PAT`
261+
262+
If a secret is missing, the corresponding publish job is automatically skipped.
263+
264+
Homebrew tap target repo defaults to:
265+
- `hanbu97/homebrew-tap` (tap name: `hanbu97/tap`)
238266

239267
```bash
240268
git tag v0.1.0
241269
git push origin v0.1.0
242270
```
243271

244-
This will build multi-platform binaries, generate `SHA256SUMS.txt`, and publish assets to GitHub Releases.
272+
Version sync requirement before tagging:
273+
- `Cargo.toml` `package.version` must equal tag without `v`
274+
- `npm/tu/package.json` `version` must equal tag without `v`
245275

246276
## License
247277

npm/tu/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 tokenusage contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

npm/tu/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# @hanbu97/tokenusage
2+
3+
NPM distribution for [`tokenusage`](https://github.com/hanbu97/tokenusage).
4+
5+
## Install
6+
7+
```bash
8+
npm i -g @hanbu97/tokenusage
9+
```
10+
11+
## Usage
12+
13+
```bash
14+
tu --help
15+
```

npm/tu/bin/tu.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env node
2+
const { spawnSync } = require('node:child_process');
3+
const fs = require('node:fs');
4+
const path = require('node:path');
5+
6+
const isWindows = process.platform === 'win32';
7+
const binName = isWindows ? 'tu.exe' : 'tu';
8+
const binPath = path.join(__dirname, '..', 'vendor', binName);
9+
10+
if (!fs.existsSync(binPath)) {
11+
console.error('[tokenusage] Native binary is missing.');
12+
console.error('[tokenusage] Try reinstalling: npm i -g @hanbu97/tokenusage');
13+
process.exit(1);
14+
}
15+
16+
const child = spawnSync(binPath, process.argv.slice(2), {
17+
stdio: 'inherit',
18+
});
19+
20+
if (child.error) {
21+
console.error(`[tokenusage] Failed to launch ${binName}:`, child.error.message);
22+
process.exit(1);
23+
}
24+
25+
process.exit(child.status ?? 1);

npm/tu/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@hanbu97/tokenusage",
3+
"version": "0.1.0",
4+
"description": "Blazing-fast Rust token usage tracker for Codex and Claude Code.",
5+
"license": "MIT",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/hanbu97/tokenusage.git"
9+
},
10+
"homepage": "https://github.com/hanbu97/tokenusage#readme",
11+
"bugs": {
12+
"url": "https://github.com/hanbu97/tokenusage/issues"
13+
},
14+
"bin": {
15+
"tu": "bin/tu.js"
16+
},
17+
"files": [
18+
"bin",
19+
"scripts",
20+
"vendor",
21+
"README.md",
22+
"LICENSE"
23+
],
24+
"scripts": {
25+
"postinstall": "node ./scripts/postinstall.js"
26+
},
27+
"engines": {
28+
"node": ">=18"
29+
},
30+
"os": [
31+
"darwin",
32+
"linux",
33+
"win32"
34+
],
35+
"keywords": [
36+
"codex",
37+
"claude-code",
38+
"token-usage",
39+
"cli",
40+
"rust"
41+
]
42+
}

0 commit comments

Comments
 (0)