name: Prepare Release

on:
  workflow_dispatch:
  push:
    tags:
      - 'v*'

env:
  APP_NAME: tailwindcss-oxide
  NODE_VERSION: 20
  OXIDE_LOCATION: ./crates/node

permissions:
  contents: read

jobs:
  build:
    strategy:
      matrix:
        include:
          # Windows
          - os: windows-latest
            target: x86_64-pc-windows-msvc
          - os: windows-latest
            target: aarch64-pc-windows-msvc
          # macOS
          - os: macos-latest
            target: x86_64-apple-darwin
            strip: strip -x # Must use -x on macOS. This produces larger results on linux.
          - os: macos-latest
            target: aarch64-apple-darwin
            page-size: 14
            strip: strip -x # Must use -x on macOS. This produces larger results on linux.
          # Android
          - os: ubuntu-latest
            target: aarch64-linux-android
            strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
          - os: ubuntu-latest
            target: armv7-linux-androideabi
            strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
          # Linux
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            strip: strip
            container:
              image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            strip: llvm-strip
            container:
              image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
          - os: ubuntu-latest
            target: armv7-unknown-linux-gnueabihf
            strip: llvm-strip
            container:
              image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            strip: aarch64-linux-musl-strip
            download: true
            container:
              image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            strip: strip
            download: true
            container:
              image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine

    name: Build ${{ matrix.target }} (oxide)
    runs-on: ${{ matrix.os }}
    container: ${{ matrix.container }}
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'

      - name: Install gcc-arm-linux-gnueabihf
        if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
        run: |
          sudo apt-get update
          sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y

      # Cargo already skips downloading dependencies if they already exist
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}

      # Cache the `oxide` Rust build
      - name: Cache oxide build
        uses: actions/cache@v4
        with:
          path: |
            ./crates/node/*.node
            ./crates/node/*.wasm
            ./crates/node/index.d.ts
            ./crates/node/index.js
            ./crates/node/browser.js
            ./crates/node/tailwindcss-oxide.wasi-browser.js
            ./crates/node/tailwindcss-oxide.wasi.cjs
            ./crates/node/wasi-worker-browser.mjs
            ./crates/node/wasi-worker.mjs
          key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }}

      - name: Install Node.JS
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}

      - name: Install Rust (Stable)
        if: ${{ matrix.download }}
        run: |
          rustup default stable

      - name: Setup rust target
        run: rustup target add ${{ matrix.target }}

      - name: Install dependencies
        run: pnpm install --ignore-scripts --filter=!./playgrounds/*

      - name: Build release
        run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }}
        env:
          RUST_TARGET: ${{ matrix.target }}
          JEMALLOC_SYS_WITH_LG_PAGE: ${{ matrix.page-size }}

      - name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
        if: ${{ matrix.strip }}
        run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: bindings-${{ matrix.target }}
          path: ${{ env.OXIDE_LOCATION }}/*.node

  build-freebsd:
    name: Build x86_64-unknown-freebsd (OXIDE)
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - name: Build FreeBSD
        uses: cross-platform-actions/action@v0.25.0
        env:
          DEBUG: napi:*
          RUSTUP_HOME: /usr/local/rustup
          CARGO_HOME: /usr/local/cargo
          RUSTUP_IO_THREADS: 1
          RUST_TARGET: x86_64-unknown-freebsd
        with:
          operating_system: freebsd
          version: '14.0'
          memory: 13G
          cpu_count: 3
          environment_variables: 'DEBUG RUSTUP_IO_THREADS'
          shell: bash
          run: |
            sudo pkg install -y -f curl node libnghttp2 npm
            sudo npm install -g pnpm@9.6.0 --unsafe-perm=true
            curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init
            chmod +x rustup-init
            ./rustup-init -y --profile minimal
            source "$HOME/.cargo/env"
            pnpm install --ignore-scripts --filter=!./playgrounds/* || true
            echo "~~~~ rustc --version ~~~~"
            rustc --version
            echo "~~~~ node -v ~~~~"
            node -v
            echo "~~~~ pnpm --version ~~~~"
            pnpm --version
            pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform
            strip -x ${{ env.OXIDE_LOCATION }}/*.node
            ls -la ${{ env.OXIDE_LOCATION }}
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: bindings-x86_64-unknown-freebsd
          path: ${{ env.OXIDE_LOCATION }}/*.node

  prepare:
    runs-on: macos-14
    timeout-minutes: 15
    name: Build and release Tailwind CSS

    permissions:
      contents: write # for softprops/action-gh-release to create GitHub release
      # https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions
      id-token: write

    needs:
      - build
      - build-freebsd

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 20

      - run: git fetch --tags -f

      - name: Resolve version
        id: vars
        run: |
          echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV

      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'pnpm'
          registry-url: 'https://registry.npmjs.org'

      # Cargo already skips downloading dependencies if they already exist
      - name: Cache cargo
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}

      # Cache the `oxide` Rust build
      - name: Cache oxide build
        uses: actions/cache@v4
        with:
          path: |
            ./crates/node/*.node
            ./crates/node/*.wasm
            ./crates/node/index.d.ts
            ./crates/node/index.js
            ./crates/node/browser.js
            ./crates/node/tailwindcss-oxide.wasi-browser.js
            ./crates/node/tailwindcss-oxide.wasi.cjs
            ./crates/node/wasi-worker-browser.mjs
            ./crates/node/wasi-worker.mjs
          key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }}

      - name: Setup WASM target
        run: rustup target add wasm32-wasip1-threads

      - name: Install dependencies
        run: pnpm --filter=!./playgrounds/* install

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          path: ${{ env.OXIDE_LOCATION }}

      - name: Move artifacts
        run: |
          cd ${{ env.OXIDE_LOCATION }}
          cp bindings-x86_64-pc-windows-msvc/* ./npm/win32-x64-msvc/
          cp bindings-aarch64-pc-windows-msvc/* ./npm/win32-arm64-msvc/
          cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/
          cp bindings-aarch64-apple-darwin/* ./npm/darwin-arm64/
          cp bindings-aarch64-linux-android/* ./npm/android-arm64/
          cp bindings-armv7-linux-androideabi/* ./npm/android-arm-eabi/
          cp bindings-aarch64-unknown-linux-gnu/* ./npm/linux-arm64-gnu/
          cp bindings-aarch64-unknown-linux-musl/* ./npm/linux-arm64-musl/
          cp bindings-armv7-unknown-linux-gnueabihf/* ./npm/linux-arm-gnueabihf/
          cp bindings-x86_64-unknown-linux-gnu/* ./npm/linux-x64-gnu/
          cp bindings-x86_64-unknown-linux-musl/* ./npm/linux-x64-musl/
          cp bindings-x86_64-unknown-freebsd/* ./npm/freebsd-x64/

      - name: Build Tailwind CSS
        run: pnpm run build
        env:
          FEATURES_ENV: stable

      - name: Run pre-publish optimizations scripts
        run: node ./scripts/pre-publish-optimizations.mjs

      - name: Lock pre-release versions
        run: node ./scripts/lock-pre-release-versions.mjs

      - name: Get release notes
        run: |
          RELEASE_NOTES=$(node ./scripts/release-notes.mjs)
          echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
          echo "$RELEASE_NOTES" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - name: Upload standalone artifacts
        uses: actions/upload-artifact@v4
        with:
          name: tailwindcss-standalone
          path: packages/@tailwindcss-standalone/dist/

      - name: Upload npm package tarballs
        uses: actions/upload-artifact@v4
        with:
          name: npm-package-tarballs
          path: dist/*.tgz

      - name: Prepare GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          draft: true
          tag_name: ${{ env.TAG_NAME }}
          body: |
            ${{ env.RELEASE_NOTES }}
          files: |
            packages/@tailwindcss-standalone/dist/sha256sums.txt
            packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64
            packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64-musl
            packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64
            packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64-musl
            packages/@tailwindcss-standalone/dist/tailwindcss-macos-arm64
            packages/@tailwindcss-standalone/dist/tailwindcss-macos-x64
            packages/@tailwindcss-standalone/dist/tailwindcss-windows-x64.exe