Replace the release tooling with the suite's CI

Upstream's cargo-dist pipeline published to npm and Homebrew under its own
names, built an MSI, and ran on every pull request. It goes, with its
dist-workspace.toml, the wix/ installer files and the README image.

CI now follows the rest of the suite. .gitea/workflows runs `cargo test
--locked` on Gitea's runners, or, when the org variable BUILD_ON is
'github', waits for the result GitHub reports on the commit. On GitHub,
.github/workflows/ci.yml runs the same tests, and for a v* tag on main
whose version matches Cargo.toml builds scripts/build-release.sh on native
amd64 and arm64 runners (Ubuntu 22.04, for the widest glibc range),
attaches inbuxa-migrate-linux-{amd64,arm64}.tar.gz and SHA256SUMS to the
Gitea Release, copies the Release to GitHub, and reports back. Releases are
built only on GitHub; with BUILD_ON unset a tag is tested but not released.
Published releases are announced on the forum.
This commit is contained in:
2026-09-30 10:12:48 -07:00
parent 7fbcf5031e
commit 42413563ef
6 changed files with 302 additions and 586 deletions
+257
View File
@@ -0,0 +1,257 @@
# SPDX-FileCopyrightText: 2026 John Coffey <[email protected]>
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# CI on GitHub Actions, for the GitHub copy of this repository.
#
# The repository lives on the self-hosted Gitea; GitHub holds a push mirror
# that Gitea updates on every commit. Nothing is merged here -- pull requests
# happen on Gitea, and their branches reach GitHub as ordinary pushes, which is
# why this workflow runs on `push` and not on `pull_request`.
#
# Which forge does the building is one switch, the variable BUILD_ON, set on
# both forges at the organization level:
#
# BUILD_ON=github every job here runs; .gitea/workflows/ci.yml skips its
# test job and waits for the status this workflow reports
# back instead.
# unset / other every job here skips; Gitea runs the tests. Releases need
# GitHub: they are built on native amd64 and arm64 runners.
#
# The result goes back to Gitea as one commit status, "github/ci (branch)" or
# "github/ci (tag)", which is what Gitea's `github` job waits on.
#
# v* tags build a release: the tag must be on main and name the version in
# Cargo.toml; each architecture is built on its own native runner by
# scripts/build-release.sh, and the archives plus SHA256SUMS are attached to
# the Gitea Release -- Gitea is where the install guide points. The Release is
# created as a draft, filled, then published, so it is never visible with
# assets missing; if an upload fails, a Release this run created is deleted.
# Gitea announces the release once this run has reported success.
#
# The binaries link glibc dynamically, so they are built on Ubuntu 22.04: the
# oldest glibc GitHub offers, and so the widest range of servers they run on.
#
# Configuration, all at the organization level on GitHub: variables BUILD_ON,
# GITEA_URL; secret GITEA_TOKEN (write:repository on Gitea).
#
# Every `uses:` is pinned to a full commit SHA, with the release it was in the
# trailing comment. Nothing schedules here: schedules belong to Gitea.
name: ci
on:
push:
branches: ['**']
tags: ['**']
# For a run GitHub queued and then orphaned. Run it from a tag to redo that
# tag's release; attaching skips any file already on the Release.
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_type != 'tag' }}
permissions:
contents: read
env:
GITEA_URL: ${{ vars.GITEA_URL }}
CONTEXT: github/ci (${{ github.ref_type }})
CARGO_TERM_COLOR: never
jobs:
# Tells Gitea a result is on its way, so a status that is still missing
# reads as "running" rather than "never started".
pending:
if: vars.BUILD_ON == 'github'
runs-on: ubuntu-latest
steps:
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
curl -fsS -o /dev/null -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
"$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA" \
-d "$(jq -n --arg c "$CONTEXT" --arg u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
'{state:"pending", context:$c, target_url:$u, description:"GitHub Actions"}')"
# The unit and mock tests. The #[ignore]d ones need live servers or
# containers and run by hand (README, "Testing").
test:
if: vars.BUILD_ON == 'github'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-test-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}
restore-keys: cargo-test-${{ runner.os }}-${{ runner.arch }}-
# rustup is on the runner; rust-toolchain.toml picks the toolchain.
- run: rustup show active-toolchain || rustup toolchain install
- run: cargo test --locked
# Guards shared by both architectures, checked once.
version:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
runs-on: ubuntu-latest
env:
TAG: ${{ github.ref_name }}
steps:
# Full history: the ancestry check cannot be answered from a shallow
# clone.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# A release can never describe code that was not reviewed onto main,
# and its tag must be the version the binary reports.
- run: |
git fetch --quiet origin main
git merge-base --is-ancestor "$(git rev-parse "${TAG}^{commit}")" origin/main \
|| { echo "::error::$TAG is not on main"; exit 1; }
want="v$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
[ "$TAG" = "$want" ] || { echo "::error::$TAG does not match Cargo.toml ($want)"; exit 1; }
build:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
needs: [test, version]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
include:
- arch: amd64
runner: ubuntu-22.04
- arch: arm64
runner: ubuntu-22.04-arm
env:
TAG: ${{ github.ref_name }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- run: rustup show active-toolchain || rustup toolchain install
- run: |
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct "$TAG")" scripts/build-release.sh "$TAG" dist
sha256sum dist/*.tar.gz
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: archive-${{ matrix.arch }}
path: dist/*.tar.gz
retention-days: 7
overwrite: true
if-no-files-found: error
release:
if: vars.BUILD_ON == 'github' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
needs: [build]
runs-on: ubuntu-latest
env:
TAG: ${{ github.ref_name }}
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: archive-*
merge-multiple: true
- run: |
(cd dist && sha256sum ./*.tar.gz | sed 's| \./| |' > SHA256SUMS)
cat dist/SHA256SUMS
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
API="$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY"
auth=(-H "Authorization: token $GITEA_TOKEN")
# Reuse the Release if the tag already has one (a re-run), else make a
# draft of our own.
created=0
id="$(curl -fsS "${auth[@]}" "$API/releases/tags/$TAG" 2>/dev/null | jq -r '.id // empty' || true)"
if [ -z "$id" ]; then
id="$(curl -fsS "${auth[@]}" -H 'Content-Type: application/json' \
-d "$(jq -n --arg t "$TAG" '{tag_name:$t, name:$t, draft:true,
body:"Binaries for linux/amd64 and linux/arm64. Verify with SHA256SUMS."}')" \
"$API/releases" | jq -r '.id // empty')"
[ -n "$id" ] || { echo "::error::could not create the release"; exit 1; }
created=1
fi
have="$(curl -fsS "${auth[@]}" "$API/releases/$id/assets" | jq -r '.[].name')"
for f in dist/*; do
n="$(basename "$f")"
if grep -qxF "$n" <<<"$have"; then echo "already attached: $n"; continue; fi
echo "uploading $n"
curl -fsS -o /dev/null "${auth[@]}" --form "attachment=@$f" "$API/releases/$id/assets?name=$n" || {
[ "$created" = 1 ] && curl -sS -o /dev/null "${auth[@]}" -X DELETE "$API/releases/$id"
exit 1
}
done
if [ "$created" = 1 ]; then
curl -fsS -o /dev/null "${auth[@]}" -H 'Content-Type: application/json' -X PATCH \
-d '{"draft":false}' "$API/releases/$id"
fi
# ---------------------------------------------------- github release ------
# Copies this tag's Gitea release -- notes and files -- to a GitHub release,
# so the replica's Releases page, and anyone watching it, keeps up. Gitea's
# release is the real one; this is left out of the report to Gitea, so a
# failure here cannot fail a release. PR and issue numbers in the notes are
# rewritten to Gitea links: on GitHub a bare #16 is some other PR.
github-release:
if: ${{ vars.BUILD_ON == 'github' && github.ref_type == 'tag' }}
needs: [release]
runs-on: ubuntu-latest
permissions:
contents: write
env:
GITEA_URL: ${{ vars.GITEA_URL }}
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
steps:
- run: |
set -euo pipefail
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "GitHub already has a release for $TAG"; exit 0
fi
# The Gitea release exists by now if this run made it; allow a few
# minutes either way.
code=0
for _ in $(seq 1 15); do
code="$(curl -sS -o rel.json -w '%{http_code}' "$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/releases/tags/$TAG")"
[ "$code" = 200 ] && break
sleep 20
done
if [ "$code" != 200 ]; then echo "No Gitea release for $TAG; nothing to copy"; exit 0; fi
if [ "$(jq -r .draft rel.json)" = true ]; then echo "The Gitea release is a draft; not copying"; exit 0; fi
export BASE="$(jq -r '.html_url | sub("/releases/tag/.*$"; "")' rel.json)"
jq -r '.body // ""' rel.json | perl -pe 's{(?<![\w/&\[])#(\d+)\b}{[#$1]($ENV{BASE}/pulls/$1)}g' > notes.md
printf '\n\n_Mirrored from [the Gitea release](%s); report issues on [Gitea](%s/issues)._\n' \
"$(jq -r .html_url rel.json)" "$BASE" >> notes.md
files=()
mkdir -p files
while IFS=$'\t' read -r name url; do
curl -fsSL -o "files/$name" "$url"; files+=("files/$name")
done < <(jq -r '.assets[]? | [.name, .browser_download_url] | @tsv' rel.json)
title="$(jq -r '.name // ""' rel.json)"; [ -n "$title" ] || title="$TAG"
if [ "$(jq -r .prerelease rel.json)" = true ]; then kind=--prerelease; else kind=--latest; fi
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$title" \
--notes-file notes.md "$kind" "${files[@]}"
echo "created the GitHub release for $TAG with ${#files[@]} file(s)"
report:
if: always() && vars.BUILD_ON == 'github'
needs: [pending, test, version, build, release]
runs-on: ubuntu-latest
steps:
- env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
STATE: ${{ contains(needs.*.result, 'failure') && 'failure' || (contains(needs.*.result, 'cancelled') && 'cancelled' || 'success') }}
run: |
# A cancelled run was superseded by a newer run for the same commit (the
# mirror can push one commit twice); that run reports. Posting "failure"
# here would fail the Gitea check while the real build is still going.
if [ "$STATE" = cancelled ]; then echo "cancelled: leaving the result to the newer run"; exit 0; fi
curl -fsS -o /dev/null -X POST -H "Authorization: token $GITEA_TOKEN" -H 'Content-Type: application/json' \
"$GITEA_URL/api/v1/repos/$GITHUB_REPOSITORY/statuses/$GITHUB_SHA" \
-d "$(jq -n --arg s "$STATE" --arg c "$CONTEXT" --arg u "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
'{state:$s, context:$c, target_url:$u, description:"GitHub Actions"}')"
-342
View File
@@ -1,342 +0,0 @@
name: Release
permissions:
"contents": "write"
on:
pull_request:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Install dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v7
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
- id: plan
run: |
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v7
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
AWS_LC_SYS_PREBUILT_NASM: 1
permissions:
"attestations": "write"
"contents": "read"
"id-token": "write"
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- name: Install dist
run: ${{ matrix.install_dist.run }}
- name: Fetch local artifacts
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Update Rust toolchain to current stable
shell: bash
run: |
if command -v rustup > /dev/null 2>&1; then
rustup update stable
rustup default stable
fi
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
- name: Attest
uses: actions/attest-build-provenance@v4
with:
subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*"
- id: cargo-dist
name: Post-build
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v7
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Fetch local artifacts
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v7
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v8
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
- name: Fetch artifacts
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
shell: bash
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v7
with:
name: artifacts-dist-manifest
path: dist-manifest.json
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
RELEASE_COMMIT: "${{ github.sha }}"
run: |
awk '/^## \[/{c++} c==1' CHANGELOG.md > $RUNNER_TEMP/notes.txt
# Tag-push releases are created as drafts; the `announce` job un-drafts
# them only after the build succeeds, so watcher notifications don't
# fire on broken builds.
gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --draft --title "${{ needs.plan.outputs.tag }}" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
publish-homebrew-formula:
needs:
- plan
- host
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PLAN: ${{ needs.plan.outputs.val }}
GITHUB_USER: "axo bot"
GITHUB_EMAIL: "[email protected]"
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: true
repository: "stalwartlabs/homebrew-tap"
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
- name: Fetch homebrew formulae
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: Formula/
merge-multiple: true
- name: Commit formula files
run: |
git config --global user.name "${GITHUB_USER}"
git config --global user.email "${GITHUB_EMAIL}"
for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do
filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output)
name=$(echo "$filename" | sed "s/\.rb$//")
version=$(echo "$release" | jq .app_version --raw-output)
export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH"
brew update
# We avoid reformatting user-provided data such as the app description and homepage.
brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true
git add "Formula/${filename}"
git commit -m "${name} ${version}"
done
git push
publish-npm:
needs:
- plan
- host
runs-on: "ubuntu-22.04"
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
permissions:
"contents": "read"
"id-token": "write"
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Fetch npm tarball
uses: actions/download-artifact@v8
with:
pattern: artifacts-*
path: artifacts/
merge-multiple: true
- name: Upgrade npm for trusted publishing
run: npm install -g npm@latest
- name: Publish to npm
env:
IS_PRERELEASE: ${{ fromJson(needs.plan.outputs.val).announcement_is_prerelease }}
run: |
TAG_FLAG=""
if [ "$IS_PRERELEASE" = "true" ]; then
TAG_FLAG="--tag next"
fi
npm publish ./artifacts/vandelay-npm-package.tar.gz --provenance --access public $TAG_FLAG
announce:
needs:
- plan
- host
- publish-homebrew-formula
- publish-npm
if: ${{ always() && needs.host.result == 'success' && (needs.publish-homebrew-formula.result == 'skipped' || needs.publish-homebrew-formula.result == 'success') && (needs.publish-npm.result == 'skipped' || needs.publish-npm.result == 'success') }}
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: recursive
- name: Un-draft release
run: gh release edit "${{ needs.plan.outputs.tag }}" --draft=false --repo "${{ github.repository }}"
cleanup:
name: Cleanup failed release
needs:
- plan
- build-local-artifacts
- build-global-artifacts
- host
- publish-homebrew-formula
- publish-npm
- announce
if: ${{ failure() && needs.plan.outputs.publishing == 'true' }}
runs-on: "ubuntu-22.04"
permissions:
contents: write
steps:
- name: Delete draft release and tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release delete "${{ needs.plan.outputs.tag }}" --yes --cleanup-tag --repo "${{ github.repository }}" || true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

-16
View File
@@ -1,16 +0,0 @@
[workspace]
members = ["cargo:."]
[dist]
cargo-dist-version = "0.31.0"
ci = "github"
installers = ["shell", "powershell", "npm", "homebrew", "msi"]
unix-archive = ".tar.gz"
tap = "stalwartlabs/homebrew-tap"
npm-scope = "@stalwartlabs"
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "armv7-unknown-linux-gnueabihf", "armv7-unknown-linux-musleabihf", "arm-unknown-linux-gnueabihf", "arm-unknown-linux-musleabihf", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
install-path = "CARGO_HOME"
publish-jobs = ["homebrew", "npm"]
install-updater = false
github-attestations = true
allow-dirty = ["ci", "msi"]
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2026 John Coffey <[email protected]>
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
# Build one release archive for the machine this runs on.
#
# Usage: scripts/build-release.sh VERSION OUTDIR
# scripts/build-release.sh v2026.9.30 dist
#
# CI runs exactly this on one native runner per architecture (amd64 and
# arm64) and joins the results with SHA256SUMS, so a release can be checked
# before tagging on any Linux machine with cargo. The archive name carries no
# version, so .../releases/latest/download/<name> always means the newest.
set -euo pipefail
VERSION="${1:?usage: $0 VERSION OUTDIR}"
OUT="${2:?usage: $0 VERSION OUTDIR}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
case "$(uname -m)" in
x86_64) arch=amd64 ;;
aarch64 | arm64) arch=arm64 ;;
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
# The tag names the version the binary reports, so the two cannot disagree.
want="v$(cd "$ROOT" && cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
[ "$VERSION" = "$want" ] || { echo "tag $VERSION does not match Cargo.toml ($want)" >&2; exit 1; }
name="inbuxa-migrate-linux-$arch"
mkdir -p "$OUT"
OUT="$(cd "$OUT" && pwd)"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
echo "==> building $name ($VERSION)"
(cd "$ROOT" && cargo build --release --locked)
mkdir -p "$STAGE/$name"
cp "$ROOT/target/release/inbuxa-migrate" "$ROOT/README.md" "$STAGE/$name/"
cp -r "$ROOT/LICENSES" "$STAGE/$name/"
# Fixed owner, order and time, so the archive's layout and metadata do not
# change from one build of a tag to the next.
tar --sort=name --owner=0 --group=0 --numeric-owner --mtime="@${SOURCE_DATE_EPOCH:-0}" \
-C "$STAGE/$name" -czf "$OUT/$name.tar.gz" inbuxa-migrate LICENSES README.md
echo "==> $OUT/$name.tar.gz"
-228
View File
@@ -1,228 +0,0 @@
<?xml version='1.0' encoding='windows-1252'?>
<!--
Copyright (C) 2017 Christopher R. Field.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
The "cargo wix" subcommand provides a variety of predefined variables available
for customization of this template. The values for each variable are set at
installer creation time. The following variables are available:
TargetTriple = The rustc target triple name.
TargetEnv = The rustc target environment. This is typically either
"msvc" or "gnu" depending on the toolchain downloaded and
installed.
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
does support other vendors, like "uwp".
CargoTargetBinDir = The complete path to the directory containing the
binaries (exes) to include. The default would be
"target\release\". If an explicit rustc target triple is
used, i.e. cross-compiling, then the default path would
be "target\<CARGO_TARGET>\<CARGO_PROFILE>",
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
variable value and "<CARGO_PROFILE>" is replaced with the
value from the "CargoProfile" variable. This can also
be overridden manually with the "target-bin-dir" flag.
CargoTargetDir = The path to the directory for the build artifacts, i.e.
"target".
CargoProfile = The cargo profile used to build the binaries
(usually "debug" or "release").
Version = The version for the installer. The default is the
"Major.Minor.Fix" semantic versioning number of the Rust
package.
-->
<!--
Please do not remove these pre-processor If-Else blocks. These are used with
the `cargo wix` subcommand to automatically determine the installation
destination for 32-bit versus 64-bit installers. Removal of these lines will
cause installation errors.
-->
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<Product
Id='*'
Name='vandelay'
UpgradeCode='5C8148E7-34A9-4216-A802-FFB8345AECC4'
Manufacturer='Stalwart Labs LLC'
Language='1033'
Codepage='1252'
Version='$(var.Version)'>
<Package Id='*'
Keywords='Installer'
Description='Vandelay, the JMAP importer-exporter'
Manufacturer='Stalwart Labs LLC'
InstallerVersion='450'
Languages='1033'
Compressed='yes'
InstallScope='perMachine'
SummaryCodepage='1252'
/>
<MajorUpgrade
Schedule='afterInstallInitialize'
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
<Property Id='DiskPrompt' Value='vandelay Installation'/>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='vandelay'>
<!--
Enabling the license sidecar file in the installer is a four step process:
1. Uncomment the `Component` tag and its contents.
2. Change the value for the `Source` attribute in the `File` tag to a path
to the file that should be included as the license sidecar file. The path
can, and probably should be, relative to this file.
3. Change the value for the `Name` attribute in the `File` tag to the
desired name for the file when it is installed alongside the `bin` folder
in the installation directory. This can be omitted if the desired name is
the same as the file name.
4. Uncomment the `ComponentRef` tag with the Id attribute value of "License"
further down in this file.
-->
<!--
<Component Id='License' Guid='*'>
<File Id='LicenseFile' Name='ChangeMe' DiskId='1' Source='C:\Path\To\File' KeyPath='yes'/>
</Component>
-->
<Directory Id='Bin' Name='bin'>
<Component Id='Path' Guid='FA9E97EB-5E5C-4809-9018-9DC1EDB0E93D' KeyPath='yes'>
<Environment
Id='PATH'
Name='PATH'
Value='[Bin]'
Permanent='no'
Part='last'
Action='set'
System='yes'/>
</Component>
<Component Id='binary0' Guid='*'>
<File
Id='exe0'
Name='vandelay.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\vandelay.exe'
KeyPath='yes'/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature
Id='Binaries'
Title='Application'
Description='Installs all binaries and the license.'
Level='1'
ConfigurableDirectory='APPLICATIONFOLDER'
AllowAdvertise='no'
Display='expand'
Absent='disallow'>
<!--
Uncomment the following `ComponentRef` tag to add the license
sidecar file to the installer.
-->
<!--<ComponentRef Id='License'/>-->
<ComponentRef Id='binary0'/>
<Feature
Id='Environment'
Title='PATH Environment Variable'
Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
Level='1'
Absent='allow'>
<ComponentRef Id='Path'/>
</Feature>
</Feature>
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
<!--
Uncomment the following `Icon` and `Property` tags to change the product icon.
The product icon is the graphic that appears in the Add/Remove
Programs control panel for the application.
-->
<!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
<!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
<Property Id='ARPHELPLINK' Value='https://github.com/stalwartlabs/vandelay'/>
<UI>
<UIRef Id='WixUI_FeatureTree'/>
<!--
Enabling the EULA dialog in the installer is a three step process:
1. Comment out or remove the two `Publish` tags that follow the
`WixVariable` tag.
2. Uncomment the `<WixVariable Id='WixUILicenseRtf' Value='Path\to\Eula.rft'>` tag further down
3. Replace the `Value` attribute of the `WixVariable` tag with
the path to a RTF file that will be used as the EULA and
displayed in the license agreement dialog.
-->
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
</UI>
<!--
Enabling the EULA dialog in the installer requires uncommenting
the following `WixUILicenseRTF` tag and changing the `Value`
attribute.
-->
<!-- <WixVariable Id='WixUILicenseRtf' Value='Relative\Path\to\Eula.rtf'/> -->
<!--
Uncomment the next `WixVariable` tag to customize the installer's
Graphical User Interface (GUI) and add a custom banner image across
the top of each screen. See the WiX Toolset documentation for details
about customization.
The banner BMP dimensions are 493 x 58 pixels.
-->
<!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
<!--
Uncomment the next `WixVariable` tag to customize the installer's
Graphical User Interface (GUI) and add a custom image to the first
dialog, or screen. See the WiX Toolset documentation for details about
customization.
The dialog BMP dimensions are 493 x 312 pixels.
-->
<!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
</Product>
</Wix>