Skip to content

Commit 1ac34f3

Browse files
committed
Add multi-platform release pipeline with npm, Homebrew, and binary distribution
- Release workflow building 8 targets (Linux gnu/musl, macOS, Windows on x64/ARM64) - macOS codesigning with Developer ID certificate and notarization via API key - npm distribution using per-platform optional dependency pattern with OIDC trusted publishing - Homebrew tap auto-update with checksums from release artifacts - Tabbed install section on docs site and updated README with all install methods - Migrate repository references from felipefdl/no to network-output/no
1 parent 83c6fd3 commit 1ac34f3

17 files changed

Lines changed: 686 additions & 8 deletions

File tree

.github/workflows/release.yml

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: write
9+
id-token: write
10+
11+
env:
12+
CARGO_TERM_COLOR: always
13+
14+
jobs:
15+
build:
16+
name: Build (${{ matrix.name }})
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
include:
21+
- name: Linux x86_64
22+
target: x86_64-unknown-linux-gnu
23+
runner: ubuntu-latest
24+
archive: tar.gz
25+
install-deps: libssl-dev pkg-config
26+
- name: Linux ARM64
27+
target: aarch64-unknown-linux-gnu
28+
runner: ubuntu-24.04-arm
29+
archive: tar.gz
30+
install-deps: libssl-dev pkg-config
31+
- name: Linux x86_64 musl
32+
target: x86_64-unknown-linux-musl
33+
runner: ubuntu-latest
34+
archive: tar.gz
35+
install-deps: musl-tools pkg-config
36+
extra-cargo-flags: --features reqwest/native-tls-vendored
37+
- name: Linux ARM64 musl
38+
target: aarch64-unknown-linux-musl
39+
runner: ubuntu-24.04-arm
40+
archive: tar.gz
41+
install-deps: musl-tools pkg-config
42+
extra-cargo-flags: --features reqwest/native-tls-vendored
43+
- name: macOS x86_64
44+
target: x86_64-apple-darwin
45+
runner: macos-15-intel
46+
archive: tar.gz
47+
- name: macOS ARM64
48+
target: aarch64-apple-darwin
49+
runner: macos-latest
50+
archive: tar.gz
51+
- name: Windows x86_64
52+
target: x86_64-pc-windows-msvc
53+
runner: windows-latest
54+
archive: zip
55+
- name: Windows ARM64
56+
target: aarch64-pc-windows-msvc
57+
runner: windows-11-arm
58+
archive: zip
59+
runs-on: ${{ matrix.runner }}
60+
steps:
61+
- uses: actions/checkout@v4
62+
63+
- uses: dtolnay/rust-toolchain@stable
64+
with:
65+
targets: ${{ matrix.target }}
66+
67+
- uses: Swatinem/rust-cache@v2
68+
with:
69+
key: release-${{ matrix.target }}
70+
71+
- name: Install system dependencies
72+
if: matrix.install-deps
73+
run: sudo apt-get update && sudo apt-get install -y ${{ matrix.install-deps }}
74+
75+
- name: Build
76+
run: cargo build --release --target ${{ matrix.target }} ${{ matrix.extra-cargo-flags }}
77+
78+
- name: Import codesigning certificate
79+
if: runner.os == 'macOS'
80+
env:
81+
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
82+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
83+
run: |
84+
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
85+
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
86+
KEYCHAIN_PASSWORD=$(uuidgen)
87+
88+
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
89+
90+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
91+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
92+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
93+
94+
security import "$CERTIFICATE_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" \
95+
-A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
96+
security set-key-partition-list -S apple-tool:,apple: \
97+
-s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
98+
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
99+
100+
- name: Codesign binary
101+
if: runner.os == 'macOS'
102+
env:
103+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
104+
run: |
105+
codesign --sign "Developer ID Application: Felipe Lima ($APPLE_TEAM_ID)" \
106+
--options runtime --timestamp \
107+
target/${{ matrix.target }}/release/no
108+
109+
- name: Package (tar.gz)
110+
if: matrix.archive == 'tar.gz'
111+
run: |
112+
VERSION="${GITHUB_REF_NAME#v}"
113+
ARCHIVE="no-v${VERSION}-${{ matrix.target }}.tar.gz"
114+
tar -czf "$ARCHIVE" -C target/${{ matrix.target }}/release no
115+
shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
116+
echo "ARCHIVE=$ARCHIVE" >> $GITHUB_ENV
117+
118+
- name: Package (zip)
119+
if: matrix.archive == 'zip'
120+
shell: pwsh
121+
run: |
122+
$version = "$env:GITHUB_REF_NAME" -replace '^v', ''
123+
$archive = "no-v${version}-${{ matrix.target }}.zip"
124+
Compress-Archive -Path "target/${{ matrix.target }}/release/no.exe" -DestinationPath $archive
125+
$hash = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLower()
126+
"$hash $archive" | Out-File -Encoding ascii "${archive}.sha256"
127+
"ARCHIVE=$archive" | Out-File -Append $env:GITHUB_ENV
128+
129+
- name: Upload artifacts
130+
uses: actions/upload-artifact@v4
131+
with:
132+
name: binary-${{ matrix.target }}
133+
path: |
134+
${{ env.ARCHIVE }}
135+
${{ env.ARCHIVE }}.sha256
136+
137+
upload:
138+
name: Upload to release
139+
needs: build
140+
runs-on: macos-latest
141+
steps:
142+
- uses: actions/checkout@v4
143+
144+
- name: Download all artifacts
145+
uses: actions/download-artifact@v4
146+
with:
147+
path: artifacts
148+
pattern: binary-*
149+
merge-multiple: true
150+
151+
- name: Generate combined checksums
152+
working-directory: artifacts
153+
run: |
154+
cat *.sha256 > checksums.txt
155+
cat checksums.txt
156+
157+
- name: Upload to GitHub release
158+
env:
159+
GH_TOKEN: ${{ github.token }}
160+
run: |
161+
gh release upload "$GITHUB_REF_NAME" \
162+
artifacts/*.tar.gz artifacts/*.zip artifacts/*.sha256 artifacts/checksums.txt \
163+
--clobber
164+
165+
- name: Notarize macOS binaries
166+
env:
167+
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
168+
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
169+
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
170+
run: |
171+
KEY_PATH=$RUNNER_TEMP/AuthKey.p8
172+
echo "$APPLE_API_KEY_BASE64" | base64 --decode > "$KEY_PATH"
173+
174+
for archive in artifacts/*apple-darwin*.tar.gz; do
175+
echo "Submitting $archive for notarization..."
176+
xcrun notarytool submit "$archive" \
177+
--key "$KEY_PATH" \
178+
--key-id "$APPLE_API_KEY_ID" \
179+
--issuer "$APPLE_API_ISSUER_ID" \
180+
--wait
181+
done
182+
183+
npm:
184+
name: Publish to npm
185+
needs: upload
186+
runs-on: ubuntu-latest
187+
permissions:
188+
contents: read
189+
id-token: write
190+
steps:
191+
- uses: actions/checkout@v4
192+
193+
- uses: actions/setup-node@v4
194+
with:
195+
node-version: "24"
196+
registry-url: "https://registry.npmjs.org"
197+
198+
- name: Download all artifacts
199+
uses: actions/download-artifact@v4
200+
with:
201+
path: artifacts
202+
pattern: binary-*
203+
merge-multiple: true
204+
205+
- name: Extract version
206+
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
207+
208+
- name: Publish platform packages
209+
env:
210+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
211+
run: |
212+
declare -A TARGET_MAP=(
213+
["no-darwin-arm64"]="aarch64-apple-darwin"
214+
["no-darwin-x64"]="x86_64-apple-darwin"
215+
["no-linux-arm64"]="aarch64-unknown-linux-gnu"
216+
["no-linux-x64"]="x86_64-unknown-linux-gnu"
217+
["no-linux-arm64-musl"]="aarch64-unknown-linux-musl"
218+
["no-linux-x64-musl"]="x86_64-unknown-linux-musl"
219+
["no-win32-arm64"]="aarch64-pc-windows-msvc"
220+
["no-win32-x64"]="x86_64-pc-windows-msvc"
221+
)
222+
223+
for pkg in "${!TARGET_MAP[@]}"; do
224+
target="${TARGET_MAP[$pkg]}"
225+
pkg_dir="npm/@network-output/$pkg"
226+
227+
if [[ "$target" == *windows* ]]; then
228+
archive="artifacts/no-v${VERSION}-${target}.zip"
229+
unzip -o "$archive" -d "$pkg_dir/"
230+
else
231+
archive="artifacts/no-v${VERSION}-${target}.tar.gz"
232+
tar -xzf "$archive" -C "$pkg_dir/"
233+
fi
234+
235+
cd "$pkg_dir"
236+
npm version "$VERSION" --no-git-tag-version --allow-same-version
237+
npm publish --provenance --access public
238+
cd "$GITHUB_WORKSPACE"
239+
done
240+
241+
- name: Publish root package
242+
env:
243+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
244+
run: |
245+
cd npm/network-output
246+
247+
npm version "$VERSION" --no-git-tag-version --allow-same-version
248+
249+
node -e "
250+
const fs = require('fs');
251+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
252+
for (const dep of Object.keys(pkg.optionalDependencies)) {
253+
pkg.optionalDependencies[dep] = '$VERSION';
254+
}
255+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
256+
"
257+
258+
npm publish --provenance --access public
259+
260+
homebrew:
261+
name: Update Homebrew tap
262+
needs: upload
263+
runs-on: ubuntu-latest
264+
steps:
265+
- uses: actions/checkout@v4
266+
267+
- name: Download checksums from release
268+
env:
269+
GH_TOKEN: ${{ github.token }}
270+
run: |
271+
gh release download "$GITHUB_REF_NAME" --pattern "checksums.txt" --dir .
272+
273+
- name: Extract version and checksums
274+
run: |
275+
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
276+
277+
for target in aarch64-apple-darwin x86_64-apple-darwin aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu; do
278+
sha=$(grep "no-v.*-${target}.tar.gz" checksums.txt | awk '{print $1}')
279+
var_name=$(echo "$target" | tr '-' '_' | tr '[:lower:]' '[:upper:]')
280+
echo "SHA_${var_name}=${sha}" >> $GITHUB_ENV
281+
done
282+
283+
- name: Update Homebrew formula
284+
env:
285+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
286+
run: |
287+
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/network-output/homebrew-tap.git" tap
288+
mkdir -p tap/Formula
289+
290+
cp homebrew/Formula/network-output.rb tap/Formula/network-output.rb
291+
292+
cd tap
293+
sed -i 's/version ".*"/version "'"$VERSION"'"/' Formula/network-output.rb
294+
295+
declare -A SHA_MAP=(
296+
["aarch64-apple-darwin"]="$SHA_AARCH64_APPLE_DARWIN"
297+
["x86_64-apple-darwin"]="$SHA_X86_64_APPLE_DARWIN"
298+
["aarch64-unknown-linux-gnu"]="$SHA_AARCH64_UNKNOWN_LINUX_GNU"
299+
["x86_64-unknown-linux-gnu"]="$SHA_X86_64_UNKNOWN_LINUX_GNU"
300+
)
301+
302+
for target in "${!SHA_MAP[@]}"; do
303+
sha="${SHA_MAP[$target]}"
304+
# Replace the PLACEHOLDER on the line following the URL for this target
305+
sed -i "/${target}/{ n; s/\"PLACEHOLDER\"/\"${sha}\"/ }" Formula/network-output.rb
306+
done
307+
308+
git config user.name "github-actions[bot]"
309+
git config user.email "github-actions[bot]@users.noreply.github.com"
310+
git add Formula/network-output.rb
311+
git commit -m "Update network-output to $VERSION"
312+
git push

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ Desktop.ini
2020
# Environment
2121
.env
2222
.env.*
23+
24+
# npm platform binaries (placed by CI during publish)
25+
npm/@network-output/*/no
26+
npm/@network-output/*/no.exe

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ rust-version = "1.85"
66
description = "Networking CLI for HTTP, WebSocket, TCP, UDP, DNS, Ping, WHOIS, MQTT, and SSE. Structured JSON output built for machines, readable by humans."
77
license = "Apache-2.0"
88
authors = ["Felipe Lima <felipefdl@network-output.com>"]
9-
repository = "https://github.com/felipefdl/no"
9+
repository = "https://github.com/network-output/no"
1010
homepage = "https://network-output.com"
1111
readme = "README.md"
1212
keywords = ["cli", "http", "json", "networking", "websocket"]
1313
categories = ["command-line-utilities", "network-programming"]
14-
exclude = [".github/", ".claude/", "justfile", "AGENTS.md"]
14+
exclude = [".github/", ".claude/", "justfile", "AGENTS.md", "npm/", "homebrew/"]
1515

1616
[[bin]]
1717
name = "no"

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,35 @@ A fast, structured networking CLI for HTTP, WebSocket, TCP, UDP, MQTT, SSE, DNS,
66

77
## Installation
88

9+
### Cargo
10+
911
```sh
1012
cargo install network-output
1113
```
1214

13-
Or build from source:
15+
### Homebrew
16+
17+
```sh
18+
brew install network-output/tap/network-output
19+
```
20+
21+
### npm
22+
23+
```sh
24+
npx network-output
25+
```
26+
27+
For a permanent install:
28+
29+
```sh
30+
npm install -g network-output
31+
```
32+
33+
### GitHub Releases
34+
35+
Download prebuilt binaries from [GitHub Releases](https://github.com/network-output/no/releases).
36+
37+
### Build from source
1438

1539
```sh
1640
cargo install --path .

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Only the latest release is supported with security updates.
1313

1414
**Do not open a public issue for security vulnerabilities.**
1515

16-
Please report vulnerabilities through [GitHub's private security advisory feature](https://github.com/felipefdl/no/security/advisories/new).
16+
Please report vulnerabilities through [GitHub's private security advisory feature](https://github.com/network-output/no/security/advisories/new).
1717

1818
Include as much detail as possible:
1919

0 commit comments

Comments
 (0)