tailwindlabs/tailwindcss

5 workflows · maturity 67% · 8 patterns · GitHub ↗

Security 25/100

Practices

✓ Matrix✓ Permissions○ Security scan○ AI review✓ Cache✓ Concurrency○ Reusable workflows

Detected patterns

Security dimensions

permissions
25
security scan
0
supply chain
0
secret handling
0
harden runner
0

Workflows (5)

ci matrix perms .github/workflows/ci.yml
Triggers
push, pull_request
Runs on
${{ matrix.runner.os }}, ubuntu-latest
Jobs
tests, notify
Matrix
exclude, exclude.run-all, exclude.runner, run-all, runner, runner.name, runner.os→ ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') || github.event.pull_request.user.login == 'depfu[bot]' }}, False, Linux, Windows, macOS, macos-14, namespace-profile-default, windows-latest
Actions
pnpm/action-setup, discord-actions/message
Commands
  • rustup target add wasm32-wasip1-threads
  • pnpm install
  • pnpm run build
  • pnpm run lint
  • pnpm run test
  • npx playwright install --with-deps
  • npm run test:ui
View raw YAML
name: CI

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

env:
  NODE_VERSION: 24

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  tests:
    strategy:
      fail-fast: false
      matrix:
        runner:
          - name: Windows
            os: windows-latest

          - name: Linux
            os: namespace-profile-default

          - name: macOS
            os: macos-14

        # Exclude windows and macos from being built on feature branches
        run-all:
          - ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') || github.event.pull_request.user.login == 'depfu[bot]' }}
        exclude:
          - run-all: false
            runner:
              name: Windows
          - run-all: false
            runner:
              name: macOS

    runs-on: ${{ matrix.runner.os }}
    timeout-minutes: 30

    name: ${{ matrix.runner.name }}

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4

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

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

      # Cache the `oxide` Rust build
      - name: Cache oxide build
        uses: actions/cache@v5
        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 }}-oxide-${{ hashFiles('./crates/**/*') }}

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

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm run build
        env:
          CARGO_PROFILE_RELEASE_LTO: 'off'
          CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: 'lld-link'

      - name: Lint
        run: pnpm run lint
        # Only lint on linux to avoid \r\n line ending errors
        if: matrix.runner.os == 'ubuntu-latest'

      - name: Test
        run: pnpm run test

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npm run test:ui

  notify:
    if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }}
    needs: tests
    runs-on: ubuntu-latest
    steps:
      - name: Notify Discord
        uses: discord-actions/message@v2
        with:
          webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
          message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'
integration-tests matrix perms .github/workflows/integration-tests.yml
Triggers
push, pull_request
Runs on
${{ matrix.runner.os }}, ubuntu-latest
Jobs
tests, notify
Matrix
exclude, exclude.run-all, exclude.runner, integration, run-all, runner, runner.name, runner.os→ ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') }}, False, Linux, Windows, cli, macOS, macos-14, namespace-profile-default, oxide, postcss, upgrade, vite, webpack, windows-latest
Actions
pnpm/action-setup, discord-actions/message
Commands
  • git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
  • rustup target add wasm32-wasip1-threads
  • pnpm install
  • pnpm run build
  • pnpm run test:integrations ./integrations/${{ matrix.integration }}
View raw YAML
name: Integration Tests

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

