diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 63f79fa10..42645aa49 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -50,7 +50,7 @@ If you open a pull request for a new feature, we're likely to close it not becau
## Coding standards
-Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindlabs/tailwindcss/blob/main/package.json). You can check your code against these standards by running:
+Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindcss/tailwindcss/blob/main/package.json). You can check your code against these standards by running:
```sh
pnpm run lint
@@ -76,7 +76,7 @@ To run the integration tests, use:
pnpm build && pnpm test:integrations
```
-Additionally, some features require testing in browsers (i.e. to ensure CSS variable resolution works as expected). These can be run via:
+Additionally, some features require testing in browsers (i.e to ensure CSS variable resolution works as expected). These can be run via:
```sh
pnpm build && pnpm test:ui
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index aa0793aa3..ee9d3f9ab 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -8,7 +8,7 @@ It's never a fun experience to have your pull request declined after investing a
For more info, check out the contributing guide:
-https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md
+https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md
-->
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5ae59e80f..b0c8f483e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,18 +8,12 @@ on:
permissions:
contents: read
-env:
- NODE_VERSION: 24
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
jobs:
tests:
strategy:
fail-fast: false
matrix:
+ node-version: [20]
runner:
- name: Windows
os: windows-latest
@@ -27,14 +21,12 @@ jobs:
- name: Linux
os: namespace-profile-default
- # Playwright 1.62+ dropped WebKit support for macOS 14, and hangs
- # instead of failing when launching WebKit on a macos-14 runner.
- name: macOS
- os: macos-15
+ 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]' }}
+ - ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') }}
exclude:
- run-all: false
runner:
@@ -49,21 +41,21 @@ jobs:
name: ${{ matrix.runner.name }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
- - name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
with:
- node-version: ${{ env.NODE_VERSION }}
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+ uses: actions/cache@v4
with:
path: |
+ ~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
@@ -72,7 +64,7 @@ jobs:
# Cache the `oxide` Rust build
- name: Cache oxide build
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+ uses: actions/cache@v4
with:
path: |
./crates/node/*.node
@@ -90,7 +82,7 @@ jobs:
run: rustup target add wasm32-wasip1-threads
- name: Install dependencies
- run: pnpm install --frozen-lockfile
+ run: pnpm install
- name: Build
run: pnpm run build
@@ -112,13 +104,9 @@ jobs:
- 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@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
+ if: failure() && github.ref == 'refs/heads/main'
+ 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.'
+ message: 'The [most recent build](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 457c38424..0148dd983 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -8,18 +8,13 @@ on:
permissions:
contents: read
-env:
- NODE_VERSION: 24
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
jobs:
tests:
strategy:
fail-fast: false
matrix:
+ node-version: [20]
+
runner:
- name: Windows
os: windows-latest
@@ -55,25 +50,21 @@ jobs:
name: ${{ matrix.runner.name }} / ${{ matrix.integration }}
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
+ - uses: actions/checkout@v4
+ - 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
with:
- node-version: ${{ env.NODE_VERSION }}
+ node-version: ${{ matrix.node-version }}
+ cache: 'pnpm'
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+ uses: actions/cache@v4
with:
path: |
+ ~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
@@ -82,7 +73,7 @@ jobs:
# Cache the `oxide` Rust build
- name: Cache oxide build
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
+ uses: actions/cache@v4
with:
path: |
./crates/node/*.node
@@ -108,18 +99,24 @@ jobs:
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 }}
+ - name: Test ${{ matrix.integration }} 1/3
+ run: pnpm run test:integrations ./integrations/${{ matrix.integration }} --shard 1/3
+ env:
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+
+ - name: Test ${{ matrix.integration }} 2/3
+ run: pnpm run test:integrations ./integrations/${{ matrix.integration }} --shard 2/3
+ env:
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+
+ - name: Test ${{ matrix.integration }} 3/3
+ run: pnpm run test:integrations ./integrations/${{ matrix.integration }} --shard 3/3
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@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
+ if: failure() && github.ref == 'refs/heads/main'
+ 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.'
+ message: 'The [most recent build](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 24e829afd..be1050b03 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -2,29 +2,18 @@ name: Prepare Release
on:
workflow_dispatch:
- inputs:
- dry_run:
- description: Skip creating the draft GitHub release
- required: false
- default: true
- type: boolean
push:
tags:
- 'v*'
env:
APP_NAME: tailwindcss-oxide
- NODE_VERSION: 24
- PNPM_VERSION: '11.9.0'
+ NODE_VERSION: 20
OXIDE_LOCATION: ./crates/node
permissions:
contents: read
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
jobs:
build:
strategy:
@@ -54,40 +43,44 @@ jobs:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
strip: strip
- build-flags: --use-napi-cross
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- strip: aarch64-linux-gnu-strip
- build-flags: --use-napi-cross
+ 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: arm-linux-gnueabihf-strip
- build-flags: --use-napi-cross
+ 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-zig: true
- build-flags: -x
+ 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
- build-flags: -x
+ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- with:
- version: ${{ env.PNPM_VERSION }}
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- package-manager-cache: false
+ cache: 'pnpm'
- name: Install gcc-arm-linux-gnueabihf
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
@@ -95,56 +88,62 @@ jobs:
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
- - name: Install binutils-aarch64-linux-gnu
- if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
+ # 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: |
- sudo apt-get update
- sudo apt-get install binutils-aarch64-linux-gnu -y
-
- - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
- if: ${{ contains(matrix.target, 'musl') }}
- with:
- version: 0.14.1
- use-cache: false
-
- - name: Install cargo-zigbuild
- uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2
- if: ${{ contains(matrix.target, 'musl') }}
- env:
- GITHUB_TOKEN: ${{ github.token }}
- with:
- tool: cargo-zigbuild
+ rustup default stable
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
- run: pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/*
+ run: pnpm install --ignore-scripts --filter=!./playgrounds/*
- name: Build release
- run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }}
+ 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 || matrix.strip-zig }}
- env:
- STRIP_COMMAND: ${{ matrix.strip }}
- STRIP_ZIG: ${{ matrix.strip-zig }}
- run: |
- if [ "$STRIP_ZIG" = "true" ]; then
- for file in ${{ env.OXIDE_LOCATION }}/*.node; do
- zig objcopy --strip-all "$file" "$file.stripped"
- mv "$file.stripped" "$file"
- done
- exit 0
- fi
-
- eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node"
+ if: ${{ matrix.strip }}
+ run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: ${{ env.OXIDE_LOCATION }}/*.node
@@ -154,11 +153,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
+ - uses: actions/checkout@v4
- name: Build FreeBSD
- uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0
+ uses: cross-platform-actions/action@v0.25.0
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
@@ -174,12 +171,12 @@ jobs:
shell: bash
run: |
sudo pkg install -y -f curl node libnghttp2 npm
- sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true
+ 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 --frozen-lockfile --filter=!./playgrounds/* || true
+ pnpm install --ignore-scripts --filter=!./playgrounds/* || true
echo "~~~~ rustc --version ~~~~"
rustc --version
echo "~~~~ node -v ~~~~"
@@ -190,7 +187,7 @@ jobs:
strip -x ${{ env.OXIDE_LOCATION }}/*.node
ls -la ${{ env.OXIDE_LOCATION }}
- name: Upload artifacts
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: bindings-x86_64-unknown-freebsd
path: ${{ env.OXIDE_LOCATION }}/*.node
@@ -201,17 +198,18 @@ jobs:
name: Build and release Tailwind CSS
permissions:
- contents: write # Required for creating releases
+ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ - uses: actions/checkout@v4
with:
fetch-depth: 20
- persist-credentials: false
- run: git fetch --tags -f
@@ -220,25 +218,51 @@ jobs:
run: |
echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- with:
- version: ${{ env.PNPM_VERSION }}
+ - uses: pnpm/action-setup@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- package-manager-cache: false
+
+ # 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 --frozen-lockfile
+ run: pnpm --filter=!./playgrounds/* install
- name: Download artifacts
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
+ uses: actions/download-artifact@v4
with:
path: ${{ env.OXIDE_LOCATION }}
@@ -277,31 +301,30 @@ jobs:
echo "EOF" >> $GITHUB_ENV
- name: Upload standalone artifacts
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: tailwindcss-standalone
path: packages/@tailwindcss-standalone/dist/
- name: Upload npm package tarballs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: npm-package-tarballs
path: dist/*.tgz
- name: Prepare GitHub Release
- if: ${{ !inputs.dry_run }}
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- gh release create "${TAG_NAME}" \
- --draft \
- --title "${TAG_NAME}" \
- --notes "${RELEASE_NOTES}" \
- 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
+ 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
diff --git a/.github/workflows/release-insiders.yml b/.github/workflows/release-insiders.yml
new file mode 100644
index 000000000..53478a227
--- /dev/null
+++ b/.github/workflows/release-insiders.yml
@@ -0,0 +1,318 @@
+name: Release Insiders
+
+on:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+env:
+ APP_NAME: tailwindcss-oxide
+ NODE_VERSION: 20
+ OXIDE_LOCATION: ./crates/node
+ RELEASE_CHANNEL: insiders
+
+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"
+ 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@v4
+ 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@v4
+ 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@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: '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@v4
+ 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@v7
+ with:
+ github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
+ script: |
+ await github.rest.actions.createWorkflowDispatch({
+ owner: 'tailwindlabs',
+ repo: 'upgrades',
+ ref: 'main',
+ workflow_id: 'upgrade-tailwindcss.yml'
+ })
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2c957d3af..8b27773b3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,34 +1,18 @@
name: Release
on:
- push:
- branches: [main]
release:
types: [published]
workflow_dispatch:
- inputs:
- channel:
- description: Release channel to publish
- required: true
- default: insiders
- type: choice
- options:
- - insiders
- - release
permissions:
contents: read
env:
APP_NAME: tailwindcss-oxide
- NODE_VERSION: 24
- PNPM_VERSION: '11.9.0'
+ NODE_VERSION: 20
OXIDE_LOCATION: ./crates/node
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
- cancel-in-progress: true
-
jobs:
build:
strategy:
@@ -58,40 +42,44 @@ jobs:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
strip: strip
- build-flags: --use-napi-cross
+ container:
+ image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
- strip: aarch64-linux-gnu-strip
- build-flags: --use-napi-cross
+ 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: arm-linux-gnueabihf-strip
- build-flags: --use-napi-cross
+ 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-zig: true
- build-flags: -x
+ 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
- build-flags: -x
+ 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- with:
- version: ${{ env.PNPM_VERSION }}
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- package-manager-cache: false
+ cache: 'pnpm'
- name: Install gcc-arm-linux-gnueabihf
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
@@ -99,56 +87,62 @@ jobs:
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
- - name: Install binutils-aarch64-linux-gnu
- if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
+ # 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: |
- sudo apt-get update
- sudo apt-get install binutils-aarch64-linux-gnu -y
-
- - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
- if: ${{ contains(matrix.target, 'musl') }}
- with:
- version: 0.14.1
- use-cache: false
-
- - name: Install cargo-zigbuild
- uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2
- if: ${{ contains(matrix.target, 'musl') }}
- env:
- GITHUB_TOKEN: ${{ github.token }}
- with:
- tool: cargo-zigbuild
+ rustup default stable
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
- run: pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/*
+ run: pnpm install --ignore-scripts --filter=!./playgrounds/*
- name: Build release
- run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }}
+ 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 || matrix.strip-zig }}
- env:
- STRIP_COMMAND: ${{ matrix.strip }}
- STRIP_ZIG: ${{ matrix.strip-zig }}
- run: |
- if [ "$STRIP_ZIG" = "true" ]; then
- for file in ${{ env.OXIDE_LOCATION }}/*.node; do
- zig objcopy --strip-all "$file" "$file.stripped"
- mv "$file.stripped" "$file"
- done
- exit 0
- fi
-
- eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node"
+ if: ${{ matrix.strip }}
+ run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
- name: Upload artifacts
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: bindings-${{ matrix.target }}
path: ${{ env.OXIDE_LOCATION }}/*.node
@@ -158,11 +152,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
+ - uses: actions/checkout@v4
- name: Build FreeBSD
- uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0
+ uses: cross-platform-actions/action@v0.25.0
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
@@ -178,7 +170,7 @@ jobs:
shell: bash
run: |
sudo pkg install -y -f curl node libnghttp2 npm
- sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true
+ 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
@@ -189,12 +181,12 @@ jobs:
node -v
echo "~~~~ pnpm --version ~~~~"
pnpm --version
- pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/* || true
+ 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ uses: actions/upload-artifact@v4
with:
name: bindings-x86_64-unknown-freebsd
path: ${{ env.OXIDE_LOCATION }}/*.node
@@ -202,10 +194,10 @@ jobs:
release:
runs-on: macos-14
timeout-minutes: 15
- name: Build and publish Tailwind CSS
+ name: Build and release Tailwind CSS
permissions:
- contents: read
+ 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
@@ -214,51 +206,55 @@ jobs:
- build-freebsd
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ - uses: actions/checkout@v4
with:
fetch-depth: 20
- persist-credentials: false
- - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- with:
- version: ${{ env.PNPM_VERSION }}
+ - uses: pnpm/action-setup@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- package-manager-cache: false
- # npm trusted publishing validates the caller workflow filename, so all npm publishes live here.
- # This workflow rebuilds the publish artifacts instead of depending on prepare-release.yml.
- - name: Resolve release metadata
- env:
- INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }}
- run: |
- if [[ "${{ github.event_name }}" == "release" || "$INPUT_CHANNEL" == "release" ]]; then
- release_channel=$(node ./scripts/release-channel.js)
+ # 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') }}
- echo "RELEASE_KIND=release" >> $GITHUB_ENV
- echo "RELEASE_CHANNEL=$release_channel" >> $GITHUB_ENV
- echo "FEATURES_ENV=stable" >> $GITHUB_ENV
- else
- sha_short=$(git rev-parse --short HEAD)
-
- echo "RELEASE_KIND=insiders" >> $GITHUB_ENV
- echo "RELEASE_CHANNEL=insiders" >> $GITHUB_ENV
- echo "SHA_SHORT=$sha_short" >> $GITHUB_ENV
- echo "INSIDERS_VERSION=0.0.0-insiders.$sha_short" >> $GITHUB_ENV
- fi
+ # 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 --frozen-lockfile
+ run: pnpm --filter=!./playgrounds/* install
- name: Download artifacts
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
+ uses: actions/download-artifact@v4
with:
path: ${{ env.OXIDE_LOCATION }}
@@ -278,19 +274,10 @@ jobs:
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: ${{ env.INSIDERS_VERSION }}'
- if: env.RELEASE_KIND == 'insiders'
- run: pnpm run version-packages ${INSIDERS_VERSION}
-
- name: Build Tailwind CSS
- if: env.RELEASE_KIND == 'insiders'
- run: pnpm run build
-
- - name: Build Tailwind CSS
- if: env.RELEASE_KIND == 'release'
run: pnpm run build
env:
- FEATURES_ENV: ${{ env.FEATURES_ENV }}
+ FEATURES_ENV: stable
- name: Run pre-publish optimizations scripts
run: node ./scripts/pre-publish-optimizations.mjs
@@ -298,20 +285,22 @@ jobs:
- name: Lock pre-release versions
run: node ./scripts/lock-pre-release-versions.mjs
- - name: Upload npm package tarballs
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
- with:
- name: npm-package-tarballs
- path: dist/*.tgz
+ - 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 ${RELEASE_CHANNEL} --no-git-checks
+ 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 ${RELEASE_CHANNEL} --no-git-checks --config.node-linker=hoisted; popd;
+ 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ if: env.RELEASE_CHANNEL == 'latest'
+ uses: actions/github-script@v7
with:
github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
script: |
@@ -321,38 +310,3 @@ jobs:
ref: 'main',
workflow_id: 'upgrade-tailwindcss.yml'
})
-
- notify:
- if: ${{ always() && (needs.build.result == 'failure' || needs.build-freebsd.result == 'failure' || needs.release.result == 'failure') }}
- needs:
- - build
- - build-freebsd
- - release
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- persist-credentials: false
-
- - name: Resolve release label
- id: release
- env:
- INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }}
- RELEASE_TAG: ${{ github.event.release.tag_name || '' }}
- run: |
- if [[ "${{ github.event_name }}" == "release" ]]; then
- tag_name="${RELEASE_TAG:-${GITHUB_REF_NAME}}"
- echo "label=release ${tag_name}" >> $GITHUB_OUTPUT
- elif [[ "$INPUT_CHANNEL" == "release" ]]; then
- version=$(node -p "require('./packages/tailwindcss/package.json').version")
- echo "label=release v${version}" >> $GITHUB_OUTPUT
- else
- sha_short=$(git rev-parse --short HEAD)
- echo "label=insiders release 0.0.0-insiders.${sha_short}" >> $GITHUB_OUTPUT
- fi
-
- - name: Notify Discord
- uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
- with:
- webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
- message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) for `${{ steps.release.outputs.label }}` has failed.'
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 000000000..ded82e2f6
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+auto-install-peers = true
diff --git a/.prettierignore b/.prettierignore
index 4f50b932f..3de4530dd 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,6 +4,5 @@ pnpm-lock.yaml
target/
crates/node/index.d.ts
crates/node/index.js
-crates/ignore/
.next
.fingerprint
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..25358efc6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,40 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- `packages/*` — TypeScript packages. Core lives in `packages/tailwindcss`; adapters/tools are under `@tailwindcss-*`.
+- `crates/*` — Rust crates (e.g. `crates/oxide`, `crates/node`). Node bindings are published from here.
+- `integrations/` — Vitest-based integration tests for CLIs/build tools.
+- `scripts/` — Release and maintenance utilities (e.g. `version-packages.mjs`).
+- `dist/` — Packed artifacts after builds; useful for local installs.
+- `playgrounds/*` — Sample apps to manually verify changes.
+
+## Build, Test, and Development Commands
+- `pnpm install` — Install JS deps. Ensure Rust toolchain via `rustup default stable` and add `wasm32-wasip1-threads`.
+- `pnpm dev` — Turbo watch builds; filter example: `pnpm dev --filter=tailwindcss`.
+- `pnpm build` — Build all packages (excludes playgrounds). Creates tarballs in `dist/`.
+- `pnpm test` — Run Rust `cargo test` and Vitest across the workspace.
+- `pnpm test:integrations` — Run integration tests (run `pnpm build` first).
+- `pnpm test:ui` — Run Playwright UI tests for browser-related behavior.
+- `pnpm lint` / `pnpm format` — Check/fix formatting and package-level lint scripts.
+
+## Coding Style & Naming Conventions
+- Prettier is authoritative: `semi: false`, `singleQuote: true`, `printWidth: 100`.
+- Indentation: 2 spaces. TypeScript files prefer `.ts`; compatibility shims may use `.cts`.
+- Naming: kebab-case file names, camelCase functions/vars, PascalCase types/classes. Rust modules use `snake_case`.
+- Run `pnpm lint` locally before opening a PR.
+
+## Testing Guidelines
+- Unit tests: Vitest; name files `*.test.ts` near the code they cover or in `src/**/__tests__`.
+- Integration: tests live in `integrations/**`; run `pnpm build && pnpm test:integrations`.
+- UI: Playwright tests via `pnpm test:ui`.
+- Rust: crate tests run with `cargo test` (also covered by `pnpm test`). Include tests with any behavior change.
+
+## Commit & Pull Request Guidelines
+- Commit messages: clear, present-tense summaries; reference issues like `#123` when relevant.
+- New features: open a discussion before investing significant work.
+- PRs must include: problem summary, rationale, and a test plan (exact commands; screenshots if UI-related). Ensure all tests pass; use `[ci-all]` in the description to run all platform suites.
+- See `.github/CONTRIBUTING.md` and `.github/PULL_REQUEST_TEMPLATE.md` for details.
+
+## Security & Configuration Tips
+- Do not commit secrets or machine-specific paths. Use `pnpm@9` (workspace-managed) and Rust stable. Turbo caches builds; when in doubt, rebuild with `pnpm build`.
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5e6f6f89..2b746da01 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,289 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
-- Add `@tailwindcss/turbopack` package to run Tailwind CSS with Next.js ([20367](https://github.com/tailwindlabs/tailwindcss/pull/20367))
-
-### Fixed
-
-- Ensure watch mode detects changes to symlinked `@source` files whose real paths aren't otherwise scanned ([#20356](https://github.com/tailwindlabs/tailwindcss/pull/20356))
-- Ensure custom variants using `@scope` wrap the generated utilities instead of nesting inside them ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369))
-- Fix flattening of `@scope` at-rules ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369))
-- Fix standalone declarations in `@scope`, wrap them in `:where(:scope)` ([#20369](https://github.com/tailwindlabs/tailwindcss/pull/20369))
-- Always emit a space for empty fallback values in CSS variables (e.g. `var(--tw-blur,)` → `var(--tw-blur, )`) ([#20373](https://github.com/tailwindlabs/tailwindcss/pull/20373))
-- Canonicalization: convert arbitrary breakpoint and container query variants to named equivalents (e.g. `max-[64rem]` → `max-lg`) ([#20380](https://github.com/tailwindlabs/tailwindcss/pull/20380))
-- Prevent `@tailwindcss/vite` from crashing on every edit under Vite's experimental `bundledDev` mode ([#20379](https://github.com/tailwindlabs/tailwindcss/pull/20379))
-- Ensure `@tailwindcss/oxide` falls back to WASM on platforms without native bindings ([#20383](https://github.com/tailwindlabs/tailwindcss/pull/20383))
-- Detect classes in Ruby percent literals using angle brackets or custom delimiters (e.g. `%w