name: release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
env:
CARGO_TERM_COLOR: always
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1
with:
changelog: CHANGELOG.md
token: ${{ secrets.GITHUB_TOKEN }}
upload-assets:
needs: create-release
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
build_tool: cargo-zigbuild
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
build_tool: cargo-zigbuild
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
build_tool: cargo-zigbuild
- target: x86_64-apple-darwin
os: macos-latest
- target: aarch64-apple-darwin
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
bin: git-agent-verdict
target: ${{ matrix.target }}
build-tool: ${{ matrix.build_tool || 'cargo' }}
archive: $bin-$target
checksum: sha256
token: ${{ secrets.GITHUB_TOKEN }}
publish-crate:
needs: create-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Publish unless this version is already on crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -eu
# Capture OUTSIDE the checkout: cargo publish refuses on a dirty working
# directory, so a log file written here fails the publish it is recording.
err="$(mktemp)"
if cargo publish 2>"$err"; then
exit 0
fi
cat "$err" >&2
grep -qE 'already (exists|been uploaded|uploaded)' "$err"
echo "Version already on crates.io; nothing to do."
publish-npm:
needs: upload-assets
runs-on: ubuntu-latest
permissions:
id-token: write contents: read
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- run: npm install -g npm@latest && npm --version
- name: Publish to npm
run: |
set -euo pipefail
# The manifest is the one version there is. A tag disagreeing with it would
# put a number on npm that no other channel carries.
version="$(sed -n '/^\[package\]/,/^\[/s/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)"
[ "${GITHUB_REF_NAME#v}" = "$version" ] || { echo "tag $GITHUB_REF_NAME is not Cargo.toml $version" >&2; exit 1; }
stage="$(mktemp -d)"
main="$stage/git-agent-verdict"
mkdir -p "$main/bin"
for entry in "linux-x64-musl x86_64-unknown-linux-musl linux x64" \
"linux-arm64-musl aarch64-unknown-linux-musl linux arm64" \
"darwin-x64 x86_64-apple-darwin darwin x64" \
"darwin-arm64 aarch64-apple-darwin darwin arm64"; do
set -- $entry
plat="$1"; target="$2"; os="$3"; cpu="$4"
mkdir -p "$stage/$plat"
gh release download "$GITHUB_REF_NAME" -p "git-agent-verdict-$target.tar.gz" -O "$stage/$target.tar.gz" --clobber
tar -xzf "$stage/$target.tar.gz" -C "$stage/$plat"
chmod 755 "$stage/$plat/git-agent-verdict"
# Both linux binaries are static musl, so each serves glibc hosts as well.
libc=""
[ "$os" = linux ] && libc='"libc": ["musl", "glibc"],'
# No bin field: the command belongs to the main package, and a second
# claim on it would leave the symlink to chance.
cat > "$stage/$plat/package.json" <<EOF
{
"name": "git-agent-verdict-$plat",
"version": "$version",
"description": "Prebuilt git-agent-verdict binary for $os $cpu. Installed automatically as an optional dependency of git-agent-verdict.",
"license": "MIT",
"repository": { "type": "git", "url": "git+https://github.com/fredrikolis/git-agent-verdict.git" },
"os": ["$os"],
"cpu": ["$cpu"],
$libc
"files": ["git-agent-verdict"]
}
EOF
done
cat > "$main/package.json" <<EOF
{
"name": "git-agent-verdict",
"version": "$version",
"description": "Blocks agents from committing code that does not comply with your organization's standards.",
"keywords": ["cli", "git", "hook", "code-review", "agents"],
"homepage": "https://github.com/fredrikolis/git-agent-verdict",
"repository": { "type": "git", "url": "git+https://github.com/fredrikolis/git-agent-verdict.git" },
"license": "MIT",
"bin": { "git-agent-verdict": "bin/git-agent-verdict.js" },
"files": ["bin/git-agent-verdict.js"],
"engines": { "node": ">=18" },
"optionalDependencies": {
"git-agent-verdict-linux-x64-musl": "$version",
"git-agent-verdict-linux-arm64-musl": "$version",
"git-agent-verdict-darwin-x64": "$version",
"git-agent-verdict-darwin-arm64": "$version"
}
}
EOF
# The shebang is load-bearing: npm links this file as the command, and git
# runs it as the agent-verdict subcommand. There is no postinstall step, so
# an install with --ignore-scripts or no network still works.
cat > "$main/bin/git-agent-verdict.js" <<'JS'
#!/usr/bin/env node
"use strict";
const path = require("path");
const { execFileSync } = require("child_process");
const PACKAGES = {
"linux-x64": "git-agent-verdict-linux-x64-musl",
"linux-arm64": "git-agent-verdict-linux-arm64-musl",
"darwin-x64": "git-agent-verdict-darwin-x64",
"darwin-arm64": "git-agent-verdict-darwin-arm64",
};
function fail(message) {
process.stderr.write(`git-agent-verdict: ${message}\n`);
process.exit(1);
}
const key = `${process.platform}-${process.arch}`;
const pkg = PACKAGES[key];
if (!pkg) {
fail(`no prebuilt binary is published for ${key}: cargo install git-agent-verdict`);
}
let bin;
try {
bin = path.join(path.dirname(require.resolve(`${pkg}/package.json`)), "git-agent-verdict");
} catch (_err) {
fail(`the binary for ${key} is missing: npm install -g git-agent-verdict, without --no-optional`);
}
try {
execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
} catch (err) {
// The binary's own status, which is what a commit-msg hook reads.
if (typeof err.status === "number") {
process.exit(err.status);
}
fail(err.signal ? `killed by signal ${err.signal}` : err.message);
}
JS
chmod 755 "$main/bin/git-agent-verdict.js"
# The npmjs.com page is this repository's README, copied at publish time so
# no second copy of it exists to drift.
cp README.md "$main/README.md"
# The platform packages first: the main package pins them, and npm refuses
# an optional dependency at a version that does not exist yet.
for plat in linux-x64-musl linux-arm64-musl darwin-x64 darwin-arm64; do
npm publish ${NPM_PUBLISH_ARGS:-} "$stage/$plat"
done
npm publish ${NPM_PUBLISH_ARGS:-} "$main"