diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c4a57f3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,257 @@ +# SPDX-FileCopyrightText: 2026 John Coffey +# 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{(? 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"}')" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 55f6aec..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -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<> "$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<> "$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: "admin+bot@axo.dev" - 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 diff --git a/assets/importer-exporter.jpg b/assets/importer-exporter.jpg deleted file mode 100644 index 1fa96d2..0000000 Binary files a/assets/importer-exporter.jpg and /dev/null differ diff --git a/dist-workspace.toml b/dist-workspace.toml deleted file mode 100644 index 36f4789..0000000 --- a/dist-workspace.toml +++ /dev/null @@ -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"] diff --git a/scripts/build-release.sh b/scripts/build-release.sh new file mode 100755 index 0000000..ea9108c --- /dev/null +++ b/scripts/build-release.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2026 John Coffey +# 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/ 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" diff --git a/wix/main.wxs b/wix/main.wxs deleted file mode 100644 index 1715d41..0000000 --- a/wix/main.wxs +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - 1 - - - - - - - - - - - - - - - - - -