name: elixir
on:
  push:
    branches:
      - main
    tags:
      - v*
  pull_request:
  workflow_dispatch:
    inputs:
      hex_repo:
        description: "hex repo to publish to"
        default: staging
        required: true
        options:
          - staging
          - prod

permissions:
  contents: read

env:
  # cache-busting key -- change it if the build changes in a way that invalidates old
  # cached state
  cache_key: init

  package_artifact_base: ts_elixir-${{ github.sha }}

  is_tag_push: ${{ startsWith(github.ref, 'refs/tags/') }}
  is_dispatch: ${{ github.event_name == 'workflow_dispatch' }}

  hex_environment: &hex_environment ${{ case(inputs.hex_repo == 'prod', 'hex.pm', startsWith(github.ref, 'refs/tags/'), 'hex.pm', 'staging.hex.pm') }}

  latest_elixir: &latest_elixir 1.19.5
  latest_otp: &latest_otp 28.4.2
  latest_rust: &latest_rust 1.95.0

defaults:
  run:
    working-directory: ts_elixir
    shell: bash

jobs:
  build_test:
    name: compile, test, static checks (elixir ${{ matrix.elixir.label }}, otp ${{ matrix.otp.label }}, rust ${{ matrix.rust_toolchain.label }}, platform ${{ matrix.platform.name }})
    # This matrix targets self-hosted runner labels that only exist in the upstream tailscale
    # org; skip off-upstream to avoid queued-run spam.
    if: ${{ github.repository_owner == 'tailscale' }}
    runs-on: ${{ matrix.platform.runner }}

    strategy:
      matrix:
        elixir:
          - version: *latest_elixir
            label: latest
        otp:
          - version: *latest_otp
            label: latest
        rust_toolchain:
          - version: *latest_rust
            label: latest
        platform:
          - name: linux (x86_64)
            runner: linux-x86_64-16cpu
            triple: x86_64-unknown-linux-gnu
          - name: linux (aarch64)
            runner: linux-arm64-16cpu
            triple: aarch64-unknown-linux-gnu
          - name: macos
            runner: macos-26
            triple: aarch64-apple-darwin
          - name: windows (x86_64)
            runner: windows-8vcpu
            triple: x86_64-pc-windows-msvc

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Cache mix
        id: cache-mix
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.mix
            ts_elixir/_build/
            ts_elixir/deps/
          key: ${{ matrix.platform.triple }}-mix-${{ env.cache_key }}-elixir-${{ matrix.elixir.version }}-otp-${{ matrix.otp.version }}-${{ hashFiles('**/mix.lock') }}

      - name: Install elixir
        id: install-elixir
        uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # 1.24.0
        with:
          otp-version: ${{ matrix.otp.version }}
          elixir-version: ${{ matrix.elixir.version }}

      - name: Setup rust
        id: setup-rust
        uses: ./.github/actions/setup-rust
        with:
          toolchain-version: ${{ matrix.rust_toolchain.version }}
          builder-triple: ${{ matrix.platform.triple }}

      - name: Install dependencies
        run: mix deps.get

      - name: Compile dependencies
        run: mix deps.compile

      - name: Compile (MIX_ENV=dev)
        run: mix compile --warnings-as-errors

      - name: Compile (MIX_ENV=prod)
        run: mix compile --warnings-as-errors
        env:
          MIX_ENV: prod

      - name: Test
        run: mix test
        env:
          # TODO(npry): testing tailnet + set TS_RS_TEST_AUTHKEY
          TS_RS_TEST_NET: 0

      - &detach_cargo_toml
        name: Detach Cargo.toml from workspace
        working-directory: ts_elixir/native/ts_elixir
        run: |
          pip install tomli-w
          
          python deworkspace_cargo_toml.py \
            --root ../../../Cargo.toml \
            --repo_sha ${{ github.sha }} \
            < Cargo.toml > Cargo.toml.new
          
          mv Cargo.toml.new Cargo.toml
          echo generated Cargo.toml:
          cat Cargo.toml

      - name: Build Hex package (no publish)
        run: mix hex.build --unpack -o tailscale
        env:
          MIX_ENV: prod

      - name: Upload package tarball
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ env.package_artifact_base }}-${{ matrix.platform.triple }}
          path: ts_elixir/tailscale

  arch_independent:
    name: arch-independent checks
    # Targets a self-hosted runner label that only exists in the upstream tailscale org; skip
    # off-upstream to avoid queued-run spam.
    if: ${{ github.repository_owner == 'tailscale' }}
    runs-on: linux-x86_64-16cpu

    env:
      target_triple: x86_64-unknown-linux-gnu

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Cache mix
        id: cache-mix
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.mix
            ts_elixir/_build/
            ts_elixir/deps/
          key: ${{ env.target_triple }}-mix-${{ env.cache_key }}-elixir-${{ env.latest_elixir }}-otp-${{ env.latest_otp }}-${{ hashFiles('**/mix.lock') }}

      - name: Install elixir
        id: install-elixir
        uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # 1.24.0
        with:
          otp-version: ${{ env.latest_otp }}
          elixir-version: ${{ env.latest_elixir }}

      - name: Setup rust
        id: setup-rust
        uses: ./.github/actions/setup-rust
        with:
          toolchain-version: ${{ env.latest_rust }}
          builder-triple: ${{ env.target_triple }}

      - name: Install dependencies
        run: mix deps.get

      - name: Compile dependencies
        run: mix deps.compile

      - name: Format
        run: mix format --check-formatted

      - name: Dialyzer
        run: mix dialyzer --format github

      - name: Credo
        run: mix credo list --format oneline

      - name: Docs
        run: mix docs --warnings-as-errors

      - name: Upload docs
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: exdoc-${{ github.sha }}
          path: ts_elixir/doc
          retention-days: 7

  publish:
    name: publish
    runs-on: linux-x86_64-16cpu
    needs:
      - build_test
      - arch_independent

    # Targets a self-hosted runner label that only exists in the upstream tailscale org; skip
    # off-upstream to avoid queued-run spam (in addition to the tag/dispatch gate).
    if: ${{ github.repository_owner == 'tailscale' && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') }}

    environment: *hex_environment
    env:
      target_triple: x86_64-unknown-linux-gnu

    # Artifact generation, signing, upload, respectively
    permissions:
      attestations: write
      id-token: write
      contents: write

    steps:
      - name: Checkout
        uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

      - name: Cache mix
        id: cache-mix
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: |
            ~/.mix
            ts_elixir/_build/
            ts_elixir/deps/
          key: ${{ runner.os }}-mix-${{ env.cache_key }}-elixir-${{ env.latest_elixir }}-otp-${{ env.latest_otp }}-${{ hashFiles('**/mix.lock') }}

      - name: Install elixir
        id: install-elixir
        uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # 1.24.0
        with:
          otp-version: ${{ env.latest_otp }}
          elixir-version: ${{ env.latest_elixir }}

      - name: Setup rust
        id: setup-rust
        uses: ./.github/actions/setup-rust
        with:
          toolchain-version: ${{ env.latest_rust }}
          builder-triple: ${{ env.target_triple }}

      - name: Install dependencies
        run: mix deps.get

      - name: Compile dependencies
        run: mix deps.compile

      - name: Download built package
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: ${{ env.package_artifact_base }}-${{ env.target_triple }}
          path: ts_elixir/tailscale

      - name: Generate attestation
        uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
        with:
          subject-path: ts_elixir/tailscale

      - *detach_cargo_toml

      - name: Publish deps to staging.hex.pm
        if: ${{ env.hex_environment == 'staging.hex.pm' }}
        run: python staging_publish_deps.py
        env:
          HEX_API_URL: ${{ vars.HEX_API_URL }}
          HEX_API_KEY: ${{ secrets.HEX_API_KEY }}

      - name: Publish (dry run)
        run: mix hex.publish --dry-run --yes
        env:
          HEX_API_URL: ${{ vars.HEX_API_URL }}
          HEX_API_KEY: ${{ secrets.HEX_API_KEY }}

      - name: Publish
        run: mix hex.publish --yes
        env:
          HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
          HEX_API_URL: ${{ vars.HEX_API_URL }}
