diff --git a/.github/workflows/publish-core.yml b/.github/workflows/publish-core.yml index 1a7f0622..4389aded 100644 --- a/.github/workflows/publish-core.yml +++ b/.github/workflows/publish-core.yml @@ -21,6 +21,9 @@ env: jobs: publish-packages: name: Publish crates and core npm package + # Forks may use this workflow to test CLI release assets, but must never + # publish packages to the public registries. + if: ${{ github.repository == 'pondpilot/flowscope' }} runs-on: ubuntu-latest environment: prod env: @@ -147,3 +150,216 @@ jobs: fi env: NODE_AUTH_TOKEN: '' + + build-cli-linux-binaries: + name: Build CLI (${{ matrix.platform.name }}) + runs-on: ${{ matrix.platform.runs-on }} + # Build against an older glibc so the published binaries run on common + # long-term-support distributions. The image supports both amd64 and arm64. + container: + image: rust:1.95-bullseye@sha256:28afaeb8445f2a2e7d878bd34ed39ba02bb517efb29986188cbd59b7cf4f2fdf + permissions: + contents: read + strategy: + fail-fast: false + matrix: + platform: + - name: Linux x86_64 + runs-on: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + - name: Linux aarch64 + runs-on: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + + steps: + - uses: actions/checkout@v4 + + - name: Resolve CLI version + id: cli_version + shell: bash + run: | + python3 - <<'PY' >> "$GITHUB_OUTPUT" + import pathlib + import re + + data = pathlib.Path('Cargo.toml').read_text() + match = re.search(r'^version\s*=\s*"([^"]+)"', data, re.MULTILINE) + if not match: + raise SystemExit('Could not find workspace version in Cargo.toml') + print(f"version={match.group(1)}") + PY + + - name: Verify release tag + if: ${{ github.ref_type == 'tag' }} + shell: bash + env: + CLI_VERSION: ${{ steps.cli_version.outputs.version }} + run: | + set -euo pipefail + expected_tag="v${CLI_VERSION}" + if [[ "$GITHUB_REF_NAME" != "$expected_tag" ]]; then + echo "Release tag '$GITHUB_REF_NAME' does not match CLI version '$CLI_VERSION'." + exit 1 + fi + + - name: Build CLI binary + run: | + cargo build -p flowscope-cli --release --locked + strip target/release/flowscope + + - name: Smoke test CLI on glibc 2.31 + run: target/release/flowscope --version + + - name: Package CLI archive + uses: houseabsolute/actions-rust-release@v1 + with: + executable-name: flowscope + archive-name: flowscope-v${{ steps.cli_version.outputs.version }}-${{ matrix.platform.target }} + changes-file: '' + extra-files: README.md + + build-cli-other-binaries: + name: Build CLI (${{ matrix.platform.name }}) + runs-on: ${{ matrix.platform.runs-on }} + permissions: + contents: read + strategy: + fail-fast: false + matrix: + platform: + - name: macOS x86_64 + runs-on: macos-15-intel + target: x86_64-apple-darwin + - name: macOS aarch64 + runs-on: macos-15 + target: aarch64-apple-darwin + - name: Windows x86_64 + runs-on: windows-2022 + target: x86_64-pc-windows-msvc + + steps: + - uses: actions/checkout@v4 + + - name: Resolve CLI version + id: cli_version + shell: bash + run: | + python - <<'PY' >> "$GITHUB_OUTPUT" + import pathlib + import re + + data = pathlib.Path('Cargo.toml').read_text() + match = re.search(r'^version\s*=\s*"([^"]+)"', data, re.MULTILINE) + if not match: + raise SystemExit('Could not find workspace version in Cargo.toml') + print(f"version={match.group(1)}") + PY + + - name: Verify release tag + if: ${{ github.ref_type == 'tag' }} + shell: bash + env: + CLI_VERSION: ${{ steps.cli_version.outputs.version }} + run: | + set -euo pipefail + expected_tag="v${CLI_VERSION}" + if [[ "$GITHUB_REF_NAME" != "$expected_tag" ]]; then + echo "Release tag '$GITHUB_REF_NAME' does not match CLI version '$CLI_VERSION'." + exit 1 + fi + + - name: Build CLI binary + uses: houseabsolute/actions-rust-cross@v1 + with: + command: build + target: ${{ matrix.platform.target }} + args: '--package flowscope-cli --locked --release' + strip: true + + - name: Package CLI archive + uses: houseabsolute/actions-rust-release@v1 + with: + executable-name: flowscope + target: ${{ matrix.platform.target }} + archive-name: flowscope-v${{ steps.cli_version.outputs.version }}-${{ matrix.platform.target }} + changes-file: '' + extra-files: README.md + + publish-cli-binaries: + name: Publish CLI binaries + needs: + - publish-packages + - build-cli-linux-binaries + - build-cli-other-binaries + if: >- + ${{ + always() && + github.ref_type == 'tag' && + (github.event_name == 'push' || inputs.dry_run != 'true') && + needs['build-cli-linux-binaries'].result == 'success' && + needs['build-cli-other-binaries'].result == 'success' && + ( + github.repository != 'pondpilot/flowscope' || + needs['publish-packages'].result == 'success' + ) + }} + runs-on: ubuntu-24.04 + permissions: + actions: read + attestations: write + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + + - name: Download packaged CLI assets + uses: actions/download-artifact@v4 + with: + pattern: flowscope-v* + path: release-assets + merge-multiple: true + + - name: Generate aggregate SHA-256 checksums + shell: bash + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + mapfile -t archives < <( + find release-assets -maxdepth 1 -type f \ + \( -name 'flowscope-*.tar.gz' -o -name 'flowscope-*.zip' \) \ + -printf '%f\n' | sort + ) + if [[ "${#archives[@]}" -ne 5 ]]; then + echo "Expected five CLI archives, found ${#archives[@]}." + printf '%s\n' "${archives[@]}" + exit 1 + fi + ( + cd release-assets + sha256sum "${archives[@]}" > "flowscope-${RELEASE_TAG}-SHA256SUMS" + ) + + - name: Upload aggregate checksum artifact + uses: actions/upload-artifact@v4 + with: + name: flowscope-${{ github.ref_name }}-SHA256SUMS + path: release-assets/flowscope-${{ github.ref_name }}-SHA256SUMS + if-no-files-found: error + + - name: Attest CLI release assets + uses: actions/attest-build-provenance@v4 + with: + subject-path: | + release-assets/*.tar.gz + release-assets/*.zip + release-assets/*.sha256 + release-assets/*SHA256SUMS + + - name: Publish CLI release + uses: houseabsolute/actions-rust-release/publish@v1 + with: + executable-name: flowscope + artifact-regex: '\Aflowscope-v.*(\.tar\.gz|\.zip|-SHA256SUMS)\Z' + changes-file: '' + generate-release-notes: true