name: Release
on:
push:
branches:
- main
env:
NPM_SCOPE: "@taitide"
HOMEBREW_TAP: "taitide-labs/homebrew-tap"
permissions: {}
jobs:
release-please:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 id: app-token
with:
client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: write
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 id: release
with:
token: ${{ steps.app-token.outputs.token }}
build:
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-latest
ext: tar.gz
- target: x86_64-apple-darwin
runner: macos-latest
ext: tar.gz
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
ext: tar.gz
- target: aarch64-unknown-linux-gnu
runner: ubuntu-latest
ext: tar.gz
cross_linux_arm64: true
- target: x86_64-pc-windows-msvc
runner: windows-latest
ext: zip
runs-on: ${{ matrix.runner }}
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 with:
toolchain: stable
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 with:
key: ${{ matrix.target }}
- name: Install aarch64 Linux cross-linker
if: ${{ matrix.cross_linux_arm64 }}
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Run cross-platform tests
if: ${{ !matrix.cross_linux_arm64 }}
shell: bash
env:
TARGET: ${{ matrix.target }}
run: cargo test --locked --target "$TARGET"
- name: Build binary
shell: bash
env:
TARGET: ${{ matrix.target }}
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: cargo build --release --locked --target "$TARGET"
- name: Package artifact
shell: bash
env:
TARGET: ${{ matrix.target }}
EXT: ${{ matrix.ext }}
run: |
meta=$(cargo metadata --no-deps --format-version 1)
pkg_name=$(echo "$meta" | jq -r '.packages[0].name')
bin_name=$(echo "$meta" | jq -r '(.packages[0].targets[] | select(.kind | index("bin")) | .name)' | head -n 1)
bin_name="${bin_name:-$pkg_name}"
mkdir -p dist
if [ "$EXT" = "zip" ]; then
7z a "dist/${pkg_name}-${TARGET}.zip" "./target/${TARGET}/release/${bin_name}.exe"
else
tar -C "target/${TARGET}/release" -czf "dist/${pkg_name}-${TARGET}.tar.gz" "$bin_name"
fi
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 with:
name: dist-${{ matrix.target }}
path: dist/*
upload-assets:
needs: [release-please, build]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 with:
pattern: dist-*
path: artifacts
merge-multiple: true
- name: Generate checksums
run: |
cd artifacts
sha256sum *.tar.gz *.zip > SHA256SUMS.txt
- name: Attest build provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 with:
subject-path: |
artifacts/*.tar.gz
artifacts/*.zip
- name: Upload release assets and publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.release-please.outputs.tag_name }}
run: |
gh release upload "$TAG" artifacts/* --clobber
gh release edit "$TAG" --draft=false
publish-crates:
needs: [release-please, upload-assets]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: rust-lang/crates-io-auth-action@3268eb413133e2960fa4ae0e963ca76380c54cd9 id: auth
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish --locked
publish-npm:
needs: [release-please, upload-assets]
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 with:
node-version: "lts/*"
registry-url: "https://registry.npmjs.org"
package-manager-cache: false
- name: Build and publish npm wrapper
env:
TAG: ${{ needs.release-please.outputs.tag_name }}
REPO: ${{ github.repository }}
run: |
version="${TAG#v}"
meta=$(cargo metadata --no-deps --format-version 1)
pkg_name=$(echo "$meta" | jq -r '.packages[0].name')
pkg_desc=$(echo "$meta" | jq -r '.packages[0].description')
pkg_license=$(echo "$meta" | jq -r '.packages[0].license')
pkg_repo=$(echo "$meta" | jq -r '.packages[0].repository')
mkdir -p npm-pkg/bin
jq -n \
--arg name "${NPM_SCOPE}/${pkg_name}" \
--arg version "$version" \
--arg desc "$pkg_desc" \
--arg license "$pkg_license" \
--arg repo "${pkg_repo}.git" \
--arg bin "$pkg_name" \
'{
name: $name,
version: $version,
description: $desc,
license: $license,
repository: { type: "git", url: $repo },
bin: { ($bin): ("bin/" + $bin + ".js") },
scripts: { postinstall: "node install.js" }
}' > npm-pkg/package.json
cat > "npm-pkg/bin/${pkg_name}.js" <<EOF
#!/usr/bin/env node
const { spawnSync } = require("child_process");
const path = require("path");
const bin = path.join(__dirname, process.platform === "win32" ? "${pkg_name}.exe" : "${pkg_name}");
const res = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
process.exit(res.status ?? 1);
EOF
cat > npm-pkg/install.js <<EOF
const https = require("https");
const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const pkg = require("./package.json");
const map = {
"darwin-arm64": "aarch64-apple-darwin.tar.gz",
"darwin-x64": "x86_64-apple-darwin.tar.gz",
"linux-x64": "x86_64-unknown-linux-gnu.tar.gz",
"linux-arm64": "aarch64-unknown-linux-gnu.tar.gz",
"win32-x64": "x86_64-pc-windows-msvc.zip"
};
const suffix = map[\`\${process.platform}-\${process.arch}\`];
if (!suffix) throw new Error(\`Unsupported platform: \${process.platform}-\${process.arch}\`);
const url = \`https://github.com/${REPO}/releases/download/v\${pkg.version}/${pkg_name}-\${suffix}\`;
const out = path.join(__dirname, "bin", suffix);
function download(u, cb) {
https.get(u, (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
return download(res.headers.location, cb);
}
if (res.statusCode !== 200) return cb(new Error(\`HTTP \${res.statusCode}\`));
const file = fs.createWriteStream(out);
res.pipe(file).on("finish", () => file.close(cb));
}).on("error", cb);
}
download(url, (err) => {
if (err) throw err;
if (suffix.endsWith(".zip")) {
execSync(\`powershell -Command "Expand-Archive -Force '\${out}' '\${path.join(__dirname, "bin")}'"\`);
} else {
execSync(\`tar -xzf "\${out}" -C "\${path.join(__dirname, "bin")}"\`);
}
fs.unlinkSync(out);
});
EOF
npm publish --provenance --access public ./npm-pkg
publish-homebrew:
needs: [release-please, upload-assets]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
persist-credentials: false
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 with:
pattern: dist-*
path: artifacts
merge-multiple: true
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with:
repository: ${{ env.HOMEBREW_TAP }}
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap
persist-credentials: false
- name: Update Homebrew formula
env:
TAG: ${{ needs.release-please.outputs.tag_name }}
REPO: ${{ github.repository }}
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
version="${TAG#v}"
meta=$(cargo metadata --no-deps --format-version 1)
pkg_name=$(echo "$meta" | jq -r '.packages[0].name')
pkg_desc=$(echo "$meta" | jq -r '.packages[0].description')
pkg_license=$(echo "$meta" | jq -r '.packages[0].license')
pkg_home=$(echo "$meta" | jq -r '.packages[0].homepage')
formula_class=$(echo "$pkg_name" | awk -F'[-_]' '{for(i=1;i<=NF;i++) printf toupper(substr($i,1,1)) substr($i,2)}')
sha_mac_arm=$(sha256sum "artifacts/${pkg_name}-aarch64-apple-darwin.tar.gz" | awk '{print $1}')
sha_mac_x64=$(sha256sum "artifacts/${pkg_name}-x86_64-apple-darwin.tar.gz" | awk '{print $1}')
sha_lin_x64=$(sha256sum "artifacts/${pkg_name}-x86_64-unknown-linux-gnu.tar.gz" | awk '{print $1}')
sha_lin_arm=$(sha256sum "artifacts/${pkg_name}-aarch64-unknown-linux-gnu.tar.gz" | awk '{print $1}')
mkdir -p tap/Formula
cat > "tap/Formula/${pkg_name}.rb" <<EOF
class ${formula_class} < Formula
desc "${pkg_desc}"
homepage "${pkg_home}"
version "${version}"
license "${pkg_license}"
on_macos do
on_arm do
url "https://github.com/${REPO}/releases/download/${TAG}/${pkg_name}-aarch64-apple-darwin.tar.gz"
sha256 "${sha_mac_arm}"
end
on_intel do
url "https://github.com/${REPO}/releases/download/${TAG}/${pkg_name}-x86_64-apple-darwin.tar.gz"
sha256 "${sha_mac_x64}"
end
end
on_linux do
on_arm do
url "https://github.com/${REPO}/releases/download/${TAG}/${pkg_name}-aarch64-unknown-linux-gnu.tar.gz"
sha256 "${sha_lin_arm}"
end
on_intel do
url "https://github.com/${REPO}/releases/download/${TAG}/${pkg_name}-x86_64-unknown-linux-gnu.tar.gz"
sha256 "${sha_lin_x64}"
end
end
def install
bin.install "${pkg_name}"
end
test do
assert_match version.to_s, shell_output("#{bin}/${pkg_name} --version")
end
end
EOF
cd tap
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "Formula/${pkg_name}.rb"
git commit -m "${pkg_name} ${version}"
git push "https://x-access-token:${TAP_TOKEN}@github.com/${HOMEBREW_TAP}.git" HEAD