beachcomber 0.2.0

A centralized daemon that caches shell state (git, battery, hostname, etc.) so every consumer reads from one fast cache instead of independently forking shells
Documentation
name: Release

on:
  push:
    tags: ['v*']

permissions:
  contents: write

jobs:
  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
            musl: true
          # aarch64-linux targets deferred — cross-rs GLIBC mismatch needs resolution
          # - target: aarch64-unknown-linux-gnu
          #   os: ubuntu-latest
          #   cross: true
          # - target: aarch64-unknown-linux-musl
          #   os: ubuntu-latest
          #   cross: true
    steps:
      - uses: actions/checkout@v4

      - name: Install musl tools
        if: matrix.musl
        run: sudo apt-get update && sudo apt-get install -y musl-tools

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      - name: Package
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../beachcomber-${{ github.ref_name }}-${{ matrix.target }}.tar.gz comb
          cd ../../..

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: beachcomber-${{ matrix.target }}
          path: beachcomber-*.tar.gz

  c-sdk-tarball:
    name: Package C SDK
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create C SDK tarball
        run: |
          tar czf beachcomber-c-sdk-${{ github.ref_name }}.tar.gz \
            -C sdks/c \
            beachcomber.c beachcomber.h json.c json.h Makefile README.md
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: beachcomber-c-sdk
          path: beachcomber-c-sdk-*.tar.gz

  package-linux:
    name: Package Linux (${{ matrix.format }}-${{ matrix.arch }})
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - format: deb
            arch: x86_64
            target: x86_64-unknown-linux-gnu
          - format: rpm
            arch: x86_64
            target: x86_64-unknown-linux-gnu
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - name: Download binary artifact
        uses: actions/download-artifact@v4
        with:
          name: beachcomber-${{ matrix.target }}

      - name: Extract binary
        run: |
          mkdir -p target/${{ matrix.target }}/release
          tar xzf beachcomber-*.tar.gz -C target/${{ matrix.target }}/release

      - name: Build deb
        if: matrix.format == 'deb'
        run: |
          cargo install cargo-deb
          cargo deb --no-build --target ${{ matrix.target }}

      - name: Build rpm
        if: matrix.format == 'rpm'
        run: |
          cargo install cargo-generate-rpm
          cargo generate-rpm --target ${{ matrix.target }}

      - name: Upload package
        uses: actions/upload-artifact@v4
        with:
          name: beachcomber-${{ matrix.format }}-${{ matrix.arch }}
          path: |
            target/${{ matrix.target }}/debian/*.deb
            target/${{ matrix.target }}/generate-rpm/*.rpm

  test-packages:
    name: Test ${{ matrix.distro }} package
    needs: package-linux
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - distro: ubuntu
            format: deb
            image: ubuntu:latest
          - distro: fedora
            format: rpm
            image: fedora:latest
    steps:
      - name: Download package
        uses: actions/download-artifact@v4
        with:
          name: beachcomber-${{ matrix.format }}-x86_64
          path: pkg

      - name: Test install in container
        run: |
          docker run --rm -v ${{ github.workspace }}/pkg:/pkg ${{ matrix.image }} \
            bash -c 'pkg_file=$(find /pkg -name "*.${{ matrix.format }}" -type f); echo "Installing $pkg_file"; if [ "${{ matrix.format }}" = "deb" ]; then dpkg -i "$pkg_file"; else rpm -i "$pkg_file"; fi && comb --version'

  release:
    name: Create Release
    needs: [build, c-sdk-tarball, package-linux, test-packages]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          merge-multiple: true
      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true
          files: |
            beachcomber-*.tar.gz
            *.deb
            *.rpm

  publish-crates:
    name: Publish to crates.io
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: rust-lang/crates-io-auth-action@v1
        id: crates-auth
      - name: Publish libbeachcomber
        run: cargo publish -p libbeachcomber || echo "Already published, skipping"
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
      - name: Publish beachcomber
        run: cargo publish -p beachcomber || echo "Already published, skipping"
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}

  publish-pypi:
    name: Publish to PyPI
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: sdks/python
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Build
        run: pip install build && python -m build
      - name: Publish
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: sdks/python/dist/
          skip-existing: true

  publish-npm:
    name: Publish to npm
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: sdks/node
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
      - name: Install dependencies
        run: npm ci
      - name: Publish
        run: unset NODE_AUTH_TOKEN && npm publish --access public --provenance || echo "Already published, skipping"

  publish-rubygems:
    name: Publish to RubyGems
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: sdks/ruby
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
      - name: Build gem
        run: gem build libbeachcomber.gemspec
      - uses: rubygems/configure-rubygems-credentials@main
      - name: Publish
        run: gem push libbeachcomber-*.gem || echo "Already published, skipping"

  publish-luarocks:
    name: Publish to LuaRocks
    needs: release
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: sdks/lua
    steps:
      - uses: actions/checkout@v4
      - uses: leafo/gh-actions-lua@v10
        with:
          luaVersion: '5.4'
      - uses: leafo/gh-actions-luarocks@v4
      - name: Install JSON library
        run: luarocks install dkjson
      - name: Publish
        run: luarocks upload --skip-pack rockspec/libbeachcomber-0.2.0-1.rockspec --api-key=${{ secrets.LUAROCKS_API_KEY }} || echo "Already published, skipping"

  publish-go:
    name: Tag Go module
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create Go module tag
        run: |
          git tag sdks/go/${{ github.ref_name }} || echo "Tag already exists, skipping"
          git push origin sdks/go/${{ github.ref_name }} || echo "Tag already pushed, skipping"

  publish-homebrew:
    name: Update Homebrew tap
    needs: release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          repository: NavistAu/homebrew-tap
          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
          path: homebrew-tap

      - name: Download macOS artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: beachcomber-*-apple-darwin
          merge-multiple: true
          path: artifacts

      - name: Generate formula
        run: |
          VERSION="${{ github.ref_name }}"
          VERSION="${VERSION#v}"
          ARM_SHA=$(shasum -a 256 artifacts/beachcomber-${{ github.ref_name }}-aarch64-apple-darwin.tar.gz | cut -d' ' -f1)
          INTEL_SHA=$(shasum -a 256 artifacts/beachcomber-${{ github.ref_name }}-x86_64-apple-darwin.tar.gz | cut -d' ' -f1)

          cat > homebrew-tap/Formula/beachcomber.rb << FORMULA
          class Beachcomber < Formula
            desc "Daemon that caches shell environment state for instant prompt rendering"
            homepage "https://github.com/NavistAu/beachcomber"
            version "${VERSION}"
            license "MIT"

            if Hardware::CPU.arm?
              url "https://github.com/NavistAu/beachcomber/releases/download/${{ github.ref_name }}/beachcomber-${{ github.ref_name }}-aarch64-apple-darwin.tar.gz"
              sha256 "${ARM_SHA}"
            else
              url "https://github.com/NavistAu/beachcomber/releases/download/${{ github.ref_name }}/beachcomber-${{ github.ref_name }}-x86_64-apple-darwin.tar.gz"
              sha256 "${INTEL_SHA}"
            end

            def install
              bin.install "comb"
            end

            test do
              assert_match "beachcomber", shell_output("#{bin}/comb --version")
            end
          end
          FORMULA

      - name: Push formula
        run: |
          cd homebrew-tap
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add Formula/beachcomber.rb
          git commit -m "beachcomber ${{ github.ref_name }}"
          git push

  publish-npm-binary:
    name: Publish beachcomber binary installer to npm
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: placeholders/npm
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
      - name: Set version from tag
        run: npm version --no-git-tag-version --allow-same-version "${GITHUB_REF_NAME#v}"
      - name: Publish
        run: unset NODE_AUTH_TOKEN && npm publish --access public --provenance || echo "Already published, skipping"

  publish-pypi-binary:
    name: Publish beachcomber binary installer to PyPI
    needs: release
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    defaults:
      run:
        working-directory: placeholders/pypi
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Set version from tag
        run: sed -i "s/^version = .*/version = \"${GITHUB_REF_NAME#v}\"/" pyproject.toml
      - name: Build
        run: pip install build && python -m build
      - name: Publish
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          packages-dir: placeholders/pypi/dist/
          skip-existing: true