env:
  NODE_VERSION: 24

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  tests:
    strategy:
      fail-fast: false
      matrix:
        runner:
          - name: Windows
            os: windows-latest

          - name: Linux
            os: namespace-profile-default

          - name: macOS
            os: macos-14

        integration:
          - upgrade
          - vite
          - cli
          - postcss
          - oxide
          - webpack

        # Exclude windows and macos from being built on feature branches
        run-all:
          - ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') }}
        exclude:
          - run-all: false
            runner:
              name: Windows
          - run-all: false
            runner:
              name: macOS

    runs-on: ${{ matrix.runner.os }}
    timeout-minutes: 30

    name: ${{ matrix.runner.name }} /  ${{ matrix.integration }}

    steps:
      - uses: actions/checkout@v6
      - uses: pnpm/action-setup@v4

      - run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

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

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

      # Cache the `oxide` Rust build
      - name: Cache oxide build
        uses: actions/cache@v5
        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 }}-oxide-${{ hashFiles('./crates/**/*') }}

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

      - name: Install dependencies
        run: pnpm install

      - name: Build
        run: pnpm run build
        env:
          CARGO_PROFILE_RELEASE_LTO: 'off'
          CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: 'lld-link'

      - name: Test ${{ matrix.integration }}
        run: pnpm run test:integrations ./integrations/${{ matrix.integration }}
        env:
          GITHUB_WORKSPACE: ${{ github.workspace }}

  notify:
    if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }}
    needs: tests
    runs-on: ubuntu-latest
    steps:
      - name: Notify Discord
        uses: discord-actions/message@v2
        with:
          webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
          message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'
