From 42413563ef427bc0e57ea89513a6b77b1a76d691 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Wed, 30 Sep 2026 10:12:48 -0700 Subject: [PATCH] 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. --- .github/workflows/ci.yml | 257 +++++++++++++++++++++++++ .github/workflows/release.yml | 342 ---------------------------------- assets/importer-exporter.jpg | Bin 7754 -> 0 bytes dist-workspace.toml | 16 -- scripts/build-release.sh | 45 +++++ wix/main.wxs | 228 ----------------------- 6 files changed, 302 insertions(+), 586 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 assets/importer-exporter.jpg delete mode 100644 dist-workspace.toml create mode 100755 scripts/build-release.sh delete mode 100644 wix/main.wxs 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 1fa96d2a6fffe9312d8f661ae4148a732f788af1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7754 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 eI^AFxT~_lix8{?0)WpcFd(poQCh+sq>i+ +# 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 - - - - - - - - - - - - - - - - - -