Skip to content

Release 0.11.2

Release 0.11.2 #46

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version to scrub, with or without leading v"
required: true
type: string
release_notes:
description: "Optional extra release text path to scrub during preflight"
required: false
type: string
default: ""
pr_number:
description: "Optional release PR number whose body should be scrubbed"
required: false
type: string
default: ""
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
scrub-public-text-preflight:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Scrub public release text before tag
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_NOTES: ${{ inputs.release_notes }}
RELEASE_PR_NUMBER: ${{ inputs.pr_number }}
RELEASE_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
version="${RELEASE_VERSION#v}"
if [ -z "$version" ]; then
echo "scrub-public-text failed: release preflight requires a version" >&2
exit 1
fi
changelog_section="$(mktemp)"
awk -v version="$version" '
index($0, "## [" version "]") == 1 { in_section=1; print; next }
in_section && /^## \[/ { exit }
in_section { print }
' CHANGELOG.md > "$changelog_section"
if [ ! -s "$changelog_section" ]; then
echo "scrub-public-text failed: CHANGELOG.md has no section for v${version}" >&2
exit 1
fi
files=("$changelog_section")
if [ -n "$RELEASE_NOTES" ]; then
if [ ! -f "$RELEASE_NOTES" ]; then
echo "scrub-public-text failed: missing release notes file ${RELEASE_NOTES}" >&2
exit 1
fi
files+=("$RELEASE_NOTES")
fi
if [ -n "$RELEASE_PR_NUMBER" ]; then
pr_body="$(mktemp)"
gh pr view "$RELEASE_PR_NUMBER" --json body --jq .body > "$pr_body"
files+=("$pr_body")
fi
cargo run -p xtask -- scrub-public-text "${files[@]}"
build:
if: github.event_name == 'push'
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
target: aarch64-apple-darwin
artifact: gaze-aarch64-apple-darwin
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
artifact: gaze-x86_64-linux-gnu
runs-on: ${{ matrix.os }}
env:
GAZE_RELEASE_FEATURES: document,proxy
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- name: Build
run: cargo build --release --bin gaze --target ${{ matrix.target }} --features "$GAZE_RELEASE_FEATURES"
- name: Package
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/gaze dist/${{ matrix.artifact }}
chmod +x dist/${{ matrix.artifact }}
(cd dist && shasum -a 256 ${{ matrix.artifact }} > ${{ matrix.artifact }}.sha256)
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: dist/*
- uses: actions/download-artifact@v8
with:
name: ${{ matrix.artifact }}
path: smoke
- name: Smoke artifact
shell: bash
run: |
set -euo pipefail
bin="smoke/${{ matrix.artifact }}"
chmod +x "$bin"
"$bin" --version
"$bin" clean --help | grep -q -- '--session-scope'
"$bin" clean --help | grep -q -- '--ner-model-dir'
"$bin" clean --help | grep -q -- '--ner-locale'
"$bin" clean --help | grep -q -- '--rulepack-bundled'
"$bin" clean --help | grep -q -- '--rulepack-path'
"$bin" document --help | grep -q 'clean'
"$bin" proxy --help | grep -q 'serve'
"$bin" proxy --help | grep -q 'start'
"$bin" proxy --help | grep -q 'stop'
"$bin" proxy --help | grep -q 'status'
"$bin" proxy --help | grep -q 'restart'
clean_json="$(printf 'alice@example.invalid' | "$bin" clean)"
clean_text="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["clean_text"])' <<< "$clean_json")"
grep -q '<' <<< "$clean_text" || (echo "smoke failed: no token in output"; exit 1)
restore_request="$(python3 -c 'import json,sys; v=json.load(sys.stdin); print(json.dumps({"session_blob": v["session_blob"], "text": v["clean_text"]}))' <<< "$clean_json")"
restored_json="$(printf '%s' "$restore_request" | "$bin" restore)"
python3 -c 'import json,sys; assert json.load(sys.stdin)["text"] == "alice@example.invalid"' <<< "$restored_json"
policy="$(mktemp)"
cat > "$policy" <<'POLICY'
[session]
scope = "persistent"
ttl_secs = 86400
[policy.rulepacks]
bundled = ["core", "core-extended"]
[[rule]]
kind = "class"
class = "custom:ip_address"
action = "tokenize"
[[rule]]
kind = "default"
action = "preserve"
POLICY
extended_json="$(printf 'host 192.0.2.1' | "$bin" clean --policy="$policy")"
extended_text="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["clean_text"])' <<< "$extended_json")"
grep -q 'Custom:ip_address' <<< "$extended_text" || (echo "smoke failed: core-extended did not tokenize IP fixture"; exit 1)
smoke_home="$(mktemp -d)"
proxy_port="$(python3 - <<'PY'
import socket
s = socket.socket()
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])
s.close()
PY
)"
export HOME="$smoke_home"
trap '"$bin" proxy stop --force >/dev/null 2>&1 || true; rm -rf "$smoke_home"' EXIT
# Capture `proxy status` stdout via $() instead of piping into `grep -q`.
# grep -q closes stdin on first match → upstream gaze hits EPIPE on
# the next println and the CLI panic hook emits {"error":"Pipeline","exit":3}.
# The $() form lets gaze finish writing before grep inspects the result.
# Underlying SIGPIPE tolerance is tracked as a v0.8.2 follow-up.
status_out="$("$bin" proxy status)"; grep -q 'not running' <<< "$status_out"
"$bin" proxy start --bind "127.0.0.1:$proxy_port"
sleep 1
status_out="$("$bin" proxy status)"; grep -q 'running' <<< "$status_out"
"$bin" proxy restart --force
sleep 1
status_out="$("$bin" proxy status)"; grep -q 'running' <<< "$status_out"
"$bin" proxy stop --force
status_out="$("$bin" proxy status)"; grep -q 'not running' <<< "$status_out"
release:
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: Scrub public release text before publication
shell: bash
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
changelog_section="$(mktemp)"
awk -v version="$version" '
index($0, "## [" version "]") == 1 { in_section=1; print; next }
in_section && /^## \[/ { exit }
in_section { print }
' CHANGELOG.md > "$changelog_section"
if [ ! -s "$changelog_section" ]; then
echo "scrub-public-text failed: CHANGELOG.md has no section for ${GITHUB_REF_NAME}" >&2
exit 1
fi
cargo run -p xtask -- scrub-public-text "$changelog_section"
- name: Generate NER checksums
shell: bash
run: |
set -euo pipefail
hf_repo="$(sed -n 's/^HF_REPO="\([^"]*\)"/\1/p' scripts/fetch/fetch-ner-model.sh)"
hf_commit_sha="$(sed -n 's/^HF_COMMIT_SHA="\([^"]*\)"/\1/p' scripts/fetch/fetch-ner-model.sh)"
if [ -z "$hf_repo" ] || [ -z "$hf_commit_sha" ]; then
echo "failed to read HF_REPO/HF_COMMIT_SHA from scripts/fetch/fetch-ner-model.sh" >&2
exit 2
fi
work_dir="$(mktemp -d)"
trap 'rm -rf "$work_dir"' EXIT
cd "$work_dir"
fetch_raw() {
local source_file="$1"
local dest_file="$2"
local url="https://huggingface.co/${hf_repo}/resolve/${hf_commit_sha}/${source_file}"
curl -fL --retry 3 -o "$dest_file" "$url"
}
fetch_raw "onnx/model_int8.onnx" "model.onnx"
fetch_raw "tokenizer.json" "tokenizer.json"
fetch_raw "tokenizer_config.json" "tokenizer_config.json"
fetch_raw "config.json" "config.json"
fetch_raw "special_tokens_map.json" "special_tokens_map.json"
fetch_raw "vocab.txt" "vocab.txt"
cp "${GITHUB_WORKSPACE}/crates/gaze-recognizers/assets/ner/labels.davlan-mbert.json" labels.json
shasum -a 256 \
model.onnx \
tokenizer.json \
tokenizer_config.json \
config.json \
special_tokens_map.json \
vocab.txt \
labels.json \
> "${GITHUB_WORKSPACE}/dist/SHA256SUMS.ner"
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
files: dist/*
prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-alpha') || contains(github.ref, '-beta') }}
generate_release_notes: true