prepare-release matrix perms .github/workflows/prepare-release.yml
Triggers
workflow_dispatch, push
Runs on
${{ matrix.os }}, ubuntu-latest, macos-14
Jobs
build, build-freebsd, prepare
Matrix
container.image, include, include.container, include.download, include.os, include.page-size, include.strip, include.target→ ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip, 14, True, aarch64-apple-darwin, aarch64-linux-android, aarch64-linux-musl-strip, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, armv7-linux-androideabi, armv7-unknown-linux-gnueabihf, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig, llvm-strip, macos-latest, strip, strip -x, ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Actions
pnpm/action-setup, cross-platform-actions/action, pnpm/action-setup, softprops/action-gh-release
Commands
  • sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
  • rustup default stable
  • rustup target add ${{ matrix.target }}
  • pnpm install --ignore-scripts --filter=!./playgrounds/*
  • pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }}
  • ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
  • git fetch --tags -f
  • echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
View raw YAML
name: Prepare Release

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

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

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

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@v6
      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        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@v5
        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@v5
        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@v6
        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@v6
        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@v6
      - 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@v6
        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@v6
        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@v6
        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@v5
        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@v5
        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@v7
        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@v6
        with:
          name: tailwindcss-standalone
          path: packages/@tailwindcss-standalone/dist/

      - name: Upload npm package tarballs
        uses: actions/upload-artifact@v6
        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
release matrix perms .github/workflows/release.yml
Triggers
release, workflow_dispatch
Runs on
${{ matrix.os }}, ubuntu-latest, macos-14
Jobs
build, build-freebsd, release
Matrix
container.image, include, include.container, include.download, include.os, include.page-size, include.strip, include.target→ ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip, 14, True, aarch64-apple-darwin, aarch64-linux-android, aarch64-linux-musl-strip, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, armv7-linux-androideabi, armv7-unknown-linux-gnueabihf, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig, llvm-strip, macos-latest, strip, strip -x, ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Actions
pnpm/action-setup, cross-platform-actions/action, pnpm/action-setup
Commands
  • sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
  • rustup default stable
  • rustup target add ${{ matrix.target }}
  • pnpm install --ignore-scripts --filter=!./playgrounds/*
  • pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }}
  • ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
  • rustup target add wasm32-wasip1-threads
  • pnpm --filter=!./playgrounds/* install
View raw YAML
name: Release

on:
  release:
    types: [published]
  workflow_dispatch:

permissions:
  contents: read

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

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@v6
      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        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@v5
        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@v5
        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@v6
        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@v6
        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@v6
      - 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"
            echo "~~~~ rustc --version ~~~~"
            rustc --version
            echo "~~~~ node -v ~~~~"
            node -v
            echo "~~~~ pnpm --version ~~~~"
            pnpm --version
            pnpm install --ignore-scripts --filter=!./playgrounds/* || true
            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@v6
        with:
          name: bindings-x86_64-unknown-freebsd
          path: ${{ env.OXIDE_LOCATION }}/*.node

  release:
    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@v6
        with:
          fetch-depth: 20

      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        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@v5
        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@v5
        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@v7
        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: Calculate environment variables
        run: |
          echo "RELEASE_CHANNEL=$(node ./scripts/release-channel.js)" >> $GITHUB_ENV
          echo "TAILWINDCSS_VERSION=$(node -e 'console.log(require(`./packages/tailwindcss/package.json`).version);')" >> $GITHUB_ENV

      - name: Publish
        run: |
          pnpm --recursive --filter="!@tailwindcss/oxide-wasm32-wasi" publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks
          # The wasm package needs a special npm config that isn't read when pnpm --recursive is used
          pushd crates/node/npm/wasm32-wasi; pnpm publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks; popd;
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Trigger Tailwind Play update
        if: env.RELEASE_CHANNEL == 'latest'
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: 'tailwindlabs',
              repo: 'upgrades',
              ref: 'main',
              workflow_id: 'upgrade-tailwindcss.yml'
            })
release-insiders matrix perms .github/workflows/release-insiders.yml
Triggers
push
Runs on
${{ matrix.os }}, ubuntu-latest, macos-14
Jobs
build, build-freebsd, release
Matrix
container.image, include, include.container, include.download, include.os, include.page-size, include.strip, include.target→ ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip, 14, True, aarch64-apple-darwin, aarch64-linux-android, aarch64-linux-musl-strip, aarch64-pc-windows-msvc, aarch64-unknown-linux-gnu, aarch64-unknown-linux-musl, armv7-linux-androideabi, armv7-unknown-linux-gnueabihf, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64, ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig, llvm-strip, macos-latest, strip, strip -x, ubuntu-latest, windows-latest, x86_64-apple-darwin, x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl
Actions
pnpm/action-setup, cross-platform-actions/action, pnpm/action-setup
Commands
  • sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
  • rustup default stable
  • rustup target add ${{ matrix.target }}
  • pnpm install --ignore-scripts --filter=!./playgrounds/*
  • pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }}
  • ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
  • echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
  • rustup target add wasm32-wasip1-threads
View raw YAML
name: Release Insiders

on:
  push:
    branches: [main]

permissions:
  contents: read

env:
  APP_NAME: tailwindcss-oxide
  NODE_VERSION: 24
  OXIDE_LOCATION: ./crates/node
  RELEASE_CHANNEL: insiders

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

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@v6
      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        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@v5
        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@v5
        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@v6
        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@v6
        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@v6
      - 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"
            echo "~~~~ rustc --version ~~~~"
            rustc --version
            echo "~~~~ node -v ~~~~"
            node -v
            echo "~~~~ pnpm --version ~~~~"
            pnpm --version
            pnpm install --ignore-scripts --filter=!./playgrounds/* || true
            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@v6
        with:
          name: bindings-x86_64-unknown-freebsd
          path: ${{ env.OXIDE_LOCATION }}/*.node

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

    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@v6
        with:
          fetch-depth: 20

      - name: Resolve version
        id: vars
        run: |
          echo "SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV

      - uses: pnpm/action-setup@v4

      - name: Use Node.js ${{ env.NODE_VERSION }}
        uses: actions/setup-node@v6
        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@v5
        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@v5
        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@v7
        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: 'Version based on commit: 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }}'
        run: pnpm run version-packages 0.0.0-${{ env.RELEASE_CHANNEL }}.${{ env.SHA_SHORT }}

      - name: Build Tailwind CSS
        run: pnpm run build

      - 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: Upload npm package tarballs
        uses: actions/upload-artifact@v6
        with:
          name: npm-package-tarballs
          path: dist/*.tgz

      - name: Publish
        run: |
          pnpm --recursive --filter="!@tailwindcss/oxide-wasm32-wasi" publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks
          # The wasm package needs a special npm config that isn't read when pnpm --recursive is used
          pushd crates/node/npm/wasm32-wasi; pnpm publish --tag ${{ env.RELEASE_CHANNEL }} --no-git-checks; popd;
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Trigger Tailwind Play update
        uses: actions/github-script@v8
        with:
          github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
          script: |
            await github.rest.actions.createWorkflowDispatch({
              owner: 'tailwindlabs',
              repo: 'upgrades',
              ref: 'main',
              workflow_id: 'upgrade-tailwindcss.yml'
            })