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/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 848c851b1..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://tailwindcss.com/sponsor'] 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..824056b09 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 @@ -113,13 +104,9 @@ jobs: 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/CHANGELOG.md b/CHANGELOG.md index e5e6f6f89..e772f3eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,429 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### 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`, `%w|flex|`), including in Slim and Haml templates ([#20387](https://github.com/tailwindlabs/tailwindcss/pull/20387)) -- Preserve whitespace in `--default(…)` values in custom functional utilities (e.g. `--default(box alphabetic)` no longer becomes `boxalphabetic`) ([#20392](https://github.com/tailwindlabs/tailwindcss/pull/20392)) -- Don't scan gitignored directories (e.g. `node_modules` and `.git`) when the project uses a safelist-style `.gitignore` (e.g. `/*` followed by `!/…` negations) ([#20397](https://github.com/tailwindlabs/tailwindcss/pull/20397)) -- Ensure root `theme('…')` namespace lookups in JavaScript plugins and config files return the full namespace object instead of the value of its `DEFAULT` key ([#20399](https://github.com/tailwindlabs/tailwindcss/pull/20399)) -- Skip ignored directories entirely when computing watch globs (`scanner.globs`), instead of walking their full contents on every rebuild ([#20408](https://github.com/tailwindlabs/tailwindcss/pull/20408)) -- Oxide: drop invalid UTF-8 candidates ([#20389](https://github.com/tailwindlabs/tailwindcss/pull/20389)) -- `@tailwindcss/vite` no longer forces a full page reload for external files (e.g.: `.php` files) ([#20414](https://github.com/tailwindlabs/tailwindcss/issues/20414)) -- Canonicalization: don't merge utilities that reference different theme variables set to CSS-wide keywords like `unset` ([#20417](https://github.com/tailwindlabs/tailwindcss/pull/20417)) -- Don't generate utilities when a modifier is used that would otherwise be silently ignored (e.g. `rounded-sm/[5]`, `shadow-sm/foo`, `stroke-2/50`) ([#20419](https://github.com/tailwindlabs/tailwindcss/pull/20419)) -- Only normalize top-level `and`, `or`, and `not` keywords in `supports-[…]` variants (e.g. `selector(a: not (.foo))` → `selector(a:not(.foo))`) ([#20420](https://github.com/tailwindlabs/tailwindcss/pull/20420)) -- Don't warn about Angular's `::ng-deep` and `:host-context()` when optimizing CSS ([#20434](https://github.com/tailwindlabs/tailwindcss/pull/20434)) -- Don't generate CSS for candidates containing an empty additional modifier (e.g. `bg-red-500/50/` and `group-hover/foo//bar:flex`) ([#20466](https://github.com/tailwindlabs/tailwindcss/pull/20466)) -- Sort `min-*`, `max-*`, and container query variants with decimal values numerically (e.g. `min-[40.25rem]` before `min-[40.5rem]`) ([#20512](https://github.com/tailwindlabs/tailwindcss/pull/20512)) -- Ensure CSS comments ending with `\*/` are closed correctly instead of swallowing the CSS that follows (e.g. `/* C:\temp\*/`) ([#20508](https://github.com/tailwindlabs/tailwindcss/pull/20508)) -- Improve style invalidation performance of `group-*` and `peer-*` variants ([#20513](https://github.com/tailwindlabs/tailwindcss/pull/20513)) - -## [4.3.3] - 2026-07-16 - -### Fixed - -- Support `--watch --poll[=ms]` in `@tailwindcss/cli` when filesystem events are unreliable or unavailable ([#20297](https://github.com/tailwindlabs/tailwindcss/pull/20297)) -- Canonicalization: match arbitrary hex colors against theme colors case-insensitively (e.g. `bg-[#fff]` and `bg-[#FFF]` → `bg-white`) ([#20298](https://github.com/tailwindlabs/tailwindcss/pull/20298)) -- Prevent Preflight from overriding Firefox's native `iframe:focus-visible` outline styles ([#20292](https://github.com/tailwindlabs/tailwindcss/pull/20292)) -- Ensure `theme('colors.foo')` in JS plugins resolves correctly when both `--color-foo` and `--color-foo-bar` exist ([#20299](https://github.com/tailwindlabs/tailwindcss/pull/20299)) -- Ensure fractional opacity modifiers work with named shadow sizes like `shadow-sm/12.5`, `text-shadow-sm/12.5`, `drop-shadow-sm/12.5`, and `inset-shadow-sm/12.5` ([#20302](https://github.com/tailwindlabs/tailwindcss/pull/20302)) -- Parse selectors like `[data-foo]div` as two selectors instead of one ([#20303](https://github.com/tailwindlabs/tailwindcss/pull/20303)) -- Ensure `@tailwindcss/postcss` rebuilds when a preprocessor like Sass changes the input CSS without changing the input file on disk ([#20310](https://github.com/tailwindlabs/tailwindcss/pull/20310)) -- Ensure CSS nesting is handled even when Lightning CSS isn't run, such as in `@tailwindcss/browser` and Tailwind Play ([#20124](https://github.com/tailwindlabs/tailwindcss/pull/20124)) -- Prevent achromatic theme colors from shifting hue when mixed in polar color spaces like `oklch` ([#20314](https://github.com/tailwindlabs/tailwindcss/pull/20314)) -- Ensure `--spacing(0)` is optimized to `0px` instead of `0` so it remains a `` when used in `calc(…)` ([#20319](https://github.com/tailwindlabs/tailwindcss/pull/20319)) -- Load `@parcel/watcher` only when needed in `@tailwindcss/cli --watch` mode, so one-off builds and `--watch --poll` work when `@parcel/watcher` can't be loaded ([#20325](https://github.com/tailwindlabs/tailwindcss/pull/20325)) -- Use explicit platform fonts instead of `system-ui` and `ui-sans-serif` so CJK text respects the page's `lang` attribute on Windows ([#20318](https://github.com/tailwindlabs/tailwindcss/pull/20318)) -- Prevent `@tailwindcss/upgrade` from rewriting ignored files when run from a subdirectory ([#20329](https://github.com/tailwindlabs/tailwindcss/pull/20329)) -- Ensure earlier `@source` rules pointing to nested files are scanned when later `@source` rules point to files in parent folders ([#20335](https://github.com/tailwindlabs/tailwindcss/pull/20335)) -- Prevent `@tailwindcss/vite` from triggering full page reloads when scanned files are processed by Vite but haven't been loaded as modules yet ([#20336](https://github.com/tailwindlabs/tailwindcss/pull/20336)) - -## [4.3.2] - 2026-06-26 - -### Fixed - -- Support bare spacing values for `auto-rows-*` and `auto-cols-*` utilities (e.g. `auto-rows-12` and `auto-cols-16`) ([#20229](https://github.com/tailwindlabs/tailwindcss/pull/20229)) -- Prevent `@tailwindcss/cli` in `--watch` mode from crashing on Windows when `@source` points to a directory that doesn't exist ([#20242](https://github.com/tailwindlabs/tailwindcss/pull/20242)) -- Prevent `@tailwindcss/vite` from crashing in Deno v2.8.x when `context.parentURL` is not a valid URL ([#20245](https://github.com/tailwindlabs/tailwindcss/pull/20245)) -- Ensure `@tailwindcss/cli` in `--watch` mode rebuilds when the input CSS file changes in an ignored directory ([#20246](https://github.com/tailwindlabs/tailwindcss/pull/20246)) -- Allow `@variant` rules used in `addBase(…)` to use custom variants defined later ([#20247](https://github.com/tailwindlabs/tailwindcss/pull/20247)) -- Prevent `@tailwindcss/vite` from crashing during HMR when scanned files or directories are deleted ([#20259](https://github.com/tailwindlabs/tailwindcss/pull/20259)) -- Generate `font-size` instead of `color` declarations for `text-[--spacing(…)]` ([#20260](https://github.com/tailwindlabs/tailwindcss/pull/20260)) -- Prevent `@source` patterns from scanning unrelated sibling files and folders ([#20263](https://github.com/tailwindlabs/tailwindcss/pull/20263)) -- Extract class candidates adjacent to Template Toolkit delimiters like `%]…[%` in `.tt`, `.tt2`, and `.tx` files ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269)) -- Extract class candidates from conditional Maud syntax like `p.text-black[condition]` ([#20269](https://github.com/tailwindlabs/tailwindcss/pull/20269)) -- Prevent `@position-try` rules from triggering unknown at-rule warnings when optimizing CSS ([#20277](https://github.com/tailwindlabs/tailwindcss/pull/20277)) -- Support class suggestions for named opacity modifiers from `--opacity` theme values ([#20287](https://github.com/tailwindlabs/tailwindcss/pull/20287)) -- Prevent type errors in `@tailwindcss/postcss` when used with newer PostCSS patch releases ([#20289](https://github.com/tailwindlabs/tailwindcss/pull/20289)) - -## [4.3.1] - 2026-06-12 - -### Added - -- Add `--silent` option to suppress output in `@tailwindcss/cli` ([#20100](https://github.com/tailwindlabs/tailwindcss/pull/20100)) - -### Fixed - -- Remove deprecation warnings by using `Module#registerHooks` instead of `Module#register` on Node 26+ ([#20028](https://github.com/tailwindlabs/tailwindcss/pull/20028)) -- Canonicalization: don't crash when plugin utilities throw for unsupported values ([#20052](https://github.com/tailwindlabs/tailwindcss/pull/20052)) -- Allow `@apply` to be used with CSS mixins ([#19427](https://github.com/tailwindlabs/tailwindcss/pull/19427)) -- Ensure `not-*` correctly negates `@container` queries, including `style(…)` queries ([#20059](https://github.com/tailwindlabs/tailwindcss/pull/20059)) -- Ensure `drop-shadow-*` color utilities work with custom shadow values containing `calc(…)` ([#20080](https://github.com/tailwindlabs/tailwindcss/pull/20080)) -- Fix 'Sourcemap is likely to be incorrect' warnings when using `@tailwindcss/vite` ([#20103](https://github.com/tailwindlabs/tailwindcss/pull/20103)) -- Ensure `@tailwindcss/webpack` can be installed in Rspack projects without requiring `webpack` as a peer dependency ([#20027](https://github.com/tailwindlabs/tailwindcss/pull/20027)) -- Canonicalization: don't suggest invalid `calc(…)` expressions (e.g. `px-[calc(1rem+0px)]` → `px-[calc(1rem+0)]`) ([#20127](https://github.com/tailwindlabs/tailwindcss/pull/20127)) -- Canonicalization: avoid suggesting large spacing-scale values for arbitrary lengths (e.g. `left-[99999px]` → `left-[99999px]`, not `left-24999.75`) ([#20130](https://github.com/tailwindlabs/tailwindcss/pull/20130)) -- Ensure `@tailwindcss/cli` in `--watch` mode recovers when a tracked dependency is deleted and restored ([#20137](https://github.com/tailwindlabs/tailwindcss/pull/20137)) -- Ensure standalone `@tailwindcss/cli` binaries are ignored when scanning for class candidates ([#20139](https://github.com/tailwindlabs/tailwindcss/pull/20139)) -- Ensure class candidates are extracted from Twig `addClass(…)` and `removeClass(…)` calls ([#20198](https://github.com/tailwindlabs/tailwindcss/pull/20198)) -- Don't crash in the Ruby or Vue preprocessors when scanning files containing invalid UTF-8 bytes ([#19588](https://github.com/tailwindlabs/tailwindcss/pull/19588)) -- Allow `@variant` to be used inside `addBase` ([#19480](https://github.com/tailwindlabs/tailwindcss/pull/19480)) -- Ensure `@source` globs with symlinks are preserved ([#20203](https://github.com/tailwindlabs/tailwindcss/pull/20203)) -- Ensure later `@source` rules can re-include files excluded by earlier `@source not` rules ([#20203](https://github.com/tailwindlabs/tailwindcss/pull/20203)) -- Upgrade: don't migrate empty class rules to invalid `@utility` rules ([#20205](https://github.com/tailwindlabs/tailwindcss/pull/20205)) -- Ensure transitions between `inset-shadow-none` and other inset shadows work correctly ([#20208](https://github.com/tailwindlabs/tailwindcss/pull/20208)) -- Ensure explicitly referenced `@source` directories are scanned even when ignored by git ([#20214](https://github.com/tailwindlabs/tailwindcss/pull/20214)) -- Ensure `@source` globs ending in `**/*` preserve dynamic path segments to avoid scanning too many files ([#20217](https://github.com/tailwindlabs/tailwindcss/pull/20217)) -- Canonicalization: don't fold `calc(…)` divisions when the result would require high precision (e.g. `w-[calc(100%/3.5)]` → `w-[calc(100%/3.5)]`, not `w-[28.571428571428573%]`) ([#20221](https://github.com/tailwindlabs/tailwindcss/pull/20221)) -- Serve ESM type declarations to ESM importers of `@tailwindcss/postcss` ([#20228](https://github.com/tailwindlabs/tailwindcss/pull/20228)) - -### Changed - -- Generate `0` instead of `calc(var(--spacing) * 0)` for spacing utilities like `m-0` and `left-0` ([#20196](https://github.com/tailwindlabs/tailwindcss/pull/20196)) -- Generate `var(--spacing)` instead of `calc(var(--spacing) * 1)` for spacing utilities like `m-1` and `left-1` ([#20196](https://github.com/tailwindlabs/tailwindcss/pull/20196)) - -## [4.3.0] - 2026-05-08 - -### Added - -- Add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901)) -- Add `scrollbar-{auto,thin,none}` utilities for `scrollbar-width`, and `scrollbar-thumb-*` / `scrollbar-track-*` color utilities for `scrollbar-color` ([#19981](https://github.com/tailwindlabs/tailwindcss/pull/19981), [#20019](https://github.com/tailwindlabs/tailwindcss/pull/20019)) -- Add `scrollbar-gutter-*` utilities ([#20018](https://github.com/tailwindlabs/tailwindcss/pull/20018)) -- Add `zoom-*` utilities ([#20020](https://github.com/tailwindlabs/tailwindcss/pull/20020)) -- Add `tab-*` utilities ([#20022](https://github.com/tailwindlabs/tailwindcss/pull/20022)) -- Allow using `@variant` with stacked variants (e.g. `@variant hover:focus { … }`) ([#19996](https://github.com/tailwindlabs/tailwindcss/pull/19996)) -- Allow using `@variant` with compound variants (e.g. `@variant hover, focus { … }`) ([#19996](https://github.com/tailwindlabs/tailwindcss/pull/19996)) -- Support `--default(…)` in `--value(…)` and `--modifier(…)` for functional `@utility` definitions ([#19989](https://github.com/tailwindlabs/tailwindcss/pull/19989)) - -### Fixed - -- Ensure `@plugin` resolves package JavaScript entries instead of browser CSS entries when using `@tailwindcss/vite` ([#19949](https://github.com/tailwindlabs/tailwindcss/pull/19949)) -- Fix relative `@import` and `@plugin` paths resolving from the wrong directory when using `@tailwindcss/vite` ([#19965](https://github.com/tailwindlabs/tailwindcss/pull/19965)) -- Ensure CSS files containing `@variant` are processed by `@tailwindcss/vite` ([#19966](https://github.com/tailwindlabs/tailwindcss/pull/19966)) -- Resolve imports relative to `base` when `result.opts.from` is not provided when using `@tailwindcss/postcss` ([#19980](https://github.com/tailwindlabs/tailwindcss/pull/19980)) -- Canonicalization: preserve significant `_` whitespace in arbitrary values ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986)) -- Canonicalization: add parentheses when removing whitespace from arbitrary values would hurt readability (e.g. `w-[calc(100%---spacing(60))]` → `w-[calc(100%-(--spacing(60)))]`) ([#19986](https://github.com/tailwindlabs/tailwindcss/pull/19986)) -- Canonicalization: preserve the original unit in arbitrary values instead of normalizing to base units (e.g. `-mt-[20in]` → `mt-[-20in]`, not `mt-[-1920px]`) ([#19988](https://github.com/tailwindlabs/tailwindcss/pull/19988)) -- Canonicalization: migrate arbitrary `:has()` variants from `[&:has(…)]` to `has-[…]` ([#19991](https://github.com/tailwindlabs/tailwindcss/pull/19991)) -- Upgrade: don’t migrate inline `style` attributes (e.g. `style="flex-grow: 1"` → `style="flex-grow: 1"`, not `style="grow: 1"`) ([#19918](https://github.com/tailwindlabs/tailwindcss/pull/19918)) -- Allow multiple `@utility` definitions with the same name but different value types ([#19777](https://github.com/tailwindlabs/tailwindcss/pull/19777)) -- Export missing `PluginWithConfig` type from `tailwindcss/plugin` to fix errors when inferring plugin config types ([#19707](https://github.com/tailwindlabs/tailwindcss/pull/19707)) -- Ensure `start` and `end` legacy utilities without values do not generate CSS ([#20003](https://github.com/tailwindlabs/tailwindcss/pull/20003)) -- Ensure `--value(…)` is required in functional `@utility` definitions ([#20005](https://github.com/tailwindlabs/tailwindcss/pull/20005)) -- Canonicalization: preserve required whitespace around operators in negated arbitrary values (e.g. `-left-[(var(--a)+var(--b))]`) ([#20011](https://github.com/tailwindlabs/tailwindcss/pull/20011)) - -## [4.2.4] - 2026-04-21 - -### Fixed - -- Ensure imports in `@import` and `@plugin` still resolve correctly when using Vite aliases in `@tailwindcss/vite` ([#19947](https://github.com/tailwindlabs/tailwindcss/pull/19947)) - -## [4.2.3] - 2026-04-20 - -### Fixed - -- Canonicalization: improve canonicalization for `tracking-*` utilities by preferring non-negative utilities (e.g. `-tracking-tighter` → `tracking-wider`) ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827)) -- Fix crash due to invalid characters in candidate (exceeding valid unicode code point range) ([#19829](https://github.com/tailwindlabs/tailwindcss/pull/19829)) -- Ensure query params in imports are considered unique resources when using `@tailwindcss/webpack` ([#19723](https://github.com/tailwindlabs/tailwindcss/pull/19723)) -- Canonicalization: collapse arbitrary values into shorthand utilities (e.g. `px-[1.2rem] py-[1.2rem]` → `p-[1.2rem]`) ([#19837](https://github.com/tailwindlabs/tailwindcss/pull/19837)) -- Canonicalization: collapse `border-{t,b}-*` into `border-y-*`, `border-{l,r}-*` into `border-x-*`, and `border-{t,r,b,l}-*` into `border-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `scroll-m{t,b}-*` into `scroll-my-*`, `scroll-m{l,r}-*` into `scroll-mx-*`, and `scroll-m{t,r,b,l}-*` into `scroll-m-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `scroll-p{t,b}-*` into `scroll-py-*`, `scroll-p{l,r}-*` into `scroll-px-*`, and `scroll-p{t,r,b,l}-*` into `scroll-p-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `overflow-{x,y}-*` into `overflow-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Canonicalization: collapse `overscroll-{x,y}-*` into `overscroll-*` ([#19842](https://github.com/tailwindlabs/tailwindcss/pull/19842)) -- Read from `--placeholder-color` instead of `--background-color` for `placeholder-*` utilities ([#19843](https://github.com/tailwindlabs/tailwindcss/pull/19843)) -- Upgrade: ensure files are not emptied out when killing the upgrade process while it's running ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Upgrade: use `config.content` when migrating from Tailwind CSS v3 to Tailwind CSS v4 ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Upgrade: never migrate files that are ignored by git ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Add `.env` and `.env.*` to default ignored content files ([#19846](https://github.com/tailwindlabs/tailwindcss/pull/19846)) -- Canonicalization: migrate `overflow-ellipsis` into `text-ellipsis` ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: migrate `start-full` → `inset-s-full`, `start-auto` → `inset-s-auto`, `start-px` → `inset-s-px`, and `start-` → `inset-s-` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: migrate `end-full` → `inset-e-full`, `end-auto` → `inset-e-auto`, `end-px` → `inset-e-px`, and `end-` → `inset-e-` as well as negative versions ([#19849](https://github.com/tailwindlabs/tailwindcss/pull/19849)) -- Canonicalization: move the `-` sign inside the arbitrary value `-left-[9rem]` → `left-[-9rem]` ([#19858](https://github.com/tailwindlabs/tailwindcss/pull/19858)) -- Canonicalization: move the `-` sign outside the arbitrary value `ml-[calc(-1*var(--width))]` → `-ml-(--width)` ([#19858](https://github.com/tailwindlabs/tailwindcss/pull/19858)) -- Improve performance when scanning JSONL / NDJSON files ([#19862](https://github.com/tailwindlabs/tailwindcss/pull/19862)) -- Support `NODE_PATH` environment variable in standalone CLI ([#19617](https://github.com/tailwindlabs/tailwindcss/pull/19617)) - -## [4.2.2] - 2026-03-18 - -### Fixed - -- Don't crash when candidates contain prototype properties like `row-constructor` ([#19725](https://github.com/tailwindlabs/tailwindcss/pull/19725)) -- Canonicalize `calc(var(--spacing)*…)` expressions into `--spacing(…)` ([#19769](https://github.com/tailwindlabs/tailwindcss/pull/19769)) -- Fix crash in canonicalization step when handling utilities containing `@property` at-rules (e.g. `shadow-sm border`) ([#19727](https://github.com/tailwindlabs/tailwindcss/pull/19727)) -- Skip full reload for server only modules scanned by client CSS when using `@tailwindcss/vite` ([#19745](https://github.com/tailwindlabs/tailwindcss/pull/19745)) -- Add support for Vite 8 in `@tailwindcss/vite` ([#19790](https://github.com/tailwindlabs/tailwindcss/pull/19790)) -- Improve canonicalization for bare values exceeding default spacing scale suggestions (e.g. `w-1234 h-1234` → `size-1234`) ([#19809](https://github.com/tailwindlabs/tailwindcss/pull/19809)) -- Fix canonicalization resulting in empty list (e.g. `w-5 h-5 size-5` → `''` instead of `size-5`) ([#19812](https://github.com/tailwindlabs/tailwindcss/pull/19812)) -- Resolve tsconfig paths to allow for `@import '@/path/to/file';` when using `@tailwindcss/vite` ([#19803](https://github.com/tailwindlabs/tailwindcss/pull/19803)) - -## [4.2.1] - 2026-02-23 - -### Fixed - -- Allow trailing dash in functional utility names for backwards compatibility ([#19696](https://github.com/tailwindlabs/tailwindcss/pull/19696)) -- Properly detect classes containing `.` characters within curly braces in MDX files ([#19711](https://github.com/tailwindlabs/tailwindcss/pull/19711)) - -## [4.2.0] - 2026-02-18 - -### Added - -- Add mauve, olive, mist, and taupe color palettes to the default theme ([#19627](https://github.com/tailwindlabs/tailwindcss/pull/19627)) -- Add `@tailwindcss/webpack` package to run Tailwind CSS as a webpack plugin ([#19610](https://github.com/tailwindlabs/tailwindcss/pull/19610)) -- Add `pbs-*` and `pbe-*` utilities for `padding-block-start` and `padding-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `mbs-*` and `mbe-*` utilities for `margin-block-start` and `margin-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `scroll-pbs-*` and `scroll-pbe-*` utilities for `scroll-padding-block-start` and `scroll-padding-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `scroll-mbs-*` and `scroll-mbe-*` utilities for `scroll-margin-block-start` and `scroll-margin-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `border-bs-*` and `border-be-*` utilities for `border-block-start` and `border-block-end` ([#19601](https://github.com/tailwindlabs/tailwindcss/pull/19601)) -- Add `inline-*`, `min-inline-*`, `max-inline-*` utilities for `inline-size`, `min-inline-size`, and `max-inline-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612)) -- Add `block-*`, `min-block-*`, `max-block-*` utilities for `block-size`, `min-block-size`, and `max-block-size` ([#19612](https://github.com/tailwindlabs/tailwindcss/pull/19612)) -- Add `inset-s-*`, `inset-e-*`, `inset-bs-*`, `inset-be-*` utilities for `inset-inline-start`, `inset-inline-end`, `inset-block-start`, and `inset-block-end` ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613)) -- Add `font-features-*` utility for `font-feature-settings` ([#19623](https://github.com/tailwindlabs/tailwindcss/pull/19623)) - -### Fixed - -- Prevent double `@supports` wrapper for `color-mix` values ([#19450](https://github.com/tailwindlabs/tailwindcss/pull/19450)) -- Allow whitespace around `@source inline()` argument ([#19461](https://github.com/tailwindlabs/tailwindcss/pull/19461)) -- Emit comment when source maps are saved to files when using `@tailwindcss/cli` ([#19447](https://github.com/tailwindlabs/tailwindcss/pull/19447)) -- Detect utilities containing capital letters followed by numbers ([#19465](https://github.com/tailwindlabs/tailwindcss/pull/19465)) -- Fix class extraction for Rails' strict locals ([#19525](https://github.com/tailwindlabs/tailwindcss/pull/19525)) -- Align `@utility` name validation with Oxide scanner rules ([#19524](https://github.com/tailwindlabs/tailwindcss/pull/19524)) -- Fix infinite loop when using `@variant` inside `@custom-variant` ([#19633](https://github.com/tailwindlabs/tailwindcss/pull/19633)) -- Allow multiples of `.25` in `aspect-*` fractions (e.g. `aspect-8.5/11`) ([#19688](https://github.com/tailwindlabs/tailwindcss/pull/19688)) -- Ensure changes to external files listed via `@source` trigger a full page reload when using `@tailwindcss/vite` ([#19670](https://github.com/tailwindlabs/tailwindcss/pull/19670)) -- Improve performance of Oxide scanner in bigger projects by reducing file system walks ([#19632](https://github.com/tailwindlabs/tailwindcss/pull/19632)) -- Ensure import aliases in Astro v5 work without crashing when using `@tailwindcss/vite` ([#19677](https://github.com/tailwindlabs/tailwindcss/issues/19677)) -- Allow escape characters in `@utility` names to improve support with formatters such as Biome ([#19626](https://github.com/tailwindlabs/tailwindcss/pull/19626)) -- Fix incorrect canonicalization results when canonicalizing multiple times ([#19675](https://github.com/tailwindlabs/tailwindcss/pull/19675)) -- Add `.jj` to default ignored content directories ([#19687](https://github.com/tailwindlabs/tailwindcss/pull/19687)) - -### Deprecated - -- Deprecate `start-*` and `end-*` utilities in favor of `inset-s-*` and `inset-e-*` utilities ([#19613](https://github.com/tailwindlabs/tailwindcss/pull/19613)) - -## [4.1.18] - 2025-12-11 - -### Fixed - -- Ensure validation of `source(…)` happens relative to the file it is in ([#19274](https://github.com/tailwindlabs/tailwindcss/pull/19274)) -- Include filename and line numbers in CSS parse errors ([#19282](https://github.com/tailwindlabs/tailwindcss/pull/19282)) -- Skip comments in Ruby files when checking for class names ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) -- Skip over arbitrary property utilities with a top-level `!` in the value ([#19243](https://github.com/tailwindlabs/tailwindcss/pull/19243)) -- Support environment API in `@tailwindcss/vite` ([#18970](https://github.com/tailwindlabs/tailwindcss/pull/18970)) -- Preserve case of theme keys from JS configs and plugins ([#19337](https://github.com/tailwindlabs/tailwindcss/pull/19337)) -- Write source maps correctly on the CLI when using `--watch` ([#19373](https://github.com/tailwindlabs/tailwindcss/pull/19373)) -- Handle special defaults (like `ringColor.DEFAULT`) in JS configs ([#19348](https://github.com/tailwindlabs/tailwindcss/pull/19348)) -- Improve backwards compatibility for `content` theme key from JS configs ([#19381](https://github.com/tailwindlabs/tailwindcss/pull/19381)) -- Upgrade: Handle `future` and `experimental` config keys ([#19344](https://github.com/tailwindlabs/tailwindcss/pull/19344)) -- Try to canonicalize any arbitrary utility to a bare value ([#19379](https://github.com/tailwindlabs/tailwindcss/pull/19379)) -- Validate candidates similarly to Oxide ([#19397](https://github.com/tailwindlabs/tailwindcss/pull/19397)) -- Canonicalization: combine `text-*` and `leading-*` classes ([#19396](https://github.com/tailwindlabs/tailwindcss/pull/19396)) -- Correctly handle duplicate CLI arguments ([#19416](https://github.com/tailwindlabs/tailwindcss/pull/19416)) -- Don’t emit color-mix fallback rules inside `@keyframes` ([#19419](https://github.com/tailwindlabs/tailwindcss/pull/19419)) -- CLI: Don't hang when output is `/dev/stdout` ([#19421](https://github.com/tailwindlabs/tailwindcss/pull/19421)) - -## [3.4.19] - 2025-12-10 - -### Fixed - -- Don’t break `sibling-*()` functions when used inside `calc(…)` ([#19335](https://github.com/tailwindlabs/tailwindcss/pull/19335)) - -## [4.1.17] - 2025-11-06 - -### Fixed - -- Substitute `@variant` inside legacy JS APIs ([#19263](https://github.com/tailwindlabs/tailwindcss/pull/19263)) -- Prevent occasional crash on Windows when loaded into a worker thread ([#19242](https://github.com/tailwindlabs/tailwindcss/pull/19242)) - -## [4.1.16] - 2025-10-23 - -### Fixed - -- Discard candidates with an empty data type ([#19172](https://github.com/tailwindlabs/tailwindcss/pull/19172)) -- Fix canonicalization of arbitrary variants with attribute selectors ([#19176](https://github.com/tailwindlabs/tailwindcss/pull/19176)) -- Fix invalid colors due to nested `&` ([#19184](https://github.com/tailwindlabs/tailwindcss/pull/19184)) -- Improve canonicalization for `& > :pseudo` and `& :pseudo` arbitrary variants ([#19178](https://github.com/tailwindlabs/tailwindcss/pull/19178)) - -## [4.1.15] - 2025-10-20 - -### Fixed - -- Fix Safari devtools rendering issue due to `color-mix` fallback ([#19069](https://github.com/tailwindlabs/tailwindcss/pull/19069)) -- Suppress Lightning CSS warnings about `:deep`, `:slotted`, and `:global` ([#19094](https://github.com/tailwindlabs/tailwindcss/pull/19094)) -- Fix resolving theme keys when starting with the name of another theme key in JS configs and plugins ([#19097](https://github.com/tailwindlabs/tailwindcss/pull/19097)) -- Allow named groups in combination with `not-*`, `has-*`, and `in-*` ([#19100](https://github.com/tailwindlabs/tailwindcss/pull/19100)) -- Prevent important utilities from affecting other utilities ([#19110](https://github.com/tailwindlabs/tailwindcss/pull/19110)) -- Don’t index into strings with the `theme(…)` function ([#19111](https://github.com/tailwindlabs/tailwindcss/pull/19111)) -- Fix parsing issue when `\t` is used in at-rules ([#19130](https://github.com/tailwindlabs/tailwindcss/pull/19130)) -- Upgrade: Canonicalize utilities containing `0` values ([#19095](https://github.com/tailwindlabs/tailwindcss/pull/19095)) -- Upgrade: Migrate deprecated `break-words` to `wrap-break-word` ([#19157](https://github.com/tailwindlabs/tailwindcss/pull/19157)) - -### Changed - -- Remove the `postinstall` script from oxide ([#19149])(https://github.com/tailwindlabs/tailwindcss/pull/19149) - -## [4.1.14] - 2025-10-01 - -### Fixed - -- Handle `'` syntax in ClojureScript when extracting classes ([#18888](https://github.com/tailwindlabs/tailwindcss/pull/18888)) -- Handle `@variant` inside `@custom-variant` ([#18885](https://github.com/tailwindlabs/tailwindcss/pull/18885)) -- Merge suggestions when using `@utility` ([#18900](https://github.com/tailwindlabs/tailwindcss/pull/18900)) -- Ensure that file system watchers created when using the CLI are always cleaned up ([#18905](https://github.com/tailwindlabs/tailwindcss/pull/18905)) -- Do not generate `grid-column` utilities when configuring `grid-column-start` or `grid-column-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) -- Do not generate `grid-row` utilities when configuring `grid-row-start` or `grid-row-end` ([#18907](https://github.com/tailwindlabs/tailwindcss/pull/18907)) -- Prevent duplicate CSS when overwriting a static utility with a theme key ([#18056](https://github.com/tailwindlabs/tailwindcss/pull/18056)) -- Show Lightning CSS warnings (if any) when optimizing/minifying ([#18918](https://github.com/tailwindlabs/tailwindcss/pull/18918)) -- Use `default` export condition for `@tailwindcss/vite` ([#18948](https://github.com/tailwindlabs/tailwindcss/pull/18948)) -- Re-throw errors from PostCSS nodes ([#18373](https://github.com/tailwindlabs/tailwindcss/pull/18373)) -- Detect classes in markdown inline directives ([#18967](https://github.com/tailwindlabs/tailwindcss/pull/18967)) -- Ensure files with only `@theme` produce no output when built ([#18979](https://github.com/tailwindlabs/tailwindcss/pull/18979)) -- Support Maud templates when extracting classes ([#18988](https://github.com/tailwindlabs/tailwindcss/pull/18988)) -- Upgrade: Do not migrate `variant = 'outline'` during upgrades ([#18922](https://github.com/tailwindlabs/tailwindcss/pull/18922)) -- Upgrade: Show version mismatch (if any) when running upgrade tool ([#19028](https://github.com/tailwindlabs/tailwindcss/pull/19028)) -- Upgrade: Ensure first class inside `className` is migrated ([#19031](https://github.com/tailwindlabs/tailwindcss/pull/19031)) -- Upgrade: Migrate classes inside `*ClassName` and `*Class` attributes ([#19031](https://github.com/tailwindlabs/tailwindcss/pull/19031)) - -## [3.4.18] - 2025-10-01 - -### Fixed - -- Improve support for raw `supports-[…]` queries in arbitrary values ([#13605](https://github.com/tailwindlabs/tailwindcss/pull/13605)) -- Fix `require.cache` error when loaded through a TypeScript file in Node 22.18+ ([#18665](https://github.com/tailwindlabs/tailwindcss/pull/18665)) -- Support `import.meta.resolve(…)` in configs for new enough Node.js versions ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) -- Allow using newer versions of `postcss-load-config` for better ESM and TypeScript PostCSS config support with the CLI ([#18938](https://github.com/tailwindlabs/tailwindcss/pull/18938)) -- Remove irrelevant utility rules when matching important classes ([#19030](https://github.com/tailwindlabs/tailwindcss/pull/19030)) - -## [4.1.13] - 2025-09-03 - -### Changed - -- Drop warning from browser build ([#18731](https://github.com/tailwindlabs/tailwindcss/issues/18731)) -- Drop exact duplicate declarations when emitting CSS ([#18809](https://github.com/tailwindlabs/tailwindcss/issues/18809)) - -### Fixed - -- Don't transition `visibility` when using `transition` ([#18795](https://github.com/tailwindlabs/tailwindcss/pull/18795)) -- Discard matched variants with unknown named values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) -- Discard matched variants with non-string values ([#18799](https://github.com/tailwindlabs/tailwindcss/pull/18799)) -- Show suggestions for known `matchVariant` values ([#18798](https://github.com/tailwindlabs/tailwindcss/pull/18798)) -- Replace deprecated `clip` with `clip-path` in `sr-only` ([#18769](https://github.com/tailwindlabs/tailwindcss/pull/18769)) -- Hide internal fields from completions in `matchUtilities` ([#18820](https://github.com/tailwindlabs/tailwindcss/pull/18820)) -- Ignore `.vercel` folders by default (can be overridden by `@source …` rules) ([#18855](https://github.com/tailwindlabs/tailwindcss/pull/18855)) -- Consider variants starting with `@-` to be invalid (e.g. `@-2xl:flex`) ([#18869](https://github.com/tailwindlabs/tailwindcss/pull/18869)) -- Do not allow custom variants to start or end with a `-` or `_` ([#18867](https://github.com/tailwindlabs/tailwindcss/pull/18867), [#18872](https://github.com/tailwindlabs/tailwindcss/pull/18872)) -- Upgrade: Migrate `aria` theme keys to `@custom-variant` ([#18815](https://github.com/tailwindlabs/tailwindcss/pull/18815)) -- Upgrade: Migrate `data` theme keys to `@custom-variant` ([#18816](https://github.com/tailwindlabs/tailwindcss/pull/18816)) -- Upgrade: Migrate `supports` theme keys to `@custom-variant` ([#18817](https://github.com/tailwindlabs/tailwindcss/pull/18817)) - -## [4.1.12] - 2025-08-13 - -### Fixed - -- Don't consider the global important state in `@apply` ([#18404](https://github.com/tailwindlabs/tailwindcss/pull/18404)) -- Add missing suggestions for `flex-` utilities ([#18642](https://github.com/tailwindlabs/tailwindcss/pull/18642)) -- Fix trailing `)` from interfering with extraction in Clojure keywords ([#18345](https://github.com/tailwindlabs/tailwindcss/pull/18345)) -- Detect classes inside Elixir charlist, word list, and string sigils ([#18432](https://github.com/tailwindlabs/tailwindcss/pull/18432)) -- Track source locations through `@plugin` and `@config` ([#18345](https://github.com/tailwindlabs/tailwindcss/pull/18345)) -- Allow boolean values of `process.env.DEBUG` in `@tailwindcss/node` ([#18485](https://github.com/tailwindlabs/tailwindcss/pull/18485)) -- Ignore consecutive semicolons in the CSS parser ([#18532](https://github.com/tailwindlabs/tailwindcss/pull/18532)) -- Center the dropdown icon added to an input with a paired datalist by default ([#18511](https://github.com/tailwindlabs/tailwindcss/pull/18511)) -- Extract candidates in Slang templates ([#18565](https://github.com/tailwindlabs/tailwindcss/pull/18565)) -- Improve error messages when encountering invalid functional utility names ([#18568](https://github.com/tailwindlabs/tailwindcss/pull/18568)) -- Discard CSS AST objects with `false` or `undefined` properties ([#18571](https://github.com/tailwindlabs/tailwindcss/pull/18571)) -- Allow users to disable URL rebasing in `@tailwindcss/postcss` via `transformAssetUrls: false` ([#18321](https://github.com/tailwindlabs/tailwindcss/pull/18321)) -- Fix false-positive migrations in `addEventListener` and JavaScript variable names ([#18718](https://github.com/tailwindlabs/tailwindcss/pull/18718)) -- Fix Standalone CLI showing default Bun help when run via symlink on Windows ([#18723](https://github.com/tailwindlabs/tailwindcss/pull/18723)) -- Read from `--border-color-*` theme keys in `divide-*` utilities for backwards compatibility ([#18704](https://github.com/tailwindlabs/tailwindcss/pull/18704/)) -- Don't scan `.hdr` and `.exr` files for classes by default ([#18734](https://github.com/tailwindlabs/tailwindcss/pull/18734)) - -## [4.1.11] - 2025-06-26 - -### Fixed - -- Add heuristic to skip candidate migrations inside `emit(…)` ([#18330](https://github.com/tailwindlabs/tailwindcss/pull/18330)) -- Extract candidates with variants in Clojure/ClojureScript keywords ([#18338](https://github.com/tailwindlabs/tailwindcss/pull/18338)) -- Document `--watch=always` in the CLI's usage ([#18337](https://github.com/tailwindlabs/tailwindcss/pull/18337)) -- Add support for Vite 7 to `@tailwindcss/vite` ([#18384](https://github.com/tailwindlabs/tailwindcss/pull/18384)) - -## [4.1.10] - 2025-06-11 - -### Fixed - -- Fix incorrectly generated CSS when using percentages in arbitrary values with calc (e.g. `w-[calc(100%-var(--offset))]`) ([#18289](https://github.com/tailwindlabs/tailwindcss/pull/18289)) - -## [4.1.9] - 2025-06-11 - -### Fixed - -- Correctly parse custom properties with strings containing semicolons ([#18251](https://github.com/tailwindlabs/tailwindcss/pull/18251)) -- Upgrade: Migrate arbitrary modifiers without percentage signs to bare values (e.g. `/[0.16]` → `/16`) ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184)) -- Upgrade: Migrate CSS variable shorthands where fallback value contains function call ([#18184](https://github.com/tailwindlabs/tailwindcss/pull/18184)) -- Upgrade: Migrate negative arbitrary values to negative bare values (e.g. `mb-[-32rem]` → `-mb-128`) ([#18212](https://github.com/tailwindlabs/tailwindcss/pull/18212)) -- Upgrade: Do not migrate `blur` in `wire:model.blur` ([#18216](https://github.com/tailwindlabs/tailwindcss/pull/18216)) -- Don't add spaces around CSS dashed idents when formatting math expressions ([#18220](https://github.com/tailwindlabs/tailwindcss/pull/18220)) - -## [4.1.8] - 2025-05-27 - -### Added - -- Improve error messages when `@apply` fails ([#18059](https://github.com/tailwindlabs/tailwindcss/pull/18059)) - -### Fixed - -- Upgrade: Do not migrate declarations that look like candidates in ` - `, - 'src/input.css': css` - @import 'tailwindcss'; - - .foo { - flex-shrink: 1; - } - - .bar { - @apply !underline; - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('./src/**/*.{css,vue}')).toMatchInlineSnapshot(` - " - --- ./src/index.vue --- - - - - - --- ./src/input.css --- - @import 'tailwindcss'; - - .foo { - flex-shrink: 1; - } - - .bar { - @apply underline!; - } - " - `) - }, -) - function withBOM(text: string): string { return '\uFEFF' + text } diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index cab0d3d62..d2240fbf0 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -1,6 +1,6 @@ import path from 'node:path' import { describe } from 'vitest' -import { css, html, json, test, ts, txt } from '../utils' +import { css, html, json, test, ts } from '../utils' test( `upgrade JS config files with flat theme values, darkMode, and content fields`, @@ -30,16 +30,9 @@ test( 400: '#f87171', 500: 'red', }, - superRed: '#ff0000', steel: 'rgb(70 130 180 / )', smoke: 'rgba(245, 245, 245, var(--smoke-alpha, ))', }, - ringColor: { - DEFAULT: '#c0ffee', - }, - opacity: { - superOpaque: '0.95', - }, fontSize: { xs: ['0.75rem', { lineHeight: '1rem' }], sm: ['0.875rem', { lineHeight: '1.5rem' }], @@ -151,10 +144,9 @@ test( } `, 'src/index.html': html` -
-
`, 'node_modules/my-external-lib/src/template.html': html`
@@ -164,15 +156,14 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,js,html}')).toMatchInlineSnapshot(` " --- src/index.html ---
-
+ class="[letter-spacing:var(--tracking-super-wide)] [line-height:var(--leading-super-loose)]" + >
--- src/input.css --- @import 'tailwindcss'; @@ -190,15 +181,9 @@ test( --color-red-500: #ef4444; --color-red-600: #dc2626; - --color-superRed: #ff0000; --color-steel: rgb(70 130 180); --color-smoke: rgba(245, 245, 245, var(--smoke-alpha, 1)); - --default-ring-color: #c0ffee; - - --opacity-*: initial; - --opacity-superOpaque: 95%; - --text-*: initial; --text-xs: 0.75rem; --text-xs--line-height: 1rem; @@ -270,9 +255,9 @@ test( --animate-spin-clockwise: spin-clockwise 1s linear infinite; --animate-spin-counterclockwise: spin-counterclockwise 1s linear infinite; - --tracking-superWide: 0.25em; + --tracking-super-wide: 0.25em; - --leading-superLoose: 3; + --leading-super-loose: 3; @keyframes spin-clockwise { 0% { @@ -322,105 +307,6 @@ test( }, ) -test( - 'skips gitignored template files even when they are explicitly referenced in `content`', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - '.gitignore': txt` - node_modules - src/ignored - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - - module.exports = { - content: [ - './src/migrate-me.html', - './src/ignored/do-not-migrate-me.html', - './node_modules/my-external-lib/template.html', - ], - theme: {}, - plugins: [], - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - 'src/migrate-me.html': html` -
- `, - 'src/ignored/do-not-migrate-me.html': html` -
- `, - 'node_modules/my-external-lib/template.html': html` -
- `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect( - await fs.dumpFiles( - '{src/**/*.{css,html},node_modules/my-external-lib/template.html,.gitignore}', - ), - ).toMatchInlineSnapshot(` - " - --- .gitignore --- - node_modules - src/ignored - - --- src/input.css --- - @import 'tailwindcss'; - - @source './ignored/do-not-migrate-me.html'; - @source '../node_modules/my-external-lib/template.html'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - - --- src/migrate-me.html --- -
- - --- node_modules/my-external-lib/template.html --- -
- - --- src/ignored/do-not-migrate-me.html --- -
- " - `) - }, -) - test( 'upgrades JS config files with plugins', { @@ -484,7 +370,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -586,7 +472,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,ts}')).toMatchInlineSnapshot(` " @@ -594,17 +480,17 @@ test( @import 'tailwindcss'; @theme { - --color-gray-50: oklch(98.5% 0 none); - --color-gray-100: oklch(97% 0 none); - --color-gray-200: oklch(92.2% 0 none); - --color-gray-300: oklch(87% 0 none); - --color-gray-400: oklch(70.8% 0 none); - --color-gray-500: oklch(55.6% 0 none); - --color-gray-600: oklch(43.9% 0 none); - --color-gray-700: oklch(37.1% 0 none); - --color-gray-800: oklch(26.9% 0 none); - --color-gray-900: oklch(20.5% 0 none); - --color-gray-950: oklch(14.5% 0 none); + --color-gray-50: oklch(98.5% 0 0); + --color-gray-100: oklch(97% 0 0); + --color-gray-200: oklch(92.2% 0 0); + --color-gray-300: oklch(87% 0 0); + --color-gray-400: oklch(70.8% 0 0); + --color-gray-500: oklch(55.6% 0 0); + --color-gray-600: oklch(43.9% 0 0); + --color-gray-700: oklch(37.1% 0 0); + --color-gray-800: oklch(26.9% 0 0); + --color-gray-900: oklch(20.5% 0 0); + --color-gray-950: oklch(14.5% 0 0); } /* @@ -672,7 +558,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -750,7 +636,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -824,7 +710,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -934,7 +820,7 @@ test( }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('project-{a,b}/**/*.{css,ts}')).toMatchInlineSnapshot(` " @@ -1032,7 +918,7 @@ test( }, }, async ({ root, exec, fs, expect }) => { - await exec('pnpm exec upgrade', { + await exec('npx @tailwindcss/upgrade', { cwd: path.join(root, 'frontend'), }) @@ -1069,252 +955,6 @@ test( }, ) -test( - 'migrate aria theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - aria: { - // Built-in (not really, but visible because of intellisense) - busy: 'busy="true"', - - // Automatically handled by bare values - foo: 'foo="true"', - - // Quotes are optional in CSS for these kinds of attribute - // selectors - bar: 'bar=true', - - // Not automatically handled by bare values because names differ - baz: 'qux="true"', - - // Completely custom - asc: 'sort="ascending"', - desc: 'sort="descending"', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant aria-baz (&[aria-qux="true"]); - @custom-variant aria-asc (&[aria-sort="ascending"]); - @custom-variant aria-desc (&[aria-sort="descending"]); - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - -test( - 'migrate data theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - data: { - // Automatically handled by bare values - foo: 'foo', - - // Not automatically handled by bare values because names differ - bar: 'baz', - - // Custom - checked: 'ui~="checked"', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant data-bar (&[data-baz]); - @custom-variant data-checked (&[data-ui~="checked"]); - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - -test( - 'migrate supports theme keys to custom variants', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - export default { - content: { - relative: true, - files: ['./src/**/*.html'], - }, - theme: { - extend: { - supports: { - // Automatically handled by bare values (using CSS variable as the value) - foo: 'foo: var(--foo)', // parentheses are optional - bar: '(bar: var(--bar))', - - // Not automatically handled by bare values because names differ - foo: 'bar: var(--foo)', // parentheses are optional - bar: '(qux: var(--bar))', - - // Custom - grid: 'display: grid', - }, - }, - }, - } - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant supports-foo { - @supports (bar: var(--foo)) { - @slot; - } - } - @custom-variant supports-bar { - @supports ((qux: var(--bar))) { - @slot; - } - } - @custom-variant supports-grid { - @supports (display: grid) { - @slot; - } - } - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - }, -) - describe('border compatibility', () => { test( 'migrate border compatibility', @@ -1346,7 +986,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1408,7 +1048,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1470,7 +1110,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1509,7 +1149,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1580,7 +1220,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` " @@ -1683,7 +1323,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " @@ -1793,7 +1433,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " @@ -1895,7 +1535,7 @@ describe('border compatibility', () => { }, }, async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') + await exec('npx @tailwindcss/upgrade') expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(` " @@ -1945,243 +1585,3 @@ describe('border compatibility', () => { }, ) }) - -test( - `future and experimental keys are supported`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - hoverOnlyWhenSupported: true, - respectDefaultRingColorOpacity: true, - disableColorOpacityUtilitiesByDefault: true, - relativeContentPathsByDefault: true, - }, - experimental: { - generalizedModifiers: true, - }, - theme: { - colors: { - red: { - 400: '#f87171', - 500: 'red', - }, - }, - }, - plugins: [], - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @custom-variant dark (&:where(.dark, .dark *)); - - @theme { - --color-*: initial; - --color-red-400: #f87171; - --color-red-500: red; - } - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toBe('') - }, -) - -test( - `unknown future keys dont migrate the config`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - something: true, - }, - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @config '../tailwind.config.ts'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toMatchInlineSnapshot(` - "--- tailwind.config.ts --- - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - future: { - something: true, - }, - } satisfies Config" - `) - }, -) - -test( - `unknown experimental keys dont migrate the config`, - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - } - } - `, - 'tailwind.config.ts': ts` - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - experimental: { - something: true, - }, - } satisfies Config - `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm exec upgrade') - - expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(` - " - --- src/input.css --- - @import 'tailwindcss'; - - @config '../tailwind.config.ts'; - - /* - The default border color has changed to \`currentcolor\` in Tailwind CSS v4, - so we've added these compatibility styles to make sure everything still - looks the same as it did with Tailwind CSS v3. - - If we ever want to remove these styles, we need to add an explicit border - color utility to any element that depends on these defaults. - */ - @layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: var(--color-gray-200, currentcolor); - } - } - " - `) - - expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toMatchInlineSnapshot(` - "--- tailwind.config.ts --- - import { type Config } from 'tailwindcss' - import defaultTheme from 'tailwindcss/defaultTheme' - - module.exports = { - darkMode: 'selector', - content: ['./src/**/*.{html,js}'], - experimental: { - something: true, - }, - } satisfies Config" - `) - }, -) diff --git a/integrations/upgrade/upgrade-errors.test.ts b/integrations/upgrade/upgrade-errors.test.ts deleted file mode 100644 index fdc60828e..000000000 --- a/integrations/upgrade/upgrade-errors.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { stripVTControlCharacters } from 'node:util' -import { css, html, js, json, test } from '../utils' - -test( - 'upgrades half-upgraded v3 project to v4 (pnpm)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('pnpm exec upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('pnpm exec upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: pnpm exec upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`pnpm install\` and try again. - - " - `) - }, -) - -test( - 'upgrades half-upgraded v3 project to v4 (bun)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^", - "bun": "^1.0.0" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Use `bun` to install dependencies - await exec('rm ./pnpm-lock.yaml') - try { - await exec('npx bun install', {}, { ignoreStdErr: true }) - } catch (e) { - // When preparing for a release, the version in `package.json` will point - // to a non-existent version because it's not published yet. - // TODO: Find a better approach to handle this and actually test it even - // on release branches. Note: the pnpm version _does_ work because of - // overrides in the package.json file. - if (`${e}`.includes('No version matching')) return - throw e - } - - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('npx @tailwindcss/upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('npx @tailwindcss/upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: npx @tailwindcss/upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`bun install\` and try again. - - " - `) - }, -) - -test( - 'upgrades half-upgraded v3 project to v4 (npm)', - { - fs: { - 'package.json': json` - { - "dependencies": { - "tailwindcss": "^3", - "@tailwindcss/upgrade": "workspace:^" - }, - "devDependencies": { - "@tailwindcss/cli": "workspace:^" - } - } - `, - 'tailwind.config.js': js` - /** @type {import('tailwindcss').Config} */ - module.exports = { - content: ['./src/**/*.{html,js}'], - } - `, - 'src/index.html': html` -
Hello World
- `, - 'src/input.css': css` - @tailwind base; - @tailwind components; - @tailwind utilities; - `, - }, - retry: 0, - }, - async ({ exec, expect }) => { - // Use `npm` to install dependencies - await exec('rm ./pnpm-lock.yaml') - await exec('rm -rf ./node_modules') - try { - await exec('npm install', {}, { ignoreStdErr: true }) - } catch (e) { - // When preparing for a release, the version in `package.json` will point - // to a non-existent version because it's not published yet. - // TODO: Find a better approach to handle this and actually test it even - // on release branches. Note: the pnpm version _does_ work because of - // overrides in the package.json file. - if (`${e}`.includes('npm error code ETARGET')) return - throw e - } - - // Ensure we are in a git repo - await exec('git init') - await exec('git add --all') - await exec('git commit -m "before migration"') - - // Fully upgrade to v4 - await exec('npx @tailwindcss/upgrade') - - // Undo all changes to the current repo. This will bring the repo back to a - // v3 state, but the `node_modules` will now have v4 installed. - await exec('git reset --hard HEAD') - - // Re-running the upgrade should result in an error - return expect(() => { - return exec('npx @tailwindcss/upgrade', {}, { ignoreStdErr: true }).catch((e) => { - // Replacing the current version with a hardcoded `v4` to make it stable - // when we release new minor/patch versions. - return Promise.reject( - stripVTControlCharacters(e.message.replace(/\d+\.\d+\.\d+/g, '4.0.0')), - ) - }) - }).rejects.toThrowErrorMatchingInlineSnapshot(` - "Command failed: npx @tailwindcss/upgrade - ≈ tailwindcss v4.0.0 - - │ ↳ Upgrading from Tailwind CSS \`v4.0.0\` - - │ ↳ Version mismatch - │ - │ \`\`\`diff - │ - "tailwindcss": "^3" (expected version in \`package.json\`) - │ + "tailwindcss": "4.0.0" (installed version in \`node_modules\`) - │ \`\`\` - │ - │ Make sure to run \`npm install\` and try again. - - " - `) - }, -) diff --git a/integrations/utils.ts b/integrations/utils.ts index 9863918d1..03c4c4e71 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -1,27 +1,22 @@ import dedent from 'dedent' import fastGlob from 'fast-glob' -import { exec, execFile, spawn, type ChildProcess } from 'node:child_process' +import { exec, spawn } from 'node:child_process' import fs from 'node:fs/promises' -import { createServer } from 'node:net' import { platform, tmpdir } from 'node:os' import path from 'node:path' -import { promisify, stripVTControlCharacters } from 'node:util' +import { stripVTControlCharacters } from 'node:util' import { RawSourceMap, SourceMapConsumer } from 'source-map-js' import { test as defaultTest, type ExpectStatic } from 'vitest' -import * as Yaml from 'yaml' import { createLineTable } from '../packages/tailwindcss/src/source-maps/line-table' import { escape } from '../packages/tailwindcss/src/utils/escape' const REPO_ROOT = path.join(__dirname, '..') -const ROOT_PNPM_WORKSPACE = Yaml.parse( - await fs.readFile(path.join(REPO_ROOT, 'pnpm-workspace.yaml'), 'utf8'), -) const PUBLIC_PACKAGES = (await fs.readdir(path.join(REPO_ROOT, 'dist'))).map((name) => name.replace('tailwindcss-', '@tailwindcss/').replace('.tgz', ''), ) interface SpawnedProcess { - dispose: () => Promise + dispose: () => void flush: () => void onStdout: (predicate: (message: string) => boolean) => Promise onStderr: (predicate: (message: string) => boolean) => Promise @@ -42,9 +37,7 @@ interface TestConfig { [filePath: string]: string | Uint8Array } - timeout?: number installDependencies?: boolean - retry?: number } interface TestContext { root: string @@ -54,17 +47,15 @@ interface TestContext { parseSourceMap(opts: string | SourceMapOptions): SourceMap fs: { write(filePath: string, content: string, encoding?: BufferEncoding): Promise - symlink(dst: string, src: string): Promise create(filePaths: string[]): Promise read(filePath: string): Promise - delete(filePath: string): Promise glob(pattern: string): Promise<[string, string][]> dumpFiles(pattern: string): Promise expectFileToContain( filePath: string, contents: string | RegExp | (string | RegExp)[], ): Promise - expectFileNotToContain(filePath: string, contents: string[]): Promise + expectFileNotToContain(filePath: string, contents: string | string[]): Promise } } type TestCallback = (context: TestContext) => Promise | void @@ -72,15 +63,12 @@ interface TestFlags { only?: boolean skip?: boolean debug?: boolean - concurrent?: boolean } type SpawnActor = { predicate: (message: string) => boolean; resolve: () => void } export const IS_WINDOWS = platform() === 'win32' -const execFileAsync = promisify(execFile) - const TEST_TIMEOUT = IS_WINDOWS ? 120000 : 60000 const ASSERTION_TIMEOUT = IS_WINDOWS ? 10000 : 5000 @@ -93,16 +81,16 @@ export function test( name: string, config: TestConfig, testCallback: TestCallback, - { only = false, skip = false, debug = false, concurrent = false }: TestFlags = {}, + { only = false, skip = false, debug = false }: TestFlags = {}, ) { return defaultTest( name, { - timeout: config.timeout ?? TEST_TIMEOUT, - retry: config.retry ?? (process.env.CI ? 2 : 0), + timeout: TEST_TIMEOUT, + retry: process.env.CI ? 2 : 0, only: only || (!process.env.CI && debug), skip, - concurrent, + concurrent: true, }, async (options) => { let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT @@ -126,39 +114,6 @@ export function test( execOptions: ExecOptions = {}, ) { let cwd = childProcessOptions.cwd ?? root - let originalCommand = command - - // Avoid `pnpm exec upgrade` on Windows because the upgrader runs - // `pnpm add`, which rewrites Windows `.cmd` shims while the current - // shim is still executing. - // - // Pretty sure this is a pnpm v11 bug on Windows. - if (IS_WINDOWS && command.includes('pnpm exec upgrade')) { - for (let base = cwd; ; base = path.dirname(base)) { - let upgradeBin = path.join( - base, - 'node_modules', - '@tailwindcss', - 'upgrade', - 'dist', - 'index.mjs', - ) - - try { - await fs.access(upgradeBin) - command = command.replace('pnpm exec upgrade', `node "${upgradeBin}"`) - break - } catch (error: any) { - if (error?.code !== 'ENOENT') throw error - } - - let parent = path.dirname(base) - if (parent === base) { - throw new Error(`Unable to resolve @tailwindcss/upgrade from ${cwd}`) - } - } - } - if (debug && cwd !== root) { let relative = path.relative(root, cwd) if (relative[0] !== '.') relative = `./${relative}` @@ -178,11 +133,6 @@ export function test( }, (error, stdout, stderr) => { if (error) { - if (command !== originalCommand) { - error.message = error.message.replace(command, originalCommand) - let mappedError = error as any - mappedError.cmd = originalCommand - } if (execOptions.ignoreStdErr !== true) console.error(stderr) if (only || debug) { console.error(stdout) @@ -219,7 +169,6 @@ export function test( if (debug) console.log(`>& ${command}`) let child = spawn(command, { cwd, - detached: !IS_WINDOWS, shell: true, ...childProcessOptions, env: { @@ -228,26 +177,22 @@ export function test( }, }) - let disposed = false + function dispose() { + if (!child.kill()) { + child.kill('SIGKILL') + } - async function dispose() { - if (disposed) return disposePromise - disposed = true - - await killProcessTree(child) - - let timer = setTimeout(() => { - forceKillProcessTree(child) - rejectDisposal?.(new Error(`spawned process (${command}) did not exit in time`)) - }, ASSERTION_TIMEOUT) - disposePromise.then( - () => clearTimeout(timer), - () => clearTimeout(timer), + let timer = setTimeout( + () => + rejectDisposal?.(new Error(`spawned process (${command}) did not exit in time`)), + ASSERTION_TIMEOUT, ) + disposePromise.finally(() => { + clearTimeout(timer) + }) return disposePromise } disposables.push(dispose) - function onExit() { resolveDisposal?.() } @@ -338,7 +283,11 @@ export function test( } }, fs: { - async write(filename, content, encoding = 'utf8') { + async write( + filename: string, + content: string | Uint8Array, + encoding: BufferEncoding = 'utf8', + ): Promise { let full = path.join(root, filename) let dir = path.dirname(full) await fs.mkdir(dir, { recursive: true }) @@ -349,8 +298,6 @@ export function test( if (filename.endsWith('package.json')) { content = await overwriteVersionsInPackageJson(content) - } else if (filename.endsWith('pnpm-workspace.yaml')) { - content = overwriteVersionsInPnpmWorkspace(content) } // Ensure that files written on Windows use \r\n line ending @@ -361,19 +308,7 @@ export function test( await fs.writeFile(full, content, encoding) }, - async symlink(target, src) { - let targetAbsolute = path.join(root, target) - let targetParent = path.dirname(targetAbsolute) - await fs.mkdir(targetParent, { recursive: true }) - - let srcAbsolute = path.join(root, src) - let srcParent = path.dirname(srcAbsolute) - await fs.mkdir(srcParent, { recursive: true }) - - await fs.symlink(targetAbsolute, srcAbsolute) - }, - - async create(filenames) { + async create(filenames: string[]): Promise { for (let filename of filenames) { let full = path.join(root, filename) @@ -383,11 +318,7 @@ export function test( } }, - async delete(filename) { - await fs.unlink(path.join(root, filename)) - }, - - async read(filePath) { + async read(filePath: string) { let content = await fs.readFile(path.resolve(root, filePath), 'utf8') // Ensure that files read on Windows have \r\n line endings removed @@ -397,7 +328,7 @@ export function test( return content }, - async glob(pattern) { + async glob(pattern: string) { let files = await fastGlob(pattern, { cwd: root }) return Promise.all( files.map(async (file) => { @@ -410,7 +341,7 @@ export function test( }), ) }, - async dumpFiles(pattern) { + async dumpFiles(pattern: string) { let files = await context.fs.glob(pattern) return `\n${files .slice() @@ -469,44 +400,21 @@ export function test( node_modules/ ` - // Disable Git's automatic line ending conversion in these throwaway test - // repositories. Tools like pnpm write files (e.g. `pnpm-lock.yaml`) with - // LF endings, which on Windows (where `core.autocrlf=true` is common) - // makes Git emit noisy "LF will be replaced by CRLF" warnings when it - // touches those files. - config.fs['.gitattributes'] ??= txt` - * -text - ` - - // Ensure there is always a root `pnpm-workspace.yaml` so that transitive - // dependency overrides (and the build allow list) are injected into it. - // As of pnpm v10, these can no longer live in the `pnpm.overrides` field - // of `package.json`. - config.fs['pnpm-workspace.yaml'] ??= '' - for (let [filename, content] of Object.entries(config.fs)) { - if (content.toString().startsWith('symlink:')) { - // The symlink path is relative to the target destination's path - let target = path.join( - filename, - content.toString().slice('symlink:'.length), // Relative path - ) - - await context.fs.symlink(target, filename) - } else { - await context.fs.write(filename, content) - } + await context.fs.write(filename, content) } let shouldInstallDependencies = config.installDependencies ?? true try { - // Every test project gets its own root `pnpm-workspace.yaml` (see - // above). This makes the test directory its own workspace root, so even - // in debug mode — where the directory lives inside this repository's - // pnpm workspace — `pnpm install` won't attach to the parent workspace. + // In debug mode, the directory is going to be inside the pnpm workspace + // of the tailwindcss package. This means that `pnpm install` will run + // pnpm install on the workspace instead (expect if the root dir defines + // a separate workspace). We work around this by using the + // `--ignore-workspace` flag. if (shouldInstallDependencies) { - await context.exec(`pnpm install`) + let ignoreWorkspace = debug && !config.fs['pnpm-workspace.yaml'] + await context.exec(`pnpm install${ignoreWorkspace ? ' --ignore-workspace' : ''}`) } } catch (error: any) { console.error(error) @@ -518,18 +426,11 @@ export function test( let disposables: (() => Promise)[] = [] async function dispose() { - let results = await Promise.allSettled(disposables.map((dispose) => dispose())) + await Promise.all(disposables.map((dispose) => dispose())) if (!debug) { await gracefullyRemove(root) } - - let errors = results.flatMap((result) => - result.status === 'rejected' ? [result.reason] : [], - ) - if (errors.length > 0) { - throw new AggregateError(errors, 'Failed to clean up spawned processes') - } } options.onTestFinished(dispose) @@ -539,7 +440,7 @@ export function test( try { await context.exec('git init', { cwd: root }) await context.exec('git add --all', { cwd: root }) - await context.exec('git commit -m "before migration" --allow-empty', { cwd: root }) + await context.exec('git commit -m "before migration"', { cwd: root }) } catch (error: any) { console.error(error) console.error(error.stdout?.toString()) @@ -558,9 +459,6 @@ test.only = (name: string, config: TestConfig, testCallback: TestCallback) => { test.skip = (name: string, config: TestConfig, testCallback: TestCallback) => { return test(name, config, testCallback, { skip: true }) } -test.concurrent = (name: string, config: TestConfig, testCallback: TestCallback) => { - return test(name, config, testCallback, { concurrent: true }) -} test.debug = (name: string, config: TestConfig, testCallback: TestCallback) => { return test(name, config, testCallback, { debug: true }) } @@ -584,43 +482,27 @@ async function overwriteVersionsInPackageJson(content: string): Promise } } - return JSON.stringify(json, null, 2) -} - -function overwriteVersionsInPnpmWorkspace(content: string): string { - let workspace = content.trim() === '' ? {} : Yaml.parse(content) - - // Inherit information from the root workspace - workspace.allowBuilds = { - ...ROOT_PNPM_WORKSPACE.allowBuilds, - ...workspace.allowBuilds, - } - // Inject transitive dependency overwrite. This is necessary because // @tailwindcss/vite internally depends on a specific version of // @tailwindcss/oxide and we instead want to resolve it to the locally built // version. - // - // As of pnpm v10, the `pnpm.overrides` field in `package.json` is no longer - // read. Overrides have to be defined in `pnpm-workspace.yaml` instead. - workspace.overrides ||= {} + json.pnpm ||= {} + json.pnpm.overrides ||= {} for (let pkg of PUBLIC_PACKAGES) { if (pkg === 'tailwindcss') { // We want to be explicit about the `tailwindcss` package so our tests can // also import v3 without conflicting v4 tarballs. - workspace.overrides['@tailwindcss/node>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/upgrade>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/cli>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/postcss>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/turbopack>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/vite>tailwindcss'] = resolveVersion(pkg) - workspace.overrides['@tailwindcss/webpack>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/node>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/upgrade>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/cli>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/postcss>tailwindcss'] = resolveVersion(pkg) + json.pnpm.overrides['@tailwindcss/vite>tailwindcss'] = resolveVersion(pkg) } else { - workspace.overrides[pkg] = resolveVersion(pkg) + json.pnpm.overrides[pkg] = resolveVersion(pkg) } } - return Yaml.stringify(workspace) + return JSON.stringify(json, null, 2) } function resolveVersion(dependency: string) { @@ -670,7 +552,6 @@ export async function retryAssertion( try { return await fn() } catch (err) { - Error.captureStackTrace(err, retryAssertion) error = err await new Promise((resolve) => setTimeout(resolve, delay)) } @@ -686,7 +567,7 @@ export async function fetchStyles(base: string, path = '/'): Promise { let index = await fetch(`${base}${path}`) let html = await index.text() - let linkRegex = /]*>([\s\S]*?)<\/style>/gi let stylesheets: string[] = [] @@ -723,65 +604,6 @@ export async function fetchStyles(base: string, path = '/'): Promise { }, '') } -export async function getRandomPort() { - return new Promise((resolve, reject) => { - let server = createServer() - server.unref() - server.on('error', reject) - server.listen(0, '127.0.0.1', () => { - let address = server.address() - server.close(() => { - if (address && typeof address === 'object') { - resolve(address.port) - } else { - reject(new Error('Unable to allocate random port')) - } - }) - }) - }) -} - -async function killProcessTree(child: ChildProcess) { - if (child.exitCode !== null || child.signalCode !== null || child.pid === undefined) { - return - } - - if (IS_WINDOWS) { - await execFileAsync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { - timeout: ASSERTION_TIMEOUT, - windowsHide: true, - }).catch(() => {}) - return - } - - try { - process.kill(-child.pid, 'SIGTERM') - } catch (error: any) { - if (error?.code !== 'ESRCH') { - child.kill() - } - } -} - -function forceKillProcessTree(child: ChildProcess) { - if (child.exitCode !== null || child.signalCode !== null || child.pid === undefined) { - return - } - - if (IS_WINDOWS) { - execFile('taskkill', ['/pid', String(child.pid), '/T', '/F'], { windowsHide: true }, () => {}) - return - } - - try { - process.kill(-child.pid, 'SIGKILL') - } catch (error: any) { - if (error?.code !== 'ESRCH') { - child.kill('SIGKILL') - } - } -} - async function gracefullyRemove(dir: string) { // Skip removing the directory in CI because it can stall on Windows if (!process.env.CI) { diff --git a/integrations/vite/astro.test.ts b/integrations/vite/astro.test.ts index f828b5071..43406624c 100644 --- a/integrations/vite/astro.test.ts +++ b/integrations/vite/astro.test.ts @@ -1,4 +1,4 @@ -import { candidate, css, fetchStyles, html, js, json, retryAssertion, test, ts } from '../utils' +import { candidate, fetchStyles, html, js, json, retryAssertion, test, ts } from '../utils' test( 'dev mode', @@ -129,73 +129,3 @@ test( await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`overline`]) }, ) - -// https://github.com/tailwindlabs/tailwindcss/issues/19677 -test( - 'import aliases should work in - - -

Astro

- - - `, - 'src/styles/global.css': css`@import 'tailwindcss';`, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm astro build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - - await fs.expectFileToContain(files[0][0], [candidate`underline`]) - }, -) diff --git a/integrations/vite/bundled-dev.test.ts b/integrations/vite/bundled-dev.test.ts deleted file mode 100644 index 8ba1b6b73..000000000 --- a/integrations/vite/bundled-dev.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -import path from 'node:path' -import { candidate, css, html, json, retryAssertion, test, ts, txt, yaml } from '../utils' - -// Vite's experimental `bundledDev` mode invokes the `hotUpdate` hook without a -// `server`, which used to crash the plugin on every file edit. -// -// - https://github.com/tailwindlabs/tailwindcss/issues/20378 -// - https://vite.dev/blog/announcing-vite8-1#experimental-bundled-dev-mode -test( - 'dev mode (experimental `bundledDev`)', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8.1" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - experimental: { - bundledDev: true, - }, - plugins: [tailwindcss()], - }) - `, - 'project-a/index.html': html` - - - - -
Hello, world!
- - `, - 'project-a/src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @source '../../project-b/src/**/*.html'; - `, - 'project-b/src/index.html': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - env: { - // Some CI machines have an upstream rolldown bug where its file watcher - // never delivers a single event. The debug output is the only way to - // tell that apart from a real regression (see below). - DEBUG: 'vite:full-bundle-mode', - }, - }) - - // Any debug output beyond the "INITIAL:" startup lines proves rolldown's - // watcher delivered at least one file event. - let watcherAlive = false - - // `hotUpdate` errors don't kill the dev server, they are only printed to - // stderr. Track them explicitly so a crash fails the test even if the - // rebuild happens to succeed anyway. - let pluginErrors: string[] = [] - process.onStderr((message) => { - if (message.includes('vite:full-bundle-mode')) { - if (!message.includes('INITIAL:')) watcherAlive = true - } else if (message.includes('@tailwindcss/vite')) { - pluginErrors.push(message) - } - return false - }) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - // In `bundledDev` mode the stylesheet is not served separately. Instead the - // generated CSS is embedded in the bundled JS and injected at runtime, so - // extract the bundle from the served HTML. While the bundle is being built, - // Vite serves a temporary fallback page instead. - // - // The bundle can be split into multiple chunks (e.g. the HMR client runtime - // and the app itself), and the chunk containing the CSS is not always the - // first one, so fetch every referenced script and stylesheet. - async function fetchBundledStyles(): Promise { - let index = await fetch(`${url}/`) - let html = await index.text() - if (html.includes('__vite_is_fallback_page__')) { - throw new Error('Bundling still in progress') - } - - let sources = [ - ...html.matchAll(/]*\ssrc="([^"]+)"/g), - ...html.matchAll(/]*\srel="stylesheet"[^>]*\shref="([^"]+)"/g), - ].map((match) => match[1]) - if (sources.length === 0) throw new Error(`No scripts or stylesheets found in:\n\n${html}`) - - let contents = await Promise.all( - sources.map(async (src) => { - let response = await fetch(new URL(src, `${url}/`)) - return await response.text() - }), - ) - return contents.join('\n') - } - - await retryAssertion( - async () => { - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - }, - { timeout: 10_000, delay: 100 }, - ) - - // Edit a module file and a manually `@source`d file. Without a connected - // HMR websocket client (this test only uses HTTP fetches), a file change - // does not trigger an eager rebuild — it only marks the bundle as stale, - // and the next fetch kicks off the rebuild. So poll with fetches without - // touching the files again: rewriting would re-mark the bundle stale right - // before each fetch and the rebuild could never be observed. - await fs.write( - 'project-a/index.html', - html` - - - - -
Hello, world!
- - `, - ) - - await fs.write('project-b/src/index.html', html` -
- `) - - try { - await retryAssertion( - async () => { - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - expect(styles).toContain(candidate`m-2`) - expect(styles).toContain(candidate`font-bold`) - }, - { timeout: 15_000, delay: 100 }, - ) - } catch (error) { - // If rolldown's watcher delivered any file event, the updates were - // genuinely dropped somewhere along the way — a real failure. - if (watcherAlive) throw error - - // Otherwise the watcher never delivered a single event, so the updates - // can never be observed no matter how long we wait — an upstream - // rolldown bug on some CI machines, not a plugin regression. The crash - // regression this test guards is still covered: `hotUpdate` is driven - // by Vite's own (working) chokidar watcher and asserted via - // `pluginErrors` below. - // - // Skipping is only sound while the server still healthily serves the - // original bundle. If it stopped serving (crashed, or wedged on the - // fallback page), that's real breakage a silent watcher must not mask. - console.log(error) - let styles = await fetchBundledStyles() - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - console.warn( - 'Skipping update assertions: rolldown’s file watcher delivered no events on this machine.', - ) - } - - expect(pluginErrors).toEqual([]) - }, -) diff --git a/integrations/vite/config.test.ts b/integrations/vite/config.test.ts index 0ac68f5e9..3bf560e07 100644 --- a/integrations/vite/config.test.ts +++ b/integrations/vite/config.test.ts @@ -12,7 +12,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -76,7 +76,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -140,7 +140,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -219,7 +219,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/css-modules.test.ts b/integrations/vite/css-modules.test.ts index a680ab9b7..668281cb4 100644 --- a/integrations/vite/css-modules.test.ts +++ b/integrations/vite/css-modules.test.ts @@ -14,8 +14,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, diff --git a/integrations/vite/html-style-blocks.test.ts b/integrations/vite/html-style-blocks.test.ts index 22bb91d78..4a86f96dc 100644 --- a/integrations/vite/html-style-blocks.test.ts +++ b/integrations/vite/html-style-blocks.test.ts @@ -12,7 +12,7 @@ test( }, "devDependencies": { "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/ignored-packages.test.ts b/integrations/vite/ignored-packages.test.ts index 1efa9dc61..f7f85ff14 100644 --- a/integrations/vite/ignored-packages.test.ts +++ b/integrations/vite/ignored-packages.test.ts @@ -11,7 +11,7 @@ const WORKSPACE = { "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/index.test.ts b/integrations/vite/index.test.ts index c346f8e37..4c64f220e 100644 --- a/integrations/vite/index.test.ts +++ b/integrations/vite/index.test.ts @@ -7,7 +7,6 @@ import { html, js, json, - jsx, retryAssertion, test, ts, @@ -34,8 +33,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -111,8 +110,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -294,113 +293,6 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { }, ) - // https://github.com/tailwindlabs/tailwindcss/issues/20346 - test( - 'dev mode + symlinked `@source` files pick up changes to the real file', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - - packages/* - `, - 'project-a/package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "repro-package": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" - } - } - `, - 'project-a/vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'project-a/index.html': html` - - - - -
Hello, world!
- - `, - - // The `@source` points through the `node_modules` symlink that pnpm - // creates for the workspace package. Vite's file watcher ignores - // `node_modules` by default, so the symlinked path can not be watched. - // The real file (in `packages/repro-package`) has to be watched - // instead. - 'project-a/src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @source '../node_modules/repro-package/source.html'; - `, - 'packages/repro-package/package.json': json` - { - "name": "repro-package", - "private": true, - "version": "1.0.0" - } - `, - 'packages/repro-package/source.html': html` -
- Hello, world! -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`content-['v1']`) - }) - - await retryAssertion(async () => { - // Changes to the real file (behind the symlink) should be picked up - await fs.write( - 'packages/repro-package/source.html', - html` -
- Hello, world! -
- `, - ) - - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`content-['v1']`) - expect(styles).toContain(candidate`content-['v2']`) - }) - }, - ) - test( 'watch mode', { @@ -419,8 +311,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -580,352 +472,6 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { }, ) - describe.each([['^6'], ['7.0.8'], ['7.1.12'], ['7.3.1'], ['8.0.0']])( - 'Using Vite %s', - (version) => { - test( - 'external source file changes update the CSS', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "${version}" - } - } - `, - 'project-a/vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - logLevel: 'info', - }) - `, - 'project-a/index.html': html` - - - - - -
- - - - `, - 'project-a/src/main.ts': jsx`import { classes } from './app'`, - 'project-a/src/app.ts': jsx`export let classes = "content-['project-a/src/app.ts']"`, - 'project-a/src/index.css': css` - @import 'tailwindcss'; - @source '../../project-b/**/*.php'; - `, - 'project-b/src/index.php': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev --debug hmr', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['project-b/src/index.php']`) - }) - - // Flush all messages so that we can be sure the next messages are - // from the file changes we're about to make - process.flush() - - // Changing an external .php file hot-updates the generated CSS - { - await fs.write( - 'project-b/src/index.php', - txt`
`, - ) - - // On Vite < 7.1, Vite itself hard-invalidates watched files that - // aren't part of the module graph and reloads the page. - // - // On newer versions nothing reloads the page: the CSS hot-updates - // through the regular pipeline because the changed file is a - // watch dependency of the CSS root. - // - // Reloading the page for external template changes is the - // responsibility of the backend integration (e.g. `laravel-vite-plugin`'s `refresh` option, or `vite-plugin-full-reload`). - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - if (version === '^6' || version === '7.0.8') { - await process.onStdout((m) => m.includes('page reload') && m.includes('index.php')) - } else { - await process.onStdout((m) => m.includes('hmr update') && m.includes('index.css')) - } - - // Ensure the styles were regenerated with the new content - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:project-b/src/index.php']`) - } - }, - ) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/20320 - // https://github.com/tailwindlabs/tailwindcss/issues/19903 - test( - 'editing scanned files that Vite can process as modules does not trigger a full reload', - { - fs: { - 'package.json': json`{}`, - 'pnpm-workspace.yaml': yaml` - # - packages: - - project-a - `, - 'project-a/package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^8" - } - } - `, - 'project-a/vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [ - tailwindcss(), - { - // A plugin that processes a custom file type into a JS - // module, similar to e.g. \`.vue\` or \`.svelte\` files - name: 'custom-file-type', - transform(code, id) { - if (id.endsWith('.custom')) { - return { code: 'export default ' + JSON.stringify(code), map: null } - } - }, - }, - { - // Log all HMR payloads to a file so the test can assert on - // them - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - return send(payload) - } - } - }, - }, - ], - }) - `, - 'project-a/index.html': html` - - - - - -
- - - - `, - 'project-a/src/main.ts': ts` - import compA from './comp-a.custom' - import snippet from './snippet.php?raw' - console.log(compA, snippet) - `, - 'project-a/src/snippet.php': html` -
- `, - 'project-a/src/comp-a.custom': html` -
- `, - 'project-a/src/comp-b.custom': html` -
- `, - 'project-a/src/lazy.tsx': jsx` - export default function Lazy() { - return
- } - `, - 'project-a/src/unimported.css': css` - .unimported { - color: red; - } - `, - 'project-a/src/index.css': css` - @import 'tailwindcss'; - @source '../../project-b/**/*.php'; - `, - 'project-b/src/index.php': html` -
- `, - }, - }, - async ({ root, spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev', { - cwd: path.join(root, 'project-a'), - }) - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['src/lazy.tsx']`) - expect(styles).toContain(candidate`content-['src/comp-b.custom']`) - expect(styles).toContain(candidate`content-['project-b/src/index.php']`) - }) - - // Load `main.ts`, `comp-a.custom`, and `snippet.php?raw` as real - // modules, like a browser visiting the page would - await fetch(`${url}/src/main.ts`) - await fetch(`${url}/src/comp-a.custom?import`) - await fetch(`${url}/src/snippet.php?raw`) - - // Changing a scanned `.tsx` file that is not part of the module graph - // (e.g. a lazily-loaded route that hasn't been visited yet) should not - // trigger a full reload, but new classes should still apply - // - // https://github.com/tailwindlabs/tailwindcss/issues/20320 - { - await fs.write( - 'project-a/src/lazy.tsx', - jsx` - export default function Lazy() { - return
- } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/lazy.tsx']`) - }) - } - - // The same holds for a custom file type as long as some file of the - // same type was processed as a module before - { - await fs.write( - 'project-a/src/comp-b.custom', - html`
`, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/comp-b.custom']`) - }) - } - - // Changing a scanned stylesheet that is not part of the module graph - // (e.g. a component stylesheet that a framework plugin compiles into - // the component) should not trigger a full reload either - // - // https://github.com/tailwindlabs/tailwindcss/issues/19903 - { - let updates = (await fs.read('project-a/hmr.log')).split('"type":"update"').length - - await fs.write( - 'project-a/src/unimported.css', - css` - .unimported { - color: blue; - } - `, - ) - - // Wait until the change was handled and an update was pushed - await retryAssertion(async () => { - let log = await fs.read('project-a/hmr.log') - expect(log.split('"type":"update"').length).toBeGreaterThan(updates) - }) - } - - // Changing an external file (e.g. a PHP template) hot-updates the - // generated CSS but does not trigger a full reload either. Reloading the - // page for external template changes is the responsibility of the backend - // integration (e.g. `laravel-vite-plugin`'s `refresh` option, or - // `vite-plugin-full-reload`). - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - { - let updates = (await fs.read('project-a/hmr.log')).split('"type":"update"').length - - await fs.write( - 'project-b/src/index.php', - html`
`, - ) - - await retryAssertion(async () => { - let log = await fs.read('project-a/hmr.log') - expect(log.split('"type":"update"').length).toBeGreaterThan(updates) - }) - - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:project-b/src/index.php']`) - } - }, - ) - test( `source(none) disables looking at the module graph`, { @@ -944,8 +490,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1030,8 +576,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1132,8 +678,8 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, @@ -1178,92 +724,12 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { async ({ root, fs, exec, expect }) => { await expect(() => exec('pnpm vite build', { cwd: path.join(root, 'project-a') }, { ignoreStdErr: true }), - ).rejects.toThrow('The `source(../i-do-not-exist)` does not exist') + ).rejects.toThrowError('The `source(../i-do-not-exist)` does not exist') let files = await fs.glob('project-a/dist/**/*.css') expect(files).toHaveLength(0) }, ) - - test( - 'source(…) and `@source` are relative to the file they are in', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"}, - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - `, - 'index.css': css` @import './project-a/src/index.css'; `, - - 'project-a/src/index.css': css` - /* Run auto-content detection in ../../project-b */ - @import 'tailwindcss/utilities' source('../../project-b'); - - /* Explicitly using node_modules in the @source allows git ignored folders */ - @source '../../project-c'; - `, - - // Project A is the current folder, but we explicitly configured - // `source(project-b)`, therefore project-a should not be included in - // the output. - 'project-a/src/index.html': html` -
- `, - - // Project B is the configured `source(…)`, therefore auto source - // detection should include known extensions and folders in the output. - 'project-b/src/index.html': html` -
- `, - - // Project C should apply auto source detection, therefore known - // extensions and folders should be included in the output. - 'project-c/src/index.html': html` -
- `, - }, - }, - async ({ fs, exec, spawn, root, expect }) => { - await exec('pnpm vite build', { cwd: root }) - - let content = await fs.dumpFiles('./dist/assets/*.css') - - expect(content).not.toContain(candidate`content-['project-a/src/index.html']`) - expect(content).toContain(candidate`content-['project-b/src/index.html']`) - expect(content).toContain(candidate`content-['project-c/src/index.html']`) - }, - ) }) test( @@ -1278,7 +744,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -1350,7 +816,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -1413,7 +879,7 @@ test( "@tailwindcss/vite": "workspace:^", "tailwindcss": "workspace:^", "plotly.js": "^3", - "vite": "^7" + "vite": "^6" } } `, @@ -1432,14 +898,6 @@ test( `, 'src/index.js': js`import Plotly from 'plotly.js/lib/core'`, - - // `plotly.js` pulls in `es5-ext`, which has a build script. As of pnpm - // v10, build scripts only run when explicitly allowed. - 'pnpm-workspace.yaml': yaml` - # - allowBuilds: - es5-ext: true - `, }, }, async ({ exec, expect, fs }) => { @@ -1456,271 +914,3 @@ test( function firstLine(str: string) { return str.split('\n')[0] } - -test( - 'optimize option: disabled', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss({ optimize: false })], - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ exec, expect, fs }) => { - await exec('pnpm vite build') - - // Should not be minified when optimize is disabled - expect((await fs.dumpFiles('dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css')) - .toMatchInlineSnapshot(` - " - --- dist/assets/index-.css --- - .focus\\:text-\\[black\\]:focus { - color: black; - } - " - `) - }, -) - -test( - 'optimize option: enabled with minify disabled', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss({ optimize: { minify: false } })], - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ exec, expect, fs }) => { - await exec('pnpm vite build') - - expect((await fs.dumpFiles('dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css')) - .toMatchInlineSnapshot(` - " - --- dist/assets/index-.css --- - @media (hover: hover) { - .hover\\:text-\\[black\\]:hover { - color: #000; - } - } - " - `) - }, -) - -test( - `the plugin works when using the environment API`, - { - fs: { - 'package.json': txt` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [tailwindcss()], - builder: {}, - environments: { - server: { - build: { - cssMinify: false, - emitAssets: true, - rollupOptions: { input: './src/server.ts' }, - }, - }, - }, - }) - `, - // Has to exist or the build fails - 'index.html': html` -
- `, - 'src/server.ts': js` - // Import the stylesheet in the server build - import a from './index.css?url' - console.log(a) - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ root, fs, exec, expect }) => { - await exec('pnpm vite build', { cwd: root }) - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`content-['index.html']`]) - }, -) - -// https://github.com/tailwindlabs/tailwindcss/issues/17532 -test( - 'deleting a file should not crash Vite', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "6.2.5" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - -
- - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/main.js': js` - import iconUrl from './assets/icon.svg?url' - import './index.css' - - document.querySelector('#app').innerHTML = iconUrl - `, - 'src/assets/icon.svg': html` - - - - `, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let [main, css] = await Promise.all([ - fetch(`${url}/src/main.js`).then((r) => r.text()), - fetch(`${url}/src/index.css`).then((r) => r.text()), - ]) - - expect(main).toContain('/src/assets/icon.svg?import&url') - expect(main).toContain('/src/index.css') - expect(css).toContain('.fill-red-500') - }) - - process.flush() - - let unexpectedError = process.onStderr((m) => { - return /error|crash|panic/i.test(m) - }) - - await fs.write( - 'src/main.js', - js` - import './index.css' - document.querySelector('#app').innerHTML = 'Hello World' - `, - ) - - await fs.delete('src/assets/icon.svg') - - let result = await Promise.race([ - unexpectedError.then(() => 'stderr'), - new Promise((resolve) => setTimeout(() => resolve('timeout'), 500)), - ]) - - expect(result).toBe('timeout') - - await retryAssertion(async () => { - let response = await fetch(`${url}/src/index.css?t=${Date.now()}`) - expect(response.status).toBe(200) - }) - }, -) diff --git a/integrations/vite/multi-root.test.ts b/integrations/vite/multi-root.test.ts index 4020aff0f..d1a1337a4 100644 --- a/integrations/vite/multi-root.test.ts +++ b/integrations/vite/multi-root.test.ts @@ -12,7 +12,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, @@ -96,7 +96,7 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/nuxt.test.ts b/integrations/vite/nuxt.test.ts index 20f367020..ab14628c7 100644 --- a/integrations/vite/nuxt.test.ts +++ b/integrations/vite/nuxt.test.ts @@ -1,15 +1,4 @@ -import { - candidate, - css, - fetchStyles, - getRandomPort, - html, - json, - retryAssertion, - test, - ts, - yaml, -} from '../utils' +import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' const SETUP = { fs: { @@ -18,18 +7,12 @@ const SETUP = { "type": "module", "dependencies": { "@tailwindcss/vite": "workspace:^", - "nuxt": "3.16.0", - "nitropack": "2.11.0", + "nuxt": "^3.13.1", "tailwindcss": "workspace:^", "vue": "latest" } } `, - 'pnpm-workspace.yaml': yaml` - # - overrides: - nuxi: 3.28.0 - `, 'nuxt.config.ts': ts` import tailwindcss from '@tailwindcss/vite' @@ -95,8 +78,7 @@ test('build', SETUP, async ({ spawn, exec, expect }) => { await exec('pnpm nuxt build') // The Nuxt preview server does not automatically assign a free port if 3000 // is taken, so we use a random port instead. - let port = await getRandomPort() - let process = await spawn(`pnpm nuxt preview --port ${port}`, { + let process = await spawn(`pnpm nuxt preview --port 8724`, { env: { TEST: 'false', NODE_ENV: 'development', diff --git a/integrations/vite/other-transforms.test.ts b/integrations/vite/other-transforms.test.ts index d56396065..b1cb5819c 100644 --- a/integrations/vite/other-transforms.test.ts +++ b/integrations/vite/other-transforms.test.ts @@ -13,8 +13,8 @@ function createSetup(transformer: 'postcss' | 'lightningcss') { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} - "vite": "^7" + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} + "vite": "^6" } } `, diff --git a/integrations/vite/preact.test.ts b/integrations/vite/preact.test.ts deleted file mode 100644 index 8e4deec2b..000000000 --- a/integrations/vite/preact.test.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts, txt } from '../utils' - -test( - 'dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "preact": "^10" - }, - "devDependencies": { - "@preact/preset-vite": "^2", - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import preact from '@preact/preset-vite' - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - plugins: [ - tailwindcss(), - preact(), - { - // Log all HMR payloads to a file so the test can assert on them - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - return send(payload) - } - } - }, - }, - ], - }) - `, - 'index.html': html` - - - - - -
- - - - `, - 'src/main.tsx': ts` - import { render } from 'preact' - import { App } from './app' - - render(, document.getElementById('app')!) - `, - 'src/app.tsx': ts` - import { useState } from 'preact/hooks' - - export function App() { - const [count, setCount] = useState(0) - return ( - - ) - } - `, - 'src/index.css': css`@import 'tailwindcss';`, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - }) - - // Load the component modules, like a browser visiting the page would - await fetch(`${url}/src/main.tsx`) - await fetch(`${url}/src/app.tsx`) - - // Editing a component keeps HMR intact: new classes are delivered through - // a regular update, not a full page reload (which would lose all state) - { - await fs.write( - 'src/app.tsx', - ts` - import { useState } from 'preact/hooks' - - export function App() { - const [count, setCount] = useState(0) - return ( - - ) - } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`flex`) - }) - expect(await fs.read('hmr.log')).toContain('"type":"update"') - expect(await fs.read('hmr.log')).not.toContain('full-reload') - } - - // Changing a scanned file that is not part of the module graph (e.g. - // `package.json`, which package managers and other tooling write to while - // the dev server is running) should not trigger a full reload either — - // that would destroy client state. New candidates should still be picked - // up because the file is a watch dependency of the CSS root, so the CSS - // hot-updates through Vite's regular pipeline. - // - // https://github.com/tailwindlabs/tailwindcss/issues/20411 - { - await fs.write( - 'package.json', - txt` - { - "type": "module", - "description": "content-['package.json']", - "dependencies": { - "preact": "^10" - }, - "devDependencies": { - "@preact/preset-vite": "^2", - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^", - "vite": "^8" - } - } - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url) - expect(styles).toContain(candidate`content-['package.json']`) - }) - expect(await fs.read('hmr.log')).not.toContain('full-reload') - } - }, -) diff --git a/integrations/vite/react-router.test.ts b/integrations/vite/react-router.test.ts index 98b0c928d..5b9574fd6 100644 --- a/integrations/vite/react-router.test.ts +++ b/integrations/vite/react-router.test.ts @@ -102,101 +102,13 @@ test('dev mode', { fs: WORKSPACE }, async ({ fs, spawn, expect }) => { }) }) -test( - // cf. https://github.com/remix-run/react-router/blob/00cb4d7b310663b2e84152700c05d3b503005e83/integration/vite-hmr-hdr-test.ts#L311-L318 - 'dev mode, editing a server-only loader dependency triggers HDR instead of a full reload', - { - fs: { - ...WORKSPACE, - 'package.json': json` - { - "type": "module", - "dependencies": { - "@react-router/dev": "^7", - "@react-router/node": "^7", - "@react-router/serve": "^7", - "@tailwindcss/vite": "workspace:^", - "@types/node": "^20", - "@types/react-dom": "^19", - "@types/react": "^19", - "isbot": "^5", - "react-dom": "^19", - "react-router": "^7", - "react": "^19", - "tailwindcss": "workspace:^", - "vite": "^7" - } - } - `, - 'app/routes/home.tsx': ts` - import type { Route } from './+types/home' - import { direct } from '../direct-hdr-dep' - - export async function loader() { - return { message: direct } - } - - export default function Home({ loaderData }: Route.ComponentProps) { - return ( -
-

{loaderData.message}

- -
- ) - } - `, - 'app/direct-hdr-dep.ts': ts` export const direct = 'HDR: 0' `, - }, - }, - async ({ fs, spawn, expect }) => { - let process = await spawn('pnpm react-router dev') - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - // check initial state - await retryAssertion(async () => { - let html = await (await fetch(url)).text() - expect(html).toContain('HDR: 0') - - let css = await fetchStyles(url) - expect(css).toContain(candidate`font-bold`) - }) - - // Flush stdout so we only see messages triggered by the edit below. - process.flush() - - // Edit the server-only module. The client environment watches this file - // but it only exists in the server module graph. Without the fix, the - // Tailwind CSS plugin would trigger a full page reload on the client - // instead of letting react-router handle HDR. - await fs.write('app/direct-hdr-dep.ts', ts` export const direct = 'HDR: 1' `) - - // check update - await retryAssertion(async () => { - let html = await (await fetch(url)).text() - expect(html).toContain('HDR: 1') - - let css = await fetchStyles(url) - expect(css).toContain(candidate`font-bold`) - }) - - // Assert the client receives an HMR update (not a full page reload). - await process.onStdout((m) => m.includes('(client) hmr update')) - }, -) - test('build mode', { fs: WORKSPACE }, async ({ spawn, exec, expect }) => { await exec('pnpm react-router build') let process = await spawn('pnpm react-router-serve ./build/server/index.js') let url = '' await process.onStdout((m) => { - let match = /\[react-router-serve\]\s*(http.*) \/?/.exec(m) + let match = /\[react-router-serve\]\s*(http.*)\ \/?/.exec(m) if (match) url = match[1] return url != '' }) diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts index fc9aca5c7..6072c37da 100644 --- a/integrations/vite/resolvers.test.ts +++ b/integrations/vite/resolvers.test.ts @@ -1,661 +1,5 @@ import { describe } from 'vitest' -import { - candidate, - css, - fetchStyles, - html, - js, - json, - retryAssertion, - test, - ts, - txt, - yaml, -} from '../utils' - -test( - 'resolves tsconfig paths in production build', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'tsconfig.json': json` - { - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - tsconfigPaths: true, - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/styles/base.css'; - @plugin '@/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`]) - }, -) - -test( - 'resolves tsconfig paths in dev mode', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'tsconfig.json': json` - { - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - tsconfigPaths: true, - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/styles/base.css'; - @plugin '@/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`underline`) - expect(styles).toContain(candidate`custom-underline`) - }) - }, -) - -test( - 'resolves at-sign aliases in production build', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - import { fileURLToPath } from 'node:url' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }], - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import '@/src/styles/base.css'; - @plugin '@/src/plugin.js'; - `, - 'src/styles/base.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`]) - }, -) - -test( - 'resolves package plugins in production build with at-sign aliases', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "tailwind-scrollbar": "^4.0.2", - "tailwindcss-motion": "^1.1.1", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - import { fileURLToPath } from 'node:url' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - resolve: { - alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }], - }, - }) - `, - 'index.html': html` - - - - -
Hello, world!
- - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'tailwind-scrollbar'; - @plugin 'tailwindcss-motion'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [ - candidate`scrollbar`, - candidate`scrollbar-thumb-red-500`, - candidate`motion-preset-fade`, - ]) - }, -) - -test( - 'resolves package plugins to JS entries in production build when browser points to CSS', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "plugin-browser-css": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'pnpm-workspace.yaml': yaml` - # - packages: - - packages/* - `, - 'packages/plugin-browser-css/package.json': json` - { - "name": "plugin-browser-css", - "version": "1.0.0", - "type": "module", - "main": "./index.js", - "module": "./index.js", - "browser": "./browser.css" - } - `, - 'packages/plugin-browser-css/index.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.browser-css-plugin': { 'border-bottom': '1px solid green' } }) - } - `, - 'packages/plugin-browser-css/browser.css': css` - .should-not-be-loaded-as-a-plugin { - display: none; - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - -
Hello, world!
- - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'plugin-browser-css'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] - - await fs.expectFileToContain(filename, [candidate`browser-css-plugin`]) - }, -) - -test( - 'resolves package plugins to JS entries in dev mode when browser points to CSS', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "plugin-browser-css": "workspace:*", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'pnpm-workspace.yaml': yaml` - # - packages: - - packages/* - `, - 'packages/plugin-browser-css/package.json': json` - { - "name": "plugin-browser-css", - "version": "1.0.0", - "type": "module", - "main": "./index.js", - "module": "./index.js", - "browser": "./browser.css" - } - `, - 'packages/plugin-browser-css/index.js': js` - export default function ({ addUtilities }) { - addUtilities({ '.browser-css-plugin': { 'border-bottom': '1px solid green' } }) - } - `, - 'packages/plugin-browser-css/browser.css': css` - .should-not-be-loaded-as-a-plugin { - display: none; - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - -
Hello, world!
- - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'plugin-browser-css'; - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`browser-css-plugin`) - }) - }, -) - -test( - 'resolves package plugins in dev mode when package exports CSS files', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "daisyui": "^5", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - - - `, - 'src/index.css': css` - @import 'tailwindcss'; - @plugin 'daisyui'; - `, - }, - }, - async ({ spawn, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain('.btn') - expect(styles).toContain('.btn-primary') - }) - }, -) - -test( - 'resolve relative CSS files correctly', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @import './themes/glow.css'; - `, - // References a file in the current folder, which names happens to match a - // file in the parent folder as well. - 'src/themes/glow.css': css`@import './entry.css';`, - 'src/themes/entry.css': css` - .do-include-me { - color: green; - } - `, - - // Never rerefenced, so should not be included - 'src/entry.css': css` - .do-not-include-me { - color: red; - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - expect( - (await fs.dumpFiles('./dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css'), - ).toMatchInlineSnapshot(` - " - --- ./dist/assets/index-.css --- - .do-include-me { - color: green; - } - " - `) - }, -) - -test( - 'resolve relative JS files correctly', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - - - - - - - `, - 'src/index.css': css` - @reference 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - @import './themes/glow.css'; - `, - // References a file in the current folder, which names happens to match a - // file in the parent folder as well. - 'src/themes/glow.css': css`@plugin "./my-plugin.js";`, - 'src/themes/my-plugin.js': ts` - export default function ({ addBase }) { - addBase({ '.do-include-me': { color: 'green' } }) - } - `, - - // Never rerefenced, so should not be included - 'src/my-plugin.js': css` - export default function ({ addBase }) { - addBase({ '.do-not-include-me': { 'color': 'red' } }) - } - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - expect( - (await fs.dumpFiles('./dist/**/*.css')).replace(/-([-_a-zA-Z0-9]*?)\.css/g, '-.css'), - ).toMatchInlineSnapshot(` - " - --- ./dist/assets/index-.css --- - @layer base { - .do-include-me { - color: green; - } - } - " - `) - }, -) +import { candidate, css, fetchStyles, html, js, retryAssertion, test, ts, txt } from '../utils' describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { test( @@ -670,7 +14,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "vite": "^5.3.5" } } @@ -738,7 +82,7 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "vite": "^5.3.5" } } diff --git a/integrations/vite/source-maps.test.ts b/integrations/vite/source-maps.test.ts index fe26d9a7c..8857d62b9 100644 --- a/integrations/vite/source-maps.test.ts +++ b/integrations/vite/source-maps.test.ts @@ -12,8 +12,8 @@ test( "tailwindcss": "workspace:^" }, "devDependencies": { - "lightningcss": "^1", - "vite": "^7" + "lightningcss": "^1.26.0", + "vite": "^6" } } `, @@ -91,67 +91,3 @@ test( }) }, ) - -// https://github.com/tailwindlabs/tailwindcss/issues/19930 -test( - 'production build without Tailwind roots should not result in source map warnings', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [ - tailwindcss(), - { - name: 'inspect-source-map-chain', - enforce: 'pre', - transform(_, id) { - if (id.includes('.css')) { - // Force Rollup to collapse the sourcemap chain during this build. - this.getCombinedSourcemap() - } - }, - }, - ], - css: { - devSourcemap: true, - }, - build: { - sourcemap: true, - }, - }) - `, - 'index.html': html` - - -
Hello, world!
- - `, - 'src/index.js': ts` import './index.css' `, - 'src/index.css': css` - body { - color: red; - } - `, - }, - }, - async ({ exec, expect }) => { - let output = await exec('pnpm vite build') - - expect(output).not.toContain('Sourcemap is likely to be incorrect') - }, -) diff --git a/integrations/vite/ssr.test.ts b/integrations/vite/ssr.test.ts index a1d660816..d4d227b8e 100644 --- a/integrations/vite/ssr.test.ts +++ b/integrations/vite/ssr.test.ts @@ -1,72 +1,121 @@ -import { describe } from 'vitest' import { candidate, css, html, json, test, ts } from '../utils' -describe.each([['^5.3'], ['^6.0'], ['^7'], ['^8']])('Using Vite %s', (version) => { - test( - `SSR build`, - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "@tailwindcss/vite": "workspace:^", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "vite": "${version}" - } +const WORKSPACE = { + 'index.html': html` + +
+ + + `, + 'src/index.css': css`@import 'tailwindcss';`, + 'src/index.ts': ts` + import './index.css' + + document.querySelector('#app').innerHTML = \` +
Hello, world!
+ \` + `, + 'server.ts': ts` + import css from './src/index.css?url' + + document.querySelector('#app').innerHTML = \` + +
Hello, world!
+ \` + `, +} + +test( + 'Vite 5', + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "_comment": "This test uses Vite 5.3 on purpose. Do not upgrade it to Vite 6.", + "devDependencies": { + "vite": "^5.3" } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { defineConfig } from 'vite' + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' - export default defineConfig({ - build: { - cssMinify: false, - ssrEmitAssets: true, - }, - plugins: [tailwindcss()], - }) - `, - 'index.html': html` - -
- - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/index.ts': ts` - import './index.css' - - document.querySelector('#app').innerHTML = \` -
Hello, world!
- \` - `, - 'server.ts': ts` - import css from './src/index.css?url' - - document.querySelector('#app').innerHTML = \` - -
Hello, world!
- \` - `, - }, + export default defineConfig({ + build: { + cssMinify: false, + ssrEmitAssets: true, + }, + plugins: [tailwindcss()], + }) + `, + ...WORKSPACE, }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build --ssr server.ts') + }, + async ({ fs, exec, expect }) => { + await exec('pnpm vite build --ssr server.ts') - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - let [filename] = files[0] + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] - await fs.expectFileToContain(filename, [ - candidate`underline`, - candidate`m-2`, - candidate`overline`, - candidate`m-3`, - ]) + await fs.expectFileToContain(filename, [ + candidate`underline`, + candidate`m-2`, + candidate`overline`, + candidate`m-3`, + ]) + }, +) + +test( + `Vite 6`, + { + fs: { + 'package.json': json` + { + "type": "module", + "dependencies": { + "@tailwindcss/vite": "workspace:^", + "tailwindcss": "workspace:^" + }, + "devDependencies": { + "vite": "^6.0" + } + } + `, + 'vite.config.ts': ts` + import tailwindcss from '@tailwindcss/vite' + import { defineConfig } from 'vite' + + export default defineConfig({ + build: { + cssMinify: false, + ssrEmitAssets: true, + }, + plugins: [tailwindcss()], + }) + `, + ...WORKSPACE, }, - ) -}) + }, + async ({ fs, exec, expect }) => { + await exec('pnpm vite build --ssr server.ts') + + let files = await fs.glob('dist/**/*.css') + expect(files).toHaveLength(1) + let [filename] = files[0] + + await fs.expectFileToContain(filename, [ + candidate`underline`, + candidate`m-2`, + candidate`overline`, + candidate`m-3`, + ]) + }, +) diff --git a/integrations/vite/svelte.test.ts b/integrations/vite/svelte.test.ts index e186c3f85..1efb38987 100644 --- a/integrations/vite/svelte.test.ts +++ b/integrations/vite/svelte.test.ts @@ -14,7 +14,7 @@ test( "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, @@ -101,7 +101,7 @@ test( await fs.expectFileToContain(files[0][0], [ candidate`underline`, '.global{color:var(--color-green-500,oklch(72.3% .219 149.579));animation:2s ease-in-out infinite globalKeyframes}', - /\.local.svelte-.*\{color:var\(--color-red-500,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, + /\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(63\.7% \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/, /@keyframes globalKeyframes\{/, /@keyframes svelte-.*-localKeyframes\{/, ]) @@ -125,7 +125,7 @@ test( "devDependencies": { "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/sveltekit.test.ts b/integrations/vite/sveltekit.test.ts deleted file mode 100644 index 92e855da9..000000000 --- a/integrations/vite/sveltekit.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { candidate, css, html, js, json, test, ts } from '../utils' - -test( - 'sveltekit build resolves package plugins', - { - fs: { - 'package.json': json` - { - "type": "module", - "scripts": { - "prepare": "svelte-kit sync || echo ''" - }, - "dependencies": { - "svelte": "^5", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^6", - "@sveltejs/kit": "^2", - "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/vite": "workspace:^", - "vite": "^8" - } - } - `, - 'svelte.config.js': js` - import adapter from '@sveltejs/adapter-auto' - - export default { - kit: { - adapter: adapter(), - }, - } - `, - 'vite.config.ts': ts` - import tailwindcss from '@tailwindcss/vite' - import { sveltekit } from '@sveltejs/kit/vite' - import { defineConfig } from 'vite' - - export default defineConfig({ - build: { cssMinify: false }, - plugins: [tailwindcss(), sveltekit()], - }) - `, - 'src/app.html': html` - - - - %sveltekit.head% - - -
%sveltekit.body%
- - - `, - 'src/routes/+layout.svelte': html` - - - - `, - 'src/routes/+page.svelte': html` -
Hello, world!
- `, - 'src/app.css': css` - @import 'tailwindcss'; - @plugin '@tailwindcss/forms'; - `, - }, - }, - async ({ exec, fs, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('.svelte-kit/output/client/**/*.css') - expect(files.length).toBeGreaterThan(0) - - await fs.expectFileToContain(files[0][0], [candidate`form-input`]) - }, -) diff --git a/integrations/vite/url-rewriting.test.ts b/integrations/vite/url-rewriting.test.ts index 500fe6d71..4077df16d 100644 --- a/integrations/vite/url-rewriting.test.ts +++ b/integrations/vite/url-rewriting.test.ts @@ -15,9 +15,9 @@ describe.each(['postcss', 'lightningcss'])('%s', (transformer) => { "tailwindcss": "workspace:^" }, "devDependencies": { - ${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''} + ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''} "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, diff --git a/integrations/vite/vue.test.ts b/integrations/vite/vue.test.ts index b04f57c6b..7b5da2904 100644 --- a/integrations/vite/vue.test.ts +++ b/integrations/vite/vue.test.ts @@ -1,5 +1,4 @@ -import { stripVTControlCharacters } from 'node:util' -import { candidate, css, fetchStyles, html, json, retryAssertion, test, ts } from '../utils' +import { candidate, html, json, test, ts } from '../utils' test( 'production build', @@ -15,7 +14,7 @@ test( "devDependencies": { "@vitejs/plugin-vue": "^5.1.2", "@tailwindcss/vite": "workspace:^", - "vite": "^7" + "vite": "^6" } } `, @@ -72,309 +71,3 @@ test( await fs.expectFileToContain(files[0][0], ['.bar{']) }, ) - -{ - const VUE_COMPONENT_COUNT = 1_000 - - let vueComponentsWithReferences = Object.fromEntries( - Array.from({ length: VUE_COMPONENT_COUNT }, (_, idx) => [ - `src/components/Component${idx}.vue`, - html` - - - - `, - ]), - ) - - let vueComponentImports = Array.from( - { length: VUE_COMPONENT_COUNT }, - (_, idx) => `import Component${idx} from './components/Component${idx}.vue'`, - ).join('\n') - - let vueComponentUsages = Array.from( - { length: VUE_COMPONENT_COUNT }, - (_, idx) => ``, - ).join('\n') - - test( - 'production build with many Vue style blocks referencing the main stylesheet', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.1.2", - "@tailwindcss/vite": "workspace:^", - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [vue(), tailwindcss()], - }) - `, - 'index.html': html` - - - -
- - - - `, - 'src/main.css': css`@import 'tailwindcss';`, - 'src/main.ts': ts` - import { createApp } from 'vue' - import './main.css' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - - - `, - ...vueComponentsWithReferences, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm vite build') - - let files = await fs.glob('dist/**/*.css') - expect(files).toHaveLength(1) - - await fs.expectFileToContain(files[0][0], [ - candidate`content-['component-0']`, - candidate`component-0`, - candidate`content-['component-99']`, - candidate`component-99`, - candidate`content-['component-999']`, - candidate`component-999`, - ]) - }, - ) -} - -test( - 'error when using `@apply` without `@reference`', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.1.2", - "@tailwindcss/vite": "workspace:^", - "vite": "^7" - } - } - `, - 'vite.config.ts': ts` - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [vue(), tailwindcss()], - }) - `, - 'index.html': html` - - - -
- - - - `, - 'src/main.ts': ts` - import { createApp } from 'vue' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - - - `, - }, - }, - async ({ exec, expect }) => { - expect.assertions(1) - - try { - await exec('pnpm vite build', {}, { ignoreStdErr: true }) - } catch (error) { - let [, message] = - /error during build:([\s\S]*?)file:/g.exec( - stripVTControlCharacters(error.message.replace(/\r?\n/g, '\n')), - ) ?? [] - expect(message.trim()).toMatchInlineSnapshot( - `"[@tailwindcss/vite:generate:build] Cannot apply unknown utility class \`text-red-500\`. Are you using CSS modules or similar and missing \`@reference\`? https://tailwindcss.com/docs/functions-and-directives#reference-directive"`, - ) - } - }, -) - -// https://github.com/tailwindlabs/tailwindcss/issues/20320 -test( - 'editing a scanned `.vue` file that is not loaded as a module does not trigger a full reload', - { - fs: { - 'package.json': json` - { - "type": "module", - "dependencies": { - "vue": "^3.4.37", - "tailwindcss": "workspace:^" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^6", - "@tailwindcss/vite": "workspace:^", - "vite": "^8" - } - } - `, - 'vite.config.ts': ts` - import fs from 'node:fs' - import path from 'node:path' - import { defineConfig } from 'vite' - import vue from '@vitejs/plugin-vue' - import tailwindcss from '@tailwindcss/vite' - - export default defineConfig({ - plugins: [ - vue(), - tailwindcss(), - { - // Log update and full-reload HMR payloads to a file so the - // test can assert on them. Custom events are not logged - // because \`@vitejs/plugin-vue\` sends a \`file-changed\` event - // for every file change, including changes to the log file - // itself, which would cause an infinite feedback loop. - name: 'hmr-wiretap', - configureServer(server) { - let logFile = path.resolve('hmr.log') - fs.writeFileSync(logFile, '') - for (let environment of Object.values(server.environments)) { - let send = environment.hot.send.bind(environment.hot) - environment.hot.send = (payload) => { - if (payload.type === 'update' || payload.type === 'full-reload') { - fs.appendFileSync(logFile, JSON.stringify(payload) + '\\n') - } - return send(payload) - } - } - }, - }, - ], - }) - `, - 'index.html': html` - - - - - - -
- - - - `, - 'src/index.css': css`@import 'tailwindcss';`, - 'src/main.ts': ts` - import { createApp } from 'vue' - import App from './App.vue' - - createApp(App).mount('#app') - `, - 'src/App.vue': html` - - `, - - // This file is scanned by Tailwind but never imported, so it is not - // part of the loaded module graph (e.g. a lazy route that hasn't been - // visited yet) - 'src/LazyRoute.vue': html` - - `, - }, - }, - async ({ spawn, fs, expect }) => { - let process = await spawn('pnpm vite dev') - await process.onStdout((m) => m.includes('ready in')) - - let url = '' - await process.onStdout((m) => { - let match = /Local:\s*(http.*)\//.exec(m) - if (match) url = match[1] - return Boolean(url) - }) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['src/App.vue']`) - expect(styles).toContain(candidate`content-['src/LazyRoute.vue']`) - }) - - // Load `main.ts` and `App.vue` as real modules, like a browser visiting - // the page would - await fetch(`${url}/src/main.ts`) - await fetch(`${url}/src/App.vue`) - - // Changing the scanned but unloaded `.vue` file should not trigger a - // full reload, but new classes should still apply - await fs.write( - 'src/LazyRoute.vue', - html` - - `, - ) - - await retryAssertion(async () => { - let styles = await fetchStyles(url, '/index.html') - expect(styles).toContain(candidate`content-['updated:src/LazyRoute.vue']`) - }) - expect(await fs.read('hmr.log')).not.toContain('full-reload') - }, -) diff --git a/integrations/vitest.config.mts b/integrations/vitest.config.ts similarity index 100% rename from integrations/vitest.config.mts rename to integrations/vitest.config.ts diff --git a/integrations/webpack/loader.test.ts b/integrations/webpack/loader.test.ts deleted file mode 100644 index 9c3d870af..000000000 --- a/integrations/webpack/loader.test.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { css, html, js, json, test } from '../utils' - -test( - '@tailwindcss/webpack loader (build)', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader (watch)', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, spawn, exec, expect }) => { - // Generate the initial build so output CSS files exist on disk - await exec('pnpm webpack --mode=development') - - let process = await spawn('pnpm webpack --mode=development --watch') - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - " - `) - - // Add a new Tailwind class to the HTML file - await fs.write('src/index.html', html` -
- `) - await process.onStdout((m) => m.includes('compiled successfully in')) - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex { - display: flex; - } - .underline { - text-decoration-line: underline; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with @apply', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.css': css` - @import 'tailwindcss/theme'; - - .btn { - @apply flex items-center px-4 py-2 rounded-md; - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --spacing: 0.25rem; - --radius-md: 0.375rem; - } - .btn { - display: flex; - align-items: center; - border-radius: var(--radius-md); - padding-inline: calc(var(--spacing) * 4); - padding-block: calc(var(--spacing) * 2); - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with optimization', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - { - loader: '@tailwindcss/webpack', - options: { - optimize: true, - }, - }, - ], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .flex{display:flex} - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with CSS @import', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import './custom.css'; - @import 'tailwindcss/theme'; - @import 'tailwindcss/utilities'; - `, - 'src/custom.css': css` - /**/ - @utility custom-util { - color: var(--color-red-500); - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - :root, :host { - --color-red-500: oklch(63.7% 0.237 25.331); - } - .flex { - display: flex; - } - .custom-util { - color: var(--color-red-500); - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader with @plugin', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - - module.exports = { - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, - } - `, - 'src/index.js': js`import './index.css'`, - 'src/index.html': html` -
- `, - 'src/index.css': css` - @import 'tailwindcss/utilities'; - @plugin './plugin.js'; - `, - 'src/plugin.js': js` - export default function ({ addUtilities }) { - addUtilities({ - '.custom-underline': { - 'border-bottom': '1px solid green', - }, - }) - } - `, - }, - }, - async ({ fs, exec, expect }) => { - await exec('pnpm webpack --mode=development') - - expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(` - " - --- ./dist/main.css --- - .custom-underline { - border-bottom: 1px solid green; - } - " - `) - }, -) - -test( - '@tailwindcss/webpack loader isolates cache by resource including query', - { - fs: { - 'package.json': json` - { - "main": "./src/index.js", - "browser": "./src/index.js", - "dependencies": { - "css-loader": "^6", - "webpack": "^5", - "webpack-cli": "^5", - "mini-css-extract-plugin": "^2", - "tailwindcss": "workspace:^", - "@tailwindcss/webpack": "workspace:^" - } - } - `, - 'webpack.config.js': js` - let MiniCssExtractPlugin = require('mini-css-extract-plugin') - let path = require('node:path') - - module.exports = { - mode: 'development', - entry: { - a: './src/a.js', - b: './src/b.js', - }, - output: { - clean: true, - }, - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /.css$/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - '@tailwindcss/webpack', - path.resolve(__dirname, 'query-loader.js'), - ], - }, - ], - }, - } - `, - 'query-loader.js': js` - module.exports = function (source) { - if (this.resourceQuery.includes('a')) { - return '@import "tailwindcss/utilities"; @utility only-a {color: var(--color-red-500);}' - } - - if (this.resourceQuery.includes('b')) { - return '@import "tailwindcss/utilities"; @utility only-b {color: var(--color-blue-500);}' - } - - return source - } - `, - 'src/a.js': js`import './index.css?a'`, - 'src/b.js': js`import './index.css?b'`, - 'src/index.css': css``, - }, - }, - async ({ fs, exec }) => { - await exec('pnpm webpack --mode=development') - - await fs.expectFileToContain('dist/a.css', ['only-a', '--color-red-500']) - await fs.expectFileToContain('dist/b.css', ['only-b', '--color-blue-500']) - await fs.expectFileNotToContain('dist/a.css', ['only-b', '--color-blue-500']) - await fs.expectFileNotToContain('dist/b.css', ['only-a', '--color-red-500']) - }, -) diff --git a/package.json b/package.json index 7d41a469e..9ee492eb3 100644 --- a/package.json +++ b/package.json @@ -48,17 +48,23 @@ }, "license": "MIT", "devDependencies": { - "@playwright/test": "^1.62.1", + "@playwright/test": "^1.52.0", "@types/node": "catalog:", - "postcss": "catalog:", - "postcss-import": "^16.1.1", + "postcss": "8.5.3", + "postcss-import": "^16.1.0", "prettier": "catalog:", - "prettier-plugin-embed": "^0.5.1", - "prettier-plugin-organize-imports": "^4.3.0", - "tsup": "^8.5.1", - "turbo": "^2.10.8", - "typescript": "^5.9.3", - "vitest": "^4.1.10" + "prettier-plugin-embed": "^0.5.0", + "prettier-plugin-organize-imports": "^4.0.0", + "tsup": "^8.4.0", + "turbo": "^2.5.2", + "typescript": "^5.5.4", + "vitest": "^2.0.5" }, - "packageManager": "pnpm@11.9.0" + "packageManager": "pnpm@9.6.0", + "pnpm": { + "patchedDependencies": { + "lightningcss@1.29.2": "patches/lightningcss@1.29.2.patch", + "@parcel/watcher@2.5.1": "patches/@parcel__watcher@2.5.1.patch" + } + } } diff --git a/packages/@tailwindcss-browser/README.md b/packages/@tailwindcss-browser/README.md index 5f532607d..95ec9d87d 100644 --- a/packages/@tailwindcss-browser/README.md +++ b/packages/@tailwindcss-browser/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,10 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/@tailwindcss-browser/package.json b/packages/@tailwindcss-browser/package.json index 70af78c1e..5674f2d8e 100644 --- a/packages/@tailwindcss-browser/package.json +++ b/packages/@tailwindcss-browser/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/browser", - "version": "4.3.3", + "version": "4.1.6", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "main": "./dist/index.global.js", @@ -30,8 +30,8 @@ "access": "public" }, "devDependencies": { - "h3": "^1.15.11", - "listhen": "^1.10.1", + "h3": "^1.15.3", + "listhen": "^1.9.0", "tailwindcss": "workspace:*" } } diff --git a/packages/@tailwindcss-browser/playwright.config.ts b/packages/@tailwindcss-browser/playwright.config.ts index c6059dd82..c3ad05b91 100644 --- a/packages/@tailwindcss-browser/playwright.config.ts +++ b/packages/@tailwindcss-browser/playwright.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [['html', { open: 'never' }]], + reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -30,11 +30,10 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - // Playwright 1.60's Windows WebKit worker hangs on shutdown after all tests - // pass, causing CI to fail with a worker force-kill error. - ...(process.platform === 'win32' - ? [] - : [{ name: 'webkit', use: { ...devices['Desktop Safari'] } }]), + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, { name: 'firefox', use: { diff --git a/packages/@tailwindcss-browser/src/index.ts b/packages/@tailwindcss-browser/src/index.ts index 49c7e4675..74992157f 100644 --- a/packages/@tailwindcss-browser/src/index.ts +++ b/packages/@tailwindcss-browser/src/index.ts @@ -2,6 +2,12 @@ import * as tailwindcss from 'tailwindcss' import * as assets from './assets' import { Instrumentation } from './instrumentation' +// Warn users about using the browser build in production as early as possible. +// It can take time for the script to do its work so this must be at the top. +console.warn( + 'The browser build of Tailwind CSS should not be used in production. To use Tailwind CSS in production, use the Tailwind CLI, Vite plugin, or PostCSS plugin: https://tailwindcss.com/docs/installation', +) + /** * The type used by ` - // ``` - if ( - // Whitespace before the candidate - location.contents[location.start - 1]?.match(/\s/) && - // A colon followed by whitespace after the candidate - location.contents.slice(location.end, location.end + 2)?.match(/^:\s/) - ) { - // Compute all `` tag -// - All `` -// - No nested `', offset) - if (endTag === -1) return ranges - offset = endTag + 1 - - ranges.push(startTag, endTag) - } -}) - -const BACKSLASH = 0x5c -const DOUBLE_QUOTE = 0x22 -const SINGLE_QUOTE = 0x27 -const BACKTICK = 0x60 -const TAB = 0x09 -const NEWLINE = 0x0a -const FORM_FEED = 0x0c -const CARRIAGE_RETURN = 0x0d -const SPACE = 0x20 -const SLASH = 0x2f -const EQUALS = 0x3d -const GREATER_THAN = 0x3e - -function isMiddleOfString(line: string): boolean { - let currentQuote: number | null = null - - for (let i = 0; i < line.length; i++) { - let char = line.charCodeAt(i) - switch (char) { - // Escaped character, skip the next character - case BACKSLASH: - i++ - break - - case SINGLE_QUOTE: - case DOUBLE_QUOTE: - case BACKTICK: - // Found matching quote, we are outside of a string - if (currentQuote === char) { - currentQuote = null - } - - // Found a quote, we are inside a string - else if (currentQuote === null) { - currentQuote = char - } - break - } - } - - return currentQuote !== null -} - -const inlineStyleAttributeValueRanges = new DefaultMap((source: string) => { - let ranges: number[] = [] - let offset = 0 - - while (true) { - let tagStart = source.indexOf('<', offset) - if (tagStart === -1) return ranges - - let tagEnd = source.indexOf('>', tagStart + 1) - if (tagEnd === -1) return ranges - - offset = tagEnd + 1 - - for (let i = tagStart + 1; i < tagEnd; i++) { - let char = source.charCodeAt(i) - - if ( - char === SPACE || - char === TAB || - char === NEWLINE || - char === CARRIAGE_RETURN || - char === FORM_FEED - ) { - continue - } - - let start = i - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char === SPACE || - char === TAB || - char === NEWLINE || - char === CARRIAGE_RETURN || - char === FORM_FEED || - char === EQUALS || - char === GREATER_THAN || - char === SLASH - ) { - break - } - - i++ - } - - let attribute = source.slice(start, i).toLowerCase() - if (attribute !== 'style' && attribute !== ':style') continue - - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char !== SPACE && - char !== TAB && - char !== NEWLINE && - char !== CARRIAGE_RETURN && - char !== FORM_FEED - ) { - break - } - - i++ - } - - if (source[i] !== '=') continue - - i++ - while (i < tagEnd) { - let char = source.charCodeAt(i) - if ( - char !== SPACE && - char !== TAB && - char !== NEWLINE && - char !== CARRIAGE_RETURN && - char !== FORM_FEED - ) { - break - } - - i++ - } - - let quote = source[i] - if (quote !== '"' && quote !== "'") continue - - let valueStart = i + 1 - let valueEnd = source.indexOf(quote, valueStart) - if (valueEnd === -1 || valueEnd > tagEnd) break - - ranges.push(valueStart, valueEnd) - i = valueEnd - } - } -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts new file mode 100644 index 000000000..6a4c4166c --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.test.ts @@ -0,0 +1,349 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import type { UserConfig } from '../../../../tailwindcss/src/compat/config/types' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { migrateArbitraryUtilities } from './migrate-arbitrary-utilities' +import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' +import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types' +import { migrateOptimizeModifier } from './migrate-optimize-modifier' + +const designSystems = new DefaultMap((base: string) => { + return new DefaultMap((input: string) => { + return __unstable__loadDesignSystem(input, { base }) + }) +}) + +function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawCandidate: string) { + for (let migration of [ + migrateArbitraryUtilities, + migrateDropUnnecessaryDataTypes, + migrateArbitraryValueToBareValue, + migrateOptimizeModifier, + ]) { + rawCandidate = migration(designSystem, userConfig, rawCandidate) + } + return rawCandidate +} + +describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + + // Basic input with minimal design system to keep the tests fast + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + @theme { + --*: initial; + --spacing: 0.25rem; + --color-red-500: red; + + /* Equivalent of blue-500/50 */ + --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); + } + ` + + test.each([ + // Arbitrary property to static utility + ['[text-wrap:balance]', 'text-balance'], + + // Arbitrary property to static utility with slight differences in + // whitespace. This will require some canonicalization. + ['[display:_flex_]', 'flex'], + ['[display:_flex]', 'flex'], + ['[display:flex_]', 'flex'], + + // Arbitrary property to static utility + // Map number to keyword-like value + ['leading-[1]', 'leading-none'], + + // Arbitrary property to named functional utility + ['[color:var(--color-red-500)]', 'text-red-500'], + ['[background-color:var(--color-red-500)]', 'bg-red-500'], + + // Arbitrary property with modifier to named functional utility with modifier + ['[color:var(--color-red-500)]/25', 'text-red-500/25'], + + // Arbitrary property with arbitrary modifier to named functional utility with + // arbitrary modifier + ['[color:var(--color-red-500)]/[25%]', 'text-red-500/25'], + ['[color:var(--color-red-500)]/[100%]', 'text-red-500'], + ['[color:var(--color-red-500)]/100', 'text-red-500'], + // No need for `/50` because that's already encoded in the `--color-primary` + // value + ['[color:oklch(62.3%_0.214_259.815)]/50', 'text-primary'], + + // Arbitrary property to arbitrary value + ['[max-height:20px]', 'max-h-[20px]'], + + // Arbitrary property to bare value + ['[grid-column:2]', 'col-2'], + ['[grid-column:1234]', 'col-1234'], + + // Arbitrary value to bare value + ['border-[2px]', 'border-2'], + ['border-[1234px]', 'border-1234'], + + // Arbitrary value with data type, to more specific arbitrary value + ['bg-[position:123px]', 'bg-position-[123px]'], + ['bg-[size:123px]', 'bg-size-[123px]'], + + // Arbitrary value with inferred data type, to more specific arbitrary value + ['bg-[123px]', 'bg-position-[123px]'], + + // Arbitrary value with spacing mul + ['w-[64rem]', 'w-256'], + + // Complex arbitrary property to arbitrary value + [ + '[grid-template-columns:repeat(2,minmax(100px,1fr))]', + 'grid-cols-[repeat(2,minmax(100px,1fr))]', + ], + // Complex arbitrary property to bare value + ['[grid-template-columns:repeat(2,minmax(0,1fr))]', 'grid-cols-2'], + + // Arbitrary value to bare value with percentage + ['from-[25%]', 'from-25%'], + + // Arbitrary percentage value must be a whole number. Should not migrate to + // a bare value. + ['from-[2.5%]', 'from-[2.5%]'], + ])(testName, async (candidate, result) => { + if (strategy === 'with-variant') { + candidate = `focus:${candidate}` + result = `focus:${result}` + } else if (strategy === 'important') { + candidate = `${candidate}!` + result = `${result}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` + } + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) + }) +}) + +const css = String.raw +test('migrate with custom static utility `@utility custom {…}`', async () => { + let candidate = '[--key:value]' + let result = 'custom' + + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @utility custom { + --key: value; + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('migrate with custom functional utility `@utility custom-* {…}`', async () => { + let candidate = '[--key:value]' + let result = 'custom-value' + + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @utility custom-* { + --key: --value('value'); + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('migrate with custom functional utility `@utility custom-* {…}` that supports bare values', async () => { + let candidate = '[tab-size:4]' + let result = 'tab-4' + + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @utility tab-* { + tab-size: --value(integer); + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test.each([ + ['[tab-size:0]', 'tab-0'], + ['[tab-size:4]', 'tab-4'], + ['[tab-size:8]', 'tab-github'], + ['tab-[0]', 'tab-0'], + ['tab-[4]', 'tab-4'], + ['tab-[8]', 'tab-github'], +])( + 'migrate custom @utility from arbitrary values to bare values and named values (based on theme)', + async (candidate, expected) => { + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + --tab-size-github: 8; + } + + @utility tab-* { + tab-size: --value(--tab-size, integer, [integer]); + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(expected) + }, +) + +describe.each([['@theme'], ['@theme inline']])('%s', (theme) => { + test.each([ + ['[color:CanvasText]', 'text-canvas'], + ['text-[CanvasText]', 'text-canvas'], + ])('migrate arbitrary value to theme value %s => %s', async (candidate, result) => { + let input = css` + @import 'tailwindcss'; + ${theme} { + --*: initial; + --color-canvas: CanvasText; + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) + }) + + // Some utilities read from specific namespaces, in this case we do not want + // to migrate to a value in that namespace if we reference a variable that + // results in the same value, but comes from a different namespace. + // + // E.g.: `max-w` reads from: ['--max-width', '--spacing', '--container'] + test.each([ + // `max-w` does not read from `--breakpoint-md`, but `--breakpoint-md` and + // `--container-3xl` happen to result in the same value. The difference is + // the semantics of the value. + ['max-w-(--breakpoint-md)', 'max-w-(--breakpoint-md)'], + ['max-w-(--container-3xl)', 'max-w-3xl'], + ])('migrate arbitrary value to theme value %s => %s', async (candidate, result) => { + let input = css` + @import 'tailwindcss'; + ${theme} { + --*: initial; + --breakpoint-md: 48rem; + --container-3xl: 48rem; + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(result) + }) +}) + +test('migrate a arbitrary property without spaces, to a theme value with spaces (canonicalization)', async () => { + let candidate = 'font-[foo,bar,baz]' + let expected = 'font-example' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + --font-example: foo, bar, baz; + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(expected) +}) + +describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + test.each([ + // Default spacing scale + ['w-[64rem]', 'w-256', '0.25rem'], + + // Keep arbitrary value if units are different + ['w-[124px]', 'w-[124px]', '0.25rem'], + + // Keep arbitrary value if bare value doesn't fit in steps of .25 + ['w-[0.123rem]', 'w-[0.123rem]', '0.25rem'], + + // Custom pixel based spacing scale + ['w-[123px]', 'w-123', '1px'], + ['w-[256px]', 'w-128', '2px'], + ])(testName, async (candidate, expected, spacing) => { + if (strategy === 'with-variant') { + candidate = `focus:${candidate}` + expected = `focus:${expected}` + } else if (strategy === 'important') { + candidate = `${candidate}!` + expected = `${expected}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + expected = `tw:${expected.replaceAll('var(--', 'var(--tw-')}` + } + + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + + @theme { + --*: initial; + --spacing: ${spacing}; + } + ` + let designSystem = await __unstable__loadDesignSystem(input, { + base: __dirname, + }) + + let migrated = migrate(designSystem, {}, candidate) + expect(migrated).toEqual(expected) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts new file mode 100644 index 000000000..2f3ee7e43 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-utilities.ts @@ -0,0 +1,339 @@ +import { printModifier, type Candidate } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' +import { dimensions } from '../../utils/dimension' +import type { Writable } from '../../utils/types' +import { computeUtilitySignature } from './signatures' + +// For all static utilities in the system, compute a lookup table that maps the +// utility signature to the utility name. This is used to find the utility name +// for a given utility signature. +// +// For all functional utilities, we can compute static-like utilities by +// essentially pre-computing the values and modifiers. This is a bit slow, but +// also only has to happen once per design system. +const preComputedUtilities = new DefaultMap>((ds) => { + let signatures = computeUtilitySignature.get(ds) + let lookup = new DefaultMap(() => []) + + for (let [className, meta] of ds.getClassList()) { + let signature = signatures.get(className) + if (typeof signature !== 'string') continue + lookup.get(signature).push(className) + + for (let modifier of meta.modifiers) { + // Modifiers representing numbers can be computed and don't need to be + // pre-computed. Doing the math and at the time of writing this, this + // would save you 250k additionally pre-computed utilities... + if (isValidSpacingMultiplier(modifier)) { + continue + } + + let classNameWithModifier = `${className}/${modifier}` + let signature = signatures.get(classNameWithModifier) + if (typeof signature !== 'string') continue + lookup.get(signature).push(classNameWithModifier) + } + } + + return lookup +}) + +const baseReplacementsCache = new DefaultMap>( + () => new Map(), +) + +const spacing = new DefaultMap | null>((ds) => { + let spacingMultiplier = ds.resolveThemeValue('--spacing') + if (spacingMultiplier === undefined) return null + + let parsed = dimensions.get(spacingMultiplier) + if (!parsed) return null + + let [value, unit] = parsed + + return new DefaultMap((input) => { + let parsed = dimensions.get(input) + if (!parsed) return null + + let [myValue, myUnit] = parsed + if (myUnit !== unit) return null + + return myValue / value + }) +}) + +export function migrateArbitraryUtilities( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let utilities = preComputedUtilities.get(designSystem) + let signatures = computeUtilitySignature.get(designSystem) + + for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { + // We are only interested in arbitrary properties and arbitrary values + if ( + // Arbitrary property + readonlyCandidate.kind !== 'arbitrary' && + // Arbitrary value + !(readonlyCandidate.kind === 'functional' && readonlyCandidate.value?.kind === 'arbitrary') + ) { + continue + } + + // 1. Canonicalize the value. This might be a bit wasteful because it might + // have been done by other migrations before, but essentially we want to + // canonicalize the arbitrary value to its simplest canonical form. We + // won't be constant folding `calc(…)` expressions (yet?), but we can + // remove unnecessary whitespace (which the `printCandidate` already + // handles for us). + // + // E.g.: + // + // ``` + // [display:_flex_] => [display:flex] + // [display:_flex] => [display:flex] + // [display:flex_] => [display:flex] + // [display:flex] => [display:flex] + // ``` + // + let canonicalizedCandidate = designSystem.printCandidate(readonlyCandidate) + if (canonicalizedCandidate !== rawCandidate) { + return migrateArbitraryUtilities(designSystem, _userConfig, canonicalizedCandidate) + } + + // The below logic makes use of mutation. Since candidates in the + // DesignSystem are cached, we can't mutate them directly. + let candidate = structuredClone(readonlyCandidate) as Writable + + // Create a basic stripped candidate without variants or important flag. We + // will re-add those later but they are irrelevant for what we are trying to + // do here (and will increase cache hits because we only have to deal with + // the base utility, nothing more). + let targetCandidate = structuredClone(candidate) + targetCandidate.important = false + targetCandidate.variants = [] + + let targetCandidateString = designSystem.printCandidate(targetCandidate) + if (baseReplacementsCache.get(designSystem).has(targetCandidateString)) { + let target = structuredClone( + baseReplacementsCache.get(designSystem).get(targetCandidateString)!, + ) + // Re-add the variants and important flag from the original candidate + target.variants = candidate.variants + target.important = candidate.important + + return designSystem.printCandidate(target) + } + + // Compute the signature for the target candidate + let targetSignature = signatures.get(targetCandidateString) + if (typeof targetSignature !== 'string') continue + + // Try a few options to find a suitable replacement utility + for (let replacementCandidate of tryReplacements(targetSignature, targetCandidate)) { + let replacementString = designSystem.printCandidate(replacementCandidate) + let replacementSignature = signatures.get(replacementString) + if (replacementSignature !== targetSignature) { + continue + } + + // Ensure that if CSS variables were used, that they are still used + if (!allVariablesAreUsed(designSystem, candidate, replacementCandidate)) { + continue + } + + replacementCandidate = structuredClone(replacementCandidate) + + // Cache the result so we can re-use this work later + baseReplacementsCache.get(designSystem).set(targetCandidateString, replacementCandidate) + + // Re-add the variants and important flag from the original candidate + replacementCandidate.variants = candidate.variants + replacementCandidate.important = candidate.important + + // Update the candidate with the new value + Object.assign(candidate, replacementCandidate) + + // We will re-print the candidate to get the migrated candidate out + return designSystem.printCandidate(candidate) + } + } + + return rawCandidate + + function* tryReplacements( + targetSignature: string, + candidate: Extract, + ): Generator { + // Find a corresponding utility for the same signature + let replacements = utilities.get(targetSignature) + + // Multiple utilities can map to the same signature. Not sure how to migrate + // this one so let's just skip it for now. + // + // TODO: Do we just migrate to the first one? + if (replacements.length > 1) return + + // If we didn't find any replacement utilities, let's try to strip the + // modifier and find a replacement then. If we do, we can try to re-add the + // modifier later and verify if we have a valid migration. + // + // This is necessary because `text-red-500/50` will not be pre-computed, + // only `text-red-500` will. + if (replacements.length === 0 && candidate.modifier) { + let candidateWithoutModifier = { ...candidate, modifier: null } + let targetSignatureWithoutModifier = signatures.get( + designSystem.printCandidate(candidateWithoutModifier), + ) + if (typeof targetSignatureWithoutModifier === 'string') { + for (let replacementCandidate of tryReplacements( + targetSignatureWithoutModifier, + candidateWithoutModifier, + )) { + yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) + } + } + } + + // If only a single utility maps to the signature, we can use that as the + // replacement. + if (replacements.length === 1) { + for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { + yield replacementCandidate + } + } + + // Find a corresponding functional utility for the same signature + else if (replacements.length === 0) { + // An arbitrary property will only set a single property, we can use that + // to find functional utilities that also set this property. + let value = + candidate.kind === 'arbitrary' ? candidate.value : (candidate.value?.value ?? null) + if (value === null) return + + let spacingMultiplier = spacing.get(designSystem)?.get(value) + + for (let root of designSystem.utilities.keys('functional')) { + // Try as bare value + for (let replacementCandidate of parseCandidate(designSystem, `${root}-${value}`)) { + yield replacementCandidate + } + + // Try as bare value with modifier + if (candidate.modifier) { + for (let replacementCandidate of parseCandidate( + designSystem, + `${root}-${value}${candidate.modifier}`, + )) { + yield replacementCandidate + } + } + + // Try bare value based on the `--spacing` value. E.g.: + // + // - `w-[64rem]` → `w-256` + if (spacingMultiplier !== null) { + for (let replacementCandidate of parseCandidate( + designSystem, + `${root}-${spacingMultiplier}`, + )) { + yield replacementCandidate + } + + // Try bare value based on the `--spacing` value, but with a modifier + if (candidate.modifier) { + for (let replacementCandidate of parseCandidate( + designSystem, + `${root}-${spacingMultiplier}${printModifier(candidate.modifier)}`, + )) { + yield replacementCandidate + } + } + } + + // Try as arbitrary value + for (let replacementCandidate of parseCandidate(designSystem, `${root}-[${value}]`)) { + yield replacementCandidate + } + + // Try as arbitrary value with modifier + if (candidate.modifier) { + for (let replacementCandidate of parseCandidate( + designSystem, + `${root}-[${value}]${printModifier(candidate.modifier)}`, + )) { + yield replacementCandidate + } + } + } + } + } +} + +function parseCandidate(designSystem: DesignSystem, input: string) { + return designSystem.parseCandidate( + designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) + ? `${designSystem.theme.prefix}:${input}` + : input, + ) +} + +// Let's make sure that all variables used in the value are also all used in the +// found replacement. If not, then we are dealing with a different namespace or +// we could lose functionality in case the variable was changed higher up in the +// DOM tree. +function allVariablesAreUsed( + designSystem: DesignSystem, + candidate: Candidate, + replacement: Candidate, +) { + let value: string | null = null + + // Functional utility with arbitrary value and variables + if ( + candidate.kind === 'functional' && + candidate.value?.kind === 'arbitrary' && + candidate.value.value.includes('var(--') + ) { + value = candidate.value.value + } + + // Arbitrary property with variables + else if (candidate.kind === 'arbitrary' && candidate.value.includes('var(--')) { + value = candidate.value + } + + // No variables in the value, so this is a safe migration + if (value === null) { + return true + } + + let replacementAsCss = designSystem + .candidatesToCss([designSystem.printCandidate(replacement)]) + .join('\n') + + let isSafeMigration = true + ValueParser.walk(ValueParser.parse(value), (node) => { + if (node.kind === 'function' && node.value === 'var') { + let variable = node.nodes[0].value + let r = new RegExp(`var\\(${variable}[,)]\\s*`, 'g') + if ( + // We need to check if the variable is used in the replacement + !r.test(replacementAsCss) || + // The value cannot be set to a different value in the + // replacement because that would make it an unsafe migration + replacementAsCss.includes(`${variable}:`) + ) { + isSafeMigration = false + return ValueParser.ValueWalkAction.Stop + } + } + }) + + return isSafeMigration +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts new file mode 100644 index 000000000..45ab8bd2a --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts @@ -0,0 +1,68 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' + +test.each([ + ['aspect-[12/34]', 'aspect-12/34'], + ['aspect-[1.2/34]', 'aspect-[1.2/34]'], + ['col-start-[7]', 'col-start-7'], + ['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities + + ['grid-cols-[subgrid]', 'grid-cols-subgrid'], + ['grid-rows-[subgrid]', 'grid-rows-subgrid'], + + // Only 50-200% (inclusive) are valid: + // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage + ['font-stretch-[50%]', 'font-stretch-50%'], + ['font-stretch-[50.5%]', 'font-stretch-[50.5%]'], + ['font-stretch-[201%]', 'font-stretch-[201%]'], + ['font-stretch-[49%]', 'font-stretch-[49%]'], + // Should stay as-is + ['font-stretch-[1/2]', 'font-stretch-[1/2]'], + + // Bare value with % is valid for these utilities + ['from-[28%]', 'from-28%'], + ['via-[28%]', 'via-28%'], + ['to-[28%]', 'to-28%'], + ['from-[28.5%]', 'from-[28.5%]'], + ['via-[28.5%]', 'via-[28.5%]'], + ['to-[28.5%]', 'to-[28.5%]'], + + // This test in itself is a bit flawed because `text-[1/2]` currently + // generates something. Converting it to `text-1/2` doesn't produce anything. + ['text-[1/2]', 'text-[1/2]'], + + // Leading is special, because `leading-[123]` is the direct value of 123, but + // `leading-123` maps to `calc(--spacing(123))`. + ['leading-[123]', 'leading-[123]'], + + ['data-[selected]:flex', 'data-selected:flex'], + ['data-[foo=bar]:flex', 'data-[foo=bar]:flex'], + + ['supports-[gap]:flex', 'supports-gap:flex'], + ['supports-[display:grid]:flex', 'supports-[display:grid]:flex'], + + ['group-data-[selected]:flex', 'group-data-selected:flex'], + ['group-data-[foo=bar]:flex', 'group-data-[foo=bar]:flex'], + ['group-has-data-[selected]:flex', 'group-has-data-selected:flex'], + + ['aria-[selected]:flex', 'aria-[selected]:flex'], + ['aria-[selected="true"]:flex', 'aria-selected:flex'], + ['aria-[selected*="true"]:flex', 'aria-[selected*="true"]:flex'], + + ['group-aria-[selected]:flex', 'group-aria-[selected]:flex'], + ['group-aria-[selected="true"]:flex', 'group-aria-selected:flex'], + ['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'], + + ['max-lg:hover:data-[selected]:flex!', 'max-lg:hover:data-selected:flex!'], + [ + 'data-[selected]:aria-[selected="true"]:aspect-[12/34]', + 'data-selected:aria-selected:aspect-12/34', + ], +])('%s => %s (%#)', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(migrateArbitraryValueToBareValue(designSystem, {}, candidate)).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts new file mode 100644 index 000000000..116888ca3 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts @@ -0,0 +1,180 @@ +import { + parseCandidate, + type Candidate, + type NamedUtilityValue, +} from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { + isPositiveInteger, + isValidSpacingMultiplier, +} from '../../../../tailwindcss/src/utils/infer-data-type' +import { segment } from '../../../../tailwindcss/src/utils/segment' +import { walkVariants } from '../../utils/walk-variants' +import { computeUtilitySignature } from './signatures' + +export function migrateArbitraryValueToBareValue( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let signatures = computeUtilitySignature.get(designSystem) + + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + let clone = structuredClone(candidate) + let changed = false + + // Migrate arbitrary values to bare values + if (clone.kind === 'functional' && clone.value?.kind === 'arbitrary') { + let expectedSignature = signatures.get(rawCandidate) + if (expectedSignature !== null) { + for (let value of tryValueReplacements(clone)) { + let newSignature = signatures.get(designSystem.printCandidate({ ...clone, value })) + if (newSignature === expectedSignature) { + changed = true + clone.value = value + break + } + } + } + } + + for (let [variant] of walkVariants(clone)) { + // Convert `data-[selected]` to `data-selected` + if ( + variant.kind === 'functional' && + variant.root === 'data' && + variant.value?.kind === 'arbitrary' && + !variant.value.value.includes('=') + ) { + changed = true + variant.value = { + kind: 'named', + value: variant.value.value, + } + } + + // Convert `aria-[selected="true"]` to `aria-selected` + else if ( + variant.kind === 'functional' && + variant.root === 'aria' && + variant.value?.kind === 'arbitrary' && + (variant.value.value.endsWith('=true') || + variant.value.value.endsWith('="true"') || + variant.value.value.endsWith("='true'")) + ) { + let [key, _value] = segment(variant.value.value, '=') + if ( + // aria-[foo~="true"] + key[key.length - 1] === '~' || + // aria-[foo|="true"] + key[key.length - 1] === '|' || + // aria-[foo^="true"] + key[key.length - 1] === '^' || + // aria-[foo$="true"] + key[key.length - 1] === '$' || + // aria-[foo*="true"] + key[key.length - 1] === '*' + ) { + continue + } + + changed = true + variant.value = { + kind: 'named', + value: variant.value.value.slice(0, variant.value.value.indexOf('=')), + } + } + + // Convert `supports-[gap]` to `supports-gap` + else if ( + variant.kind === 'functional' && + variant.root === 'supports' && + variant.value?.kind === 'arbitrary' && + /^[a-z-][a-z0-9-]*$/i.test(variant.value.value) + ) { + changed = true + variant.value = { + kind: 'named', + value: variant.value.value, + } + } + } + + return changed ? designSystem.printCandidate(clone) : rawCandidate + } + + return rawCandidate +} + +// Convert functional utilities with arbitrary values to bare values if we can. +// We know that bare values can only be: +// +// 1. A number (with increments of .25) +// 2. A percentage (with increments of .25 followed by a `%`) +// 3. A ratio with whole numbers +// +// Not a bare value per se, but if we are dealing with a keyword, that could +// potentially also look like a bare value (aka no `[` or `]`). E.g.: +// ```diff +// grid-cols-[subgrid] +// grid-cols-subgrid +// ``` +function* tryValueReplacements( + candidate: Extract, + value: string = candidate.value?.value ?? '', + seen: Set = new Set(), +): Generator { + if (seen.has(value)) return + seen.add(value) + + // 0. Just try to drop the square brackets and see if it works + // 1. A number (with increments of .25) + yield { + kind: 'named', + value, + fraction: null, + } + + // 2. A percentage (with increments of .25 followed by a `%`) + // Try to drop the `%` and see if it works + if (value.endsWith('%') && isValidSpacingMultiplier(value.slice(0, -1))) { + yield { + kind: 'named', + value: value.slice(0, -1), + fraction: null, + } + } + + // 3. A ratio with whole numbers + if (value.includes('/')) { + let [numerator, denominator] = value.split('/') + if (isPositiveInteger(numerator) && isPositiveInteger(denominator)) { + yield { + kind: 'named', + value: numerator, + fraction: `${numerator}/${denominator}`, + } + } + } + + // It could also be that we have `20px`, we can try just `20` and see if it + // results in the same signature. + let allNumbersAndFractions = new Set() + + // Figure out all numbers and fractions in the value + for (let match of value.matchAll(/(\d+\/\d+)|(\d+\.?\d+)/g)) { + allNumbersAndFractions.add(match[0].trim()) + } + + // Sort the numbers and fractions where the smallest length comes first. This + // will result in the smallest replacement. + let options = Array.from(allNumbersAndFractions).sort((a, z) => { + return a.length - z.length + }) + + // Try all the options + for (let option of options) { + yield* tryValueReplacements(candidate, option, seen) + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.test.ts new file mode 100644 index 000000000..439082570 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.test.ts @@ -0,0 +1,158 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { migrateArbitraryVariants } from './migrate-arbitrary-variants' + +const css = String.raw +const designSystems = new DefaultMap((base: string) => { + return new DefaultMap((input: string) => { + return __unstable__loadDesignSystem(input, { base }) + }) +}) + +describe.each([['default'], ['important'], ['prefix']].slice(0, 1))('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + + // Basic input with minimal design system to keep the tests fast + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + @theme { + --*: initial; + } + ` + + test.each([ + // Arbitrary variant to static variant + ['[&:focus]:flex', 'focus:flex'], + + // Arbitrary variant to static variant with at-rules + ['[@media(scripting:_none)]:flex', 'noscript:flex'], + + // Arbitrary variant to static utility at-rules and with slight differences + // in whitespace. This will require some canonicalization. + ['[@media(scripting:none)]:flex', 'noscript:flex'], + ['[@media(scripting:_none)]:flex', 'noscript:flex'], + ['[@media_(scripting:_none)]:flex', 'noscript:flex'], + + // With compound variants + ['has-[&:focus]:flex', 'has-focus:flex'], + ['not-[&:focus]:flex', 'not-focus:flex'], + ['group-[&:focus]:flex', 'group-focus:flex'], + ['peer-[&:focus]:flex', 'peer-focus:flex'], + ['in-[&:focus]:flex', 'in-focus:flex'], + ])(testName, async (candidate, result) => { + if (strategy === 'important') { + candidate = `${candidate}!` + result = `${result}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` + } + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) + }) +}) + +test('unsafe migrations keep the candidate as-is', async () => { + // `hover:` also includes an `@media` query in addition to the `&:hover` + // state. Migration is not safe because the functionality would be different. + let candidate = '[&:hover]:flex' + let result = '[&:hover]:flex' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + ` + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('make unsafe migration safe (1)', async () => { + // Overriding the `hover:` variant to only use a selector will make the + // migration safe. + let candidate = '[&:hover]:flex' + let result = 'hover:flex' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @variant hover (&:hover); + ` + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('make unsafe migration safe (2)', async () => { + // Overriding the `hover:` variant to only use a selector will make the + // migration safe. This time with the long-hand `@variant` syntax. + let candidate = '[&:hover]:flex' + let result = 'hover:flex' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @variant hover { + &:hover { + @slot; + } + } + ` + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('custom selector-based variants', async () => { + let candidate = '[&.macos]:flex' + let result = 'is-macos:flex' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @variant is-macos (&.macos); + ` + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) + +test('custom @media-based variants', async () => { + let candidate = '[@media(prefers-reduced-transparency:reduce)]:flex' + let result = 'transparency-safe:flex' + let input = css` + @import 'tailwindcss'; + @theme { + --*: initial; + } + @variant transparency-safe { + @media (prefers-reduced-transparency: reduce) { + @slot; + } + } + ` + + let designSystem = await designSystems.get(__dirname).get(input) + let migrated = migrateArbitraryVariants(designSystem, {}, candidate) + expect(migrated).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts index 258ba1b33..d2d78931e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-variants.ts @@ -1,23 +1,36 @@ -import { cloneCandidate } from '../../../../tailwindcss/src/candidate' -import { - PRE_COMPUTED_VARIANTS_KEY, - prepareDesignSystemStorage, - VARIANT_SIGNATURE_KEY, -} from '../../../../tailwindcss/src/canonicalize-candidates' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import type { Writable } from '../../../../tailwindcss/src/types' -import { replaceObject } from '../../../../tailwindcss/src/utils/replace-object' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { replaceObject } from '../../utils/replace-object' +import type { Writable } from '../../utils/types' import { walkVariants } from '../../utils/walk-variants' +import { computeVariantSignature } from './signatures' + +const variantsLookup = new DefaultMap>( + (designSystem) => { + let signatures = computeVariantSignature.get(designSystem) + let lookup = new DefaultMap(() => []) + + // Actual static variants + for (let [root, variant] of designSystem.variants.entries()) { + if (variant.kind === 'static') { + let signature = signatures.get(root) + if (typeof signature !== 'string') continue + lookup.get(signature).push(root) + } + } + + return lookup + }, +) export function migrateArbitraryVariants( - baseDesignSystem: DesignSystem, + designSystem: DesignSystem, _userConfig: Config | null, rawCandidate: string, ): string { - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(null) - let variants = designSystem.storage[PRE_COMPUTED_VARIANTS_KEY].get(null) + let signatures = computeVariantSignature.get(designSystem) + let variants = variantsLookup.get(designSystem) for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { // We are only interested in the variants @@ -25,7 +38,7 @@ export function migrateArbitraryVariants( // The below logic makes use of mutation. Since candidates in the // DesignSystem are cached, we can't mutate them directly. - let candidate = cloneCandidate(readonlyCandidate) as Writable + let candidate = structuredClone(readonlyCandidate) as Writable for (let [variant] of walkVariants(candidate)) { if (variant.kind === 'compound') continue @@ -35,11 +48,7 @@ export function migrateArbitraryVariants( if (typeof targetSignature !== 'string') continue let foundVariants = variants.get(targetSignature) - if (foundVariants.length === 0) continue - - // The variant is already in a canonical form, e.g.: `min-lg` and `lg` - // produce the same CSS, but both are valid so we keep them as-is. - if (foundVariants.includes(targetString)) continue + if (foundVariants.length !== 1) continue let foundVariant = foundVariants[0] let parsedVariant = designSystem.parseVariant(foundVariant) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts index 5da651d51..d26c185cc 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts @@ -40,11 +40,6 @@ test.each([ ['w-[--spacing(5)]', 'w-[--spacing(5)]'], ['bg-[--theme(--color-red-500)]', 'bg-[--theme(--color-red-500)]'], - // Fallback values should be included inside the `var(…)` function - ['bg-[--my-color,red]', 'bg-(--my-color,red)'], - // Fallback values can contain CSS functions - ['bg-[--my-color,theme(spacing.1)]', 'bg-(--my-color,theme(spacing.1))'], - // Some properties never had var() injection in v3. ['[scroll-timeline-name:--myTimeline]', '[scroll-timeline-name:--myTimeline]'], ['[timeline-scope:--myScope]', '[timeline-scope:--myScope]'], diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts index 0ea3bd691..62da826d9 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts @@ -1,9 +1,7 @@ -import { cloneCandidate, type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' +import { walk, WalkAction } from '../../../../tailwindcss/src/ast' +import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import type { Writable } from '../../../../tailwindcss/src/types' -import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { walk, WalkAction } from '../../../../tailwindcss/src/walk' export function migrateAutomaticVarInjection( designSystem: DesignSystem, @@ -13,7 +11,7 @@ export function migrateAutomaticVarInjection( for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { // The below logic makes extended use of mutation. Since candidates in the // DesignSystem are cached, we can't mutate them directly. - let candidate = cloneCandidate(readonlyCandidate) as Writable + let candidate = structuredClone(readonlyCandidate) as Candidate let didChange = false @@ -75,23 +73,9 @@ export function migrateAutomaticVarInjection( function injectVar(value: string): { value: string; didChange: boolean } { let didChange = false - if (value.startsWith('--')) { - // E.g.: - // - // - `--my-color` → `var(--my-color)` Convert variable - // - `--my-color,red` → `var(--my-color,red)` Convert variable with fallback - // - `--theme(color.red)` → `--theme(color.red)` Do not convert functions - // - if ( - // No `(` definitely means there is no function - !value.includes('(') || - // There could be a function call in the fallback value, but it cannot be - // top-level, so we can safely check the first part - ValueParser.parse(value)[0]?.kind !== 'function' - ) { - value = `var(${value})` - didChange = true - } + if (value.startsWith('--') && !value.includes('(')) { + value = `var(${value})` + didChange = true } else if (value.startsWith(' --')) { value = value.slice(1) didChange = true diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts new file mode 100644 index 000000000..df0ca747b --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts @@ -0,0 +1,22 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { migrateBgGradient } from './migrate-bg-gradient' + +test.each([ + ['bg-gradient-to-t', 'bg-linear-to-t'], + ['bg-gradient-to-tr', 'bg-linear-to-tr'], + ['bg-gradient-to-r', 'bg-linear-to-r'], + ['bg-gradient-to-br', 'bg-linear-to-br'], + ['bg-gradient-to-b', 'bg-linear-to-b'], + ['bg-gradient-to-bl', 'bg-linear-to-bl'], + ['bg-gradient-to-l', 'bg-linear-to-l'], + ['bg-gradient-to-tl', 'bg-linear-to-tl'], + + ['max-lg:hover:bg-gradient-to-t', 'max-lg:hover:bg-linear-to-t'], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect(migrateBgGradient(designSystem, {}, candidate)).toEqual(result) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts new file mode 100644 index 000000000..ca81d4f4d --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts @@ -0,0 +1,26 @@ +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' + +const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl'] + +export function migrateBgGradient( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + for (let candidate of designSystem.parseCandidate(rawCandidate)) { + if (candidate.kind === 'static' && candidate.root.startsWith('bg-gradient-to-')) { + let direction = candidate.root.slice(15) + + if (!DIRECTIONS.includes(direction)) { + continue + } + + return designSystem.printCandidate({ + ...candidate, + root: `bg-linear-to-${direction}`, + }) + } + } + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts deleted file mode 100644 index ed5be23c0..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -test.each([ - ['text-superRed', 'text-super-red'], - ['text-red/superOpaque', 'text-red/super-opaque'], - ['text-superRed/superOpaque', 'text-super-red/super-opaque'], - - // Should not migrate named values in modifiers - ['group-hover/superGroup:underline', 'group-hover/superGroup:underline'], - - ['hover:text-superRed', 'hover:text-super-red'], - ['hover:text-red/superOpaque', 'hover:text-red/super-opaque'], - ['hover:text-superRed/superOpaque', 'hover:text-super-red/super-opaque'], -])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) - - expect(migrateCamelcaseInNamedValue(designSystem, {}, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts deleted file mode 100644 index 9cfbc7de0..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-camelcase-in-named-value.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { cloneCandidate } from '../../../../tailwindcss/src/candidate' -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import * as version from '../../utils/version' - -// Converts named values to use kebab-case. This is necessary because the -// upgrade tool also renames the theme values to kebab-case, so `text-superRed` -// will have its theme value renamed to `--color-super-red` and thus the utility -// will be renamed to `text-super-red`. -export function migrateCamelcaseInNamedValue( - designSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -): string { - if (!version.isMajor(3)) return rawCandidate - - for (let candidate of designSystem.parseCandidate(rawCandidate)) { - if (candidate.kind !== 'functional') continue - let clone = cloneCandidate(candidate) - let didChange = false - - if ( - candidate.value && - clone.value && - candidate.value.kind === 'named' && - clone.value.kind === 'named' && - candidate.value.value.match(/[A-Z]/) - ) { - clone.value.value = camelToKebab(candidate.value.value) - didChange = true - } - - if ( - candidate.modifier && - clone.modifier && - candidate.modifier.kind === 'named' && - clone.modifier.kind === 'named' && - candidate.modifier.value.match(/[A-Z]/) - ) { - clone.modifier.value = camelToKebab(candidate.modifier.value) - didChange = true - } - - if (didChange) { - return designSystem.printCandidate(clone) - } - } - - return rawCandidate -} - -function camelToKebab(str: string): string { - return str.replace(/([A-Z])/g, '-$1').toLowerCase() -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts deleted file mode 100644 index 57fe80c22..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { __unstable__loadDesignSystem } from '@tailwindcss/node' -import { expect, test, vi } from 'vitest' -import * as versions from '../../utils/version' -import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate' -vi.spyOn(versions, 'isMajor').mockReturnValue(true) - -test.each([ - // Normalize whitespace in arbitrary properties - ['[display:flex]', '[display:flex]'], - ['[display:_flex]', '[display:flex]'], - ['[display:flex_]', '[display:flex]'], - ['[display:_flex_]', '[display:flex]'], - - // Normalize whitespace in `calc` expressions - ['w-[calc(100%-2rem)]', 'w-[calc(100%-2rem)]'], - ['w-[calc(100%_-_2rem)]', 'w-[calc(100%-2rem)]'], - - // Normalize the important modifier - ['!flex', 'flex!'], - ['flex!', 'flex!'], -])('%s => %s', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { - base: __dirname, - }) - - expect(migrateCanonicalizeCandidate(designSystem, {}, candidate)).toEqual(result) -}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts deleted file mode 100644 index 32d126467..000000000 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-canonicalize-candidate.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../../tailwindcss/src/design-system' - -// Canonicalize the value to its minimal form. This will normalize whitespace, -// and print the important modifier `!` in the correct place. -// -// E.g.: -// -// ``` -// [display:_flex_] => [display:flex] -// [display:_flex] => [display:flex] -// [display:flex_] => [display:flex] -// [display:flex] => [display:flex] -// ``` -// -export function migrateCanonicalizeCandidate( - designSystem: DesignSystem, - _userConfig: Config | null, - rawCandidate: string, -) { - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - let canonicalizedCandidate = designSystem.printCandidate(readonlyCandidate) - if (canonicalizedCandidate !== rawCandidate) { - return canonicalizedCandidate - } - } - - return rawCandidate -} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.test.ts new file mode 100644 index 000000000..c0a8bda8f --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.test.ts @@ -0,0 +1,63 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types' + +const css = String.raw + +const designSystems = new DefaultMap((base: string) => { + return new DefaultMap((input: string) => { + return __unstable__loadDesignSystem(input, { base }) + }) +}) + +describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + + // Basic input with minimal design system to keep the tests fast + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + @theme { + --*: initial; + } + ` + + test.each([ + // A color value can be inferred from the value + ['bg-[color:#008cc]', 'bg-[#008cc]'], + + // A position can be inferred from the value + ['bg-[position:123px]', 'bg-[123px]'], + + // A color is the default for `bg-*` + ['bg-(color:--my-value)', 'bg-(--my-value)'], + + // A position is not the default, so the `position` data type is kept + ['bg-(position:--my-value)', 'bg-(position:--my-value)'], + ])(testName, async (candidate, expected) => { + if (strategy === 'with-variant') { + candidate = `focus:${candidate}` + expected = `focus:${expected}` + } else if (strategy === 'important') { + candidate = `${candidate}!` + expected = `${expected}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + expected = `tw:${expected.replaceAll('var(--', 'var(--tw-')}` + } + + let designSystem = await designSystems.get(__dirname).get(input) + + let migrated = migrateDropUnnecessaryDataTypes(designSystem, {}, candidate) + expect(migrated).toEqual(expected) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.ts new file mode 100644 index 000000000..ae32458ef --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-drop-unnecessary-data-types.ts @@ -0,0 +1,30 @@ +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { computeUtilitySignature } from './signatures' + +export function migrateDropUnnecessaryDataTypes( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let signatures = computeUtilitySignature.get(designSystem) + + for (let candidate of designSystem.parseCandidate(rawCandidate)) { + if ( + candidate.kind === 'functional' && + candidate.value?.kind === 'arbitrary' && + candidate.value.dataType !== null + ) { + let replacement = designSystem.printCandidate({ + ...candidate, + value: { ...candidate.value, dataType: null }, + }) + + if (signatures.get(rawCandidate) === signatures.get(replacement)) { + return replacement + } + } + } + + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts new file mode 100644 index 000000000..bc84538aa --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts @@ -0,0 +1,68 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { migrateImportant } from './migrate-important' + +test.each([ + ['!flex', 'flex!'], + ['min-[calc(1000px+12em)]:!flex', 'min-[calc(1000px+12em)]:flex!'], + ['md:!block', 'md:block!'], + + // Does not change non-important candidates + ['bg-blue-500', 'bg-blue-500'], + ['min-[calc(1000px+12em)]:flex', 'min-[calc(1000px+12em)]:flex'], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect( + migrateImportant(designSystem, {}, candidate, { + contents: `"${candidate}"`, + start: 1, + end: candidate.length + 1, + }), + ).toEqual(result) +}) + +test('does not match false positives', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + expect( + migrateImportant(designSystem, {}, '!border', { + contents: `let notBorder = !border\n`, + start: 16, + end: 16 + '!border'.length, + }), + ).toEqual('!border') +}) + +test('does not replace classes in invalid positions', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + function shouldNotReplace(example: string, candidate = '!border') { + expect( + migrateImportant(designSystem, {}, candidate, { + contents: example, + start: example.indexOf(candidate), + end: example.indexOf(candidate) + candidate.length, + }), + ).toEqual(candidate) + } + + shouldNotReplace(`let notBorder = !border \n`) + shouldNotReplace(`{ "foo": !border.something + ""}\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) + shouldNotReplace(`
\n`) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts new file mode 100644 index 000000000..27a663f47 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts @@ -0,0 +1,48 @@ +import { parseCandidate } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { isSafeMigration } from './is-safe-migration' + +// In v3 the important modifier `!` sits in front of the utility itself, not +// before any of the variants. In v4, we want it to be at the end of the utility +// so that it's always in the same location regardless of whether you used +// variants or not. +// +// So this: +// +// !flex md:!block +// +// Should turn into: +// +// flex! md:block! +export function migrateImportant( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, +): string { + nextCandidate: for (let candidate of parseCandidate(rawCandidate, designSystem)) { + if (candidate.important && candidate.raw[candidate.raw.length - 1] !== '!') { + // The important migration is one of the most broad migrations with a high + // potential of matching false positives since `!` is a valid character in + // most programming languages. Since v4 is technically backward compatible + // with v3 in that it can read `!` in the front of the utility too, we err + // on the side of caution and only migrate candidates that we are certain + // are inside of a string. + if (location && !isSafeMigration(location)) { + continue nextCandidate + } + + // The printCandidate function will already put the exclamation mark in + // the right place, so we just need to mark this candidate as requiring a + // migration. + return designSystem.printCandidate(candidate) + } + } + + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts index 2bd7d2fd5..89afbb21e 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts @@ -1,4 +1,4 @@ -import { cloneCandidate, parseCandidate } from '../../../../tailwindcss/src/candidate' +import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' @@ -9,7 +9,7 @@ export function migrateLegacyArbitraryValues( rawCandidate: string, ): string { for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = cloneCandidate(candidate) + let clone = structuredClone(candidate) let changed = false // Convert commas to spaces. E.g.: [auto,1fr] to [auto_1fr] diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts index 5b204386f..1dbe90426 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts @@ -27,8 +27,6 @@ test.each([ ['ring', 'ring-3'], - ['outline', 'outline-solid'], - ['blur!', 'blur-sm!'], ['hover:blur', 'hover:blur-sm'], ['hover:blur!', 'hover:blur-sm!'], @@ -43,3 +41,38 @@ test.each([ expect(await migrateLegacyClasses(designSystem, {}, candidate)).toEqual(result) }) + +test('does not replace classes in invalid positions', async () => { + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) + + async function shouldNotReplace(example: string, candidate = 'shadow') { + expect( + await migrateLegacyClasses(designSystem, {}, candidate, { + contents: example, + start: example.indexOf(candidate), + end: example.indexOf(candidate) + candidate.length, + }), + ).toEqual(candidate) + } + + await shouldNotReplace(`let notShadow = shadow \n`) + await shouldNotReplace(`{ "foo": shadow.something + ""}\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + await shouldNotReplace(`
\n`) + + // Next.js Image placeholder cases + await shouldNotReplace(``, 'blur') + await shouldNotReplace(``, 'blur') + await shouldNotReplace(``, 'blur') +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts index 2b088aa2e..234907e8a 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts @@ -1,12 +1,12 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import path from 'node:path' import url from 'node:url' -import { cloneCandidate, type Candidate } from '../../../../tailwindcss/src/candidate' +import type { Candidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import * as version from '../../utils/version' -import { baseCandidate } from './candidates' +import { isSafeMigration } from './is-safe-migration' const __filename = url.fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -33,8 +33,6 @@ const LEGACY_CLASS_MAP = new Map([ ['backdrop-blur-sm', 'backdrop-blur-xs'], ['ring', 'ring-3'], - - ['outline', 'outline-solid'], ]) const THEME_KEYS = new Map([ @@ -59,7 +57,7 @@ const THEME_KEYS = new Map([ ['backdrop-blur-sm', '--backdrop-blur-sm'], ['backdrop-blur-xs', '--backdrop-blur-xs'], - ['ring', '--default-ring-width'], + ['ring', '--ring-width'], ['ring-3', '--ring-width-3'], ]) @@ -71,6 +69,11 @@ export async function migrateLegacyClasses( designSystem: DesignSystem, _userConfig: Config | null, rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, ): Promise { // These migrations are only safe when migrating from v3 to v4. // @@ -87,8 +90,10 @@ export async function migrateLegacyClasses( for (let candidate of designSystem.parseCandidate(rawCandidate)) { // Create a base candidate string from the candidate. // E.g.: `hover:blur!` -> `blur` - let base = baseCandidate(candidate) - let baseCandidateString = designSystem.printCandidate(base) + let baseCandidate = structuredClone(candidate) as Candidate + baseCandidate.variants = [] + baseCandidate.important = false + let baseCandidateString = designSystem.printCandidate(baseCandidate) // Find the new base candidate string. `blur` -> `blur-sm` let newBaseCandidateString = LEGACY_CLASS_MAP.get(baseCandidateString) @@ -100,11 +105,12 @@ export async function migrateLegacyClasses( // Re-apply the variants and important flag from the original candidate. // E.g.: `hover:blur!` -> `blur` -> `blur-sm` -> `hover:blur-sm!` - let newCandidate = cloneCandidate(newBaseCandidate) as Candidate + let newCandidate = structuredClone(newBaseCandidate) as Candidate newCandidate.variants = candidate.variants newCandidate.important = candidate.important yield [ + candidate, newCandidate, THEME_KEYS.get(baseCandidateString), THEME_KEYS.get(newBaseCandidateString), @@ -112,7 +118,34 @@ export async function migrateLegacyClasses( } } - for (let [toCandidate, fromThemeKey, toThemeKey] of migrate(rawCandidate)) { + for (let [fromCandidate, toCandidate, fromThemeKey, toThemeKey] of migrate(rawCandidate)) { + // Every utility that has a simple representation (e.g.: `blur`, `radius`, + // etc.`) without variants or special characters _could_ be a potential + // problem during the migration. + let isPotentialProblematicClass = (() => { + if (fromCandidate.variants.length > 0) { + return false + } + + if (fromCandidate.kind === 'arbitrary') { + return false + } + + if (fromCandidate.kind === 'static') { + return !fromCandidate.root.includes('-') + } + + if (fromCandidate.kind === 'functional') { + return fromCandidate.value === null || !fromCandidate.root.includes('-') + } + + return false + })() + + if (location && isPotentialProblematicClass && !isSafeMigration(location)) { + continue + } + if (fromThemeKey && toThemeKey) { // Migrating something that resolves to a value in the theme. let customFrom = designSystem.resolveThemeValue(fromThemeKey, true) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts index b1887ae0b..a4fda26ea 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts @@ -9,17 +9,12 @@ import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-v import { migratePrefix } from './migrate-prefix' vi.spyOn(versions, 'isMajor').mockReturnValue(true) -const css = String.raw - function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawCandidate: string) { for (let migration of [ migrateEmptyArbitraryValues, migratePrefix, migrateModernizeArbitraryValues, migrateArbitraryVariants, - (designSystem: DesignSystem, _: UserConfig | null, rawCandidate: string) => { - return designSystem.canonicalizeCandidates([rawCandidate]).pop() ?? rawCandidate - }, ]) { rawCandidate = migration(designSystem, userConfig, rawCandidate) } @@ -27,6 +22,29 @@ function migrate(designSystem: DesignSystem, userConfig: UserConfig | null, rawC } test.each([ + // Arbitrary variants + ['[[data-visible]]:flex', 'data-visible:flex'], + ['[&[data-visible]]:flex', 'data-visible:flex'], + ['[[data-visible]&]:flex', 'data-visible:flex'], + ['[&>[data-visible]]:flex', '*:data-visible:flex'], + ['[&_>_[data-visible]]:flex', '*:data-visible:flex'], + ['[&>*]:flex', '*:flex'], + ['[&_>_*]:flex', '*:flex'], + + ['[&_[data-visible]]:flex', '**:data-visible:flex'], + ['[&_*]:flex', '**:flex'], + + ['[&:first-child]:flex', 'first:flex'], + ['[&:not(:first-child)]:flex', 'not-first:flex'], + + // in-* variants + ['[p_&]:flex', 'in-[p]:flex'], + ['[.foo_&]:flex', 'in-[.foo]:flex'], + ['[[data-visible]_&]:flex', 'in-data-visible:flex'], + // Multiple selectors, should stay as-is + ['[[data-foo][data-bar]_&]:flex', '[[data-foo][data-bar]_&]:flex'], + // Using `>` instead of ` ` should not be transformed: + ['[figure>&]:my-0', '[figure>&]:my-0'], // Some extreme examples of what happens in the wild: ['group-[]:flex', 'in-[.group]:flex'], ['group-[]/name:flex', 'in-[.group\\/name]:flex'], @@ -41,16 +59,75 @@ test.each([ ['has-group-[]/name:flex', 'has-in-[.group\\/name]:flex'], ['not-group-[]:flex', 'not-in-[.group]:flex'], ['not-group-[]/name:flex', 'not-in-[.group\\/name]:flex'], + + // nth-child + ['[&:nth-child(2)]:flex', 'nth-2:flex'], + ['[&:not(:nth-child(2))]:flex', 'not-nth-2:flex'], + + ['[&:nth-child(-n+3)]:flex', 'nth-[-n+3]:flex'], + ['[&:not(:nth-child(-n+3))]:flex', 'not-nth-[-n+3]:flex'], + ['[&:nth-child(-n_+_3)]:flex', 'nth-[-n+3]:flex'], + ['[&:not(:nth-child(-n_+_3))]:flex', 'not-nth-[-n+3]:flex'], + + // nth-last-child + ['[&:nth-last-child(2)]:flex', 'nth-last-2:flex'], + ['[&:not(:nth-last-child(2))]:flex', 'not-nth-last-2:flex'], + + ['[&:nth-last-child(-n+3)]:flex', 'nth-last-[-n+3]:flex'], + ['[&:not(:nth-last-child(-n+3))]:flex', 'not-nth-last-[-n+3]:flex'], + ['[&:nth-last-child(-n_+_3)]:flex', 'nth-last-[-n+3]:flex'], + ['[&:not(:nth-last-child(-n_+_3))]:flex', 'not-nth-last-[-n+3]:flex'], + + // nth-child odd/even + ['[&:nth-child(odd)]:flex', 'odd:flex'], + ['[&:not(:nth-child(odd))]:flex', 'even:flex'], + ['[&:nth-child(even)]:flex', 'even:flex'], + ['[&:not(:nth-child(even))]:flex', 'odd:flex'], + + // Keep multiple attribute selectors as-is + ['[[data-visible][data-dark]]:flex', '[[data-visible][data-dark]]:flex'], + + // Keep `:where(…)` as is + ['[:where([data-visible])]:flex', '[:where([data-visible])]:flex'], + + // Complex attribute selectors with operators, quotes and insensitivity flags + ['[[data-url*="example"]]:flex', 'data-[url*="example"]:flex'], + ['[[data-url$=".com"_i]]:flex', 'data-[url$=".com"_i]:flex'], + ['[[data-url$=.com_i]]:flex', 'data-[url$=.com_i]:flex'], + + // Attribute selector wrapped in `&:is(…)` + ['[&:is([data-visible])]:flex', 'data-visible:flex'], + + // Media queries + ['[@media(pointer:fine)]:flex', 'pointer-fine:flex'], + ['[@media_(pointer_:_fine)]:flex', 'pointer-fine:flex'], + ['[@media_not_(pointer_:_fine)]:flex', 'not-pointer-fine:flex'], + ['[@media_print]:flex', 'print:flex'], + ['[@media_not_print]:flex', 'not-print:flex'], + + // Hoist the `:not` part to a compound variant + ['[@media_not_(prefers-color-scheme:dark)]:flex', 'not-dark:flex'], + [ + '[@media_not_(prefers-color-scheme:unknown)]:flex', + 'not-[@media_(prefers-color-scheme:unknown)]:flex', + ], + + // Compound arbitrary variants + ['has-[[data-visible]]:flex', 'has-data-visible:flex'], + ['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'], + ['has-[&>[data-visible]]:flex', 'has-[&>[data-visible]]:flex'], + + ['has-[[data-slot=description]]:flex', 'has-data-[slot=description]:flex'], + ['has-[&:is([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], + + ['has-[[aria-visible="true"]]:flex', 'has-aria-visible:flex'], + ['has-[[aria-visible]]:flex', 'has-aria-[visible]:flex'], + + ['has-[&:not(:nth-child(even))]:flex', 'has-odd:flex'], ])('%s => %s (%#)', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - `, - { base: __dirname }, - ) + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { + base: __dirname, + }) expect(migrate(designSystem, {}, candidate)).toEqual(result) }) @@ -77,15 +154,9 @@ test.each([ ['not-group-[]:tw-flex', 'tw:not-in-[.tw\\:group]:flex'], ['not-group-[]/name:tw-flex', 'tw:not-in-[.tw\\:group\\/name]:flex'], ])('%s => %s (%#)', async (candidate, result) => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss' prefix(tw); - @theme { - --*: initial; - } - `, - { base: __dirname }, - ) + let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss" prefix(tw);', { + base: __dirname, + }) expect(migrate(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts index 91718b323..8c75587cd 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts @@ -1,19 +1,25 @@ -import { cloneCandidate, parseCandidate } from '../../../../tailwindcss/src/candidate' +import SelectorParser from 'postcss-selector-parser' +import { parseCandidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { replaceObject } from '../../../../tailwindcss/src/utils/replace-object' +import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' +import { replaceObject } from '../../utils/replace-object' import { walkVariants } from '../../utils/walk-variants' +import { computeVariantSignature } from './signatures' export function migrateModernizeArbitraryValues( designSystem: DesignSystem, _userConfig: Config | null, rawCandidate: string, ): string { + let signatures = computeVariantSignature.get(designSystem) + for (let candidate of parseCandidate(rawCandidate, designSystem)) { - let clone = cloneCandidate(candidate) + let clone = structuredClone(candidate) let changed = false - for (let [variant] of walkVariants(clone)) { + for (let [variant, parent] of walkVariants(clone)) { // Forward modifier from the root to the compound variant if ( variant.kind === 'compound' && @@ -63,6 +69,324 @@ export function migrateModernizeArbitraryValues( } continue } + + // Expecting an arbitrary variant + if (variant.kind === 'arbitrary') { + // Expecting a non-relative arbitrary variant + if (variant.relative) continue + + let ast = SelectorParser().astSync(variant.selector) + + // Expecting a single selector node + if (ast.nodes.length !== 1) continue + + // `[&>*]` can be replaced with `*` + if ( + // Only top-level, so `has-[&>*]` is not supported + parent === null && + // [&_>_*]:flex + // ^ ^ ^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === '>' && + ast.nodes[0].nodes[2].type === 'universal' + ) { + changed = true + replaceObject(variant, designSystem.parseVariant('*')) + continue + } + + // `[&_*]` can be replaced with `**` + if ( + // Only top-level, so `has-[&_*]` is not supported + parent === null && + // [&_*]:flex + // ^ ^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'universal' + ) { + changed = true + replaceObject(variant, designSystem.parseVariant('**')) + continue + } + + // `in-*` variant. If the selector ends with ` &`, we can convert it to an + // `in-*` variant. + // + // E.g.: `[[data-visible]_&]` => `in-data-visible` + if ( + // Only top-level, so `in-[&_[data-visible]]` is not supported + parent === null && + // [[data-visible]___&]:flex + // ^^^^^^^^^^^^^^ ^ ^ + ast.nodes[0].nodes.length === 3 && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'nesting' + ) { + ast.nodes[0].nodes.pop() // Remove the nesting node + ast.nodes[0].nodes.pop() // Remove the combinator + + changed = true + // When handling a compound like `in-[[data-visible]]`, we will first + // handle `[[data-visible]]`, then the parent `in-*` part. This means + // that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`. + // + // Later this gets converted to `in-data-visible`. + replaceObject(variant, designSystem.parseVariant(`in-[${ast.toString()}]`)) + continue + } + + // Hoist `not` modifier for `@media` or `@supports` variants + // + // E.g.: `[@media_not_(scripting:none)]:` -> `not-[@media_(scripting:none)]:` + if ( + // Only top-level, so something like `in-[@media(scripting:none)]` + // (which is not valid anyway) is not supported + parent === null && + // [@media_not(scripting:none)]:flex + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ast.nodes[0].nodes[0].type === 'tag' && + (ast.nodes[0].nodes[0].value.startsWith('@media') || + ast.nodes[0].nodes[0].value.startsWith('@supports')) + ) { + let targetSignature = signatures.get(designSystem.printVariant(variant)) + let parsed = ValueParser.parse(ast.nodes[0].toString().trim()) + let containsNot = false + ValueParser.walk(parsed, (node, { replaceWith }) => { + if (node.kind === 'word' && node.value === 'not') { + containsNot = true + replaceWith([]) + } + }) + + // Remove unnecessary whitespace + parsed = ValueParser.parse(ValueParser.toCss(parsed)) + ValueParser.walk(parsed, (node) => { + if (node.kind === 'separator' && node.value !== ' ' && node.value.trim() === '') { + // node.value contains at least 2 spaces. Normalize it to a single + // space. + node.value = ' ' + } + }) + + if (containsNot) { + let hoistedNot = designSystem.parseVariant(`not-[${ValueParser.toCss(parsed)}]`) + if (hoistedNot === null) continue + let hoistedNotSignature = signatures.get(designSystem.printVariant(hoistedNot)) + if (targetSignature === hoistedNotSignature) { + changed = true + replaceObject(variant, hoistedNot) + continue + } + } + } + + let prefixedVariant: Variant | null = null + + // Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible` + if ( + // Only top-level, so `has-[&>[data-visible]]` is not supported + parent === null && + // [&_>_[data-visible]]:flex + // ^ ^ ^^^^^^^^^^^^^^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === '>' && + ast.nodes[0].nodes[2].type === 'attribute' + ) { + ast.nodes[0].nodes = [ast.nodes[0].nodes[2]] + prefixedVariant = designSystem.parseVariant('*') + } + + // Handling a grand child combinator. E.g.: `[&_[data-visible]]` => `**:data-visible` + if ( + // Only top-level, so `has-[&_[data-visible]]` is not supported + parent === null && + // [&_[data-visible]]:flex + // ^ ^^^^^^^^^^^^^^ + ast.nodes[0].length === 3 && + ast.nodes[0].nodes[0].type === 'nesting' && + ast.nodes[0].nodes[0].value === '&' && + ast.nodes[0].nodes[1].type === 'combinator' && + ast.nodes[0].nodes[1].value === ' ' && + ast.nodes[0].nodes[2].type === 'attribute' + ) { + ast.nodes[0].nodes = [ast.nodes[0].nodes[2]] + prefixedVariant = designSystem.parseVariant('**') + } + + // Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]` + let selectorNodes = ast.nodes[0].filter((node) => node.type !== 'nesting') + + // Expecting a single selector (normal selector or attribute selector) + if (selectorNodes.length !== 1) continue + + let target = selectorNodes[0] + if (target.type === 'pseudo' && target.value === ':is') { + // Expecting a single selector node + if (target.nodes.length !== 1) continue + + // Expecting a single attribute selector + if (target.nodes[0].nodes.length !== 1) continue + + // Unwrap the selector from inside `&:is(…)` + target = target.nodes[0].nodes[0] + } + + // Expecting a pseudo selector + if (target.type === 'pseudo') { + let targetNode = target + let compoundNot = false + if (target.value === ':not') { + compoundNot = true + if (target.nodes.length !== 1) continue + if (target.nodes[0].type !== 'selector') continue + if (target.nodes[0].nodes.length !== 1) continue + if (target.nodes[0].nodes[0].type !== 'pseudo') continue + + targetNode = target.nodes[0].nodes[0] + } + + let newVariant = ((value) => { + if ( + value === ':nth-child' && + targetNode.nodes.length === 1 && + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + targetNode.nodes[0].nodes[0].value === 'odd' + ) { + if (compoundNot) { + compoundNot = false + return 'even' + } + return 'odd' + } + if ( + value === ':nth-child' && + targetNode.nodes.length === 1 && + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + targetNode.nodes[0].nodes[0].value === 'even' + ) { + if (compoundNot) { + compoundNot = false + return 'odd' + } + return 'even' + } + + for (let [selector, variantName] of [ + [':nth-child', 'nth'], + [':nth-last-child', 'nth-last'], + [':nth-of-type', 'nth-of-type'], + [':nth-last-of-type', 'nth-of-last-type'], + ]) { + if (value === selector && targetNode.nodes.length === 1) { + if ( + targetNode.nodes[0].nodes.length === 1 && + targetNode.nodes[0].nodes[0].type === 'tag' && + isPositiveInteger(targetNode.nodes[0].nodes[0].value) + ) { + return `${variantName}-${targetNode.nodes[0].nodes[0].value}` + } + + return `${variantName}-[${targetNode.nodes[0].toString()}]` + } + } + + // Hoist `not` modifier + if (compoundNot) { + let targetSignature = signatures.get(designSystem.printVariant(variant)) + let replacementSignature = signatures.get(`not-[${value}]`) + if (targetSignature === replacementSignature) { + return `[&${value}]` + } + } + + return null + })(targetNode.value) + + if (newVariant === null) continue + + // Add `not-` prefix + if (compoundNot) newVariant = `not-${newVariant}` + + let parsed = designSystem.parseVariant(newVariant) + if (parsed === null) continue + + // Update original variant + changed = true + replaceObject(variant, structuredClone(parsed)) + } + + // Expecting an attribute selector + else if (target.type === 'attribute') { + // Attribute selectors + let attributeKey = target.attribute + let attributeValue = target.value + ? target.quoted + ? `${target.quoteMark}${target.value}${target.quoteMark}` + : target.value + : null + + // Insensitive attribute selectors. E.g.: `[data-foo="value" i]` + // ^ + if (target.insensitive && attributeValue) { + attributeValue += ' i' + } + + let operator = target.operator ?? '=' + + // Migrate `data-*` + if (attributeKey.startsWith('data-')) { + changed = true + attributeKey = attributeKey.slice(5) // Remove `data-` + replaceObject(variant, { + kind: 'functional', + root: 'data', + modifier: null, + value: + attributeValue === null + ? { kind: 'named', value: attributeKey } + : { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` }, + } satisfies Variant) + } + + // Migrate `aria-*` + else if (attributeKey.startsWith('aria-')) { + changed = true + attributeKey = attributeKey.slice(5) // Remove `aria-` + replaceObject(variant, { + kind: 'functional', + root: 'aria', + modifier: null, + value: + attributeValue === null + ? { kind: 'arbitrary', value: attributeKey } // aria-[foo] + : operator === '=' && target.value === 'true' && !target.insensitive + ? { kind: 'named', value: attributeKey } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true] + : { kind: 'arbitrary', value: `${attributeKey}${operator}${attributeValue}` }, // aria-[foo~="true"], aria-[foo|="true"], … + } satisfies Variant) + } + } + + if (prefixedVariant) { + let idx = clone.variants.indexOf(variant) + if (idx === -1) continue + + // Ensure we inject the prefixed variant + clone.variants.splice(idx, 1, variant, prefixedVariant) + } + } } return changed ? designSystem.printCandidate(clone) : rawCandidate diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.test.ts new file mode 100644 index 000000000..d4a6e1bd4 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.test.ts @@ -0,0 +1,65 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { describe, expect, test } from 'vitest' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { migrateOptimizeModifier } from './migrate-optimize-modifier' + +const css = String.raw + +const designSystems = new DefaultMap((base: string) => { + return new DefaultMap((input: string) => { + return __unstable__loadDesignSystem(input, { base }) + }) +}) + +describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { + let testName = '%s => %s (%#)' + if (strategy === 'with-variant') { + testName = testName.replaceAll('%s', 'focus:%s') + } else if (strategy === 'important') { + testName = testName.replaceAll('%s', '%s!') + } else if (strategy === 'prefix') { + testName = testName.replaceAll('%s', 'tw:%s') + } + + // Basic input with minimal design system to keep the tests fast + let input = css` + @import 'tailwindcss' ${strategy === 'prefix' ? 'prefix(tw)' : ''}; + @theme { + --*: initial; + --color-red-500: red; + } + ` + + test.each([ + // Keep the modifier as-is, nothing to optimize + ['bg-red-500/25', 'bg-red-500/25'], + + // Use a bare value modifier + ['bg-red-500/[25%]', 'bg-red-500/25'], + + // Drop unnecessary modifiers + ['bg-red-500/[100%]', 'bg-red-500'], + ['bg-red-500/100', 'bg-red-500'], + + // Keep modifiers on classes that don't _really_ exist + ['group/name', 'group/name'], + ])(testName, async (candidate, expected) => { + if (strategy === 'with-variant') { + candidate = `focus:${candidate}` + expected = `focus:${expected}` + } else if (strategy === 'important') { + candidate = `${candidate}!` + expected = `${expected}!` + } else if (strategy === 'prefix') { + // Not only do we need to prefix the candidate, we also have to make + // sure that we prefix all CSS variables. + candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` + expected = `tw:${expected.replaceAll('var(--', 'var(--tw-')}` + } + + let designSystem = await designSystems.get(__dirname).get(input) + + let migrated = migrateOptimizeModifier(designSystem, {}, candidate) + expect(migrated).toEqual(expected) + }) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.ts new file mode 100644 index 000000000..b4d5d58e5 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-optimize-modifier.ts @@ -0,0 +1,63 @@ +import type { NamedUtilityValue } from '../../../../tailwindcss/src/candidate' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import type { Writable } from '../../utils/types' +import { computeUtilitySignature } from './signatures' + +// Optimize the modifier +// +// E.g.: +// +// - `/[25%]` → `/25` +// - `/[100%]` → `/100` → +// - `/100` → +// +export function migrateOptimizeModifier( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let signatures = computeUtilitySignature.get(designSystem) + + for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { + let candidate = structuredClone(readonlyCandidate) as Writable + if ( + (candidate.kind === 'functional' && candidate.modifier !== null) || + (candidate.kind === 'arbitrary' && candidate.modifier !== null) + ) { + let targetSignature = signatures.get(rawCandidate) + let modifier = candidate.modifier + let changed = false + + // 1. Try to drop the modifier entirely + if ( + targetSignature === + signatures.get(designSystem.printCandidate({ ...candidate, modifier: null })) + ) { + changed = true + candidate.modifier = null + } + + // 2. Try to remove the square brackets and the `%` sign + if (!changed) { + let newModifier: NamedUtilityValue = { + kind: 'named', + value: modifier.value.endsWith('%') ? modifier.value.slice(0, -1) : modifier.value, + fraction: null, + } + + if ( + targetSignature === + signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier })) + ) { + changed = true + candidate.modifier = newModifier + } + } + + return changed ? designSystem.printCandidate(candidate) : rawCandidate + } + } + + return rawCandidate +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts index 776032850..3e014817c 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts @@ -1,4 +1,3 @@ -import { decl } from '../../../../tailwindcss/src/ast' import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' @@ -17,18 +16,8 @@ export function migratePrefix( if (!version.isMajor(3)) return rawCandidate if (!seenDesignSystems.has(designSystem)) { - designSystem.utilities.functional('group', (value) => [ - // To ensure that `@apply group` works when computing a signature - decl('--phantom-class', 'group'), - // To ensure `group` and `group/foo` are considered different classes - decl('--phantom-modifier', value.modifier?.value), - ]) - designSystem.utilities.functional('peer', (value) => [ - // To ensure that `@apply peer` works when computing a signature - decl('--phantom-class', 'peer'), - // To ensure `peer` and `peer/foo` are considered different classes - decl('--phantom-modifier', value.modifier?.value), - ]) + designSystem.utilities.functional('group', () => null) + designSystem.utilities.functional('peer', () => null) seenDesignSystems.add(designSystem) } diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts new file mode 100644 index 000000000..e30109429 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts @@ -0,0 +1,195 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' +import { expect, test } from 'vitest' +import { migrateThemeToVar } from './migrate-theme-to-var' + +let css = String.raw + +test.each([ + // Keep candidates that don't contain `theme(…)` or `theme(…, …)` + ['[color:red]', '[color:red]'], + + // Handle special cases around `.1` in the `theme(…)` + ['[--value:theme(spacing.1)]', '[--value:--spacing(1)]'], + ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'], + ['[--value:theme(spacing[1.25])]', '[--value:--spacing(1.25)]'], + + // Should not convert invalid spacing values to calc + ['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'], + + // Convert to `var(…)` if we can resolve the path + ['[color:theme(colors.red.500)]', '[color:var(--color-red-500)]'], // Arbitrary property + ['[color:theme(colors.red.500)]/50', '[color:var(--color-red-500)]/50'], // Arbitrary property + modifier + ['bg-[theme(colors.red.500)]', 'bg-(--color-red-500)'], // Arbitrary value + ['bg-[size:theme(spacing.4)]', 'bg-[size:--spacing(4)]'], // Arbitrary value + data type hint + + // Pretty print CSS functions preceded by an operator to prevent consecutive + // operator characters. + ['w-[calc(100dvh-theme(spacing.2))]', 'w-[calc(100dvh-(--spacing(2)))]'], + ['w-[calc(100dvh+theme(spacing.2))]', 'w-[calc(100dvh+(--spacing(2)))]'], + ['w-[calc(100dvh/theme(spacing.2))]', 'w-[calc(100dvh/(--spacing(2)))]'], + ['w-[calc(100dvh*theme(spacing.2))]', 'w-[calc(100dvh*(--spacing(2)))]'], + + // Convert to `var(…)` if we can resolve the path, but keep fallback values + ['bg-[theme(colors.red.500,red)]', 'bg-(--color-red-500,red)'], + + // Keep `theme(…)` if we can't resolve the path + ['bg-[theme(colors.foo.1000)]', 'bg-[theme(colors.foo.1000)]'], + + // Keep `theme(…)` if we can't resolve the path, but still try to convert the + // fallback value. + [ + 'bg-[theme(colors.foo.1000,theme(colors.red.500))]', + 'bg-[theme(colors.foo.1000,var(--color-red-500))]', + ], + + // Use `theme(…)` (deeply nested) inside of a `calc(…)` function + ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--text-xs)*2)]'], + + // Multiple `theme(… / …)` calls should result in modern syntax of `theme(…)` + // - Can't convert to `var(…)` because that would lose the modifier. + // - Can't convert to a candidate modifier because there are multiple + // `theme(…)` calls. + // + // If we really want to, we can make a fancy migration that tries to move it + // to a candidate modifier _if_ all `theme(…)` calls use the same modifier. + [ + '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]', + '[color:--theme(--color-red-500/50,--theme(--color-blue-500/50))]', + ], + [ + '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]/50', + '[color:--theme(--color-red-500/50,--theme(--color-blue-500/50))]/50', + ], + + // Convert the `theme(…)`, but try to move the inline modifier (e.g. `50%`), + // to a candidate modifier. + // Arbitrary property, with simple percentage modifier + ['[color:theme(colors.red.500/75%)]', '[color:var(--color-red-500)]/75'], + + // Arbitrary property, with numbers (0-1) without a unit + ['[color:theme(colors.red.500/.12)]', '[color:var(--color-red-500)]/12'], + ['[color:theme(colors.red.500/0.12)]', '[color:var(--color-red-500)]/12'], + + // Arbitrary property, with more complex modifier (we only allow whole numbers + // as bare modifiers). Convert the complex numbers to arbitrary values instead. + ['[color:theme(colors.red.500/12.34%)]', '[color:var(--color-red-500)]/[12.34%]'], + ['[color:theme(colors.red.500/var(--opacity))]', '[color:var(--color-red-500)]/(--opacity)'], + ['[color:theme(colors.red.500/.12345)]', '[color:var(--color-red-500)]/[12.345]'], + ['[color:theme(colors.red.500/50.25%)]', '[color:var(--color-red-500)]/[50.25%]'], + + // Arbitrary value + ['bg-[theme(colors.red.500/75%)]', 'bg-(--color-red-500)/75'], + ['bg-[theme(colors.red.500/12.34%)]', 'bg-(--color-red-500)/[12.34%]'], + + // Arbitrary property that already contains a modifier + ['[color:theme(colors.red.500/50%)]/50', '[color:--theme(--color-red-500/50%)]/50'], + + // Values that don't contain only `theme(…)` calls should not be converted to + // use a modifier since the color is not the whole value. + [ + 'shadow-[shadow:inset_0px_1px_theme(colors.white/15%)]', + 'shadow-[shadow:inset_0px_1px_--theme(--color-white/15%)]', + ], + + // Arbitrary value, where the candidate already contains a modifier + // This should still migrate the `theme(…)` syntax to the modern syntax. + ['bg-[theme(colors.red.500/50%)]/50', 'bg-[--theme(--color-red-500/50%)]/50'], + + // Variants, we can't use `var(…)` especially inside of `@media(…)`. We can + // still upgrade the `theme(…)` to the modern syntax. + ['max-[theme(screens.lg)]:flex', 'max-[--theme(--breakpoint-lg)]:flex'], + // There are no variables for `--spacing` multiples, so we can't convert this + ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], + + // This test in itself doesn't make much sense. But we need to make sure + // that this doesn't end up as the modifier in the candidate itself. + ['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'], + + // `theme(…)` calls in another CSS function is replaced correctly. + // Additionally we remove unnecessary whitespace. + ['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,--spacing(80))_auto]'], + + // `theme(…)` calls valid in v3, but not in v4 should still be converted. + ['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'], + + // Renamed theme keys + ['max-w-[theme(screens.md)]', 'max-w-(--breakpoint-md)'], + ['w-[theme(maxWidth.md)]', 'w-(--container-md)'], + + // Invalid cases + ['[--foo:theme(colors.red.500/50/50)]', '[--foo:theme(colors.red.500/50/50)]'], + ['[--foo:theme(colors.red.500/50/50)]/50', '[--foo:theme(colors.red.500/50/50)]/50'], + + // Partially invalid cases + [ + '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]', + '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]', + ], + [ + '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', + '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', + ], +])('%s => %s', async (candidate, result) => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { + base: __dirname, + }, + ) + + expect(migrateThemeToVar(designSystem, {}, candidate)).toEqual(result) +}) + +test('extended space scale converts to var or calc', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @theme { + --spacing-2: 2px; + --spacing-miami: 0.875rem; + } + `, + { + base: __dirname, + }, + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:--spacing(1)]', + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( + '[--value:var(--spacing-miami)]', + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( + '[--value:theme(spacing.nyc)]', + ) +}) + +test('custom space scale converts to var', async () => { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + @theme { + --spacing-*: initial; + --spacing-1: 0.25rem; + --spacing-2: 0.5rem; + } + `, + { + base: __dirname, + }, + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + '[--value:var(--spacing-1)]', + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + '[--value:var(--spacing-2)]', + ) + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual( + '[--value:theme(spacing.3)]', + ) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts index e566e7d6e..6f7a3ece1 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts @@ -1,11 +1,12 @@ -import { type CandidateModifier } from '../../../../tailwindcss/src/candidate' +import { parseCandidate, type CandidateModifier } from '../../../../tailwindcss/src/candidate' import { keyPathToCssProperty } from '../../../../tailwindcss/src/compat/apply-config-to-theme' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path' import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { walk, WalkAction } from '../../../../tailwindcss/src/walk' +import { walkVariants } from '../../utils/walk-variants' export const enum Convert { All = 0, @@ -13,6 +14,68 @@ export const enum Convert { MigrateThemeOnly = 1 << 1, } +export function migrateThemeToVar( + designSystem: DesignSystem, + _userConfig: Config | null, + rawCandidate: string, +): string { + let convert = createConverter(designSystem) + + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + let clone = structuredClone(candidate) + let changed = false + + if (clone.kind === 'arbitrary') { + let [newValue, modifier] = convert( + clone.value, + clone.modifier === null ? Convert.MigrateModifier : Convert.All, + ) + if (newValue !== clone.value) { + changed = true + clone.value = newValue + + if (modifier !== null) { + clone.modifier = modifier + } + } + } else if (clone.kind === 'functional' && clone.value?.kind === 'arbitrary') { + let [newValue, modifier] = convert( + clone.value.value, + clone.modifier === null ? Convert.MigrateModifier : Convert.All, + ) + if (newValue !== clone.value.value) { + changed = true + clone.value.value = newValue + + if (modifier !== null) { + clone.modifier = modifier + } + } + } + + // Handle variants + for (let [variant] of walkVariants(clone)) { + if (variant.kind === 'arbitrary') { + let [newValue] = convert(variant.selector, Convert.MigrateThemeOnly) + if (newValue !== variant.selector) { + changed = true + variant.selector = newValue + } + } else if (variant.kind === 'functional' && variant.value?.kind === 'arbitrary') { + let [newValue] = convert(variant.value.value, Convert.MigrateThemeOnly) + if (newValue !== variant.value.value) { + changed = true + variant.value.value = newValue + } + } + } + + return changed ? designSystem.printCandidate(clone) : rawCandidate + } + + return rawCandidate +} + export function createConverter(designSystem: DesignSystem, { prettyPrint = false } = {}) { function convert(input: string, options = Convert.All): [string, CandidateModifier | null] { let ast = ValueParser.parse(input) @@ -28,7 +91,7 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals let themeModifierCount = 0 // Analyze AST - walk(ast, (node) => { + ValueParser.walk(ast, (node) => { if (node.kind !== 'function') return if (node.value !== 'theme') return @@ -36,19 +99,19 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals themeUsageCount += 1 // Figure out if a modifier is used - walk(node.nodes, (child) => { + ValueParser.walk(node.nodes, (child) => { // If we see a `,`, it means that we have a fallback value if (child.kind === 'separator' && child.value.includes(',')) { - return WalkAction.Stop + return ValueParser.ValueWalkAction.Stop } // If we see a `/`, we have a modifier - else if (child.kind === 'word' && child.value === '/') { + else if (child.kind === 'separator' && child.value.trim() === '/') { themeModifierCount += 1 - return WalkAction.Stop + return ValueParser.ValueWalkAction.Stop } - return WalkAction.Skip + return ValueParser.ValueWalkAction.Skip }) }) @@ -130,10 +193,6 @@ export function createConverter(designSystem: DesignSystem, { prettyPrint = fals let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const if (!designSystem.theme.get([variable])) return null - if (designSystem.theme.prefix) { - return `--${designSystem.theme.prefix}-${variable.slice(2)}` - } - return variable } @@ -173,7 +232,7 @@ function substituteFunctionsInValue( ast: ValueParser.ValueAstNode[], handle: (value: string, fallback?: string) => string | null, ) { - walk(ast, (node, ctx) => { + ValueParser.walk(ast, (node, { parent, replaceWith }) => { if (node.kind === 'function' && node.value === 'theme') { if (node.nodes.length < 1) return @@ -211,10 +270,10 @@ function substituteFunctionsInValue( fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path) if (replacement === null) return - { - let idx = ctx.index - 1 + if (parent) { + let idx = parent.nodes.indexOf(node) - 1 while (idx !== -1) { - let previous = ctx.siblings[idx] + let previous = parent.nodes[idx] // Skip the space separator if (previous.kind === 'separator' && previous.value.trim() === '') { idx -= 1 @@ -242,7 +301,7 @@ function substituteFunctionsInValue( } } - return WalkAction.Replace(ValueParser.parse(replacement)) + replaceWith(ValueParser.parse(replacement)) } }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts index 3fd84af7c..fb5306caa 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts @@ -1,8 +1,7 @@ -import { type AstNode } from '../../../../tailwindcss/src/ast' +import { walk, type AstNode } from '../../../../tailwindcss/src/ast' import { type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { walk } from '../../../../tailwindcss/src/walk' import * as version from '../../utils/version' export function migrateVariantOrder( diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts index 24bb3d5ef..45a5a15a9 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.test.ts @@ -37,7 +37,7 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', ` test.each([ - // Arbitrary property to named functional utility + // Arbitrary property to named functional utlity ['[color:red]', 'text-red-500'], // Promote data types to more specific utility if it exists @@ -45,35 +45,6 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', // Promote inferred data type to more specific utility if it exists ['bg-[123px]', 'bg-position-[123px]'], - - // Do not migrate bare values or arbitrary values to named values that are - // deprecated - ['order-[0]', 'order-0'], - ['order-0', 'order-0'], - - // Migrate deprecated named values to bare values - ['order-none', 'order-0'], - - // `-0` should not be migrated to `0`. - // - // This used to be a bug that translate `mt-[0px]` into `-mt-[0px]` because - // `-mt-[0px]` translates to `margin-top: calc(0px * -1);` and therefore we - // handle the `0px * -1` case which translates to `0px` not `-0px`. - // - // This translation is actually fine, because now, we will prefer the - // non-negative version first so we can replace `-mt-[0px]` with `mt-0`. - ['mt-[0px]', 'mt-0'], - ['-mt-[0px]', 'mt-0'], - - // Shorthand CSS Variables should be converted to the new syntax, even if - // the fallback contains functions. The fallback should also be migrated to - // the newest syntax. - ['bg-[--my-color,theme(colors.red.500)]', 'bg-(--my-color,var(--color-red-500))'], - - // Both the positive and negative versions of arbitrary utilities should be - // converted to the bare value version. - ['mb-[32rem]', 'mb-128'], - ['mb-[-32rem]', '-mb-128'], ])(testName, async (candidate, result) => { if (strategy === 'with-variant') { candidate = `focus:${candidate}` @@ -92,38 +63,4 @@ describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', let migrated = await migrate(designSystem, {}, candidate) expect(migrated).toEqual(result) }) - - test.each([ - ['order-[0]', 'order-0'], - ['order-0', 'order-0'], - - // Do not migrate `order-none` if defined as a custom utility as it is then - // not safe to migrate to `order-0` - ['order-none', 'order-none'], - ])(`${testName} with custom implementations`, async (candidate, result) => { - if (strategy === 'with-variant') { - candidate = `focus:${candidate}` - result = `focus:${result}` - } else if (strategy === 'important') { - candidate = `${candidate}!` - result = `${result}!` - } else if (strategy === 'prefix') { - // Not only do we need to prefix the candidate, we also have to make - // sure that we prefix all CSS variables. - candidate = `tw:${candidate.replaceAll('var(--', 'var(--tw-')}` - result = `tw:${result.replaceAll('var(--', 'var(--tw-')}` - } - - let localInput = css` - ${input} - - @utility order-none { - order: none; /* imagine this exists */ - } - ` - - let designSystem = await designSystems.get(__dirname).get(localInput) - let migrated = await migrate(designSystem, {}, candidate) - expect(migrated).toEqual(result) - }) }) diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts index 976c63a83..029a2a97b 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts @@ -1,75 +1,59 @@ import fs from 'node:fs/promises' import path, { extname } from 'node:path' -import { - createSignatureOptions, - prepareDesignSystemStorage, - UTILITY_SIGNATURE_KEY, -} from '../../../../tailwindcss/src/canonicalize-candidates' +import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' import { spliceChangesIntoString, type StringChange } from '../../utils/splice-changes-into-string' -import { writeFileSafely } from '../../utils/write-file-safely' import { extractRawCandidates } from './candidates' -import { isSafeMigration } from './is-safe-migration' +import { migrateArbitraryUtilities } from './migrate-arbitrary-utilities' +import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' +import { migrateArbitraryVariants } from './migrate-arbitrary-variants' import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' -import { migrateCamelcaseInNamedValue } from './migrate-camelcase-in-named-value' -import { migrateCanonicalizeCandidate } from './migrate-canonicalize-candidate' +import { migrateBgGradient } from './migrate-bg-gradient' +import { migrateDropUnnecessaryDataTypes } from './migrate-drop-unnecessary-data-types' import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' +import { migrateImportant } from './migrate-important' import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values' import { migrateLegacyClasses } from './migrate-legacy-classes' import { migrateMaxWidthScreen } from './migrate-max-width-screen' import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values' +import { migrateOptimizeModifier } from './migrate-optimize-modifier' import { migratePrefix } from './migrate-prefix' import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes' +import { migrateThemeToVar } from './migrate-theme-to-var' import { migrateVariantOrder } from './migrate-variant-order' export type Migration = ( designSystem: DesignSystem, userConfig: Config | null, rawCandidate: string, + location?: { + contents: string + start: number + end: number + }, ) => string | Promise export const DEFAULT_MIGRATIONS: Migration[] = [ migrateEmptyArbitraryValues, migratePrefix, - migrateCanonicalizeCandidate, + migrateImportant, + migrateBgGradient, migrateSimpleLegacyClasses, - migrateCamelcaseInNamedValue, migrateLegacyClasses, migrateMaxWidthScreen, + migrateThemeToVar, migrateVariantOrder, // Has to happen before migrations that modify variants migrateAutomaticVarInjection, migrateLegacyArbitraryValues, + migrateArbitraryUtilities, migrateModernizeArbitraryValues, + migrateArbitraryVariants, + migrateDropUnnecessaryDataTypes, + migrateArbitraryValueToBareValue, + migrateOptimizeModifier, ] -let migrateCached = new DefaultMap((baseDesignSystem: DesignSystem) => { - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - let options = createSignatureOptions(designSystem) - - return new DefaultMap((userConfig: Config | null) => { - return new DefaultMap(async (rawCandidate) => { - let original = rawCandidate - - for (let migration of DEFAULT_MIGRATIONS) { - rawCandidate = await migration(designSystem, userConfig, rawCandidate) - } - - // Canonicalize the final migrated candidate to its final form - rawCandidate = designSystem.canonicalizeCandidates([rawCandidate]).pop()! - - // Verify that the candidate actually makes sense at all. E.g.: `duration` - // is not a valid candidate, but it will parse because `duration-` - // exists. - let signature = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options).get(rawCandidate) - if (typeof signature !== 'string') return original - - return rawCandidate - }) - }) -}) - export async function migrateCandidate( designSystem: DesignSystem, userConfig: Config | null, @@ -81,12 +65,23 @@ export async function migrateCandidate( end: number }, ): Promise { - // Skip this migration if we think that the migration is unsafe - if (location && !isSafeMigration(rawCandidate, location, designSystem)) { - return rawCandidate + let original = rawCandidate + for (let migration of DEFAULT_MIGRATIONS) { + rawCandidate = await migration(designSystem, userConfig, rawCandidate, location) } - return migrateCached.get(designSystem).get(userConfig).get(rawCandidate) + // If nothing changed, let's parse it again and re-print it. This will migrate + // pretty print candidates to the new format. If it did change, we already had + // to re-print it. + // + // E.g.: `bg-red-500/[var(--my-opacity)]` -> `bg-red-500/(--my-opacity)` + if (rawCandidate === original) { + for (let candidate of parseCandidate(rawCandidate, designSystem)) { + return designSystem.printCandidate(candidate) + } + } + + return rawCandidate } export default async function migrateContents( @@ -120,18 +115,12 @@ export default async function migrateContents( return spliceChangesIntoString(contents, changes) } -export async function migrate( - designSystem: DesignSystem, - userConfig: Config | null, - file: string, -): Promise { +export async function migrate(designSystem: DesignSystem, userConfig: Config | null, file: string) { let fullPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file) let contents = await fs.readFile(fullPath, 'utf-8') - let migrated = await migrateContents(designSystem, userConfig, contents, extname(file)) - if (migrated === contents) return false // Nothing changed - if (migrated.trim() === '') return false // Emptied out, something went horribly wrong - - await writeFileSafely(fullPath, migrated) - return true + await fs.writeFile( + fullPath, + await migrateContents(designSystem, userConfig, contents, extname(file)), + ) } diff --git a/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts new file mode 100644 index 000000000..f15cf35d4 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/template/signatures.ts @@ -0,0 +1,404 @@ +import { substituteAtApply } from '../../../../tailwindcss/src/apply' +import { atRule, styleRule, toCss, walk, type AstNode } from '../../../../tailwindcss/src/ast' +import { printArbitraryValue } from '../../../../tailwindcss/src/candidate' +import * as SelectorParser from '../../../../tailwindcss/src/compat/selector-parser' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { ThemeOptions } from '../../../../tailwindcss/src/theme' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' +import { dimensions } from '../../utils/dimension' + +// Given a utility, compute a signature that represents the utility. The +// signature will be a normalised form of the generated CSS for the utility, or +// a unique symbol if the utility is not valid. The class in the selector will +// be replaced with the `.x` selector. +// +// This function should only be passed the base utility so `flex`, `hover:flex` +// and `focus:flex` will all use just `flex`. Variants are handled separately. +// +// E.g.: +// +// | UTILITY | GENERATED SIGNATURE | +// | ---------------- | ----------------------- | +// | `[display:flex]` | `.x { display: flex; }` | +// | `flex` | `.x { display: flex; }` | +// +// These produce the same signature, therefore they represent the same utility. +export const computeUtilitySignature = new DefaultMap< + DesignSystem, + DefaultMap +>((designSystem) => { + return new DefaultMap((utility) => { + try { + // Ensure the prefix is added to the utility if it is not already present. + utility = + designSystem.theme.prefix && !utility.startsWith(designSystem.theme.prefix) + ? `${designSystem.theme.prefix}:${utility}` + : utility + + // Use `@apply` to normalize the selector to `.x` + let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])] + + temporarilyDisableThemeInline(designSystem, () => substituteAtApply(ast, designSystem)) + + // We will be mutating the AST, so we need to clone it first to not affect + // the original AST + ast = structuredClone(ast) + + // Optimize the AST. This is needed such that any internal intermediate + // nodes are gone. This will also cleanup declaration nodes with undefined + // values or `--tw-sort` declarations. + walk(ast, (node, { replaceWith }) => { + // Optimize declarations + if (node.kind === 'declaration') { + if (node.value === undefined || node.property === '--tw-sort') { + replaceWith([]) + } + } + + // Replace special nodes with its children + else if (node.kind === 'context' || node.kind === 'at-root') { + replaceWith(node.nodes) + } + + // Remove comments + else if (node.kind === 'comment') { + replaceWith([]) + } + }) + + // Resolve theme values to their inlined value. + // + // E.g.: + // + // `[color:var(--color-red-500)]` → `[color:oklch(63.7%_0.237_25.331)]` + // `[color:oklch(63.7%_0.237_25.331)]` → `[color:oklch(63.7%_0.237_25.331)]` + // + // Due to the `@apply` from above, this will become: + // + // ```css + // .example { + // color: oklch(63.7% 0.237 25.331); + // } + // ``` + // + // Which conveniently will be equivalent to: `text-red-500` when we inline + // the value. + // + // Without inlining: + // ```css + // .example { + // color: var(--color-red-500, oklch(63.7% 0.237 25.331)); + // } + // ``` + // + // Inlined: + // ```css + // .example { + // color: oklch(63.7% 0.237 25.331); + // } + // ``` + // + // Recently we made sure that utilities like `text-red-500` also generate + // the fallback value for usage in `@reference` mode. + // + // The second assumption is that if you use `var(--key, fallback)` that + // happens to match a known variable _and_ its inlined value. Then we can + // replace it with the inlined variable. This allows us to handle custom + // `@theme` and `@theme inline` definitions. + walk(ast, (node) => { + // Handle declarations + if (node.kind === 'declaration' && node.value !== undefined) { + if (node.value.includes('var(')) { + let valueAst = ValueParser.parse(node.value) + + let seen = new Set() + ValueParser.walk(valueAst, (valueNode, { replaceWith }) => { + if (valueNode.kind !== 'function') return + if (valueNode.value !== 'var') return + + // Resolve the underlying value of the variable + if (valueNode.nodes.length !== 1 && valueNode.nodes.length < 3) { + return + } + + let variable = valueNode.nodes[0].value + + // Drop the prefix from the variable name if it is present. The + // internal variable doesn't have the prefix. + if ( + designSystem.theme.prefix && + variable.startsWith(`--${designSystem.theme.prefix}-`) + ) { + variable = variable.slice(`--${designSystem.theme.prefix}-`.length) + } + let variableValue = designSystem.resolveThemeValue(variable) + // Prevent infinite recursion when the variable value contains the + // variable itself. + if (seen.has(variable)) return + seen.add(variable) + if (variableValue === undefined) return // Couldn't resolve the variable + + // Inject variable fallbacks when no fallback is present yet. + // + // A fallback could consist of multiple values. + // + // E.g.: + // + // ``` + // var(--font-sans, ui-sans-serif, system-ui, sans-serif, …) + // ``` + { + // More than 1 argument means that a fallback is already present + if (valueNode.nodes.length === 1) { + // Inject the fallback value into the variable lookup + valueNode.nodes.push(...ValueParser.parse(`,${variableValue}`)) + } + } + + // Replace known variable + inlined fallback value with the value + // itself again + { + // We need at least 3 arguments. The variable, the separator and a fallback value. + if (valueNode.nodes.length >= 3) { + let nodeAsString = ValueParser.toCss(valueNode.nodes) // This could include more than just the variable + let constructedValue = `${valueNode.nodes[0].value},${variableValue}` + if (nodeAsString === constructedValue) { + replaceWith(ValueParser.parse(variableValue)) + } + } + } + }) + + // Replace the value with the new value + node.value = ValueParser.toCss(valueAst) + } + + // Very basic `calc(…)` constant folding to handle the spacing scale + // multiplier: + // + // Input: `--spacing(4)` + // → `calc(var(--spacing, 0.25rem) * 4)` + // → `calc(0.25rem * 4)` ← this is the case we will see + // after inlining the variable + // → `1rem` + if (node.value.includes('calc')) { + let folded = false + let valueAst = ValueParser.parse(node.value) + ValueParser.walk(valueAst, (valueNode, { replaceWith }) => { + if (valueNode.kind !== 'function') return + if (valueNode.value !== 'calc') return + + // [ + // { kind: 'word', value: '0.25rem' }, 0 + // { kind: 'separator', value: ' ' }, 1 + // { kind: 'word', value: '*' }, 2 + // { kind: 'separator', value: ' ' }, 3 + // { kind: 'word', value: '256' } 4 + // ] + if (valueNode.nodes.length !== 5) return + if (valueNode.nodes[2].kind !== 'word' && valueNode.nodes[2].value !== '*') return + + let parsed = dimensions.get(valueNode.nodes[0].value) + if (parsed === null) return + + let [value, unit] = parsed + + let multiplier = Number(valueNode.nodes[4].value) + if (Number.isNaN(multiplier)) return + + folded = true + replaceWith(ValueParser.parse(`${value * multiplier}${unit}`)) + }) + + if (folded) { + node.value = ValueParser.toCss(valueAst) + } + } + + // We will normalize the `node.value`, this is the same kind of logic + // we use when printing arbitrary values. It will remove unnecessary + // whitespace. + // + // Essentially normalizing the `node.value` to a canonical form. + node.value = printArbitraryValue(node.value) + } + }) + + // Compute the final signature, by generating the CSS for the utility + let signature = toCss(ast) + return signature + } catch { + // A unique symbol is returned to ensure that 2 signatures resulting in + // `null` are not considered equal. + return Symbol() + } + }) +}) + +// Given a variant, compute a signature that represents the variant. The +// signature will be a normalised form of the generated CSS for the variant, or +// a unique symbol if the variant is not valid. The class in the selector will +// be replaced with `.x`. +// +// E.g.: +// +// | VARIANT | GENERATED SIGNATURE | +// | ---------------- | ----------------------------- | +// | `[&:focus]:flex` | `.x:focus { display: flex; }` | +// | `focus:flex` | `.x:focus { display: flex; }` | +// +// These produce the same signature, therefore they represent the same variant. +export const computeVariantSignature = new DefaultMap< + DesignSystem, + DefaultMap +>((designSystem) => { + return new DefaultMap((variant) => { + try { + // Ensure the prefix is added to the utility if it is not already present. + variant = + designSystem.theme.prefix && !variant.startsWith(designSystem.theme.prefix) + ? `${designSystem.theme.prefix}:${variant}` + : variant + + // Use `@apply` to normalize the selector to `.x` + let ast: AstNode[] = [styleRule('.x', [atRule('@apply', `${variant}:flex`)])] + substituteAtApply(ast, designSystem) + + // Canonicalize selectors to their minimal form + walk(ast, (node) => { + // At-rules + if (node.kind === 'at-rule' && node.params.includes(' ')) { + node.params = node.params.replaceAll(' ', '') + } + + // Style rules + else if (node.kind === 'rule') { + let selectorAst = SelectorParser.parse(node.selector) + let changed = false + SelectorParser.walk(selectorAst, (node, { replaceWith }) => { + if (node.kind === 'separator' && node.value !== ' ') { + node.value = node.value.trim() + changed = true + } + + // Remove unnecessary `:is(…)` selectors + else if (node.kind === 'function' && node.value === ':is') { + // A single selector inside of `:is(…)` can be replaced with the + // selector itself. + // + // E.g.: `:is(.foo)` → `.foo` + if (node.nodes.length === 1) { + changed = true + replaceWith(node.nodes) + } + + // A selector with the universal selector `*` followed by a pseudo + // class, can be replaced with the pseudo class itself. + else if ( + node.nodes.length === 2 && + node.nodes[0].kind === 'selector' && + node.nodes[0].value === '*' && + node.nodes[1].kind === 'selector' && + node.nodes[1].value[0] === ':' + ) { + changed = true + replaceWith(node.nodes[1]) + } + } + + // Ensure `*` exists before pseudo selectors inside of `:not(…)`, + // `:where(…)`, … + // + // E.g.: + // + // `:not(:first-child)` → `:not(*:first-child)` + // + else if ( + node.kind === 'function' && + node.value[0] === ':' && + node.nodes[0]?.kind === 'selector' && + node.nodes[0]?.value[0] === ':' + ) { + changed = true + node.nodes.unshift({ kind: 'selector', value: '*' }) + } + }) + + if (changed) { + node.selector = SelectorParser.toCss(selectorAst) + } + } + }) + + // Compute the final signature, by generating the CSS for the variant + let signature = toCss(ast) + return signature + } catch { + // A unique symbol is returned to ensure that 2 signatures resulting in + // `null` are not considered equal. + return Symbol() + } + }) +}) + +function temporarilyDisableThemeInline(designSystem: DesignSystem, cb: () => T): T { + // Turn off `@theme inline` feature such that `@theme` and `@theme inline` are + // considered the same. The biggest motivation for this is referencing + // variables in another namespace that happen to contain the same value as the + // utility's own namespaces it is reading from. + // + // E.g.: + // + // The `max-w-*` utility doesn't read from the `--breakpoint-*` namespace. + // But it does read from the `--container-*` namespace. It also happens to + // be the case that `--breakpoint-md` and `--container-3xl` are the exact + // same value. + // + // If you then use the `max-w-(--breakpoint-md)` utility, inlining the + // variable would mean: + // - `max-w-(--breakpoint-md)` → `max-width: 48rem;` → `max-w-3xl` + // - `max-w-(--contianer-3xl)` → `max-width: 48rem;` → `max-w-3xl` + // + // Not inlining the variable would mean: + // - `max-w-(--breakpoint-md)` → `max-width: var(--breakpoint-md);` → `max-w-(--breakpoint-md)` + // - `max-w-(--container-3xl)` → `max-width: var(--container-3xl);` → `max-w-3xl` + + // @ts-expect-error We are monkey-patching a method that's considered private + // in TypeScript + let originalGet = designSystem.theme.values.get + + // Track all values with the inline option set, so we can restore them later. + let restorableInlineOptions = new Set<{ options: ThemeOptions }>() + + // @ts-expect-error We are monkey-patching a method that's considered private + // in TypeScript + designSystem.theme.values.get = (key: string) => { + // @ts-expect-error We are monkey-patching a method that's considered private + // in TypeScript + let value = originalGet.call(designSystem.theme.values, key) + if (value === undefined) return value + + // Remove `inline` if it was set + if (value.options & ThemeOptions.INLINE) { + restorableInlineOptions.add(value) + value.options &= ~ThemeOptions.INLINE + } + + return value + } + + try { + // Run the callback with the `@theme inline` feature disabled + return cb() + } finally { + // Restore the `@theme inline` to the original value + // @ts-expect-error We are monkey-patching a method that's private + designSystem.theme.values.get = originalGet + + // Re-add the `inline` option, in case future lookups are done + for (let value of restorableInlineOptions) { + value.options |= ThemeOptions.INLINE + } + } +} diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index d304bbe1f..483a4e3db 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -1,10 +1,9 @@ #!/usr/bin/env node import { Scanner } from '@tailwindcss/oxide' -import { globby, isGitIgnored } from 'globby' +import { globby } from 'globby' import fs from 'node:fs/promises' import path from 'node:path' -import pc from 'picocolors' import postcss from 'postcss' import { migrateJsConfig } from './codemods/config/migrate-js-config' import { migratePostCSSConfig } from './codemods/config/migrate-postcss' @@ -19,11 +18,10 @@ import { prepareConfig } from './codemods/template/prepare-config' import { help } from './commands/help' import { Stylesheet } from './stylesheet' import { args, type Arg } from './utils/args' -import { gitRoot, isRepoDirty } from './utils/git' +import { isRepoDirty } from './utils/git' import { pkg } from './utils/packages' import { eprintln, error, header, highlight, info, relative, success } from './utils/renderer' import * as version from './utils/version' -import { writeFileSafely } from './utils/write-file-safely' const options = { '--config': { type: 'string', description: 'Path to the configuration file', alias: '-c' }, @@ -43,9 +41,6 @@ if (flags['--help']) { async function run() { let base = process.cwd() - // Use the project's git root, instead of the `base` to ensure all - // `.gitignore` rules are being used. - let isIgnored = await isGitIgnored({ cwd: gitRoot(base) ?? base }) eprintln(header()) eprintln() @@ -63,31 +58,6 @@ async function run() { } } - info(`Upgrading from Tailwind CSS ${highlight(`v${version.installedTailwindVersion(base)}`)}`, { - prefix: '↳ ', - }) - - if (version.installedTailwindVersion(base) !== version.expectedTailwindVersion(base)) { - let pkgManager = await pkg(base).manager() - - error( - [ - 'Version mismatch', - '', - pc.dim('```diff'), - `${pc.red('-')} ${`${pc.dim('"tailwindcss":')} ${`${pc.dim('"')}${pc.blue(version.expectedTailwindVersion(base))}${pc.dim('"')}`}`} (expected version in ${highlight('package.json')})`, - `${pc.green('+')} ${`${pc.dim('"tailwindcss":')} ${`${pc.dim('"')}${pc.blue(version.installedTailwindVersion(base))}${pc.dim('"')}`}`} (installed version in ${highlight('node_modules')})`, - pc.dim('```'), - '', - `Make sure to run ${highlight(`${pkgManager} install`)} and try again.`, - ].join('\n'), - { - prefix: '↳ ', - }, - ) - process.exit(1) - } - { // Stylesheet migrations @@ -99,11 +69,8 @@ async function run() { info('Searching for CSS files in the current directory and its subdirectories…') files = await globby(['**/*.css'], { - cwd: base, absolute: true, gitignore: true, - // gitignore: true will first search for all .gitignore including node_modules folders, this makes the initial search much faster - ignore: ['**/node_modules/**'], }) } @@ -127,7 +94,7 @@ async function run() { // Analyze the stylesheets try { - await analyzeStylesheets(stylesheets, { base }) + await analyzeStylesheets(stylesheets) } catch (e: any) { error(`${e?.message ?? e}`, { prefix: '↳ ' }) } @@ -201,13 +168,6 @@ async function run() { } } - // Prime the compiler/designSystem cache against the pre-migration AST - // so the later template-migration phase sees the same v4 state. After - // this point, `sheet.root` may be mutated by `migrateStylesheet`. - if (!version.isMajor(3) && sheet.isTailwindRoot) { - await Promise.all([sheet.compiler(), sheet.designSystem()]) - } - await migrateStylesheet(sheet, { newPrefix: config?.newPrefix ?? null, designSystem: config?.designSystem ?? (await sheet.designSystem()), @@ -262,7 +222,7 @@ async function run() { for (let sheet of stylesheets) { if (!sheet.file) continue - await writeFileSafely(sheet.file, sheet.root.toString()) + await fs.writeFile(sheet.file, sheet.root.toString()) if (sheet.isTailwindRoot) { success(`Migrated stylesheet: ${highlight(relative(sheet.file, base))}`, { prefix: '↳ ' }) @@ -305,8 +265,6 @@ async function run() { let designSystem = await sheet.designSystem() if (!designSystem) continue - let config = configBySheet.get(sheet) - // Figure out the source files to migrate let sources = (() => { // Disable auto source detection @@ -314,123 +272,43 @@ async function run() { return [] } - // No root specified + // No root specified, use the base directory if (compiler.root === null) { - // When coming from Tailwind CSS v3, we have to use the - // `config.sources` (which came from `config.content` originally) - if (version.isMajor(3)) { - if (config?.sources) { - return config.sources.map((source) => ({ ...source, negated: false })) - } - - // When we don't have any sources, then we have to fallback to no - // sources at all. We cannot fallback to the `**/*` pattern. - return [] - } - - // When we are upgrading a Tailwind CSS v4 and up version, we use - // the default `**/*` pattern. All custom `@source` directives will - // be attached later as sources. return [{ base, pattern: '**/*', negated: false }] } // Use the specified root return [{ ...compiler.root, negated: false }] })().concat(compiler.sources) + + let config = configBySheet.get(sheet) let scanner = new Scanner({ sources }) let filesToMigrate = [] - - let ignoredPaths = new Set() - for (let file of scanner.files) { - file = await fs.realpath(file).catch(() => file) // Ensure we are dealing with the real path, not symlinks if (file.endsWith('.css')) continue - - // When a file is git ignored, then we don't want to migrate it even - // if it was listed in the `config.content` array or part of any - // `@source` directives. - // - // We can make this an option later to explicitly allow this, but - // this should be the default. This guarantees that: - // - // 1. Files coming from node_modules aren't touched - // 2. Generated files aren't changed (the source should update, not the target) - // 3. You can see all the changes that happened - try { - if (isIgnored(file)) { - let culprit = file - - // To prevent print all ignored files, we can also walk up the - // parent tree and log those instead _if_ they are: - // - // 1. Also git ignored - // 2. Are not going outside of the current repo - let parent = path.dirname(file) - do { - try { - if (isIgnored(parent)) { - culprit = parent - } - } catch { - // Escaping the current repo - break - } - - let nextParent = path.dirname(parent) - if (nextParent === parent) break - parent = nextParent - } while (parent) - - if (ignoredPaths.has(culprit)) continue // Already logged, skip - ignoredPaths.add(culprit) - - if (culprit === file) { - info(`Git ignored, skipping: ${highlight(relative(culprit, base))}`, { - prefix: '↳ ', - }) - } else { - info(`Git ignored folder, skipping: ${highlight(relative(culprit, base))}`, { - prefix: '↳ ', - }) - } - - continue - } - } catch (err) { - info(`Outside repository, skipping: ${highlight(relative(file, base))}`, { - prefix: '↳ ', - }) - // Skip this file when we run into errors. E.g.: when the current - // file is not part of the current git repo it will throw an error. - continue - } - if (seenFiles.has(file)) continue seenFiles.add(file) filesToMigrate.push(file) } // Migrate each file - let changes = 0 await Promise.allSettled( - filesToMigrate.map(async (file) => { - let changed = await migrateTemplate(designSystem, config?.userConfig ?? null, file) - if (changed) { - changes++ - info(`Migrated ${highlight(relative(file, base))}`, { prefix: '↳ ' }) - } - }), + filesToMigrate.map((file) => + migrateTemplate(designSystem, config?.userConfig ?? null, file), + ), ) if (config?.configFilePath) { success( - `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))} (${changes} file${changes === 1 ? '' : 's'} changed)`, + `Migrated templates for configuration file: ${highlight(relative(config.configFilePath, base))}`, { prefix: '↳ ' }, ) } else { success( - `Migrated templates for: ${highlight(relative(sheet.file ?? '', base))} (${changes} file${changes === 1 ? '' : 's'} changed)`, - { prefix: '↳ ' }, + `Migrated templates for: ${highlight(relative(sheet.file ?? '', base))}`, + { + prefix: '↳ ', + }, ) } } diff --git a/packages/@tailwindcss-upgrade/src/stylesheet.ts b/packages/@tailwindcss-upgrade/src/stylesheet.ts index 573413262..42cd1f3c6 100644 --- a/packages/@tailwindcss-upgrade/src/stylesheet.ts +++ b/packages/@tailwindcss-upgrade/src/stylesheet.ts @@ -265,26 +265,23 @@ export class Stylesheet { return false } - #compiler?: Promise> | null> - #designSystem?: Promise> | null> - async compiler(): Promise> | null> { if (!this.isTailwindRoot) return null if (!this.file) return null - return (this.#compiler ??= compileAst(postCssAstToCssAst(this.root), { + return compileAst(postCssAstToCssAst(this.root), { base: path.dirname(this.file), onDependency() {}, - })) + }) } async designSystem(): Promise> | null> { if (!this.isTailwindRoot) return null if (!this.file) return null - return (this.#designSystem ??= __unstable__loadDesignSystem(this.root.toString(), { + return __unstable__loadDesignSystem(this.root.toString(), { base: path.dirname(this.file), - })) + }) } [util.inspect.custom]() { diff --git a/packages/tailwindcss/src/utils/dimensions.ts b/packages/@tailwindcss-upgrade/src/utils/dimension.ts similarity index 70% rename from packages/tailwindcss/src/utils/dimensions.ts rename to packages/@tailwindcss-upgrade/src/utils/dimension.ts index 7d647ffe0..a1dd4bded 100644 --- a/packages/tailwindcss/src/utils/dimensions.ts +++ b/packages/@tailwindcss-upgrade/src/utils/dimension.ts @@ -1,20 +1,18 @@ import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' -const DIMENSION_REGEX = /^(?[-+]?(?:\d*\.)?\d+)(?[a-z]+|%)?$/i - // Parse a dimension such as `64rem` into `[64, 'rem']`. export const dimensions = new DefaultMap((input) => { - let match = DIMENSION_REGEX.exec(input) + let match = /^(?-?(?:\d*\.)?\d+)(?[a-z]+|%)$/i.exec(input) if (!match) return null let value = match.groups?.value if (value === undefined) return null + let unit = match.groups?.unit + if (unit === undefined) return null + let valueAsNumber = Number(value) if (Number.isNaN(valueAsNumber)) return null - let unit = match.groups?.unit - if (unit === undefined) return [valueAsNumber, null] as const - return [valueAsNumber, unit] as const }) diff --git a/packages/@tailwindcss-upgrade/src/utils/git.ts b/packages/@tailwindcss-upgrade/src/utils/git.ts index 38edf21cb..d716fbb8a 100644 --- a/packages/@tailwindcss-upgrade/src/utils/git.ts +++ b/packages/@tailwindcss-upgrade/src/utils/git.ts @@ -1,20 +1,8 @@ import { execSync } from 'node:child_process' -export function gitRoot(cwd?: string): string | null { +export function isRepoDirty() { try { - return execSync('git rev-parse --show-toplevel', { - encoding: 'utf-8', - cwd, - stdio: ['ignore', 'pipe', 'ignore'], - }).trim() - } catch { - return null - } -} - -export function isRepoDirty(cwd?: string) { - try { - let stdout = execSync('git status --porcelain', { encoding: 'utf-8', cwd }) + let stdout = execSync('git status --porcelain', { encoding: 'utf-8' }) return stdout.trim() !== '' } catch (error) { // If it's not a git repository we don't know if it's dirty or not. But we diff --git a/packages/@tailwindcss-upgrade/src/utils/packages.ts b/packages/@tailwindcss-upgrade/src/utils/packages.ts index 461a6983a..9f7f5619f 100644 --- a/packages/@tailwindcss-upgrade/src/utils/packages.ts +++ b/packages/@tailwindcss-upgrade/src/utils/packages.ts @@ -24,9 +24,6 @@ const manifests = new DefaultMap((base) => { export function pkg(base: string) { return { - async manager() { - return await packageManagerForBase.get(base) - }, async add(packages: string[], location: 'dependencies' | 'devDependencies' = 'dependencies') { let packageManager = await packageManagerForBase.get(base) let args = packages.slice() @@ -34,13 +31,6 @@ export function pkg(base: string) { args.push(SAVE_DEV[packageManager] || SAVE_DEV.default) } - // Allow running the `pnpm` command in the workspace root without - // erroring. Can't just use `--workspace-root` because that will force - // install dependencies in the workspace root. - if (packageManager === 'pnpm') { - args.push('--ignore-workspace-root-check') - } - let command = `${packageManager} add ${args.join(' ')}` try { return await exec(command, { cwd: base }) diff --git a/packages/@tailwindcss-upgrade/src/utils/renderer.ts b/packages/@tailwindcss-upgrade/src/utils/renderer.ts index c9a967801..38bd9826a 100644 --- a/packages/@tailwindcss-upgrade/src/utils/renderer.ts +++ b/packages/@tailwindcss-upgrade/src/utils/renderer.ts @@ -44,7 +44,7 @@ export function wordWrap(text: string, width: number): string[] { // Handle text with newlines by maintaining the newlines, then splitting // each line separately. if (text.includes('\n')) { - return text.split('\n').flatMap((line) => (line ? wordWrap(line, width) : [''])) + return text.split('\n').flatMap((line) => wordWrap(line, width)) } let words = text.split(' ') diff --git a/packages/tailwindcss/src/utils/replace-object.ts b/packages/@tailwindcss-upgrade/src/utils/replace-object.ts similarity index 100% rename from packages/tailwindcss/src/utils/replace-object.ts rename to packages/@tailwindcss-upgrade/src/utils/replace-object.ts diff --git a/packages/tailwindcss/src/types.ts b/packages/@tailwindcss-upgrade/src/utils/types.ts similarity index 100% rename from packages/tailwindcss/src/types.ts rename to packages/@tailwindcss-upgrade/src/utils/types.ts diff --git a/packages/@tailwindcss-upgrade/src/utils/version.ts b/packages/@tailwindcss-upgrade/src/utils/version.ts index 27bc44f2b..e36ba06ba 100644 --- a/packages/@tailwindcss-upgrade/src/utils/version.ts +++ b/packages/@tailwindcss-upgrade/src/utils/version.ts @@ -1,4 +1,3 @@ -import { execSync } from 'node:child_process' import semver from 'semver' import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' import { getPackageVersionSync } from './package-version' @@ -12,52 +11,12 @@ export function isMajor(version: number) { return semver.satisfies(installedTailwindVersion(), `>=${version}.0.0 <${version + 1}.0.0`) } -/** - * Must be of greater than the current major version including minor and patch. - * - * E.g.: `isGreaterThan(3)` - */ -export function isGreaterThan(version: number) { - return semver.gte(installedTailwindVersion(), `${version + 1}.0.0`) -} - let cache = new DefaultMap((base) => { let tailwindVersion = getPackageVersionSync('tailwindcss', base) if (!tailwindVersion) throw new Error('Tailwind CSS is not installed') return tailwindVersion }) -export function installedTailwindVersion(base = process.cwd()): string { +function installedTailwindVersion(base = process.cwd()): string { return cache.get(base) } - -let expectedCache = new DefaultMap((base) => { - try { - // This will report a problem if the package.json/package-lock.json - // mismatches with the installed version in node_modules. - // - // Also tested this with Bun and PNPM, both seem to work fine. - execSync('npm ls tailwindcss --json', { cwd: base, stdio: 'pipe' }) - return installedTailwindVersion(base) - } catch (_e) { - try { - let e = _e as { stdout: Buffer } - let data = JSON.parse(e.stdout.toString()) - - return ( - // Could be a sub-dependency issue, but we are only interested in - // the top-level version mismatch. - /"(.*?)" from the root project/.exec(data.dependencies.tailwindcss.invalid)?.[1] ?? - // Fallback to the installed version - installedTailwindVersion(base) - ) - } catch { - // We don't know how to verify, so let's just return the installed - // version to not block the user. - return installedTailwindVersion(base) - } - } -}) -export function expectedTailwindVersion(base = process.cwd()): string { - return expectedCache.get(base) -} diff --git a/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts b/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts deleted file mode 100644 index 2ff99f9e7..000000000 --- a/packages/@tailwindcss-upgrade/src/utils/write-file-safely.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { randomUUID } from 'node:crypto' -import fs from 'node:fs/promises' -import path from 'node:path' - -export async function writeFileSafely(file: string, contents: string) { - // Resolve symlinks so the rename replaces the actual target file rather than - // replacing the symlink itself with a regular file. - let realFile = await fs.realpath(file).catch(() => file) - - // Start by creating a new file in the current directory that is guaranteed to - // be unique (via `uuid`). We can embed the `process.id` in case we need to - // debug things later. - // - // While we can write this to a more global `/tmp` folder, I want to be 100% - // sure that we are on the same file system (same drive) so the rename - // operation is atomic. Once the file is written, we will rename the file. If - // this fails, the old file is still intact, if it works we have an updated - // file. - // - // If this still causes problems (but it will slow things down): - // 1. We could make sure that we inherit the file permissions - // 2. Use an explicit fsync to force a flush to disk - let temporaryFile = path.join( - path.dirname(realFile), - `.${path.basename(realFile)}.tailwind-upgrade.${process.pid}.${randomUUID()}.tmp`, - ) - - // Write file uses the `w` flag by default, which is defined as: - // > Open file for writing. The file is created (if it does not exist) or truncated (if it exists). - // > https://nodejs.org/api/fs.html#file-system-flags - // - // Which means that if this function is actively running, and you cancel the - // process at the wrong time, then the truncated files are present. Since all - // these migrations happen in parallel, multiple files are open and available - // to be written to, it could mean in multiple truncated files. - // - // Writing to a temp file first means that if the process is cancelled at this - // point, that the old original file is still correct. - // - // The rename part should be atomic (especially because we guarantee it to be - // on the same file system) so this either succeeds or doesn't happen. - try { - await fs.writeFile(temporaryFile, contents, 'utf8') - await fs.rename(temporaryFile, realFile) - } catch (error) { - await fs.unlink(temporaryFile).catch(() => {}) - throw error - } -} diff --git a/packages/@tailwindcss-vite/README.md b/packages/@tailwindcss-vite/README.md index 1a5d25c73..95ec9d87d 100644 --- a/packages/@tailwindcss-vite/README.md +++ b/packages/@tailwindcss-vite/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,50 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. - ---- - -## `@tailwindcss/vite` plugin API - -### Enabling or disabling Lightning CSS - -By default, this plugin detects whether or not the CSS is being built for production by checking the `NODE_ENV` environment variable. When building for production Lightning CSS will be enabled otherwise it is disabled. - -If you want to always enable or disable Lightning CSS the `optimize` option may be used: - -```js -import tailwindcss from '@tailwindcss/vite' -import { defineConfig } from 'vite' - -export default defineConfig({ - plugins: [ - tailwindcss({ - // Disable Lightning CSS optimization - optimize: false, - }), - ], -}) -``` - -It's also possible to keep Lightning CSS enabled but disable minification: - -```js -import tailwindcss from '@tailwindcss/vite' -import { defineConfig } from 'vite' - -export default defineConfig({ - plugins: [ - tailwindcss({ - // Enable Lightning CSS but disable minification - optimize: { minify: false }, - }), - ], -}) -``` +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json index 821704a1d..6bdba080d 100644 --- a/packages/@tailwindcss-vite/package.json +++ b/packages/@tailwindcss-vite/package.json @@ -1,6 +1,6 @@ { "name": "@tailwindcss/vite", - "version": "4.3.3", + "version": "4.1.6", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { @@ -24,7 +24,7 @@ "exports": { ".": { "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" + "import": "./dist/index.mjs" } }, "dependencies": { @@ -37,6 +37,6 @@ "vite": "catalog:" }, "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" + "vite": "^5.2.0 || ^6" } } diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index 8fc660eaa..00ba92db4 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -11,158 +11,37 @@ import { clearRequireCache } from '@tailwindcss/node/require-cache' import { Scanner } from '@tailwindcss/oxide' import fs from 'node:fs/promises' import path from 'node:path' -import type { Environment, InternalResolveOptions, Plugin, ResolvedConfig } from 'vite' -import * as vite from 'vite' +import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite' const DEBUG = env.DEBUG const SPECIAL_QUERY_RE = /[?&](?:worker|sharedworker|raw|url)\b/ const COMMON_JS_PROXY_RE = /\?commonjs-proxy/ -const INLINE_STYLE_ID_RE = /[?&]index=\d+\.css$/ +const INLINE_STYLE_ID_RE = /[?&]index\=\d+\.css$/ -export type PluginOptions = { - /** - * Optimize and minify the output CSS. - */ - optimize?: boolean | { minify?: boolean } -} - -function createCustomResolver( - resolvers: ((id: string, importer: string) => Promise)[], - filter = (_path: string) => true, -) { - return async (id: string, base: string) => { - // The resolver expects an `importer` file. We don't really know where the - // current `id` was imported from, but Vite will essentially do a - // `path.dirname(importer)` so it doesn't really matter. - // - // It does matter that this is a file, otherwise we would go up a directory, - // which means that we would be resolving files from a parent folder first, - // instead of the current folder we are in. - let importer = path.resolve(base, '__placeholder__.ts') - - for (let resolver of resolvers) { - let resolved = await resolver(id, importer) - - // If we didn't resolve, we don't have to bail immediately, but we can try - // the next resolver - if (!resolved) continue - - if (resolved === id) continue - - // Looks like a relative file, let's resolve it to an absolute path - if (resolved[0] === '.') resolved = path.resolve(base, resolved) - - // Must adhere to additional filters (e.g.: must be a .css file) - if (!filter(resolved)) continue - - // If it's not an absolute path, then we don't really know how to read - // the file from disk. - if (!path.isAbsolute(resolved)) continue - - return resolved - } - } -} - -export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { +export default function tailwindcss(): Plugin[] { + let servers: ViteDevServer[] = [] let config: ResolvedConfig | null = null - let rootsByEnv = new DefaultMap>((env: string) => new Map()) let isSSR = false - let shouldOptimize = true - let minify = true + let minify = false - function createRoot(env: Environment | null, id: string) { - type ResolveFn = (id: string, base: string) => Promise - - let customCssResolver: ResolveFn - let customJsResolver: ResolveFn - - if (!env) { - // Older, pre-environment Vite API - // TODO: Can we drop this?? - let cssResolver = config!.createResolver({ - ...config!.resolve, - extensions: ['.css'], - mainFields: ['style'], - conditions: ['style', 'development|production'], - tryIndex: false, - preferRelative: true, - }) - - let jsResolver = config!.createResolver(config!.resolve) - - customCssResolver = createCustomResolver( - [ - (id, importer) => cssResolver(id, importer, true, isSSR), - (id, importer) => cssResolver(id, importer, false, isSSR), - ], - (path) => path.endsWith('.css'), - ) - customJsResolver = createCustomResolver( - [ - (id, importer) => jsResolver(id, importer, true, isSSR), - (id, importer) => jsResolver(id, importer, false, isSSR), - ], - (path) => !path.endsWith('.css'), - ) - } else { - type ResolveIdFn = ( - environment: Environment, - id: string, - importer?: string, - aliasOnly?: boolean, - ) => Promise - - // There are cases where Environment API is available, - // but `createResolver` is still overridden (for example astro v5) - // - // Copied as-is from vite, because this function is not a part of public API - // - // TODO: Remove this function and pre-environment code when Vite < 7 is no longer supported - function createBackCompatIdResolver( - config: ResolvedConfig, - options?: Partial, - ): ResolveIdFn { - const compatResolve = config.createResolver(options) - let resolve: ResolveIdFn - return async (environment, id, importer, aliasOnly) => { - if (environment.name === 'client' || environment.name === 'ssr') { - return compatResolve(id, importer, aliasOnly, environment.name === 'ssr') - } - resolve ??= vite.createIdResolver(config, options) - return resolve(environment, id, importer, aliasOnly) - } - } - - // Newer Vite versions - let cssResolver = createBackCompatIdResolver(env.config, { - ...env.config.resolve, - extensions: ['.css'], - mainFields: ['style'], - conditions: ['style', 'development|production'], - tryIndex: false, - preferRelative: true, - }) - - let jsResolver = createBackCompatIdResolver(env.config, env.config.resolve) - - customCssResolver = createCustomResolver( - [ - (id, importer) => cssResolver(env, id, importer, true), - (id, importer) => cssResolver(env, id, importer, false), - ], - (path) => path.endsWith('.css'), - ) - customJsResolver = createCustomResolver( - [ - (id, importer) => jsResolver(env, id, importer, true), - (id, importer) => jsResolver(env, id, importer, false), - ], - (path) => !path.endsWith('.css'), - ) + let roots: DefaultMap = new DefaultMap((id) => { + let cssResolver = config!.createResolver({ + ...config!.resolve, + extensions: ['.css'], + mainFields: ['style'], + conditions: ['style', 'development|production'], + tryIndex: false, + preferRelative: true, + }) + function customCssResolver(id: string, base: string) { + return cssResolver(id, base, true, isSSR) } + let jsResolver = config!.createResolver(config!.resolve) + function customJsResolver(id: string, base: string) { + return jsResolver(id, base, true, isSSR) + } return new Root( id, config!.root, @@ -172,7 +51,7 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { customCssResolver, customJsResolver, ) - } + }) return [ { @@ -180,24 +59,14 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:scan', enforce: 'pre', + configureServer(server) { + servers.push(server) + }, + async configResolved(_config) { config = _config + minify = config.build.cssMinify !== false isSSR = config.build.ssr !== false && config.build.ssr !== undefined - - // By default we optimize CSS during the build phase but if the user - // provides explicit options we'll use those instead - if (opts.optimize !== undefined) { - shouldOptimize = opts.optimize !== false - } - - // Minification is also performed when optimizing as long as it's also - // enabled in Vite - minify = shouldOptimize && config.build.cssMinify !== false - - // But again, the user can override that choice explicitly - if (typeof opts.optimize === 'object') { - minify = opts.optimize.minify !== false - } }, }, @@ -206,35 +75,23 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:generate:serve', apply: 'serve', enforce: 'pre', - transform: { - filter: { - id: { - exclude: [/\/\.vite\//, SPECIAL_QUERY_RE, COMMON_JS_PROXY_RE], - include: [/\.css(?:\?.*)?$/, /&lang\.css/, INLINE_STYLE_ID_RE], - }, - }, - async handler(src, id) { - if (!isPotentialCssRootFile(id)) return - using I = new Instrumentation() - DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)') + async transform(src, id, options) { + if (!isPotentialCssRootFile(id)) return - let roots = rootsByEnv.get(this.environment?.name ?? 'default') - let root = roots.get(id) - if (!root) { - root ??= createRoot(this.environment ?? null, id) - roots.set(id, root) - } + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/vite] Generate CSS (serve)') - let result = await root.generate(src, (file) => this.addWatchFile(file), I) - if (!result) { - roots.delete(id) - return - } + let root = roots.get(id) - DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)') - return result - }, + let result = await root.generate(src, (file) => this.addWatchFile(file), I) + if (!result) { + roots.delete(id) + return src + } + + DEBUG && I.end('[@tailwindcss/vite] Generate CSS (serve)') + return result }, }, @@ -243,44 +100,30 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] { name: '@tailwindcss/vite:generate:build', apply: 'build', enforce: 'pre', - transform: { - filter: { - id: { - exclude: [/\/\.vite\//, SPECIAL_QUERY_RE, COMMON_JS_PROXY_RE], - include: [/\.css(?:\?.*)?$/, /&lang\.css/, INLINE_STYLE_ID_RE], - }, - }, - async handler(src, id) { - if (!isPotentialCssRootFile(id)) return - using I = new Instrumentation() - DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)') + async transform(src, id) { + if (!isPotentialCssRootFile(id)) return - let roots = rootsByEnv.get(this.environment?.name ?? 'default') - let root = roots.get(id) - if (!root) { - root ??= createRoot(this.environment ?? null, id) - roots.set(id, root) - } + using I = new Instrumentation() + DEBUG && I.start('[@tailwindcss/vite] Generate CSS (build)') - let result = await root.generate(src, (file) => this.addWatchFile(file), I) - if (!result) { - roots.delete(id) - return - } - DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)') + let root = roots.get(id) - if (shouldOptimize) { - DEBUG && I.start('[@tailwindcss/vite] Optimize CSS') - result = optimize(result.code, { - minify, - map: result.map, - }) - DEBUG && I.end('[@tailwindcss/vite] Optimize CSS') - } + let result = await root.generate(src, (file) => this.addWatchFile(file), I) + if (!result) { + roots.delete(id) + return src + } + DEBUG && I.end('[@tailwindcss/vite] Generate CSS (build)') - return result - }, + DEBUG && I.start('[@tailwindcss/vite] Optimize CSS') + result = optimize(result.code, { + minify, + map: result.map, + }) + DEBUG && I.end('[@tailwindcss/vite] Optimize CSS') + + return result }, }, ] satisfies Plugin[] @@ -292,15 +135,13 @@ function getExtension(id: string) { } function isPotentialCssRootFile(id: string) { - if (id.includes('/.vite/')) return false - - // Don't intercept special static asset resources - if (SPECIAL_QUERY_RE.test(id)) return false - if (COMMON_JS_PROXY_RE.test(id)) return false - + if (id.includes('/.vite/')) return let extension = getExtension(id) - let isCssFile = extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE) - + let isCssFile = + (extension === 'css' || id.includes('&lang.css') || id.match(INLINE_STYLE_ID_RE)) && + // Don't intercept special static asset resources + !SPECIAL_QUERY_RE.test(id) && + !COMMON_JS_PROXY_RE.test(id) return isCssFile } @@ -372,7 +213,7 @@ class Root { let inputPath = idToPath(this.id) function addWatchFile(file: string) { - // Don't watch the input file since it's already a dependency and causes + // Don't watch the input file since it's already a dependency anc causes // issues with some setups (e.g. Qwik). if (file === inputPath) { return @@ -382,7 +223,7 @@ class Root { // crash Vite. We work around this for now by ignoring updates to them. // // https://github.com/tailwindlabs/tailwindcss/issues/16877 - if (/[#?].*\.svg$/.test(file)) { + if (/[\#\?].*\.svg$/.test(file)) { return } _addWatchFile(file) @@ -442,11 +283,7 @@ class Root { if ( !( this.compiler.features & - (Features.AtApply | - Features.JsPluginCompat | - Features.ThemeFunction | - Features.Utilities | - Features.Variants) + (Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities) ) ) { return false @@ -464,7 +301,6 @@ class Root { } if (this.compiler.features & Features.Utilities) { - DEBUG && I.start('Register dependency messages') // Watch individual files found via custom `@source` paths for (let file of this.scanner.files) { addWatchFile(file) @@ -501,7 +337,6 @@ class Root { } } } - DEBUG && I.end('Register dependency messages') } DEBUG && I.start('Build CSS') diff --git a/packages/@tailwindcss-webpack/README.md b/packages/@tailwindcss-webpack/README.md deleted file mode 100644 index 56484ed7f..000000000 --- a/packages/@tailwindcss-webpack/README.md +++ /dev/null @@ -1,102 +0,0 @@ -

- - - - - Tailwind CSS - - -

- -

- A utility-first CSS framework for rapidly building custom user interfaces. -

- -

- Build Status - Total Downloads - Latest Release - License -

- ---- - -## Documentation - -For full documentation, visit [tailwindcss.com](https://tailwindcss.com). - -## Community - -For help, discussion about best practices, or feature ideas: - -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) - -## Contributing - -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. - ---- - -## @tailwindcss/webpack - -A webpack loader for Tailwind CSS v4. - -## Installation - -```sh -npm install @tailwindcss/webpack -``` - -### Usage - -```javascript -// webpack.config.js -const MiniCssExtractPlugin = require('mini-css-extract-plugin') - -module.exports = { - plugins: [new MiniCssExtractPlugin()], - module: { - rules: [ - { - test: /\.css$/i, - use: [MiniCssExtractPlugin.loader, 'css-loader', '@tailwindcss/webpack'], - }, - ], - }, -} -``` - -Then create a CSS file that imports Tailwind: - -```css -/* src/index.css */ -@import 'tailwindcss'; -``` - -### Options - -#### `base` - -The base directory to scan for class candidates. Defaults to the current working directory. - -```javascript -{ - loader: '@tailwindcss/webpack', - options: { - base: process.cwd(), - }, -} -``` - -#### `optimize` - -Whether to optimize and minify the output CSS. Defaults to `true` in production mode. - -```javascript -{ - loader: '@tailwindcss/webpack', - options: { - optimize: true, // or { minify: true } - }, -} -``` diff --git a/packages/@tailwindcss-webpack/package.json b/packages/@tailwindcss-webpack/package.json deleted file mode 100644 index dce09429d..000000000 --- a/packages/@tailwindcss-webpack/package.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "@tailwindcss/webpack", - "version": "4.3.3", - "description": "A webpack loader for Tailwind CSS v4.", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/tailwindlabs/tailwindcss.git", - "directory": "packages/@tailwindcss-webpack" - }, - "bugs": "https://github.com/tailwindlabs/tailwindcss/issues", - "homepage": "https://tailwindcss.com", - "scripts": { - "build": "tsup-node", - "dev": "pnpm run build -- --watch" - }, - "files": [ - "dist/" - ], - "publishConfig": { - "provenance": true, - "access": "public" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.js" - } - }, - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "workspace:*", - "@tailwindcss/oxide": "workspace:*", - "tailwindcss": "workspace:*" - }, - "devDependencies": { - "@types/node": "catalog:", - "webpack": "catalog:" - }, - "peerDependencies": { - "@rspack/core": "^1.0.0 || ^2.0.0", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } -} diff --git a/packages/@tailwindcss-webpack/src/index.cts b/packages/@tailwindcss-webpack/src/index.cts deleted file mode 100644 index dbdded748..000000000 --- a/packages/@tailwindcss-webpack/src/index.cts +++ /dev/null @@ -1,5 +0,0 @@ -import tailwindLoader from './index.ts' - -// CommonJS export for webpack loaders - must be the function directly -// @ts-ignore -export = tailwindLoader diff --git a/packages/@tailwindcss-webpack/src/index.ts b/packages/@tailwindcss-webpack/src/index.ts deleted file mode 100644 index 6d02f1f0b..000000000 --- a/packages/@tailwindcss-webpack/src/index.ts +++ /dev/null @@ -1,287 +0,0 @@ -import QuickLRU from '@alloc/quick-lru' -import { - compile, - env, - Features, - Instrumentation, - normalizePath, - optimize, - Polyfills, -} from '@tailwindcss/node' -import { clearRequireCache } from '@tailwindcss/node/require-cache' -import { Scanner } from '@tailwindcss/oxide' -import fs from 'node:fs' -import path from 'node:path' -import type { LoaderContext } from 'webpack' - -const DEBUG = env.DEBUG - -export interface LoaderOptions { - /** - * The base directory to scan for class candidates. - * - * Defaults to the current working directory. - */ - base?: string - - /** - * Optimize and minify the output CSS. - */ - optimize?: boolean | { minify?: boolean } -} - -interface CacheEntry { - mtimes: Map - compiler: null | Awaited> - scanner: null | Scanner - candidates: Set - fullRebuildPaths: string[] -} - -const cache = new QuickLRU({ maxSize: 50 }) - -function getCacheKey(resourceId: string, opts: LoaderOptions): string { - return `${resourceId}:${opts.base ?? ''}:${JSON.stringify(opts.optimize)}` -} - -function getContextFromCache(resourceId: string, opts: LoaderOptions): CacheEntry { - let key = getCacheKey(resourceId, opts) - if (cache.has(key)) return cache.get(key)! - let entry: CacheEntry = { - mtimes: new Map(), - compiler: null, - scanner: null, - candidates: new Set(), - fullRebuildPaths: [], - } - cache.set(key, entry) - return entry -} - -export default async function tailwindLoader( - this: LoaderContext, - source: string, -): Promise { - let callback = this.async() - let options = this.getOptions() ?? {} - let inputFile = this.resourcePath - let resourceId = this.resource - let base = options.base ?? process.cwd() - let shouldOptimize = options.optimize ?? process.env.NODE_ENV === 'production' - let isCSSModuleFile = inputFile.endsWith('.module.css') - - using I = new Instrumentation() - - DEBUG && I.start(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - - // Bail out early if this is guaranteed to be a non-Tailwind CSS file. - { - DEBUG && I.start('Quick bail check') - let canBail = !/@(import|reference|theme|variant|config|plugin|apply|tailwind)\b/.test(source) - if (canBail) { - DEBUG && I.end('Quick bail check') - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - DEBUG && I.end('Quick bail check') - } - - try { - let context = getContextFromCache(resourceId, options) - let inputBasePath = path.dirname(path.resolve(inputFile)) - - // Whether this is the first build or not - let isInitialBuild = context.compiler === null - - async function createCompiler() { - DEBUG && I.start('Setup compiler') - if (context.fullRebuildPaths.length > 0 && !isInitialBuild) { - clearRequireCache(context.fullRebuildPaths) - } - - context.fullRebuildPaths = [] - - DEBUG && I.start('Create compiler') - let compiler = await compile(source, { - from: inputFile, - base: inputBasePath, - shouldRewriteUrls: true, - onDependency: (depPath) => context.fullRebuildPaths.push(depPath), - // In CSS Module files, we have to disable the `@property` polyfill since these will - // emit global `*` rules which are considered to be non-pure and will cause builds - // to fail. - polyfills: isCSSModuleFile ? Polyfills.All ^ Polyfills.AtProperty : Polyfills.All, - }) - DEBUG && I.end('Create compiler') - - DEBUG && I.end('Setup compiler') - return compiler - } - - // Setup the compiler if it doesn't exist yet - context.compiler ??= await createCompiler() - - // Early exit if no Tailwind features are used - if (context.compiler.features === Features.None) { - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - - let rebuildStrategy: 'full' | 'incremental' = 'incremental' - - // Track file modification times to CSS files - DEBUG && I.start('Register full rebuild paths') - { - // Report dependencies for config files, plugins, etc. - for (let file of context.fullRebuildPaths) { - this.addDependency(path.resolve(file)) - } - - let files = [...context.fullRebuildPaths, inputFile] - - for (let file of files) { - let changedTime: number | null = null - try { - changedTime = fs.statSync(file)?.mtimeMs ?? null - } catch { - // File might not exist - } - - if (changedTime === null) { - if (file === inputFile) { - rebuildStrategy = 'full' - } - continue - } - - let prevTime = context.mtimes.get(file) - if (prevTime === changedTime) continue - - rebuildStrategy = 'full' - context.mtimes.set(file, changedTime) - } - } - DEBUG && I.end('Register full rebuild paths') - - if (rebuildStrategy === 'full' && !isInitialBuild) { - context.compiler = await createCompiler() - } - - let compiler = context.compiler - - // Check if we need to process this file at all - if ( - !( - compiler.features & - (Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities) - ) - ) { - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, source) - return - } - - // Setup or update scanner if needed - if (context.scanner === null || rebuildStrategy === 'full') { - DEBUG && I.start('Setup scanner') - let sources = (() => { - // Disable auto source detection - if (compiler.root === 'none') { - return [] - } - - // No root specified, use the base directory - if (compiler.root === null) { - return [{ base, pattern: '**/*', negated: false }] - } - - // Use the specified root - return [{ ...compiler.root, negated: false }] - })().concat(compiler.sources) - - context.scanner = new Scanner({ sources }) - DEBUG && I.end('Setup scanner') - } - - // Scan for candidates if utilities are used - if (compiler.features & Features.Utilities) { - DEBUG && I.start('Scan for candidates') - for (let candidate of context.scanner.scan()) { - context.candidates.add(candidate) - } - DEBUG && I.end('Scan for candidates') - - DEBUG && I.start('Register dependency messages') - // Add all found files as direct dependencies - let resolvedInputFile = path.resolve(base, inputFile) - for (let file of context.scanner.files) { - let absolutePath = path.resolve(file) - // The CSS file cannot be a dependency of itself - if (absolutePath === resolvedInputFile) { - continue - } - this.addDependency(absolutePath) - } - - // Register context dependencies for glob patterns - for (let glob of context.scanner.globs) { - // Skip negated patterns - if (glob.pattern[0] === '!') continue - - // Avoid adding a dependency on the base directory itself - if (glob.pattern === '*' && base === glob.base) { - continue - } - - this.addContextDependency(path.resolve(glob.base)) - } - - // Validate that source(...) paths are directories - let root = compiler.root - if (root !== 'none' && root !== null) { - let basePath = normalizePath(path.resolve(root.base, root.pattern)) - try { - let stats = fs.statSync(basePath) - if (!stats.isDirectory()) { - throw new Error( - `The path given to \`source(…)\` must be a directory but got \`source(${basePath})\` instead.`, - ) - } - } catch (err) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { - throw err - } - // Directory doesn't exist yet, which is fine - } - } - DEBUG && I.end('Register dependency messages') - } - - DEBUG && I.start('Build utilities') - let css = compiler.build([...context.candidates]) - DEBUG && I.end('Build utilities') - - // Optionally optimize the output - let result = css - if (shouldOptimize) { - DEBUG && I.start('Optimization') - let optimized = optimize(css, { - minify: typeof shouldOptimize === 'object' ? shouldOptimize.minify : true, - }) - result = optimized.code - DEBUG && I.end('Optimization') - } - - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(null, result) - } catch (error) { - // Clear the cache entry on error to force a full rebuild next time - let key = getCacheKey(resourceId, options) - cache.delete(key) - - DEBUG && I.end(`[@tailwindcss/webpack] ${path.relative(base, inputFile)}`) - callback(error as Error) - } -} diff --git a/packages/@tailwindcss-webpack/tsconfig.json b/packages/@tailwindcss-webpack/tsconfig.json deleted file mode 100644 index d2131cbeb..000000000 --- a/packages/@tailwindcss-webpack/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "verbatimModuleSyntax": false, - }, -} diff --git a/packages/@tailwindcss-webpack/tsup.config.ts b/packages/@tailwindcss-webpack/tsup.config.ts deleted file mode 100644 index 998eebe4f..000000000 --- a/packages/@tailwindcss-webpack/tsup.config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineConfig } from 'tsup' - -export default defineConfig([ - { - format: ['esm'], - clean: true, - minify: true, - cjsInterop: true, - dts: true, - entry: ['src/index.ts'], - }, - { - format: ['cjs'], - minify: true, - cjsInterop: true, - dts: true, - entry: ['src/index.cts'], - }, -]) diff --git a/packages/tailwindcss/README.md b/packages/tailwindcss/README.md index 5f532607d..95ec9d87d 100644 --- a/packages/tailwindcss/README.md +++ b/packages/tailwindcss/README.md @@ -13,10 +13,10 @@

- Build Status + Build Status Total Downloads - Latest Release - License + Latest Release + License

--- @@ -27,10 +27,14 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com). ## Community -For help, discussion about best practices, or feature ideas: +For help, discussion about best practices, or any other conversation that would benefit from being searchable: -[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions) +[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions) + +For chatting with others using the framework: + +[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe) ## Contributing -If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**. +If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**. diff --git a/packages/tailwindcss/package.json b/packages/tailwindcss/package.json index d1288d6fa..69d59a2c2 100644 --- a/packages/tailwindcss/package.json +++ b/packages/tailwindcss/package.json @@ -1,6 +1,6 @@ { "name": "tailwindcss", - "version": "4.3.3", + "version": "4.1.6", "description": "A utility-first CSS framework for rapidly building custom user interfaces.", "license": "MIT", "repository": { @@ -127,12 +127,12 @@ "utilities.css" ], "devDependencies": { - "@jridgewell/remapping": "^2.3.5", + "@ampproject/remapping": "^2.3.0", "@tailwindcss/oxide": "workspace:^", "@types/node": "catalog:", - "dedent": "catalog:", + "dedent": "1.5.3", "lightningcss": "catalog:", - "magic-string": "^1.1.0", + "magic-string": "^0.30.17", "source-map-js": "^1.2.1" } } diff --git a/packages/tailwindcss/playwright.config.ts b/packages/tailwindcss/playwright.config.ts index ba8f95d70..c0a123abf 100644 --- a/packages/tailwindcss/playwright.config.ts +++ b/packages/tailwindcss/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [['html', { open: 'never' }]], + reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -36,11 +36,10 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - // Playwright 1.60's Windows WebKit worker hangs on shutdown after all tests - // pass, causing CI to fail with a worker force-kill error. - ...(process.platform === 'win32' - ? [] - : [{ name: 'webkit', use: { ...devices['Desktop Safari'] } }]), + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, { name: 'firefox', use: { diff --git a/packages/tailwindcss/preflight.css b/packages/tailwindcss/preflight.css index 04e87905c..0976a40d4 100644 --- a/packages/tailwindcss/preflight.css +++ b/packages/tailwindcss/preflight.css @@ -32,13 +32,8 @@ html, tab-size: 4; /* 3 */ font-family: --theme( --default-font-family, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - 'Helvetica Neue', - 'Noto Sans', - Arial, + ui-sans-serif, + system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', @@ -175,7 +170,7 @@ table { Use the modern Firefox focus style for all focusable elements. */ -:-moz-focusring:where(:not(iframe)) { +:-moz-focusring { outline: auto; } @@ -354,14 +349,6 @@ textarea { padding-block: 0; } -/* - Center dropdown marker shown on inputs with paired ``s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499) -*/ - -::-webkit-calendar-picker-indicator { - line-height: 1; -} - /* Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) */ diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index 9921709d7..e8159b0f6 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -1,8 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using the default theme 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-shadow: 0 0 #0000; @@ -124,18 +123,14 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using syntax: "*"; inherits: false; initial-value: 0 0 #0000; -} -" +}" `; exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` -" -@layer theme { +"@layer theme { :root, :host { - --tw-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, - sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --tw-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", - monospace; + --tw-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --tw-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --tw-default-font-family: var(--tw-font-sans); --tw-default-mono-font-family: var(--tw-font-mono); } @@ -160,7 +155,7 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` -webkit-text-size-adjust: 100%; tab-size: 4; line-height: 1.5; - font-family: var(--tw-default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-family: var(--tw-default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); font-feature-settings: var(--tw-default-font-feature-settings, normal); font-variation-settings: var(--tw-default-font-variation-settings, normal); -webkit-tap-highlight-color: transparent; @@ -226,7 +221,7 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` border-collapse: collapse; } - :-moz-focusring:where(:not(iframe)) { + :-moz-focusring { outline: auto; } @@ -359,10 +354,6 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` padding-block: 0; } - ::-webkit-calendar-picker-indicator { - line-height: 1; - } - :-moz-ui-invalid { box-shadow: none; } @@ -388,6 +379,5 @@ exports[`compiling CSS > prefix all CSS variables inside preflight 1`] = ` } } -@layer components, utilities; -" +@layer components, utilities;" `; diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index d595e8c01..8c3b136a7 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -306,6 +306,66 @@ exports[`getClassList 1`] = ` "-col-start-11", "-col-start-12", "-col-start-13", + "-end-0", + "-end-0.5", + "-end-1", + "-end-1.5", + "-end-2", + "-end-2.5", + "-end-3", + "-end-3.5", + "-end-4", + "-end-5", + "-end-6", + "-end-7", + "-end-8", + "-end-9", + "-end-10", + "-end-11", + "-end-12", + "-end-14", + "-end-16", + "-end-20", + "-end-24", + "-end-28", + "-end-32", + "-end-36", + "-end-40", + "-end-44", + "-end-48", + "-end-52", + "-end-56", + "-end-60", + "-end-64", + "-end-72", + "-end-80", + "-end-96", + "-end-1/2", + "-end-1/3", + "-end-1/4", + "-end-1/5", + "-end-1/6", + "-end-1/12", + "-end-2/3", + "-end-2/4", + "-end-2/5", + "-end-2/6", + "-end-2/12", + "-end-3/4", + "-end-3/5", + "-end-3/6", + "-end-3/12", + "-end-4/5", + "-end-4/6", + "-end-4/12", + "-end-5/6", + "-end-5/12", + "-end-6/12", + "-end-7/12", + "-end-8/12", + "-end-9/12", + "-end-10/12", + "-end-11/12", "-end-full", "-end-px", "-indent-0", @@ -403,256 +463,8 @@ exports[`getClassList 1`] = ` "-inset-9/12", "-inset-10/12", "-inset-11/12", - "-inset-be-0", - "-inset-be-0.5", - "-inset-be-1", - "-inset-be-1.5", - "-inset-be-2", - "-inset-be-2.5", - "-inset-be-3", - "-inset-be-3.5", - "-inset-be-4", - "-inset-be-5", - "-inset-be-6", - "-inset-be-7", - "-inset-be-8", - "-inset-be-9", - "-inset-be-10", - "-inset-be-11", - "-inset-be-12", - "-inset-be-14", - "-inset-be-16", - "-inset-be-20", - "-inset-be-24", - "-inset-be-28", - "-inset-be-32", - "-inset-be-36", - "-inset-be-40", - "-inset-be-44", - "-inset-be-48", - "-inset-be-52", - "-inset-be-56", - "-inset-be-60", - "-inset-be-64", - "-inset-be-72", - "-inset-be-80", - "-inset-be-96", - "-inset-be-1/2", - "-inset-be-1/3", - "-inset-be-1/4", - "-inset-be-1/5", - "-inset-be-1/6", - "-inset-be-1/12", - "-inset-be-2/3", - "-inset-be-2/4", - "-inset-be-2/5", - "-inset-be-2/6", - "-inset-be-2/12", - "-inset-be-3/4", - "-inset-be-3/5", - "-inset-be-3/6", - "-inset-be-3/12", - "-inset-be-4/5", - "-inset-be-4/6", - "-inset-be-4/12", - "-inset-be-5/6", - "-inset-be-5/12", - "-inset-be-6/12", - "-inset-be-7/12", - "-inset-be-8/12", - "-inset-be-9/12", - "-inset-be-10/12", - "-inset-be-11/12", - "-inset-be-full", - "-inset-be-px", - "-inset-bs-0", - "-inset-bs-0.5", - "-inset-bs-1", - "-inset-bs-1.5", - "-inset-bs-2", - "-inset-bs-2.5", - "-inset-bs-3", - "-inset-bs-3.5", - "-inset-bs-4", - "-inset-bs-5", - "-inset-bs-6", - "-inset-bs-7", - "-inset-bs-8", - "-inset-bs-9", - "-inset-bs-10", - "-inset-bs-11", - "-inset-bs-12", - "-inset-bs-14", - "-inset-bs-16", - "-inset-bs-20", - "-inset-bs-24", - "-inset-bs-28", - "-inset-bs-32", - "-inset-bs-36", - "-inset-bs-40", - "-inset-bs-44", - "-inset-bs-48", - "-inset-bs-52", - "-inset-bs-56", - "-inset-bs-60", - "-inset-bs-64", - "-inset-bs-72", - "-inset-bs-80", - "-inset-bs-96", - "-inset-bs-1/2", - "-inset-bs-1/3", - "-inset-bs-1/4", - "-inset-bs-1/5", - "-inset-bs-1/6", - "-inset-bs-1/12", - "-inset-bs-2/3", - "-inset-bs-2/4", - "-inset-bs-2/5", - "-inset-bs-2/6", - "-inset-bs-2/12", - "-inset-bs-3/4", - "-inset-bs-3/5", - "-inset-bs-3/6", - "-inset-bs-3/12", - "-inset-bs-4/5", - "-inset-bs-4/6", - "-inset-bs-4/12", - "-inset-bs-5/6", - "-inset-bs-5/12", - "-inset-bs-6/12", - "-inset-bs-7/12", - "-inset-bs-8/12", - "-inset-bs-9/12", - "-inset-bs-10/12", - "-inset-bs-11/12", - "-inset-bs-full", - "-inset-bs-px", - "-inset-e-0", - "-inset-e-0.5", - "-inset-e-1", - "-inset-e-1.5", - "-inset-e-2", - "-inset-e-2.5", - "-inset-e-3", - "-inset-e-3.5", - "-inset-e-4", - "-inset-e-5", - "-inset-e-6", - "-inset-e-7", - "-inset-e-8", - "-inset-e-9", - "-inset-e-10", - "-inset-e-11", - "-inset-e-12", - "-inset-e-14", - "-inset-e-16", - "-inset-e-20", - "-inset-e-24", - "-inset-e-28", - "-inset-e-32", - "-inset-e-36", - "-inset-e-40", - "-inset-e-44", - "-inset-e-48", - "-inset-e-52", - "-inset-e-56", - "-inset-e-60", - "-inset-e-64", - "-inset-e-72", - "-inset-e-80", - "-inset-e-96", - "-inset-e-1/2", - "-inset-e-1/3", - "-inset-e-1/4", - "-inset-e-1/5", - "-inset-e-1/6", - "-inset-e-1/12", - "-inset-e-2/3", - "-inset-e-2/4", - "-inset-e-2/5", - "-inset-e-2/6", - "-inset-e-2/12", - "-inset-e-3/4", - "-inset-e-3/5", - "-inset-e-3/6", - "-inset-e-3/12", - "-inset-e-4/5", - "-inset-e-4/6", - "-inset-e-4/12", - "-inset-e-5/6", - "-inset-e-5/12", - "-inset-e-6/12", - "-inset-e-7/12", - "-inset-e-8/12", - "-inset-e-9/12", - "-inset-e-10/12", - "-inset-e-11/12", - "-inset-e-full", - "-inset-e-px", "-inset-full", "-inset-px", - "-inset-s-0", - "-inset-s-0.5", - "-inset-s-1", - "-inset-s-1.5", - "-inset-s-2", - "-inset-s-2.5", - "-inset-s-3", - "-inset-s-3.5", - "-inset-s-4", - "-inset-s-5", - "-inset-s-6", - "-inset-s-7", - "-inset-s-8", - "-inset-s-9", - "-inset-s-10", - "-inset-s-11", - "-inset-s-12", - "-inset-s-14", - "-inset-s-16", - "-inset-s-20", - "-inset-s-24", - "-inset-s-28", - "-inset-s-32", - "-inset-s-36", - "-inset-s-40", - "-inset-s-44", - "-inset-s-48", - "-inset-s-52", - "-inset-s-56", - "-inset-s-60", - "-inset-s-64", - "-inset-s-72", - "-inset-s-80", - "-inset-s-96", - "-inset-s-1/2", - "-inset-s-1/3", - "-inset-s-1/4", - "-inset-s-1/5", - "-inset-s-1/6", - "-inset-s-1/12", - "-inset-s-2/3", - "-inset-s-2/4", - "-inset-s-2/5", - "-inset-s-2/6", - "-inset-s-2/12", - "-inset-s-3/4", - "-inset-s-3/5", - "-inset-s-3/6", - "-inset-s-3/12", - "-inset-s-4/5", - "-inset-s-4/6", - "-inset-s-4/12", - "-inset-s-5/6", - "-inset-s-5/12", - "-inset-s-6/12", - "-inset-s-7/12", - "-inset-s-8/12", - "-inset-s-9/12", - "-inset-s-10/12", - "-inset-s-11/12", - "-inset-s-full", - "-inset-s-px", "-inset-x-0", "-inset-x-0.5", "-inset-x-1", @@ -927,76 +739,6 @@ exports[`getClassList 1`] = ` "-mb-80", "-mb-96", "-mb-px", - "-mbe-0", - "-mbe-0.5", - "-mbe-1", - "-mbe-1.5", - "-mbe-2", - "-mbe-2.5", - "-mbe-3", - "-mbe-3.5", - "-mbe-4", - "-mbe-5", - "-mbe-6", - "-mbe-7", - "-mbe-8", - "-mbe-9", - "-mbe-10", - "-mbe-11", - "-mbe-12", - "-mbe-14", - "-mbe-16", - "-mbe-20", - "-mbe-24", - "-mbe-28", - "-mbe-32", - "-mbe-36", - "-mbe-40", - "-mbe-44", - "-mbe-48", - "-mbe-52", - "-mbe-56", - "-mbe-60", - "-mbe-64", - "-mbe-72", - "-mbe-80", - "-mbe-96", - "-mbe-px", - "-mbs-0", - "-mbs-0.5", - "-mbs-1", - "-mbs-1.5", - "-mbs-2", - "-mbs-2.5", - "-mbs-3", - "-mbs-3.5", - "-mbs-4", - "-mbs-5", - "-mbs-6", - "-mbs-7", - "-mbs-8", - "-mbs-9", - "-mbs-10", - "-mbs-11", - "-mbs-12", - "-mbs-14", - "-mbs-16", - "-mbs-20", - "-mbs-24", - "-mbs-28", - "-mbs-32", - "-mbs-36", - "-mbs-40", - "-mbs-44", - "-mbs-48", - "-mbs-52", - "-mbs-56", - "-mbs-60", - "-mbs-64", - "-mbs-72", - "-mbs-80", - "-mbs-96", - "-mbs-px", "-me-0", "-me-0.5", "-me-1", @@ -1497,76 +1239,6 @@ exports[`getClassList 1`] = ` "-scroll-mb-80", "-scroll-mb-96", "-scroll-mb-px", - "-scroll-mbe-0", - "-scroll-mbe-0.5", - "-scroll-mbe-1", - "-scroll-mbe-1.5", - "-scroll-mbe-2", - "-scroll-mbe-2.5", - "-scroll-mbe-3", - "-scroll-mbe-3.5", - "-scroll-mbe-4", - "-scroll-mbe-5", - "-scroll-mbe-6", - "-scroll-mbe-7", - "-scroll-mbe-8", - "-scroll-mbe-9", - "-scroll-mbe-10", - "-scroll-mbe-11", - "-scroll-mbe-12", - "-scroll-mbe-14", - "-scroll-mbe-16", - "-scroll-mbe-20", - "-scroll-mbe-24", - "-scroll-mbe-28", - "-scroll-mbe-32", - "-scroll-mbe-36", - "-scroll-mbe-40", - "-scroll-mbe-44", - "-scroll-mbe-48", - "-scroll-mbe-52", - "-scroll-mbe-56", - "-scroll-mbe-60", - "-scroll-mbe-64", - "-scroll-mbe-72", - "-scroll-mbe-80", - "-scroll-mbe-96", - "-scroll-mbe-px", - "-scroll-mbs-0", - "-scroll-mbs-0.5", - "-scroll-mbs-1", - "-scroll-mbs-1.5", - "-scroll-mbs-2", - "-scroll-mbs-2.5", - "-scroll-mbs-3", - "-scroll-mbs-3.5", - "-scroll-mbs-4", - "-scroll-mbs-5", - "-scroll-mbs-6", - "-scroll-mbs-7", - "-scroll-mbs-8", - "-scroll-mbs-9", - "-scroll-mbs-10", - "-scroll-mbs-11", - "-scroll-mbs-12", - "-scroll-mbs-14", - "-scroll-mbs-16", - "-scroll-mbs-20", - "-scroll-mbs-24", - "-scroll-mbs-28", - "-scroll-mbs-32", - "-scroll-mbs-36", - "-scroll-mbs-40", - "-scroll-mbs-44", - "-scroll-mbs-48", - "-scroll-mbs-52", - "-scroll-mbs-56", - "-scroll-mbs-60", - "-scroll-mbs-64", - "-scroll-mbs-72", - "-scroll-mbs-80", - "-scroll-mbs-96", - "-scroll-mbs-px", "-scroll-me-0", "-scroll-me-0.5", "-scroll-me-1", @@ -1900,6 +1572,66 @@ exports[`getClassList 1`] = ` "-space-y-80", "-space-y-96", "-space-y-px", + "-start-0", + "-start-0.5", + "-start-1", + "-start-1.5", + "-start-2", + "-start-2.5", + "-start-3", + "-start-3.5", + "-start-4", + "-start-5", + "-start-6", + "-start-7", + "-start-8", + "-start-9", + "-start-10", + "-start-11", + "-start-12", + "-start-14", + "-start-16", + "-start-20", + "-start-24", + "-start-28", + "-start-32", + "-start-36", + "-start-40", + "-start-44", + "-start-48", + "-start-52", + "-start-56", + "-start-60", + "-start-64", + "-start-72", + "-start-80", + "-start-96", + "-start-1/2", + "-start-1/3", + "-start-1/4", + "-start-1/5", + "-start-1/6", + "-start-1/12", + "-start-2/3", + "-start-2/4", + "-start-2/5", + "-start-2/6", + "-start-2/12", + "-start-3/4", + "-start-3/5", + "-start-3/6", + "-start-3/12", + "-start-4/5", + "-start-4/6", + "-start-4/12", + "-start-5/6", + "-start-5/12", + "-start-6/12", + "-start-7/12", + "-start-8/12", + "-start-9/12", + "-start-10/12", + "-start-11/12", "-start-full", "-start-px", "-top-0", @@ -2222,7 +1954,6 @@ exports[`getClassList 1`] = ` "accent-current/90", "accent-current/95", "accent-current/100", - "accent-current/background", "accent-inherit", "accent-inherit/0", "accent-inherit/5", @@ -2245,7 +1976,6 @@ exports[`getClassList 1`] = ` "accent-inherit/90", "accent-inherit/95", "accent-inherit/100", - "accent-inherit/background", "accent-transparent", "accent-transparent/0", "accent-transparent/5", @@ -2268,7 +1998,6 @@ exports[`getClassList 1`] = ` "accent-transparent/90", "accent-transparent/95", "accent-transparent/100", - "accent-transparent/background", "align-baseline", "align-bottom", "align-middle", @@ -2593,7 +2322,6 @@ exports[`getClassList 1`] = ` "bg-current/90", "bg-current/95", "bg-current/100", - "bg-current/background", "bg-fixed", "bg-inherit", "bg-inherit/0", @@ -2617,7 +2345,6 @@ exports[`getClassList 1`] = ` "bg-inherit/90", "bg-inherit/95", "bg-inherit/100", - "bg-inherit/background", "bg-left", "bg-linear-0", "bg-linear-0/oklab", @@ -2846,79 +2573,7 @@ exports[`getClassList 1`] = ` "bg-transparent/90", "bg-transparent/95", "bg-transparent/100", - "bg-transparent/background", "block", - "block-0", - "block-0.5", - "block-1", - "block-1.5", - "block-2", - "block-2.5", - "block-3", - "block-3.5", - "block-4", - "block-5", - "block-6", - "block-7", - "block-8", - "block-9", - "block-10", - "block-11", - "block-12", - "block-14", - "block-16", - "block-20", - "block-24", - "block-28", - "block-32", - "block-36", - "block-40", - "block-44", - "block-48", - "block-52", - "block-56", - "block-60", - "block-64", - "block-72", - "block-80", - "block-96", - "block-1/2", - "block-1/3", - "block-1/4", - "block-1/5", - "block-1/6", - "block-1/12", - "block-2/3", - "block-2/4", - "block-2/5", - "block-2/6", - "block-2/12", - "block-3/4", - "block-3/5", - "block-3/6", - "block-3/12", - "block-4/5", - "block-4/6", - "block-4/12", - "block-5/6", - "block-5/12", - "block-6/12", - "block-7/12", - "block-8/12", - "block-9/12", - "block-10/12", - "block-11/12", - "block-auto", - "block-dvh", - "block-fit", - "block-full", - "block-lh", - "block-lvh", - "block-max", - "block-min", - "block-px", - "block-screen", - "block-svh", "blur-none", "border", "border/0", @@ -2942,7 +2597,6 @@ exports[`getClassList 1`] = ` "border/90", "border/95", "border/100", - "border/background", "border-0", "border-2", "border-4", @@ -2969,7 +2623,6 @@ exports[`getClassList 1`] = ` "border-b/90", "border-b/95", "border-b/100", - "border-b/background", "border-b-0", "border-b-2", "border-b-4", @@ -2996,7 +2649,6 @@ exports[`getClassList 1`] = ` "border-b-current/90", "border-b-current/95", "border-b-current/100", - "border-b-current/background", "border-b-inherit", "border-b-inherit/0", "border-b-inherit/5", @@ -3019,7 +2671,6 @@ exports[`getClassList 1`] = ` "border-b-inherit/90", "border-b-inherit/95", "border-b-inherit/100", - "border-b-inherit/background", "border-b-transparent", "border-b-transparent/0", "border-b-transparent/5", @@ -3042,199 +2693,6 @@ exports[`getClassList 1`] = ` "border-b-transparent/90", "border-b-transparent/95", "border-b-transparent/100", - "border-b-transparent/background", - "border-be", - "border-be/0", - "border-be/5", - "border-be/10", - "border-be/15", - "border-be/20", - "border-be/25", - "border-be/30", - "border-be/35", - "border-be/40", - "border-be/45", - "border-be/50", - "border-be/55", - "border-be/60", - "border-be/65", - "border-be/70", - "border-be/75", - "border-be/80", - "border-be/85", - "border-be/90", - "border-be/95", - "border-be/100", - "border-be/background", - "border-be-0", - "border-be-2", - "border-be-4", - "border-be-8", - "border-be-current", - "border-be-current/0", - "border-be-current/5", - "border-be-current/10", - "border-be-current/15", - "border-be-current/20", - "border-be-current/25", - "border-be-current/30", - "border-be-current/35", - "border-be-current/40", - "border-be-current/45", - "border-be-current/50", - "border-be-current/55", - "border-be-current/60", - "border-be-current/65", - "border-be-current/70", - "border-be-current/75", - "border-be-current/80", - "border-be-current/85", - "border-be-current/90", - "border-be-current/95", - "border-be-current/100", - "border-be-current/background", - "border-be-inherit", - "border-be-inherit/0", - "border-be-inherit/5", - "border-be-inherit/10", - "border-be-inherit/15", - "border-be-inherit/20", - "border-be-inherit/25", - "border-be-inherit/30", - "border-be-inherit/35", - "border-be-inherit/40", - "border-be-inherit/45", - "border-be-inherit/50", - "border-be-inherit/55", - "border-be-inherit/60", - "border-be-inherit/65", - "border-be-inherit/70", - "border-be-inherit/75", - "border-be-inherit/80", - "border-be-inherit/85", - "border-be-inherit/90", - "border-be-inherit/95", - "border-be-inherit/100", - "border-be-inherit/background", - "border-be-transparent", - "border-be-transparent/0", - "border-be-transparent/5", - "border-be-transparent/10", - "border-be-transparent/15", - "border-be-transparent/20", - "border-be-transparent/25", - "border-be-transparent/30", - "border-be-transparent/35", - "border-be-transparent/40", - "border-be-transparent/45", - "border-be-transparent/50", - "border-be-transparent/55", - "border-be-transparent/60", - "border-be-transparent/65", - "border-be-transparent/70", - "border-be-transparent/75", - "border-be-transparent/80", - "border-be-transparent/85", - "border-be-transparent/90", - "border-be-transparent/95", - "border-be-transparent/100", - "border-be-transparent/background", - "border-bs", - "border-bs/0", - "border-bs/5", - "border-bs/10", - "border-bs/15", - "border-bs/20", - "border-bs/25", - "border-bs/30", - "border-bs/35", - "border-bs/40", - "border-bs/45", - "border-bs/50", - "border-bs/55", - "border-bs/60", - "border-bs/65", - "border-bs/70", - "border-bs/75", - "border-bs/80", - "border-bs/85", - "border-bs/90", - "border-bs/95", - "border-bs/100", - "border-bs/background", - "border-bs-0", - "border-bs-2", - "border-bs-4", - "border-bs-8", - "border-bs-current", - "border-bs-current/0", - "border-bs-current/5", - "border-bs-current/10", - "border-bs-current/15", - "border-bs-current/20", - "border-bs-current/25", - "border-bs-current/30", - "border-bs-current/35", - "border-bs-current/40", - "border-bs-current/45", - "border-bs-current/50", - "border-bs-current/55", - "border-bs-current/60", - "border-bs-current/65", - "border-bs-current/70", - "border-bs-current/75", - "border-bs-current/80", - "border-bs-current/85", - "border-bs-current/90", - "border-bs-current/95", - "border-bs-current/100", - "border-bs-current/background", - "border-bs-inherit", - "border-bs-inherit/0", - "border-bs-inherit/5", - "border-bs-inherit/10", - "border-bs-inherit/15", - "border-bs-inherit/20", - "border-bs-inherit/25", - "border-bs-inherit/30", - "border-bs-inherit/35", - "border-bs-inherit/40", - "border-bs-inherit/45", - "border-bs-inherit/50", - "border-bs-inherit/55", - "border-bs-inherit/60", - "border-bs-inherit/65", - "border-bs-inherit/70", - "border-bs-inherit/75", - "border-bs-inherit/80", - "border-bs-inherit/85", - "border-bs-inherit/90", - "border-bs-inherit/95", - "border-bs-inherit/100", - "border-bs-inherit/background", - "border-bs-transparent", - "border-bs-transparent/0", - "border-bs-transparent/5", - "border-bs-transparent/10", - "border-bs-transparent/15", - "border-bs-transparent/20", - "border-bs-transparent/25", - "border-bs-transparent/30", - "border-bs-transparent/35", - "border-bs-transparent/40", - "border-bs-transparent/45", - "border-bs-transparent/50", - "border-bs-transparent/55", - "border-bs-transparent/60", - "border-bs-transparent/65", - "border-bs-transparent/70", - "border-bs-transparent/75", - "border-bs-transparent/80", - "border-bs-transparent/85", - "border-bs-transparent/90", - "border-bs-transparent/95", - "border-bs-transparent/100", - "border-bs-transparent/background", "border-collapse", "border-current", "border-current/0", @@ -3258,7 +2716,6 @@ exports[`getClassList 1`] = ` "border-current/90", "border-current/95", "border-current/100", - "border-current/background", "border-dashed", "border-dotted", "border-double", @@ -3284,7 +2741,6 @@ exports[`getClassList 1`] = ` "border-e/90", "border-e/95", "border-e/100", - "border-e/background", "border-e-0", "border-e-2", "border-e-4", @@ -3311,7 +2767,6 @@ exports[`getClassList 1`] = ` "border-e-current/90", "border-e-current/95", "border-e-current/100", - "border-e-current/background", "border-e-inherit", "border-e-inherit/0", "border-e-inherit/5", @@ -3334,7 +2789,6 @@ exports[`getClassList 1`] = ` "border-e-inherit/90", "border-e-inherit/95", "border-e-inherit/100", - "border-e-inherit/background", "border-e-transparent", "border-e-transparent/0", "border-e-transparent/5", @@ -3357,7 +2811,6 @@ exports[`getClassList 1`] = ` "border-e-transparent/90", "border-e-transparent/95", "border-e-transparent/100", - "border-e-transparent/background", "border-hidden", "border-inherit", "border-inherit/0", @@ -3381,7 +2834,6 @@ exports[`getClassList 1`] = ` "border-inherit/90", "border-inherit/95", "border-inherit/100", - "border-inherit/background", "border-l", "border-l/0", "border-l/5", @@ -3404,7 +2856,6 @@ exports[`getClassList 1`] = ` "border-l/90", "border-l/95", "border-l/100", - "border-l/background", "border-l-0", "border-l-2", "border-l-4", @@ -3431,7 +2882,6 @@ exports[`getClassList 1`] = ` "border-l-current/90", "border-l-current/95", "border-l-current/100", - "border-l-current/background", "border-l-inherit", "border-l-inherit/0", "border-l-inherit/5", @@ -3454,7 +2904,6 @@ exports[`getClassList 1`] = ` "border-l-inherit/90", "border-l-inherit/95", "border-l-inherit/100", - "border-l-inherit/background", "border-l-transparent", "border-l-transparent/0", "border-l-transparent/5", @@ -3477,7 +2926,6 @@ exports[`getClassList 1`] = ` "border-l-transparent/90", "border-l-transparent/95", "border-l-transparent/100", - "border-l-transparent/background", "border-none", "border-r", "border-r/0", @@ -3501,7 +2949,6 @@ exports[`getClassList 1`] = ` "border-r/90", "border-r/95", "border-r/100", - "border-r/background", "border-r-0", "border-r-2", "border-r-4", @@ -3528,7 +2975,6 @@ exports[`getClassList 1`] = ` "border-r-current/90", "border-r-current/95", "border-r-current/100", - "border-r-current/background", "border-r-inherit", "border-r-inherit/0", "border-r-inherit/5", @@ -3551,7 +2997,6 @@ exports[`getClassList 1`] = ` "border-r-inherit/90", "border-r-inherit/95", "border-r-inherit/100", - "border-r-inherit/background", "border-r-transparent", "border-r-transparent/0", "border-r-transparent/5", @@ -3574,7 +3019,6 @@ exports[`getClassList 1`] = ` "border-r-transparent/90", "border-r-transparent/95", "border-r-transparent/100", - "border-r-transparent/background", "border-s", "border-s/0", "border-s/5", @@ -3597,7 +3041,6 @@ exports[`getClassList 1`] = ` "border-s/90", "border-s/95", "border-s/100", - "border-s/background", "border-s-0", "border-s-2", "border-s-4", @@ -3624,7 +3067,6 @@ exports[`getClassList 1`] = ` "border-s-current/90", "border-s-current/95", "border-s-current/100", - "border-s-current/background", "border-s-inherit", "border-s-inherit/0", "border-s-inherit/5", @@ -3647,7 +3089,6 @@ exports[`getClassList 1`] = ` "border-s-inherit/90", "border-s-inherit/95", "border-s-inherit/100", - "border-s-inherit/background", "border-s-transparent", "border-s-transparent/0", "border-s-transparent/5", @@ -3670,7 +3111,6 @@ exports[`getClassList 1`] = ` "border-s-transparent/90", "border-s-transparent/95", "border-s-transparent/100", - "border-s-transparent/background", "border-separate", "border-solid", "border-spacing-0", @@ -3800,7 +3240,6 @@ exports[`getClassList 1`] = ` "border-t/90", "border-t/95", "border-t/100", - "border-t/background", "border-t-0", "border-t-2", "border-t-4", @@ -3827,7 +3266,6 @@ exports[`getClassList 1`] = ` "border-t-current/90", "border-t-current/95", "border-t-current/100", - "border-t-current/background", "border-t-inherit", "border-t-inherit/0", "border-t-inherit/5", @@ -3850,7 +3288,6 @@ exports[`getClassList 1`] = ` "border-t-inherit/90", "border-t-inherit/95", "border-t-inherit/100", - "border-t-inherit/background", "border-t-transparent", "border-t-transparent/0", "border-t-transparent/5", @@ -3873,7 +3310,6 @@ exports[`getClassList 1`] = ` "border-t-transparent/90", "border-t-transparent/95", "border-t-transparent/100", - "border-t-transparent/background", "border-transparent", "border-transparent/0", "border-transparent/5", @@ -3896,7 +3332,6 @@ exports[`getClassList 1`] = ` "border-transparent/90", "border-transparent/95", "border-transparent/100", - "border-transparent/background", "border-x", "border-x/0", "border-x/5", @@ -3919,7 +3354,6 @@ exports[`getClassList 1`] = ` "border-x/90", "border-x/95", "border-x/100", - "border-x/background", "border-x-0", "border-x-2", "border-x-4", @@ -3946,7 +3380,6 @@ exports[`getClassList 1`] = ` "border-x-current/90", "border-x-current/95", "border-x-current/100", - "border-x-current/background", "border-x-inherit", "border-x-inherit/0", "border-x-inherit/5", @@ -3969,7 +3402,6 @@ exports[`getClassList 1`] = ` "border-x-inherit/90", "border-x-inherit/95", "border-x-inherit/100", - "border-x-inherit/background", "border-x-transparent", "border-x-transparent/0", "border-x-transparent/5", @@ -3992,7 +3424,6 @@ exports[`getClassList 1`] = ` "border-x-transparent/90", "border-x-transparent/95", "border-x-transparent/100", - "border-x-transparent/background", "border-y", "border-y/0", "border-y/5", @@ -4015,7 +3446,6 @@ exports[`getClassList 1`] = ` "border-y/90", "border-y/95", "border-y/100", - "border-y/background", "border-y-0", "border-y-2", "border-y-4", @@ -4042,7 +3472,6 @@ exports[`getClassList 1`] = ` "border-y-current/90", "border-y-current/95", "border-y-current/100", - "border-y-current/background", "border-y-inherit", "border-y-inherit/0", "border-y-inherit/5", @@ -4065,7 +3494,6 @@ exports[`getClassList 1`] = ` "border-y-inherit/90", "border-y-inherit/95", "border-y-inherit/100", - "border-y-inherit/background", "border-y-transparent", "border-y-transparent/0", "border-y-transparent/5", @@ -4088,7 +3516,6 @@ exports[`getClassList 1`] = ` "border-y-transparent/90", "border-y-transparent/95", "border-y-transparent/100", - "border-y-transparent/background", "bottom-0", "bottom-0.5", "bottom-1", @@ -4179,6 +3606,7 @@ exports[`getClassList 1`] = ` "break-inside-avoid-page", "break-keep", "break-normal", + "break-words", "brightness-0", "brightness-50", "brightness-75", @@ -4215,7 +3643,6 @@ exports[`getClassList 1`] = ` "caret-current/90", "caret-current/95", "caret-current/100", - "caret-current/background", "caret-inherit", "caret-inherit/0", "caret-inherit/5", @@ -4238,7 +3665,6 @@ exports[`getClassList 1`] = ` "caret-inherit/90", "caret-inherit/95", "caret-inherit/100", - "caret-inherit/background", "caret-transparent", "caret-transparent/0", "caret-transparent/5", @@ -4261,7 +3687,6 @@ exports[`getClassList 1`] = ` "caret-transparent/90", "caret-transparent/95", "caret-transparent/100", - "caret-transparent/background", "clear-both", "clear-end", "clear-left", @@ -4417,7 +3842,6 @@ exports[`getClassList 1`] = ` "decoration-current/90", "decoration-current/95", "decoration-current/100", - "decoration-current/background", "decoration-dashed", "decoration-dotted", "decoration-double", @@ -4444,7 +3868,6 @@ exports[`getClassList 1`] = ` "decoration-inherit/90", "decoration-inherit/95", "decoration-inherit/100", - "decoration-inherit/background", "decoration-solid", "decoration-transparent", "decoration-transparent/0", @@ -4468,7 +3891,6 @@ exports[`getClassList 1`] = ` "decoration-transparent/90", "decoration-transparent/95", "decoration-transparent/100", - "decoration-transparent/background", "decoration-wavy", "delay-75", "delay-100", @@ -4501,7 +3923,6 @@ exports[`getClassList 1`] = ` "divide-current/90", "divide-current/95", "divide-current/100", - "divide-current/background", "divide-dashed", "divide-dotted", "divide-double", @@ -4527,7 +3948,6 @@ exports[`getClassList 1`] = ` "divide-inherit/90", "divide-inherit/95", "divide-inherit/100", - "divide-inherit/background", "divide-none", "divide-solid", "divide-transparent", @@ -4552,7 +3972,6 @@ exports[`getClassList 1`] = ` "divide-transparent/90", "divide-transparent/95", "divide-transparent/100", - "divide-transparent/background", "divide-x", "divide-x-0", "divide-x-2", @@ -4587,7 +4006,6 @@ exports[`getClassList 1`] = ` "drop-shadow-current/90", "drop-shadow-current/95", "drop-shadow-current/100", - "drop-shadow-current/background", "drop-shadow-inherit", "drop-shadow-inherit/0", "drop-shadow-inherit/5", @@ -4610,7 +4028,6 @@ exports[`getClassList 1`] = ` "drop-shadow-inherit/90", "drop-shadow-inherit/95", "drop-shadow-inherit/100", - "drop-shadow-inherit/background", "drop-shadow-none", "drop-shadow-sm", "drop-shadow-transparent", @@ -4635,7 +4052,6 @@ exports[`getClassList 1`] = ` "drop-shadow-transparent/90", "drop-shadow-transparent/95", "drop-shadow-transparent/100", - "drop-shadow-transparent/background", "duration-75", "duration-100", "duration-150", @@ -4647,6 +4063,66 @@ exports[`getClassList 1`] = ` "duration-initial", "ease-initial", "ease-linear", + "end-0", + "end-0.5", + "end-1", + "end-1.5", + "end-2", + "end-2.5", + "end-3", + "end-3.5", + "end-4", + "end-5", + "end-6", + "end-7", + "end-8", + "end-9", + "end-10", + "end-11", + "end-12", + "end-14", + "end-16", + "end-20", + "end-24", + "end-28", + "end-32", + "end-36", + "end-40", + "end-44", + "end-48", + "end-52", + "end-56", + "end-60", + "end-64", + "end-72", + "end-80", + "end-96", + "end-1/2", + "end-1/3", + "end-1/4", + "end-1/5", + "end-1/6", + "end-1/12", + "end-2/3", + "end-2/4", + "end-2/5", + "end-2/6", + "end-2/12", + "end-3/4", + "end-3/5", + "end-3/6", + "end-3/12", + "end-4/5", + "end-4/6", + "end-4/12", + "end-5/6", + "end-5/12", + "end-6/12", + "end-7/12", + "end-8/12", + "end-9/12", + "end-10/12", + "end-11/12", "end-auto", "end-full", "end-px", @@ -4674,7 +4150,6 @@ exports[`getClassList 1`] = ` "fill-current/90", "fill-current/95", "fill-current/100", - "fill-current/background", "fill-inherit", "fill-inherit/0", "fill-inherit/5", @@ -4697,7 +4172,6 @@ exports[`getClassList 1`] = ` "fill-inherit/90", "fill-inherit/95", "fill-inherit/100", - "fill-inherit/background", "fill-none", "fill-transparent", "fill-transparent/0", @@ -4721,21 +4195,8 @@ exports[`getClassList 1`] = ` "fill-transparent/90", "fill-transparent/95", "fill-transparent/100", - "fill-transparent/background", "fixed", "flex", - "flex-1", - "flex-2", - "flex-3", - "flex-4", - "flex-5", - "flex-6", - "flex-7", - "flex-8", - "flex-9", - "flex-10", - "flex-11", - "flex-12", "flex-1/2", "flex-1/3", "flex-1/4", @@ -4845,7 +4306,6 @@ exports[`getClassList 1`] = ` "from-current/90", "from-current/95", "from-current/100", - "from-current/background", "from-inherit", "from-inherit/0", "from-inherit/5", @@ -4868,7 +4328,6 @@ exports[`getClassList 1`] = ` "from-inherit/90", "from-inherit/95", "from-inherit/100", - "from-inherit/background", "from-transparent", "from-transparent/0", "from-transparent/5", @@ -4891,7 +4350,6 @@ exports[`getClassList 1`] = ` "from-transparent/90", "from-transparent/95", "from-transparent/100", - "from-transparent/background", "gap-0", "gap-0.5", "gap-1", @@ -5159,81 +4617,9 @@ exports[`getClassList 1`] = ` "indent-96", "indent-px", "inline", - "inline-0", - "inline-0.5", - "inline-1", - "inline-1.5", - "inline-2", - "inline-2.5", - "inline-3", - "inline-3.5", - "inline-4", - "inline-5", - "inline-6", - "inline-7", - "inline-8", - "inline-9", - "inline-10", - "inline-11", - "inline-12", - "inline-14", - "inline-16", - "inline-20", - "inline-24", - "inline-28", - "inline-32", - "inline-36", - "inline-40", - "inline-44", - "inline-48", - "inline-52", - "inline-56", - "inline-60", - "inline-64", - "inline-72", - "inline-80", - "inline-96", - "inline-auto", "inline-block", - "inline-dvw", - "inline-fit", "inline-flex", - "inline-full", "inline-grid", - "inline-lg", - "inline-lvw", - "inline-max", - "inline-md", - "inline-1/2", - "inline-1/3", - "inline-1/4", - "inline-1/5", - "inline-1/6", - "inline-1/12", - "inline-2/3", - "inline-2/4", - "inline-2/5", - "inline-2/6", - "inline-2/12", - "inline-3/4", - "inline-3/5", - "inline-3/6", - "inline-3/12", - "inline-4/5", - "inline-4/6", - "inline-4/12", - "inline-5/6", - "inline-5/12", - "inline-6/12", - "inline-7/12", - "inline-8/12", - "inline-9/12", - "inline-10/12", - "inline-11/12", - "inline-min", - "inline-px", - "inline-screen", - "inline-svw", "inline-table", "inset-0", "inset-0.5", @@ -5296,195 +4682,6 @@ exports[`getClassList 1`] = ` "inset-10/12", "inset-11/12", "inset-auto", - "inset-be-0", - "inset-be-0.5", - "inset-be-1", - "inset-be-1.5", - "inset-be-2", - "inset-be-2.5", - "inset-be-3", - "inset-be-3.5", - "inset-be-4", - "inset-be-5", - "inset-be-6", - "inset-be-7", - "inset-be-8", - "inset-be-9", - "inset-be-10", - "inset-be-11", - "inset-be-12", - "inset-be-14", - "inset-be-16", - "inset-be-20", - "inset-be-24", - "inset-be-28", - "inset-be-32", - "inset-be-36", - "inset-be-40", - "inset-be-44", - "inset-be-48", - "inset-be-52", - "inset-be-56", - "inset-be-60", - "inset-be-64", - "inset-be-72", - "inset-be-80", - "inset-be-96", - "inset-be-1/2", - "inset-be-1/3", - "inset-be-1/4", - "inset-be-1/5", - "inset-be-1/6", - "inset-be-1/12", - "inset-be-2/3", - "inset-be-2/4", - "inset-be-2/5", - "inset-be-2/6", - "inset-be-2/12", - "inset-be-3/4", - "inset-be-3/5", - "inset-be-3/6", - "inset-be-3/12", - "inset-be-4/5", - "inset-be-4/6", - "inset-be-4/12", - "inset-be-5/6", - "inset-be-5/12", - "inset-be-6/12", - "inset-be-7/12", - "inset-be-8/12", - "inset-be-9/12", - "inset-be-10/12", - "inset-be-11/12", - "inset-be-auto", - "inset-be-full", - "inset-be-px", - "inset-bs-0", - "inset-bs-0.5", - "inset-bs-1", - "inset-bs-1.5", - "inset-bs-2", - "inset-bs-2.5", - "inset-bs-3", - "inset-bs-3.5", - "inset-bs-4", - "inset-bs-5", - "inset-bs-6", - "inset-bs-7", - "inset-bs-8", - "inset-bs-9", - "inset-bs-10", - "inset-bs-11", - "inset-bs-12", - "inset-bs-14", - "inset-bs-16", - "inset-bs-20", - "inset-bs-24", - "inset-bs-28", - "inset-bs-32", - "inset-bs-36", - "inset-bs-40", - "inset-bs-44", - "inset-bs-48", - "inset-bs-52", - "inset-bs-56", - "inset-bs-60", - "inset-bs-64", - "inset-bs-72", - "inset-bs-80", - "inset-bs-96", - "inset-bs-1/2", - "inset-bs-1/3", - "inset-bs-1/4", - "inset-bs-1/5", - "inset-bs-1/6", - "inset-bs-1/12", - "inset-bs-2/3", - "inset-bs-2/4", - "inset-bs-2/5", - "inset-bs-2/6", - "inset-bs-2/12", - "inset-bs-3/4", - "inset-bs-3/5", - "inset-bs-3/6", - "inset-bs-3/12", - "inset-bs-4/5", - "inset-bs-4/6", - "inset-bs-4/12", - "inset-bs-5/6", - "inset-bs-5/12", - "inset-bs-6/12", - "inset-bs-7/12", - "inset-bs-8/12", - "inset-bs-9/12", - "inset-bs-10/12", - "inset-bs-11/12", - "inset-bs-auto", - "inset-bs-full", - "inset-bs-px", - "inset-e-0", - "inset-e-0.5", - "inset-e-1", - "inset-e-1.5", - "inset-e-2", - "inset-e-2.5", - "inset-e-3", - "inset-e-3.5", - "inset-e-4", - "inset-e-5", - "inset-e-6", - "inset-e-7", - "inset-e-8", - "inset-e-9", - "inset-e-10", - "inset-e-11", - "inset-e-12", - "inset-e-14", - "inset-e-16", - "inset-e-20", - "inset-e-24", - "inset-e-28", - "inset-e-32", - "inset-e-36", - "inset-e-40", - "inset-e-44", - "inset-e-48", - "inset-e-52", - "inset-e-56", - "inset-e-60", - "inset-e-64", - "inset-e-72", - "inset-e-80", - "inset-e-96", - "inset-e-1/2", - "inset-e-1/3", - "inset-e-1/4", - "inset-e-1/5", - "inset-e-1/6", - "inset-e-1/12", - "inset-e-2/3", - "inset-e-2/4", - "inset-e-2/5", - "inset-e-2/6", - "inset-e-2/12", - "inset-e-3/4", - "inset-e-3/5", - "inset-e-3/6", - "inset-e-3/12", - "inset-e-4/5", - "inset-e-4/6", - "inset-e-4/12", - "inset-e-5/6", - "inset-e-5/12", - "inset-e-6/12", - "inset-e-7/12", - "inset-e-8/12", - "inset-e-9/12", - "inset-e-10/12", - "inset-e-11/12", - "inset-e-auto", - "inset-e-full", - "inset-e-px", "inset-full", "inset-px", "inset-ring", @@ -5515,7 +4712,6 @@ exports[`getClassList 1`] = ` "inset-ring-current/90", "inset-ring-current/95", "inset-ring-current/100", - "inset-ring-current/background", "inset-ring-inherit", "inset-ring-inherit/0", "inset-ring-inherit/5", @@ -5538,7 +4734,6 @@ exports[`getClassList 1`] = ` "inset-ring-inherit/90", "inset-ring-inherit/95", "inset-ring-inherit/100", - "inset-ring-inherit/background", "inset-ring-transparent", "inset-ring-transparent/0", "inset-ring-transparent/5", @@ -5561,70 +4756,6 @@ exports[`getClassList 1`] = ` "inset-ring-transparent/90", "inset-ring-transparent/95", "inset-ring-transparent/100", - "inset-ring-transparent/background", - "inset-s-0", - "inset-s-0.5", - "inset-s-1", - "inset-s-1.5", - "inset-s-2", - "inset-s-2.5", - "inset-s-3", - "inset-s-3.5", - "inset-s-4", - "inset-s-5", - "inset-s-6", - "inset-s-7", - "inset-s-8", - "inset-s-9", - "inset-s-10", - "inset-s-11", - "inset-s-12", - "inset-s-14", - "inset-s-16", - "inset-s-20", - "inset-s-24", - "inset-s-28", - "inset-s-32", - "inset-s-36", - "inset-s-40", - "inset-s-44", - "inset-s-48", - "inset-s-52", - "inset-s-56", - "inset-s-60", - "inset-s-64", - "inset-s-72", - "inset-s-80", - "inset-s-96", - "inset-s-1/2", - "inset-s-1/3", - "inset-s-1/4", - "inset-s-1/5", - "inset-s-1/6", - "inset-s-1/12", - "inset-s-2/3", - "inset-s-2/4", - "inset-s-2/5", - "inset-s-2/6", - "inset-s-2/12", - "inset-s-3/4", - "inset-s-3/5", - "inset-s-3/6", - "inset-s-3/12", - "inset-s-4/5", - "inset-s-4/6", - "inset-s-4/12", - "inset-s-5/6", - "inset-s-5/12", - "inset-s-6/12", - "inset-s-7/12", - "inset-s-8/12", - "inset-s-9/12", - "inset-s-10/12", - "inset-s-11/12", - "inset-s-auto", - "inset-s-full", - "inset-s-px", "inset-shadow-current", "inset-shadow-current/0", "inset-shadow-current/5", @@ -5647,7 +4778,6 @@ exports[`getClassList 1`] = ` "inset-shadow-current/90", "inset-shadow-current/95", "inset-shadow-current/100", - "inset-shadow-current/background", "inset-shadow-inherit", "inset-shadow-inherit/0", "inset-shadow-inherit/5", @@ -5670,7 +4800,6 @@ exports[`getClassList 1`] = ` "inset-shadow-inherit/90", "inset-shadow-inherit/95", "inset-shadow-inherit/100", - "inset-shadow-inherit/background", "inset-shadow-initial", "inset-shadow-none", "inset-shadow-sm", @@ -5717,7 +4846,6 @@ exports[`getClassList 1`] = ` "inset-shadow-transparent/90", "inset-shadow-transparent/95", "inset-shadow-transparent/100", - "inset-shadow-transparent/background", "inset-x-0", "inset-x-0.5", "inset-x-1", @@ -6119,7 +5247,6 @@ exports[`getClassList 1`] = ` "mask-b-from-current/90", "mask-b-from-current/95", "mask-b-from-current/100", - "mask-b-from-current/background", "mask-b-from-inherit", "mask-b-from-inherit/0", "mask-b-from-inherit/5", @@ -6142,7 +5269,6 @@ exports[`getClassList 1`] = ` "mask-b-from-inherit/90", "mask-b-from-inherit/95", "mask-b-from-inherit/100", - "mask-b-from-inherit/background", "mask-b-from-transparent", "mask-b-from-transparent/0", "mask-b-from-transparent/5", @@ -6165,7 +5291,6 @@ exports[`getClassList 1`] = ` "mask-b-from-transparent/90", "mask-b-from-transparent/95", "mask-b-from-transparent/100", - "mask-b-from-transparent/background", "mask-b-to-0", "mask-b-to-0%", "mask-b-to-0.5", @@ -6243,7 +5368,6 @@ exports[`getClassList 1`] = ` "mask-b-to-current/90", "mask-b-to-current/95", "mask-b-to-current/100", - "mask-b-to-current/background", "mask-b-to-inherit", "mask-b-to-inherit/0", "mask-b-to-inherit/5", @@ -6266,7 +5390,6 @@ exports[`getClassList 1`] = ` "mask-b-to-inherit/90", "mask-b-to-inherit/95", "mask-b-to-inherit/100", - "mask-b-to-inherit/background", "mask-b-to-transparent", "mask-b-to-transparent/0", "mask-b-to-transparent/5", @@ -6289,7 +5412,6 @@ exports[`getClassList 1`] = ` "mask-b-to-transparent/90", "mask-b-to-transparent/95", "mask-b-to-transparent/100", - "mask-b-to-transparent/background", "mask-bottom", "mask-bottom-left", "mask-bottom-right", @@ -6387,7 +5509,6 @@ exports[`getClassList 1`] = ` "mask-conic-from-current/90", "mask-conic-from-current/95", "mask-conic-from-current/100", - "mask-conic-from-current/background", "mask-conic-from-inherit", "mask-conic-from-inherit/0", "mask-conic-from-inherit/5", @@ -6410,7 +5531,6 @@ exports[`getClassList 1`] = ` "mask-conic-from-inherit/90", "mask-conic-from-inherit/95", "mask-conic-from-inherit/100", - "mask-conic-from-inherit/background", "mask-conic-from-transparent", "mask-conic-from-transparent/0", "mask-conic-from-transparent/5", @@ -6433,7 +5553,6 @@ exports[`getClassList 1`] = ` "mask-conic-from-transparent/90", "mask-conic-from-transparent/95", "mask-conic-from-transparent/100", - "mask-conic-from-transparent/background", "mask-conic-to-0", "mask-conic-to-0%", "mask-conic-to-0.5", @@ -6511,7 +5630,6 @@ exports[`getClassList 1`] = ` "mask-conic-to-current/90", "mask-conic-to-current/95", "mask-conic-to-current/100", - "mask-conic-to-current/background", "mask-conic-to-inherit", "mask-conic-to-inherit/0", "mask-conic-to-inherit/5", @@ -6534,7 +5652,6 @@ exports[`getClassList 1`] = ` "mask-conic-to-inherit/90", "mask-conic-to-inherit/95", "mask-conic-to-inherit/100", - "mask-conic-to-inherit/background", "mask-conic-to-transparent", "mask-conic-to-transparent/0", "mask-conic-to-transparent/5", @@ -6557,7 +5674,6 @@ exports[`getClassList 1`] = ` "mask-conic-to-transparent/90", "mask-conic-to-transparent/95", "mask-conic-to-transparent/100", - "mask-conic-to-transparent/background", "mask-contain", "mask-cover", "mask-ellipse", @@ -6640,7 +5756,6 @@ exports[`getClassList 1`] = ` "mask-l-from-current/90", "mask-l-from-current/95", "mask-l-from-current/100", - "mask-l-from-current/background", "mask-l-from-inherit", "mask-l-from-inherit/0", "mask-l-from-inherit/5", @@ -6663,7 +5778,6 @@ exports[`getClassList 1`] = ` "mask-l-from-inherit/90", "mask-l-from-inherit/95", "mask-l-from-inherit/100", - "mask-l-from-inherit/background", "mask-l-from-transparent", "mask-l-from-transparent/0", "mask-l-from-transparent/5", @@ -6686,7 +5800,6 @@ exports[`getClassList 1`] = ` "mask-l-from-transparent/90", "mask-l-from-transparent/95", "mask-l-from-transparent/100", - "mask-l-from-transparent/background", "mask-l-to-0", "mask-l-to-0%", "mask-l-to-0.5", @@ -6764,7 +5877,6 @@ exports[`getClassList 1`] = ` "mask-l-to-current/90", "mask-l-to-current/95", "mask-l-to-current/100", - "mask-l-to-current/background", "mask-l-to-inherit", "mask-l-to-inherit/0", "mask-l-to-inherit/5", @@ -6787,7 +5899,6 @@ exports[`getClassList 1`] = ` "mask-l-to-inherit/90", "mask-l-to-inherit/95", "mask-l-to-inherit/100", - "mask-l-to-inherit/background", "mask-l-to-transparent", "mask-l-to-transparent/0", "mask-l-to-transparent/5", @@ -6810,7 +5921,6 @@ exports[`getClassList 1`] = ` "mask-l-to-transparent/90", "mask-l-to-transparent/95", "mask-l-to-transparent/100", - "mask-l-to-transparent/background", "mask-left", "mask-linear-0", "mask-linear-1", @@ -6898,7 +6008,6 @@ exports[`getClassList 1`] = ` "mask-linear-from-current/90", "mask-linear-from-current/95", "mask-linear-from-current/100", - "mask-linear-from-current/background", "mask-linear-from-inherit", "mask-linear-from-inherit/0", "mask-linear-from-inherit/5", @@ -6921,7 +6030,6 @@ exports[`getClassList 1`] = ` "mask-linear-from-inherit/90", "mask-linear-from-inherit/95", "mask-linear-from-inherit/100", - "mask-linear-from-inherit/background", "mask-linear-from-transparent", "mask-linear-from-transparent/0", "mask-linear-from-transparent/5", @@ -6944,7 +6052,6 @@ exports[`getClassList 1`] = ` "mask-linear-from-transparent/90", "mask-linear-from-transparent/95", "mask-linear-from-transparent/100", - "mask-linear-from-transparent/background", "mask-linear-to-0", "mask-linear-to-0%", "mask-linear-to-0.5", @@ -7022,7 +6129,6 @@ exports[`getClassList 1`] = ` "mask-linear-to-current/90", "mask-linear-to-current/95", "mask-linear-to-current/100", - "mask-linear-to-current/background", "mask-linear-to-inherit", "mask-linear-to-inherit/0", "mask-linear-to-inherit/5", @@ -7045,7 +6151,6 @@ exports[`getClassList 1`] = ` "mask-linear-to-inherit/90", "mask-linear-to-inherit/95", "mask-linear-to-inherit/100", - "mask-linear-to-inherit/background", "mask-linear-to-transparent", "mask-linear-to-transparent/0", "mask-linear-to-transparent/5", @@ -7068,7 +6173,6 @@ exports[`getClassList 1`] = ` "mask-linear-to-transparent/90", "mask-linear-to-transparent/95", "mask-linear-to-transparent/100", - "mask-linear-to-transparent/background", "mask-luminance", "mask-match", "mask-no-clip", @@ -7157,7 +6261,6 @@ exports[`getClassList 1`] = ` "mask-r-from-current/90", "mask-r-from-current/95", "mask-r-from-current/100", - "mask-r-from-current/background", "mask-r-from-inherit", "mask-r-from-inherit/0", "mask-r-from-inherit/5", @@ -7180,7 +6283,6 @@ exports[`getClassList 1`] = ` "mask-r-from-inherit/90", "mask-r-from-inherit/95", "mask-r-from-inherit/100", - "mask-r-from-inherit/background", "mask-r-from-transparent", "mask-r-from-transparent/0", "mask-r-from-transparent/5", @@ -7203,7 +6305,6 @@ exports[`getClassList 1`] = ` "mask-r-from-transparent/90", "mask-r-from-transparent/95", "mask-r-from-transparent/100", - "mask-r-from-transparent/background", "mask-r-to-0", "mask-r-to-0%", "mask-r-to-0.5", @@ -7281,7 +6382,6 @@ exports[`getClassList 1`] = ` "mask-r-to-current/90", "mask-r-to-current/95", "mask-r-to-current/100", - "mask-r-to-current/background", "mask-r-to-inherit", "mask-r-to-inherit/0", "mask-r-to-inherit/5", @@ -7304,7 +6404,6 @@ exports[`getClassList 1`] = ` "mask-r-to-inherit/90", "mask-r-to-inherit/95", "mask-r-to-inherit/100", - "mask-r-to-inherit/background", "mask-r-to-transparent", "mask-r-to-transparent/0", "mask-r-to-transparent/5", @@ -7327,7 +6426,6 @@ exports[`getClassList 1`] = ` "mask-r-to-transparent/90", "mask-r-to-transparent/95", "mask-r-to-transparent/100", - "mask-r-to-transparent/background", "mask-radial-at-bottom", "mask-radial-at-bottom-left", "mask-radial-at-bottom-right", @@ -7418,7 +6516,6 @@ exports[`getClassList 1`] = ` "mask-radial-from-current/90", "mask-radial-from-current/95", "mask-radial-from-current/100", - "mask-radial-from-current/background", "mask-radial-from-inherit", "mask-radial-from-inherit/0", "mask-radial-from-inherit/5", @@ -7441,7 +6538,6 @@ exports[`getClassList 1`] = ` "mask-radial-from-inherit/90", "mask-radial-from-inherit/95", "mask-radial-from-inherit/100", - "mask-radial-from-inherit/background", "mask-radial-from-transparent", "mask-radial-from-transparent/0", "mask-radial-from-transparent/5", @@ -7464,7 +6560,6 @@ exports[`getClassList 1`] = ` "mask-radial-from-transparent/90", "mask-radial-from-transparent/95", "mask-radial-from-transparent/100", - "mask-radial-from-transparent/background", "mask-radial-to-0", "mask-radial-to-0%", "mask-radial-to-0.5", @@ -7542,7 +6637,6 @@ exports[`getClassList 1`] = ` "mask-radial-to-current/90", "mask-radial-to-current/95", "mask-radial-to-current/100", - "mask-radial-to-current/background", "mask-radial-to-inherit", "mask-radial-to-inherit/0", "mask-radial-to-inherit/5", @@ -7565,7 +6659,6 @@ exports[`getClassList 1`] = ` "mask-radial-to-inherit/90", "mask-radial-to-inherit/95", "mask-radial-to-inherit/100", - "mask-radial-to-inherit/background", "mask-radial-to-transparent", "mask-radial-to-transparent/0", "mask-radial-to-transparent/5", @@ -7588,7 +6681,6 @@ exports[`getClassList 1`] = ` "mask-radial-to-transparent/90", "mask-radial-to-transparent/95", "mask-radial-to-transparent/100", - "mask-radial-to-transparent/background", "mask-repeat", "mask-repeat-round", "mask-repeat-space", @@ -7673,7 +6765,6 @@ exports[`getClassList 1`] = ` "mask-t-from-current/90", "mask-t-from-current/95", "mask-t-from-current/100", - "mask-t-from-current/background", "mask-t-from-inherit", "mask-t-from-inherit/0", "mask-t-from-inherit/5", @@ -7696,7 +6787,6 @@ exports[`getClassList 1`] = ` "mask-t-from-inherit/90", "mask-t-from-inherit/95", "mask-t-from-inherit/100", - "mask-t-from-inherit/background", "mask-t-from-transparent", "mask-t-from-transparent/0", "mask-t-from-transparent/5", @@ -7719,7 +6809,6 @@ exports[`getClassList 1`] = ` "mask-t-from-transparent/90", "mask-t-from-transparent/95", "mask-t-from-transparent/100", - "mask-t-from-transparent/background", "mask-t-to-0", "mask-t-to-0%", "mask-t-to-0.5", @@ -7797,7 +6886,6 @@ exports[`getClassList 1`] = ` "mask-t-to-current/90", "mask-t-to-current/95", "mask-t-to-current/100", - "mask-t-to-current/background", "mask-t-to-inherit", "mask-t-to-inherit/0", "mask-t-to-inherit/5", @@ -7820,7 +6908,6 @@ exports[`getClassList 1`] = ` "mask-t-to-inherit/90", "mask-t-to-inherit/95", "mask-t-to-inherit/100", - "mask-t-to-inherit/background", "mask-t-to-transparent", "mask-t-to-transparent/0", "mask-t-to-transparent/5", @@ -7843,7 +6930,6 @@ exports[`getClassList 1`] = ` "mask-t-to-transparent/90", "mask-t-to-transparent/95", "mask-t-to-transparent/100", - "mask-t-to-transparent/background", "mask-top", "mask-top-left", "mask-top-right", @@ -7926,7 +7012,6 @@ exports[`getClassList 1`] = ` "mask-x-from-current/90", "mask-x-from-current/95", "mask-x-from-current/100", - "mask-x-from-current/background", "mask-x-from-inherit", "mask-x-from-inherit/0", "mask-x-from-inherit/5", @@ -7949,7 +7034,6 @@ exports[`getClassList 1`] = ` "mask-x-from-inherit/90", "mask-x-from-inherit/95", "mask-x-from-inherit/100", - "mask-x-from-inherit/background", "mask-x-from-transparent", "mask-x-from-transparent/0", "mask-x-from-transparent/5", @@ -7972,7 +7056,6 @@ exports[`getClassList 1`] = ` "mask-x-from-transparent/90", "mask-x-from-transparent/95", "mask-x-from-transparent/100", - "mask-x-from-transparent/background", "mask-x-to-0", "mask-x-to-0%", "mask-x-to-0.5", @@ -8050,7 +7133,6 @@ exports[`getClassList 1`] = ` "mask-x-to-current/90", "mask-x-to-current/95", "mask-x-to-current/100", - "mask-x-to-current/background", "mask-x-to-inherit", "mask-x-to-inherit/0", "mask-x-to-inherit/5", @@ -8073,7 +7155,6 @@ exports[`getClassList 1`] = ` "mask-x-to-inherit/90", "mask-x-to-inherit/95", "mask-x-to-inherit/100", - "mask-x-to-inherit/background", "mask-x-to-transparent", "mask-x-to-transparent/0", "mask-x-to-transparent/5", @@ -8096,7 +7177,6 @@ exports[`getClassList 1`] = ` "mask-x-to-transparent/90", "mask-x-to-transparent/95", "mask-x-to-transparent/100", - "mask-x-to-transparent/background", "mask-y-from-0", "mask-y-from-0%", "mask-y-from-0.5", @@ -8174,7 +7254,6 @@ exports[`getClassList 1`] = ` "mask-y-from-current/90", "mask-y-from-current/95", "mask-y-from-current/100", - "mask-y-from-current/background", "mask-y-from-inherit", "mask-y-from-inherit/0", "mask-y-from-inherit/5", @@ -8197,7 +7276,6 @@ exports[`getClassList 1`] = ` "mask-y-from-inherit/90", "mask-y-from-inherit/95", "mask-y-from-inherit/100", - "mask-y-from-inherit/background", "mask-y-from-transparent", "mask-y-from-transparent/0", "mask-y-from-transparent/5", @@ -8220,7 +7298,6 @@ exports[`getClassList 1`] = ` "mask-y-from-transparent/90", "mask-y-from-transparent/95", "mask-y-from-transparent/100", - "mask-y-from-transparent/background", "mask-y-to-0", "mask-y-to-0%", "mask-y-to-0.5", @@ -8298,7 +7375,6 @@ exports[`getClassList 1`] = ` "mask-y-to-current/90", "mask-y-to-current/95", "mask-y-to-current/100", - "mask-y-to-current/background", "mask-y-to-inherit", "mask-y-to-inherit/0", "mask-y-to-inherit/5", @@ -8321,7 +7397,6 @@ exports[`getClassList 1`] = ` "mask-y-to-inherit/90", "mask-y-to-inherit/95", "mask-y-to-inherit/100", - "mask-y-to-inherit/background", "mask-y-to-transparent", "mask-y-to-transparent/0", "mask-y-to-transparent/5", @@ -8344,78 +7419,6 @@ exports[`getClassList 1`] = ` "mask-y-to-transparent/90", "mask-y-to-transparent/95", "mask-y-to-transparent/100", - "mask-y-to-transparent/background", - "max-block-0", - "max-block-0.5", - "max-block-1", - "max-block-1.5", - "max-block-2", - "max-block-2.5", - "max-block-3", - "max-block-3.5", - "max-block-4", - "max-block-5", - "max-block-6", - "max-block-7", - "max-block-8", - "max-block-9", - "max-block-10", - "max-block-11", - "max-block-12", - "max-block-14", - "max-block-16", - "max-block-20", - "max-block-24", - "max-block-28", - "max-block-32", - "max-block-36", - "max-block-40", - "max-block-44", - "max-block-48", - "max-block-52", - "max-block-56", - "max-block-60", - "max-block-64", - "max-block-72", - "max-block-80", - "max-block-96", - "max-block-1/2", - "max-block-1/3", - "max-block-1/4", - "max-block-1/5", - "max-block-1/6", - "max-block-1/12", - "max-block-2/3", - "max-block-2/4", - "max-block-2/5", - "max-block-2/6", - "max-block-2/12", - "max-block-3/4", - "max-block-3/5", - "max-block-3/6", - "max-block-3/12", - "max-block-4/5", - "max-block-4/6", - "max-block-4/12", - "max-block-5/6", - "max-block-5/12", - "max-block-6/12", - "max-block-7/12", - "max-block-8/12", - "max-block-9/12", - "max-block-10/12", - "max-block-11/12", - "max-block-dvh", - "max-block-fit", - "max-block-full", - "max-block-lh", - "max-block-lvh", - "max-block-max", - "max-block-min", - "max-block-none", - "max-block-px", - "max-block-screen", - "max-block-svh", "max-h-0", "max-h-0.5", "max-h-1", @@ -8490,78 +7493,6 @@ exports[`getClassList 1`] = ` "max-h-screen", "max-h-svh", "max-h-svw", - "max-inline-0", - "max-inline-0.5", - "max-inline-1", - "max-inline-1.5", - "max-inline-2", - "max-inline-2.5", - "max-inline-3", - "max-inline-3.5", - "max-inline-4", - "max-inline-5", - "max-inline-6", - "max-inline-7", - "max-inline-8", - "max-inline-9", - "max-inline-10", - "max-inline-11", - "max-inline-12", - "max-inline-14", - "max-inline-16", - "max-inline-20", - "max-inline-24", - "max-inline-28", - "max-inline-32", - "max-inline-36", - "max-inline-40", - "max-inline-44", - "max-inline-48", - "max-inline-52", - "max-inline-56", - "max-inline-60", - "max-inline-64", - "max-inline-72", - "max-inline-80", - "max-inline-96", - "max-inline-dvw", - "max-inline-fit", - "max-inline-full", - "max-inline-lg", - "max-inline-lvw", - "max-inline-max", - "max-inline-md", - "max-inline-1/2", - "max-inline-1/3", - "max-inline-1/4", - "max-inline-1/5", - "max-inline-1/6", - "max-inline-1/12", - "max-inline-2/3", - "max-inline-2/4", - "max-inline-2/5", - "max-inline-2/6", - "max-inline-2/12", - "max-inline-3/4", - "max-inline-3/5", - "max-inline-3/6", - "max-inline-3/12", - "max-inline-4/5", - "max-inline-4/6", - "max-inline-4/12", - "max-inline-5/6", - "max-inline-5/12", - "max-inline-6/12", - "max-inline-7/12", - "max-inline-8/12", - "max-inline-9/12", - "max-inline-10/12", - "max-inline-11/12", - "max-inline-min", - "max-inline-none", - "max-inline-px", - "max-inline-screen", - "max-inline-svw", "max-w-0", "max-w-0.5", "max-w-1", @@ -8634,6 +7565,7 @@ exports[`getClassList 1`] = ` "max-w-min", "max-w-none", "max-w-px", + "max-w-screen", "max-w-svh", "max-w-svw", "mb-0", @@ -8672,78 +7604,6 @@ exports[`getClassList 1`] = ` "mb-96", "mb-auto", "mb-px", - "mbe-0", - "mbe-0.5", - "mbe-1", - "mbe-1.5", - "mbe-2", - "mbe-2.5", - "mbe-3", - "mbe-3.5", - "mbe-4", - "mbe-5", - "mbe-6", - "mbe-7", - "mbe-8", - "mbe-9", - "mbe-10", - "mbe-11", - "mbe-12", - "mbe-14", - "mbe-16", - "mbe-20", - "mbe-24", - "mbe-28", - "mbe-32", - "mbe-36", - "mbe-40", - "mbe-44", - "mbe-48", - "mbe-52", - "mbe-56", - "mbe-60", - "mbe-64", - "mbe-72", - "mbe-80", - "mbe-96", - "mbe-auto", - "mbe-px", - "mbs-0", - "mbs-0.5", - "mbs-1", - "mbs-1.5", - "mbs-2", - "mbs-2.5", - "mbs-3", - "mbs-3.5", - "mbs-4", - "mbs-5", - "mbs-6", - "mbs-7", - "mbs-8", - "mbs-9", - "mbs-10", - "mbs-11", - "mbs-12", - "mbs-14", - "mbs-16", - "mbs-20", - "mbs-24", - "mbs-28", - "mbs-32", - "mbs-36", - "mbs-40", - "mbs-44", - "mbs-48", - "mbs-52", - "mbs-56", - "mbs-60", - "mbs-64", - "mbs-72", - "mbs-80", - "mbs-96", - "mbs-auto", - "mbs-px", "me-0", "me-0.5", "me-1", @@ -8780,77 +7640,6 @@ exports[`getClassList 1`] = ` "me-96", "me-auto", "me-px", - "min-block-0", - "min-block-0.5", - "min-block-1", - "min-block-1.5", - "min-block-2", - "min-block-2.5", - "min-block-3", - "min-block-3.5", - "min-block-4", - "min-block-5", - "min-block-6", - "min-block-7", - "min-block-8", - "min-block-9", - "min-block-10", - "min-block-11", - "min-block-12", - "min-block-14", - "min-block-16", - "min-block-20", - "min-block-24", - "min-block-28", - "min-block-32", - "min-block-36", - "min-block-40", - "min-block-44", - "min-block-48", - "min-block-52", - "min-block-56", - "min-block-60", - "min-block-64", - "min-block-72", - "min-block-80", - "min-block-96", - "min-block-1/2", - "min-block-1/3", - "min-block-1/4", - "min-block-1/5", - "min-block-1/6", - "min-block-1/12", - "min-block-2/3", - "min-block-2/4", - "min-block-2/5", - "min-block-2/6", - "min-block-2/12", - "min-block-3/4", - "min-block-3/5", - "min-block-3/6", - "min-block-3/12", - "min-block-4/5", - "min-block-4/6", - "min-block-4/12", - "min-block-5/6", - "min-block-5/12", - "min-block-6/12", - "min-block-7/12", - "min-block-8/12", - "min-block-9/12", - "min-block-10/12", - "min-block-11/12", - "min-block-auto", - "min-block-dvh", - "min-block-fit", - "min-block-full", - "min-block-lh", - "min-block-lvh", - "min-block-max", - "min-block-min", - "min-block-px", - "min-block-screen", - "min-block-svh", "min-h-0", "min-h-0.5", "min-h-1", @@ -8925,78 +7714,6 @@ exports[`getClassList 1`] = ` "min-h-screen", "min-h-svh", "min-h-svw", - "min-inline-0", - "min-inline-0.5", - "min-inline-1", - "min-inline-1.5", - "min-inline-2", - "min-inline-2.5", - "min-inline-3", - "min-inline-3.5", - "min-inline-4", - "min-inline-5", - "min-inline-6", - "min-inline-7", - "min-inline-8", - "min-inline-9", - "min-inline-10", - "min-inline-11", - "min-inline-12", - "min-inline-14", - "min-inline-16", - "min-inline-20", - "min-inline-24", - "min-inline-28", - "min-inline-32", - "min-inline-36", - "min-inline-40", - "min-inline-44", - "min-inline-48", - "min-inline-52", - "min-inline-56", - "min-inline-60", - "min-inline-64", - "min-inline-72", - "min-inline-80", - "min-inline-96", - "min-inline-auto", - "min-inline-dvw", - "min-inline-fit", - "min-inline-full", - "min-inline-lg", - "min-inline-lvw", - "min-inline-max", - "min-inline-md", - "min-inline-1/2", - "min-inline-1/3", - "min-inline-1/4", - "min-inline-1/5", - "min-inline-1/6", - "min-inline-1/12", - "min-inline-2/3", - "min-inline-2/4", - "min-inline-2/5", - "min-inline-2/6", - "min-inline-2/12", - "min-inline-3/4", - "min-inline-3/5", - "min-inline-3/6", - "min-inline-3/12", - "min-inline-4/5", - "min-inline-4/6", - "min-inline-4/12", - "min-inline-5/6", - "min-inline-5/12", - "min-inline-6/12", - "min-inline-7/12", - "min-inline-8/12", - "min-inline-9/12", - "min-inline-10/12", - "min-inline-11/12", - "min-inline-min", - "min-inline-px", - "min-inline-screen", - "min-inline-svw", "min-w-0", "min-w-0.5", "min-w-1", @@ -9362,6 +8079,7 @@ exports[`getClassList 1`] = ` "order-12", "order-first", "order-last", + "order-none", "ordinal", "origin-bottom", "origin-bottom-left", @@ -9394,7 +8112,6 @@ exports[`getClassList 1`] = ` "outline/90", "outline/95", "outline/100", - "outline/background", "outline-0", "outline-1", "outline-2", @@ -9422,7 +8139,6 @@ exports[`getClassList 1`] = ` "outline-current/90", "outline-current/95", "outline-current/100", - "outline-current/background", "outline-dashed", "outline-dotted", "outline-double", @@ -9449,7 +8165,6 @@ exports[`getClassList 1`] = ` "outline-inherit/90", "outline-inherit/95", "outline-inherit/100", - "outline-inherit/background", "outline-none", "outline-offset-0", "outline-offset-1", @@ -9479,7 +8194,6 @@ exports[`getClassList 1`] = ` "outline-transparent/90", "outline-transparent/95", "outline-transparent/100", - "outline-transparent/background", "overflow-auto", "overflow-clip", "overflow-hidden", @@ -9575,76 +8289,6 @@ exports[`getClassList 1`] = ` "pb-80", "pb-96", "pb-px", - "pbe-0", - "pbe-0.5", - "pbe-1", - "pbe-1.5", - "pbe-2", - "pbe-2.5", - "pbe-3", - "pbe-3.5", - "pbe-4", - "pbe-5", - "pbe-6", - "pbe-7", - "pbe-8", - "pbe-9", - "pbe-10", - "pbe-11", - "pbe-12", - "pbe-14", - "pbe-16", - "pbe-20", - "pbe-24", - "pbe-28", - "pbe-32", - "pbe-36", - "pbe-40", - "pbe-44", - "pbe-48", - "pbe-52", - "pbe-56", - "pbe-60", - "pbe-64", - "pbe-72", - "pbe-80", - "pbe-96", - "pbe-px", - "pbs-0", - "pbs-0.5", - "pbs-1", - "pbs-1.5", - "pbs-2", - "pbs-2.5", - "pbs-3", - "pbs-3.5", - "pbs-4", - "pbs-5", - "pbs-6", - "pbs-7", - "pbs-8", - "pbs-9", - "pbs-10", - "pbs-11", - "pbs-12", - "pbs-14", - "pbs-16", - "pbs-20", - "pbs-24", - "pbs-28", - "pbs-32", - "pbs-36", - "pbs-40", - "pbs-44", - "pbs-48", - "pbs-52", - "pbs-56", - "pbs-60", - "pbs-64", - "pbs-72", - "pbs-80", - "pbs-96", - "pbs-px", "pe-0", "pe-0.5", "pe-1", @@ -9773,7 +8417,6 @@ exports[`getClassList 1`] = ` "placeholder-current/90", "placeholder-current/95", "placeholder-current/100", - "placeholder-current/background", "placeholder-inherit", "placeholder-inherit/0", "placeholder-inherit/5", @@ -9796,7 +8439,6 @@ exports[`getClassList 1`] = ` "placeholder-inherit/90", "placeholder-inherit/95", "placeholder-inherit/100", - "placeholder-inherit/background", "placeholder-transparent", "placeholder-transparent/0", "placeholder-transparent/5", @@ -9819,7 +8461,6 @@ exports[`getClassList 1`] = ` "placeholder-transparent/90", "placeholder-transparent/95", "placeholder-transparent/100", - "placeholder-transparent/background", "pointer-events-auto", "pointer-events-none", "pr-0", @@ -10094,7 +8735,6 @@ exports[`getClassList 1`] = ` "ring-current/90", "ring-current/95", "ring-current/100", - "ring-current/background", "ring-inherit", "ring-inherit/0", "ring-inherit/5", @@ -10117,7 +8757,6 @@ exports[`getClassList 1`] = ` "ring-inherit/90", "ring-inherit/95", "ring-inherit/100", - "ring-inherit/background", "ring-inset", "ring-offset-0", "ring-offset-1", @@ -10146,7 +8785,6 @@ exports[`getClassList 1`] = ` "ring-offset-current/90", "ring-offset-current/95", "ring-offset-current/100", - "ring-offset-current/background", "ring-offset-inherit", "ring-offset-inherit/0", "ring-offset-inherit/5", @@ -10169,7 +8807,6 @@ exports[`getClassList 1`] = ` "ring-offset-inherit/90", "ring-offset-inherit/95", "ring-offset-inherit/100", - "ring-offset-inherit/background", "ring-offset-transparent", "ring-offset-transparent/0", "ring-offset-transparent/5", @@ -10192,7 +8829,6 @@ exports[`getClassList 1`] = ` "ring-offset-transparent/90", "ring-offset-transparent/95", "ring-offset-transparent/100", - "ring-offset-transparent/background", "ring-transparent", "ring-transparent/0", "ring-transparent/5", @@ -10215,7 +8851,6 @@ exports[`getClassList 1`] = ` "ring-transparent/90", "ring-transparent/95", "ring-transparent/100", - "ring-transparent/background", "rotate-0", "rotate-1", "rotate-2", @@ -10453,76 +9088,6 @@ exports[`getClassList 1`] = ` "scroll-mb-80", "scroll-mb-96", "scroll-mb-px", - "scroll-mbe-0", - "scroll-mbe-0.5", - "scroll-mbe-1", - "scroll-mbe-1.5", - "scroll-mbe-2", - "scroll-mbe-2.5", - "scroll-mbe-3", - "scroll-mbe-3.5", - "scroll-mbe-4", - "scroll-mbe-5", - "scroll-mbe-6", - "scroll-mbe-7", - "scroll-mbe-8", - "scroll-mbe-9", - "scroll-mbe-10", - "scroll-mbe-11", - "scroll-mbe-12", - "scroll-mbe-14", - "scroll-mbe-16", - "scroll-mbe-20", - "scroll-mbe-24", - "scroll-mbe-28", - "scroll-mbe-32", - "scroll-mbe-36", - "scroll-mbe-40", - "scroll-mbe-44", - "scroll-mbe-48", - "scroll-mbe-52", - "scroll-mbe-56", - "scroll-mbe-60", - "scroll-mbe-64", - "scroll-mbe-72", - "scroll-mbe-80", - "scroll-mbe-96", - "scroll-mbe-px", - "scroll-mbs-0", - "scroll-mbs-0.5", - "scroll-mbs-1", - "scroll-mbs-1.5", - "scroll-mbs-2", - "scroll-mbs-2.5", - "scroll-mbs-3", - "scroll-mbs-3.5", - "scroll-mbs-4", - "scroll-mbs-5", - "scroll-mbs-6", - "scroll-mbs-7", - "scroll-mbs-8", - "scroll-mbs-9", - "scroll-mbs-10", - "scroll-mbs-11", - "scroll-mbs-12", - "scroll-mbs-14", - "scroll-mbs-16", - "scroll-mbs-20", - "scroll-mbs-24", - "scroll-mbs-28", - "scroll-mbs-32", - "scroll-mbs-36", - "scroll-mbs-40", - "scroll-mbs-44", - "scroll-mbs-48", - "scroll-mbs-52", - "scroll-mbs-56", - "scroll-mbs-60", - "scroll-mbs-64", - "scroll-mbs-72", - "scroll-mbs-80", - "scroll-mbs-96", - "scroll-mbs-px", "scroll-me-0", "scroll-me-0.5", "scroll-me-1", @@ -10838,76 +9403,6 @@ exports[`getClassList 1`] = ` "scroll-pb-80", "scroll-pb-96", "scroll-pb-px", - "scroll-pbe-0", - "scroll-pbe-0.5", - "scroll-pbe-1", - "scroll-pbe-1.5", - "scroll-pbe-2", - "scroll-pbe-2.5", - "scroll-pbe-3", - "scroll-pbe-3.5", - "scroll-pbe-4", - "scroll-pbe-5", - "scroll-pbe-6", - "scroll-pbe-7", - "scroll-pbe-8", - "scroll-pbe-9", - "scroll-pbe-10", - "scroll-pbe-11", - "scroll-pbe-12", - "scroll-pbe-14", - "scroll-pbe-16", - "scroll-pbe-20", - "scroll-pbe-24", - "scroll-pbe-28", - "scroll-pbe-32", - "scroll-pbe-36", - "scroll-pbe-40", - "scroll-pbe-44", - "scroll-pbe-48", - "scroll-pbe-52", - "scroll-pbe-56", - "scroll-pbe-60", - "scroll-pbe-64", - "scroll-pbe-72", - "scroll-pbe-80", - "scroll-pbe-96", - "scroll-pbe-px", - "scroll-pbs-0", - "scroll-pbs-0.5", - "scroll-pbs-1", - "scroll-pbs-1.5", - "scroll-pbs-2", - "scroll-pbs-2.5", - "scroll-pbs-3", - "scroll-pbs-3.5", - "scroll-pbs-4", - "scroll-pbs-5", - "scroll-pbs-6", - "scroll-pbs-7", - "scroll-pbs-8", - "scroll-pbs-9", - "scroll-pbs-10", - "scroll-pbs-11", - "scroll-pbs-12", - "scroll-pbs-14", - "scroll-pbs-16", - "scroll-pbs-20", - "scroll-pbs-24", - "scroll-pbs-28", - "scroll-pbs-32", - "scroll-pbs-36", - "scroll-pbs-40", - "scroll-pbs-44", - "scroll-pbs-48", - "scroll-pbs-52", - "scroll-pbs-56", - "scroll-pbs-60", - "scroll-pbs-64", - "scroll-pbs-72", - "scroll-pbs-80", - "scroll-pbs-96", - "scroll-pbs-px", "scroll-pe-0", "scroll-pe-0.5", "scroll-pe-1", @@ -11154,150 +9649,6 @@ exports[`getClassList 1`] = ` "scroll-py-96", "scroll-py-px", "scroll-smooth", - "scrollbar-auto", - "scrollbar-gutter-auto", - "scrollbar-gutter-both", - "scrollbar-gutter-stable", - "scrollbar-none", - "scrollbar-thin", - "scrollbar-thumb-current", - "scrollbar-thumb-current/0", - "scrollbar-thumb-current/5", - "scrollbar-thumb-current/10", - "scrollbar-thumb-current/15", - "scrollbar-thumb-current/20", - "scrollbar-thumb-current/25", - "scrollbar-thumb-current/30", - "scrollbar-thumb-current/35", - "scrollbar-thumb-current/40", - "scrollbar-thumb-current/45", - "scrollbar-thumb-current/50", - "scrollbar-thumb-current/55", - "scrollbar-thumb-current/60", - "scrollbar-thumb-current/65", - "scrollbar-thumb-current/70", - "scrollbar-thumb-current/75", - "scrollbar-thumb-current/80", - "scrollbar-thumb-current/85", - "scrollbar-thumb-current/90", - "scrollbar-thumb-current/95", - "scrollbar-thumb-current/100", - "scrollbar-thumb-current/background", - "scrollbar-thumb-inherit", - "scrollbar-thumb-inherit/0", - "scrollbar-thumb-inherit/5", - "scrollbar-thumb-inherit/10", - "scrollbar-thumb-inherit/15", - "scrollbar-thumb-inherit/20", - "scrollbar-thumb-inherit/25", - "scrollbar-thumb-inherit/30", - "scrollbar-thumb-inherit/35", - "scrollbar-thumb-inherit/40", - "scrollbar-thumb-inherit/45", - "scrollbar-thumb-inherit/50", - "scrollbar-thumb-inherit/55", - "scrollbar-thumb-inherit/60", - "scrollbar-thumb-inherit/65", - "scrollbar-thumb-inherit/70", - "scrollbar-thumb-inherit/75", - "scrollbar-thumb-inherit/80", - "scrollbar-thumb-inherit/85", - "scrollbar-thumb-inherit/90", - "scrollbar-thumb-inherit/95", - "scrollbar-thumb-inherit/100", - "scrollbar-thumb-inherit/background", - "scrollbar-thumb-transparent", - "scrollbar-thumb-transparent/0", - "scrollbar-thumb-transparent/5", - "scrollbar-thumb-transparent/10", - "scrollbar-thumb-transparent/15", - "scrollbar-thumb-transparent/20", - "scrollbar-thumb-transparent/25", - "scrollbar-thumb-transparent/30", - "scrollbar-thumb-transparent/35", - "scrollbar-thumb-transparent/40", - "scrollbar-thumb-transparent/45", - "scrollbar-thumb-transparent/50", - "scrollbar-thumb-transparent/55", - "scrollbar-thumb-transparent/60", - "scrollbar-thumb-transparent/65", - "scrollbar-thumb-transparent/70", - "scrollbar-thumb-transparent/75", - "scrollbar-thumb-transparent/80", - "scrollbar-thumb-transparent/85", - "scrollbar-thumb-transparent/90", - "scrollbar-thumb-transparent/95", - "scrollbar-thumb-transparent/100", - "scrollbar-thumb-transparent/background", - "scrollbar-track-current", - "scrollbar-track-current/0", - "scrollbar-track-current/5", - "scrollbar-track-current/10", - "scrollbar-track-current/15", - "scrollbar-track-current/20", - "scrollbar-track-current/25", - "scrollbar-track-current/30", - "scrollbar-track-current/35", - "scrollbar-track-current/40", - "scrollbar-track-current/45", - "scrollbar-track-current/50", - "scrollbar-track-current/55", - "scrollbar-track-current/60", - "scrollbar-track-current/65", - "scrollbar-track-current/70", - "scrollbar-track-current/75", - "scrollbar-track-current/80", - "scrollbar-track-current/85", - "scrollbar-track-current/90", - "scrollbar-track-current/95", - "scrollbar-track-current/100", - "scrollbar-track-current/background", - "scrollbar-track-inherit", - "scrollbar-track-inherit/0", - "scrollbar-track-inherit/5", - "scrollbar-track-inherit/10", - "scrollbar-track-inherit/15", - "scrollbar-track-inherit/20", - "scrollbar-track-inherit/25", - "scrollbar-track-inherit/30", - "scrollbar-track-inherit/35", - "scrollbar-track-inherit/40", - "scrollbar-track-inherit/45", - "scrollbar-track-inherit/50", - "scrollbar-track-inherit/55", - "scrollbar-track-inherit/60", - "scrollbar-track-inherit/65", - "scrollbar-track-inherit/70", - "scrollbar-track-inherit/75", - "scrollbar-track-inherit/80", - "scrollbar-track-inherit/85", - "scrollbar-track-inherit/90", - "scrollbar-track-inherit/95", - "scrollbar-track-inherit/100", - "scrollbar-track-inherit/background", - "scrollbar-track-transparent", - "scrollbar-track-transparent/0", - "scrollbar-track-transparent/5", - "scrollbar-track-transparent/10", - "scrollbar-track-transparent/15", - "scrollbar-track-transparent/20", - "scrollbar-track-transparent/25", - "scrollbar-track-transparent/30", - "scrollbar-track-transparent/35", - "scrollbar-track-transparent/40", - "scrollbar-track-transparent/45", - "scrollbar-track-transparent/50", - "scrollbar-track-transparent/55", - "scrollbar-track-transparent/60", - "scrollbar-track-transparent/65", - "scrollbar-track-transparent/70", - "scrollbar-track-transparent/75", - "scrollbar-track-transparent/80", - "scrollbar-track-transparent/85", - "scrollbar-track-transparent/90", - "scrollbar-track-transparent/95", - "scrollbar-track-transparent/100", - "scrollbar-track-transparent/background", "select-all", "select-auto", "select-none", @@ -11337,7 +9688,6 @@ exports[`getClassList 1`] = ` "shadow-current/90", "shadow-current/95", "shadow-current/100", - "shadow-current/background", "shadow-inherit", "shadow-inherit/0", "shadow-inherit/5", @@ -11360,7 +9710,6 @@ exports[`getClassList 1`] = ` "shadow-inherit/90", "shadow-inherit/95", "shadow-inherit/100", - "shadow-inherit/background", "shadow-initial", "shadow-none", "shadow-transparent", @@ -11385,7 +9734,6 @@ exports[`getClassList 1`] = ` "shadow-transparent/90", "shadow-transparent/95", "shadow-transparent/100", - "shadow-transparent/background", "shrink", "shrink-0", "size-0", @@ -11565,6 +9913,66 @@ exports[`getClassList 1`] = ` "space-y-reverse", "sr-only", "stacked-fractions", + "start-0", + "start-0.5", + "start-1", + "start-1.5", + "start-2", + "start-2.5", + "start-3", + "start-3.5", + "start-4", + "start-5", + "start-6", + "start-7", + "start-8", + "start-9", + "start-10", + "start-11", + "start-12", + "start-14", + "start-16", + "start-20", + "start-24", + "start-28", + "start-32", + "start-36", + "start-40", + "start-44", + "start-48", + "start-52", + "start-56", + "start-60", + "start-64", + "start-72", + "start-80", + "start-96", + "start-1/2", + "start-1/3", + "start-1/4", + "start-1/5", + "start-1/6", + "start-1/12", + "start-2/3", + "start-2/4", + "start-2/5", + "start-2/6", + "start-2/12", + "start-3/4", + "start-3/5", + "start-3/6", + "start-3/12", + "start-4/5", + "start-4/6", + "start-4/12", + "start-5/6", + "start-5/12", + "start-6/12", + "start-7/12", + "start-8/12", + "start-9/12", + "start-10/12", + "start-11/12", "start-auto", "start-full", "start-px", @@ -11596,7 +10004,6 @@ exports[`getClassList 1`] = ` "stroke-current/90", "stroke-current/95", "stroke-current/100", - "stroke-current/background", "stroke-inherit", "stroke-inherit/0", "stroke-inherit/5", @@ -11619,7 +10026,6 @@ exports[`getClassList 1`] = ` "stroke-inherit/90", "stroke-inherit/95", "stroke-inherit/100", - "stroke-inherit/background", "stroke-none", "stroke-transparent", "stroke-transparent/0", @@ -11643,11 +10049,7 @@ exports[`getClassList 1`] = ` "stroke-transparent/90", "stroke-transparent/95", "stroke-transparent/100", - "stroke-transparent/background", "subpixel-antialiased", - "tab-2", - "tab-4", - "tab-8", "table", "table-auto", "table-caption", @@ -11685,7 +10087,6 @@ exports[`getClassList 1`] = ` "text-current/90", "text-current/95", "text-current/100", - "text-current/background", "text-ellipsis", "text-end", "text-inherit", @@ -11710,7 +10111,6 @@ exports[`getClassList 1`] = ` "text-inherit/90", "text-inherit/95", "text-inherit/100", - "text-inherit/background", "text-justify", "text-left", "text-nowrap", @@ -11738,7 +10138,6 @@ exports[`getClassList 1`] = ` "text-shadow-current/90", "text-shadow-current/95", "text-shadow-current/100", - "text-shadow-current/background", "text-shadow-inherit", "text-shadow-inherit/0", "text-shadow-inherit/5", @@ -11761,7 +10160,6 @@ exports[`getClassList 1`] = ` "text-shadow-inherit/90", "text-shadow-inherit/95", "text-shadow-inherit/100", - "text-shadow-inherit/background", "text-shadow-initial", "text-shadow-none", "text-shadow-transparent", @@ -11786,7 +10184,6 @@ exports[`getClassList 1`] = ` "text-shadow-transparent/90", "text-shadow-transparent/95", "text-shadow-transparent/100", - "text-shadow-transparent/background", "text-start", "text-transparent", "text-transparent/0", @@ -11810,7 +10207,6 @@ exports[`getClassList 1`] = ` "text-transparent/90", "text-transparent/95", "text-transparent/100", - "text-transparent/background", "text-wrap", "text-xs", "to-0%", @@ -11856,7 +10252,6 @@ exports[`getClassList 1`] = ` "to-current/90", "to-current/95", "to-current/100", - "to-current/background", "to-inherit", "to-inherit/0", "to-inherit/5", @@ -11879,7 +10274,6 @@ exports[`getClassList 1`] = ` "to-inherit/90", "to-inherit/95", "to-inherit/100", - "to-inherit/background", "to-transparent", "to-transparent/0", "to-transparent/5", @@ -11902,7 +10296,6 @@ exports[`getClassList 1`] = ` "to-transparent/90", "to-transparent/95", "to-transparent/100", - "to-transparent/background", "top-0", "top-0.5", "top-1", @@ -12271,7 +10664,6 @@ exports[`getClassList 1`] = ` "via-current/90", "via-current/95", "via-current/100", - "via-current/background", "via-inherit", "via-inherit/0", "via-inherit/5", @@ -12294,7 +10686,6 @@ exports[`getClassList 1`] = ` "via-inherit/90", "via-inherit/95", "via-inherit/100", - "via-inherit/background", "via-none", "via-transparent", "via-transparent/0", @@ -12318,7 +10709,6 @@ exports[`getClassList 1`] = ` "via-transparent/90", "via-transparent/95", "via-transparent/100", - "via-transparent/background", "visible", "w-0", "w-0.5", @@ -12415,16 +10805,6 @@ exports[`getClassList 1`] = ` "z-40", "z-50", "z-auto", - "zoom-50", - "zoom-75", - "zoom-90", - "zoom-95", - "zoom-100", - "zoom-105", - "zoom-110", - "zoom-125", - "zoom-150", - "zoom-200", ] `; diff --git a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap index 43e8fdf6f..6adc96113 100644 --- a/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/utilities.test.ts.snap @@ -1,8 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`border-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -183,13 +182,11 @@ exports[`border-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-b-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -370,387 +367,11 @@ exports[`border-b-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" -`; - -exports[`border-be-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { - --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; -} - -.border-be { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 1px; -} - -.border-be-0 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0; -} - -.border-be-2 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 2px; -} - -.border-be-4 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 4px; -} - -.border-be-123 { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 123px; -} - -.border-be-\\[0_1\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0 1; -} - -.border-be-\\[0_2px_0_2px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 0 2px 0 2px; -} - -.border-be-\\[12px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 12px; -} - -.border-be-\\[12px_8px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: 12px 8px; -} - -.border-be-\\[length\\:var\\(--my-width\\)\\], .border-be-\\[line-width\\:var\\(--my-width\\)\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: var(--my-width); -} - -.border-be-\\[medium\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: medium; -} - -.border-be-\\[thick\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thick; -} - -.border-be-\\[thin\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thin; -} - -.border-be-\\[thin_2px\\] { - border-block-end-style: var(--tw-border-style); - border-block-end-width: thin 2px; -} - -.border-be-\\[\\#0088cc\\] { - border-block-end-color: #08c; -} - -.border-be-\\[\\#0088cc\\]\\/50 { - border-block-end-color: oklab(59.9824% -.067 -.124 / .5); -} - -.border-be-\\[color\\:var\\(--my-color\\)\\], .border-be-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-end-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-be-\\[var\\(--my-color\\)\\], .border-be-\\[var\\(--my-color\\)\\]\\/50 { - border-block-end-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-\\[var\\(--my-color\\)\\]\\/50 { - border-block-end-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-be-blue-500 { - border-block-end-color: var(--border-color-blue-500); -} - -.border-be-current, .border-be-current\\/50 { - border-block-end-color: currentColor; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-current\\/50 { - border-block-end-color: color-mix(in oklab, currentcolor 50%, transparent); - } -} - -.border-be-inherit { - border-block-end-color: inherit; -} - -.border-be-red-500 { - border-block-end-color: var(--color-red-500); -} - -.border-be-red-500\\/2\\.5 { - border-block-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.5 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } -} - -.border-be-red-500\\/2\\.25 { - border-block-end-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.25 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } -} - -.border-be-red-500\\/2\\.75 { - border-block-end-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/2\\.75 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } -} - -.border-be-red-500\\/50 { - border-block-end-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-be-red-500\\/50 { - border-block-end-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } -} - -.border-be-transparent { - border-block-end-color: #0000; -} - -@property --tw-border-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} -" -`; - -exports[`border-bs-* 1`] = ` -" -@layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-border-style: solid; - } - } -} - -:root, :host { - --color-red-500: #ef4444; - --border-color-blue-500: #3b82f6; -} - -.border-bs { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 1px; -} - -.border-bs-0 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0; -} - -.border-bs-2 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 2px; -} - -.border-bs-4 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 4px; -} - -.border-bs-123 { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 123px; -} - -.border-bs-\\[0_1\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0 1; -} - -.border-bs-\\[0_2px_0_2px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 0 2px 0 2px; -} - -.border-bs-\\[12px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 12px; -} - -.border-bs-\\[12px_8px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: 12px 8px; -} - -.border-bs-\\[length\\:var\\(--my-width\\)\\], .border-bs-\\[line-width\\:var\\(--my-width\\)\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: var(--my-width); -} - -.border-bs-\\[medium\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: medium; -} - -.border-bs-\\[thick\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thick; -} - -.border-bs-\\[thin\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thin; -} - -.border-bs-\\[thin_2px\\] { - border-block-start-style: var(--tw-border-style); - border-block-start-width: thin 2px; -} - -.border-bs-\\[\\#0088cc\\] { - border-block-start-color: #08c; -} - -.border-bs-\\[\\#0088cc\\]\\/50 { - border-block-start-color: oklab(59.9824% -.067 -.124 / .5); -} - -.border-bs-\\[color\\:var\\(--my-color\\)\\], .border-bs-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-start-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-\\[color\\:var\\(--my-color\\)\\]\\/50 { - border-block-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-bs-\\[var\\(--my-color\\)\\], .border-bs-\\[var\\(--my-color\\)\\]\\/50 { - border-block-start-color: var(--my-color); -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-\\[var\\(--my-color\\)\\]\\/50 { - border-block-start-color: color-mix(in oklab, var(--my-color) 50%, transparent); - } -} - -.border-bs-blue-500 { - border-block-start-color: var(--border-color-blue-500); -} - -.border-bs-current, .border-bs-current\\/50 { - border-block-start-color: currentColor; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-current\\/50 { - border-block-start-color: color-mix(in oklab, currentcolor 50%, transparent); - } -} - -.border-bs-inherit { - border-block-start-color: inherit; -} - -.border-bs-red-500 { - border-block-start-color: var(--color-red-500); -} - -.border-bs-red-500\\/2\\.5 { - border-block-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.5 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent); - } -} - -.border-bs-red-500\\/2\\.25 { - border-block-start-color: #ef444406; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.25 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent); - } -} - -.border-bs-red-500\\/2\\.75 { - border-block-start-color: #ef444407; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/2\\.75 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent); - } -} - -.border-bs-red-500\\/50 { - border-block-start-color: #ef444480; -} - -@supports (color: color-mix(in lab, red, red)) { - .border-bs-red-500\\/50 { - border-block-start-color: color-mix(in oklab, var(--color-red-500) 50%, transparent); - } -} - -.border-bs-transparent { - border-block-start-color: #0000; -} - -@property --tw-border-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} -" +}" `; exports[`border-e-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -931,13 +552,11 @@ exports[`border-e-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-l-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -1118,13 +737,11 @@ exports[`border-l-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-r-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -1305,13 +922,11 @@ exports[`border-r-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-s-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -1492,13 +1107,11 @@ exports[`border-s-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-t-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -1679,13 +1292,11 @@ exports[`border-t-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-x-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -1866,13 +1477,11 @@ exports[`border-x-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; exports[`border-y-* 1`] = ` -" -@layer properties { +"@layer properties { @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { *, :before, :after, ::backdrop { --tw-border-style: solid; @@ -2053,6 +1662,5 @@ exports[`border-y-* 1`] = ` syntax: "*"; inherits: false; initial-value: solid; -} -" +}" `; diff --git a/packages/tailwindcss/src/apply.ts b/packages/tailwindcss/src/apply.ts index 2d5e5b847..87361804f 100644 --- a/packages/tailwindcss/src/apply.ts +++ b/packages/tailwindcss/src/apply.ts @@ -1,11 +1,9 @@ import { Features } from '.' -import { cloneAstNode, rule, toCss, type AstNode } from './ast' +import { rule, toCss, walk, WalkAction, type AstNode } from './ast' import { compileCandidates } from './compile' import type { DesignSystem } from './design-system' import type { SourceLocation } from './source-maps/source' import { DefaultMap } from './utils/default-map' -import { segment } from './utils/segment' -import { walk, WalkAction } from './walk' export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { let features = Features.None @@ -26,7 +24,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { let definitions = new DefaultMap(() => new Set()) // Collect all new `@utility` definitions and all `@apply` rules first - walk([root], (node, ctx) => { + walk([root], (node, { parent, path }) => { if (node.kind !== 'at-rule') return // Do not allow `@apply` rules inside `@keyframes` rules. @@ -62,15 +60,16 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { if (node.name === '@apply') { // `@apply` cannot be top-level, so we need to have a parent such that we // can replace the `@apply` node with the actual utility classes later. - if (ctx.parent === null) return + if (parent === null) return features |= Features.AtApply - parents.add(ctx.parent) + parents.add(parent) for (let dependency of resolveApplyDependencies(node, designSystem)) { // Mark every parent in the path as having a dependency to that utility. - for (let parent of ctx.path()) { + for (let parent of path) { + if (parent === node) continue if (!parents.has(parent)) continue dependencies.get(parent).add(dependency) } @@ -158,127 +157,26 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { for (let parent of sorted) { if (!('nodes' in parent)) continue - walk(parent.nodes, (child) => { + walk(parent.nodes, (child, { replaceWith }) => { if (child.kind !== 'at-rule' || child.name !== '@apply') return let parts = child.params.split(/(\s+)/g) let candidateOffsets: Record = {} - let normalIdents: string[] = [] - let dashedIdents: string[] = [] let offset = 0 for (let [idx, part] of parts.entries()) { - if (idx % 2 === 0) { - if (part[0] === '-' && part[1] === '-') { - dashedIdents.push(part) - } else { - normalIdents.push(part) - } - - candidateOffsets[part] = offset - } - + if (idx % 2 === 0) candidateOffsets[part] = offset offset += part.length } - if (dashedIdents.length) { - // If we have an `@apply` that only consists of dashed idents then the - // user is intending to use a CSS mixin: - // https://drafts.csswg.org/css-mixins-1/#apply-rule - // - // These are not considered utilities and need to be emitted literally. - if (normalIdents.length === 0) return WalkAction.Skip - - // If we find a dashed ident *here* it means that someone is trying - // to use mixins and our `@apply` behavior together. - // - // This is invalid and the rules must be written separately. Let the - // user know they need to move them into a separate rule. - let list = dashedIdents.join(' ') - - throw new Error( - `You cannot use \`@apply\` with both mixins and utilities. Please move \`@apply ${list}\` into a separate rule.`, - ) - } - - let hasBody = child.nodes.length > 0 - - if (hasBody && normalIdents.length) { - let list = normalIdents.join(' ') - - throw new Error(`The rule \`@apply ${list}\` must not have a body.`) - } - // Replace the `@apply` rule with the actual utility classes { // Parse the candidates to an AST that we can replace the `@apply` rule // with. let candidates = Object.keys(candidateOffsets) let compiled = compileCandidates(candidates, designSystem, { - respectImportant: false, onInvalidCandidate: (candidate) => { - // When using prefix, make sure prefix is used in candidate - if (designSystem.theme.prefix && !candidate.startsWith(designSystem.theme.prefix)) { - throw new Error( - `Cannot apply unprefixed utility class \`${candidate}\`. Did you mean \`${designSystem.theme.prefix}:${candidate}\`?`, - ) - } - - // When the utility is blocklisted, let the user know - // - // Note: `@apply` is processed before handling incoming classes from - // template files. This means that the `invalidCandidates` set will - // only contain explicit classes via: - // - // - `blocklist` from a JS config - // - `@source not inline(…)` - if (designSystem.invalidCandidates.has(candidate)) { - throw new Error( - `Cannot apply utility class \`${candidate}\` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes`, - ) - } - - // Verify if variants exist - let parts = segment(candidate, ':') - if (parts.length > 1) { - let utility = parts.pop()! - - // Ensure utility on its own compiles, if not, we will fallback to - // the next error - if (designSystem.candidatesToCss([utility])[0]) { - let compiledVariants = designSystem.candidatesToCss( - parts.map((variant) => `${variant}:[--tw-variant-check:1]`), - ) - let unknownVariants = parts.filter((_, idx) => compiledVariants[idx] === null) - if (unknownVariants.length > 0) { - if (unknownVariants.length === 1) { - throw new Error( - `Cannot apply utility class \`${candidate}\` because the ${unknownVariants.map((variant) => `\`${variant}\``)} variant does not exist.`, - ) - } else { - let formatter = new Intl.ListFormat('en', { - style: 'long', - type: 'conjunction', - }) - throw new Error( - `Cannot apply utility class \`${candidate}\` because the ${formatter.format(unknownVariants.map((variant) => `\`${variant}\``))} variants do not exist.`, - ) - } - } - } - } - - // When the theme is empty, it means that no theme was loaded and - // `@import "tailwindcss"`, `@reference "app.css"` or similar is - // very likely missing. - if (designSystem.theme.size === 0) { - throw new Error( - `Cannot apply unknown utility class \`${candidate}\`. Are you using CSS modules or similar and missing \`@reference\`? https://tailwindcss.com/docs/functions-and-directives#reference-directive`, - ) - } - - // Fallback to most generic error message - throw new Error(`Cannot apply unknown utility class \`${candidate}\``) + throw new Error(`Cannot apply unknown utility class: ${candidate}`) }, }) @@ -288,7 +186,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { let candidate = compiled.nodeSorting.get(node)?.candidate let candidateOffset = candidate ? candidateOffsets[candidate] : undefined - node = cloneAstNode(node) + node = structuredClone(node) if (!src || !candidate || candidateOffset === undefined) { // While the original nodes may have come from an `@utility` we still @@ -330,7 +228,7 @@ export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { } } - return WalkAction.Replace(newNodes) + replaceWith(newNodes) } }) } diff --git a/packages/tailwindcss/src/ast.bench.ts b/packages/tailwindcss/src/ast.bench.ts index 034faee27..a2d8b2892 100644 --- a/packages/tailwindcss/src/ast.bench.ts +++ b/packages/tailwindcss/src/ast.bench.ts @@ -1,5 +1,5 @@ -import { bench, describe } from 'vitest' -import { cloneAstNode, toCss, type AstNode } from './ast' +import { bench } from 'vitest' +import { toCss } from './ast' import * as CSS from './css-parser' const css = String.raw @@ -19,46 +19,10 @@ const input = css` ` const ast = CSS.parse(input) -describe('AST to CSS', () => { - bench('toCss', () => { - toCss(ast) - }) - - bench('toCss with source maps', () => { - toCss(ast, true) - }) +bench('toCss', () => { + toCss(ast) }) -describe('Cloning AST nodes', () => { - bench('cloneAstNode()', () => { - ast.map(cloneAstNode) - }) - - bench('cloneAstNode (with spread)', () => { - ast.map(cloneAstNodeSpread) - }) - - bench('structuredClone()', () => { - structuredClone(ast) - }) +bench('toCss with source maps', () => { + toCss(ast, true) }) - -function cloneAstNodeSpread(node: T): T { - switch (node.kind) { - case 'rule': - case 'at-rule': - case 'at-root': - return { ...node, nodes: node.nodes.map(cloneAstNodeSpread) } - - case 'context': - return { ...node, context: { ...node.context }, nodes: node.nodes.map(cloneAstNodeSpread) } - - case 'declaration': - case 'comment': - return { ...node } - - default: - node satisfies never - throw new Error(`Unknown node kind: ${(node as any).kind}`) - } -} diff --git a/packages/tailwindcss/src/ast.test.ts b/packages/tailwindcss/src/ast.test.ts index 14092c9f5..9f879d64b 100644 --- a/packages/tailwindcss/src/ast.test.ts +++ b/packages/tailwindcss/src/ast.test.ts @@ -1,28 +1,26 @@ -import { describe, expect, it, test } from 'vitest' +import { expect, it } from 'vitest' import { + atRule, context, - cssContext, decl, - handleNesting, optimizeAst, - rule, styleRule, toCss, + walk, + WalkAction, type AstNode, } from './ast' import * as CSS from './css-parser' import { buildDesignSystem } from './design-system' -import { pretty } from './test-utils/run' import { Theme } from './theme' -import { walk, WalkAction } from './walk' const css = String.raw const defaultDesignSystem = buildDesignSystem(new Theme()) it('should pretty print an AST', () => { - expect(pretty(toCss(CSS.parse('.foo{color:red;&:hover{color:blue;}}')))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(optimizeAst(CSS.parse('.foo{color:red;&:hover{color:blue;}}'), defaultDesignSystem))) + .toMatchInlineSnapshot(` + ".foo { color: red; &:hover { color: blue; @@ -33,7 +31,7 @@ it('should pretty print an AST', () => { }) it('allows the placement of context nodes', () => { - let ast: AstNode[] = [ + const ast = [ styleRule('.foo', [decl('color', 'red')]), context({ context: 'a' }, [ styleRule('.bar', [ @@ -50,18 +48,17 @@ it('allows the placement of context nodes', () => { let blueContext let greenContext - walk(ast, (node, _ctx) => { + walk(ast, (node, { context }) => { if (node.kind !== 'declaration') return - let ctx = cssContext(_ctx) switch (node.value) { case 'red': - redContext = ctx.context + redContext = context break case 'blue': - blueContext = ctx.context + blueContext = context break case 'green': - greenContext = ctx.context + greenContext = context break } }) @@ -70,9 +67,8 @@ it('allows the placement of context nodes', () => { expect(blueContext).toEqual({ context: 'a' }) expect(greenContext).toEqual({ context: 'b' }) - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(optimizeAst(ast, defaultDesignSystem))).toMatchInlineSnapshot(` + ".foo { color: red; } .bar { @@ -85,6 +81,36 @@ it('allows the placement of context nodes', () => { `) }) +it('should stop walking when returning `WalkAction.Stop`', () => { + let ast = [ + styleRule('.foo', [styleRule('.nested', [styleRule('.bail', [decl('color', 'red')])])]), + styleRule('.bar'), + styleRule('.baz'), + styleRule('.qux'), + ] + + let seen = new Set() + + walk(ast, (node) => { + if (node.kind === 'rule') { + seen.add(node.selector) + } + + if (node.kind === 'rule' && node.selector === '.bail') { + return WalkAction.Stop + } + }) + + // We do not want to see `.bar`, `.baz`, or `.qux` because we bailed early + expect(seen).toMatchInlineSnapshot(` + Set { + ".foo", + ".nested", + ".bail", + } + `) +}) + it('should not emit empty rules once optimized', () => { let ast = CSS.parse(css` /* Empty rule */ @@ -127,15 +153,13 @@ it('should not emit empty rules once optimized', () => { /* Exceptions: */ @charset "UTF-8"; @layer foo, bar, baz; - @layer foo, bar, baz; /* Will be deduped */ @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @import url('https://fonts.googleapis.com/css2?family=Cedarville+Cursive&display=swap'); `) - expect(pretty(toCss(ast))).toMatchInlineSnapshot(` - " - .foo { + expect(toCss(ast)).toMatchInlineSnapshot(` + ".foo { } .foo { .bar { @@ -162,16 +186,14 @@ it('should not emit empty rules once optimized', () => { } @charset "UTF-8"; @layer foo, bar, baz; - @layer foo, bar, baz; @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @import url('https://fonts.googleapis.com/css2?family=Cedarville+Cursive&display=swap'); " `) - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - @charset "UTF-8"; + expect(toCss(optimizeAst(ast, defaultDesignSystem))).toMatchInlineSnapshot(` + "@charset "UTF-8"; @layer foo, bar, baz; @custom-media --modern (color), (hover); @namespace 'http://www.w3.org/1999/xhtml'; @@ -180,1485 +202,309 @@ it('should not emit empty rules once optimized', () => { `) }) -it('should not emit exact duplicate declarations in the same rule', () => { - let ast = CSS.parse(css` - .foo { - color: red; - .bar { - color: green; - color: blue; - color: green; - } - color: red; +it('should only visit children once when calling `replaceWith` with single element array', () => { + let visited = new Set() + + let ast = [ + atRule('@media', '', [styleRule('.foo', [decl('color', 'blue')])]), + styleRule('.bar', [decl('color', 'blue')]), + ] + + walk(ast, (node, { replaceWith }) => { + if (visited.has(node)) { + throw new Error('Visited node twice') } - .foo { - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - `) + visited.add(node) - expect(pretty(toCss(ast))).toMatchInlineSnapshot(` - " - .foo { - color: red; - .bar { - color: green; - color: blue; - color: green; - } - color: red; - } - .foo { - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - " - `) - - expect(pretty(toCss(optimizeAst(ast, defaultDesignSystem)))).toMatchInlineSnapshot(` - " - .foo { - .bar { - color: green; - color: blue; - color: green; - } - color: red; - & { - color: green; - & { - color: red; - color: green; - color: blue; - } - color: red; - } - background: blue; - .bar { - color: green; - color: blue; - color: green; - } - caret-color: orange; - } - " - `) -}) - -it('should not emit color-mix() fallbacks inside @keyframes', () => { - let ast = CSS.parse(css` - @keyframes my-animation { - 0% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - 100% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - } - `) - - let theme = new Theme() - theme.add('--color-emerald-600', 'oklch(59.6% 0.145 163.225)') - - let design = buildDesignSystem(theme) - - expect(pretty(toCss(optimizeAst(ast, design)))).toMatchInlineSnapshot(` - " - @keyframes my-animation { - 0% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - 100% { - color: color-mix(in oklab, var(--color-emerald-600) 0%, transparent); - } - } - " - `) -}) - -describe('optimization', () => { - function optimize(input: string) { - let ast = CSS.parse(input) - walk(ast, { - exit(node) { - // Depending on where we use `@scope`, it's handled slightly differently: - // - // 1. In user authored CSS, we perform the normal CSS nesting and - // hoisting of `@scope` rules - // 2. In a `@custom-variant` definition, we need to swap the `@scope` - // with the parent definition. Otherwise you'll never get the - // expected "sandwich" effect. - // - // This little `@in-variant` hack is just a different way of - // differentiating between both cases for these tests. - if (node.kind === 'at-rule' && node.name === '@in-variant') { - return WalkAction.Replace(context({ source: 'variant' }, node.nodes)) - } - }, - }) - return pretty(toCss(handleNesting(ast))) - } - - // See: https://drafts.csswg.org/css-nesting-1/ - describe('CSS Nesting Module Level 1', () => { - it('uses the descendant combinator by default', async () => { - expect( - optimize(css` - .a { - element { - --x: 1; - } - .class { - --x: 2; - } - #id { - --x: 3; - } - :pseudo-class { - --x: 4; - } - ::pseudo-element { - --x: 5; - } - [attribute] { - --x: 6; - } - * { - --x: 7; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a element { - --x: 1; - } - .a .class { - --x: 2; - } - .a #id { - --x: 3; - } - .a :pseudo-class { - --x: 4; - } - .a ::pseudo-element { - --x: 5; - } - .a [attribute] { - --x: 6; - } - .a * { - --x: 7; - } - " - `) - }) - - it('should be possible to change the combinator', async () => { - expect( - optimize(css` - .a { - + .b { - --x: 1; - } - > .c { - --x: 2; - } - ~ .d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a + .b { - --x: 1; - } - .a > .c { - --x: 2; - } - .a ~ .d { - --x: 3; - } - " - `) - }) - - it('should replace the first rule, that contains `&` with `:scope`', async () => { - expect( - optimize(css` - /* Standalone */ - & { - --x: 1; - } - - /* In an at-rule */ - @supports (--y: 1) { - & { - --x: 2; - } - } - - /* With :is(…) */ - :is(&) { - --x: 3; - } - - /* In an at-rule, with :is(…) */ - @supports (--y: 2) { - :is(&) { - --x: 4; - } - } - - /* With multiple selectors */ - &, - .a { - --x: 5; - } - - /* With multiple selectors + :is(…) */ - :is(&), - .b { - --x: 6; - } - - /* With multiple selectors in an at-rule */ - @supports (--y: 3) { - &, - .c { - --x: 7; - } - } - - /* With multiple selectors in an at-rule + :is(…) */ - @supports (--y: 4) { - :is(&), - .d { - --x: 8; - } - } - `), - ).toMatchInlineSnapshot(` - " - :scope { - --x: 1; - } - @supports (--y: 1) { - :scope { - --x: 2; - } - } - :is(:scope) { - --x: 3; - } - @supports (--y: 2) { - :is(:scope) { - --x: 4; - } - } - :scope, .a { - --x: 5; - } - :is(:scope), .b { - --x: 6; - } - @supports (--y: 3) { - :scope, .c { - --x: 7; - } - } - @supports (--y: 4) { - :is(:scope), .d { - --x: 8; - } - } - " - `) - }) - - it('should be possible to use `&` to explicitly match the parent', async () => { - expect( - optimize(css` - .a { - & + .b { - --x: 1; - } - & > .c { - --x: 2; - } - & ~ .d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a + .b { - --x: 1; - } - .a > .c { - --x: 2; - } - .a ~ .d { - --x: 3; - } - " - `) - }) - - it('should be possible to use `&` in a different location', async () => { - expect( - optimize(css` - .a { - .b & { - --x: 1; - } - .c + & { - --x: 2; - } - .d > & { - --x: 3; - } - .e ~ & { - --x: 4; - } - } - `), - ).toMatchInlineSnapshot(` - " - .b .a { - --x: 1; - } - .c + .a { - --x: 2; - } - .d > .a { - --x: 3; - } - .e ~ .a { - --x: 4; - } - " - `) - }) - - it('should be possible to use `&` on its own', async () => { - expect( - optimize(css` - .a { - & { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` nested in `:is(…)`', async () => { - expect( - optimize(css` - .a { - :is(&) { - --x: 1; - } - :is(:is(:is(&))) { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - :is(.a) { - --x: 1; - } - :is(:is(:is(.a))) { - --x: 2; - } - " - `) - }) - - it('should be possible to handle nesting with a parent selector list', async () => { - expect( - optimize(css` - .a, - .b { - .c, - .d & { - --x: 1; - &:hover { - --x: 2; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - :is(.a, .b) .c, .d :is(.a, .b) { - --x: 1; - &:hover { - --x: 2; - } - } - " - `) - }) - - it('should not replace `\&`', () => { - expect( - optimize(css` - .a { - .b-\& { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a .b-\\& { - --x: 1; - } - " - `) - }) - - it('should not replace `&` as part of a string', () => { - expect( - optimize(css` - .a { - [data-b='c&d'] { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a [data-b='c&d'] { - --x: 1; - } - " - `) - }) - - it.each([ - ['element', '&element', ':is(element)element'], // Invalid CSS - ['element', 'element&', 'element:is(element)'], - ['element', '&.class', 'element.class'], - ['element', '.class&', '.class:is(element)'], // Optimization: element.class - ['element', '&#id', 'element#id'], - ['element', '#id&', '#id:is(element)'], // Optimization: element#id - ['element', '&:hover', 'element:hover'], - ['element', ':hover&', ':hover:is(element)'], - ['element', '&::before', 'element::before'], - ['element', '::before&', '::before:is(element)'], // Invalid CSS - ['element', '&:not(.x)', 'element:not(.x)'], - ['element', ':not(.x)&', ':not(.x):is(element)'], // Optimization: element:not(.x) - ['element', '&[attribute]', 'element[attribute]'], - ['element', '[attribute]&', '[attribute]:is(element)'], // Optimization: element[attribute] - ['element', '&*', ':is(element)*'], // Invalid CSS - ['element', '*&', 'element'], - - ['.class', '&element', ':is(.class)element'], // Invalid CSS - ['.class', 'element&', 'element.class'], - ['.class', '&.class', '.class.class'], - ['.class', '.class&', '.class.class'], - ['.class', '&#id', '.class#id'], - ['.class', '#id&', '#id.class'], - ['.class', '&:hover', '.class:hover'], - ['.class', ':hover&', ':hover.class'], - ['.class', '&::before', '.class::before'], - ['.class', '::before&', '::before.class'], // Invalid CSS - ['.class', '&:not(.x)', '.class:not(.x)'], - ['.class', ':not(.x)&', ':not(.x).class'], - ['.class', '&[attribute]', '.class[attribute]'], - ['.class', '[attribute]&', '[attribute].class'], - ['.class', '&*', ':is(.class)*'], // Invalid CSS - ['.class', '*&', '.class'], - - ['#id', '&element', ':is(#id)element'], // Invalid CSS - ['#id', 'element&', 'element#id'], - ['#id', '&.class', '#id.class'], - ['#id', '.class&', '.class#id'], - ['#id', '&#id', '#id#id'], - ['#id', '#id&', '#id#id'], - ['#id', '&:hover', '#id:hover'], - ['#id', ':hover&', ':hover#id'], - ['#id', '&::before', '#id::before'], - ['#id', '::before&', '::before#id'], // Invalid CSS - ['#id', '&:not(.x)', '#id:not(.x)'], - ['#id', ':not(.x)&', ':not(.x)#id'], - ['#id', '&[attribute]', '#id[attribute]'], - ['#id', '[attribute]&', '[attribute]#id'], - ['#id', '&*', ':is(#id)*'], // Invalid CSS - ['#id', '*&', '#id'], - - [':hover', '&element', ':is(:hover)element'], // Invalid CSS - [':hover', 'element&', 'element:hover'], - [':hover', '&.class', ':hover.class'], - [':hover', '.class&', '.class:hover'], - [':hover', '&#id', ':hover#id'], - [':hover', '#id&', '#id:hover'], - [':hover', '&:hover', ':hover:hover'], - [':hover', ':hover&', ':hover:hover'], - [':hover', '&::before', ':hover::before'], - [':hover', '::before&', '::before:hover'], - [':hover', '&:not(.x)', ':hover:not(.x)'], - [':hover', ':not(.x)&', ':not(.x):hover'], - [':hover', '&[attribute]', ':hover[attribute]'], - [':hover', '[attribute]&', '[attribute]:hover'], - [':hover', '&*', ':is(:hover)*'], // Invalid CSS - [':hover', '*&', ':hover'], - - ['::before', '&element', ':is(::before)element'], // Invalid CSS - ['::before', 'element&', 'element::before'], - ['::before', '&.class', '::before.class'], - ['::before', '.class&', '.class::before'], - ['::before', '&#id', '::before#id'], - ['::before', '#id&', '#id::before'], - ['::before', '&:hover', '::before:hover'], - ['::before', ':hover&', ':hover::before'], - ['::before', '&::before', '::before::before'], - ['::before', '::before&', '::before::before'], - ['::before', '&:not(.x)', '::before:not(.x)'], - ['::before', ':not(.x)&', ':not(.x)::before'], - ['::before', '&[attribute]', '::before[attribute]'], - ['::before', '[attribute]&', '[attribute]::before'], - ['::before', '&*', ':is(::before)*'], // Invalid CSS - ['::before', '*&', '::before'], - - [':not(.x)', '&element', ':is(:not(.x))element'], // Invalid CSS - [':not(.x)', 'element&', 'element:not(.x)'], - [':not(.x)', '&.class', ':not(.x).class'], - [':not(.x)', '.class&', '.class:not(.x)'], - [':not(.x)', '&#id', ':not(.x)#id'], - [':not(.x)', '#id&', '#id:not(.x)'], - [':not(.x)', '&:hover', ':not(.x):hover'], - [':not(.x)', ':hover&', ':hover:not(.x)'], - [':not(.x)', '&::before', ':not(.x)::before'], - [':not(.x)', '::before&', '::before:not(.x)'], - [':not(.x)', '&:not(.x)', ':not(.x):not(.x)'], - [':not(.x)', ':not(.x)&', ':not(.x):not(.x)'], - [':not(.x)', '&[attribute]', ':not(.x)[attribute]'], - [':not(.x)', '[attribute]&', '[attribute]:not(.x)'], - [':not(.x)', '&*', ':is(:not(.x))*'], // Invalid CSS - [':not(.x)', '*&', ':not(.x)'], - - ['[attribute]', '&element', ':is([attribute])element'], // Invalid CSS - ['[attribute]', 'element&', 'element[attribute]'], - ['[attribute]', '&.class', '[attribute].class'], - ['[attribute]', '.class&', '.class[attribute]'], - ['[attribute]', '&#id', '[attribute]#id'], - ['[attribute]', '#id&', '#id[attribute]'], - ['[attribute]', '&:hover', '[attribute]:hover'], - ['[attribute]', ':hover&', ':hover[attribute]'], - ['[attribute]', '&::before', '[attribute]::before'], - ['[attribute]', '::before&', '::before[attribute]'], - ['[attribute]', '&:not(.x)', '[attribute]:not(.x)'], - ['[attribute]', ':not(.x)&', ':not(.x)[attribute]'], - ['[attribute]', '&[attribute]', '[attribute][attribute]'], - ['[attribute]', '[attribute]&', '[attribute][attribute]'], - ['[attribute]', '&*', ':is([attribute])*'], // Invalid CSS - ['[attribute]', '*&', '[attribute]'], - - ['*', '&element', ':is(*)element'], // Invalid CSS - ['*', 'element&', 'element:is(*)'], - ['*', '&.class', '.class'], - ['*', '.class&', '.class:is(*)'], // Optimization: *.class → .class - ['*', '&#id', '#id'], - ['*', '#id&', '#id:is(*)'], // Optimization: *#id → #id - ['*', '&:hover', ':hover'], - ['*', ':hover&', ':hover:is(*)'], - ['*', '&::before', '::before'], - ['*', '::before&', '::before:is(*)'], // Invalid CSS - ['*', '&:not(.x)', ':not(.x)'], - ['*', ':not(.x)&', ':not(.x):is(*)'], // Optimization: *:not(.x) → :not(.x) - ['*', '&[attribute]', '[attribute]'], - ['*', '[attribute]&', '[attribute]:is(*)'], // Optimization: *[attribute] → [attribute] - ['*', '&*', ':is(*)*'], // Invalid CSS - ['*', '*&', '*'], - - ['&', '&element', ':is(:scope)element'], // Invalid CSS - ['&', 'element&', 'element:scope'], - ['&', '&.class', ':scope.class'], - ['&', '.class&', '.class:scope'], - ['&', '&#id', ':scope#id'], - ['&', '#id&', '#id:scope'], - ['&', '&:hover', ':scope:hover'], - ['&', ':hover&', ':hover:scope'], - ['&', '&::before', ':scope::before'], - ['&', '::before&', '::before:scope'], - ['&', '&:not(.x)', ':scope:not(.x)'], - ['&', ':not(.x)&', ':not(.x):scope'], - ['&', '&[attribute]', ':scope[attribute]'], - ['&', '[attribute]&', '[attribute]:scope'], - ['&', '&*', ':is(:scope)*'], // Invalid CSS - ['&', '*&', ':scope'], - ])(`'%s { %s }' → '%s' (%#)`, async (root, nested, expected) => { - let optimized = optimize(toCss([rule(root, [rule(nested, [decl('--x', '0')])])])) - let ast = CSS.parse(optimized) - - let count = 0 - walk(ast, () => void count++) - - // 1 rule, 1 declaration - expect(count).toBe(2) - - if (ast[0].kind !== 'rule') throw new Error('expected a rule') - expect(ast[0].selector).toEqual(expected) - }) - - it('should not remove the `*` namespace from namespaced selectors', () => { - expect( - optimize(css` - .a { - & *|div { - color: red; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a *|div { - color: red; - } - " - `) - }) - - test('multiple selectors in the list are relative to the parent', async () => { - expect( - optimize(css` - .a, - .b { - --x: 1; - + .c { - --x: 2; - } - &.d { - --x: 3; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a, .b { - --x: 1; - + .c { - --x: 2; - } - &.d { - --x: 3; - } - } - " - `) - }) - - it('should be possible to use `&` multiple times', async () => { - expect( - optimize(css` - .a { - & .b & .c & .d { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a .b .a .c .a .d { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` multiple times in a row', async () => { - expect( - optimize(css` - .a { - &&& { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - .a.a.a { - --x: 1; - } - " - `) - }) - - it('should be possible to use `&` inside a selector', async () => { - expect( - optimize(css` - .a { - :not(&) { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - :not(.a) { - --x: 1; - } - " - `) - }) - - it('should be possible to use deeply nested CSS', async () => { - expect( - optimize(css` - .a, - .b { - --x: 1; - - .c & { - --x: 2; - - &:hover, - &:focus { - --x: 3; - .d { - --x: 4; - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - .a, .b { - --x: 1; - .c & { - --x: 2; - &:hover, &:focus { - --x: 3; - .d { - --x: 4; - } - } - } - } - " - `) - }) - - it('should properly split rules to guarantee specificity', async () => { - expect( - optimize(css` - .a { - --before: 1; - &:hover { - --inside: 1; - } - --after: 1; - } - `), - ).toMatchInlineSnapshot(` - " - .a { - --before: 1; - &:hover { - --inside: 1; - } - --after: 1; - } - " - `) - }) - - it.each([ - ['div', '&', 'div'], - ['div', '[before]&', '[before]:is(div)'], - ['div', '&[after]', 'div[after]'], - ['div', '[before]&[after]', '[before]:is(div)[after]'], - ['div', '[before] &', '[before] div'], - ['div', '& [after]', 'div [after]'], - ['div', '[before] & [after]', '[before] div [after]'], - - ['.parent', '&', '.parent'], - ['.parent', '[before]&', '[before].parent'], - ['.parent', '&[after]', '.parent[after]'], - ['.parent', '[before]&[after]', '[before].parent[after]'], - ['.parent', '[before] &', '[before] .parent'], - ['.parent', '& [after]', '.parent [after]'], - ['.parent', '[before] & [after]', '[before] .parent [after]'], - - ['.a > .b', '&', '.a > .b'], - ['.a > .b', '[before]&', '[before]:is(.a > .b)'], - ['.a > .b', '&[after]', '.a > .b[after]'], - ['.a > .b', '[before]&[after]', '[before]:is(.a > .b)[after]'], - ['.a > .b', '[before] &', '[before] :is(.a > .b)'], - ['.a > .b', '& [after]', '.a > .b [after]'], - ['.a > .b', '[before] & [after]', '[before] :is(.a > .b) [after]'], - ])(`should optimize '%s { %s }' → '%s' (%#)`, async (root, nested, expected) => { - let optimized = optimize(toCss([rule(root, [rule(nested, [decl('--x', '0')])])])) - let ast = CSS.parse(optimized) - - let count = 0 - walk(ast, () => void count++) - - // 1 rule, 1 declaration - expect(count).toBe(2) - - if (ast[0].kind !== 'rule') throw new Error('expected a rule') - expect(ast[0].selector).toEqual(expected) - }) - - it('should hoist at-rules', async () => { - expect( - optimize(css` - @layer utilities { - .a, - .b { - @media (print) { - --x: 1; - .c { - @media (min-width: 123px) { - --x: 2; - } - } - } - } - .d { - @media (print) { - @media (min-width: 123px) { - --x: 3; - } - } - } - } - @property --foo { - syntax: '*'; - } - @layer utilities { - .e { - @media (print) { - --x: 4; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @media (print) { - .a, .b { - --x: 1; - } - @media (min-width: 123px) { - :is(.a, .b) .c { - --x: 2; - } - .d { - --x: 3; - } - } - } - } - @property --foo { - syntax: '*'; - } - @layer utilities { - @media (print) { - .e { - --x: 4; - } - } - } - " - `) - }) - - it('should hoist `@scope` at-rules', async () => { - expect( - optimize(css` - @layer utilities { - .parent { - @in-variant { - @scope (.from) to (.to) { - --x: 1; - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope (.from) to (.to) { - .parent { - --x: 1; - } - } - } - " - `) - }) - - // We have an optimization where the moment we see a declaration that we - // stop handling the nesting and emit everything as-is. This won't work when - // we're dealing with `@scope` and we still want to hoist that out. - it('should hoist `@scope` at-rules, even when sibling declarations are present', async () => { - expect( - optimize(css` - @layer utilities { - .parent { - --x: 1; - @in-variant { - @scope (.from) { - .inside { - --y: 2; - } - } - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .parent { - --x: 1; - } - @scope (.from) { - .parent .inside { - --y: 2; - } - } - } - " - `) - }) - - it('should resolve `&` in the `@scope` prelude when hoisting', async () => { - // `&` in the `` selector refers to the elements matched by - // the nearest ancestor style rule, while `&` in the `` - // selector refers to the scoping root. - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - expect( - optimize(css` - .parent { - @scope (& > .scope) to (& .limit) { - .content { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent > .scope) to (:where(:scope) .limit) { - .content { - --x: 1; - } - } - " - `) - - // Selector lists need `:is(…)` to keep their meaning - expect( - optimize(css` - .parent-a, - .parent-b { - @scope (& > .from) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (:is(.parent-a, .parent-b) > .from) { - .inside { - --x: 1; - } - } - " - `) - - // The `` selector is optional, so the first group in the - // prelude is not necessarily the `` selector. - expect( - optimize(css` - .parent { - @scope to (& .to) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope to (:where(:scope) .to) { - .parent .inside { - --x: 1; - } - } - " - `) - - // The parent selector is substituted with the same logic as the nesting - // selector in regular style rules: `:is(…)` is used when substituting the - // parent as-is would change the meaning (e.g. a type selector cannot - // appear after a class selector in a compound selector), … - expect( - optimize(css` - main { - @scope (.card&) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.card:is(main)) { - .inside { - --x: 1; - } - } - " - `) - - // … and dropped when the substitution is provably equivalent, even for - // complex parent selectors - expect( - optimize(css` - .a .b { - @scope (& > .from) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.a .b > .from) { - .inside { - --x: 1; - } - } - " - `) - }) - - it('should wrap bare declarations inside `@scope` in a `&` rule', async () => { - // A bug in Lightning CSS causes an error when there are declarations - // directly in the `@scope`. To fix this we will ensure a rule with - // `:where(:scope)` is available. - expect( - optimize(css` - @scope (.from) to (.to) { - --x: 1; - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) to (.to) { - :where(:scope) { - --x: 1; - } - } - " - `) - - // When generated by a variant, the declarations resolve against the - // parent rule: the `@scope` rule scopes _where the utility applies_ - // (like nesting `@media` does, and like variants do). - // - // Note: this deviates from native CSS nesting, where the `` - // selector would be relative to `.parent` and the declarations would - // apply to the scoping root. User-authored `@scope` rules (without the - // `@in-variant` marker) keep those native semantics. - expect( - optimize(css` - .parent { - @in-variant { - @scope (.from) { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - .parent { - --x: 1; - } - } - " - `) - }) - - it('should replace `&` with `:where(:scope)` for rules directly inside `@scope`', async () => { - // Inside an `@scope` rule, the nesting selector behaves like - // `:where(:scope)` and matches the scoping root with zero specificity. - expect( - optimize(css` - @scope (.from) { - & > p { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - :where(:scope) > p { - --x: 1; - } - } - " - `) - }) - - it('should handle `&` deeply nested in `@scope` as normal references to a parent', async () => { - expect( - optimize(css` - @scope (.from) { - &, - .inside { - --x: 1; - - &:hover { - --x: 2; - } - - &:focus { - --x: 3; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) { - :where(:scope), .inside { - --x: 1; - &:hover { - --x: 2; - } - &:focus { - --x: 3; - } - } - } - " - `) - }) - - it('should compile user-authored `@scope` rules with their native CSS nesting semantics', async () => { - // The `` selector is relative to the parent rule (like - // nested style rule selectors are), the rules inside are relative to - // the scoping root, and `&` refers to the scoping root - expect( - optimize(css` - .parent { - @scope (.from) to (.to) { - .inside { - --x: 1; - } - - & > .child { - --x: 2; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .from) to (.to) { - .inside { - --x: 1; - } - :where(:scope) > .child { - --x: 2; - } - } - " - `) - - // A trailing `&` makes the parent rule the scoping root: the scoping - // roots are `.parent` elements inside `.from` elements - expect( - optimize(css` - .parent { - @scope (.from &) to (.to) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from .parent) to (.to) { - .inside { - --x: 1; - } - } - " - `) - - // Bare declarations apply to the scoping root. They are wrapped in a - // `:where(:scope)` rule, which is the spec's exact equivalent, because - // Lightning CSS drops bare declarations inside `@scope` entirely. - expect( - optimize(css` - .parent { - @scope (.from) { - --x: 1; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .from) { - :where(:scope) { - --x: 1; - } - } - " - `) - - // Also at the top level - expect( - optimize(css` - @scope (.from) to (.to) { - --x: 1; - - .inside { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.from) to (.to) { - :where(:scope) { - --x: 1; - } - .inside { - --x: 2; - } - } - " - `) - - // Every selector in a `` selector list is relative to the - // parent rule, whether it uses `&` or not - expect( - optimize(css` - .parent { - @scope (.a, & > .b) { - .inside { - --x: 1; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @scope (.parent .a, .parent > .b) { - .inside { - --x: 1; - } - } - " - `) - }) - - it('should leave `@property` and `@apply` alone', async () => { - expect( - optimize(css` - .foo { - .bar { - @apply text-red-500 hover:text-red-600; - } - } - - .baz { - @property --tw-content { - syntax: '*'; - initial-value: ''; - inherits: false; - } - - @property --tw-border-spacing-x { - syntax: ''; - inherits: false; - initial-value: 0; - } - } - `), - ).toMatchInlineSnapshot(` - " - .foo .bar { - @apply text-red-500 hover:text-red-600; - } - .baz { - @property --tw-content { - syntax: '*'; - initial-value: ''; - inherits: false; - } - @property --tw-border-spacing-x { - syntax: ''; - inherits: false; - initial-value: 0; - } - } - " - `) - }) - - it('should merge a body-less @layer with an @layer with the same name that has a body', async () => { - expect( - optimize(css` - @layer a; - - @layer a { - @layer b { - .x { - color: red; - } - } - } - `), - ).toMatchInlineSnapshot(` - " - @layer a { - @layer b { - .x { - color: red; - } - } - } - " - `) - }) - - it('should not get rid of `:is(…)` when the compound selector is part of a complex selector', async () => { - expect( - optimize(css` - .foo .bar { - .system &:focus { - --x: 1; - } - } - .foo:hover { - .system &:focus { - --x: 2; - } - } - `), - ).toMatchInlineSnapshot(` - " - .system :is(.foo .bar):focus { - --x: 1; - } - .system .foo:hover:focus { - --x: 2; - } - " - `) - }) - - it('should not dedupe adjacent at-rules with the same prelude but different bodies', async () => { - expect( - optimize(css` - @font-face { - font-family: 'A'; - src: url('/fonts/a.woff2'); - } - - @font-face { - font-family: 'B'; - src: url('/fonts/b.woff2'); - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - - @keyframes spin { - to { - transform: rotate(-360deg); - } - } - `), - ).toMatchInlineSnapshot(` - " - @font-face { - font-family: 'A'; - src: url('/fonts/a.woff2'); - } - @font-face { - font-family: 'B'; - src: url('/fonts/b.woff2'); - } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - @keyframes spin { - to { - transform: rotate(-360deg); - } - } - " - `) - }) + if (node.kind === 'at-rule') replaceWith(node.nodes) }) }) + +it('should only visit children once when calling `replaceWith` with multi-element array', () => { + let visited = new Set() + + let ast = [ + atRule('@media', '', [ + context({}, [ + styleRule('.foo', [decl('color', 'red')]), + styleRule('.baz', [decl('color', 'blue')]), + ]), + ]), + styleRule('.bar', [decl('color', 'green')]), + ] + + walk(ast, (node, { replaceWith }) => { + let key = id(node) + if (visited.has(key)) { + throw new Error('Visited node twice') + } + visited.add(key) + + if (node.kind === 'at-rule') replaceWith(node.nodes) + }) + + expect(visited).toMatchInlineSnapshot(` + Set { + "@media ", + ".foo", + "color: red", + ".baz", + "color: blue", + ".bar", + "color: green", + } + `) +}) + +it('should never visit children when calling `replaceWith` with `WalkAction.Skip`', () => { + let visited = new Set() + + let inner = styleRule('.foo', [decl('color', 'blue')]) + + let ast = [atRule('@media', '', [inner]), styleRule('.bar', [decl('color', 'blue')])] + + walk(ast, (node, { replaceWith }) => { + visited.add(node) + + if (node.kind === 'at-rule') { + replaceWith(node.nodes) + return WalkAction.Skip + } + }) + + expect(visited).not.toContain(inner) + expect(visited).toMatchInlineSnapshot(` + Set { + { + "kind": "at-rule", + "name": "@media", + "nodes": [ + { + "kind": "rule", + "nodes": [ + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + ], + "selector": ".foo", + }, + ], + "params": "", + }, + { + "kind": "rule", + "nodes": [ + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + ], + "selector": ".bar", + }, + { + "important": false, + "kind": "declaration", + "property": "color", + "value": "blue", + }, + } + `) +}) + +it('should skip the correct number of children based on the the replaced children nodes', () => { + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1')]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1')]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 2.1", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1'), decl('--index', '2.2')]) + return WalkAction.Skip + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 3", + "--index: 4", + ] + `) + } + + { + let ast = [ + decl('--index', '0'), + decl('--index', '1'), + decl('--index', '2'), + decl('--index', '3'), + decl('--index', '4'), + ] + let visited: string[] = [] + walk(ast, (node, { replaceWith }) => { + visited.push(id(node)) + if (node.kind === 'declaration' && node.value === '2') { + replaceWith([decl('--index', '2.1'), decl('--index', '2.2')]) + return WalkAction.Continue + } + }) + + expect(visited).toMatchInlineSnapshot(` + [ + "--index: 0", + "--index: 1", + "--index: 2", + "--index: 2.1", + "--index: 2.2", + "--index: 3", + "--index: 4", + ] + `) + } +}) + +function id(node: AstNode) { + switch (node.kind) { + case 'at-rule': + return `${node.name} ${node.params}` + case 'rule': + return node.selector + case 'context': + return '' + case 'at-root': + return '' + case 'declaration': + return `${node.property}: ${node.value}` + case 'comment': + return `// ${node.value}` + default: + node satisfies never + throw new Error('Unknown node kind') + } +} diff --git a/packages/tailwindcss/src/ast.ts b/packages/tailwindcss/src/ast.ts index 09c3ea051..e4b5d5a9a 100644 --- a/packages/tailwindcss/src/ast.ts +++ b/packages/tailwindcss/src/ast.ts @@ -1,17 +1,13 @@ import { Polyfills } from '.' -import * as SelectorParser from '../src/selector-parser' import { parseAtRule } from './css-parser' import type { DesignSystem } from './design-system' import type { Source, SourceLocation } from './source-maps/source' import { Theme, ThemeOptions } from './theme' import { DefaultMap } from './utils/default-map' -import { segment } from './utils/segment' import { extractUsedVariables } from './utils/variables' import * as ValueParser from './value-parser' -import { walk, WalkAction, type VisitContext } from './walk' const AT_SIGN = 0x40 -const PIPE = 0x7c export type StyleRule = { kind: 'rule' @@ -126,97 +122,157 @@ export function atRoot(nodes: AstNode[]): AtRoot { } } -export function cloneAstNode(node: T): T { - switch (node.kind) { - case 'rule': - return { - kind: node.kind, - selector: node.selector, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies StyleRule as T +export const enum WalkAction { + /** Continue walking, which is the default */ + Continue, - case 'at-rule': - return { - kind: node.kind, - name: node.name, - params: node.params, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies AtRule as T + /** Skip visiting the children of this node */ + Skip, - case 'at-root': - return { - kind: node.kind, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies AtRoot as T + /** Stop the walk entirely */ + Stop, +} - case 'context': - return { - kind: node.kind, - context: { ...node.context }, - nodes: node.nodes.map(cloneAstNode), - src: node.src, - dst: node.dst, - } satisfies Context as T +export function walk( + ast: AstNode[], + visit: ( + node: AstNode, + utils: { + parent: AstNode | null + replaceWith(newNode: AstNode | AstNode[]): void + context: Record + path: AstNode[] + }, + ) => void | WalkAction, + path: AstNode[] = [], + context: Record = {}, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let parent = path[path.length - 1] ?? null - case 'declaration': - return { - kind: node.kind, - property: node.property, - value: node.value, - important: node.important, - src: node.src, - dst: node.dst, - } satisfies Declaration as T + // We want context nodes to be transparent in walks. This means that + // whenever we encounter one, we immediately walk through its children and + // furthermore we also don't update the parent. + if (node.kind === 'context') { + if (walk(node.nodes, visit, path, { ...context, ...node.context }) === WalkAction.Stop) { + return WalkAction.Stop + } + continue + } - case 'comment': - return { - kind: node.kind, - value: node.value, - src: node.src, - dst: node.dst, - } satisfies Comment as T + path.push(node) + let replacedNode = false + let replacedNodeOffset = 0 + let status = + visit(node, { + parent, + context, + path, + replaceWith(newNode) { + if (replacedNode) return + replacedNode = true - default: - node satisfies never - throw new Error(`Unknown node kind: ${(node as any).kind}`) + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + replacedNodeOffset = 0 + } else if (newNode.length === 1) { + ast[i] = newNode[0] + replacedNodeOffset = 1 + } else { + ast.splice(i, 1, ...newNode) + replacedNodeOffset = newNode.length + } + } else { + ast[i] = newNode + replacedNodeOffset = 1 + } + }, + }) ?? WalkAction.Continue + path.pop() + + // We want to visit or skip the newly replaced node(s), which start at the + // current index (i). By decrementing the index here, the next loop will + // process this position (containing the replaced node) again. + if (replacedNode) { + if (status === WalkAction.Continue) { + i-- + } else { + i += replacedNodeOffset - 1 + } + continue + } + + // Stop the walk entirely + if (status === WalkAction.Stop) return WalkAction.Stop + + // Skip visiting the children of this node + if (status === WalkAction.Skip) continue + + if ('nodes' in node) { + path.push(node) + let result = walk(node.nodes, visit, path, context) + path.pop() + + if (result === WalkAction.Stop) { + return WalkAction.Stop + } + } } } -export function cssContext( - ctx: VisitContext, -): VisitContext & { context: Record } { - return { - depth: ctx.depth, - index: ctx.index, - siblings: ctx.siblings, - get context() { - let context: Record = {} - for (let child of ctx.path()) { - if (child.kind === 'context') { - Object.assign(context, child.context) +// This is a depth-first traversal of the AST +export function walkDepth( + ast: AstNode[], + visit: ( + node: AstNode, + utils: { + parent: AstNode | null + path: AstNode[] + context: Record + replaceWith(newNode: AstNode[]): void + }, + ) => void, + path: AstNode[] = [], + context: Record = {}, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let parent = path[path.length - 1] ?? null + + if (node.kind === 'rule' || node.kind === 'at-rule') { + path.push(node) + walkDepth(node.nodes, visit, path, context) + path.pop() + } else if (node.kind === 'context') { + walkDepth(node.nodes, visit, path, { ...context, ...node.context }) + continue + } + + path.push(node) + visit(node, { + parent, + context, + path, + replaceWith(newNode) { + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + } else if (newNode.length === 1) { + ast[i] = newNode[0] + } else { + ast.splice(i, 1, ...newNode) + } + } else { + ast[i] = newNode } - } - // Once computed, we never need to compute this again - Object.defineProperty(this, 'context', { value: context }) - return context - }, - get parent() { - let parent = (this.path().pop() as Extract) ?? null - - // Once computed, we never need to compute this again - Object.defineProperty(this, 'parent', { value: parent }) - return parent - }, - path() { - return ctx.path().filter((n) => n.kind !== 'context') - }, + // Skip over the newly inserted nodes (being depth-first it doesn't make sense to visit them) + i += newNode.length - 1 + }, + }) + path.pop() } } @@ -280,19 +336,13 @@ export function optimizeAst( // Track used animation names if (node.property === 'animation') { - for (let keyframeName of extractKeyframeNames(node.value)) { + for (let keyframeName of extractKeyframeNames(node.value)) usedKeyframeNames.add(keyframeName) - } } // Create fallback values for usages of the `color-mix(…)` function that reference variables - // found in the theme config. - if ( - polyfills & Polyfills.ColorMix && - !context.supportsColorMix && - !context.keyframes && - node.value.includes('color-mix(') - ) { + // found in the theme config. + if (polyfills & Polyfills.ColorMix && node.value.includes('color-mix(')) { colorMixDeclarations.get(parent).add(node) } @@ -301,14 +351,26 @@ export function optimizeAst( // Rule else if (node.kind === 'rule') { - let nodes: AstNode[] = [] - - for (let child of node.nodes) { - transform(child, nodes, context, depth + 1) + // Rules with `&` as the selector should be flattened + if (node.selector === '&') { + for (let child of node.nodes) { + let nodes: AstNode[] = [] + transform(child, nodes, context, depth + 1) + if (nodes.length > 0) { + parent.push(...nodes) + } + } } - if (nodes.length > 0) { - parent.push({ ...node, nodes }) + // + else { + let copy = { ...node, nodes: [] } + for (let child of node.nodes) { + transform(child, copy.nodes, context, depth + 1) + } + if (copy.nodes.length > 0) { + parent.push(copy) + } } } @@ -358,8 +420,6 @@ export function optimizeAst( else if (node.kind === 'at-rule') { if (node.name === '@keyframes') { context = { ...context, keyframes: true } - } else if (node.name === '@supports' && node.params.includes('color-mix(')) { - context = { ...context, supportsColorMix: true } } let copy = { ...node, nodes: [] } @@ -378,8 +438,7 @@ export function optimizeAst( copy.name === '@charset' || copy.name === '@custom-media' || copy.name === '@namespace' || - copy.name === '@import' || - copy.name === '@apply' + copy.name === '@import' ) { parent.push(copy) } @@ -401,21 +460,6 @@ export function optimizeAst( // Remove reference imports from printing if (node.context.reference) { return - } - - // Preserve context nodes that track the source of their subtree (variant - // bodies and the user content slotted into them), so `handleNesting` - // knows whether to compose `@scope` rules with the variant semantics or - // their native CSS nesting semantics - else if (node.context.source) { - let copy = { ...node, nodes: [] as AstNode[] } - for (let child of node.nodes) { - transform(child, copy.nodes, { ...context, ...node.context }, depth) - } - - if (copy.nodes.length > 0) { - parent.push(copy) - } } else { for (let child of node.nodes) { transform(child, parent, { ...context, ...node.context }, depth) @@ -518,12 +562,12 @@ export function optimizeAst( let ast = ValueParser.parse(declaration.value) let requiresPolyfill = false - walk(ast, (node) => { + ValueParser.walk(ast, (node, { replaceWith }) => { if (node.kind !== 'function' || node.value !== 'color-mix') return let containsUnresolvableVars = false let containsCurrentcolor = false - walk(node.nodes, (node) => { + ValueParser.walk(node.nodes, (node, { replaceWith }) => { if (node.kind == 'word' && node.value.toLowerCase() === 'currentcolor') { containsCurrentcolor = true requiresPolyfill = true @@ -567,7 +611,7 @@ export function optimizeAst( } } while (varNode) - return WalkAction.Replace({ kind: 'word', value: inlinedColor } as const) + replaceWith({ kind: 'word', value: inlinedColor }) }) if (containsUnresolvableVars || containsCurrentcolor) { @@ -578,7 +622,7 @@ export function optimizeAst( let firstColorValue = node.nodes.length > separatorIndex ? node.nodes[separatorIndex + 1] : null if (!firstColorValue) return - return WalkAction.Replace(firstColorValue) + replaceWith(firstColorValue) } else if (requiresPolyfill) { // Change the colorspace to `srgb` since the fallback values should not be represented as // `oklab(…)` functions again as their support in Safari <16 is very limited. @@ -667,975 +711,9 @@ export function optimizeAst( } } - return handleNesting(newAst) + return newAst } -export function handleNesting(ast: AstNode[]): AstNode[] { - let parseSelectorCache = new DefaultMap(SelectorParser.parse) - - // Nodes with an `@scope` rule somewhere in their subtree. These cannot use - // the "emit as-is" fast path for rules containing declarations, because - // `@scope` rules change meaning when they stay nested inside a style rule and - // must go through the hoisting machinery instead. - let containsAtScope = new Set() - { - function scan(nodes: AstNode[]): boolean { - let found = false - for (let node of nodes) { - if (node.kind === 'declaration' || node.kind === 'comment') continue - - if (scan(node.nodes) || (node.kind === 'at-rule' && node.name === '@scope')) { - containsAtScope.add(node) - found = true - } - } - return found - } - scan(ast) - } - - // Track `rule` selectors as we go - let selectorStack: [ - selector: string, - src: SourceLocation | undefined, - dst: SourceLocation | undefined, - ][] = [] - - // Track `at-rule` information as we go. Tracking this separately from the - // selector stack for rules such that we can hoist this above all the rules. - let atRuleStack: [ - name: string, - params: string, - src: SourceLocation | undefined, - dst: SourceLocation | undefined, - ][] = [] - - // The current "nodes" we can push to - let nodes = null as AstNode[] | null - - // Optimization: Track the declaration properties we've seen in the current - // nodes. - let seenDeclarationProperties = new Set() - - // Track nodes lists where we want to dedupe declarations - let dedupeDeclarationsInNodes = new Set() - - // The final, new AST - let result: AstNode[] = [] - - // Track whether we should skip a node in the `exit` phase - let skipExit = new Set() - - // Track the source of the subtree we are currently in. Variant bodies are - // wrapped in a `context` node with `source: 'variant'`, and the user content - // slotted into them is wrapped in `source: 'user'`. `@scope` rules whose - // nearest source is a variant are hoisted with the variant semantics, all - // other `@scope` rules use their native CSS nesting semantics. - let sourceStack: (string | boolean)[] = [] - - // Selector stacks that are masked while inside an `@scope` rule whose - // `` selector consumed the parent selector via `&`. - // - // Once the parent selector is part of the `` selector, the rules - // inside the `@scope` are relative to the scoping root, not to the parent - // rule. Selectors in scoped style rules only match elements in scope, and the - // parent matches ancestors of the scoping root, so prefixing the rules with - // the parent selector would make them match nothing at all. - // - // E.g.: - // - // ```css - // .parent { - // @scope (& > .scope) { - // .content { - // color: red; - // } - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.parent > .scope) { - // .content { /* ← not `.parent .content` */ - // color: red; - // } - // } - // ``` - let maskedSelectorStacks = new Map() - - walk(ast, { - enter(node) { - switch (node.kind) { - case 'rule': { - nodes = null // Start a new level - - // First time we see a rule - if (selectorStack.length === 0) { - // A rule with a selector containing `&` should replace the `&` with - // `:scope` if there is no parent rule. - // - // Note: there could be false positives when the `&` is escaped or - // part of a string inside an attribute selector. But the - // SelectorParser will take care of that. - if (node.selector.includes('&')) { - // Inside an `@scope` rule, the nesting selector behaves like - // `:where(:scope)` and matches the scoping root with zero - // specificity. E.g.: - // - // ```css - // @scope (.checkout) { - // & > p { - // color: red; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.checkout) { - // :where(:scope) > p { - // color: red; - // } - // } - // ``` - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - let replacement = atRuleStack.some(([name]) => name === '@scope') - ? ':where(:scope)' - : ':scope' - - let ast = SelectorParser.parse(node.selector) - let changed = false - - walk(ast, (node) => { - if (node.kind === 'selector' && node.value === '&') { - changed = true - node.value = replacement - } - }) - - if (changed) { - selectorStack.push([SelectorParser.toCss(ast), node.src, node.dst]) - } else { - selectorStack.push([node.selector, node.src, node.dst]) - } - } - - // No nesting markers, track as-is - else { - selectorStack.push([node.selector, node.src, node.dst]) - } - } - - // Nested rule, ensure `&` is present in each selector. Then track the - // selector. - else { - // A rule with just `&` can be replaced by its children. Let's - // ignore this node and keep walking its children. - if (node.selector === '&') { - skipExit.add(node) - return - } - - let lastSelector = selectorStack[selectorStack.length - 1][0] - let selector = segment(node.selector, ',') - .map((selector) => substituteNestingSelector(selector, lastSelector)) - .join(', ') - selectorStack.push([selector, node.src, node.dst]) - } - - // Once we hit a rule that has at least one declaration, then we can - // stop handling the nested selectors. - // - // This ensures that browser devtools can at least show _something_ - // for a given rule. This also means that we can leverage CSS nesting - // which is better for gzip results due to increased repetition. - // - // E.g.: - // - // ```css - // .a { - // &[b] { - // color: red; - // &:hover { - // color: blue; - // } - // } - // } - // ``` - // - // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - // - // ```css - // .a[b] { /* ← flattened */ - // color: red; /* ← saw a declaration, therefore */ - // &:hover { /* ← we keep this nested syntax */ - // color: blue; - // } - // } - // ``` - // - // The exception is when the rule contains an `@scope` rule somewhere, - // because it must go through the normal hoisting machinery instead. - // See `containsAtScope` for more details. - if ( - node.nodes.some((child) => child.kind === 'declaration') && - !containsAtScope.has(node) - ) { - // Emitting each child instead of the node itself because we do want - // to flatten the current node and its selector that is already - // pushed to the stack. - for (let child of node.nodes) emit(child) - return WalkAction.Skip - } - break - } - - case 'at-rule': { - nodes = null // Start a new level - - // `@layer` is hoistable, but when it's empty then we have to make - // sure that we still emit it because this might influence the layer - // order. We can't just get rid of it. - if (node.nodes.length === 0 && !DROPPABLE_IF_EMPTY_AT_RULES.has(node.name)) { - emit(node) - skipExit.add(node) - return WalkAction.Skip - } - - // Hoist at-rules - else if (HOISTABLE_AT_RULES.has(node.name)) { - let params = node.params - - // `@scope` is the only hoistable at-rule with selectors in its - // prelude, and the only one that changes meaning when it stays - // nested inside a style rule. Resolve the prelude and its contents - // before hoisting, using one of two composition modes: - // - // 1. Variant-generated `@scope` rules attach the parent selector - // (the utility) to the rules _inside_ the scope: - // - // ```css - // .scoped\:underline { - // @scope (.from) to (.to) { - // text-decoration-line: underline; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.from) to (.to) { - // .scoped\:underline { - // text-decoration-line: underline; - // } - // } - // ``` - // This way the utility is properly sandwiched between the - // `.from` and `.to` boundaries. - // - // 2. User-authored `@scope` rules keep their native CSS nesting - // semantics: the `` selector is relative to the parent - // rule, and the rules inside are relative to the scoping root: - // - // ```css - // .parent { - // @scope (.from) { - // color: red; - // } - // } - // ``` - // - // becomes: - // - // ```css - // @scope (.parent .from) { - // :where(:scope) { - // color: red; - // } - // } - // ``` - // - // In a perfect world we leave this alone, but Lightning CSS - // seems to incorrectly transform this into a version where the - // `.parent` class is gone entirely: - // - // ```css - // @scope (.from) { - // color: red; - // } - // ``` - // - // See: https://drafts.csswg.org/css-nesting-1/#nesting-at-scope - if (node.name === '@scope') { - let isVariant = sourceStack[sourceStack.length - 1] === 'variant' - let hasParent = selectorStack.length > 0 - - // Lightning CSS bug: This snippet will error - // - // ```css - // @scope { - // --x: 1; - // } - // ``` - // - // We will wrap these bare-declarations in a `&` rule to fix the crash: - // ```css - // @scope { - // & { - // --x: 1; - // } - // } - // ``` - { - let queue: (AtRule | Context)[] = [node] - - for (let parent of queue) { - let newNodes: AstNode[] = [] - let group: AstNode[] | null = null - - for (let child of parent.nodes) { - if (child.kind === 'context') queue.push(child) - - if (child.kind === 'declaration' || child.kind === 'comment') { - if (group === null) { - group = [] - newNodes.push(styleRule('&', group)) - } - group.push(child) - } else { - group = null - newNodes.push(child) - } - } - - parent.nodes = newNodes - } - } - - if (params.includes('&') || (!isVariant && hasParent)) { - // The selector to substitute for `&` in the `` - // selector: the elements matched by the nearest ancestor style - // rule, or `:scope` when there is none. - let parentSelector = hasParent ? selectorStack[selectorStack.length - 1][0] : null - - let paramsAst = ValueParser.parse(params) - - // Whether the current `(…)` group is a `` selector. - // - // ```css - // @scope (.from) to (.to) - // ^^^^^^^ scope-start (optional) - // ^^^^^ scope-end - // - // @scope to (.to) - // ^^^^^ scope-end - // ``` - let isScopeEnd = false - - // Whether the `` selector consumed the parent - // selector - let consumedParent = false - - for (let paramNode of paramsAst) { - if (paramNode.kind === 'word' && paramNode.value === 'to') { - isScopeEnd = true - continue - } - - // A `(…)` group parses as a function call without a name - if (paramNode.kind !== 'function' || paramNode.value !== '') continue - - let selector = ValueParser.toCss(paramNode.nodes) - - // A user-authored `` selector is relative to the - // parent rule, and resolves exactly like nested style rule - // selectors do: `&` refers to the elements matched by the - // parent rule, and selectors without `&` are prefixed with - // it. - if (!isScopeEnd && !isVariant && parentSelector !== null) { - paramNode.nodes = [ - ValueParser.word( - segment(selector, ',') - .map((part) => substituteNestingSelector(part.trim(), parentSelector)) - .join(', '), - ), - ] - consumedParent = true - } - - // Otherwise, only an explicit `&` is resolved. In the - // `` selector, `&` refers to the elements - // matched by the nearest ancestor style rule (or `:scope` - // when there is none). In the `` selector, `&` - // refers to the scoping root and behaves like - // `:where(:scope)`. - else if (selector.includes('&')) { - let replacement = isScopeEnd ? ':where(:scope)' : (parentSelector ?? ':scope') - - paramNode.nodes = [ - ValueParser.word(substituteNestingSelector(selector, replacement)), - ] - if (!isScopeEnd && hasParent) consumedParent = true - } - } - - params = ValueParser.toCss(paramsAst) - - // Mask the selector stack for the `@scope` rule's subtree, so - // its rules resolve like top-level rules within the scope (and - // `&` resolves to `:where(:scope)`). The stack is restored in - // the exit phase. See `maskedSelectorStacks` for more details. - if (consumedParent) { - maskedSelectorStacks.set(node, selectorStack) - selectorStack = [] - } - } - } - - atRuleStack.push([node.name, params, node.src, node.dst]) - } - - // If we can't hoist them, emit them immediately as-is - else { - emit(node) - skipExit.add(node) - return WalkAction.Skip - } - break - } - - case 'declaration': - case 'comment': { - emit(node) - break - } - - case 'context': - if (node.context.source) sourceStack.push(node.context.source) - break - - case 'at-root': - break - - default: - node satisfies never - break - } - }, - exit(node) { - if (skipExit.delete(node)) return - - switch (node.kind) { - case 'rule': { - nodes = null - selectorStack.pop() - break - } - - case 'at-rule': { - nodes = null - atRuleStack.pop() - - // Restore the selector stack that was masked by this `@scope` rule - let maskedSelectorStack = maskedSelectorStacks.get(node) - if (maskedSelectorStack) { - selectorStack = maskedSelectorStack - maskedSelectorStacks.delete(node) - } - break - } - - case 'context': - if (node.context.source) sourceStack.pop() - break - - case 'declaration': - case 'comment': - case 'at-root': - break - - default: - node satisfies never - break - } - }, - }) - - // Dedupe declarations that we've already seen before if they match the - // `property`, `value` and `important` information. - { - for (let nodes of dedupeDeclarationsInNodes) { - let seen = new Set() - for (let i = nodes.length - 1; i >= 0; --i) { - let node = nodes[i] - if (node.kind !== 'declaration') continue - - let id = `${node.property}\0${node.value}\0${node.important}` - - if (seen.has(id)) nodes.splice(i, 1) - else seen.add(id) - } - } - } - - return result - - function emit(node: AstNode) { - // Existing nodes are available, emit into those nodes - if (nodes) { - // Optimization: track used declarations in the current node. - if (node.kind === 'declaration') { - if (seenDeclarationProperties.has(node.property)) { - dedupeDeclarationsInNodes.add(nodes) - } else { - seenDeclarationProperties.add(node.property) - } - } - - nodes.push(node) - return - } - - // Nothing available, setup a fresh node - { - // There are no parent rules or at-rules available, which means that we - // can emit the node as-is. - if (selectorStack.length === 0 && atRuleStack.length === 0) { - let target = result - let lastNode = target[target.length - 1] - - // Optimization: when the current and last node are the same, ignore the - // new node entirely otherwise we will get unnecessary duplicate - // results. - // - // We only care about at-rules with no body because some of them (such - // as `@charset` or `@layer`) need to be emitted. A normal rule that's - // empty doesn't need to be emitted. At-rules _with_ a body (such as - // `@font-face` or `@keyframes`) can share the same prelude while - // containing different bodies, so they must all be emitted. - if ( - lastNode && - lastNode.kind === 'at-rule' && - node.kind === 'at-rule' && - lastNode.nodes.length === 0 && - node.nodes.length === 0 && - lastNode.name === node.name && - lastNode.params === node.params - ) { - return - } - - result.push(node) - return - } - - // Track the new "parent" nodes - { - nodes = [node] - - // Clear out seen declarations from the previous work in progress nodes - seenDeclarationProperties.clear() - - // Track new declaration - if (node.kind === 'declaration') { - seenDeclarationProperties.add(node.property) - } - } - - // Track the new root node that we build up to store in the final AST - let root = null as AstNode | null - - let target = result - let atRuleOffset = 0 - - // Optimization: merge adjacent at-rules - // - // Figure out whether we can push our new node into a previous node that - // was already emitted. - // - // We have to make sure that the order stays the same, so therefore we - // only ever have to look at the last node that was emitted. - { - let lastNode = target[target.length - 1] - if (lastNode && lastNode.kind === 'at-rule') { - for (let i = 0; i < atRuleStack.length; i++) { - let atRule = atRuleStack[i] - if (!lastNode) break - if (lastNode.kind !== 'at-rule') break - if (lastNode.name !== atRule[0]) break - if (lastNode.params !== atRule[1]) break - - atRuleOffset++ - target = lastNode.nodes - lastNode = lastNode.nodes[lastNode.nodes.length - 1] - } - } - } - - // Build up the rule - if (selectorStack.length > 0) { - let [selector, src, dst] = selectorStack[selectorStack.length - 1] - - // Optimization: merge adjacent rules with the same selector - // - // Figure out whether we can push into an existing rule. - // - // If we have some at-rules that we have to keep into account, then we - // definitely can't. - if (atRuleStack.length - atRuleOffset <= 0) { - let lastNode = target[target.length - 1] - if (lastNode && lastNode.kind === 'rule' && lastNode.selector === selector) { - lastNode.nodes.push(...nodes) - - // Ensure that our current nodes points to the nodes of the - // `lastNode`, otherwise we will lose information. - nodes = lastNode.nodes - - // We appended a group that could contain declarations already in the - // existing rule, so let the final dedupe pass handle it once. - // - // Note: we could loop over _all_ previous nodes to figure out if we - // really want to dedupe this. But this could result in a bunch of - // duplicate work if we have `n` nodes that we want to merge - // together. - dedupeDeclarationsInNodes.add(nodes) - - // We know that we don't have to handle any more at-rules, so we can - // bail early since we just merged the nodes with the same selector. - return - } - } - - // Can't push into existing node, create a new node - root = rule(selector, nodes) - if (src || dst) Object.assign(root, { src, dst }) - } - - // Wrap in at-rules, if we can push into an existing node then we can - // ignore `offset` amount of nodes since the `root`/`nodes` will already - // point to a nested node. - for (let i = atRuleStack.length - 1; i >= atRuleOffset; --i) { - let [name, params, src, dst] = atRuleStack[i] - - root = atRule(name, params, root ? [root] : nodes) - if (src || dst) Object.assign(root, { src, dst }) - } - - // Track the root node in the AST - if (root) { - target.push(root) - } - - // We didn't build up any new root, so we can move our node directly into - // the target. This can happen when we emit a node that is not a - // declaration or a comment. - else { - target.push(...nodes) - } - } - } - - // Substitutes the `&` selector with the parent selector with `:is(…)` semantics. - // - // If the `&` is not found, we will prepend it using a descendant combinator. - // E.g.: `& ${selector}` → `${parentSelector} ${selector}` - // - // In some situations we can get rid of the `:is(…)` wrapper, but only if it's safe to do so. - function substituteNestingSelector(selector: string, parentSelector: string): string { - // Slow path: we need to replace the `&` with the parent selector. A simple - // `replaceAll(…)` won't work because a `&` could be escaped, or could be - // part of an attribute selector. - // - // Much safer to parse the selector and replace the `&` that way - if (selector.includes('&')) { - let ast = SelectorParser.parse(selector) - let changed = false - walk(ast, { - enter(node, ctx) { - if (node.kind !== 'selector' || node.value !== '&') return - - changed = true - - // Safest option: use `is(…)` semantics when substituting `&` for the - // parent selector. - node.value = `:is(${parentSelector})` - - // We should always have a parent, so this shouldn't happen - if (ctx.parent === null) return - - // Optimizations: - let parentAst = parseSelectorCache.get(parentSelector) - - // 1. If we're dealing with multiple selectors, then we know that the - // `:is(…)` needs to stay. Nothing to optimize. - if (parentAst.length === 1 && parentAst[0].kind === 'list') { - return // Keep `:is(…)` semantics - } - - // 2. We know that `&` is standalone when it's inside of a complex - // selector. E.g. `[before] & [after]` - if (ctx.parent.kind === 'complex') { - // `& [after]` - // - // `:is(…)` semantics are not required, because these are equivalent: - // - // - `:is(div) [after]` → `div [after]` - // - `:is(.x) [after]` → `.x [after]` - // - `:is([before]) [after]` → `[before] [after]` - // - `:is(.a > .b) [after]` → `.a > .b [after]` - if (ctx.index === 0) { - node.value = parentSelector - return - } - - // `[before] &` - // - // `:is(…)` semantics are required if we're dealing with a complex - // parent selector. Otherwise the meaning of the selector could - // change: - // - // - `[before] :is(div)` → `[before] div` - // - `[before] :is(.x)` → `[before] .x` - // - `[before] :is([after])` → `[before] [after]` - // - `[before] :is(.a > .b)` → `[before] :is(.a > .b)` (!) - else if (ctx.index === ctx.siblings.length - 1) { - if (parentAst[0].kind === 'complex') { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - - // `[before] & [after]` - // - // `:is(…)` semantics are required if we're dealing with a complex - // parent selector. Otherwise the meaning of the selector could - // change: - // - // - `[before] :is(div) [after]` → `[before] div [after]` - // - `[before] :is(.x) [after]` → `[before] .x [after]` - // - `[before] :is(.a > .b) [after]` → `[before] :is(.a > .b) [after]` (!) - else { - if (parentAst[0].kind === 'complex') { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - } - - // 3. We know that `&` is attached to some other selector when it's - // inside of a compound selector. - // - // E.g. `[before]&[after]` - // - // We have to be careful that our parent, when it's part of a complex - // selector, that the same rules apply - // - // E.g.: `[before] &[after]` - // ^ current node - // ^^^^^^^^ compound selector - // ^^^^^^^^^^^^^^^^^ complex selector - // - else if (ctx.parent.kind === 'compound') { - if (parentAst[0].kind === 'complex') { - let path = ctx.path() - let grandParent = path[path.length - 2] - - // When our compound parent is part of a complex selector, and it's - // not the very first node, then we can't safely get rid of the - // `:is(…)` if the last selector is a complex selector as well, - // unless the `&` maps to a single selector or compound selector. - // - // ```css - // .foo .bar { /* Complex selector */ - // .system &:focus { /* Complex selector + compound selecto*/ - // --x: 1; - // } - // } - // .foo:hover { /* Compound selector */ - // .system &:focus { /* Complex selector + compound selector */ - // --x: 2; - // } - // } - // ``` - // - // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - // - // ```css - // .system :is(.foo .bar):focus { /* Cannot drop the `:is(…)`, otherwise `.system` and `.foo` can be swapped in the DOM */ - // --x: 1; - // } - // .system .foo:hover:focus { - // --x: 2; - // } - // ``` - if ( - grandParent && - grandParent.kind === 'complex' && - grandParent.nodes[0] !== ctx.parent - ) { - return // Keep `:is(…)` semantics - } - } - - // `&*` and `&div` are invalid CSS so these should stay invalid. They - // should be written as `*&` and `div&` instead. - if ( - ctx.siblings - .slice(ctx.index + 1) - .some( - (sibling) => - SelectorParser.isUniversalSelector(sibling) || - SelectorParser.isTypeSelector(sibling), - ) - ) { - return // Keep `:is(…)` semantics - } - - // `&[after]` - // - // `:is(…)` semantics are not required, because these are equivalent: - // - // - `:is(div)[after]` → `div[after]` - // - `:is(.x)[after]` → `.x[after]` - // - `:is([before])[after]` → `[before][after]` - // - `:is(.a > .b)[after]` → `.a > .b[after]` - if (ctx.index === 0) { - node.value = parentSelector - return - } - - // `[before]&` - // - // `:is(…)` semantics are required if we're dealing with a - // - // - complex parent selector, to prevent changing the meaning of the selector - // - a universal selector, because `*` needs to be first - // - a type selector, because `div` needs to be first - // - // - `[before]:is(div)` → `[before]:is(div)` (!) - // - `[before]:is(*)` → `[before]:is(*)` (!) - // - `[before]:is(.a > .b)` → `[before]:is(.a > .b)` (!) - // - `[before]:is(.x)` → `[before].x` - else if (ctx.index === ctx.siblings.length - 1) { - if ( - parentAst[0].kind === 'complex' || - SelectorParser.isUniversalSelector(parentAst[0]) || - SelectorParser.isTypeSelector(parentAst[0]) - ) { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - - // `[before]&[after]` - // - // `:is(…)` semantics are required if we're dealing with a - // - // - complex parent selector, to prevent changing the meaning of the selector - // - a universal selector, because `*` needs to be first - // - a type selector, because `div` needs to be first - // - // - `[before]:is(div)[after]` → `[before]:is(div)[after]` (!) - // - `[before]:is(*)[after]` → `[before]:is(*)[after]` (!) - // - `[before]:is(.a > .b)[after]` → `[before]:is(.a > .b)[after]` (!) - // - `[before]:is(.x)[after]` → `[before].x[after]` - else { - if ( - parentAst[0].kind === 'complex' || - SelectorParser.isUniversalSelector(parentAst[0]) || - SelectorParser.isTypeSelector(parentAst[0]) - ) { - return // Keep `:is(…)` semantics - } - - node.value = parentSelector - return - } - } - - // 4. When the current node is a function argument (e.g. `:not(&))`, - // then we can drop the `:is(…)` entirely. - // - // The only exception is when the parent has multiple selectors - // because then we would introduce multiple arguments. Multiple - // selectors are already handled. - else if (ctx.parent.kind === 'function') { - node.value = parentSelector - return - } - }, - exit(node, ctx) { - // Optimization: We can remove the universal selector `*` if they are - // part of a compound selector. E.g.: - // - // - `*:hover` → `:hover` - // - `*[attribute]` → `[attribute]` - // - // Except when the `*` is a namespace, e.g.: `*|div`, because `*|div` - // (any namespace) and `|div` (no namespace) have different meanings. - if ( - ctx.index === 0 && - ctx.siblings.length > 1 && - ctx.parent?.kind === 'compound' && - SelectorParser.isUniversalSelector(node) - ) { - let next = ctx.siblings[1] - if (next.kind === 'selector' && next.value.charCodeAt(0) === PIPE) { - return - } - return WalkAction.ReplaceSkip([]) - } - }, - }) - - if (changed) return SelectorParser.toCss(ast) - } - - // Fast path: we know there isn't an `&` so we can prepend the parent - // selector immediately. - let parentAst = parseSelectorCache.get(parentSelector) - return `${parentAst.length === 1 && parentAst[0].kind === 'list' ? `:is(${parentSelector})` : parentSelector} ${selector}` - } -} - -// A set of at-rules that can be hoisted to the top without any repercussions. -// Typically at-rules that rely on the environment, not parent information and -// contain other rules/declarations. -const HOISTABLE_AT_RULES = new Set([ - '@container', - '@layer', - '@media', - '@page', - '@scope', - '@starting-style', - '@supports', - '@view-transition', -]) - -// A set of at-rules that can be dropped if they don't contain any nodes. We -// don't have the distinction between an at-rule with no body, or an at-rule -// with a body that is empty right now. -const DROPPABLE_IF_EMPTY_AT_RULES = new Set([ - '@container', - '@media', - '@page', - '@scope', - '@starting-style', - '@supports', - '@view-transition', -]) - export function toCss(ast: AstNode[], track?: boolean) { let pos = 0 @@ -1845,10 +923,9 @@ export function toCss(ast: AstNode[], track?: boolean) { function findNode(ast: AstNode[], fn: (node: AstNode) => boolean): AstNode[] | null { let foundPath: AstNode[] = [] - walk(ast, (node, ctx) => { + walk(ast, (node, { path }) => { if (fn(node)) { - foundPath = ctx.path() - foundPath.push(node) + foundPath = [...path] return WalkAction.Stop } }) diff --git a/packages/tailwindcss/src/at-import.test.ts b/packages/tailwindcss/src/at-import.test.ts index a19123105..090279fd4 100644 --- a/packages/tailwindcss/src/at-import.test.ts +++ b/packages/tailwindcss/src/at-import.test.ts @@ -1,13 +1,37 @@ import dedent from 'dedent' import { expect, test, vi } from 'vitest' +import type { Plugin } from './compat/plugin-api' import { compile, type Config } from './index' import plugin from './plugin' -import { compileCss, run } from './test-utils/run' +import { optimizeCss } from './test-utils/run' const css = dedent +async function run( + css: string, + { + loadStylesheet = () => Promise.reject(new Error('Unexpected stylesheet')), + loadModule = () => Promise.reject(new Error('Unexpected module')), + candidates = [], + optimize = true, + }: { + loadStylesheet?: (id: string, base: string) => Promise<{ content: string; base: string }> + loadModule?: ( + id: string, + base: string, + resourceHint: 'plugin' | 'config', + ) => Promise<{ module: Config | Plugin; base: string }> + candidates?: string[] + optimize?: boolean + }, +) { + let compiler = await compile(css, { base: '/root', loadStylesheet, loadModule }) + let result = compiler.build(candidates) + return optimize ? optimizeCss(result) : result +} + test('can resolve relative @imports', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -17,20 +41,18 @@ test('can resolve relative @imports', async () => { } `, base: '/root/foo', - path: '', } } - expect( - await compileCss( + await expect( + run( css` @import './foo/bar.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - .foo { + ).resolves.toMatchInlineSnapshot(` + ".foo { color: red; } " @@ -38,14 +60,13 @@ test('can resolve relative @imports', async () => { }) test('can recursively resolve relative @imports', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { if (base === '/root' && id === './foo/bar.css') { return { content: css` @import './bar/baz.css'; `, base: '/root/foo', - path: '', } } else if (base === '/root/foo' && id === './bar/baz.css') { return { @@ -55,23 +76,21 @@ test('can recursively resolve relative @imports', async () => { } `, base: '/root/foo/bar', - path: '', } } throw new Error(`Unexpected import: ${id}`) } - expect( - await compileCss( + await expect( + run( css` @import './foo/bar.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - .baz { + ).resolves.toMatchInlineSnapshot(` + ".baz { color: #00f; } " @@ -83,56 +102,52 @@ let exampleCSS = css` color: red; } ` -async function loadStylesheet(id: string) { +let loadStylesheet = async (id: string) => { if (!id.endsWith('example.css')) throw new Error('Unexpected import: ' + id) return { content: exampleCSS, base: '/root', - path: '', } } test('extracts path from @import nodes', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " `) - expect( - await compileCss( + await expect( + run( css` @import './example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " `) - expect( - await compileCss( + await expect( + run( css` @import '/example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " @@ -140,96 +155,71 @@ test('extracts path from @import nodes', async () => { }) test('url() imports are passed-through', async () => { - expect( - await compileCss( + await expect( + run( css` @import url('example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url('./example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "./example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('./example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url('/example.css'); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "/example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url('/example.css');"`) - expect( - await compileCss( + await expect( + run( css` @import url(example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(example.css);"`) - expect( - await compileCss( + await expect( + run( css` @import url(./example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "./example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(./example.css);"`) - expect( - await compileCss( + await expect( + run( css` @import url(/example.css); `, - { base: '/root', loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')) }, + { loadStylesheet: () => Promise.reject(new Error('Unexpected stylesheet')), optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @import "/example.css"; - " - `) + ).resolves.toMatchInlineSnapshot(`"@import url(/example.css);"`) }) test('handles case-insensitive @import directive', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css'; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - a { + ).resolves.toMatchInlineSnapshot(` + "a { color: red; } " @@ -237,16 +227,15 @@ test('handles case-insensitive @import directive', async () => { }) test('@media', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' print; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media print { + ).resolves.toMatchInlineSnapshot(` + "@media print { a { color: red; } @@ -254,16 +243,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' print, screen; `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media print, screen { + ).resolves.toMatchInlineSnapshot(` + "@media print, screen { a { color: red; } @@ -271,16 +259,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' screen and (orientation: landscape); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @media screen and (orientation: landscape) { + ).resolves.toMatchInlineSnapshot(` + "@media screen and (orientation: landscape) { a { color: red; } @@ -288,16 +275,15 @@ test('@media', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' foo(bar); `, - { base: '/root', loadStylesheet }, + { loadStylesheet, optimize: false }, ), - ).toMatchInlineSnapshot(` - " - @media foo(bar) { + ).resolves.toMatchInlineSnapshot(` + "@media foo(bar) { a { color: red; } @@ -307,16 +293,15 @@ test('@media', async () => { }) test('@supports', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports(display: grid); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (display: grid) { + ).resolves.toMatchInlineSnapshot(` + "@supports (display: grid) { a { color: red; } @@ -324,16 +309,15 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports(display: grid) screen and (max-width: 400px); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (display: grid) { + ).resolves.toMatchInlineSnapshot(` + "@supports (display: grid) { @media screen and (max-width: 400px) { a { color: red; @@ -343,17 +327,16 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' supports((not (display: grid)) and (display: flex)) screen and (max-width: 400px); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports (not (display: grid)) and (display: flex) { + ).resolves.toMatchInlineSnapshot(` + "@supports (not (display: grid)) and (display: flex) { @media screen and (max-width: 400px) { a { color: red; @@ -363,18 +346,17 @@ test('@supports', async () => { " `) - expect( - await compileCss( + await expect( + run( // prettier-ignore css` @import 'example.css' supports((selector(h2 > p)) and (font-tech(color-COLRv1))); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @supports selector(h2 > p) and font-tech(color-COLRv1) { + ).resolves.toMatchInlineSnapshot(` + "@supports selector(h2 > p) and font-tech(color-COLRv1) { a { color: red; } @@ -384,16 +366,15 @@ test('@supports', async () => { }) test('@layer', async () => { - expect( - await compileCss( + await expect( + run( css` @import 'example.css' layer(utilities); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + ).resolves.toMatchInlineSnapshot(` + "@layer utilities { a { color: red; } @@ -401,16 +382,15 @@ test('@layer', async () => { " `) - expect( - await compileCss( + await expect( + run( css` @import 'example.css' layer(); `, - { base: '/root', loadStylesheet }, + { loadStylesheet }, ), - ).toMatchInlineSnapshot(` - " - @layer { + ).resolves.toMatchInlineSnapshot(` + "@layer { a { color: red; } @@ -421,14 +401,12 @@ test('@layer', async () => { test('supports theme(reference) imports', async () => { expect( - await run( - ['text-red-500'], + run( css` @tailwind utilities; @import 'example.css' theme(reference); `, { - base: '/root', loadStylesheet: () => Promise.resolve({ content: css` @@ -437,13 +415,12 @@ test('supports theme(reference) imports', async () => { } `, base: '', - path: '', }), + candidates: ['text-red-500'], }, ), - ).toMatchInlineSnapshot(` - " - .text-red-500 { + ).resolves.toMatchInlineSnapshot(` + ".text-red-500 { color: var(--color-red-500, red); } " @@ -451,27 +428,27 @@ test('supports theme(reference) imports', async () => { }) test('updates the base when loading modules inside nested files', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @config './nested-config.js'; @plugin './nested-plugin.js'; `, base: '/root/foo', - path: '', - } - } - using loadModule = vi.fn().mockResolvedValue({ base: '', path: '', module: () => {} }) + }) + let loadModule = vi.fn().mockResolvedValue({ base: '', module: () => {} }) expect( - await compileCss( - css` - @import './foo/bar.css'; - @config './root-config.js'; - @plugin './root-plugin.js'; - `, - { base: '/root', loadStylesheet, loadModule }, - ), + ( + await run( + css` + @import './foo/bar.css'; + @config './root-config.js'; + @plugin './root-plugin.js'; + `, + { loadStylesheet, loadModule }, + ) + ).trim(), ).toBe('') expect(loadModule).toHaveBeenNthCalledWith(1, './nested-config.js', '/root/foo', 'config') @@ -481,15 +458,13 @@ test('updates the base when loading modules inside nested files', async () => { }) test('emits the right base for @source directives inside nested files', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @source './nested/**/*.css'; `, base: '/root/foo', - path: '', - } - } + }) let compiler = await compile( css` @@ -506,18 +481,15 @@ test('emits the right base for @source directives inside nested files', async () }) test('emits the right base for @source found inside JS configs and plugins from nested imports', async () => { - async function loadStylesheet() { - return { + let loadStylesheet = () => + Promise.resolve({ content: css` @config './nested-config.js'; @plugin './nested-plugin.js'; `, base: '/root/foo', - path: '', - } - } - - using loadModule = vi.fn().mockImplementation((id: string) => { + }) + let loadModule = vi.fn().mockImplementation((id: string) => { let base = id.includes('nested') ? '/root/foo' : '/root' if (id.includes('config')) { let glob = id.includes('nested') ? './nested-config/*.html' : './root-config/*.html' @@ -530,14 +502,12 @@ test('emits the right base for @source found inside JS configs and plugins from plugins: [plugin(() => {}, { content: [pluginGlob] })], } satisfies Config, base: base + '-config', - path: '', } } else { let glob = id.includes('nested') ? './nested-plugin/*.html' : './root-plugin/*.html' return { module: plugin(() => {}, { content: [glob] }), base: base + '-plugin', - path: '', } } }) @@ -564,25 +534,28 @@ test('emits the right base for @source found inside JS configs and plugins from }) test('it crashes when inside a cycle', async () => { - let input = css` - @import 'foo.css'; - ` - - async function loadStylesheet() { - return { - content: input, + let loadStylesheet = () => + Promise.resolve({ + content: css` + @import 'foo.css'; + `, base: '/root', - path: '', - } - } + }) - await expect(compileCss(input, { base: '/root', loadStylesheet })).rejects.toMatchInlineSnapshot( + expect( + run( + css` + @import 'foo.css'; + `, + { loadStylesheet }, + ), + ).rejects.toMatchInlineSnapshot( `[Error: Exceeded maximum recursion depth while resolving \`foo.css\` in \`/root\`)]`, ) }) test('resolves @reference as `@import "…" reference`', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -595,22 +568,19 @@ test('resolves @reference as `@import "…" reference`', async () => { } `, base: '/root/foo', - path: '', } } - expect( - await run( - ['text-red-500'], + await expect( + run( css` @reference './foo/bar.css'; @tailwind utilities; `, - { base: '/root', loadStylesheet }, + { loadStylesheet, candidates: ['text-red-500'] }, ), - ).toMatchInlineSnapshot(` - " - .text-red-500 { + ).resolves.toMatchInlineSnapshot(` + ".text-red-500 { color: var(--color-red-500, red); } " @@ -618,7 +588,7 @@ test('resolves @reference as `@import "…" reference`', async () => { }) test('resolves `@variant` used as `@custom-variant` inside `@reference`', async () => { - async function loadStylesheet(id: string, base: string) { + let loadStylesheet = async (id: string, base: string) => { expect(base).toBe('/root') expect(id).toBe('./foo/bar.css') return { @@ -630,22 +600,19 @@ test('resolves `@variant` used as `@custom-variant` inside `@reference`', async } `, base: '/root/foo', - path: '', } } - expect( - await run( - ['dark:flex'], + await expect( + run( css` @reference './foo/bar.css'; @tailwind utilities; `, - { base: '/root', loadStylesheet }, + { loadStylesheet, candidates: ['dark:flex'] }, ), - ).toMatchInlineSnapshot(` - " - .dark\\:flex:where([data-theme="dark"] *) { + ).resolves.toMatchInlineSnapshot(` + ".dark\\:flex:where([data-theme="dark"] *) { display: flex; } " diff --git a/packages/tailwindcss/src/at-import.ts b/packages/tailwindcss/src/at-import.ts index ba7172f8e..5f13cce99 100644 --- a/packages/tailwindcss/src/at-import.ts +++ b/packages/tailwindcss/src/at-import.ts @@ -1,8 +1,7 @@ import { Features } from '.' -import { atRule, context, type AstNode } from './ast' +import { atRule, context, walk, WalkAction, type AstNode } from './ast' import * as CSS from './css-parser' import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' type LoadStylesheet = ( id: string, @@ -23,7 +22,7 @@ export async function substituteAtImports( let features = Features.None let promises: Promise[] = [] - walk(ast, (node) => { + walk(ast, (node, { replaceWith }) => { if (node.kind === 'at-rule' && (node.name === '@import' || node.name === '@reference')) { let parsed = parseImportParams(ValueParser.parse(node.params)) if (parsed === null) return @@ -67,9 +66,11 @@ export async function substituteAtImports( })(), ) + replaceWith(contextNode) + // The resolved Stylesheets already have their transitive @imports // resolved, so we can skip walking them. - return WalkAction.ReplaceSkip(contextNode) + return WalkAction.Skip } }) diff --git a/packages/tailwindcss/src/attribute-selector-parser.bench.ts b/packages/tailwindcss/src/attribute-selector-parser.bench.ts deleted file mode 100644 index 2d6e89590..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.bench.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { bench, describe } from 'vitest' -import * as AttributeSelectorParser from './attribute-selector-parser' - -let examples = [ - '[open]', - '[data-foo]', - '[data-state=expanded]', - '[data-state = expanded ]', - '[data-state*="expanded"]', - '[data-state*="expanded"i]', - '[data-state*=expanded i]', -] - -const ATTRIBUTE_REGEX = - /\[\s*(?[a-zA-Z_-][a-zA-Z0-9_-]*)\s*((?[*|~^$]?=)\s*(?['"])?\s*(?.*?)\4\s*(?[is])?\s*)?\]/ - -describe('parsing', () => { - bench('AttributeSelectorParser.parse', () => { - for (let example of examples) { - AttributeSelectorParser.parse(example) - } - }) - - bench('REGEX.test(…)', () => { - for (let example of examples) { - ATTRIBUTE_REGEX.exec(example) - } - }) - - bench('….match(REGEX)', () => { - for (let example of examples) { - example.match(ATTRIBUTE_REGEX) - } - }) -}) diff --git a/packages/tailwindcss/src/attribute-selector-parser.test.ts b/packages/tailwindcss/src/attribute-selector-parser.test.ts deleted file mode 100644 index 87f5f3013..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { parse } from './attribute-selector-parser' - -describe('parse', () => { - it.each([ - [''], - [']'], - ['[]'], - ['['], - ['="value"'], - ['data-foo]'], - ['[data-foo'], - ['[data-foo="foo]'], - ['[data-foo * = foo]'], - ['[data-foo*=]'], - ['[data-foo=value x]'], - ['[data-foo=value ix]'], - ])('should parse an invalid attribute selector (%s) as `null`', (input) => { - expect(parse(input)).toBeNull() - }) - - it.each([ - [ - '[data-foo]', - { attribute: 'data-foo', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - '[ data-foo ]', - { attribute: 'data-foo', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - // Non-ASCII code points are valid in a CSS ident - '[data-état]', - { attribute: 'data-état', operator: null, quote: null, value: null, sensitivity: null }, - ], - [ - '[data-state=expanded]', - { attribute: 'data-state', operator: '=', quote: null, value: 'expanded', sensitivity: null }, - ], - [ - '[data-state = expanded ]', - { attribute: 'data-state', operator: '=', quote: null, value: 'expanded', sensitivity: null }, - ], - [ - '[data-state*="expanded"]', - { attribute: 'data-state', operator: '*=', quote: '"', value: 'expanded', sensitivity: null }, - ], - [ - '[data-state*="expanded"i]', - { attribute: 'data-state', operator: '*=', quote: '"', value: 'expanded', sensitivity: 'i' }, - ], - [ - '[data-state*=expanded i]', - { attribute: 'data-state', operator: '*=', quote: null, value: 'expanded', sensitivity: 'i' }, - ], - ])('should parse correctly: %s', (selector, expected) => { - expect(parse(selector)).toEqual(expected) - }) - - it('should work with a real-world example', () => { - expect(parse('[data-url$=".com"i]')).toEqual({ - attribute: 'data-url', - operator: '$=', - quote: '"', - value: '.com', - sensitivity: 'i', - }) - }) -}) diff --git a/packages/tailwindcss/src/attribute-selector-parser.ts b/packages/tailwindcss/src/attribute-selector-parser.ts deleted file mode 100644 index 624043d59..000000000 --- a/packages/tailwindcss/src/attribute-selector-parser.ts +++ /dev/null @@ -1,232 +0,0 @@ -const TAB = 9 -const LINE_BREAK = 10 -const CARRIAGE_RETURN = 13 -const SPACE = 32 -const DOUBLE_QUOTE = 34 -const DOLLAR = 36 -const SINGLE_QUOTE = 39 -const ASTERISK = 42 -const EQUALS = 61 -const UPPER_I = 73 -const UPPER_S = 83 -const BACKSLASH = 92 -const CARET = 94 -const LOWER_I = 105 -const LOWER_S = 115 -const PIPE = 124 -const TILDE = 126 -const LOWER_A = 97 -const LOWER_Z = 122 -const UPPER_A = 65 -const UPPER_Z = 90 -const ZERO = 48 -const NINE = 57 -const DASH = 45 -const UNDERSCORE = 95 -const NON_ASCII = 128 - -interface AttributeSelector { - attribute: string - operator: '=' | '~=' | '|=' | '^=' | '$=' | '*=' | null - quote: '"' | "'" | null - value: string | null - sensitivity: 'i' | 's' | null -} - -export function parse(input: string): AttributeSelector | null { - // Must start with `[` and end with `]` - if (input[0] !== '[' || input[input.length - 1] !== ']') { - return null - } - - let i = 1 - let start = i - let end = input.length - 1 - - // Skip whitespace, e.g.: [ data-foo] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // Attribute name, e.g.: [data-foo] - // ^^^^^^^^ - { - start = i - for (; i < end; i++) { - let currentChar = input.charCodeAt(i) - // Skip escaped character - if (currentChar === BACKSLASH) { - i++ - continue - } - if (currentChar >= UPPER_A && currentChar <= UPPER_Z) continue - if (currentChar >= LOWER_A && currentChar <= LOWER_Z) continue - if (currentChar >= ZERO && currentChar <= NINE) continue - if (currentChar === DASH || currentChar === UNDERSCORE) continue - // Non-ASCII code points are valid in a CSS ident, e.g.: `[data-état]` - if (currentChar >= NON_ASCII) continue - break - } - - // Must have at least one character in the attribute name - if (start === i) { - return null - } - } - let attribute = input.slice(start, i) - - // Skip whitespace, e.g.: [data-foo =value] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, e.g.: `[data-foo]` - if (i === end) { - return { - attribute, - operator: null, - quote: null, - value: null, - sensitivity: null, - } - } - - // Operator, e.g.: [data-foo*=value] - // ^^ - let operator = null - let currentChar = input.charCodeAt(i) - if (currentChar === EQUALS) { - operator = '=' - i++ - } else if ( - (currentChar === TILDE || - currentChar === PIPE || - currentChar === CARET || - currentChar === DOLLAR || - currentChar === ASTERISK) && - input.charCodeAt(i + 1) === EQUALS - ) { - operator = input[i] + '=' - i += 2 - } else { - return null // Invalid operator - } - - // Skip whitespace, e.g.: [data-foo*= value] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, that means that we have an operator but no valid, which is - // invalid, e.g.: `[data-foo*=]` - if (i === end) { - return null - } - - // Value, e.g.: [data-foo*=value] - // ^^^^^ - let value = '' - - // Quoted value, e.g.: [data-foo*="value"] - // ^^^^^^^ - let quote = null - currentChar = input.charCodeAt(i) - if (currentChar === SINGLE_QUOTE || currentChar === DOUBLE_QUOTE) { - quote = input[i] as '"' | "'" - i++ - - start = i - for (let j = i; j < end; j++) { - let current = input.charCodeAt(j) - // Found ending quote - if (current === currentChar) { - i = j + 1 - } - - // Skip escaped character - else if (current === BACKSLASH) { - j++ - } - } - - value = input.slice(start, i - 1) - } - - // Unquoted value, e.g.: [data-foo*=value] - // ^^^^^ - else { - start = i - // Keep going until we find whitespace or the end - while (i < end && !isAsciiWhitespace(input.charCodeAt(i))) i++ - value = input.slice(start, i) - } - - // Skip whitespace, e.g.: [data-foo*=value ] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // At the end, e.g.: `[data-foo=value]` - if (i === end) { - return { - attribute, - operator: operator as '=' | '~=' | '|=' | '^=' | '$=' | '*=', - quote: quote as '"' | "'" | null, - value, - sensitivity: null, - } - } - - // Sensitivity, e.g.: [data-foo=value i] - // ^ - let sensitivity = null - { - switch (input.charCodeAt(i)) { - case LOWER_I: - case UPPER_I: { - sensitivity = 'i' - i++ - break - } - - case LOWER_S: - case UPPER_S: { - sensitivity = 's' - i++ - break - } - - default: - return null // Invalid sensitivity - } - } - - // Skip whitespace, e.g.: [data-foo=value i ] - // ^^^ - while (isAsciiWhitespace(input.charCodeAt(i))) i++ - - // We must be at the end now, if not, then there is an additional character - // after the sensitivity which is invalid, e.g.: [data-foo=value iX] - // ^ - if (i !== end) { - return null - } - - // Fully done - return { - attribute, - operator: operator as '=' | '~=' | '|=' | '^=' | '$=' | '*=', - quote: quote as '"' | "'" | null, - value, - sensitivity: sensitivity as 'i' | 's' | null, - } -} - -function isAsciiWhitespace(code: number): boolean { - switch (code) { - case SPACE: - case TAB: - case LINE_BREAK: - case CARRIAGE_RETURN: - return true - - default: - return false - } -} diff --git a/packages/tailwindcss/src/candidate.bench.ts b/packages/tailwindcss/src/candidate.bench.ts index 7ac0dbb0a..5b7171432 100644 --- a/packages/tailwindcss/src/candidate.bench.ts +++ b/packages/tailwindcss/src/candidate.bench.ts @@ -1,6 +1,6 @@ import { Scanner } from '@tailwindcss/oxide' -import { bench, describe } from 'vitest' -import { cloneCandidate, parseCandidate, type Candidate, type Variant } from './candidate' +import { bench } from 'vitest' +import { parseCandidate } from './candidate' import { buildDesignSystem } from './design-system' import { Theme } from './theme' @@ -8,87 +8,13 @@ import { Theme } from './theme' const root = process.env.FOLDER || process.cwd() // Auto content detection -const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*', negated: false }] }) +const scanner = new Scanner({ sources: [{ base: root, pattern: '**/*' }] }) const candidates = scanner.scan() const designSystem = buildDesignSystem(new Theme()) -describe('parsing', () => { - bench('parseCandidate', () => { - for (let candidate of candidates) { - Array.from(parseCandidate(candidate, designSystem)) - } - }) -}) - -describe('Candidate cloning', async () => { - let parsedCanddiates = candidates.flatMap((candidate) => - Array.from(parseCandidate(candidate, designSystem)), - ) - - bench('structuredClone', () => { - for (let candidate of parsedCanddiates) { - structuredClone(candidate) - } - }) - - bench('cloneCandidate', () => { - for (let candidate of parsedCanddiates) { - cloneCandidate(candidate) - } - }) - - bench('cloneCandidate (spread)', () => { - for (let candidate of parsedCanddiates) { - cloneCandidateSpread(candidate) - } - }) -}) - -function cloneCandidateSpread(candidate: Candidate): Candidate { - switch (candidate.kind) { - case 'arbitrary': - return { - ...candidate, - modifier: candidate.modifier ? { ...candidate.modifier } : null, - variants: candidate.variants.map(cloneVariantSpread), - } - - case 'static': - return { ...candidate, variants: candidate.variants.map(cloneVariantSpread) } - - case 'functional': - return { - ...candidate, - value: candidate.value ? { ...candidate.value } : null, - modifier: candidate.modifier ? { ...candidate.modifier } : null, - variants: candidate.variants.map(cloneVariantSpread), - } - - default: - candidate satisfies never - throw new Error('Unknown candidate kind') +bench('parseCandidate', () => { + for (let candidate of candidates) { + Array.from(parseCandidate(candidate, designSystem)) } -} - -function cloneVariantSpread(variant: Variant): Variant { - switch (variant.kind) { - case 'arbitrary': - case 'static': - return { ...variant } - - case 'functional': - return { ...variant, modifier: variant.modifier ? { ...variant.modifier } : null } - - case 'compound': - return { - ...variant, - variant: cloneVariantSpread(variant.variant), - modifier: variant.modifier ? { ...variant.modifier } : null, - } - - default: - variant satisfies never - throw new Error('Unknown variant kind') - } -} +}) diff --git a/packages/tailwindcss/src/candidate.test.ts b/packages/tailwindcss/src/candidate.test.ts index b8f914e1b..4355071f2 100644 --- a/packages/tailwindcss/src/candidate.test.ts +++ b/packages/tailwindcss/src/candidate.test.ts @@ -1,5 +1,4 @@ -import { describe, expect, it, test } from 'vitest' -import { __unstable__loadDesignSystem } from '.' +import { expect, it } from 'vitest' import { buildDesignSystem } from './design-system' import { Theme } from './theme' import { Utilities } from './utilities' @@ -516,8 +515,6 @@ it('should not parse functional utilities with multiple modifiers', () => { utilities.functional('bg', () => []) expect(run('bg-red-1/2/3', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/50/', { utilities })).toMatchInlineSnapshot(`[]`) - expect(run('bg-red-500/50//foo', { utilities })).toMatchInlineSnapshot(`[]`) }) it('should parse a utility with an arbitrary value', () => { @@ -1418,19 +1415,6 @@ it('should parse a functional variant with a modifier', () => { `) }) -it('should not parse variants with multiple modifiers', () => { - let utilities = new Utilities() - utilities.static('flex', () => []) - - let variants = new Variants() - variants.static('hover', () => {}) - variants.compound('group', Compounds.StyleRules, () => {}) - - expect(run('group-hover/foo:flex', { utilities, variants })).toHaveLength(1) - expect(run('group-hover/foo/:flex', { utilities, variants })).toEqual([]) - expect(run('group-hover/foo//bar:flex', { utilities, variants })).toEqual([]) -}) - it('should parse a functional variant starting with @', () => { let utilities = new Utilities() utilities.static('flex', () => []) @@ -1791,14 +1775,6 @@ it('should not parse compound group with a non-compoundable variant', () => { expect(run('group-*:flex', { utilities, variants })).toMatchInlineSnapshot(`[]`) }) -it('empty data types are invalid', () => { - let utilities = new Utilities() - utilities.functional('bg', () => []) - let variants = new Variants() - - expect(run('bg-[:foo]', { utilities, variants })).toMatchInlineSnapshot(`[]`) -}) - it('should parse a variant containing an arbitrary string with unbalanced parens, brackets, curlies and other quotes', () => { let utilities = new Utilities() utilities.static('flex', () => []) @@ -2012,12 +1988,7 @@ it.each([ // Arbitrary variant values that end the block 'data-[a]{color:red}foo[a]:flex', - - // Named values contain invalid characters - 'data-foo=bar:flex', - 'bg-foo=bar', - 'bg-red-500/foo=bar', -])('should not parse invalid values: %s', (rawCandidate) => { +])('should not parse invalid arbitrary values: %s', (rawCandidate) => { let utilities = new Utilities() utilities.static('flex', () => []) utilities.functional('bg', () => []) @@ -2028,129 +1999,3 @@ it.each([ expect(run(rawCandidate, { utilities, variants })).toEqual([]) }) - -const candidates = [ - // Arbitrary candidates - ['[color:red]', '[color:red]'], - ['[color:red]/50', '[color:red]/50'], - ['[color:red]/[0.5]', '[color:red]/[0.5]'], - ['[color:red]/50!', '[color:red]/50!'], - ['![color:red]/50', '[color:red]/50!'], - ['[color:red]/[0.5]!', '[color:red]/[0.5]!'], - - // Static candidates - ['box-border', 'box-border'], - ['underline!', 'underline!'], - ['!underline', 'underline!'], - ['-inset-full', '-inset-full'], - - // Functional candidates - ['bg-red-500', 'bg-red-500'], - ['bg-red-500/50', 'bg-red-500/50'], - ['bg-red-500/[0.5]', 'bg-red-500/[0.5]'], - ['bg-red-500!', 'bg-red-500!'], - ['!bg-red-500', 'bg-red-500!'], - ['bg-[#0088cc]/50', 'bg-[#0088cc]/50'], - ['bg-[#0088cc]/[0.5]', 'bg-[#0088cc]/[0.5]'], - ['bg-[#0088cc]!', 'bg-[#0088cc]!'], - ['!bg-[#0088cc]', 'bg-[#0088cc]!'], - ['bg-[var(--spacing)-1px]', 'bg-[var(--spacing)-1px]'], - ['bg-[var(--spacing)_-_1px]', 'bg-[var(--spacing)-1px]'], - ['bg-[var(--_spacing)]', 'bg-(--_spacing)'], - ['bg-(--_spacing)', 'bg-(--_spacing)'], - ['bg-[-1px_-1px]', 'bg-[-1px_-1px]'], - ['p-[round(to-zero,1px)]', 'p-[round(to-zero,1px)]'], - ['w-1/2', 'w-1/2'], - ['p-[calc((100vw-theme(maxWidth.2xl))_/_2)]', 'p-[calc((100vw-theme(maxWidth.2xl))/2)]'], - - // Keep spaces in strings - ['content-["hello_world"]', 'content-["hello_world"]'], - ['content-[____"hello_world"___]', 'content-["hello_world"]'], - - // Do not escape underscores for url() and CSS variable in var() - ['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'], - [ - 'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]', - 'bg-(--spacing-0_5,var(--spacing-1_5,3rem))', - ], - - // Normalize whitespace in arbitrary properties - ['[display:flex]', '[display:flex]'], - ['[display:_flex]', '[display:flex]'], - ['[display:flex_]', '[display:flex]'], - ['[display:_flex_]', '[display:flex]'], - - // Normalize whitespace in `calc` expressions - ['w-[calc(100%-2rem)]', 'w-[calc(100%-2rem)]'], - ['w-[calc(100%_-_2rem)]', 'w-[calc(100%-2rem)]'], - - // Normalize the important modifier - ['!flex', 'flex!'], - ['flex!', 'flex!'], -] - -const variants = [ - ['', ''], // no variant - ['*:', '*:'], - ['focus:', 'focus:'], - ['group-focus:', 'group-focus:'], - - ['hover:focus:', 'hover:focus:'], - ['hover:group-focus:', 'hover:group-focus:'], - ['group-hover:focus:', 'group-hover:focus:'], - ['group-hover:group-focus:', 'group-hover:group-focus:'], - - ['min-[10px]:', 'min-[10px]:'], - - // Normalize spaces - ['min-[calc(1000px_+_12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px_+12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px+_12em)]:', 'min-[calc(1000px+12em)]:'], - ['min-[calc(1000px___+___12em)]:', 'min-[calc(1000px+12em)]:'], - - ['peer-[&_p]:', 'peer-[&_p]:'], - ['peer-[&_p]:hover:', 'peer-[&_p]:hover:'], - ['hover:peer-[&_p]:', 'hover:peer-[&_p]:'], - ['hover:peer-[&_p]:focus:', 'hover:peer-[&_p]:focus:'], - ['peer-[&:hover]:peer-[&_p]:', 'peer-[&:hover]:peer-[&_p]:'], - - ['[p]:', '[p]:'], - ['[_p_]:', '[p]:'], - ['has-[p]:', 'has-[p]:'], - ['has-[_p_]:', 'has-[p]:'], - - // Simplify `&:is(p)` to `p` - ['[&:is(p)]:', '[p]:'], - ['[&:is(_p_)]:', '[p]:'], - ['has-[&:is(p)]:', 'has-[p]:'], - ['has-[&:is(_p_)]:', 'has-[p]:'], - - // Handle special `@` variants. These shouldn't be printed as `@-` - ['@xl:', '@xl:'], - ['@[123px]:', '@[123px]:'], - - // Compound variants that forward modifiers - ['not-group-hover/name:', 'not-group-hover/name:'], - ['has-group-peer-hover/name:', 'has-group-peer-hover/name:'], - ['in-group-peer-hover/name:', 'in-group-peer-hover/name:'], -] - -let combinations: [string, string][] = [] - -for (let [inputVariant, outputVariant] of variants) { - for (let [inputCandidate, outputCandidate] of candidates) { - combinations.push([`${inputVariant}${inputCandidate}`, `${outputVariant}${outputCandidate}`]) - } -} - -describe('normalize candidates', () => { - test.each(combinations)('`%s` -> `%s`', async (candidate: string, result: string) => { - let designSystem = await __unstable__loadDesignSystem('@tailwind utilities', { - base: __dirname, - }) - - let [parsed] = designSystem.parseCandidate(candidate) - - expect(designSystem.printCandidate(parsed)).toEqual(result) - }) -}) diff --git a/packages/tailwindcss/src/candidate.ts b/packages/tailwindcss/src/candidate.ts index a389af365..5a5704989 100644 --- a/packages/tailwindcss/src/candidate.ts +++ b/packages/tailwindcss/src/candidate.ts @@ -3,15 +3,12 @@ import { decodeArbitraryValue } from './utils/decode-arbitrary-value' import { DefaultMap } from './utils/default-map' import { isValidArbitrary } from './utils/is-valid-arbitrary' import { segment } from './utils/segment' -import type { ValueFunctionNode } from './value-parser' import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' const COLON = 0x3a const DASH = 0x2d const LOWER_A = 0x61 const LOWER_Z = 0x7a -const IS_VALID_NAMED_VALUE = /^[a-zA-Z0-9_.%-]+$/ export type ArbitraryUtilityValue = { kind: 'arbitrary' @@ -218,102 +215,6 @@ export type Candidate = raw: string } -export function cloneCandidate(candidate: T): T { - switch (candidate.kind) { - case 'arbitrary': - return { - kind: candidate.kind, - property: candidate.property, - value: candidate.value, - modifier: candidate.modifier - ? { kind: candidate.modifier.kind, value: candidate.modifier.value } - : null, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - case 'static': - return { - kind: candidate.kind, - root: candidate.root, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - case 'functional': - return { - kind: candidate.kind, - root: candidate.root, - value: candidate.value - ? candidate.value.kind === 'arbitrary' - ? { - kind: candidate.value.kind, - dataType: candidate.value.dataType, - value: candidate.value.value, - } - : { - kind: candidate.value.kind, - value: candidate.value.value, - fraction: candidate.value.fraction, - } - : null, - modifier: candidate.modifier - ? { kind: candidate.modifier.kind, value: candidate.modifier.value } - : null, - variants: candidate.variants.map(cloneVariant), - important: candidate.important, - raw: candidate.raw, - } satisfies Extract as T - - default: - candidate satisfies never - throw new Error('Unknown candidate kind') - } -} - -export function cloneVariant(variant: T): T { - switch (variant.kind) { - case 'arbitrary': - return { - kind: variant.kind, - selector: variant.selector, - relative: variant.relative, - } satisfies Extract as T - - case 'static': - return { kind: variant.kind, root: variant.root } satisfies Extract< - Variant, - { kind: 'static' } - > as T - - case 'functional': - return { - kind: variant.kind, - root: variant.root, - value: variant.value ? { kind: variant.value.kind, value: variant.value.value } : null, - modifier: variant.modifier - ? { kind: variant.modifier.kind, value: variant.modifier.value } - : null, - } satisfies Extract as T - - case 'compound': - return { - kind: variant.kind, - root: variant.root, - variant: cloneVariant(variant.variant), - modifier: variant.modifier - ? { kind: variant.modifier.kind, value: variant.modifier.value } - : null, - } satisfies Extract as T - - default: - variant satisfies never - throw new Error('Unknown variant kind') - } -} - export function* parseCandidate(input: string, designSystem: DesignSystem): Iterable { // hover:focus:underline // ^^^^^ ^^^^^^ -> Variants @@ -372,14 +273,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter } } - // If there's more than one modifier, the utility is invalid. - // - // E.g.: - // - // - `bg-red-500/50/50` - let parts = segment(base, '/') - if (parts.length > 2) return - // Figure out the new base and the modifier segment if present. // // E.g.: @@ -389,7 +282,14 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter // ^^^^^^^^^^ -> Base without modifier // ^^ -> Modifier segment // ``` - let [baseWithoutModifier, modifierSegment = null] = parts + let [baseWithoutModifier, modifierSegment = null, additionalModifier] = segment(base, '/') + + // If there's more than one modifier, the utility is invalid. + // + // E.g.: + // + // - `bg-red-500/50/50` + if (additionalModifier) return let parsedModifier = modifierSegment === null ? null : parseModifier(modifierSegment) @@ -485,7 +385,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter // // E.g.: // - // ``` // bg-(--my-var) // ^^ -> Root // ^^^^^^^^^^ -> Arbitrary value @@ -557,7 +456,7 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter if (!isValidArbitrary(arbitraryValue)) continue // Extract an explicit typehint if present, e.g. `bg-[color:var(--my-var)])` - let typehint: string | null = null + let typehint = '' for (let i = 0; i < arbitraryValue.length; i++) { let code = arbitraryValue.charCodeAt(i) @@ -583,8 +482,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter continue } - if (typehint === '') continue - candidate.value = { kind: 'arbitrary', dataType: typehint || null, @@ -599,8 +496,6 @@ export function* parseCandidate(input: string, designSystem: DesignSystem): Iter ? null : `${value}/${modifierSegment}` - if (!IS_VALID_NAMED_VALUE.test(value)) continue - candidate.value = { kind: 'named', value, @@ -652,8 +547,6 @@ function parseModifier(modifier: string): CandidateModifier | null { } } - if (!IS_VALID_NAMED_VALUE.test(modifier)) return null - return { kind: 'named', value: modifier, @@ -708,18 +601,17 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia // Static, functional and compound variants { + // group-hover/group-name + // ^^^^^^^^^^^ -> Variant without modifier + // ^^^^^^^^^^ -> Modifier + let [variantWithoutModifier, modifier = null, additionalModifier] = segment(variant, '/') + // If there's more than one modifier, the variant is invalid. // // E.g.: // // - `group-hover/foo/bar` - let parts = segment(variant, '/') - if (parts.length > 2) return null - - // group-hover/group-name - // ^^^^^^^^^^^ -> Variant without modifier - // ^^^^^^^^^^ -> Modifier - let [variantWithoutModifier, modifier = null] = parts + if (additionalModifier) return null let roots = findRoots(variantWithoutModifier, (root) => { return designSystem.variants.has(root) @@ -806,8 +698,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia } } - if (!IS_VALID_NAMED_VALUE.test(value)) continue - return { kind: 'functional', root, @@ -819,13 +709,6 @@ export function parseVariant(variant: string, designSystem: DesignSystem): Varia case 'compound': { if (value === null) return null - // Forward the modifier of the compound variants to its subVariant. - // This allows for `not-group-hover/name:flex` to work. - if (modifier && (root === 'not' || root === 'has' || root === 'in')) { - value = `${value}/${modifier}` - modifier = null - } - let subVariant = designSystem.parseVariant(value) if (subVariant === null) return null @@ -889,11 +772,6 @@ function* findRoots(input: string, exists: (input: string) => boolean): Iterable // can skip any further parsing. if (root[1] === '') break - // Edge case: `@-…` is not valid as a variant or a utility so we want to - // skip if an `@` is followed by a `-`. Otherwise `@-2xl:flex` and - // `@-2xl:flex` would be considered the same. - if (root[0] === '@' && exists('@') && input[idx] === '-') break - yield root } @@ -1038,56 +916,40 @@ const printArbitraryValueCache = new DefaultMap((input) => { let drop = new Set() - let symbols = new Set([ - // Selectors - '~', // Subsequent sibling combinator - '>', // Child combinator + ValueParser.walk(ast, (node, { parent }) => { + let parentArray = parent === null ? ast : (parent.nodes ?? []) - // Math operators - '+', // or next sibling combinator - '-', - '*', // or universal selector - '/', - ]) - walk(ast, (node, ctx) => { // Handle operators (e.g.: inside of `calc(…)`) - if (node.kind === 'word' && symbols.has(node.value)) { - let idx = ctx.index + if ( + node.kind === 'word' && + // Operators + (node.value === '+' || node.value === '-' || node.value === '*' || node.value === '/') + ) { + let idx = parentArray.indexOf(node) ?? -1 // This should not be possible if (idx === -1) return - // a + b - // ^ node - // ^ previous (whitespace) - let previous = ctx.siblings[idx - 1] + let previous = parentArray[idx - 1] if (previous?.kind !== 'separator' || previous.value !== ' ') return - // a + b - // ^ node - // ^ next (whitespace) - let next = ctx.siblings[idx + 1] + let next = parentArray[idx + 1] if (next?.kind !== 'separator' || next.value !== ' ') return - // a + b - // ^ node - // ^ previous (node) - let previousPrevious = ctx.siblings[idx - 2] - if (previousPrevious && symbols.has(previousPrevious.value)) return - - // a + b - // ^ node - // ^ next (node) - let nextNext = ctx.siblings[idx + 2] - if (nextNext && symbols.has(nextNext.value)) return - drop.add(previous) drop.add(next) } + // The value parser handles `/` as a separator in some scenarios. E.g.: + // `theme(colors.red/50%)`. Because of this, we have to handle this case + // separately. + else if (node.kind === 'separator' && node.value.trim() === '/') { + node.value = '/' + } + // Leading and trailing whitespace else if (node.kind === 'separator' && node.value.length > 0 && node.value.trim() === '') { - if (ctx.siblings[0] === node || ctx.siblings[ctx.siblings.length - 1] === node) { + if (parentArray[0] === node || parentArray[parentArray.length - 1] === node) { drop.add(node) } } @@ -1097,48 +959,13 @@ const printArbitraryValueCache = new DefaultMap((input) => { else if (node.kind === 'separator' && node.value.trim() === ',') { node.value = ',' } - - // Wrap custom functions starting with `--`, in parentheses if preceded by - // a symbol. E.g.: `calc(100%---spacing(2))` → `calc(100%-(--spacing(2)))` - else if (node.kind === 'function' && node.value.startsWith('--')) { - let idx = ctx.index - - // When it's the first argument, then we don't have to wrap it in `(…)` - // - // E.g.: `--spacing(…)`, no need to wrap - // `calc(100%---spacing(…))`, we want to wrap - // - if (idx <= 0) return - - // When it's not the first argument, we likely want to wrap it. However, - // when it's separated by a `,` then it's readable enough. - // - // E.g.: `min(100%,--spacing(2))` is readable, in fact - // `min(100%,(--spacing(2)))` would make it worse - let previous = ctx.siblings[idx - 1] - if (previous?.kind === 'separator' && previous.value === ',') return - - // When it's part of a bigger list, aka no special symbols were used, then - // we don't have to wrap it either. - // - // E.g.: `shadow-[inset_0px_1px_--theme(--color-white/15%)]`, wrapping would look unnecessary: - // `shadow-[inset_0px_1px_(--theme(--color-white/15%))]` - let previousPrevious = ctx.siblings[idx - 2] - if (previousPrevious && !symbols.has(previousPrevious.value)) return - - return WalkAction.ReplaceSkip({ - kind: 'function', - value: '', // Unnamed, so will result in `(…)` - nodes: [node], - } satisfies ValueFunctionNode) - } }) if (drop.size > 0) { - walk(ast, (node) => { + ValueParser.walk(ast, (node, { replaceWith }) => { if (drop.has(node)) { drop.delete(node) - return WalkAction.ReplaceSkip([]) + replaceWith([]) } }) } diff --git a/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts b/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts deleted file mode 100644 index f612e5f7b..000000000 --- a/packages/tailwindcss/src/canonicalize-calc-expressions.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect, it } from 'vitest' -import { canonicalizeCalcExpressions } from './canonicalize-calc-expressions' - -it.each([ - ['calc(-1 * var(--foo))', 'calc(var(--foo) * -1)'], - ['calc(1rem + var(--foo))', 'calc(var(--foo) + 1rem)'], - ['calc(2rem * calc(3px * var(--foo)))', 'calc(calc(var(--foo) * 3px) * 2rem)'], - ['calc(var(--b) + var(--a))', 'calc(var(--a) + var(--b))'], - ['calc(3px * 2rem)', 'calc(2rem * 3px)'], - ['calc(5px * 3px)', 'calc(3px * 5px)'], - ['calc(1 * 1rem)', 'calc(1rem * 1)'], - ['calc(10 + 2)', 'calc(2 + 10)'], -])('`%s` → `%s` (%#)', (input, expected) => { - expect(canonicalizeCalcExpressions(input)).toBe(expected) -}) - -it.each([ - ['calc(1rem - var(--foo))'], - ['calc(1rem / 2)'], - ['calc(var(--a) + 1rem)'], - ['calc(2rem * 3px)'], -])('should keep `%s` (%#)', (input) => { - expect(canonicalizeCalcExpressions(input)).toBe(input) -}) diff --git a/packages/tailwindcss/src/canonicalize-calc-expressions.ts b/packages/tailwindcss/src/canonicalize-calc-expressions.ts deleted file mode 100644 index 7e08f8622..000000000 --- a/packages/tailwindcss/src/canonicalize-calc-expressions.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { dimensions } from './utils/dimensions' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -// Assumption: We already assume that we receive somewhat valid `calc()` -// expressions. So we will see `calc(1 + 1)` and not `calc(1+1)` -export function canonicalizeCalcExpressions(input: string): string { - let [canonicalized, valueAst] = canonicalizeCalcExpressionsAst(ValueParser.parse(input)) - - return canonicalized ? ValueParser.toCss(valueAst) : input -} - -export function canonicalizeCalcExpressionsAst( - ast: ValueParser.ValueAstNode[], -): [canonicalized: boolean, ast: ValueParser.ValueAstNode[]] { - let canonicalized = false - - walk(ast, { - exit(valueNode) { - // We are only interested in binary expressions in `calc(…)` and `(…)`, - // and only with the `*` or `+` operators. - if (valueNode.kind !== 'function') return - if (valueNode.value !== 'calc' && valueNode.value !== '') return - if (valueNode.nodes.length !== 5) return - if (valueNode.nodes[2].kind !== 'word') return - if (valueNode.nodes[2].value !== '*' && valueNode.nodes[2].value !== '+') return - - let lhs = valueNode.nodes[0] - let rhs = valueNode.nodes[4] - - if (shouldSwap(lhs, rhs)) { - canonicalized = true - - let replacement: ValueParser.ValueFunctionNode = { - kind: 'function', - value: valueNode.value, - nodes: [ - rhs, // Now lhs - valueNode.nodes[1], // Separator - valueNode.nodes[2], // Operator - valueNode.nodes[3], // Separator - lhs, // Now rhs - ], - } - - return WalkAction.ReplaceSkip(replacement) - } - }, - }) - - return [canonicalized, ast] -} - -function shouldSwap(lhs: ValueParser.ValueAstNode, rhs: ValueParser.ValueAstNode): boolean { - let lhsDimension = lhs.kind === 'word' ? dimensions.get(lhs.value) : null - let rhsDimension = rhs.kind === 'word' ? dimensions.get(rhs.value) : null - - if (lhsDimension !== null && rhsDimension === null) return true - if (lhsDimension === null && rhsDimension !== null) return false - - if (lhsDimension !== null && rhsDimension !== null) { - let [lhsValue, lhsUnit] = lhsDimension - let [rhsValue, rhsUnit] = rhsDimension - - // Within dimensions, keep unit-bearing values ahead of unitless numbers so - // `1rem` sorts before `1`. - if (lhsUnit === null && rhsUnit !== null) return true - if (lhsUnit !== null && rhsUnit === null) return false - - // Then sort dimensions numerically, and finally by unit for ties. - if (lhsValue !== rhsValue) { - return lhsValue - rhsValue > 0 - } - - if (lhsUnit !== rhsUnit) { - return (lhsUnit ?? '').localeCompare(rhsUnit ?? '') > 0 - } - } - - // Both nodes are unknown values (not dimensions), sort them as strings - return ValueParser.toCss([lhs]).localeCompare(ValueParser.toCss([rhs])) > 0 -} diff --git a/packages/tailwindcss/src/canonicalize-candidates.test.ts b/packages/tailwindcss/src/canonicalize-candidates.test.ts deleted file mode 100644 index 567f0ad0d..000000000 --- a/packages/tailwindcss/src/canonicalize-candidates.test.ts +++ /dev/null @@ -1,1663 +0,0 @@ -import { randomUUID } from 'node:crypto' -import fs from 'node:fs' -import path from 'node:path' -import { describe, expect, test } from 'vitest' -import { __unstable__loadDesignSystem } from '.' -import { cartesian } from './cartesian' -import type { CanonicalizeOptions } from './intellisense' -import plugin from './plugin' -import { DefaultMap } from './utils/default-map' - -const css = String.raw -const timeout = 25_000 -const defaultTheme = fs.readFileSync(path.resolve(__dirname, '../theme.css'), 'utf8') - -const designSystems = new DefaultMap((base: string) => { - return new DefaultMap((input: string) => { - return __unstable__loadDesignSystem(input, { - base, - async loadStylesheet() { - return { - path: '', - base: '', - content: css` - @tailwind utilities; - - ${defaultTheme} - - /* TODO(perf): Only here to speed up the tests */ - @theme { - --*: initial; - --breakpoint-lg: 64rem; - --breakpoint-md: 48rem; - --color-blue-200: oklch(88.2% 0.059 254.128); - --color-blue-500: oklch(62.3% 0.214 259.815); - --color-red-500: oklch(63.7% 0.237 25.331); - --color-white: #fff; - --container-md: 28rem; - --font-weight-normal: 400; - --leading-relaxed: 1.625; - --spacing: 0.25rem; - --text-sm--line-height: calc(1.25 / 0.875); - --text-sm: 0.875rem; - --text-xs--line-height: calc(1 / 0.75); - --text-xs: 0.75rem; - } - `, - } - }, - }) - }) -}) - -const DEFAULT_CANONICALIZATION_OPTIONS: CanonicalizeOptions = { - rem: 16, - collapse: true, - logicalToPhysical: true, -} - -describe.each([['default'], ['with-variant'], ['important'], ['prefix']])('%s', (strategy) => { - let testName = '%s → %s (%#)' - if (strategy === 'with-variant') { - testName = testName.replaceAll('%s', 'focus:%s') - } else if (strategy === 'important') { - testName = testName.replaceAll('%s', '%s!') - } else if (strategy === 'prefix') { - testName = testName.replaceAll('%s', 'tw:%s') - } - - function prepare(candidate: string) { - if (strategy === 'with-variant') { - candidate = `focus:${candidate}` - } else if (strategy === 'important') { - candidate = `${candidate}!` - } else if (strategy === 'prefix') { - candidate = `tw:${candidate}` - - // Prefix all known CSS variables with `--tw-`, except when used inside of `--theme(…)`. - if (candidate.includes('--')) { - candidate = candidate - .replace( - // Replace the variable, as long as it is preceded by a `(`, e.g.: - // `bg-(--foo)` or an `:` in case of `bg-(color:--foo)`. - // - // It also has to end in a `,` or `)` to prevent replacing functions - // that look like variables, e.g.: `--spacing(…)` - /([(:])--([\w-]+)([,)])/g, - (_, start, variable, end) => `${start}--tw-${variable}${end}`, - ) - .replaceAll('--theme(--tw-', '--theme(--') - } - } - - return candidate - } - - async function expectCanonicalization( - input: string, - candidates: string, - expected: string, - options: CanonicalizeOptions = DEFAULT_CANONICALIZATION_OPTIONS, - ) { - let preparedCandidates = candidates.split(/\s+/g).map(prepare) - let preparedExpected = expected.split(/\s+/g).map(prepare) - - if (strategy === 'prefix') { - input = input.replace("@import 'tailwindcss';", "@import 'tailwindcss' prefix(tw);") - } - - let designSystem = await designSystems.get(__dirname).get(input) - let actual = designSystem.canonicalizeCandidates(preparedCandidates, options) - - try { - expect(actual).toEqual(preparedExpected) - } catch (err) { - if (err instanceof Error) Error.captureStackTrace(err, expectCanonicalization) - throw err - } - } - - /// ---------------------------------- - - describe('deprecated utilities', () => { - test.each([ - /// Legacy bg-gradient-* → bg-linear-* - ['bg-gradient-to-t', 'bg-linear-to-t'], - ['bg-gradient-to-tr', 'bg-linear-to-tr'], - ['bg-gradient-to-r', 'bg-linear-to-r'], - ['bg-gradient-to-br', 'bg-linear-to-br'], - ['bg-gradient-to-b', 'bg-linear-to-b'], - ['bg-gradient-to-bl', 'bg-linear-to-bl'], - ['bg-gradient-to-l', 'bg-linear-to-l'], - ['bg-gradient-to-tl', 'bg-linear-to-tl'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - let deprecated: [string, string][] = [ - ['order-none', 'order-0'], - ['break-words', 'wrap-break-word'], - ['overflow-ellipsis', 'text-ellipsis'], - - ['start-full', 'inset-s-full'], - ['-start-full', '-inset-s-full'], - ['start-auto', 'inset-s-auto'], - ['start-px', 'inset-s-px'], - ['-start-px', '-inset-s-px'], - ['start-8', 'inset-s-8'], // Within default spacing scale - ['-start-8', '-inset-s-8'], // Within default spacing scale - ['start-123', 'inset-s-123'], // Outside of default spacing scale - ['-start-123', '-inset-s-123'], // Outside of default spacing scale - - ['end-full', 'inset-e-full'], - ['-end-full', '-inset-e-full'], - ['end-auto', 'inset-e-auto'], - ['end-px', 'inset-e-px'], - ['-end-px', '-inset-e-px'], - ['end-8', 'inset-e-8'], // Within default spacing scale - ['-end-8', '-inset-e-8'], // Within default spacing scale - ['end-123', 'inset-e-123'], // Outside of default spacing scale - ['-end-123', '-inset-e-123'], // Outside of default spacing scale - ] - - test.each(deprecated)(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - describe('With custom implementation', () => { - // Creating a shared CSS file such that we can re-use the same design - // system for all of these. - let customImplementations = deprecated - .map( - ([candidate]) => css` - @utility ${candidate} { - --custom-${randomUUID()}: implementation; - } - `, - ) - .join('\n') - - // Keep the current utility because of the custom implementation - test.each(deprecated.map(([candidate]) => [candidate, candidate]))( - testName, - { timeout }, - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - - ${customImplementations} - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - }) - }) - - describe('arbitrary properties', () => { - test.each([ - /// theme(…) to `var(…)` - // Keep candidates that don't contain `theme(…)` or `theme(…, …)` - ['[color:red]', 'text-[red]'], - - // Handle special cases around `.1` in the `theme(…)` - ['[--value:theme(spacing.1)]', '[--value:--spacing(1)]'], - ['[--value:theme(fontSize.xs.1.lineHeight)]', '[--value:var(--text-xs--line-height)]'], - ['[--value:theme(spacing[1.25])]', '[--value:--spacing(1.25)]'], - - // Should not convert invalid spacing values to calc - ['[--value:theme(spacing[1.1])]', '[--value:theme(spacing[1.1])]'], - - // Convert to `var(…)` if we can resolve the path - ['[color:theme(colors.red.500)]', 'text-red-500'], // Arbitrary property - ['[color:theme(colors.red.500)]/50', 'text-red-500/50'], // Arbitrary property + modifier - - // Multiple `theme(… / …)` calls should result in modern syntax of `theme(…)` - // - Can't convert to `var(…)` because that would lose the modifier. - // - Can't convert to a candidate modifier because there are multiple - // `theme(…)` calls. - // - // If we really want to, we can make a fancy migration that tries to move it - // to a candidate modifier _if_ all `theme(…)` calls use the same modifier. - [ - '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]', - 'text-[--theme(--color-red-500/50,--theme(--color-blue-500/50))]', - ], - [ - '[color:theme(colors.red.500/50,theme(colors.blue.500/50))]/50', - 'text-[--theme(--color-red-500/50,--theme(--color-blue-500/50))]/50', - ], - - // Convert the `theme(…)`, but try to move the inline modifier (e.g. `50%`), - // to a candidate modifier. - // Arbitrary property, with simple percentage modifier - ['[color:theme(colors.red.500/75%)]', 'text-red-500/75'], - - // Arbitrary property, with numbers (0-1) without a unit - ['[color:theme(colors.red.500/.12)]', 'text-red-500/12'], - ['[color:theme(colors.red.500/0.12)]', 'text-red-500/12'], - - // Arbitrary property, with more complex modifier (we only allow whole numbers - // as bare modifiers). Convert the complex numbers to arbitrary values instead. - ['[color:theme(colors.red.500/12.34%)]', 'text-red-500/[12.34%]'], - ['[color:theme(colors.red.500/var(--opacity))]', 'text-red-500/(--opacity)'], - ['[color:theme(colors.red.500/.12345)]', 'text-red-500/1234.5'], - ['[color:theme(colors.red.500/50.25%)]', 'text-red-500/50.25'], - - // Arbitrary property that already contains a modifier - ['[color:theme(colors.red.500/50%)]/50', 'text-[--theme(--color-red-500/50%)]/50'], - - // `calc(var(--spacing)*…)` to `--spacing(…)` - ['[padding-top:min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'], - [ - '[padding-top:min(20%,calc(var(--spacing)*var(--other)))]', - 'pt-[min(20%,--spacing(var(--other)))]', - ], - ['[padding-top:calc(var(--spacing)*8)]', 'pt-8'], - ['[padding-top:calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'], - - // `theme(…)` calls valid in v3, but not in v4 should still be converted. - ['[--foo:theme(transitionDuration.500)]', '[--foo:theme(transitionDuration.500)]'], - - // Invalid cases - ['[--foo:theme(colors.red.500/50/50)]', '[--foo:theme(colors.red.500/50/50)]'], - ['[--foo:theme(colors.red.500/50/50)]/50', '[--foo:theme(colors.red.500/50/50)]/50'], - - // Partially invalid cases - [ - '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]', - '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]', - ], - [ - '[--foo:theme(colors.red.500/50/50)_theme(colors.blue.200)]/50', - '[--foo:theme(colors.red.500/50/50)_var(--color-blue-200)]/50', - ], - - // If a utility sets `property` and `--tw-{property}` with the same value, - // we can ignore the `--tw-{property}`. This is just here for composition. - // This means that we should be able to upgrade the one _without_ to the one - // _with_ the variable - ['[font-weight:400]', 'font-normal'], - ['[line-height:0]', 'leading-0'], - ['[border-style:solid]', 'border-solid'], - - // Do not constant fold `0` to `0` when the type is unknown (which - // is often the case with CSS variables) - ['[--foo:0px]', '[--foo:0px]'], - ['[--foo:calc(0px*1)]', '[--foo:calc(0px*1)]'], - ['[--foo:calc(0*1rem)]', '[--foo:calc(0*1rem)]'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test.each([ - // Arbitrary property to static utility - ['[text-wrap:balance]', 'text-balance'], - - // Arbitrary property to static utility with slight differences in - // whitespace. This will require some canonicalization. - ['[display:_flex_]', 'flex'], - ['[display:_flex]', 'flex'], - ['[display:flex_]', 'flex'], - - // Arbitrary property to named functional utility - ['[color:var(--color-red-500)]', 'text-red-500'], - ['[background-color:var(--color-red-500)]', 'bg-red-500'], - - // Arbitrary property to named utility (case insensitive) - ['[color:#fff]', 'text-white'], - ['[color:#FFF]', 'text-white'], - - // Arbitrary property with modifier to named functional utility with modifier - ['[color:var(--color-red-500)]/25', 'text-red-500/25'], - - // Arbitrary property with arbitrary modifier to named functional utility with - // arbitrary modifier - ['[color:var(--color-red-500)]/[25%]', 'text-red-500/25'], - ['[color:var(--color-red-500)]/[100%]', 'text-red-500'], - ['[color:var(--color-red-500)]/100', 'text-red-500'], - ['[color:var(--color-red-500)]/[10%]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[10.0%]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[.1]', 'text-red-500/10'], - ['[color:var(--color-red-500)]/[.10]', 'text-red-500/10'], - // No need for `/50` because that's already encoded in the `--color-primary` - // value - ['[color:oklch(62.3%_0.214_259.815)]/50', 'text-primary'], - - // Arbitrary property to arbitrary value - ['[max-height:20%]', 'max-h-[20%]'], - - // Arbitrary property to bare value - ['[grid-column:2]', 'col-2'], - ['[grid-column:1234]', 'col-1234'], - - // Complex arbitrary property to arbitrary value - [ - '[grid-template-columns:repeat(2,minmax(100px,1fr))]', - 'grid-cols-[repeat(2,minmax(100px,1fr))]', - ], - // Complex arbitrary property to bare value - ['[grid-template-columns:repeat(2,minmax(0,1fr))]', 'grid-cols-2'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - - @theme { - --*: initial; - --spacing: 0.25rem; - --color-red-500: red; - --color-white: #fff; - - /* Equivalent of blue-500/50 */ - --color-primary: color-mix(in oklab, oklch(62.3% 0.214 259.815) 50%, transparent); - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary values', () => { - test.each([ - // Convert to `var(…)` if we can resolve the path - ['bg-[theme(colors.red.500)]', 'bg-red-500'], // Arbitrary value - ['bg-[size:theme(spacing.4)]', 'bg-size-[--spacing(4)]'], // Arbitrary value + data type hint - - // Pretty print CSS functions preceded by an operator to prevent consecutive - // operator characters. - ['w-[calc(100dvh-theme(spacing.2))]', 'w-[calc(100dvh-(--spacing(2)))]'], - ['w-[calc(100dvh+theme(spacing.2))]', 'w-[calc(100dvh+(--spacing(2)))]'], - ['w-[calc(100dvh/theme(spacing.2))]', 'w-[calc(100dvh/(--spacing(2)))]'], - ['w-[calc(100dvh*theme(spacing.2))]', 'w-[calc(100dvh*(--spacing(2)))]'], - - // Convert to `var(…)` if we can resolve the path, but keep fallback values - ['bg-[theme(colors.red.500,red)]', 'bg-(--color-red-500,red)'], - - // Keep `theme(…)` if we can't resolve the path - ['bg-[theme(colors.foo.1000)]', 'bg-[theme(colors.foo.1000)]'], - - // Keep `theme(…)` if we can't resolve the path, but still try to convert the - // fallback value. - ['bg-[theme(colors.foo.1000,theme(colors.red.500))]', 'bg-red-500'], - - // Use `theme(…)` (deeply nested) inside of a `calc(…)` function - ['text-[calc(theme(fontSize.xs)*2)]', 'text-[calc(var(--text-xs)*2)]'], - - // Arbitrary value - ['bg-[theme(colors.red.500/75%)]', 'bg-red-500/75'], - ['bg-[theme(colors.red.500/12.34%)]', 'bg-red-500/[12.34%]'], - - // Arbitrary value with different casing - ['bg-[#fff]', 'bg-white'], - ['bg-[#FFF]', 'bg-white'], - - // Values that don't contain only `theme(…)` calls should not be converted to - // use a modifier since the color is not the whole value. - [ - 'shadow-[shadow:inset_0px_1px_theme(colors.white/15%)]', - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - ], - - // Arbitrary value, where the candidate already contains a modifier - // This should still migrate the `theme(…)` syntax to the modern syntax. - ['bg-[theme(colors.red.500/50%)]/50', 'bg-[--theme(--color-red-500/50%)]/50'], - - // Variants, we can't use `var(…)` especially inside of `@media(…)`. But - // since the value matches the `lg` breakpoint exactly, we can migrate to - // the `max-lg` variant. - ['max-[theme(screens.lg)]:flex', 'max-lg:flex'], - ['min-[theme(screens.lg)]:flex', 'lg:flex'], // We could also map to `min-lg:flex` - // There are no variables for `--spacing` multiples, so we can't convert this - ['max-[theme(spacing.4)]:flex', 'max-[theme(spacing.4)]:flex'], - - // This test in itself doesn't make much sense. But we need to make sure - // that this doesn't end up as the modifier in the candidate itself. - ['max-[theme(spacing.4/50)]:flex', 'max-[theme(spacing.4/50)]:flex'], - - // `theme(…)` calls in another CSS function is replaced correctly. - // Additionally we remove unnecessary whitespace. - ['grid-cols-[min(50%_,_theme(spacing.80))_auto]', 'grid-cols-[min(50%,--spacing(80))_auto]'], - - // `calc(var(--spacing)*…)` to `--spacing(…)` - ['pt-[min(20%,calc(var(--spacing)*8))]', 'pt-[min(20%,--spacing(8))]'], - ['pt-[min(20%,calc(var(--spacing)*var(--other)))]', 'pt-[min(20%,--spacing(var(--other)))]'], - ['pt-[calc(var(--spacing)*8)]', 'pt-8'], - ['pt-[calc(var(--spacing)*var(--other))]', 'pt-[--spacing(var(--other))]'], - - // Renamed theme keys - ['max-w-[theme(screens.md)]', 'max-w-(--breakpoint-md)'], - ['w-[theme(maxWidth.md)]', 'w-md'], - - // Arbitrary property to static utility - // Map number to keyword-like value - ['leading-[1]', 'leading-none'], - - // Arbitrary value to bare value - ['border-[2px]', 'border-2'], - ['border-[1234px]', 'border-1234'], - - // Arbitrary value with data type, to more specific arbitrary value - ['bg-[position:123px]', 'bg-position-[123px]'], - ['bg-[size:123px]', 'bg-size-[123px]'], - - // Arbitrary value with inferred data type, to more specific arbitrary value - ['bg-[123px]', 'bg-position-[123px]'], - - // Arbitrary value with spacing mul - ['w-[64rem]', 'w-256'], - - // Arbitrary value to bare value with percentage - ['from-[25%]', 'from-25%'], - - // Arbitrary percentage value must be a whole number. Should not migrate to - // a bare value. - ['from-[2.5%]', 'from-[2.5%]'], - - // Negative arbitrary values can be simplified - // 1. Try to move the sign _inside_ the arbitrary value - // 2. Try to move the sign _out_ of the arbitrary value - ['-mt-[12rem]', '-mt-48'], // Arbitrary value → bare value - ['-mt-[-12rem]', 'mt-48'], // Double negation - ['-mt-[12.34rem]', 'mt-[-12.34rem]'], // Move `-` inside - ['-mt-[-12.34rem]', 'mt-[12.34rem]'], // Move `-` inside, double negation - ['-mt-[12.34px]', 'mt-[-12.34px]'], - ['-mt-[-12.34px]', 'mt-[12.34px]'], - ['-mt-[492px]', '-mt-123'], // Moving inside, allows us to migrate to a bare value - ['-mt-[calc(-1*492px)]', 'mt-123'], // Double negation and constant folding into bare value - ['-mt-[-492px]', 'mt-123'], - ['-mt-[calc(-1*-492px)]', '-mt-123'], // Constant folding with calc expressions - ['-mt-(--my-var)', '-mt-(--my-var)'], // Keep as-is - ['-mt-[var(--my-var)]', '-mt-(--my-var)'], // Keep as-is, but convert to shorthand - ['mt-[calc(var(--my-var)*-1)]', '-mt-(--my-var)'], // Move `-` out - ['mt-[calc(-1*var(--my-var))]', '-mt-(--my-var)'], // Move `-` out - ['-mt-[calc(var(--my-var)*-1)]', 'mt-(--my-var)'], // Move `-` out - ['-mt-[calc(-1*var(--my-var))]', 'mt-(--my-var)'], // Move `-` out - ['mt-[calc(-1*calc(-1*var(--my-var)))]', 'mt-(--my-var)'], // Move `-` out - ['-mt-[calc(-1*calc(-1*var(--my-var)))]', '-mt-(--my-var)'], // Move `-` out - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - @theme { - --color-white: #fff; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary utilities', () => { - test('migrate with custom static utility `@utility custom {…}`', { timeout }, async () => { - let candidate = '[--key:value]' - let expected = 'custom' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility custom { - --key: value; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test( - 'migrate with custom functional utility `@utility custom-* {…}`', - { timeout }, - async () => { - let candidate = '[--key:value]' - let expected = 'custom-value' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility custom-* { - --key: --value('value'); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test( - 'migrate with custom functional utility `@utility custom-* {…}` that supports bare values', - { timeout }, - async () => { - let candidate = '[--resolved-value:4]' - let expected = 'example-4' - - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @utility example-* { - --resolved-value: --value(integer); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test.each([ - ['[--resolved-value:0]', 'example-0'], - ['[--resolved-value:4]', 'example-4'], - ['[--resolved-value:8]', 'example-a'], - ['example-[0]', 'example-0'], - ['example-[4]', 'example-4'], - ['example-[8]', 'example-a'], - ])( - 'migrate custom @utility from arbitrary values to bare values and named values (based on theme)', - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --example-a: 8; - } - - @utility example-* { - --resolved-value: --value(--example, integer, [integer]); - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - describe.each([['@theme'], ['@theme inline']])('%s', (theme) => { - test.each([ - ['[color:CanvasText]', 'text-canvas'], - ['text-[CanvasText]', 'text-canvas'], - ])(`migrate arbitrary value to theme value ${testName}`, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ${theme} { - --*: initial; - --color-canvas: CanvasText; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // Some utilities read from specific namespaces, in this case we do not want - // to migrate to a value in that namespace if we reference a variable that - // results in the same value, but comes from a different namespace. - // - // E.g.: `max-w` reads from: ['--max-width', '--spacing', '--container'] - test.each([ - // `max-w` does not read from `--breakpoint-md`, but `--breakpoint-md` and - // `--container-3xl` happen to result in the same value. The difference is - // the semantics of the value. - ['max-w-(--breakpoint-md)', 'max-w-(--breakpoint-md)'], - ['max-w-(--container-3xl)', 'max-w-3xl'], - ])( - `migrate arbitrary value to theme value ${testName}`, - { timeout }, - async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ${theme} { - --*: initial; - --breakpoint-md: 48rem; - --container-3xl: 48rem; - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - }) - - test( - 'migrate an arbitrary property without spaces, to a theme value with spaces (canonicalization)', - { timeout }, - async () => { - let candidate = 'font-[foo,bar,baz]' - let expected = 'font-example' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --font-example: foo, bar, baz; - } - ` - - await expectCanonicalization(input, candidate, expected) - }, - ) - - test.each([ - // Default spacing scale - ['w-[64rem]', 'w-256', '0.25rem'], - - // Non-suggested numbers - ['gap-[7.25rem]', 'gap-29', '0.25rem'], - ['gap-[calc(7rem+0.25rem)]', 'gap-29', '0.25rem'], - ['gap-[116px]', 'gap-29', '0.25rem'], - - // Non-suggested numbers, with the same spacing scale with different - // units - ['gap-[7.25rem]', 'gap-29', '4px'], - ['gap-[calc(7rem+0.25rem)]', 'gap-29', '4px'], - ['gap-[116px]', 'gap-29', '4px'], - - // Non-suggested numbers, with a different spacing scale - ['gap-[7.25rem]', 'gap-116', '1px'], - ['gap-[calc(7rem+0.25rem)]', 'gap-116', '1px'], - ['gap-[116px]', 'gap-116', '1px'], - - // Keep arbitrary value if units are different - ['w-[124px]', 'w-31', '0.25rem'], - - // Keep arbitrary value if bare value doesn't fit in steps of .25 - ['w-[0.123rem]', 'w-[0.123rem]', '0.25rem'], - - // Custom pixel based spacing scale - ['w-[123px]', 'w-123', '1px'], - ['w-[256px]', 'w-128', '2px'], - ])(`${testName} (spacing = \`%s\`)`, { timeout }, async (candidate, expected, spacing) => { - let input = css` - @import 'tailwindcss'; - - @theme { - --*: initial; - --spacing: ${spacing}; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('bare values', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --spacing: 0.25rem; - --aspect-video: 16 / 9; - --example-a: 8; - } - - @utility example-* { - --resolved-value: --value(--example, integer); - } - ` - - test.each([ - // Built-in utility with bare value fraction - ['aspect-16/9', 'aspect-video'], - - // Custom utility with bare value integer - ['example-8', 'example-a'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary variants', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --breakpoint-lg: 64rem; - } - ` - - test.each([ - // Arbitrary variant to static variant - ['[&:focus]:flex', 'focus:flex'], - - // Arbitrary variant to static variant with at-rules - ['[@media(scripting:_none)]:flex', 'noscript:flex'], - - // Arbitrary variant to static utility at-rules and with slight differences - // in whitespace. This will require some canonicalization. - ['[@media(scripting:none)]:flex', 'noscript:flex'], - ['[@media(scripting:_none)]:flex', 'noscript:flex'], - ['[@media_(scripting:_none)]:flex', 'noscript:flex'], - - // Arbitrary variants with media queries cannot use `var(…)`, but if an - // equivalent variant exists we can use that one instead. - ['[@media(width>=theme(screens.lg))]:flex', 'lg:flex'], // `min-lg:flex` would also be valid - ['[@media(width { - await expectCanonicalization(input, candidate, expected) - }) - - test('unsafe migrations keep the candidate as-is', { timeout }, async () => { - // `hover:` also includes an `@media` query in addition to the `&:hover` - // state. Migration is not safe because the functionality would be different. - let candidate = '[&:hover]:flex' - let expected = '[&:hover]:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('make unsafe migration safe (1)', { timeout }, async () => { - // Overriding the `hover:` variant to only use a selector will make the - // migration safe. - let candidate = '[&:hover]:flex' - let expected = 'hover:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant hover (&:hover); - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('make unsafe migration safe (2)', { timeout }, async () => { - // Overriding the `hover:` variant to only use a selector will make the - // migration safe. This time with the long-hand `@variant` syntax. - let candidate = '[&:hover]:flex' - let expected = 'hover:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant hover { - &:hover { - @slot; - } - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('custom selector-based variants', { timeout }, async () => { - let candidate = '[&.macos]:flex' - let expected = 'is-macos:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant is-macos (&.macos); - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test('custom @media-based variants', { timeout }, async () => { - let candidate = '[@media(prefers-reduced-transparency:reduce)]:flex' - let expected = 'transparency-safe:flex' - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - } - @variant transparency-safe { - @media (prefers-reduced-transparency: reduce) { - @slot; - } - } - ` - - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('drop unnecessary data types', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --color-red-500: red; - } - ` - - test.each([ - // A color value can be inferred from the value - ['bg-[color:#008cc]', 'bg-[#008cc]'], - - // A color is the default for `bg-*` - ['bg-(color:--my-value)', 'bg-(--my-value)'], - - // A color with a known theme variable migrates to the full utility - ['bg-(color:--color-red-500)', 'bg-red-500'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('arbitrary value to bare value', () => { - test.each([ - ['aspect-[12/34]', 'aspect-12/34'], - ['aspect-[1.2/34]', 'aspect-[1.2/34]'], - ['col-start-[7]', 'col-start-7'], - ['flex-[2]', 'flex-2'], // `flex` is implemented as static and functional utilities - - ['grid-cols-[subgrid]', 'grid-cols-subgrid'], - ['grid-rows-[subgrid]', 'grid-rows-subgrid'], - - // Handle zeroes - ['m-[0]', 'm-0'], - ['m-[0px]', 'm-0'], - ['m-[0rem]', 'm-0'], - - ['-m-[0]', 'm-0'], - ['-m-[0px]', 'm-0'], - ['-m-[0rem]', 'm-0'], - - ['m-[-0]', 'm-0'], - ['m-[-0px]', 'm-0'], - ['m-[-0rem]', 'm-0'], - - ['-m-[-0]', 'm-0'], - ['-m-[-0px]', 'm-0'], - ['-m-[-0rem]', 'm-0'], - - ['[margin:0]', 'm-0'], - ['[margin:-0]', 'm-0'], - ['[margin:0px]', 'm-0'], - - // Limit conversions for big values using the `--spacing` multiplier - ['left-[99999px]', 'left-[99999px]'], // This would otherwise result in `left-24999.75` - ['left-[-99999px]', 'left-[-99999px]'], // This would otherwise result in `-left-24999.75` - ['left-[96rem]', 'left-384'], // Within the limit - ['left-[-96rem]', '-left-384'], // Within the limit - ['left-[calc(96rem+1px)]', 'left-[calc(96rem+1px)]'], // Out of the positive limit - ['left-[calc(-96rem-1px)]', 'left-[calc(-96rem-1px)]'], // Out of the negative limit - ['z-[9999999]', 'z-9999999'], // `--spacing` multiplier is not used - - // Not a length-unit, can't safely constant fold - ['[margin:0%]', 'm-[0%]'], - - // Only 50-200% (inclusive) are valid: - // https://developer.mozilla.org/en-US/docs/Web/CSS/font-stretch#percentage - ['font-stretch-[50%]', 'font-stretch-50%'], - ['font-stretch-[50.5%]', 'font-stretch-[50.5%]'], - ['font-stretch-[201%]', 'font-stretch-[201%]'], - ['font-stretch-[49%]', 'font-stretch-[49%]'], - // Should stay as-is - ['font-stretch-[1/2]', 'font-stretch-[1/2]'], - - // Bare value with % is valid for these utilities - ['from-[28%]', 'from-28%'], - ['via-[28%]', 'via-28%'], - ['to-[28%]', 'to-28%'], - ['from-[28.5%]', 'from-[28.5%]'], - ['via-[28.5%]', 'via-[28.5%]'], - ['to-[28.5%]', 'to-[28.5%]'], - - // This test in itself is a bit flawed because `text-[1/2]` currently - // generates something. Converting it to `text-1/2` doesn't produce anything. - ['text-[1/2]', 'text-[1/2]'], - - // Leading is special, because `leading-[123]` is the direct value of 123, but - // `leading-123` maps to `calc(--spacing(123))`. - ['leading-[123]', 'leading-[123]'], - - ['data-[selected]:flex', 'data-selected:flex'], - ['data-[foo=bar]:flex', 'data-[foo=bar]:flex'], - - ['supports-[gap]:flex', 'supports-gap:flex'], - ['supports-[display:grid]:flex', 'supports-[display:grid]:flex'], - - ['group-data-[selected]:flex', 'group-data-selected:flex'], - ['group-data-[foo=bar]:flex', 'group-data-[foo=bar]:flex'], - ['group-has-data-[selected]:flex', 'group-has-data-selected:flex'], - - ['aria-[selected]:flex', 'aria-[selected]:flex'], - ['aria-[selected="true"]:flex', 'aria-selected:flex'], - ['aria-[selected*="true"]:flex', 'aria-[selected*="true"]:flex'], - - ['group-aria-[selected]:flex', 'group-aria-[selected]:flex'], - ['group-aria-[selected="true"]:flex', 'group-aria-selected:flex'], - ['group-has-aria-[selected]:flex', 'group-has-aria-[selected]:flex'], - - ['max-lg:hover:data-[selected]:flex', 'max-lg:hover:data-selected:flex'], - [ - 'data-[selected]:aria-[selected="true"]:aspect-[12/34]', - 'data-selected:aria-selected:aspect-12/34', - ], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidate, expected) - }) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20365 - describe('media query and container query variants', () => { - test.each([ - // Breakpoints (`--breakpoint-lg: 64rem`, `--breakpoint-md: 48rem`) - ['min-[64rem]:flex', 'lg:flex'], - ['max-[64rem]:flex', 'max-lg:flex'], - ['min-[48rem]:flex', 'md:flex'], - ['max-[48rem]:flex', 'max-md:flex'], - - // With the `rem` option set, equivalent `px` values should also be - // canonicalized to the breakpoint variants. - ['min-[1024px]:flex', 'lg:flex'], - ['max-[1024px]:flex', 'max-lg:flex'], - - // Container queries (`--container-md: 28rem`) - ['@min-[28rem]:flex', '@md:flex'], - ['@max-[28rem]:flex', '@max-md:flex'], - ['@min-[448px]:flex', '@md:flex'], - ['@max-[448px]:flex', '@max-md:flex'], - - // Values that don't map to a known breakpoint or container size should - // stay as-is. - ['min-[123px]:flex', 'min-[123px]:flex'], - ['max-[123px]:flex', 'max-[123px]:flex'], - ['@min-[123px]:flex', '@min-[123px]:flex'], - ['@max-[123px]:flex', '@max-[123px]:flex'], - - // Named variants that produce the same CSS should be kept as-is - ['min-lg:flex', 'min-lg:flex'], - ['@min-md:flex', '@min-md:flex'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - test.each([ - // Without the `rem` option, `px` values cannot be safely converted to - // `rem` based breakpoints. - ['min-[1024px]:flex', 'min-[1024px]:flex'], - ['@min-[448px]:flex', '@min-[448px]:flex'], - - // Same unit still works without the `rem` option - ['min-[64rem]:flex', 'lg:flex'], - ['max-[64rem]:flex', 'max-lg:flex'], - ['@min-[28rem]:flex', '@md:flex'], - ['@max-[28rem]:flex', '@max-md:flex'], - ])(`${testName} (no rem option)`, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected, { - collapse: true, - logicalToPhysical: true, - }) - }) - }) - - describe('modernize arbitrary variants', () => { - test.each([ - // Arbitrary variants - ['[[data-visible]]:flex', 'data-visible:flex'], - ['[&[data-visible]]:flex', 'data-visible:flex'], - ['[[data-visible]&]:flex', 'data-visible:flex'], - ['[&>[data-visible]]:flex', '*:data-visible:flex'], - ['[&_>_[data-visible]]:flex', '*:data-visible:flex'], - ['[&>*]:flex', '*:flex'], - ['[&_>_*]:flex', '*:flex'], - ['[&_>_[foo]]:flex', '*:[[foo]]:flex'], - ['[&_[foo]]:flex', '**:[[foo]]:flex'], - ['[&_>_[foo=bar]]:flex', '*:[[foo=bar]]:flex'], - ['[&_[foo=bar]]:flex', '**:[[foo=bar]]:flex'], - - ['[&_[data-visible]]:flex', '**:data-visible:flex'], - ['[&_*]:flex', '**:flex'], - - ['[&:first-child]:flex', 'first:flex'], - ['[&:not(:first-child)]:flex', 'not-first:flex'], - - ['[&_:first-child]:flex', '**:first:flex'], - ['[&_>_:first-child]:flex', '*:first:flex'], - ['[&_:--custom]:flex', '**:[:--custom]:flex'], - ['[&_>_:--custom]:flex', '*:[:--custom]:flex'], - - // in-* variants - ['[p_&]:flex', 'in-[p]:flex'], - ['[.foo_&]:flex', 'in-[.foo]:flex'], - ['[[data-visible]_&]:flex', 'in-data-visible:flex'], - // Multiple selectors, should stay as-is - ['[[data-foo][data-bar]_&]:flex', '[[data-foo][data-bar]_&]:flex'], - // Using `>` instead of ` ` should not be transformed: - ['[figure>&]:my-0', '[figure>&]:my-0'], - - // nth-child - ['[&:nth-child(2)]:flex', 'nth-2:flex'], - ['[&:not(:nth-child(2))]:flex', 'not-nth-2:flex'], - - ['[&:nth-child(-n+3)]:flex', 'nth-[-n+3]:flex'], - ['[&:not(:nth-child(-n+3))]:flex', 'not-nth-[-n+3]:flex'], - ['[&:nth-child(-n_+_3)]:flex', 'nth-[-n+3]:flex'], - ['[&:not(:nth-child(-n_+_3))]:flex', 'not-nth-[-n+3]:flex'], - - // nth-last-child - ['[&:nth-last-child(2)]:flex', 'nth-last-2:flex'], - ['[&:not(:nth-last-child(2))]:flex', 'not-nth-last-2:flex'], - - ['[&:nth-last-child(-n+3)]:flex', 'nth-last-[-n+3]:flex'], - ['[&:not(:nth-last-child(-n+3))]:flex', 'not-nth-last-[-n+3]:flex'], - ['[&:nth-last-child(-n_+_3)]:flex', 'nth-last-[-n+3]:flex'], - ['[&:not(:nth-last-child(-n_+_3))]:flex', 'not-nth-last-[-n+3]:flex'], - - // nth-child odd/even - ['[&:nth-child(odd)]:flex', 'odd:flex'], - ['[&:not(:nth-child(odd))]:flex', 'even:flex'], - ['[&:nth-child(even)]:flex', 'even:flex'], - ['[&:not(:nth-child(even))]:flex', 'odd:flex'], - - // Keep multiple attribute selectors as-is - ['[[data-visible][data-dark]]:flex', '[[data-visible][data-dark]]:flex'], - - // Keep `:where(…)` as is - ['[:where([data-visible])]:flex', '[:where([data-visible])]:flex'], - - // Complex attribute selectors with operators, quotes and insensitivity flags - ['[[data-url*="example"]]:flex', 'data-[url*="example"]:flex'], - ['[[data-url$=".com"_i]]:flex', 'data-[url$=".com"_i]:flex'], - ['[[data-url$=.com_i]]:flex', 'data-[url$=.com_i]:flex'], - - // Attribute selector wrapped in `&:is(…)` - ['[&:is([data-visible])]:flex', 'data-visible:flex'], - - // Media queries - ['[@media(pointer:fine)]:flex', 'pointer-fine:flex'], - ['[@media_(pointer_:_fine)]:flex', 'pointer-fine:flex'], - ['[@media_not_(pointer_:_fine)]:flex', 'not-pointer-fine:flex'], - ['[@media_print]:flex', 'print:flex'], - ['[@media_not_print]:flex', 'not-print:flex'], - - // Hoist the `:not` part to a compound variant - ['[@media_not_(prefers-color-scheme:dark)]:flex', 'not-dark:flex'], - [ - '[@media_not_(prefers-color-scheme:unknown)]:flex', - 'not-[@media_(prefers-color-scheme:unknown)]:flex', - ], - - // Compound arbitrary variants - ['has-[[data-visible]]:flex', 'has-data-visible:flex'], - ['has-[&:is([data-visible])]:flex', 'has-data-visible:flex'], - ['has-[&>[data-visible]]:flex', 'has-[&>[data-visible]]:flex'], - - ['has-[[data-slot=description]]:flex', 'has-data-[slot=description]:flex'], - ['has-[&:is([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], - - ['has-[[aria-visible="true"]]:flex', 'has-aria-visible:flex'], - ['has-[[aria-visible]]:flex', 'has-aria-[visible]:flex'], - - ['has-[&:not(:nth-child(even))]:flex', 'has-odd:flex'], - - // Arbitrary variant to compound + arbitrary variants - ['[&:has([role=checkbox])]:flex', 'has-[[role=checkbox]]:flex'], - ['[&:has([aria-visible="true"])]:flex', 'has-aria-visible:flex'], - ['[&:has([data-slot=description])]:flex', 'has-data-[slot=description]:flex'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('optimize modifier', () => { - let input = css` - @import 'tailwindcss'; - @theme { - --*: initial; - --color-red-500: red; - } - ` - - test.each([ - // Keep the modifier as-is, nothing to optimize - ['bg-red-500/25', 'bg-red-500/25'], - - // Use a bare value modifier - ['bg-red-500/[25%]', 'bg-red-500/25'], - - // Convert 0-1 values to bare values - ['bg-[#f00]/[0.16]', 'bg-[#f00]/16'], - - // Drop unnecessary modifiers - ['bg-red-500/[100%]', 'bg-red-500'], - ['bg-red-500/100', 'bg-red-500'], - - // Keep modifiers on classes that don't _really_ exist - ['group/name', 'group/name'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization(input, candidate, expected) - }) - }) - - describe('combine to shorthand utilities', () => { - test.each([ - // 4 to 1 - ['mt-1 mr-1 mb-1 ml-1', 'm-1'], - ['border-t-123 border-r-123 border-b-123 border-l-123', 'border-123'], - ['border-t-1 border-r-1 border-b-1 border-l-1', 'border'], // `border` is shorter than `border-1` - ['border-t-red-500 border-r-red-500 border-b-red-500 border-l-red-500', 'border-red-500'], - ['scroll-mt-1 scroll-mr-1 scroll-mb-1 scroll-ml-1', 'scroll-m-1'], - ['scroll-pt-1 scroll-pr-1 scroll-pb-1 scroll-pl-1', 'scroll-p-1'], - - // 2 to 1 - ['mt-1 mb-1', 'my-1'], - ['border-t-123 border-b-123', 'border-y-123'], - ['border-t-1 border-b-1', 'border-y'], // `border-y` is shorter than `border-y-1` - ['border-t-red-500 border-b-red-500', 'border-y-red-500'], - ['scroll-mt-1 scroll-mb-1', 'scroll-my-1'], - ['scroll-pt-1 scroll-pb-1', 'scroll-py-1'], - ['overflow-x-hidden overflow-y-hidden', 'overflow-hidden'], - ['overscroll-x-contain overscroll-y-contain', 'overscroll-contain'], - - // Different order as above - ['mb-1 mt-1', 'my-1'], - - // To completely different utility - ['w-4 h-4', 'size-4'], - - // Goes beyond the default spacing scale that's being used in intellisense - // for code completion. Since it's about bare values, we should still be - // able to combine them. - ['w-123 h-123', 'size-123'], - ['w-128 h-128', 'size-128'], // `w-128` on its own would become `w-lg` - ['mt-123 mb-123', 'my-123'], - - // Collapse duplicates into themselves - ['w-8 w-8', 'w-8'], - - // `w-*` and `h-*` would canonicalize to `size-5` - // `size-5` and `size-5` should then canonicalize to `size-5` - ['w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5', 'size-5'], - - // Same as above, but with an additional unrelated class - ['w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5 flex', 'size-5 flex'], - - // Do not touch if not operating on the same variants - ['hover:w-4 h-4', 'hover:w-4 h-4'], - - // Arbitrary properties to combined class - ['[width:_16px_] [height:16px]', 'size-4'], - - // Arbitrary properties to combined class with modifier - ['[font-size:14px] [line-height:1.625]', 'text-sm/relaxed'], - ])(testName, { timeout }, async (candidates, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidates, expected) - }) - }) - - describe('font-size/line-height to text-{x}/{y}', () => { - test.each([ - ...Array.from( - cartesian( - ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'], - ['[line-height:28px]', 'leading-[28px]', 'leading-7'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/7']), - ...Array.from( - cartesian( - ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'], - ['[line-height:28px]', 'leading-[28px]', 'leading-7'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/7']), - ...Array.from( - cartesian( - ['[font-size:14px]', 'text-[14px]', 'text-[14px]/6', 'text-sm', 'text-sm/6'], - ['[line-height:28.5px]', 'leading-[28.5px]'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-sm/[28.5px]']), - ...Array.from( - cartesian( - ['[font-size:15px]', 'text-[15px]', 'text-[15px]/6'], - ['[line-height:28.5px]', 'leading-[28.5px]'], - ), - ).map((classes) => [classes.join(' ').padEnd(40, ' '), 'text-[15px]/[28.5px]']), - ])(testName, { timeout }, async (candidates, expected) => { - let input = css` - @import 'tailwindcss'; - ` - await expectCanonicalization(input, candidates.trim(), expected) - }) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1558 - test.each([ - ['tracking-[-0.05em]', 'tracking-tighter'], - ['tracking-[-0.025em]', 'tracking-tight'], - ['tracking-[0em]', 'tracking-normal'], - ['tracking-[0.025em]', 'tracking-wide'], - ['tracking-[0.05em]', 'tracking-wider'], - ['tracking-[0.1em]', 'tracking-widest'], - - // Negative values that don't make sense - // See: https://tailwindcss.com/docs/letter-spacing#using-negative-values - ['-tracking-tighter', 'tracking-wider'], - ['-tracking-tight', 'tracking-wide'], - ['-tracking-normal', 'tracking-normal'], - ['-tracking-wide', 'tracking-tight'], - ['-tracking-wider', 'tracking-tighter'], - ])(testName, { timeout }, async (candidate, expected) => { - await expectCanonicalization( - css` - @import 'tailwindcss'; - @theme { - --tracking-tighter: -0.05em; - --tracking-tight: -0.025em; - --tracking-normal: 0em; - --tracking-wide: 0.025em; - --tracking-wider: 0.05em; - --tracking-widest: 0.1em; - } - `, - candidate, - expected, - ) - }) - - test.each([ - // Keep whitespace characters that are significant - ['[&:has(~_*_*:checked)]:flex', '[&:has(~_*_*:checked)]:flex'], - [ - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - 'shadow-[inset_0px_1px_--theme(--color-white/15%)]', - ], - - // Improve readability when whitespace was used for readability - ['w-[calc(100%_-_calc(var(--spacing)*60))]', 'w-[calc(100%-(--spacing(60)))]'], - ['w-[calc(100%_-_--spacing(60))]', 'w-[calc(100%-(--spacing(60)))]'], - - // No need to wrap in `(…)` after a `,` - ['m-[min(100%,_--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ['m-[min(100%_,_--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ['m-[min(100%,--spacing(6))]', 'm-[min(100%,--spacing(6))]'], - ])(testName, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1573 - test.each([ - ['-mt-[0.04in]', 'mt-[-0.04in]'], - ['mt-[-0.04in]', 'mt-[-0.04in]'], - ['-mt-[-0.04in]', 'mt-[0.04in]'], - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1591 - test.each([ - ['w-[calc(100%/3.5)]', 'w-[calc(100%/3.5)]'], // Not w-[28.571428571428573%] - ])(testName, { timeout }, async (candidate, expected) => { - let input = css` - @import 'tailwindcss'; - ` - - await expectCanonicalization(input, candidate, expected) - }) -}) - -describe('theme to var', () => { - test('extended space scale converts to var or calc', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing: 0.25rem; - --spacing-2: 2px; - --spacing-miami: 0.875rem; - } - `, - { base: __dirname }, - ) - expect( - designSystem.canonicalizeCandidates([ - '[--value:theme(spacing.1)]', - '[--value:theme(spacing.2)]', - '[--value:theme(spacing.miami)]', - '[--value:theme(spacing.nyc)]', - ]), - ).toEqual([ - '[--value:--spacing(1)]', - '[--value:var(--spacing-2)]', - '[--value:var(--spacing-miami)]', - '[--value:theme(spacing.nyc)]', - ]) - }) - - test('custom space scale converts to var', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing-*: initial; - --spacing-1: 0.25rem; - --spacing-2: 0.5rem; - } - `, - { base: __dirname }, - ) - expect( - designSystem.canonicalizeCandidates([ - '[--value:theme(spacing.1)]', - '[--value:theme(spacing.2)]', - '[--value:theme(spacing.3)]', - ]), - ).toEqual([ - '[--value:var(--spacing-1)]', - '[--value:var(--spacing-2)]', - '[--value:theme(spacing.3)]', - ]) - }) -}) - -describe('options', () => { - test('normalize `rem` units to `px`', { timeout }, async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --spacing: 0.25rem; - } - `, - { base: __dirname }, - ) - - expect(designSystem.canonicalizeCandidates(['m-[16px]'])).toEqual(['m-[16px]']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'], { rem: 16 })).toEqual(['m-4']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'], { rem: 64 })).toEqual(['m-1']) - expect(designSystem.canonicalizeCandidates(['m-[16px]'])).toEqual(['m-[16px]']) // Ensure options don't influence shared state - }) -}) - -describe('regressions', () => { - // https://github.com/schoero/eslint-plugin-better-tailwindcss/issues/321 - { - test('a subset of classes should be canonicalizable', { timeout }, async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect( - designSystem.canonicalizeCandidates(['underline', 'h-4', 'w-4', 'text-sm'], options), - ).toEqual(['underline', 'text-sm', 'size-4']) - }) - - test('collapse canonicalization is not affected by previous calls', { timeout }, async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - let target = ['underline', 'h-4', 'w-4'] - - expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4']) - - designSystem.canonicalizeCandidates(['mb-4', 'text-sm'], options) - designSystem.canonicalizeCandidates(['underline', 'mb-4'], options) - - expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4']) - expect(designSystem.canonicalizeCandidates(target.concat('text-sm'), options)).toEqual([ - 'underline', - 'text-sm', - 'size-4', - ]) - }) - } - - // https://github.com/tailwindlabs/tailwindcss/pull/19727 - test( - 'collapse does not crash when utilities with no standard properties are present', - { timeout }, - async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - // Shadow utilities use CSS custom properties and @property rules but may - // produce empty property maps in the collapse algorithm. This should not - // crash with "Cannot read properties of null" or "X is not iterable". - expect(() => - designSystem.canonicalizeCandidates(['shadow-sm', 'border'], options), - ).not.toThrow() - - expect(() => designSystem.canonicalizeCandidates(['shadow-md', 'p-4'], options)).not.toThrow() - - expect(() => - designSystem.canonicalizeCandidates(['shadow-sm', 'shadow-md'], options), - ).not.toThrow() - - // Verify the candidates are returned (not collapsed, since shadows can't - // meaningfully collapse with unrelated utilities) - expect(designSystem.canonicalizeCandidates(['shadow-sm', 'border'], options)).toEqual( - expect.arrayContaining(['shadow-sm', 'border']), - ) - - expect(designSystem.canonicalizeCandidates(['shadow-sm', 'shadow-md'], options)).toEqual( - expect.arrayContaining(['shadow-sm', 'shadow-md']), - ) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/19835 - test.each([ - // Arbitrary values should be collapsed to another arbitrary value - [ - ['px-[1.2rem]', 'py-[1.2rem]', 'text-left'], - ['text-left', 'p-[1.2rem]'], - ], - - // Arbitrary values could also be collapsed into a bare value - [ - ['px-[30.75rem]', 'py-[30.75rem]', 'text-left'], - ['text-left', 'p-123'], - ], - ])( - 'collapse canonicalization works for arbitrary values', - { timeout }, - async (candidates, expected) => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(candidates, options)).toEqual(expected) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss/issues/20051 - test( - 'does not crash when plugin matchComponents rejects speculative values during collapse', - { timeout }, - async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - @plugin "./plugin.js"; - `, - { - async loadStylesheet(_, base) { - return { - base, - path: '', - content: '@tailwind utilities;', - } - }, - async loadModule() { - return { - base: '', - path: '', - module: plugin(({ matchComponents }) => { - matchComponents( - { - myicon: () => { - throw new Error('Mimic as-if a custom plugin failed for some reason') - }, - }, - { values: { icon: __filename } }, - ) - }), - } - }, - }, - ) - - expect( - designSystem.canonicalizeCandidates(['border-[1.5px]', 'flex'], { - collapse: true, - logicalToPhysical: true, - rem: 16, - }), - ).toEqual(expect.arrayContaining(['border-[1.5px]', 'flex'])) - }, - ) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1579 - test('does not suggest invalid alternative when canonicalizing calc expressions', async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(['px-[calc(1rem+0px)]'], options)).toEqual(['px-4']) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20295 - test('canonicalizations work when casing of arbitrary values is different', async () => { - let designSystem = await designSystems.get(__dirname).get(css` - @import 'tailwindcss'; - @theme { - --color-brand-purple: #3f3cbb; - } - `) - - let options: CanonicalizeOptions = { - collapse: true, - logicalToPhysical: true, - rem: 16, - } - - expect(designSystem.canonicalizeCandidates(['bg-[#3F3cbb]'], options)).toEqual([ - 'bg-brand-purple', - ]) - expect(designSystem.canonicalizeCandidates(['bg-[#3F3Cbb]'], options)).toEqual([ - 'bg-brand-purple', - ]) - expect(designSystem.canonicalizeCandidates(['bg-[#3F3CBB]'], options)).toEqual([ - 'bg-brand-purple', - ]) - }) - - // https://github.com/tailwindlabs/tailwindcss/issues/20365 - test('canonicalizes min/max media query and container query variants', async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --breakpoint-lg: 64rem; - --container-lg: 32rem; - --spacing: 0.25rem; - } - `, - { base: __dirname }, - ) - - expect(designSystem.canonicalizeCandidates(['min-[64rem]:flex'])).toEqual(['lg:flex']) - expect(designSystem.canonicalizeCandidates(['max-[64rem]:flex'])).toEqual(['max-lg:flex']) - expect(designSystem.canonicalizeCandidates(['@min-[32rem]:flex'])).toEqual(['@lg:flex']) - expect(designSystem.canonicalizeCandidates(['@max-[32rem]:flex'])).toEqual(['@max-lg:flex']) - expect(designSystem.canonicalizeCandidates(['min-[1024px]:flex'], { rem: 16 })).toEqual([ - 'lg:flex', - ]) - }) - - // https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1610 - test('does not merge utilities whose theme variables resolve to CSS-wide keywords', async () => { - let designSystem = await __unstable__loadDesignSystem( - css` - @tailwind utilities; - @theme { - --foreground: unset; - --default: unset; - } - @theme inline { - --color-foreground: var(--foreground); - --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent); - } - `, - { base: __dirname }, - ) - - expect( - designSystem.canonicalizeCandidates(['text-foreground/60', 'text-default-soft-hover']), - ).toEqual(['text-foreground/60', 'text-default-soft-hover']) - }) -}) diff --git a/packages/tailwindcss/src/canonicalize-candidates.ts b/packages/tailwindcss/src/canonicalize-candidates.ts deleted file mode 100644 index d1dfc09de..000000000 --- a/packages/tailwindcss/src/canonicalize-candidates.ts +++ /dev/null @@ -1,3062 +0,0 @@ -import { substituteAtApply } from './apply' -import { atRule, cloneAstNode, styleRule, toCss, type AstNode } from './ast' -import * as AttributeSelectorParser from './attribute-selector-parser' -import { - cloneCandidate, - cloneVariant, - printArbitraryValue, - printModifier, - type Candidate, - type CandidateModifier, - type NamedUtilityValue, - type Variant, -} from './candidate' -import { canonicalizeCalcExpressionsAst } from './canonicalize-calc-expressions' -import { keyPathToCssProperty } from './compat/apply-config-to-theme' -import { constantFoldDeclaration, constantFoldDeclarationAst } from './constant-fold-declaration' -import type { DesignSystem as BaseDesignSystem } from './design-system' -import { CompileAstFlags } from './design-system' -import { expandDeclaration } from './expand-declaration' -import * as SelectorParser from './selector-parser' -import { ThemeOptions } from './theme' -import type { Writable } from './types' -import { DefaultMap } from './utils/default-map' -import { dimensions } from './utils/dimensions' -import { isPositiveInteger, isValidSpacingMultiplier } from './utils/infer-data-type' -import { replaceObject } from './utils/replace-object' -import { segment } from './utils/segment' -import { toKeyPath } from './utils/to-key-path' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -export interface CanonicalizeOptions { - /** - * The root font size in pixels. If provided, `rem` values will be normalized - * to `px` values. - * - * E.g.: `mt-[16px]` with `rem: 16` will become `mt-4` (assuming `--spacing: 0.25rem`). - */ - rem?: number - - /** - * Whether to collapse multiple utilities into a single utility if possible. - * - * E.g.: `mt-2 mr-2 mb-2 ml-2` → `m-2` - */ - collapse?: boolean - - /** - * Whether to convert between logical and physical properties when collapsing - * utilities. - * - * E.g.: `mr-2 ml-2` → `mx-2` - */ - logicalToPhysical?: boolean -} - -enum Features { - /** - * No features enabled (default) - */ - None = 0, - - /** - * Collapse multiple utilities into a single utility if possible. - */ - CollapseUtilities = 1 << 0, -} - -interface InternalCanonicalizeOptions { - features: Features - designSystem: DesignSystem - signatureOptions: SignatureOptions -} - -type RemValue = number | null - -interface DesignSystem extends BaseDesignSystem { - storage: { - [SIGNATURE_OPTIONS_KEY]: DefaultMap> - [COMPARE_CANDIDATES_KEY]: DefaultMap< - SignatureOptions, - (a: Candidate | string, b: Candidate | string) => boolean - > - [INTERNAL_OPTIONS_KEY]: DefaultMap< - SignatureOptions, - DefaultMap - > - [CANONICALIZE_CANDIDATE_KEY]: DefaultMap< - InternalCanonicalizeOptions, - DefaultMap - > - [CANONICALIZE_VARIANT_KEY]: DefaultMap< - InternalCanonicalizeOptions, - DefaultMap - > - [CANONICALIZE_UTILITY_KEY]: DefaultMap> - [CONVERTER_KEY]: (input: string, options?: Convert) => [string, CandidateModifier | null] - [SPACING_KEY]: DefaultMap | null - [UTILITY_SIGNATURE_KEY]: DefaultMap> - [STATIC_UTILITIES_KEY]: DefaultMap< - SignatureOptions, - DefaultMap>> - > - [UTILITY_PROPERTIES_KEY]: DefaultMap< - SignatureOptions, - DefaultMap>> - > - [PRE_COMPUTED_UTILITIES_KEY]: DefaultMap> - [VARIANT_SIGNATURE_KEY]: DefaultMap> - [PRE_COMPUTED_VARIANTS_KEY]: DefaultMap> - } -} - -export function prepareDesignSystemStorage( - baseDesignSystem: BaseDesignSystem, - options?: CanonicalizeOptions, -): DesignSystem { - let designSystem = baseDesignSystem as DesignSystem - - designSystem.storage[SIGNATURE_OPTIONS_KEY] ??= createSignatureOptionsCache() - designSystem.storage[INTERNAL_OPTIONS_KEY] ??= createInternalOptionsCache(designSystem) - designSystem.storage[CANONICALIZE_CANDIDATE_KEY] ??= createCanonicalizeCandidateCache() - designSystem.storage[CANONICALIZE_VARIANT_KEY] ??= createCanonicalizeVariantCache() - designSystem.storage[CANONICALIZE_UTILITY_KEY] ??= createCanonicalizeUtilityCache() - designSystem.storage[CONVERTER_KEY] ??= createConverterCache(designSystem) - designSystem.storage[SPACING_KEY] ??= createSpacingCache(designSystem, options) - designSystem.storage[UTILITY_SIGNATURE_KEY] ??= createUtilitySignatureCache(designSystem) - designSystem.storage[STATIC_UTILITIES_KEY] ??= createStaticUtilitiesCache() - designSystem.storage[UTILITY_PROPERTIES_KEY] ??= createUtilityPropertiesCache(designSystem) - designSystem.storage[PRE_COMPUTED_UTILITIES_KEY] ??= createPreComputedUtilitiesCache(designSystem) - designSystem.storage[VARIANT_SIGNATURE_KEY] ??= createVariantSignatureCache(designSystem) - designSystem.storage[PRE_COMPUTED_VARIANTS_KEY] ??= createPreComputedVariantsCache(designSystem) - designSystem.storage[COMPARE_CANDIDATES_KEY] ??= createSignatureComparison(designSystem) - - return designSystem -} - -const SIGNATURE_OPTIONS_KEY = Symbol() -function createSignatureOptionsCache(): DesignSystem['storage'][typeof SIGNATURE_OPTIONS_KEY] { - return new DefaultMap((rem: number | null) => { - return new DefaultMap((features: SignatureFeatures) => { - return { rem, features } satisfies SignatureOptions - }) - }) -} - -const COMPARE_CANDIDATES_KEY = Symbol() -function createSignatureComparison(designSystem: DesignSystem) { - return new DefaultMap((options: SignatureOptions) => { - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options) - - return function hasSameSignature(a: Candidate | string, b: Candidate | string): boolean { - let aCandidateString = typeof a === 'string' ? a : designSystem.printCandidate(a) - let aSignature = signatures.get(aCandidateString) - if (typeof aSignature !== 'string') return false - - let bCandidateString = typeof b === 'string' ? b : designSystem.printCandidate(b) - let bSignature = signatures.get(bCandidateString) - if (typeof bSignature !== 'string') return false - - return aSignature === bSignature - } - }) -} - -export function createSignatureOptions( - baseDesignSystem: BaseDesignSystem, - options?: CanonicalizeOptions, -): SignatureOptions { - let features = SignatureFeatures.None - if (options?.collapse) features |= SignatureFeatures.ExpandProperties - if (options?.logicalToPhysical) features |= SignatureFeatures.LogicalToPhysical - - let designSystem = prepareDesignSystemStorage(baseDesignSystem, options) - - return designSystem.storage[SIGNATURE_OPTIONS_KEY].get(options?.rem ?? null).get(features) -} - -const INTERNAL_OPTIONS_KEY = Symbol() -function createInternalOptionsCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof INTERNAL_OPTIONS_KEY] { - return new DefaultMap((signatureOptions: SignatureOptions) => { - return new DefaultMap((features: Features) => { - return { features, designSystem, signatureOptions } satisfies InternalCanonicalizeOptions - }) - }) -} - -function createCanonicalizeOptions( - baseDesignSystem: BaseDesignSystem, - signatureOptions: SignatureOptions, - options?: CanonicalizeOptions, -) { - let features = Features.None - if (options?.collapse) features |= Features.CollapseUtilities - - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - - return designSystem.storage[INTERNAL_OPTIONS_KEY].get(signatureOptions).get(features) -} - -export function canonicalizeCandidates( - baseDesignSystem: BaseDesignSystem, - candidates: string[], - options?: CanonicalizeOptions, -): string[] { - let signatureOptions = createSignatureOptions(baseDesignSystem, options) - let canonicalizeOptions = createCanonicalizeOptions(baseDesignSystem, signatureOptions, options) - - let designSystem = prepareDesignSystemStorage(baseDesignSystem) - - let result = new Set() - let cache = designSystem.storage[CANONICALIZE_CANDIDATE_KEY].get(canonicalizeOptions) - for (let candidate of candidates) { - result.add(cache.get(candidate)) - } - - return result.size <= 1 || !(canonicalizeOptions.features & Features.CollapseUtilities) - ? Array.from(result) - : collapseCandidates(canonicalizeOptions, Array.from(result)) -} - -function collapseCandidates(options: InternalCanonicalizeOptions, candidates: string[]): string[] { - let designSystem = options.designSystem - - // To keep things simple, we group candidates such that we only collapse - // candidates with the same variants and important modifier together. - let groups = new DefaultMap((_before: string) => { - return new DefaultMap((_after: string) => { - return new Set() - }) - }) - - let prefix = options.designSystem.theme.prefix ? `${options.designSystem.theme.prefix}:` : '' - - for (let candidate of candidates) { - let variants = segment(candidate, ':') - let utility = variants.pop()! - - let important = utility.endsWith('!') - if (important) { - utility = utility.slice(0, -1) - } - - let before = variants.length > 0 ? `${variants.join(':')}:` : '' - let after = important ? '!' : '' - - // Group by variants and important flag - groups.get(before).get(after).add(`${prefix}${utility}`) - } - - let result = new Set() - for (let [before, group] of groups.entries()) { - for (let [after, candidates] of group.entries()) { - for (let candidate of collapseGroup(Array.from(candidates))) { - // Drop the prefix if we had one, because the prefix is already there as - // part of the variants. - if (prefix && candidate.startsWith(prefix)) { - candidate = candidate.slice(prefix.length) - } - - result.add(`${before}${candidate}${after}`) - } - } - } - - return Array.from(result) - - function collapseGroup(candidates: string[]) { - let signatureOptions = options.signatureOptions - let computeUtilitiesPropertiesLookup = - designSystem.storage[UTILITY_PROPERTIES_KEY].get(signatureOptions) - let staticUtilities = designSystem.storage[STATIC_UTILITIES_KEY].get(signatureOptions) - - // For each candidate, compute the used properties and values. E.g.: `mt-1` → `margin-top` → `0.25rem` - // - // NOTE: Currently assuming we are dealing with static utilities only. This - // will change the moment we have `@utility` for most built-ins. - let candidatePropertiesValues = candidates.map((candidate) => - computeUtilitiesPropertiesLookup.get(candidate), - ) - - // Hard-coded optimization: if any candidate sets `line-height` and another - // candidate sets `font-size`, we pre-compute the `text-*` utilities with - // this line-height to try and collapse to those combined values. - if (candidatePropertiesValues.some((x) => x.has('line-height'))) { - let fontSizeNames = designSystem.theme.keysInNamespaces(['--text']) - if (fontSizeNames.length > 0) { - let interestingLineHeights = new Set() - let seenLineHeights = new Set() - for (let pairs of candidatePropertiesValues) { - if (!pairs.has('line-height')) continue - - for (let lineHeight of pairs.get('line-height')) { - if (seenLineHeights.has(lineHeight)) continue - seenLineHeights.add(lineHeight) - - let bareValue = designSystem.storage[SPACING_KEY]?.get(lineHeight) ?? null - if (bareValue !== null) { - if (isValidSpacingMultiplier(bareValue)) { - interestingLineHeights.add(bareValue) - - for (let name of fontSizeNames) { - computeUtilitiesPropertiesLookup.get(`text-${name}/${bareValue}`) - } - } else { - interestingLineHeights.add(lineHeight) - - for (let name of fontSizeNames) { - computeUtilitiesPropertiesLookup.get(`text-${name}/[${lineHeight}]`) - } - } - } - } - } - - let seenFontSizes = new Set() - for (let pairs of candidatePropertiesValues) { - if (!pairs.has('font-size')) continue - - for (let fontSize of pairs.get('font-size')) { - if (seenFontSizes.has(fontSize)) continue - seenFontSizes.add(fontSize) - - for (let lineHeight of interestingLineHeights) { - if (isValidSpacingMultiplier(lineHeight)) { - computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/${lineHeight}`) - } else { - computeUtilitiesPropertiesLookup.get(`text-[${fontSize}]/[${lineHeight}]`) - } - } - } - } - } - } - - let dynamicUtilities = new DefaultMap((candidate: string) => { - let result = new DefaultMap( - (_property: string) => new DefaultMap((_value: string) => new Set()), - ) - - let relevantProperties = new Set(computeUtilitiesPropertiesLookup.get(candidate).keys()) - if (relevantProperties.size === 0) return result - - for (let parsedCandidate of parseCandidate(designSystem, candidate)) { - if (parsedCandidate.kind !== 'functional' || parsedCandidate.value === null) { - continue - } - - for (let root of designSystem.utilities.keys('functional')) { - if (root === parsedCandidate.root) continue // Skip self - - let replacement = printUnprefixedCandidate(designSystem, { - ...cloneCandidate(parsedCandidate), - root, - }) - - let propertyValues = computeUtilitiesPropertiesLookup.get(replacement) - for (let [property, values] of propertyValues) { - if (!relevantProperties.has(property)) continue // Skip properties that are not relevant for the current candidate - - for (let value of values) { - result.get(property).get(value).add(replacement) - } - } - } - - return result - } - - return result - }) - - // For each property, lookup other utilities that also set this property and - // this exact value. If multiple properties are used, use the intersection of - // each property. - // - // E.g.: `margin-top` → `mt-1`, `my-1`, `m-1` - let otherUtilities = candidatePropertiesValues.map((propertyValues, idx) => { - let result: Set | null = null - for (let property of propertyValues.keys()) { - let otherUtilities = new Set() - for (let group of staticUtilities.get(property).values()) { - for (let candidate of group) { - otherUtilities.add(candidate) - } - } - - for (let value of propertyValues.get(property)) { - for (let candidate of dynamicUtilities.get(candidates[idx]).get(property).get(value)) { - otherUtilities.add(candidate) - } - } - - if (result === null) result = otherUtilities - else result = intersection(result, otherUtilities) - - // The moment no other utilities match, we can stop searching because - // all intersections with an empty set will remain empty. - if (result!.size === 0) return result! - } - return result ?? new Set() - }) - - // Link each candidate that could be linked via another utility - // (intersection). This way we can reduce the amount of required combinations. - // - // E.g.: `mt-1` and `mb-1` can be linked via `my-1`. - // - // Candidates that cannot be linked won't be able to be collapsed. - // E.g.: `mt-1` and `text-red-500` cannot be collapsed because there is no 3rd - // utility with overlapping property/value combinations. - let linked = new DefaultMap>((key) => new Set([key])) - for (let i = 0; i < otherUtilities.length; i++) { - let current = otherUtilities[i] - for (let j = i + 1; j < otherUtilities.length; j++) { - let other = otherUtilities[j] - - for (let property of current) { - if (other.has(property)) { - linked.get(i).add(j) - linked.get(j).add(i) - - // The moment we find a link, we can stop comparing and move on to the - // next candidate. This will safe us some time - break - } - } - } - } - - // Not a single candidate can be linked to another one, nothing left to do - if (linked.size === 0) return candidates - - // Each candidate index will now have a set of other candidate indexes as - // its value. Let's make the lists unique combinations so that we can - // iterate over them. - let uniqueCombinations = new DefaultMap((key: string) => key.split(',').map(Number)) - for (let group of linked.values()) { - let sorted = Array.from(group).sort((a, b) => a - b) - uniqueCombinations.get(sorted.join(',')) - } - - // Let's try to actually collapse them now. - let result = new Set(candidates) - let drop = new Set() - - for (let idxs of uniqueCombinations.values()) { - for (let combo of combinations(idxs)) { - if (combo.some((idx) => drop.has(candidates[idx]))) continue // Skip already dropped items - - let potentialReplacements = combo.flatMap((idx) => otherUtilities[idx]).reduce(intersection) - - let collapsedSignature = designSystem.storage[UTILITY_SIGNATURE_KEY].get( - signatureOptions, - ).get( - combo - .map((idx) => candidates[idx]) - .sort((a, z) => a.localeCompare(z)) // Sort to increase cache hits - .join(' '), - ) - - for (let replacement of potentialReplacements) { - let signature = - designSystem.storage[UTILITY_SIGNATURE_KEY].get(signatureOptions).get(replacement) - if (signature !== collapsedSignature) continue // Not a safe replacement - - // Use the replacement - result.add(replacement) - - // We can replace all items in the combo with the replacement. If the - // replacement is already part of the combo, keep that one around. - for (let item of combo) { - if (candidates[item] !== replacement) { - drop.add(candidates[item]) - } - } - - break - } - } - } - - for (let item of drop) { - result.delete(item) - } - - return Array.from(result) - } -} - -const CANONICALIZE_CANDIDATE_KEY = Symbol() -function createCanonicalizeCandidateCache(): DesignSystem['storage'][typeof CANONICALIZE_CANDIDATE_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - let ds = options.designSystem - let prefix = ds.theme.prefix ? `${ds.theme.prefix}:` : '' - let variantCache = ds.storage[CANONICALIZE_VARIANT_KEY].get(options) - let utilityCache = ds.storage[CANONICALIZE_UTILITY_KEY].get(options) - - return new DefaultMap((rawCandidate: string, self) => { - for (let candidate of ds.parseCandidate(rawCandidate)) { - let variants = candidate.variants - .slice() - .reverse() - .flatMap((variant) => variantCache.get(variant)) - let important = candidate.important - - // Canonicalize the base candidate (utility), and re-attach the variants - // and important flag afterwards. This way we can maximize cache hits for - // the base candidate and each individual variant. - if (important || variants.length > 0) { - let canonicalizedUtility = self.get( - ds.printCandidate({ ...candidate, variants: [], important: false }), - ) - - // Rebuild the final candidate - let result = canonicalizedUtility - - // Remove the prefix if there are variants, because the variants exist - // between the prefix and the base candidate. - if (ds.theme.prefix !== null && variants.length > 0) { - result = result.slice(prefix.length) - } - - // Re-attach the variants - if (variants.length > 0) { - result = `${variants.map((v) => ds.printVariant(v)).join(':')}:${result}` - } - - // Re-attach the important flag - if (important) { - result += '!' - } - - // Re-attach the prefix if there were variants - if (ds.theme.prefix !== null && variants.length > 0) { - result = `${prefix}${result}` - } - - return result - } - - // We are guaranteed to have no variants and no important flag, just the - // base candidate left to canonicalize. - let result = utilityCache.get(rawCandidate) - if (result !== rawCandidate) { - return result - } - } - - return rawCandidate - }) - }) -} - -type VariantCanonicalizationFunction = ( - variant: Variant, - options: InternalCanonicalizeOptions, -) => Variant | Variant[] - -const VARIANT_CANONICALIZATIONS: VariantCanonicalizationFunction[] = [ - themeToVarVariant, - arbitraryValueToBareValueVariant, - modernizeArbitraryValuesVariant, - arbitraryVariants, -] - -const CANONICALIZE_VARIANT_KEY = Symbol() -function createCanonicalizeVariantCache(): DesignSystem['storage'][typeof CANONICALIZE_VARIANT_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - return new DefaultMap((variant: Variant): Variant[] => { - let replacement = [variant] - for (let fn of VARIANT_CANONICALIZATIONS) { - for (let current of replacement.splice(0)) { - // A single variant can result in multiple variants, e.g.: - // `[&>[data-selected]]:flex` → `*:data-selected:flex` - let result = fn(cloneVariant(current), options) - if (Array.isArray(result)) { - replacement.push(...result) - continue - } else { - replacement.push(result) - } - } - } - return replacement - }) - }) -} - -type UtilityCanonicalizationFunction = ( - candidate: Candidate, - options: InternalCanonicalizeOptions, -) => Candidate - -const UTILITY_CANONICALIZATIONS: UtilityCanonicalizationFunction[] = [ - bgGradientToLinear, - themeToVarUtility, - calcToSpacingFunction, - optimizeArbitraryValueExpressions, - arbitraryUtilities, - bareValueUtilities, - deprecatedUtilities, - dropUnnecessaryDataTypes, - arbitraryValueToBareValueUtility, - optimizeModifier, -] - -const CANONICALIZE_UTILITY_KEY = Symbol() -function createCanonicalizeUtilityCache(): DesignSystem['storage'][typeof CANONICALIZE_UTILITY_KEY] { - return new DefaultMap((options: InternalCanonicalizeOptions) => { - let designSystem = options.designSystem - return new DefaultMap((rawCandidate: string): string => { - for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) { - let replacement = cloneCandidate(readonlyCandidate) as Writable - - for (let fn of UTILITY_CANONICALIZATIONS) { - replacement = fn(replacement, options) - } - - let canonicalizedCandidate = designSystem.printCandidate(replacement) - if (rawCandidate !== canonicalizedCandidate) { - return canonicalizedCandidate - } - } - - return rawCandidate - }) - }) -} - -// ---- - -const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl'] -function bgGradientToLinear(candidate: Candidate) { - if (candidate.kind === 'static' && candidate.root.startsWith('bg-gradient-to-')) { - let direction = candidate.root.slice(15) - - if (!DIRECTIONS.includes(direction)) { - return candidate - } - - candidate.root = `bg-linear-to-${direction}` - return candidate - } - - return candidate -} - -// ---- - -const enum Convert { - All = 0, - MigrateModifier = 1 << 0, - MigrateThemeOnly = 1 << 1, -} - -function themeToVarUtility(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - let convert = options.designSystem.storage[CONVERTER_KEY] - - if (candidate.kind === 'arbitrary') { - let [newValue, modifier] = convert( - candidate.value, - candidate.modifier === null ? Convert.MigrateModifier : Convert.All, - ) - if (newValue !== candidate.value) { - candidate.value = newValue - - if (modifier !== null) { - candidate.modifier = modifier - } - } - } else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { - let [newValue, modifier] = convert( - candidate.value.value, - candidate.modifier === null ? Convert.MigrateModifier : Convert.All, - ) - if (newValue !== candidate.value.value) { - candidate.value.value = newValue - - if (modifier !== null) { - candidate.modifier = modifier - } - } - } - - return candidate -} - -function themeToVarVariant( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let convert = options.designSystem.storage[CONVERTER_KEY] - - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - if (variant.kind === 'arbitrary') { - let [newValue] = convert(variant.selector, Convert.MigrateThemeOnly) - if (newValue !== variant.selector) { - variant.selector = newValue - } - } else if (variant.kind === 'functional' && variant.value?.kind === 'arbitrary') { - let [newValue] = convert(variant.value.value, Convert.MigrateThemeOnly) - if (newValue !== variant.value.value) { - variant.value.value = newValue - } - } - } - - return variant -} - -function calcToSpacingFunction( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - if (candidate.kind === 'arbitrary') { - candidate.value = spacingCalcToSpacingFunction(candidate.value, options.designSystem) - } else if (candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') { - candidate.value.value = spacingCalcToSpacingFunction( - candidate.value.value, - options.designSystem, - ) - } - - return candidate -} - -function spacingCalcToSpacingFunction(input: string, designSystem: DesignSystem) { - let spacingVariable = designSystem.theme.prefix - ? `--${designSystem.theme.prefix}-spacing` - : '--spacing' - - let ast = ValueParser.parse(input) - - walk(ast, (node) => { - // calc(…) - if (node.kind !== 'function' || node.value !== 'calc') return - - // calc(var(--spacing) * 2) - // -------------- 1. function (var) - // - 2. separator ( ) - // - 3. word (*) - // - 4. separator ( ) - // - 5. any value, word (2) - if (node.nodes.length !== 5) return - - // * - if (node.nodes[2].kind !== 'word' || node.nodes[2].value !== '*') { - return - } - - // var(--spacing) - if ( - node.nodes[0].kind !== 'function' || - node.nodes[0].value !== 'var' || - node.nodes[0].nodes.length !== 1 || - node.nodes[0].nodes[0].kind !== 'word' || - node.nodes[0].nodes[0].value !== spacingVariable - ) { - return - } - - return WalkAction.Replace( - ValueParser.parse( - `--spacing(${ - ValueParser.toCss([node.nodes[4]]) // Value node - })`, - ), - ) - }) - - return ValueParser.toCss(ast) -} - -const CONVERTER_KEY = Symbol() -function createConverterCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof CONVERTER_KEY] { - return createConverter(designSystem) - - function createConverter(designSystem: DesignSystem) { - function convert(input: string, options = Convert.All): [string, CandidateModifier | null] { - let ast = ValueParser.parse(input) - - // In some scenarios (e.g.: variants), we can't migrate to `var(…)` if it - // ends up in the `@media (…)` part. In this case we only have to migrate to - // the new `theme(…)` notation. - if (options & Convert.MigrateThemeOnly) { - return [substituteFunctionsInValue(ast, toTheme), null] - } - - let themeUsageCount = 0 - let themeModifierCount = 0 - - // Analyze AST - walk(ast, (node) => { - if (node.kind !== 'function') return - if (node.value !== 'theme') return - - // We are only interested in the `theme` function - themeUsageCount += 1 - - // Figure out if a modifier is used - walk(node.nodes, (child) => { - // If we see a `,`, it means that we have a fallback value - if (child.kind === 'separator' && child.value.includes(',')) { - return WalkAction.Stop - } - - // If we see a `/`, we have a modifier - else if (child.kind === 'word' && child.value === '/') { - themeModifierCount += 1 - return WalkAction.Stop - } - - return WalkAction.Skip - }) - }) - - // No `theme(…)` calls, nothing to do - if (themeUsageCount === 0) { - return [input, null] - } - - // No `theme(…)` with modifiers, we can migrate to `var(…)` - if (themeModifierCount === 0) { - return [substituteFunctionsInValue(ast, toVar), null] - } - - // Multiple modifiers which means that there are multiple `theme(…/…)` - // values. In this case, we can't convert the modifier to a candidate - // modifier. - // - // We also can't migrate to `var(…)` because that would lose the modifier. - // - // Try to convert each `theme(…)` call to the modern syntax. - if (themeModifierCount > 1) { - return [substituteFunctionsInValue(ast, toTheme), null] - } - - // Only a single `theme(…)` with a modifier left, that modifier will be - // migrated to a candidate modifier. - let modifier: CandidateModifier | null = null - let result = substituteFunctionsInValue(ast, (path, fallback) => { - let parts = segment(path, '/').map((part) => part.trim()) - - // Multiple `/` separators, which makes this an invalid path - if (parts.length > 2) return null - - // The path contains a `/`, which means that there is a modifier such as - // `theme(colors.red.500/50%)`. - // - // Currently, we are assuming that this is only being used for colors, - // which means that we can typically convert them to a modifier on the - // candidate itself. - // - // If there is more than one node in the AST though, `theme(…)` must not - // be the whole value so it's not safe to use a modifier instead. - // - // E.g.: `inset 0px 1px theme(colors.red.500/50%)` is a shadow, not a color. - if (ast.length === 1 && parts.length === 2 && options & Convert.MigrateModifier) { - let [pathPart, modifierPart] = parts - - // 50% -> /50 - if (/^\d+%$/.test(modifierPart)) { - modifier = { kind: 'named', value: modifierPart.slice(0, -1) } - } - - // .12 -> /12 - // .12345 -> /[12.345] - else if (/^0?\.\d+$/.test(modifierPart)) { - let value = Number(modifierPart) * 100 - modifier = { - kind: Number.isInteger(value) ? 'named' : 'arbitrary', - value: value.toString(), - } - } - - // Anything else becomes arbitrary - else { - modifier = { kind: 'arbitrary', value: modifierPart } - } - - // Update path to be the first part - path = pathPart - } - - return toVar(path, fallback) || toTheme(path, fallback) - }) - - return [result, modifier] - } - - function pathToVariableName(path: string, shouldPrefix = true) { - let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const - if (!designSystem.theme.get([variable])) return null - - if (shouldPrefix && designSystem.theme.prefix) { - return `--${designSystem.theme.prefix}-${variable.slice(2)}` - } - - return variable - } - - function toVar(path: string, fallback?: string) { - let variable = pathToVariableName(path) - if (variable) return fallback ? `var(${variable}, ${fallback})` : `var(${variable})` - - let keyPath = toKeyPath(path) - if (keyPath[0] === 'spacing' && designSystem.theme.get(['--spacing'])) { - let multiplier = keyPath[1] - if (!isValidSpacingMultiplier(multiplier)) return null - - return `--spacing(${multiplier})` - } - - return null - } - - function toTheme(path: string, fallback?: string) { - let parts = segment(path, '/').map((part) => part.trim()) - path = parts.shift()! - - let variable = pathToVariableName(path, false) - if (!variable) return null - - let modifier = parts.length > 0 ? `/${parts.join('/')}` : '' - return fallback - ? `--theme(${variable}${modifier}, ${fallback})` - : `--theme(${variable}${modifier})` - } - - return convert - } -} - -function substituteFunctionsInValue( - ast: ValueParser.ValueAstNode[], - handle: (value: string, fallback?: string) => string | null, -) { - walk(ast, (node, ctx) => { - if (node.kind === 'function' && node.value === 'theme') { - if (node.nodes.length < 1) return - - // Ignore whitespace before the first argument - if (node.nodes[0].kind === 'separator' && node.nodes[0].value.trim() === '') { - node.nodes.shift() - } - - let pathNode = node.nodes[0] - if (pathNode.kind !== 'word') return - - let path = pathNode.value - - // For the theme function arguments, we require all separators to contain - // comma (`,`), spaces alone should be merged into the previous word to - // avoid splitting in this case: - // - // theme(--color-red-500 / 75%) theme(--color-red-500 / 75%, foo, bar) - // - // We only need to do this for the first node, as the fallback values are - // passed through as-is. - let skipUntilIndex = 1 - for (let i = skipUntilIndex; i < node.nodes.length; i++) { - if (node.nodes[i].value.includes(',')) { - break - } - path += ValueParser.toCss([node.nodes[i]]) - skipUntilIndex = i + 1 - } - - path = eventuallyUnquote(path) - let fallbackValues = node.nodes.slice(skipUntilIndex + 1) - - let replacement = - fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path) - if (replacement === null) return - - { - let idx = ctx.index - 1 - while (idx !== -1) { - let previous = ctx.siblings[idx] - // Skip the space separator - if (previous.kind === 'separator' && previous.value.trim() === '') { - idx -= 1 - continue - } - - // If the previous node is a word and contains an operator, we need to - // wrap the replacement in parentheses to make the output less - // ambiguous. - // - // Input: - // - `calc(100dvh-theme(spacing.2))` - // - // Output: - // - `calc(100dvh-(--spacing(2)))` - // - // Not: - // -`calc(100dvh---spacing(2))` - // - if (/^[-+*/]$/.test(previous.value.trim())) { - replacement = `(${replacement})` - } - - break - } - } - - return WalkAction.Replace(ValueParser.parse(replacement)) - } - }) - - return ValueParser.toCss(ast) -} - -function eventuallyUnquote(value: string) { - if (value[0] !== "'" && value[0] !== '"') return value - - let unquoted = '' - let quoteChar = value[0] - for (let i = 1; i < value.length - 1; i++) { - let currentChar = value[i] - let nextChar = value[i + 1] - - if (currentChar === '\\' && (nextChar === quoteChar || nextChar === '\\')) { - unquoted += nextChar - i++ - } else { - unquoted += currentChar - } - } - - return unquoted -} - -// ---- - -function* walkVariants(variant: Variant) { - function* inner( - variant: Variant, - parent: Extract | null = null, - ): Iterable<[Variant, Extract | null]> { - yield [variant, parent] - - if (variant.kind === 'compound') { - yield* inner(variant.variant, variant) - } - } - - yield* inner(variant, null) -} - -function parseCandidate(designSystem: DesignSystem, input: string) { - return designSystem.parseCandidate( - designSystem.theme.prefix && !input.startsWith(`${designSystem.theme.prefix}:`) - ? `${designSystem.theme.prefix}:${input}` - : input, - ) -} - -function printUnprefixedCandidate(designSystem: DesignSystem, candidate: Candidate) { - let candidateString = designSystem.printCandidate(candidate) - - return designSystem.theme.prefix && candidateString.startsWith(`${designSystem.theme.prefix}:`) - ? candidateString.slice(designSystem.theme.prefix.length + 1) - : candidateString -} - -// ---- - -const SPACING_KEY = Symbol() - -// We prefer bare values over arbitrary values such as `left-[6px]` → -// `left-1.5`. -// -// There are situations where this doesn't always make sense. E.g.: -// `left-[99999px]` → `left-24999.75`. -// -// We could try and analyze the incoming value and bail out if we detect certain -// patterns. But a first step would be to limit the bare values for large values -// and keep them low. The largest rem value we have is for the 2xl breakpoint, -// which is 96rem, which is 1536px. -const MAX_BARE_VALUE_IN_PX = 1536 -const MAX_BARE_VALUE_IN_REM = MAX_BARE_VALUE_IN_PX / 16 - -function isReasonableBareValue(value: number, designSystem: DesignSystem, rem: number | null) { - let spacingMultiplier = designSystem.resolveThemeValue('--spacing') - if (spacingMultiplier === undefined) return false - - let parsed = dimensions.get(constantFoldDeclaration(spacingMultiplier, rem)) - if (parsed === null) return false - - let [spacingValue, spacingUnit] = parsed - let bareValueInPixels = value * spacingValue - - if (spacingUnit === 'px') return bareValueInPixels <= MAX_BARE_VALUE_IN_PX - if (spacingUnit === 'rem') return bareValueInPixels <= MAX_BARE_VALUE_IN_REM - return false -} - -function createSpacingCache( - designSystem: DesignSystem, - options?: CanonicalizeOptions, -): DesignSystem['storage'][typeof SPACING_KEY] { - let spacingMultiplier = designSystem.resolveThemeValue('--spacing') - if (spacingMultiplier === undefined) return null - - spacingMultiplier = constantFoldDeclaration(spacingMultiplier, options?.rem ?? null) - - let parsed = dimensions.get(spacingMultiplier) - if (!parsed) return null - - let [value, unit] = parsed - - return new DefaultMap((input) => { - // If we already know that the spacing multiplier is 0, all spacing - // multipliers will also be 0. No need to even try and parse/canonicalize - // the input value. - if (value === 0) return null - - let parsed = dimensions.get(constantFoldDeclaration(input, options?.rem ?? null)) - if (!parsed) return null - - let [myValue, myUnit] = parsed - if (myUnit !== unit) return null - - return myValue / value - }) -} - -function arbitraryUtilities(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in arbitrary properties and arbitrary values - if ( - // Arbitrary property - candidate.kind !== 'arbitrary' && - // Arbitrary value - !(candidate.kind === 'functional' && candidate.value?.kind === 'arbitrary') - ) { - return candidate - } - - let designSystem = options.designSystem - let utilities = designSystem.storage[PRE_COMPUTED_UTILITIES_KEY].get(options.signatureOptions) - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = designSystem.printCandidate(candidate) - - // Compute the signature for the target candidate - let targetSignature = signatures.get(targetCandidateString) - if (typeof targetSignature !== 'string') return candidate - - // Try a few options to find a suitable replacement utility - for (let replacementCandidate of tryReplacements(targetSignature, candidate)) { - if (!hasSameSignature(candidate, replacementCandidate)) { - continue - } - - // Ensure that if CSS variables were used, that they are still used - if (!allVariablesAreUsed(designSystem, candidate, replacementCandidate)) { - continue - } - - return replacementCandidate - } - - return candidate - - function* tryReplacements( - targetSignature: string, - candidate: Extract, - ): Generator { - // Find a corresponding utility for the same signature - let replacements = utilities.get(targetSignature) - - // Multiple utilities can map to the same signature. - if (replacements.length > 1) { - // Prefer positive values over negative values - let maybeReplacement: string | undefined = undefined - for (let replacement of replacements) { - if (replacement[0] === '-') continue // Skip negative values - - // If multiple non-negative replacements exists then we are unsure - // what to do, so let's bail. - if (maybeReplacement) return - - // Consider this replacement - maybeReplacement = replacement - } - - if (maybeReplacement) { - for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) { - yield replacementCandidate - } - } - - return - } - - // If we didn't find any replacement utilities, let's try to strip the - // modifier and find a replacement then. If we do, we can try to re-add the - // modifier later and verify if we have a valid migration. - // - // This is necessary because `text-red-500/50` will not be pre-computed, - // only `text-red-500` will. - if (replacements.length === 0 && candidate.modifier) { - let candidateWithoutModifier = { ...candidate, modifier: null } - let targetSignatureWithoutModifier = signatures.get( - designSystem.printCandidate(candidateWithoutModifier), - ) - if (typeof targetSignatureWithoutModifier === 'string') { - for (let replacementCandidate of tryReplacements( - targetSignatureWithoutModifier, - candidateWithoutModifier, - )) { - yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) - } - } - } - - // If only a single utility maps to the signature, we can use that as the - // replacement. - if (replacements.length === 1) { - for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { - yield replacementCandidate - } - } - - // Find a corresponding functional utility for the same signature - else if (replacements.length === 0) { - // An arbitrary property will only set a single property, we can use that - // to find functional utilities that also set this property. - let value = - candidate.kind === 'arbitrary' ? candidate.value : (candidate.value?.value ?? null) - if (value === null) return - - // Try to canonicalize any incoming arbitrary value. Canonicalization of - // `rem` and `px` values will be converted to `px`, so we have to - // canonicalize the spacing multiplier as well. - if ( - options.signatureOptions.rem !== null && - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' - ) { - let bareValue = designSystem.storage[SPACING_KEY]?.get(value) ?? null - if (bareValue !== null) { - if ( - isValidSpacingMultiplier(bareValue) && - isReasonableBareValue(bareValue, designSystem, options.signatureOptions.rem) - ) { - yield Object.assign({}, candidate, { - value: { kind: 'named', value: bareValue, fraction: null }, - }) - } - } - } - - let spacingMultiplier = designSystem.storage[SPACING_KEY]?.get(value) ?? null - let rootPrefix = '' - if (spacingMultiplier !== null && spacingMultiplier < 0) { - rootPrefix = '-' - spacingMultiplier = Math.abs(spacingMultiplier) - } - - for (let root of Array.from(designSystem.utilities.keys('functional')).sort( - // Sort negative roots after positive roots so that we can try - // `mt-*` before `-mt-*`. This is especially useful in situations where - // `-mt-[0px]` can be translated to `mt-[0px]`. - (a, z) => Number(a[0] === '-') - Number(z[0] === '-'), - )) { - if (rootPrefix) root = `${rootPrefix}${root}` - - // Try as bare value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-${value}`)) { - yield replacementCandidate - } - - // Try as bare value with modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${value}${candidate.modifier}`, - )) { - yield replacementCandidate - } - } - - // Try bare value based on the `--spacing` value. E.g.: - // - // - `w-[64rem]` → `w-256` - if ( - spacingMultiplier !== null && - isValidSpacingMultiplier(spacingMultiplier) && - isReasonableBareValue(spacingMultiplier, designSystem, options.signatureOptions.rem) - ) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${spacingMultiplier}`, - )) { - yield replacementCandidate - } - - // Try bare value based on the `--spacing` value, but with a modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-${spacingMultiplier}${printModifier(candidate.modifier)}`, - )) { - yield replacementCandidate - } - } - } - - // Try as arbitrary value - for (let replacementCandidate of parseCandidate(designSystem, `${root}-[${value}]`)) { - yield replacementCandidate - } - - // Try as arbitrary value with modifier - if (candidate.modifier) { - for (let replacementCandidate of parseCandidate( - designSystem, - `${root}-[${value}]${printModifier(candidate.modifier)}`, - )) { - yield replacementCandidate - } - } - } - } - } -} - -// Let's make sure that all variables used in the value are also all used in the -// found replacement. If not, then we are dealing with a different namespace or -// we could lose functionality in case the variable was changed higher up in the -// DOM tree. -function allVariablesAreUsed( - designSystem: DesignSystem, - candidate: Candidate, - replacement: Candidate, -) { - let value: string | null = null - - // Functional utility with arbitrary value and variables - if ( - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' && - candidate.value.value.includes('var(--') - ) { - value = candidate.value.value - } - - // Arbitrary property with variables - else if (candidate.kind === 'arbitrary' && candidate.value.includes('var(--')) { - value = candidate.value - } - - // No variables in the value, so this is a safe migration - if (value === null) { - return true - } - - let replacementAsCss = designSystem - .candidatesToCss([designSystem.printCandidate(replacement)]) - .join('\n') - - let isSafeMigration = true - walk(ValueParser.parse(value), (node) => { - if (node.kind === 'function' && node.value === 'var') { - let variable = node.nodes[0].value - let r = new RegExp(`var\\(${variable}[,)]\\s*`, 'g') - if ( - // We need to check if the variable is used in the replacement - !r.test(replacementAsCss) || - // The value cannot be set to a different value in the - // replacement because that would make it an unsafe migration - replacementAsCss.includes(`${variable}:`) - ) { - isSafeMigration = false - return WalkAction.Stop - } - } - }) - - return isSafeMigration -} - -// ---- - -function bareValueUtilities(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in bare value utilities - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'named') { - return candidate - } - - let designSystem = options.designSystem - let utilities = designSystem.storage[PRE_COMPUTED_UTILITIES_KEY].get(options.signatureOptions) - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = designSystem.printCandidate(candidate) - - // Compute the signature for the target candidate - let targetSignature = signatures.get(targetCandidateString) - if (typeof targetSignature !== 'string') return candidate - - // Try a few options to find a suitable replacement utility - for (let replacementCandidate of tryReplacements(targetSignature, candidate)) { - if (!hasSameSignature(candidate, replacementCandidate)) { - continue - } - - return replacementCandidate - } - - return candidate - - function* tryReplacements( - targetSignature: string, - candidate: Extract, - ): Generator { - // Find a corresponding utility for the same signature - let replacements = utilities.get(targetSignature) - - // Multiple utilities can map to the same signature. - if (replacements.length > 1) { - // Prefer positive values over negative values - let maybeReplacement: string | undefined = undefined - for (let replacement of replacements) { - if (replacement[0] === '-') continue // Skip negative values - - // If multiple non-negative replacements exists then we are unsure - // what to do, so let's bail. - if (maybeReplacement) return - - // Consider this replacement - maybeReplacement = replacement - } - - if (maybeReplacement) { - for (let replacementCandidate of parseCandidate(designSystem, maybeReplacement)) { - yield replacementCandidate - } - } - - return - } - - // If we didn't find any replacement utilities, let's try to strip the - // modifier and find a replacement then. If we do, we can try to re-add the - // modifier later and verify if we have a valid migration. - // - // This is necessary because `text-red-500/50` will not be pre-computed, - // only `text-red-500` will. - if (replacements.length === 0 && candidate.modifier) { - let candidateWithoutModifier = { ...candidate, modifier: null } - let targetSignatureWithoutModifier = signatures.get( - designSystem.printCandidate(candidateWithoutModifier), - ) - if (typeof targetSignatureWithoutModifier === 'string') { - for (let replacementCandidate of tryReplacements( - targetSignatureWithoutModifier, - candidateWithoutModifier, - )) { - yield Object.assign({}, replacementCandidate, { modifier: candidate.modifier }) - } - } - } - - // If only a single utility maps to the signature, we can use that as the - // replacement. - if (replacements.length === 1) { - for (let replacementCandidate of parseCandidate(designSystem, replacements[0])) { - yield replacementCandidate - } - } - } -} - -// ---- - -const DEPRECATION_MAP = new Map([ - ['order-none', 'order-0'], - ['break-words', 'wrap-break-word'], - ['overflow-ellipsis', 'text-ellipsis'], -]) - -const DEPRECATION_TRANSFORMATION_MAP = new Map([ - [/^(-)?start-(.*?)$/, '$1inset-s-$2'], - [/^(-)?end-(.*?)$/, '$1inset-e-$2'], -]) - -function* tryDeprecatedUtilities(candidate: string) { - // Try static replacements - let replacement = DEPRECATION_MAP.get(candidate) - if (replacement) yield replacement - - // Try dynamic replacements - for (let [searchValue, replaceValue] of DEPRECATION_TRANSFORMATION_MAP) { - let replacement = candidate.replace(searchValue, replaceValue) - if (replacement === candidate) continue - - yield replacement - } -} - -function deprecatedUtilities( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - let designSystem = options.designSystem - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let targetCandidateString = printUnprefixedCandidate(designSystem, candidate) - - for (let replacementString of tryDeprecatedUtilities(targetCandidateString)) { - if (!hasSameSignature(candidate, replacementString)) { - continue - } - - let [replacement] = parseCandidate(designSystem, replacementString) - return replacement - } - - return candidate -} - -// ---- - -function arbitraryVariants( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let designSystem = options.designSystem - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(options.signatureOptions.rem) - let variants = designSystem.storage[PRE_COMPUTED_VARIANTS_KEY].get(options.signatureOptions.rem) - - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - if (variant.kind === 'compound') continue - - let targetString = designSystem.printVariant(variant) - let targetSignature = signatures.get(targetString) - if (typeof targetSignature !== 'string') continue - - let foundVariants = variants.get(targetSignature) - if (foundVariants.length === 0) continue - - // The variant is already in a canonical form, e.g.: `min-lg` and `lg` - // produce the same CSS, but both are valid so we keep them as-is. - if (foundVariants.includes(targetString)) continue - - let foundVariant = foundVariants[0] - let parsedVariant = designSystem.parseVariant(foundVariant) - if (parsedVariant === null) continue - - replaceObject(variant, parsedVariant) - } - - return variant -} - -// ---- - -function dropUnnecessaryDataTypes( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - if ( - candidate.kind === 'functional' && - candidate.value?.kind === 'arbitrary' && - candidate.value.dataType !== null - ) { - let replacement = designSystem.printCandidate({ - ...candidate, - value: { ...candidate.value, dataType: null }, - }) - - if (signatures.get(designSystem.printCandidate(candidate)) === signatures.get(replacement)) { - candidate.value.dataType = null - } - } - - return candidate -} - -// ---- - -function arbitraryValueToBareValueUtility( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - // We are only interested in functional utilities with arbitrary values - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'arbitrary') { - return candidate - } - - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - let expectedSignature = signatures.get(designSystem.printCandidate(candidate)) - if (expectedSignature === null) return candidate - - for (let value of tryValueReplacements(candidate)) { - let newSignature = signatures.get(designSystem.printCandidate({ ...candidate, value })) - if (newSignature === expectedSignature) { - candidate.value = value - return candidate - } - } - - return candidate -} - -function arbitraryValueToBareValueVariant(variant: Variant): Variant | Variant[] { - let iterator = walkVariants(variant) - for (let [variant] of iterator) { - // Convert `data-[selected]` to `data-selected` - if ( - variant.kind === 'functional' && - variant.root === 'data' && - variant.value?.kind === 'arbitrary' && - !variant.value.value.includes('=') - ) { - variant.value = { - kind: 'named', - value: variant.value.value, - } - } - - // Convert `aria-[selected="true"]` to `aria-selected` - else if ( - variant.kind === 'functional' && - variant.root === 'aria' && - variant.value?.kind === 'arbitrary' && - (variant.value.value.endsWith('=true') || - variant.value.value.endsWith('="true"') || - variant.value.value.endsWith("='true'")) - ) { - let [key, _value] = segment(variant.value.value, '=') - if ( - // aria-[foo~="true"] - key[key.length - 1] === '~' || - // aria-[foo|="true"] - key[key.length - 1] === '|' || - // aria-[foo^="true"] - key[key.length - 1] === '^' || - // aria-[foo$="true"] - key[key.length - 1] === '$' || - // aria-[foo*="true"] - key[key.length - 1] === '*' - ) { - continue - } - - variant.value = { - kind: 'named', - value: variant.value.value.slice(0, variant.value.value.indexOf('=')), - } - } - - // Convert `supports-[gap]` to `supports-gap` - else if ( - variant.kind === 'functional' && - variant.root === 'supports' && - variant.value?.kind === 'arbitrary' && - /^[a-z-][a-z0-9-]*$/i.test(variant.value.value) - ) { - variant.value = { - kind: 'named', - value: variant.value.value, - } - } - } - - return variant -} - -// Convert functional utilities with arbitrary values to bare values if we can. -// We know that bare values can only be: -// -// 1. A number (with increments of .25) -// 2. A percentage (with increments of .25 followed by a `%`) -// 3. A ratio with whole numbers -// -// Not a bare value per se, but if we are dealing with a keyword, that could -// potentially also look like a bare value (aka no `[` or `]`). E.g.: -// ```diff -// grid-cols-[subgrid] -// grid-cols-subgrid -// ``` -function* tryValueReplacements( - candidate: Extract, - value: string = candidate.value?.value ?? '', - seen: Set = new Set(), -): Generator { - if (seen.has(value)) return - seen.add(value) - - // 0. Just try to drop the square brackets and see if it works - // 1. A number (with increments of .25) - yield { - kind: 'named', - value, - fraction: null, - } - - // 2. A percentage (with increments of .25 followed by a `%`) - // Try to drop the `%` and see if it works - if (value.endsWith('%') && isValidSpacingMultiplier(value.slice(0, -1))) { - yield { - kind: 'named', - value: value.slice(0, -1), - fraction: null, - } - } - - // 3. A ratio with whole numbers - if (value.includes('/')) { - let [numerator, denominator] = value.split('/') - if (isPositiveInteger(numerator) && isPositiveInteger(denominator)) { - yield { - kind: 'named', - value: numerator, - fraction: `${numerator}/${denominator}`, - } - } - } - - // It could also be that we have `20px`, we can try just `20` and see if it - // results in the same signature. - let allNumbersAndFractions = new Set() - - // Figure out all numbers and fractions in the value - for (let match of value.matchAll(/(\d+\/\d+)|(\d+\.?\d+)/g)) { - allNumbersAndFractions.add(match[0].trim()) - } - - // Sort the numbers and fractions where the smallest length comes first. This - // will result in the smallest replacement. - let options = Array.from(allNumbersAndFractions).sort((a, z) => { - return a.length - z.length - }) - - // Try all the options - for (let option of options) { - yield* tryValueReplacements(candidate, option, seen) - } -} - -// ---- - -function isSingleSelector(ast: SelectorParser.SelectorAstNode[]): boolean { - if (ast.length === 1 && ast[0].kind === 'list') return false - return true -} - -function isAttributeSelector(node: SelectorParser.SelectorNode): boolean { - return node.value[0] === '[' && node.value[node.value.length - 1] === ']' -} - -function modernizeArbitraryValuesVariant( - variant: Variant, - options: InternalCanonicalizeOptions, -): Variant | Variant[] { - let result = [variant] - let designSystem = options.designSystem - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(options.signatureOptions.rem) - - let iterator = walkVariants(variant) - for (let [variant, parent] of iterator) { - // Forward modifier from the root to the compound variant - if ( - variant.kind === 'compound' && - (variant.root === 'has' || variant.root === 'not' || variant.root === 'in') - ) { - if (variant.modifier !== null) { - if ('modifier' in variant.variant) { - variant.variant.modifier = variant.modifier - variant.modifier = null - } - } - } - - // Expecting an arbitrary variant - if (variant.kind === 'arbitrary') { - // Expecting a non-relative arbitrary variant - if (variant.relative) continue - - let ast = SelectorParser.parse(variant.selector) - - // Expecting a single selector node - if (!isSingleSelector(ast)) continue - - // Keep the existing arbitrary variant modernizations working on a flat - // selector sequence while the parser exposes complex selectors as a node. - if (ast.length === 1 && ast[0].kind === 'complex') { - ast = ast[0].nodes - } - - // `[&>*]` can be replaced with `*` - if ( - // Only top-level, so `has-[&>*]` is not supported - parent === null && - // [&_>_*]:flex - // ^ ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === '>' && - ast[2].kind === 'selector' && - ast[2].value === '*' - ) { - replaceObject(variant, designSystem.parseVariant('*')) - continue - } - - // `[&_*]` can be replaced with `**` - if ( - // Only top-level, so `has-[&_*]` is not supported - parent === null && - // [&_*]:flex - // ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - ast[2].value === '*' - ) { - replaceObject(variant, designSystem.parseVariant('**')) - continue - } - - // `[&:has(…)]` can be replaced with `has-[…]` - if ( - // Only top-level, so `group-[&:has(…)]` is not covered - - parent === null && - // [&:has(…)]:flex - // ^ ^^^^^^ - ast.length === 1 && - ast[0].kind === 'compound' && - ast[0].nodes.length === 2 && - ast[0].nodes[0].kind === 'selector' && - ast[0].nodes[0].value === '&' && - ast[0].nodes[1].kind === 'function' && - ast[0].nodes[1].value === ':has' && - ast[0].nodes[1].nodes.length === 1 && - ast[0].nodes[1].nodes[0].kind === 'selector' - ) { - replaceObject( - variant, - designSystem.parseVariant(`has-[${SelectorParser.toCss(ast[0].nodes[1].nodes, true)}]`), - ) - continue - } - - // `in-*` variant. If the selector ends with ` &`, we can convert it to an - // `in-*` variant. - // - // E.g.: `[[data-visible]_&]` => `in-data-visible` - if ( - // Only top-level, so `in-[&_[data-visible]]` is not supported - parent === null && - // [[data-visible]___&]:flex - // ^^^^^^^^^^^^^^ ^ ^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - ast[2].value === '&' - ) { - ast.pop() // Remove the nesting node - ast.pop() // Remove the combinator - - // When handling a compound like `in-[[data-visible]]`, we will first - // handle `[[data-visible]]`, then the parent `in-*` part. This means - // that we can convert `[[data-visible]_&]` to `in-[[data-visible]]`. - // - // Later this gets converted to `in-data-visible`. - replaceObject(variant, designSystem.parseVariant(`in-[${SelectorParser.toCss(ast, true)}]`)) - continue - } - - // Hoist `not` modifier for `@media` or `@supports` variants - // - // E.g.: `[@media_not_(scripting:none)]:` -> `not-[@media_(scripting:none)]:` - if ( - // Only top-level, so something like `in-[@media(scripting:none)]` - // (which is not valid anyway) is not supported - parent === null && - // [@media_not(scripting:none)]:flex - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ast[0].kind === 'selector' && - (ast[0].value === '@media' || ast[0].value === '@supports') - ) { - let targetSignature = signatures.get(designSystem.printVariant(variant)) - - let parsed = ValueParser.parse(SelectorParser.toCss(ast, true)) - let containsNot = false - walk(parsed, (node) => { - if (node.kind === 'word' && node.value === 'not') { - containsNot = true - return WalkAction.Replace([]) - } - }) - - // Remove unnecessary whitespace - parsed = ValueParser.parse(ValueParser.toCss(parsed)) - walk(parsed, (node) => { - if (node.kind === 'separator' && node.value !== ' ' && node.value.trim() === '') { - // node.value contains at least 2 spaces. Normalize it to a single - // space. - node.value = ' ' - } - }) - - if (containsNot) { - let hoistedNot = designSystem.parseVariant(`not-[${ValueParser.toCss(parsed)}]`) - if (hoistedNot === null) continue - let hoistedNotSignature = signatures.get(designSystem.printVariant(hoistedNot)) - if (targetSignature === hoistedNotSignature) { - replaceObject(variant, hoistedNot) - continue - } - } - } - - let prefixedVariant: Variant | null = null - - // Handling a child combinator. E.g.: `[&>[data-visible]]` => `*:data-visible` - if ( - // Only top-level, so `has-[&>[data-visible]]` is not supported - parent === null && - // [&_>_[data-visible]]:flex - // ^ ^ ^^^^^^^^^^^^^^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === '>' && - ast[2].kind === 'selector' && - (ast[2].value[0] === ':' || isAttributeSelector(ast[2])) - ) { - ast = [ast[2]] - prefixedVariant = designSystem.parseVariant('*') - } - - // Handling a grand child combinator. E.g.: `[&_[data-visible]]` => `**:data-visible` - if ( - // Only top-level, so `has-[&_[data-visible]]` is not supported - parent === null && - // [&_[data-visible]]:flex - // ^ ^^^^^^^^^^^^^^ - ast.length === 3 && - ast[0].kind === 'selector' && - ast[0].value === '&' && - ast[1].kind === 'combinator' && - ast[1].value === ' ' && - ast[2].kind === 'selector' && - (ast[2].value[0] === ':' || isAttributeSelector(ast[2])) - ) { - ast = [ast[2]] - prefixedVariant = designSystem.parseVariant('**') - } - - let selectorNodes = ast - walk(selectorNodes, { - enter(node) { - // Filter out `&`. E.g.: `&[data-foo]` => `[data-foo]` - if (node.kind === 'selector' && node.value === '&') { - return WalkAction.Replace([]) - } - - if (node.kind === 'function') { - return WalkAction.Skip - } - }, - exit(node) { - if (node.kind === 'compound' && node.nodes.length === 1) { - return WalkAction.ReplaceSkip(node.nodes) - } - }, - }) - - // Expecting a single selector (normal selector or attribute selector) - if (selectorNodes.length !== 1) continue - - let target = selectorNodes[0] - if (target.kind === 'function' && target.value === ':is') { - // Expecting a single selector node - if ( - !isSingleSelector(target.nodes) || - // [foo][bar] is considered a single selector but has multiple nodes - target.nodes.length !== 1 - ) { - continue - } - - // Expecting a single attribute selector - if (target.nodes[0].kind === 'selector' && !isAttributeSelector(target.nodes[0])) continue - - // Unwrap the selector from inside `&:is(…)` - target = target.nodes[0] - } - - // Expecting a pseudo selector (or function) - if ( - (target.kind === 'function' && target.value[0] === ':') || - (target.kind === 'selector' && target.value[0] === ':') - ) { - let targetNode = target - let compoundNot = false - if (targetNode.kind === 'function' && targetNode.value === ':not') { - compoundNot = true - if (targetNode.nodes.length !== 1) continue - if (targetNode.nodes[0].kind !== 'selector' && targetNode.nodes[0].kind !== 'function') { - continue - } - if (targetNode.nodes[0].value[0] !== ':') continue - - targetNode = targetNode.nodes[0] - } - - let newVariant = ((value) => { - if ( - value === ':nth-child' && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - targetNode.nodes[0].value === 'odd' - ) { - if (compoundNot) { - compoundNot = false - return 'even' - } - return 'odd' - } - - if ( - value === ':nth-child' && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - targetNode.nodes[0].value === 'even' - ) { - if (compoundNot) { - compoundNot = false - return 'odd' - } - return 'even' - } - - for (let [selector, variantName] of [ - [':nth-child', 'nth'], - [':nth-last-child', 'nth-last'], - [':nth-of-type', 'nth-of-type'], - [':nth-last-of-type', 'nth-of-last-type'], - ]) { - if ( - value === selector && - targetNode.kind === 'function' && - targetNode.nodes.length === 1 - ) { - if ( - targetNode.nodes.length === 1 && - targetNode.nodes[0].kind === 'value' && - isPositiveInteger(targetNode.nodes[0].value) - ) { - return `${variantName}-${targetNode.nodes[0].value}` - } - - return `${variantName}-[${SelectorParser.toCss(targetNode.nodes, true)}]` - } - } - - // Hoist `not` modifier - if (compoundNot) { - let targetSignature = signatures.get(designSystem.printVariant(variant)) - let replacementSignature = signatures.get(`not-[${value}]`) - if (targetSignature === replacementSignature) { - return `[&${value}]` - } - } - - return null - })(targetNode.value) - - if (newVariant === null) { - if (prefixedVariant) { - replaceObject(variant, { - kind: 'arbitrary', - selector: target.value, - relative: false, - } satisfies Variant) - - return [prefixedVariant, variant] - } - - continue - } - - // Add `not-` prefix - if (compoundNot) newVariant = `not-${newVariant}` - - let parsed = designSystem.parseVariant(newVariant) - if (parsed === null) continue - - // Update original variant - replaceObject(variant, parsed) - } - - // Expecting an attribute selector - else if (target.kind === 'selector' && isAttributeSelector(target)) { - let attributeSelector = AttributeSelectorParser.parse(target.value) - if (attributeSelector === null) continue // Invalid attribute selector - - // Migrate `data-*` - if (attributeSelector.attribute.startsWith('data-')) { - let name = attributeSelector.attribute.slice(5) // Remove `data-` - - replaceObject(variant, { - kind: 'functional', - root: 'data', - modifier: null, - value: - attributeSelector.value === null - ? { kind: 'named', value: name } - : { - kind: 'arbitrary', - value: `${name}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, - }, - } satisfies Variant) - } - - // Migrate `aria-*` - else if (attributeSelector.attribute.startsWith('aria-')) { - let name = attributeSelector.attribute.slice(5) // Remove `aria-` - replaceObject(variant, { - kind: 'functional', - root: 'aria', - modifier: null, - value: - attributeSelector.value === null - ? { kind: 'arbitrary', value: name } // aria-[foo] - : attributeSelector.operator === '=' && - attributeSelector.value === 'true' && - attributeSelector.sensitivity === null - ? { kind: 'named', value: name } // aria-[foo="true"] or aria-[foo='true'] or aria-[foo=true] - : { - kind: 'arbitrary', - value: `${attributeSelector.attribute}${attributeSelector.operator}${attributeSelector.quote ?? ''}${attributeSelector.value}${attributeSelector.quote ?? ''}${attributeSelector.sensitivity ? ` ${attributeSelector.sensitivity}` : ''}`, - }, // aria-[foo~="true"], aria-[foo|="true"], … - } satisfies Variant) - } - - // Arbitrary attributes - else { - replaceObject(variant, { - kind: 'arbitrary', - selector: target.value, - relative: false, - } satisfies Variant) - } - } - - if (prefixedVariant) { - return [prefixedVariant, variant] - } - } - } - - return result -} - -// --- - -function optimizeArbitraryValueExpressions( - candidate: Candidate, - options: InternalCanonicalizeOptions, -): Candidate { - if (candidate.kind !== 'functional' || candidate.value?.kind !== 'arbitrary') { - return candidate - } - - let designSystem = options.designSystem - let hasSameSignature = designSystem.storage[COMPARE_CANDIDATES_KEY].get(options.signatureOptions) - - let valueAst = ValueParser.parse(candidate.value.value) - - // Start by constant folding the value expression, when dealing with `calc(…)` - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'calc') { - let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst, null, false) - if (folded) { - let replacement = cloneCandidate(candidate) - replacement.value!.value = ValueParser.toCss(foldedValueAst) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - valueAst = foldedValueAst - } - } - } - - // Move `-` sign into the arbitrary value itself - if (candidate.root[0] === '-') { - // We're dealing with a `var(…)`, keep as-is - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'var') { - return candidate - } - - // Move `* -1` inside, and try to constant fold to see if it's even worth - // updating the candidate or not. - let expressionAst = ValueParser.parse(`calc(${candidate.value!.value} * -1)`) - let [folded, foldedExpressionAst] = constantFoldDeclarationAst(expressionAst, null, false) - if (folded) { - let replacement = cloneCandidate(candidate) - - replacement.root = replacement.root.slice(1) // Drop the leading `-` - replacement.value!.value = ValueParser.toCss(foldedExpressionAst) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - valueAst = foldedExpressionAst - } - } - } - - // Move `-` sign out of the arbitrary value - if (valueAst.length === 1 && valueAst[0].kind === 'function' && valueAst[0].value === 'calc') { - let calcArgs = valueAst[0].nodes - - // `calc(arg * -1)` or `calc(-1 * arg)` - if ( - calcArgs.length === 5 && - calcArgs[1].kind === 'separator' && - calcArgs[1].value === ' ' && - calcArgs[2].kind === 'word' && - calcArgs[2].value === '*' && - calcArgs[3].kind === 'separator' && - calcArgs[3].value === ' ' - ) { - let arg = - calcArgs[4].kind === 'word' && calcArgs[4].value === '-1' - ? calcArgs[0] - : calcArgs[0].kind === 'word' && calcArgs[0].value === '-1' - ? calcArgs[4] - : null - - if (arg) { - let replacement = cloneCandidate(candidate) - replacement.root = `-${candidate.root}` - replacement.value!.value = ValueParser.toCss([arg]) - - if (hasSameSignature(candidate, replacement)) { - candidate = replacement - } - } - } - } - - return candidate -} - -// ---- - -// Optimize the modifier -// -// E.g.: -// -// - `/[25%]` → `/25` -// - `/[100%]` → `/100` → -// - `/100` → -// -function optimizeModifier(candidate: Candidate, options: InternalCanonicalizeOptions): Candidate { - // We are only interested in functional or arbitrary utilities with a modifier - if ( - (candidate.kind !== 'functional' && candidate.kind !== 'arbitrary') || - candidate.modifier === null - ) { - return candidate - } - - let designSystem = options.designSystem - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options.signatureOptions) - - let targetSignature = signatures.get(designSystem.printCandidate(candidate)) - let modifier = candidate.modifier - - // 1. Try to drop the modifier entirely - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: null })) - ) { - candidate.modifier = null - return candidate - } - - // 2. Try to remove the square brackets and the `%` sign - { - let newModifier: NamedUtilityValue = { - kind: 'named', - value: modifier.value.endsWith('%') - ? modifier.value.includes('.') - ? `${Number(modifier.value.slice(0, -1))}` - : modifier.value.slice(0, -1) - : modifier.value, - fraction: null, - } - - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier })) - ) { - candidate.modifier = newModifier - return candidate - } - } - - // 3. Try to remove the square brackets, but multiply by 100. E.g.: `[0.16]` -> `16` - { - let newModifier: NamedUtilityValue = { - kind: 'named', - value: `${parseFloat(modifier.value) * 100}`, - fraction: null, - } - - if ( - targetSignature === - signatures.get(designSystem.printCandidate({ ...candidate, modifier: newModifier })) - ) { - candidate.modifier = newModifier - return candidate - } - } - - return candidate -} - -export enum SignatureFeatures { - None = 0, - ExpandProperties = 1 << 0, - LogicalToPhysical = 1 << 1, -} - -interface SignatureOptions { - /** - * The root font size in pixels. If provided, `rem` values will be normalized - * to `px` values. - * - * E.g.: `mt-[16px]` with `rem: 16` will become `mt-4` (assuming `--spacing: 0.25rem`). - */ - rem: number | null - - /** - * Features that influence how signatures are computed. - */ - features: SignatureFeatures -} - -// Given a utility, compute a signature that represents the utility. The -// signature will be a normalised form of the generated CSS for the utility, or -// a unique symbol if the utility is not valid. The class in the selector will -// be replaced with the `.x` selector. -// -// This function should only be passed the base utility so `flex`, `hover:flex` -// and `focus:flex` will all use just `flex`. Variants are handled separately. -// -// E.g.: -// -// | UTILITY | GENERATED SIGNATURE | -// | ---------------- | ----------------------- | -// | `[display:flex]` | `.x { display: flex; }` | -// | `flex` | `.x { display: flex; }` | -// -// These produce the same signature, therefore they represent the same utility. -export const UTILITY_SIGNATURE_KEY = Symbol() -function createUtilitySignatureCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof UTILITY_SIGNATURE_KEY] { - return new DefaultMap((options: SignatureOptions) => { - return new DefaultMap((utility) => { - try { - // Ensure the prefix is added to the utility if it is not already present. - utility = - designSystem.theme.prefix && !utility.startsWith(designSystem.theme.prefix) - ? `${designSystem.theme.prefix}:${utility}` - : utility - - // Use `@apply` to normalize the selector to `.x` - let ast: AstNode[] = [styleRule('.x', [atRule('@apply', utility)])] - - temporarilyDisableThemeInline(designSystem, () => { - // There's separate utility caches for respect important vs not - // so we want to compile them both with `@theme inline` disabled - for (let candidate of designSystem.parseCandidate(utility)) { - designSystem.compileAstNodes(candidate, CompileAstFlags.RespectImportant) - } - - substituteAtApply(ast, designSystem) - }) - - // Optimize the AST. This is needed such that any internal intermediate - // nodes are gone. This will also cleanup declaration nodes with undefined - // values or `--tw-sort` declarations. - canonicalizeAst(designSystem, ast, options) - - // Compute the final signature, by generating the CSS for the utility - let signature = toCss(ast) - return signature - } catch { - // A unique symbol is returned to ensure that 2 signatures resulting in - // `null` are not considered equal. - return Symbol() - } - }) - }) -} - -// #RGB -// #RGBA -// #RRGGBB -// #RRGGBBAA -const HEX_REGEX = /#(?:[a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{4}|[a-f0-9]{3})/gi - -// Optimize the CSS AST to make it suitable for signature comparison. We want to -// expand declarations, ignore comments, sort declarations etc... -function canonicalizeAst(designSystem: DesignSystem, ast: AstNode[], options: SignatureOptions) { - let { rem } = options - - walk(ast, { - enter(node, ctx) { - // Optimize declarations - if (node.kind === 'declaration') { - if (node.value === undefined || node.property === '--tw-sort') { - return WalkAction.Replace([]) - } - - // Ignore `--tw-{property}` if `{property}` exists with the same value - if (node.property.startsWith('--tw-')) { - if ( - ctx.siblings.some( - (sibling) => - sibling.kind === 'declaration' && - node.value === sibling.value && - node.important === sibling.important && - !sibling.property.startsWith('--tw-'), - ) - ) { - return WalkAction.Replace([]) - } - } - - if (options.features & SignatureFeatures.ExpandProperties) { - let replacement = expandDeclaration(node, options.features) - if (replacement) return WalkAction.Replace(replacement) - } - - // Resolve theme values to their inlined value. - if (node.value.includes('var(')) { - node.value = resolveVariablesInValue(node.value, designSystem) - } - - let valueAst = ValueParser.parse(node.value) - - // Very basic `calc(…)` constant folding to handle the spacing scale - // multiplier: - // - // Input: `--spacing(4)` - // → `calc(var(--spacing, 0.25rem) * 4)` - // → `calc(0.25rem * 4)` ← this is the case we will see - // after inlining the variable - // → `1rem` - let [folded, foldedValueAst] = constantFoldDeclarationAst(valueAst, rem) - - // Normalize `calc(…)` expressions such that arguments that are - // associatively equivalent are rendered in the same way. - // - // `calc(var(--foo) * -1)` === `calc(-1 * var(--foo))` - let [normalized, canonicalizedValueAst] = canonicalizeCalcExpressionsAst(foldedValueAst) - - if (folded || normalized) { - node.value = ValueParser.toCss(canonicalizedValueAst) - } - - // We will normalize the `node.value`, this is the same kind of logic - // we use when printing arbitrary values. It will remove unnecessary - // whitespace. - // - // Essentially normalizing the `node.value` to a canonical form. - node.value = printArbitraryValue(node.value) - } - - // Replace special nodes with its children - else if (node.kind === 'context' || node.kind === 'at-root') { - return WalkAction.Replace(node.nodes) - } - - // Remove comments - else if (node.kind === 'comment') { - return WalkAction.Replace([]) - } - - // Remove at-rules that are not needed for the signature - else if (node.kind === 'at-rule' && node.name === '@property') { - return WalkAction.Replace([]) - } - }, - exit(node) { - if (node.kind === 'rule' || node.kind === 'at-rule') { - // Remove declarations that are re-defined again later. - // - // This could maybe result in unwanted behavior (because similar - // properties typically exist for backwards compatibility), but for - // signature purposes we can assume that the last declaration wins. - if (node.nodes.length > 1) { - let seen = new Set() - for (let i = node.nodes.length - 1; i >= 0; i--) { - let child = node.nodes[i] - if (child.kind !== 'declaration') continue - if (child.value === undefined) continue - - if (seen.has(child.property)) { - node.nodes.splice(i, 1) - } - seen.add(child.property) - } - } - - // Sort declarations alphabetically by property name - node.nodes.sort((a, b) => { - if (a.kind !== 'declaration') return 0 - if (b.kind !== 'declaration') return 0 - return a.property.localeCompare(b.property) - }) - } - - // - else if (node.kind === 'declaration' && node.value) { - // Leave CSS variables alone - if (node.property[0] === '-' && node.property[1] === '-') return - - // Ensure hex colors are always lowercased - { - HEX_REGEX.lastIndex = 0 - node.value = node.value.replace(HEX_REGEX, (color) => color.toLowerCase()) - } - } - }, - }) - - return ast -} - -// Variables whose theme value is a CSS-wide keyword (e.g.: `unset`) are never -// inlined. These are typically registered as a placeholder to be re-assigned at -// runtime, so two variables that share such a value are not interchangeable. -// -// E.g.: -// -// ```css -// @theme { -// --foreground: unset; -// --background: unset; -// } -// ``` -// -// Inlining would make `text-(--foreground)` and `text-(--background)` produce -// the same signature `color: unset`, even though they are different at runtime. -// -// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Data_types#css-wide_keywords -const CSS_WIDE_KEYWORDS = ['initial', 'inherit', 'revert', 'revert-layer', 'revert-rule', 'unset'] - -// Resolve theme values to their inlined value. -// -// E.g.: -// -// `[color:var(--color-red-500)]` → `[color:oklch(63.7%_0.237_25.331)]` -// `[color:oklch(63.7%_0.237_25.331)]` → `[color:oklch(63.7%_0.237_25.331)]` -// -// Due to the `@apply` from above, this will become: -// -// ```css -// .example { -// color: oklch(63.7% 0.237 25.331); -// } -// ``` -// -// Which conveniently will be equivalent to: `text-red-500` when we inline the -// value. -// -// Without inlining: -// ```css -// .example { -// color: var(--color-red-500, oklch(63.7% 0.237 25.331)); -// } -// ``` -// -// Inlined: -// ```css -// .example { -// color: oklch(63.7% 0.237 25.331); -// } -// ``` -// -// Recently we made sure that utilities like `text-red-500` also generate the -// fallback value for usage in `@reference` mode. -// -// The second assumption is that if you use `var(--key, fallback)` that happens -// to match a known variable _and_ its inlined value. Then we can replace it -// with the inlined variable. This allows us to handle custom `@theme` and -// `@theme inline` definitions. -function resolveVariablesInValue(value: string, designSystem: DesignSystem): string { - let changed = false - let valueAst = ValueParser.parse(value) - - let seen = new Set() - walk(valueAst, (valueNode) => { - if (valueNode.kind !== 'function') return - if (valueNode.value !== 'var') return - - // Resolve the underlying value of the variable - if (valueNode.nodes.length !== 1 && valueNode.nodes.length < 3) { - return - } - - let variable = valueNode.nodes[0].value - - // Drop the prefix from the variable name if it is present. The - // internal variable doesn't have the prefix. - if (designSystem.theme.prefix && variable.startsWith(`--${designSystem.theme.prefix}-`)) { - variable = variable.slice(`--${designSystem.theme.prefix}-`.length) - } - let variableValue = designSystem.resolveThemeValue(variable) - // Prevent infinite recursion when the variable value contains the - // variable itself. - if (seen.has(variable)) return - seen.add(variable) - if (variableValue === undefined) return // Couldn't resolve the variable - - // CSS-wide keywords are never inlined - if (CSS_WIDE_KEYWORDS.includes(variableValue.toLowerCase())) return - - // Inject variable fallbacks when no fallback is present yet. - // - // A fallback could consist of multiple values. - // - // E.g.: - // - // ``` - // var(--font-sans, ui-sans-serif, system-ui, sans-serif, …) - // ``` - { - // More than 1 argument means that a fallback is already present - if (valueNode.nodes.length === 1) { - // Inject the fallback value into the variable lookup - changed = true - valueNode.nodes.push(...ValueParser.parse(`,${variableValue}`)) - } - } - - // Replace known variable + inlined fallback value with the value - // itself again - { - // We need at least 3 arguments. The variable, the separator and a fallback value. - if (valueNode.nodes.length >= 3) { - let nodeAsString = ValueParser.toCss(valueNode.nodes) // This could include more than just the variable - let constructedValue = `${valueNode.nodes[0].value},${variableValue}` - if (nodeAsString === constructedValue) { - changed = true - return WalkAction.Replace(ValueParser.parse(variableValue)) - } - } - } - }) - - // Replace the value with the new value - if (changed) return ValueParser.toCss(valueAst) - return value -} - -// Index all static utilities by property and value -const STATIC_UTILITIES_KEY = Symbol() -function createStaticUtilitiesCache(): DesignSystem['storage'][typeof STATIC_UTILITIES_KEY] { - return new DefaultMap((_optiones: SignatureOptions) => { - return new DefaultMap((_property: string) => { - return new DefaultMap((_value: string) => { - return new Set() - }) - }) - }) -} - -const UTILITY_PROPERTIES_KEY = Symbol() -function createUtilityPropertiesCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof UTILITY_PROPERTIES_KEY] { - return new DefaultMap((options: SignatureOptions) => { - return new DefaultMap((className) => { - let localPropertyValueLookup = new DefaultMap((_property) => new Set()) - - if (designSystem.theme.prefix && !className.startsWith(designSystem.theme.prefix)) { - className = `${designSystem.theme.prefix}:${className}` - } - let parsed = designSystem.parseCandidate(className) - if (parsed.length === 0) return localPropertyValueLookup - - try { - let ast = designSystem.compileAstNodes(parsed[0]).map((x) => cloneAstNode(x.node)) - walk(canonicalizeAst(designSystem, ast, options), (node) => { - if (node.kind === 'declaration') { - localPropertyValueLookup.get(node.property).add(node.value!) - designSystem.storage[STATIC_UTILITIES_KEY].get(options) - .get(node.property) - .get(node.value!) - .add(className) - } - }) - } catch { - // Ignore errors, this could happen when we call a plugin with a value - // it didn't expect. But since plugins are functions, we can't know - // ahead of time what it expects. - } - - return localPropertyValueLookup - }) - }) -} - -// For all static utilities in the system, compute a lookup table that maps the -// utility signature to the utility name. This is used to find the utility name -// for a given utility signature. -// -// For all functional utilities, we can compute static-like utilities by -// essentially pre-computing the values and modifiers. This is a bit slow, but -// also only has to happen once per design system. -const PRE_COMPUTED_UTILITIES_KEY = Symbol() -function createPreComputedUtilitiesCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof PRE_COMPUTED_UTILITIES_KEY] { - return new DefaultMap((options: SignatureOptions) => { - let signatures = designSystem.storage[UTILITY_SIGNATURE_KEY].get(options) - let lookup = new DefaultMap(() => []) - - // Right now all plugins are implemented using functions so they are a black - // box. Let's use the `getClassList` and consider every known suggestion as a - // static utility for now. - for (let [className, meta] of designSystem.getClassList()) { - let signature = signatures.get(className) - if (typeof signature !== 'string') continue - - // Skip the utility if `-{utility}-0` has the same signature as - // `{utility}-0` (its positive version). This will prefer positive values - // over negative values. - if (className[0] === '-' && className.endsWith('-0')) { - let positiveSignature = signatures.get(className.slice(1)) - if (typeof positiveSignature === 'string' && signature === positiveSignature) { - continue - } - } - - lookup.get(signature).push(className) - designSystem.storage[UTILITY_PROPERTIES_KEY].get(options).get(className) - - for (let modifier of meta.modifiers) { - // Modifiers representing numbers can be computed and don't need to be - // pre-computed. Doing the math and at the time of writing this, this - // would save you 250k additionally pre-computed utilities... - if (isValidSpacingMultiplier(modifier)) { - continue - } - - let classNameWithModifier = `${className}/${modifier}` - let signature = signatures.get(classNameWithModifier) - if (typeof signature !== 'string') continue - lookup.get(signature).push(classNameWithModifier) - designSystem.storage[UTILITY_PROPERTIES_KEY].get(options).get(classNameWithModifier) - } - } - - return lookup - }) -} - -// Given a variant, compute a signature that represents the variant. The -// signature will be a normalised form of the generated CSS for the variant, or -// a unique symbol if the variant is not valid. The class in the selector will -// be replaced with `.x`. -// -// E.g.: -// -// | VARIANT | GENERATED SIGNATURE | -// | ---------------- | ----------------------------- | -// | `[&:focus]:flex` | `.x:focus { display: flex; }` | -// | `focus:flex` | `.x:focus { display: flex; }` | -// -// These produce the same signature, therefore they represent the same variant. -export const VARIANT_SIGNATURE_KEY = Symbol() -function createVariantSignatureCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof VARIANT_SIGNATURE_KEY] { - return new DefaultMap((rem: number | null) => { - return new DefaultMap((variant) => { - try { - // Ensure the prefix is added to the utility if it is not already present. - variant = - designSystem.theme.prefix && !variant.startsWith(designSystem.theme.prefix) - ? `${designSystem.theme.prefix}:${variant}` - : variant - - // Use `@apply` to normalize the selector to `.x` - let ast: AstNode[] = [styleRule('.x', [atRule('@apply', `${variant}:flex`)])] - substituteAtApply(ast, designSystem) - - // Canonicalize selectors to their minimal form - walk(ast, (node) => { - // At-rules - if (node.kind === 'at-rule') { - // Normalize dimensions such that equivalent values in different - // units are considered the same. E.g.: with a root font size of - // `16px`, `@media (width >= 64rem)` and `@media (width >= 1024px)` - // are equivalent. - node.params = constantFoldDeclaration(node.params, rem) - - if (node.params.includes(' ')) { - node.params = node.params.replaceAll(' ', '') - } - } - - // Style rules - else if (node.kind === 'rule') { - let selectorAst = SelectorParser.parse(node.selector) - let changed = false - walk(selectorAst, (node) => { - // Assumption: when we have a list of selectors: `.foo, .bar` we - // want to mark this as changed because this will be re-printed as - // `.foo,.bar`. - // - // Similarly, when we receive a combinator like `.foo + .bar`, this - // will be printed as `.foo+.bar`. - // - // It could be that this was already optimal, but then this would be - // a no-op situation. - if (node.kind === 'list' || node.kind === 'combinator') { - changed = true - } - - // Remove unnecessary `:is(…)` selectors - else if (node.kind === 'function' && node.value === ':is') { - // A single selector inside of `:is(…)` can be replaced with the - // selector itself. - // - // E.g.: `:is(.foo)` → `.foo` - if (node.nodes.length === 1) { - changed = true - return WalkAction.Replace(node.nodes) - } - - // A selector with the universal selector `*` followed by a pseudo - // class, can be replaced with the pseudo class itself. - else if ( - node.nodes.length === 2 && - node.nodes[0].kind === 'selector' && - node.nodes[0].value === '*' && - node.nodes[1].kind === 'selector' && - node.nodes[1].value[0] === ':' - ) { - changed = true - return WalkAction.Replace(node.nodes[1]) - } - } - - // Ensure `*` exists before pseudo selectors inside of `:not(…)`, - // `:where(…)`, … - // - // E.g.: - // - // `:not(:first-child)` → `:not(*:first-child)` - // - else if ( - node.kind === 'function' && - node.value[0] === ':' && - node.nodes[0]?.kind === 'selector' && - node.nodes[0]?.value[0] === ':' - ) { - changed = true - node.nodes.unshift({ kind: 'selector', value: '*' }) - } - }) - - if (changed) { - node.selector = SelectorParser.toCss(selectorAst, true) - } - } - }) - - // Compute the final signature, by generating the CSS for the variant - let signature = toCss(ast) - return signature - } catch { - // A unique symbol is returned to ensure that 2 signatures resulting in - // `null` are not considered equal. - return Symbol() - } - }) - }) -} - -export const PRE_COMPUTED_VARIANTS_KEY = Symbol() -function createPreComputedVariantsCache( - designSystem: DesignSystem, -): DesignSystem['storage'][typeof PRE_COMPUTED_VARIANTS_KEY] { - return new DefaultMap((rem: number | null) => { - let signatures = designSystem.storage[VARIANT_SIGNATURE_KEY].get(rem) - let lookup = new DefaultMap(() => []) - - for (let [root, variant] of designSystem.variants.entries()) { - // Actual static variants - if (variant.kind === 'static') { - let signature = signatures.get(root) - if (typeof signature !== 'string') continue - lookup.get(signature).push(root) - } - - // Functional variants with known values, e.g.: `max-lg`, `@min-md`, … - else if (variant.kind === 'functional') { - for (let value of designSystem.variants.getCompletions(root)) { - let name = root === '@' ? `@${value}` : `${root}-${value}` - let signature = signatures.get(name) - if (typeof signature !== 'string') continue - lookup.get(signature).push(name) - } - } - } - - return lookup - }) -} - -function temporarilyDisableThemeInline(designSystem: DesignSystem, cb: () => T): T { - // Turn off `@theme inline` feature such that `@theme` and `@theme inline` are - // considered the same. The biggest motivation for this is referencing - // variables in another namespace that happen to contain the same value as the - // utility's own namespaces it is reading from. - // - // E.g.: - // - // The `max-w-*` utility doesn't read from the `--breakpoint-*` namespace. - // But it does read from the `--container-*` namespace. It also happens to - // be the case that `--breakpoint-md` and `--container-3xl` are the exact - // same value. - // - // If you then use the `max-w-(--breakpoint-md)` utility, inlining the - // variable would mean: - // - `max-w-(--breakpoint-md)` → `max-width: 48rem;` → `max-w-3xl` - // - `max-w-(--contianer-3xl)` → `max-width: 48rem;` → `max-w-3xl` - // - // Not inlining the variable would mean: - // - `max-w-(--breakpoint-md)` → `max-width: var(--breakpoint-md);` → `max-w-(--breakpoint-md)` - // - `max-w-(--container-3xl)` → `max-width: var(--container-3xl);` → `max-w-3xl` - - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - let originalGet = designSystem.theme.values.get - - // Track all values with the inline option set, so we can restore them later. - let restorableInlineOptions = new Set<{ options: ThemeOptions }>() - - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - designSystem.theme.values.get = (key: string) => { - // @ts-expect-error We are monkey-patching a method that's considered private - // in TypeScript - let value = originalGet.call(designSystem.theme.values, key) - if (value === undefined) return value - - // Remove `inline` if it was set - if (value.options & ThemeOptions.INLINE) { - restorableInlineOptions.add(value) - value.options &= ~ThemeOptions.INLINE - } - - return value - } - - try { - // Run the callback with the `@theme inline` feature disabled - return cb() - } finally { - // Restore the `@theme inline` to the original value - // @ts-expect-error We are monkey-patching a method that's private - designSystem.theme.values.get = originalGet - - // Re-add the `inline` option, in case future lookups are done - for (let value of restorableInlineOptions) { - value.options |= ThemeOptions.INLINE - } - } -} - -// Generator that generates all combinations of the given set. Using a generator -// so we can stop early when we found a suitable combination. -// -// NOTE: -// -// 1. Yield biggest combinations first -// 2. Sets of size 1 and 0 are not yielded -function* combinations(arr: T[]): Generator { - let n = arr.length - let limit = 1n << BigInt(n) - - for (let k = n; k >= 2; k--) { - let mask = (1n << BigInt(k)) - 1n - - while (mask < limit) { - let out = [] - for (let i = 0; i < n; i++) { - if ((mask >> BigInt(i)) & 1n) { - out.push(arr[i]) - } - } - yield out - - // Gosper's hack: - // - https://programmingforinsomniacs.blogspot.com/2018/03/gospers-hack-explained.html - // - https://rosettacode.org/wiki/Gosper%27s_hack - // - // We need to generate the next mask in lexicographical order. - let carry = mask & -mask - let ripple = mask + carry - mask = (((ripple ^ mask) >> 2n) / carry) | ripple - } - } -} - -function intersection(a: Set, b: Set): Set { - // @ts-expect-error Set.prototype.intersection is only available in Node.js v22+ - if (typeof a.intersection === 'function') return a.intersection(b) - - // Polyfill for environments that do not support Set.prototype.intersection yet - if (a.size === 0 || b.size === 0) return new Set() - - let result = new Set(a) - for (let item of b) { - if (!result.has(item)) { - result.delete(item) - } - } - - return result -} diff --git a/packages/tailwindcss/src/cartesian.ts b/packages/tailwindcss/src/cartesian.ts deleted file mode 100644 index f0f7bbba4..000000000 --- a/packages/tailwindcss/src/cartesian.ts +++ /dev/null @@ -1,45 +0,0 @@ -type CartesianInput = readonly unknown[][] - -type CartesianResult = T extends [ - infer Head extends unknown[], - ...infer Tail extends CartesianInput, -] - ? [Head[number], ...CartesianResult] - : [] - -export function* cartesian(...sets: T): Generator> { - let n = sets.length - if (n === 0) return - - // If any input set is empty, the Cartesian product is empty. - if (sets.some((set) => set.length === 0)) { - return - } - - // Index lookup - let idx = Array(n).fill(0) - - while (true) { - // Compute current combination - let result = [] as CartesianResult - for (let i = 0; i < n; i++) { - result[i] = sets[i][idx[i]] - } - yield result - - // Update index vector - let k = n - 1 - while (k >= 0) { - idx[k]++ - if (idx[k] < sets[k].length) { - break - } - idx[k] = 0 - k-- - } - - if (k < 0) { - return - } - } -} diff --git a/packages/tailwindcss/src/compat/apply-compat-hooks.ts b/packages/tailwindcss/src/compat/apply-compat-hooks.ts index 036b9d88e..fb619561b 100644 --- a/packages/tailwindcss/src/compat/apply-compat-hooks.ts +++ b/packages/tailwindcss/src/compat/apply-compat-hooks.ts @@ -1,9 +1,7 @@ import { Features } from '..' -import { cssContext, styleRule, toCss, type AstNode } from '../ast' +import { styleRule, toCss, walk, WalkAction, type AstNode } from '../ast' import type { DesignSystem } from '../design-system' -import type { SourceLocation } from '../source-maps/source' import { segment } from '../utils/segment' -import { walk, WalkAction } from '../walk' import { applyConfigToTheme } from './apply-config-to-theme' import { applyKeyframesToTheme } from './apply-keyframes-to-theme' import { createCompatConfig } from './config/create-compat-config' @@ -40,24 +38,16 @@ export async function applyCompatibilityHooks({ sources: { base: string; pattern: string; negated: boolean }[] }) { let features = Features.None - let pluginPaths: [ - { id: string; base: string; reference: boolean; src: SourceLocation | undefined }, - CssPluginOptions | null, - ][] = [] - let configPaths: { - id: string - base: string - reference: boolean - src: SourceLocation | undefined - }[] = [] + let pluginPaths: [{ id: string; base: string; reference: boolean }, CssPluginOptions | null][] = + [] + let configPaths: { id: string; base: string; reference: boolean }[] = [] - walk(ast, (node, _ctx) => { + walk(ast, (node, { parent, replaceWith, context }) => { if (node.kind !== 'at-rule') return - let ctx = cssContext(_ctx) // Collect paths from `@plugin` at-rules if (node.name === '@plugin') { - if (ctx.parent !== null) { + if (parent !== null) { throw new Error('`@plugin` cannot be nested.') } @@ -110,17 +100,13 @@ export async function applyCompatibilityHooks({ } pluginPaths.push([ - { - id: pluginPath, - base: ctx.context.base as string, - reference: !!ctx.context.reference, - src: node.src, - }, + { id: pluginPath, base: context.base as string, reference: !!context.reference }, Object.keys(options).length > 0 ? options : null, ]) + replaceWith([]) features |= Features.JsPluginCompat - return WalkAction.Replace([]) + return } // Collect paths from `@config` at-rules @@ -129,18 +115,18 @@ export async function applyCompatibilityHooks({ throw new Error('`@config` cannot have a body.') } - if (ctx.parent !== null) { + if (parent !== null) { throw new Error('`@config` cannot be nested.') } configPaths.push({ id: node.params.slice(1, -1), - base: ctx.context.base as string, - reference: !!ctx.context.reference, - src: node.src, + base: context.base as string, + reference: !!context.reference, }) + replaceWith([]) features |= Features.JsPluginCompat - return WalkAction.Replace([]) + return } }) @@ -176,19 +162,18 @@ export async function applyCompatibilityHooks({ let [configs, pluginDetails] = await Promise.all([ Promise.all( - configPaths.map(async ({ id, base, reference, src }) => { + configPaths.map(async ({ id, base, reference }) => { let loaded = await loadModule(id, base, 'config') return { path: id, base: loaded.base, config: loaded.module as UserConfig, reference, - src, } }), ), Promise.all( - pluginPaths.map(async ([{ id, base, reference, src }, pluginOptions]) => { + pluginPaths.map(async ([{ id, base, reference }, pluginOptions]) => { let loaded = await loadModule(id, base, 'plugin') return { path: id, @@ -196,7 +181,6 @@ export async function applyCompatibilityHooks({ plugin: loaded.module as Plugin, options: pluginOptions, reference, - src, } }), ), @@ -231,7 +215,6 @@ function upgradeToFullPluginSupport({ base: string config: UserConfig reference: boolean - src: SourceLocation | undefined }[] pluginDetails: { path: string @@ -239,7 +222,6 @@ function upgradeToFullPluginSupport({ plugin: Plugin options: CssPluginOptions | null reference: boolean - src: SourceLocation | undefined }[] }) { let features = Features.None @@ -249,7 +231,6 @@ function upgradeToFullPluginSupport({ config: { plugins: [detail.plugin] }, base: detail.base, reference: detail.reference, - src: detail.src, } } @@ -258,7 +239,6 @@ function upgradeToFullPluginSupport({ config: { plugins: [detail.plugin(detail.options)] }, base: detail.base, reference: detail.reference, - src: detail.src, } } @@ -268,32 +248,15 @@ function upgradeToFullPluginSupport({ let userConfig = [...pluginConfigs, ...configs] let { resolvedConfig } = resolveConfig(designSystem, [ - { config: createCompatConfig(designSystem.theme), base, reference: true, src: undefined }, + { config: createCompatConfig(designSystem.theme), base, reference: true }, ...userConfig, - { config: { plugins: [darkModePlugin] }, base, reference: true, src: undefined }, + { config: { plugins: [darkModePlugin] }, base, reference: true }, ]) let { resolvedConfig: resolvedUserConfig, replacedThemeKeys } = resolveConfig( designSystem, userConfig, ) - let pluginApiConfig = { - designSystem, - ast, - resolvedConfig, - featuresRef: { - set current(value: number) { - features |= value - }, - }, - } - - let sharedPluginApi = buildPluginApi({ - ...pluginApiConfig, - referenceMode: false, - src: undefined, - }) - // Replace `resolveThemeValue` with a version that is backwards compatible // with dot-notation but also aware of any JS theme configurations registered // by plugins or JS config files. This is significantly slower than just @@ -307,56 +270,49 @@ function upgradeToFullPluginSupport({ return defaultResolveThemeValue(path, forceInline) } - let resolvedValue = sharedPluginApi.theme(path, undefined) + let resolvedValue = pluginApi.theme(path, undefined) - // If we're dealing with an object that has the `DEFAULT` key, unwrap it to - // the default value first. This happens for namespace root lookups where - // the CSS theme defines a bare value, e.g. `--shadow` resolved via - // `theme('shadow')`. - if ( - typeof resolvedValue === 'object' && - resolvedValue !== null && - !Array.isArray(resolvedValue) && - 'DEFAULT' in resolvedValue - ) { - resolvedValue = resolvedValue.DEFAULT - } - - // When a tuple is returned, return the first element if (Array.isArray(resolvedValue) && resolvedValue.length === 2) { + // When a tuple is returned, return the first element return resolvedValue[0] - } - - // Arrays get serialized into a comma-separated lists - else if (Array.isArray(resolvedValue)) { + } else if (Array.isArray(resolvedValue)) { + // Arrays get serialized into a comma-separated lists return resolvedValue.join(', ') - } - - // Otherwise only allow string values here, objects (and namespace maps) - // are treated as non-resolved values for the CSS `theme()` function. - else if (typeof resolvedValue === 'string') { + } else if (typeof resolvedValue === 'string') { + // Otherwise only allow string values here, objects (and namespace maps) + // are treated as non-resolved values for the CSS `theme()` function. return resolvedValue } } - for (let { handler, reference, src } of resolvedConfig.plugins) { - // Each plugin gets its own instance of the plugin API because nodes added - // to the AST may need to point to the `@config` or `@plugin` that they - // originated from - let api = buildPluginApi({ - ...pluginApiConfig, - referenceMode: reference ?? false, - src, - }) + let pluginApiConfig = { + designSystem, + ast, + resolvedConfig, + featuresRef: { + set current(value: number) { + features |= value + }, + }, + } - handler(api) + let pluginApi = buildPluginApi({ ...pluginApiConfig, referenceMode: false }) + let referenceModePluginApi = undefined + + for (let { handler, reference } of resolvedConfig.plugins) { + if (reference) { + referenceModePluginApi ||= buildPluginApi({ ...pluginApiConfig, referenceMode: true }) + handler(referenceModePluginApi) + } else { + handler(pluginApi) + } } // Merge the user-configured theme keys into the design system. The compat // config would otherwise expand into namespaces like `background-color` which // core utilities already read from. applyConfigToTheme(designSystem, resolvedUserConfig, replacedThemeKeys) - applyKeyframesToTheme(designSystem, resolvedUserConfig) + applyKeyframesToTheme(designSystem, resolvedUserConfig, replacedThemeKeys) registerThemeVariantOverrides(resolvedUserConfig, designSystem) registerScreensConfig(resolvedUserConfig, designSystem) @@ -389,18 +345,18 @@ function upgradeToFullPluginSupport({ if (typeof resolvedConfig.important === 'string') { let wrappingSelector = resolvedConfig.important - walk(ast, (node, _ctx) => { + walk(ast, (node, { replaceWith, parent }) => { if (node.kind !== 'at-rule') return if (node.name !== '@tailwind' || node.params !== 'utilities') return - let ctx = cssContext(_ctx) - // The AST node was already manually wrapped so there's nothing to do - if (ctx.parent?.kind === 'rule' && ctx.parent.selector === wrappingSelector) { + if (parent?.kind === 'rule' && parent.selector === wrappingSelector) { return WalkAction.Stop } - return WalkAction.ReplaceStop(styleRule(wrappingSelector, [node])) + replaceWith(styleRule(wrappingSelector, [node])) + + return WalkAction.Stop }) } diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts index 12f195e94..3a748fd8b 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.test.ts @@ -31,16 +31,6 @@ test('config values can be merged into the theme', () => { normal: '0 1px 3px black', }, - borderWidth: { - DEFAULT: '1.5px', - }, - outlineWidth: { - DEFAULT: '2.5px', - }, - ringWidth: { - DEFAULT: '3.5px', - }, - borderRadius: { sm: '0.33rem', }, @@ -67,10 +57,6 @@ test('config values can be merged into the theme', () => { '2xl': ['2rem'], }, - ringColor: { - DEFAULT: '#fff', - }, - letterSpacing: { superWide: '0.25em', }, @@ -91,17 +77,12 @@ test('config values can be merged into the theme', () => { }, transitionTimingFunction: { - DEFAULT: 'ease-in-out', fast: 'cubic-bezier(0, 0.55, 0.45, 1)', }, - transitionDuration: { - DEFAULT: '1234ms', - }, }, }, base: '/root', reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -136,30 +117,13 @@ test('config values can be merged into the theme', () => { ]) expect(theme.resolve('2xl', ['--text'])).toEqual('2rem') expect(theme.resolveWith('2xl', ['--text'], ['--line-height'])).toEqual(['2rem', {}]) - expect(theme.resolve('superWide', ['--tracking'])).toEqual('0.25em') - expect(theme.resolve('superLoose', ['--leading'])).toEqual('3') + expect(theme.resolve('super-wide', ['--tracking'])).toEqual('0.25em') + expect(theme.resolve('super-loose', ['--leading'])).toEqual('3') expect(theme.resolve('1/2', ['--width'])).toEqual('60%') expect(theme.resolve('0.5', ['--width'])).toEqual('60%') expect(theme.resolve('100%', ['--width'])).toEqual('100%') expect(theme.resolve('9xs', ['--container'])).toEqual('6rem') expect(theme.resolve('fast', ['--ease'])).toEqual('cubic-bezier(0, 0.55, 0.45, 1)') - - expect(theme.get(['--border'])).toEqual(null) - expect(theme.get(['--default-border-width'])).toEqual('1.5px') - - expect(theme.get(['--outline'])).toEqual(null) - expect(theme.get(['--default-outline-width'])).toEqual('2.5px') - - expect(theme.get(['--ring-color'])).toEqual(null) - expect(theme.get(['--default-ring-color'])).toEqual('#fff') - - expect(theme.get(['--ring-width'])).toEqual(null) - expect(theme.get(['--default-ring-width'])).toEqual('3.5px') - - expect(theme.get(['--default-transition-duration'])).toEqual('1234ms') - - expect(theme.get(['--ease'])).toEqual(null) - expect(theme.get(['--default-transition-timing-function'])).toEqual('ease-in-out') }) test('will reset default theme values with overwriting theme values', () => { @@ -196,8 +160,6 @@ test('will reset default theme values with overwriting theme values', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -227,8 +189,6 @@ test('invalid keys are not merged into the theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -267,8 +227,6 @@ test('converts opacity modifiers from decimal to percentage values', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) @@ -303,7 +261,6 @@ test('handles setting theme keys to null', async () => { }, base: '/root', reference: false, - src: undefined, }, ]) applyConfigToTheme(design, resolvedConfig, replacedThemeKeys) diff --git a/packages/tailwindcss/src/compat/apply-config-to-theme.ts b/packages/tailwindcss/src/compat/apply-config-to-theme.ts index 8757520ee..c957d3209 100644 --- a/packages/tailwindcss/src/compat/apply-config-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-config-to-theme.ts @@ -147,57 +147,32 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk return toAdd } -const SPECIAL_DEFAULT_KEYS: Record = { - borderWidth: 'border-width', - outlineWidth: 'outline-width', - ringColor: 'ring-color', - ringWidth: 'ring-width', - transitionDuration: 'transition-duration', - transitionTimingFunction: 'transition-timing-function', -} - -const OLD_TO_NEW_NAMESPACE: Record = { - animation: 'animate', - aspectRatio: 'aspect', - borderRadius: 'radius', - boxShadow: 'shadow', - colors: 'color', - containers: 'container', - fontFamily: 'font', - fontSize: 'text', - letterSpacing: 'tracking', - lineHeight: 'leading', - maxWidth: 'container', - screens: 'breakpoint', - transitionTimingFunction: 'ease', -} - -const IS_VALID_KEY = /^[a-zA-Z0-9-_%/.]+$/ +const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/ export function keyPathToCssProperty(path: string[]) { - // In some special cases the `DEFAULT` key did not map to a "default" utility - // e.g. `ringColor.DEFAULT` wasn't *just* used for `ring`. It was used for - // all ring utilities as the color when one wasn't specified. - // - // We place these specialty values under the `--default-*` namespace to signal - // that they are defaults used by (potentially) multiple utilities. - let specialDefault = SPECIAL_DEFAULT_KEYS[path[0]] - if (specialDefault && path[1] === 'DEFAULT') return `default-${specialDefault}` - // The legacy container component config should not be included in the Theme if (path[0] === 'container') return null + path = structuredClone(path) + + if (path[0] === 'animation') path[0] = 'animate' + if (path[0] === 'aspectRatio') path[0] = 'aspect' + if (path[0] === 'borderRadius') path[0] = 'radius' + if (path[0] === 'boxShadow') path[0] = 'shadow' + if (path[0] === 'colors') path[0] = 'color' + if (path[0] === 'containers') path[0] = 'container' + if (path[0] === 'fontFamily') path[0] = 'font' + if (path[0] === 'fontSize') path[0] = 'text' + if (path[0] === 'letterSpacing') path[0] = 'tracking' + if (path[0] === 'lineHeight') path[0] = 'leading' + if (path[0] === 'maxWidth') path[0] = 'container' + if (path[0] === 'screens') path[0] = 'breakpoint' + if (path[0] === 'transitionTimingFunction') path[0] = 'ease' + for (let part of path) { if (!IS_VALID_KEY.test(part)) return null } - // Map old v3 namespaces to new theme namespaces - let ns = OLD_TO_NEW_NAMESPACE[path[0]] - if (ns) { - path = path.slice() - path[0] = ns - } - return ( path // [1] should move into the nested object tuple. To create the CSS variable @@ -210,27 +185,11 @@ export function keyPathToCssProperty(path: string[]) { .map((path, idx, all) => (path === '1' && idx !== all.length - 1 ? '' : path)) // Resolve the key path to a CSS variable segment - .map((part, idx) => { - part = part.replaceAll('.', '_') - - let shouldConvert = - // The first "namespace" part should be converted to kebab-case - // This converts things like backgroundColor to `background-color` - idx === 0 || - // Any tuple nested key should be converted to kebab-case - // These are identified with a leading `-` - // e.g. `fontSize.xs.1.lineHeight` -> `font-size-xs--line-height` - part.startsWith('-') || - // `lineHeight` is a bit of a special case in which it does not - // always begin with a leading `-` even when as a nested tuple key - part === 'lineHeight' - - if (shouldConvert) { - part = part.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`) - } - - return part - }) + .map((part) => + part + .replaceAll('.', '_') + .replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}-${b.toLowerCase()}`), + ) // Remove the `DEFAULT` key at the end of a path // We're reading from CSS anyway so it'll be a string diff --git a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts index aed761fce..a0b1c54d5 100644 --- a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts +++ b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.test.ts @@ -9,7 +9,7 @@ test('keyframes can be merged into the theme', () => { let theme = new Theme() let design = buildDesignSystem(theme) - let { resolvedConfig } = resolveConfig(design, [ + let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [ { config: { theme: { @@ -28,11 +28,9 @@ test('keyframes can be merged into the theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) - applyKeyframesToTheme(design, resolvedConfig) + applyKeyframesToTheme(design, resolvedConfig, replacedThemeKeys) expect(toCss(design.theme.getKeyframes())).toMatchInlineSnapshot(` "@keyframes fade-in { @@ -72,7 +70,7 @@ test('will append to the default keyframes with new keyframes', () => { ]), ) - let { resolvedConfig } = resolveConfig(design, [ + let { resolvedConfig, replacedThemeKeys } = resolveConfig(design, [ { config: { theme: { @@ -93,11 +91,9 @@ test('will append to the default keyframes with new keyframes', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) - applyKeyframesToTheme(design, resolvedConfig) + applyKeyframesToTheme(design, resolvedConfig, replacedThemeKeys) expect(toCss(design.theme.getKeyframes())).toMatchInlineSnapshot(` "@keyframes slide-in { diff --git a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts index d5855545f..a58ca13df 100644 --- a/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts +++ b/packages/tailwindcss/src/compat/apply-keyframes-to-theme.ts @@ -6,6 +6,7 @@ import { objectToAst } from './plugin-api' export function applyKeyframesToTheme( designSystem: DesignSystem, resolvedConfig: Pick, + replacedThemeKeys: Set, ) { for (let rule of keyframesToRules(resolvedConfig)) { designSystem.theme.addKeyframes(rule) diff --git a/packages/tailwindcss/src/compat/colors.ts b/packages/tailwindcss/src/compat/colors.ts index 64e3bc9c7..2f443b4b6 100644 --- a/packages/tailwindcss/src/compat/colors.ts +++ b/packages/tailwindcss/src/compat/colors.ts @@ -31,7 +31,7 @@ export default { '950': 'oklch(13% 0.028 261.692)', }, zinc: { - '50': 'oklch(98.5% 0 none)', + '50': 'oklch(98.5% 0 0)', '100': 'oklch(96.7% 0.001 286.375)', '200': 'oklch(92% 0.004 286.32)', '300': 'oklch(87.1% 0.006 286.286)', @@ -44,17 +44,17 @@ export default { '950': 'oklch(14.1% 0.005 285.823)', }, neutral: { - '50': 'oklch(98.5% 0 none)', - '100': 'oklch(97% 0 none)', - '200': 'oklch(92.2% 0 none)', - '300': 'oklch(87% 0 none)', - '400': 'oklch(70.8% 0 none)', - '500': 'oklch(55.6% 0 none)', - '600': 'oklch(43.9% 0 none)', - '700': 'oklch(37.1% 0 none)', - '800': 'oklch(26.9% 0 none)', - '900': 'oklch(20.5% 0 none)', - '950': 'oklch(14.5% 0 none)', + '50': 'oklch(98.5% 0 0)', + '100': 'oklch(97% 0 0)', + '200': 'oklch(92.2% 0 0)', + '300': 'oklch(87% 0 0)', + '400': 'oklch(70.8% 0 0)', + '500': 'oklch(55.6% 0 0)', + '600': 'oklch(43.9% 0 0)', + '700': 'oklch(37.1% 0 0)', + '800': 'oklch(26.9% 0 0)', + '900': 'oklch(20.5% 0 0)', + '950': 'oklch(14.5% 0 0)', }, stone: { '50': 'oklch(98.5% 0.001 106.423)', @@ -69,58 +69,6 @@ export default { '900': 'oklch(21.6% 0.006 56.043)', '950': 'oklch(14.7% 0.004 49.25)', }, - mauve: { - '50': 'oklch(98.5% 0 none)', - '100': 'oklch(96% 0.003 325.6)', - '200': 'oklch(92.2% 0.005 325.62)', - '300': 'oklch(86.5% 0.012 325.68)', - '400': 'oklch(71.1% 0.019 323.02)', - '500': 'oklch(54.2% 0.034 322.5)', - '600': 'oklch(43.5% 0.029 321.78)', - '700': 'oklch(36.4% 0.029 323.89)', - '800': 'oklch(26.3% 0.024 320.12)', - '900': 'oklch(21.2% 0.019 322.12)', - '950': 'oklch(14.5% 0.008 326)', - }, - olive: { - '50': 'oklch(98.8% 0.003 106.5)', - '100': 'oklch(96.6% 0.005 106.5)', - '200': 'oklch(93% 0.007 106.5)', - '300': 'oklch(88% 0.011 106.6)', - '400': 'oklch(73.7% 0.021 106.9)', - '500': 'oklch(58% 0.031 107.3)', - '600': 'oklch(46.6% 0.025 107.3)', - '700': 'oklch(39.4% 0.023 107.4)', - '800': 'oklch(28.6% 0.016 107.4)', - '900': 'oklch(22.8% 0.013 107.4)', - '950': 'oklch(15.3% 0.006 107.1)', - }, - mist: { - '50': 'oklch(98.7% 0.002 197.1)', - '100': 'oklch(96.3% 0.002 197.1)', - '200': 'oklch(92.5% 0.005 214.3)', - '300': 'oklch(87.2% 0.007 219.6)', - '400': 'oklch(72.3% 0.014 214.4)', - '500': 'oklch(56% 0.021 213.5)', - '600': 'oklch(45% 0.017 213.2)', - '700': 'oklch(37.8% 0.015 216)', - '800': 'oklch(27.5% 0.011 216.9)', - '900': 'oklch(21.8% 0.008 223.9)', - '950': 'oklch(14.8% 0.004 228.8)', - }, - taupe: { - '50': 'oklch(98.6% 0.002 67.8)', - '100': 'oklch(96% 0.002 17.2)', - '200': 'oklch(92.2% 0.005 34.3)', - '300': 'oklch(86.8% 0.007 39.5)', - '400': 'oklch(71.4% 0.014 41.2)', - '500': 'oklch(54.7% 0.021 43.1)', - '600': 'oklch(43.8% 0.017 39.3)', - '700': 'oklch(36.7% 0.016 35.7)', - '800': 'oklch(26.8% 0.011 36.5)', - '900': 'oklch(21.4% 0.009 43.1)', - '950': 'oklch(14.7% 0.004 49.3)', - }, red: { '50': 'oklch(97.1% 0.013 17.38)', '100': 'oklch(93.6% 0.032 17.717)', diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 1f69ae446..ea64fe3a1 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1,7 +1,6 @@ import { describe, expect, test, vi } from 'vitest' import { compile, type Config } from '..' import { default as plugin } from '../plugin' -import { compileCss, run } from '../test-utils/run' import flattenColorPalette from './flatten-color-palette' const css = String.raw @@ -13,28 +12,25 @@ test('Config files can add content', async () => { ` let compiler = await compile(input, { - loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root', path: '' }), + loadModule: async () => ({ module: { content: ['./file.txt'] }, base: '/root' }), }) expect(compiler.sources).toEqual([{ base: '/root', pattern: './file.txt', negated: false }]) }) test('Config files can change dark mode (media)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root', path: '' }), - }, - ), - ).toMatchInlineSnapshot(` - " - @media (prefers-color-scheme: dark) { - .dark\\:underline { + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ module: { darkMode: 'media' }, base: '/root' }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + @media (prefers-color-scheme: dark) { text-decoration-line: underline; } } @@ -43,80 +39,73 @@ test('Config files can change dark mode (media)', async () => { }) test('Config files can change dark mode (selector)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root', path: '' }), - }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(.dark, .dark *) { - text-decoration-line: underline; + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ module: { darkMode: 'selector' }, base: '/root' }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(.dark, .dark *) { + text-decoration-line: underline; + } } " `) }) test('Config files can change dark mode (variant)', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { darkMode: ['variant', '&:where(:not(.light))'] }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { darkMode: ['variant', '&:where(:not(.light))'] }, + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config files can add plugins', async () => { - expect( - await run( - ['no-scrollbar'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - plugins: [ - plugin(function ({ addUtilities }) { - addUtilities({ - '.no-scrollbar': { - 'scrollbar-width': 'none', - }, - }) - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + plugins: [ + plugin(function ({ addUtilities }) { + addUtilities({ + '.no-scrollbar': { + 'scrollbar-width': 'none', + }, + }) + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .no-scrollbar { + base: '/root', + }), + }) + + expect(compiler.build(['no-scrollbar'])).toMatchInlineSnapshot(` + ".no-scrollbar { scrollbar-width: none; } " @@ -124,181 +113,137 @@ test('Config files can add plugins', async () => { }) test('Plugins loaded from config files can contribute to the config', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - plugins: [ - plugin(() => {}, { - darkMode: ['variant', '&:where(:not(.light))'], - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + plugins: [ + plugin(() => {}, { + darkMode: ['variant', '&:where(:not(.light))'], + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config file presets can contribute to the config', async () => { - expect( - await run( - ['dark:underline'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { presets: [{ darkMode: ['variant', '&:where(:not(.light))'] }] }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + presets: [ + { + darkMode: ['variant', '&:where(:not(.light))'], + }, + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline:where(:not(.light)) { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:where(:not(.light)) { + text-decoration-line: underline; + } } " `) }) test('Config files can affect the theme', async () => { - expect( - await run( - ['bg-primary', 'scrollbar-primary'], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - colors: { - primary: '#c0ffee', - }, - }, + let input = css` + @tailwind utilities; + @config "./config.js"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + colors: { + primary: '#c0ffee', }, - - plugins: [ - plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.scrollbar-primary': { - scrollbarColor: theme('colors.primary'), - }, - }) - }), - ], }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .scrollbar-primary { - scrollbar-color: #c0ffee; - } + }, - .bg-primary { + plugins: [ + plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.scrollbar-primary': { + scrollbarColor: theme('colors.primary'), + }, + }) + }), + ], + }, + base: '/root', + }), + }) + + expect(compiler.build(['bg-primary', 'scrollbar-primary'])).toMatchInlineSnapshot(` + ".bg-primary { background-color: #c0ffee; } - " - `) -}) - -// https://github.com/tailwindlabs/tailwindcss/issues/19091 -test('Accessing a default color if a sub-color exists via CSS should work as expected', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @config "./config.js"; - - .example { - color: theme('colors.foo-bar'); - border-color: theme('colors.foo'); - } - `, - { - loadModule: async () => ({ - module: { - theme: { - // Internally this object gets converted to something like: - // ``` - // { - // foo: { DEFAULT: 'var(--foo-foo)', bar: 'var(--foo-foo-bar)' }, - // } - // ``` - colors: { - foo: 'var(--foo-foo)', - 'foo-bar': 'var(--foo-foo-bar)', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .example { - color: var(--foo-foo-bar); - border-color: var(--foo-foo); + .scrollbar-primary { + scrollbar-color: #c0ffee; } " `) }) test('Variants in CSS overwrite variants from plugins', async () => { - expect( - await run( - ['dark:underline', 'light:underline'], - css` - @tailwind utilities; - @config "./config.js"; - @custom-variant dark (&:is(.my-dark)); - @custom-variant light (&:is(.my-light)); - `, - { - loadModule: async () => ({ - module: { - darkMode: ['variant', '&:is(.dark)'], - plugins: [ - plugin(function ({ addVariant }) { - addVariant('light', '&:is(.light)') - }), - ], - }, - base: '/root', - path: '', - }), + let input = css` + @tailwind utilities; + @config "./config.js"; + @custom-variant dark (&:is(.my-dark)); + @custom-variant light (&:is(.my-light)); + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + darkMode: ['variant', '&:is(.dark)'], + plugins: [ + plugin(function ({ addVariant }) { + addVariant('light', '&:is(.light)') + }), + ], }, - ), - ).toMatchInlineSnapshot(` - " - .dark\\:underline.my-dark, .light\\:underline.my-light { - text-decoration-line: underline; + base: '/root', + }), + }) + + expect(compiler.build(['dark:underline', 'light:underline'])).toMatchInlineSnapshot(` + ".dark\\:underline { + &:is(.my-dark) { + text-decoration-line: underline; + } + } + .light\\:underline { + &:is(.my-light) { + text-decoration-line: underline; + } } " `) @@ -308,167 +253,154 @@ describe('theme callbacks', () => { test('tuple values from the config overwrite `@theme default` tuple-ish values from the CSS theme', async ({ expect, }) => { - expect( - await run( - ['leading-base', 'leading-md', 'leading-xl', 'prose'], - css` - @theme default { - --text-base: 0rem; - --text-base--line-height: 1rem; - --text-md: 0rem; - --text-md--line-height: 1rem; - --text-xl: 0rem; - --text-xl--line-height: 1rem; - } - @theme { - --text-base: 100rem; - --text-md--line-height: 101rem; - } - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - fontSize: { - base: ['200rem', { lineHeight: '201rem' }], - md: ['200rem', { lineHeight: '201rem' }], - xl: ['200rem', { lineHeight: '201rem' }], - }, + let input = css` + @theme default { + --text-base: 0rem; + --text-base--line-height: 1rem; + --text-md: 0rem; + --text-md--line-height: 1rem; + --text-xl: 0rem; + --text-xl--line-height: 1rem; + } + @theme { + --text-base: 100rem; + --text-md--line-height: 101rem; + } + @tailwind utilities; + @config "./config.js"; + ` - // Direct access - lineHeight: ({ theme }) => ({ - base: theme('fontSize.base[1].lineHeight'), - md: theme('fontSize.md[1].lineHeight'), - xl: theme('fontSize.xl[1].lineHeight'), - }), - - // Tuple access - typography: ({ theme }) => ({ - '[class~=lead-base]': { - fontSize: theme('fontSize.base')[0], - ...theme('fontSize.base')[1], - }, - '[class~=lead-md]': { - fontSize: theme('fontSize.md')[0], - ...theme('fontSize.md')[1], - }, - '[class~=lead-xl]': { - fontSize: theme('fontSize.xl')[0], - ...theme('fontSize.xl')[1], - }, - }), - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + fontSize: { + base: ['200rem', { lineHeight: '201rem' }], + md: ['200rem', { lineHeight: '201rem' }], + xl: ['200rem', { lineHeight: '201rem' }], }, - plugins: [ - plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.prose': { - ...theme('typography'), - }, - }) - }), - ], - } satisfies Config, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer properties { - @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) { - *, :before, :after, ::backdrop { - --tw-leading: initial; + // Direct access + lineHeight: ({ theme }) => ({ + base: theme('fontSize.base[1].lineHeight'), + md: theme('fontSize.md[1].lineHeight'), + xl: theme('fontSize.xl[1].lineHeight'), + }), + + // Tuple access + typography: ({ theme }) => ({ + '[class~=lead-base]': { + fontSize: theme('fontSize.base')[0], + ...theme('fontSize.base')[1], + }, + '[class~=lead-md]': { + fontSize: theme('fontSize.md')[0], + ...theme('fontSize.md')[1], + }, + '[class~=lead-xl]': { + fontSize: theme('fontSize.xl')[0], + ...theme('fontSize.xl')[1], + }, + }), + }, + }, + + plugins: [ + plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.prose': { + ...theme('typography'), + }, + }) + }), + ], + } satisfies Config, + base: '/root', + }), + }) + + expect(compiler.build(['leading-base', 'leading-md', 'leading-xl', 'prose'])) + .toMatchInlineSnapshot(` + "@layer properties; + .prose { + [class~=lead-base] { + font-size: 100rem; + line-height: 201rem; + } + [class~=lead-md] { + font-size: 200rem; + line-height: 101rem; + } + [class~=lead-xl] { + font-size: 200rem; + line-height: 201rem; } } - } - - .prose [class~="lead-base"] { - font-size: 100rem; - line-height: 201rem; - } - - .prose [class~="lead-md"] { - font-size: 200rem; - line-height: 101rem; - } - - .prose [class~="lead-xl"] { - font-size: 200rem; - line-height: 201rem; - } - - .leading-base { - --tw-leading: 201rem; - line-height: 201rem; - } - - .leading-md { - --tw-leading: 101rem; - line-height: 101rem; - } - - .leading-xl { - --tw-leading: 201rem; - line-height: 201rem; - } - - @property --tw-leading { - syntax: "*"; - inherits: false - } - " - `) + .leading-base { + --tw-leading: 201rem; + line-height: 201rem; + } + .leading-md { + --tw-leading: 101rem; + line-height: 101rem; + } + .leading-xl { + --tw-leading: 201rem; + line-height: 201rem; + } + @property --tw-leading { + syntax: "*"; + inherits: false; + } + @layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-leading: initial; + } + } + } + " + `) }) }) describe('theme overrides order', () => { test('user theme > js config > default theme', async () => { - expect( - await run( - ['bg-red', 'bg-blue'], - css` - @theme default { - --color-red: red; - } - @theme { - --color-blue: blue; - } - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - colors: { - red: 'very-red', - blue: 'very-blue', - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --color-red: red; + } + @theme { --color-blue: blue; } + @tailwind utilities; + @config "./config.js"; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + colors: { + red: 'very-red', + blue: 'very-blue', + }, + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['bg-red', 'bg-blue'])).toMatchInlineSnapshot(` + ":root, :host { + --color-blue: blue; + } .bg-blue { background-color: var(--color-blue); } - .bg-red { background-color: very-red; } @@ -477,134 +409,131 @@ describe('theme overrides order', () => { }) test('user theme > js config > default theme (with nested object)', async () => { - expect( - await run( - [ - 'bg-slate-100', - 'bg-slate-200', - 'bg-slate-300', - 'bg-slate-400', - 'bg-slate-500', - 'bg-slate-600', - 'hover-bg-slate-100', - 'hover-bg-slate-200', - 'hover-bg-slate-300', - 'hover-bg-slate-400', - 'hover-bg-slate-500', - 'hover-bg-slate-600', - ], - css` - @theme default { - --color-slate-100: #000100; - --color-slate-200: #000200; - --color-slate-300: #000300; - } - @theme { - --color-slate-400: #100400; - --color-slate-500: #100500; - } - @tailwind utilities; - @config "./config.js"; - @plugin "./plugin.js"; - `, - { - loadModule: async (id) => { - if (id.includes('config.js')) { - return { - module: { - theme: { - extend: { - colors: { - slate: { - 200: '#200200', - 400: '#200400', - 600: '#200600', - }, - }, + let input = css` + @theme default { + --color-slate-100: #000100; + --color-slate-200: #000200; + --color-slate-300: #000300; + } + @theme { + --color-slate-400: #100400; + --color-slate-500: #100500; + } + @tailwind utilities; + @config "./config.js"; + @plugin "./plugin.js"; + ` + + let compiler = await compile(input, { + loadModule: async (id) => { + if (id.includes('config.js')) { + return { + module: { + theme: { + extend: { + colors: { + slate: { + 200: '#200200', + 400: '#200400', + 600: '#200600', }, }, - } satisfies Config, - base: '/root', - path: '', - } - } else { - return { - module: plugin(({ matchUtilities, theme }) => { - matchUtilities( - { - 'hover-bg': (value) => { - return { - '&:hover': { - backgroundColor: value, - }, - } + }, + }, + } satisfies Config, + base: '/root', + } + } else { + return { + module: plugin(({ matchUtilities, theme }) => { + matchUtilities( + { + 'hover-bg': (value) => { + return { + '&:hover': { + backgroundColor: value, }, - }, - { values: flattenColorPalette(theme('colors')) }, - ) - }), - base: '/root', - path: '', - } - } - }, - }, - ), + } + }, + }, + { values: flattenColorPalette(theme('colors')) }, + ) + }), + base: '/root', + } + } + }, + }) + + expect( + compiler.build([ + 'bg-slate-100', + 'bg-slate-200', + 'bg-slate-300', + 'bg-slate-400', + 'bg-slate-500', + 'bg-slate-600', + 'hover-bg-slate-100', + 'hover-bg-slate-200', + 'hover-bg-slate-300', + 'hover-bg-slate-400', + 'hover-bg-slate-500', + 'hover-bg-slate-600', + ]), ).toMatchInlineSnapshot(` - " - :root, :host { + ":root, :host { --color-slate-100: #000100; --color-slate-300: #000300; --color-slate-400: #100400; --color-slate-500: #100500; } - .bg-slate-100 { background-color: var(--color-slate-100); } - .bg-slate-200 { background-color: #200200; } - .bg-slate-300 { background-color: var(--color-slate-300); } - .bg-slate-400 { background-color: var(--color-slate-400); } - .bg-slate-500 { background-color: var(--color-slate-500); } - .bg-slate-600 { background-color: #200600; } - - .hover-bg-slate-100:hover { - background-color: #000100; + .hover-bg-slate-100 { + &:hover { + background-color: #000100; + } } - - .hover-bg-slate-200:hover { - background-color: #200200; + .hover-bg-slate-200 { + &:hover { + background-color: #200200; + } } - - .hover-bg-slate-300:hover { - background-color: #000300; + .hover-bg-slate-300 { + &:hover { + background-color: #000300; + } } - - .hover-bg-slate-400:hover { - background-color: #100400; + .hover-bg-slate-400 { + &:hover { + background-color: #100400; + } } - - .hover-bg-slate-500:hover { - background-color: #100500; + .hover-bg-slate-500 { + &:hover { + background-color: #100500; + } } - - .hover-bg-slate-600:hover { - background-color: #200600; + .hover-bg-slate-600 { + &:hover { + background-color: #200600; + } } " `) @@ -613,35 +542,31 @@ describe('theme overrides order', () => { describe('default font family compatibility', () => { test('overriding `fontFamily.sans` sets `--default-font-family`', async () => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: 'Potato Sans', - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: 'Potato Sans', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; } " @@ -651,37 +576,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans[1].fontFeatureSettings` sets `--default-font-feature-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }], - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Potato Sans', { fontFeatureSettings: '"cv06"' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-feature-settings: "cv06"; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-feature-settings: "cv06"; } " `) @@ -690,37 +611,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans[1].fontVariationSettings` sets `--default-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }], - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Potato Sans', { fontVariationSettings: '"XHGT" 0.7' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-variation-settings: "XHGT" 0.7; } " `) @@ -729,41 +646,37 @@ describe('default font family compatibility', () => { test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.sans` sets `--default-font-feature-settings` and `--default-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: [ - 'Potato Sans', - { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, - ], - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: [ + 'Potato Sans', + { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, + ], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { - font-feature-settings: "cv06"; - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Potato Sans; + font-feature-settings: "cv06"; + font-variation-settings: "XHGT" 0.7; } " `) @@ -772,41 +685,36 @@ describe('default font family compatibility', () => { test('overriding `--font-family-sans` in `@theme` without `default` preserves the original `--default-font-*` values', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @theme { - --font-sans: Sandwich Sans; - } - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: 'Potato Sans', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @theme { --font-sans: Sandwich Sans; } + @tailwind utilities; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: 'Potato Sans', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ":root, :host { + --font-sans: Sandwich Sans; + } .font-sans { font-family: var(--font-sans); } @@ -817,35 +725,31 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans` in a config file with an array sets `--default-font-family`', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: ['Inter', 'system-ui', 'sans-serif'], - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-sans { + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(` + ".font-sans { font-family: Inter, system-ui, sans-serif; } " @@ -855,67 +759,58 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.sans` in a config file with an unexpected type is ignored', async ({ expect, }) => { - expect( - await run( - ['font-sans'], - css` - @theme default { - --default-font-family: var(--font-family-sans); - --default-font-feature-settings: var(--font-family-sans--font-feature-settings); - --default-font-variation-settings: var(--font-family-sans--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - sans: { foo: 'bar', banana: 'sandwich' }, - }, - }, + let input = css` + @theme default { + --default-font-family: var(--font-family-sans); + --default-font-feature-settings: var(--font-family-sans--font-feature-settings); + --default-font-variation-settings: var(--font-family-sans--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + sans: { foo: 'bar', banana: 'sandwich' }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toEqual('') + base: '/root', + }), + }) + + expect(compiler.build(['font-sans'])).toMatchInlineSnapshot(`""`) }) test('overriding `fontFamily.mono` sets `--default-mono-font-family`', async () => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-family-mono); - --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); - --default-mono-font-variation-settings: var( - --font-family-mono--font-variation-settings - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: 'Potato Mono', - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-family-mono); + --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: 'Potato Mono', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; } " @@ -925,39 +820,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono[1].fontFeatureSettings` sets `--default-mono-font-feature-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-family-mono); - --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); - --default-mono-font-variation-settings: var( - --font-family-mono--font-variation-settings - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }], - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-family-mono); + --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: ['Potato Mono', { fontFeatureSettings: '"cv06"' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-feature-settings: "cv06"; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-feature-settings: "cv06"; } " `) @@ -966,39 +855,33 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono[1].fontVariationSettings` sets `--default-mono-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-family-mono); - --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); - --default-mono-font-variation-settings: var( - --font-family-mono--font-variation-settings - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }], - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-family-mono); + --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: ['Potato Mono', { fontVariationSettings: '"XHGT" 0.7' }], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-variation-settings: "XHGT" 0.7; } " `) @@ -1007,41 +890,37 @@ describe('default font family compatibility', () => { test('overriding `fontFeatureSettings` and `fontVariationSettings` for `fontFamily.mono` sets `--default-mono-font-feature-settings` and `--default-mono-font-variation-settings`', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: [ - 'Potato Mono', - { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, - ], - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: [ + 'Potato Mono', + { fontFeatureSettings: '"cv06"', fontVariationSettings: '"XHGT" 0.7' }, + ], }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .font-mono { - font-feature-settings: "cv06"; - font-variation-settings: "XHGT" .7; + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ".font-mono { font-family: Potato Mono; + font-feature-settings: "cv06"; + font-variation-settings: "XHGT" 0.7; } " `) @@ -1050,41 +929,36 @@ describe('default font family compatibility', () => { test('overriding `--font-family-mono` in `@theme` without `default` preserves the original `--default-mono-font-*` values', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-mono); - --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); - --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); - } - @config "./config.js"; - @theme { - --font-mono: Sandwich Mono; - } - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: 'Potato Mono', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + let input = css` + @theme default { + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + } + @config "./config.js"; + @theme { --font-mono: Sandwich Mono; } + @tailwind utilities; + ` + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: 'Potato Mono', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(` + ":root, :host { + --font-mono: Sandwich Mono; + } .font-mono { font-family: var(--font-mono); } @@ -1095,117 +969,115 @@ describe('default font family compatibility', () => { test('overriding `fontFamily.mono` in a config file with an unexpected type is ignored', async ({ expect, }) => { - expect( - await run( - ['font-mono'], - css` - @theme default { - --default-mono-font-family: var(--font-family-mono); - --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); - --default-mono-font-variation-settings: var( - --font-family-mono--font-variation-settings - ); - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - fontFamily: { - mono: { foo: 'bar', banana: 'sandwich' }, - }, - }, + let input = css` + @theme default { + --default-mono-font-family: var(--font-family-mono); + --default-mono-font-feature-settings: var(--font-family-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-family-mono--font-variation-settings); + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + fontFamily: { + mono: { foo: 'bar', banana: 'sandwich' }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toEqual('') + base: '/root', + }), + }) + + expect(compiler.build(['font-mono'])).toMatchInlineSnapshot(`""`) }) }) test('creates variants for `data`, `supports`, and `aria` theme options at the same level as the core utility ', async () => { - expect( - await run( - [ - 'aria-polite:underline', - 'supports-child-combinator:underline', - 'supports-foo:underline', - 'data-checked:underline', + let input = css` + @tailwind utilities; + @config "./config.js"; + ` - // Ensure core variants still work - 'aria-hidden:flex', - 'supports-grid:flex', - 'data-foo:flex', - - // The `print` variant should still be sorted last, even after registering - // the other custom variants. - 'print:flex', - ], - css` - @tailwind utilities; - @config "./config.js"; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - aria: { - polite: 'live="polite"', - }, - supports: { - 'child-combinator': 'selector(h2 > p)', - foo: 'bar', - }, - data: { - checked: 'ui~="checked"', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + aria: { + polite: 'live="polite"', + }, + supports: { + 'child-combinator': 'selector(h2 > p)', + foo: 'bar', + }, + data: { + checked: 'ui~="checked"', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'aria-polite:underline', + 'supports-child-combinator:underline', + 'supports-foo:underline', + 'data-checked:underline', + + // Ensure core variants still work + 'aria-hidden:flex', + 'supports-grid:flex', + 'data-foo:flex', + + // The `print` variant should still be sorted last, even after registering + // the other custom variants. + 'print:flex', + ]), ).toMatchInlineSnapshot(` - " - .aria-hidden\\:flex[aria-hidden="true"] { - display: flex; - } - - .aria-polite\\:underline[aria-live="polite"], .data-checked\\:underline[data-ui~="checked"] { - text-decoration-line: underline; - } - - .data-foo\\:flex[data-foo] { - display: flex; - } - - @supports selector(h2 > p) { - .supports-child-combinator\\:underline { - text-decoration-line: underline; - } - } - - @supports (bar: var(--tw)) { - .supports-foo\\:underline { - text-decoration-line: underline; - } - } - - @supports (grid: var(--tw)) { - .supports-grid\\:flex { + ".aria-hidden\\:flex { + &[aria-hidden="true"] { display: flex; } } - - @media print { - .print\\:flex { + .aria-polite\\:underline { + &[aria-live="polite"] { + text-decoration-line: underline; + } + } + .data-checked\\:underline { + &[data-ui~="checked"] { + text-decoration-line: underline; + } + } + .data-foo\\:flex { + &[data-foo] { + display: flex; + } + } + .supports-child-combinator\\:underline { + @supports selector(h2 > p) { + text-decoration-line: underline; + } + } + .supports-foo\\:underline { + @supports (bar: var(--tw)) { + text-decoration-line: underline; + } + } + .supports-grid\\:flex { + @supports (grid: var(--tw)) { + display: flex; + } + } + .print\\:flex { + @media print { display: flex; } } @@ -1214,66 +1086,62 @@ test('creates variants for `data`, `supports`, and `aria` theme options at the s }) test('merges css breakpoints with js config screens', async () => { - expect( - await run( - ['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '44rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '44rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - @media (min-width: 44rem) { - .sm\\:flex { - display: flex; - } + base: '/root', + }), + }) - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { - text-decoration-line: underline; + expect(compiler.build(['sm:flex', 'md:flex', 'lg:flex', 'min-sm:max-md:underline'])) + .toMatchInlineSnapshot(` + ".sm\\:flex { + @media (width >= 44rem) { + display: flex; + } + } + .min-sm\\:max-md\\:underline { + @media (width >= 44rem) { + @media (width < 50rem) { + text-decoration-line: underline; + } } } - } - - @media (min-width: 50rem) { .md\\:flex { - display: flex; + @media (width >= 50rem) { + display: flex; + } } - } - - @media (min-width: 64rem) { .lg\\:flex { - display: flex; + @media (width >= 64rem) { + display: flex; + } } - } - " - `) + " + `) }) test('utilities must be prefixed', async () => { @@ -1286,60 +1154,66 @@ test('utilities must be prefixed', async () => { } ` - let options: Partial[1]> = { - loadModule: async (_id, base) => ({ + let compiler = await compile(input, { + loadModule: async (id, base) => ({ path: '', base, module: { prefix: 'tw' }, }), - } + }) // Prefixed utilities are generated - expect(await run(['tw:underline', 'tw:hover:line-through', 'tw:custom'], input, options)) + expect(compiler.build(['tw:underline', 'tw:hover:line-through', 'tw:custom'])) .toMatchInlineSnapshot(` - " - .tw\\:custom { + ".tw\\:custom { color: red; } - .tw\\:underline { text-decoration-line: underline; } - - @media (hover: hover) { - .tw\\:hover\\:line-through:hover { - text-decoration-line: line-through; + .tw\\:hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through; + } } } " `) // Non-prefixed utilities are ignored - expect(await run(['underline', 'hover:line-through', 'custom'], input, options)).toEqual('') + compiler = await compile(input, { + loadModule: async (id, base) => ({ + path: '', + base, + module: { prefix: 'tw' }, + }), + }) + + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toEqual('') }) test('utilities used in @apply must be prefixed', async () => { - // Prefixed utilities are generated - expect( - await compileCss( - css` - @config "./config.js"; + let compiler = await compile( + css` + @config "./config.js"; - .my-underline { - @apply tw:underline; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { prefix: 'tw' }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .my-underline { + .my-underline { + @apply tw:underline; + } + `, + { + loadModule: async (id, base) => ({ + path: '', + base, + module: { prefix: 'tw' }, + }), + }, + ) + + // Prefixed utilities are generated + expect(compiler.build([])).toMatchInlineSnapshot(` + ".my-underline { text-decoration-line: underline; } " @@ -1349,7 +1223,6 @@ test('utilities used in @apply must be prefixed', async () => { await expect( compile( css` - @tailwind utilities; @config "./config.js"; .my-underline { @@ -1357,7 +1230,7 @@ test('utilities used in @apply must be prefixed', async () => { } `, { - loadModule: async (_id, base) => ({ + loadModule: async (id, base) => ({ path: '', base, module: { prefix: 'tw' }, @@ -1365,42 +1238,40 @@ test('utilities used in @apply must be prefixed', async () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Cannot apply unprefixed utility class \`underline\`. Did you mean \`tw:underline\`?]`, + `[Error: Cannot apply unknown utility class: underline]`, ) }) test('Prefixes configured in CSS take precedence over those defined in JS configs', async () => { - expect( - await run( - ['wat:custom'], - css` - @theme prefix(wat) { - --color-red: #f00; - --color-green: #0f0; - --breakpoint-sm: 640px; + let compiler = await compile( + css` + @theme prefix(wat) { + --color-red: #f00; + --color-green: #0f0; + --breakpoint-sm: 640px; + } + + @config "./plugin.js"; + + @tailwind utilities; + + @utility custom { + color: red; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: { prefix: 'tw' }, } - - @config "./plugin.js"; - - @tailwind utilities; - - @utility custom { - color: red; - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: { prefix: 'tw' }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .wat\\:custom { + }, + ) + + expect(compiler.build(['wat:custom'])).toMatchInlineSnapshot(` + ".wat\\:custom { color: red; } " @@ -1414,7 +1285,7 @@ test('a prefix must be letters only', async () => { @config "./plugin.js"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -1429,38 +1300,37 @@ test('a prefix must be letters only', async () => { }) test('important: `#app`', async () => { - expect( - await run( - ['underline', 'hover:line-through', 'custom'], - css` - @tailwind utilities; - @config "./config.js"; + let input = css` + @tailwind utilities; + @config "./config.js"; - @utility custom { - color: red; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { important: '#app' }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - #app .custom { + @utility custom { color: red; } + ` - #app .underline { - text-decoration-line: underline; - } + let compiler = await compile(input, { + loadModule: async (_, base) => ({ + path: '', + base, + module: { important: '#app' }, + }), + }) - @media (hover: hover) { - #app .hover\\:line-through:hover { - text-decoration-line: line-through; + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(` + "#app { + .custom { + color: red; + } + .underline { + text-decoration-line: underline; + } + .hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through; + } + } } } " @@ -1468,38 +1338,35 @@ test('important: `#app`', async () => { }) test('important: true', async () => { - expect( - await run( - ['underline', 'hover:line-through', 'custom'], - css` - @tailwind utilities; - @config "./config.js"; + let input = css` + @tailwind utilities; + @config "./config.js"; - @utility custom { - color: red; - } - `, - { - loadModule: async (_id, base) => ({ - path: '', - base, - module: { important: true }, - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .custom { + @utility custom { + color: red; + } + ` + + let compiler = await compile(input, { + loadModule: async (_, base) => ({ + path: '', + base, + module: { important: true }, + }), + }) + + expect(compiler.build(['underline', 'hover:line-through', 'custom'])).toMatchInlineSnapshot(` + ".custom { color: red !important; } - .underline { text-decoration-line: underline !important; } - - @media (hover: hover) { - .hover\\:line-through:hover { - text-decoration-line: line-through !important; + .hover\\:line-through { + &:hover { + @media (hover: hover) { + text-decoration-line: line-through !important; + } } } " @@ -1507,45 +1374,43 @@ test('important: true', async () => { }) test('blocklisted candidates are not generated', async () => { - let input = css` - @theme reference { - --color-white: #fff; - --breakpoint-md: 48rem; - } - @tailwind utilities; - @config "./config.js"; - ` - - let options: Partial[1]> = { - async loadModule(_id, base) { - return { - path: '', - base, - module: { - blocklist: ['bg-white'], - }, + let compiler = await compile( + css` + @theme reference { + --color-white: #fff; + --breakpoint-md: 48rem; } + @tailwind utilities; + @config "./config.js"; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: { + blocklist: ['bg-white'], + }, + } + }, }, - } + ) // bg-white will not get generated - expect(await run(['bg-white'], input, options)).toEqual('') + expect(compiler.build(['bg-white'])).toEqual('') // underline will as will md:bg-white - expect(await run(['underline', 'bg-white', 'md:bg-white'], input, options)) - .toMatchInlineSnapshot(` - " - .underline { - text-decoration-line: underline; + expect(compiler.build(['underline', 'bg-white', 'md:bg-white'])).toMatchInlineSnapshot(` + ".underline { + text-decoration-line: underline; + } + .md\\:bg-white { + @media (width >= 48rem) { + background-color: var(--color-white, #fff); } - - @media (min-width: 48rem) { - .md\\:bg-white { - background-color: var(--color-white, #fff); - } - } - " - `) + } + " + `) }) test('blocklisted candidates cannot be used with `@apply`', async () => { @@ -1563,7 +1428,7 @@ test('blocklisted candidates cannot be used with `@apply`', async () => { } `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -1575,12 +1440,12 @@ test('blocklisted candidates cannot be used with `@apply`', async () => { }, ), ).rejects.toThrowErrorMatchingInlineSnapshot( - `[Error: Cannot apply utility class \`bg-white\` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes]`, + `[Error: Cannot apply unknown utility class: bg-white]`, ) }) test('old theme values are merged with their renamed counterparts in the CSS theme', async () => { - using didCallPluginFn = vi.fn() + let didCallPluginFn = vi.fn() await compile( css` @@ -1616,7 +1481,7 @@ test('old theme values are merged with their renamed counterparts in the CSS the @plugin "./plugin.js"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -1704,146 +1569,52 @@ test('old theme values are merged with their renamed counterparts in the CSS the }) test('handles setting theme keys to null', async () => { - expect( - await run( - ['bg-red-50', 'bg-red-100', 'bg-red-200'], - css` - @theme default { - --color-red-50: oklch(0.971 0.013 17.38); - --color-red-100: oklch(0.936 0.032 17.717); - } - @config "./my-config.js"; - @tailwind utilities; - @theme { - --color-red-100: oklch(0.936 0.032 17.717); - --color-red-200: oklch(0.885 0.062 18.334); - } - `, - { - loadModule: async () => { - return { - module: { - theme: { - extend: { - colors: { - red: null, - }, + let compiler = await compile( + css` + @theme default { + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + } + @config "./my-config.js"; + @tailwind utilities; + @theme { + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + } + `, + { + loadModule: async () => { + return { + module: { + theme: { + extend: { + colors: { + red: null, }, }, }, - base: '/root', - path: '', - } - }, + }, + base: '/root', + } }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --color-red-50: oklch(97.1% .013 17.38); - --color-red-100: oklch(93.6% .032 17.717); - --color-red-200: oklch(88.5% .062 18.334); - } + }, + ) + expect(compiler.build(['bg-red-50', 'bg-red-100', 'bg-red-200'])).toMatchInlineSnapshot(` + ":root, :host { + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + } .bg-red-50 { background-color: var(--color-red-50); } - .bg-red-100 { background-color: var(--color-red-100); } - .bg-red-200 { background-color: var(--color-red-200); } " `) }) - -test('The theme() function does not try indexing into strings', async () => { - expect( - await compileCss(css` - @theme default { - --color-what-50: #f00; - --color-what-950: #f00; - } - - @theme { - /* - * The value of this theme variable is > 50 chars because colors.what.50 was previously - * indexing the string: "light-dark(theme(colors.what.950), theme(colors.what.50))" - * because the resolved config contained an object with a "what" key that was that string - */ - --color-what: light-dark(theme(colors.what.950), theme(colors.what.50)); - } - - @source inline("text-what"); - - @tailwind utilities; - `), - ).toMatchInlineSnapshot(` - " - :root, :host { - --color-what: red; - } - - .text-what { - color: var(--color-what); - } - " - `) -}) - -test('camel case keys are preserved', async () => { - expect( - await run( - [ - // From CSS - 'bg-blue-green', // should be output - 'bg-blueGreen', // should not - - // From JS config - 'bg-light-green', // should not be output - 'bg-lightGreen', // should be - ], - css` - @tailwind utilities; - @theme { - --color-blue-green: slate; - } - @config "./plugin.js"; - `, - { - loadModule: async () => { - return { - base: '/', - path: '', - module: { - theme: { - extend: { - backgroundColor: { - lightGreen: '#c0ffee', - }, - }, - }, - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .bg-blue-green { - background-color: var(--color-blue-green); - } - - .bg-lightGreen { - background-color: #c0ffee; - } - - :root, :host { - --color-blue-green: slate; - } - " - `) -}) diff --git a/packages/tailwindcss/src/compat/config/create-compat-config.test.ts b/packages/tailwindcss/src/compat/config/create-compat-config.test.ts deleted file mode 100644 index aa1b21cb7..000000000 --- a/packages/tailwindcss/src/compat/config/create-compat-config.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { buildDesignSystem } from '../../design-system' -import { Theme } from '../../theme' -import { createCompatConfig } from './create-compat-config' -import { resolveConfig } from './resolve-config' - -function buildCompatConfig(cssValues: Record) { - let theme = new Theme() - for (let [key, value] of Object.entries(cssValues)) { - theme.add(key, value) - } - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { config: createCompatConfig(design.theme), base: '/root', reference: true, src: undefined }, - ]) - - return resolvedConfig -} - -describe('theme namespace lookups', () => { - test('a namespace that only defines a bare value resolves to `{ DEFAULT: … }`, not a string', () => { - // When the CSS theme only defines `--text` (a single bare value), - // `theme('text', {})` used to return the string `'1rem'`. - // - // Spreading that string, like the compat config does, produced char-indexed - // garbage (`{ '0': '1', '1': 'r', … }`) instead of `{ DEFAULT: '1rem' }`. - let config = buildCompatConfig({ '--text': '1rem' }) - - expect(config.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(config.theme?.fontSize).not.toHaveProperty('0') - }) - - test('a bare value is kept alongside suffixed values in the same namespace', () => { - let config = buildCompatConfig({ - '--text': '1rem', - '--text-sm': '0.875rem', - }) - - expect(config.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(config.theme?.fontSize?.sm).toBe('0.875rem') - }) - - test('a naive spread of a theme namespace in a user config is safe', () => { - let theme = new Theme() - theme.add('--text', '1rem') - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { - config: { - theme: { - extend: { - fontSize: ({ theme }: any) => ({ ...theme('text', {}) }), - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - expect(resolvedConfig.theme?.fontSize?.DEFAULT).toBe('1rem') - expect(resolvedConfig.theme?.fontSize).not.toHaveProperty('0') - }) - - test('namespace root lookups resolve to an object, key lookups resolve to the value', () => { - let theme = new Theme() - theme.add('--text', '1rem') - theme.add('--animate-spin', 'spin 1s linear infinite') - let design = buildDesignSystem(theme) - - let root: unknown - let rootWithDefault: unknown - let leaf: unknown - - resolveConfig(design, [ - { - config: { - theme: { - extend: { - fontSize: ({ theme }: any) => { - root = theme('text') - rootWithDefault = theme('text', {}) - leaf = theme('animate.spin') - return {} - }, - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - // A namespace root lookup always resolves to an object, like in v3, even - // when the namespace only contains a bare value - expect(root).toMatchObject({ DEFAULT: '1rem' }) - expect(rootWithDefault).toMatchObject({ DEFAULT: '1rem' }) - - // Lookups of a specific key resolve to the value itself - expect(leaf).toBe('spin 1s linear infinite') - }) - - test('lookups of a specific key still resolve to the value itself', () => { - let theme = new Theme() - theme.add('--animate-spin', 'spin 1s linear infinite') - let design = buildDesignSystem(theme) - - let { resolvedConfig } = resolveConfig(design, [ - { - config: { - theme: { - extend: { - animation: ({ theme }: any) => ({ spin: theme('animate.spin') }), - }, - }, - }, - base: '/root', - reference: true, - src: undefined, - }, - ]) - - expect(resolvedConfig.theme?.animation?.spin).toBe('spin 1s linear infinite') - }) -}) - -describe('createCompatConfig namespace mappings', () => { - test('fontSize maps from CSS text namespace', () => { - let config = buildCompatConfig({ '--text-base': '1rem' }) - expect(config.theme?.fontSize?.base).toBe('1rem') - }) - - test('boxShadow maps from CSS shadow namespace', () => { - let config = buildCompatConfig({ '--shadow-md': '0 4px 6px #000' }) - expect(config.theme?.boxShadow?.md).toBe('0 4px 6px #000') - }) - - test('animation maps from CSS animate namespace', () => { - let config = buildCompatConfig({ '--animate-spin': 'spin 1s linear infinite' }) - expect(config.theme?.animation?.spin).toBe('spin 1s linear infinite') - }) - - test('aspectRatio maps from CSS aspect namespace', () => { - let config = buildCompatConfig({ '--aspect-square': '1 / 1' }) - expect(config.theme?.aspectRatio?.square).toBe('1 / 1') - }) - - test('borderRadius maps from CSS radius namespace', () => { - let config = buildCompatConfig({ '--radius-lg': '0.5rem' }) - expect(config.theme?.borderRadius?.lg).toBe('0.5rem') - }) - - test('screens maps from CSS breakpoint namespace', () => { - let config = buildCompatConfig({ '--breakpoint-sm': '640px' }) - expect(config.theme?.screens?.sm).toBe('640px') - }) - - test('letterSpacing maps from CSS tracking namespace', () => { - let config = buildCompatConfig({ '--tracking-wide': '0.025em' }) - expect(config.theme?.letterSpacing?.wide).toBe('0.025em') - }) - - test('lineHeight maps from CSS leading namespace', () => { - let config = buildCompatConfig({ '--leading-relaxed': '1.75' }) - expect(config.theme?.lineHeight?.relaxed).toBe('1.75') - }) - - test('maxWidth maps from the container namespace in the default theme', () => { - let config = buildCompatConfig({}) - // The default theme has container.sm = '24rem' - expect(config.theme?.maxWidth?.sm).toBe('24rem') - }) - - test('transitionDuration gets DEFAULT from --default-transition-duration', () => { - let config = buildCompatConfig({ '--default-transition-duration': '150ms' }) - expect(config.theme?.transitionDuration?.DEFAULT).toBe('150ms') - }) - - test('transitionTimingFunction gets DEFAULT from --default-transition-timing-function', () => { - let config = buildCompatConfig({ - '--default-transition-timing-function': 'cubic-bezier(0.4, 0, 0.2, 1)', - }) - expect(config.theme?.transitionTimingFunction?.DEFAULT).toBe('cubic-bezier(0.4, 0, 0.2, 1)') - }) -}) diff --git a/packages/tailwindcss/src/compat/config/resolve-config.test.ts b/packages/tailwindcss/src/compat/config/resolve-config.test.ts index d8cc21a10..e47fa4eda 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.test.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.test.ts @@ -20,8 +20,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -32,8 +30,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -44,8 +40,6 @@ test('top level theme keys are replaced', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -79,8 +73,6 @@ test('theme can be extended', () => { }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -93,8 +85,6 @@ test('theme can be extended', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -130,8 +120,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -145,8 +133,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, { config: { @@ -159,8 +145,6 @@ test('theme keys can reference other theme keys using the theme function regardl }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -228,8 +212,6 @@ test('theme keys can read from the CSS theme', () => { }, }, base: '/root', - reference: false, - src: undefined, }, ]) @@ -292,7 +274,6 @@ test('handles null as theme values', () => { }, base: '/root', reference: false, - src: undefined, }, { config: { @@ -306,7 +287,6 @@ test('handles null as theme values', () => { }, base: '/root', reference: false, - src: undefined, }, ]) diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts index 858f4bbbe..6801c7b60 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.ts @@ -1,5 +1,4 @@ import type { DesignSystem } from '../../design-system' -import type { SourceLocation } from '../../source-maps/source' import colors from '../colors' import type { PluginWithConfig } from '../plugin-api' import { createThemeFn } from '../plugin-functions' @@ -17,7 +16,6 @@ export interface ConfigFile { base: string config: UserConfig reference: boolean - src: SourceLocation | undefined } interface ResolutionContext { @@ -33,7 +31,6 @@ interface ResolutionContext { let minimal: ResolvedConfig = { blocklist: [], future: {}, - experimental: {}, prefix: '', important: false, darkMode: null, @@ -134,7 +131,7 @@ export type PluginUtils = { function extractConfigs( ctx: ResolutionContext, - { config, base, path, reference, src }: ConfigFile, + { config, base, path, reference }: ConfigFile, ): void { let plugins: PluginWithConfig[] = [] @@ -143,17 +140,17 @@ function extractConfigs( if ('__isOptionsFunction' in plugin) { // Happens with `plugin.withOptions()` when no options were passed: // e.g. `require("my-plugin")` instead of `require("my-plugin")(options)` - plugins.push({ ...plugin(), reference, src }) + plugins.push({ ...plugin(), reference }) } else if ('handler' in plugin) { // Happens with `plugin(…)`: // e.g. `require("my-plugin")` // // or with `plugin.withOptions()` when the user passed options: // e.g. `require("my-plugin")(options)` - plugins.push({ ...plugin, reference, src }) + plugins.push({ ...plugin, reference }) } else { // Just a plain function without using the plugin(…) API - plugins.push({ handler: plugin, reference, src }) + plugins.push({ handler: plugin, reference }) } } @@ -165,7 +162,7 @@ function extractConfigs( } for (let preset of config.presets ?? []) { - extractConfigs(ctx, { path, base, config: preset, reference, src }) + extractConfigs(ctx, { path, base, config: preset, reference }) } // Apply configs from plugins @@ -173,13 +170,7 @@ function extractConfigs( ctx.plugins.push(plugin) if (plugin.config) { - extractConfigs(ctx, { - path, - base, - config: plugin.config, - reference: !!plugin.reference, - src: plugin.src ?? src, - }) + extractConfigs(ctx, { path, base, config: plugin.config, reference: !!plugin.reference }) } } diff --git a/packages/tailwindcss/src/compat/config/types.ts b/packages/tailwindcss/src/compat/config/types.ts index 76851a397..dbcb30de3 100644 --- a/packages/tailwindcss/src/compat/config/types.ts +++ b/packages/tailwindcss/src/compat/config/types.ts @@ -52,7 +52,7 @@ type DarkModeStrategy = // Use the `class` strategy with a custom class instead of `.dark`. | ['class', string] - // Use the `selector` strategy — same as `class` but uses `:where()` for more predictable behavior + // Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior | 'selector' // Use the `selector` strategy with a custom selector instead of `.dark`. @@ -105,12 +105,3 @@ export interface UserConfig { export interface ResolvedConfig { future: Record } - -// `experimental` key support -export interface UserConfig { - experimental?: 'all' | Record -} - -export interface ResolvedConfig { - experimental: Record -} diff --git a/packages/tailwindcss/src/compat/container-config.test.ts b/packages/tailwindcss/src/compat/container-config.test.ts index 39a12557c..b5618de84 100644 --- a/packages/tailwindcss/src/compat/container-config.test.ts +++ b/packages/tailwindcss/src/compat/container-config.test.ts @@ -1,74 +1,54 @@ import { expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compile } from '..' const css = String.raw test('creates a custom utility to extend the built-in container', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: '2rem', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; } + @config "./config.js"; + @tailwind utilities; + ` - @media (min-width: 40rem) { - .container { + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: '2rem', + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { margin-inline: auto; padding-inline: 2rem; @@ -78,87 +58,61 @@ test('creates a custom utility to extend the built-in container', async () => { }) test('allows padding to be defined at custom breakpoints', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - // The order here is messed up on purpose - '2xl': '3rem', - DEFAULT: '1rem', - lg: '2rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + // The order here is messed up on purpose + '2xl': '3rem', + DEFAULT: '1rem', + lg: '2rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { padding-inline: 1rem; - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { padding-inline: 2rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { padding-inline: 3rem; } } @@ -167,87 +121,61 @@ test('allows padding to be defined at custom breakpoints', async () => { }) test('allows breakpoints to be overwritten', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - screens: { - xl: '1280px', - '2xl': '1536px', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + screens: { + xl: '1280px', + '2xl': '1536px', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - - @media (min-width: 40rem) { - .container { + .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 1280px) { - .container { + @media (width >= 1280px) { max-width: 1280px; } - } - - @media (min-width: 1536px) { - .container { + @media (width >= 1536px) { max-width: 1536px; } } @@ -256,84 +184,61 @@ test('allows breakpoints to be overwritten', async () => { }) test('padding applies to custom `container` screens', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - sm: '2rem', - md: '3rem', - }, - screens: { - md: '48rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + sm: '2rem', + md: '3rem', + }, + screens: { + md: '48rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - - @media (min-width: 40rem) { - .container { + .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; padding-inline: 3rem; } @@ -343,81 +248,58 @@ test('padding applies to custom `container` screens', async () => { }) test("an empty `screen` config will undo all custom media screens and won't apply any breakpoint-specific padding", async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - padding: { - DEFAULT: '1rem', - sm: '2rem', - md: '3rem', - }, - screens: {}, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; } + @config "./config.js"; + @tailwind utilities; + ` - @media (min-width: 40rem) { - .container { + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + padding: { + DEFAULT: '1rem', + sm: '2rem', + md: '3rem', + }, + screens: {}, + }, + }, + }, + base: '/root', + }), + }) + + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { padding-inline: 1rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } } @@ -426,50 +308,45 @@ test("an empty `screen` config will undo all custom media screens and won't appl }) test('legacy container component does not interfere with new --container variables', async () => { - expect( - await run( - ['max-w-sm'], - css` - @theme default { - --container-3xs: 16rem; - --container-2xs: 18rem; - --container-xs: 20rem; - --container-sm: 24rem; - --container-md: 28rem; - --container-lg: 32rem; - --container-xl: 36rem; - --container-2xl: 42rem; - --container-3xl: 48rem; - --container-4xl: 56rem; - --container-5xl: 64rem; - --container-6xl: 72rem; - --container-7xl: 80rem; - --container-prose: 65ch; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: '2rem', - }, - }, + let input = css` + @theme default { + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + --container-prose: 65ch; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: '2rem', }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { + base: '/root', + }), + }) + + expect(compiler.build(['max-w-sm'])).toMatchInlineSnapshot(` + ":root, :host { --container-sm: 24rem; } - .max-w-sm { max-width: var(--container-sm); } @@ -478,168 +355,107 @@ test('legacy container component does not interfere with new --container variabl }) test('combines custom padding and screen overwrites', async () => { - expect( - await run( - ['container', '!container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: { - DEFAULT: '2rem', - '2xl': '4rem', - }, - screens: { - md: '48rem', // Matches a default --breakpoint - xl: '1280px', - '2xl': '1536px', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: { + DEFAULT: '2rem', + '2xl': '4rem', + }, + screens: { + md: '48rem', // Matches a default --breakpoint + xl: '1280px', + '2xl': '1536px', }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .\\!container { - width: 100% !important; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .\\!container { + expect(compiler.build(['container', '!container'])).toMatchInlineSnapshot(` + ".\\!container { + width: 100% !important; + @media (width >= 40rem) { max-width: 40rem !important; } - } - - @media (min-width: 48rem) { - .\\!container { + @media (width >= 48rem) { max-width: 48rem !important; } - } - - @media (min-width: 64rem) { - .\\!container { + @media (width >= 64rem) { max-width: 64rem !important; } - } - - @media (min-width: 80rem) { - .\\!container { + @media (width >= 80rem) { max-width: 80rem !important; } - } - - @media (min-width: 96rem) { - .\\!container { + @media (width >= 96rem) { max-width: 96rem !important; } } - .container { width: 100%; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .\\!container { margin-inline: auto !important; padding-inline: 2rem !important; - } - - @media (min-width: 40rem) { - .\\!container { + @media (width >= 40rem) { max-width: none !important; } - } - - @media (min-width: 48rem) { - .\\!container { + @media (width >= 48rem) { max-width: 48rem !important; } - } - - @media (min-width: 1280px) { - .\\!container { + @media (width >= 1280px) { max-width: 1280px !important; } - } - - @media (min-width: 1536px) { - .\\!container { + @media (width >= 1536px) { max-width: 1536px !important; padding-inline: 4rem !important; } } - .container { margin-inline: auto; padding-inline: 2rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 1280px) { - .container { + @media (width >= 1280px) { max-width: 1280px; } - } - - @media (min-width: 1536px) { - .container { + @media (width >= 1536px) { max-width: 1536px; padding-inline: 4rem; } @@ -649,106 +465,74 @@ test('combines custom padding and screen overwrites', async () => { }) test('filters out complex breakpoints', async () => { - expect( - await run( - ['container'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - container: { - center: true, - padding: { - DEFAULT: '2rem', - '2xl': '4rem', - }, - screens: { - sm: '20px', - md: { min: '100px' }, - lg: { max: '200px' }, - xl: { min: '300px', max: '400px' }, - '2xl': { raw: 'print' }, - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + container: { + center: true, + padding: { + DEFAULT: '2rem', + '2xl': '4rem', + }, + screens: { + sm: '20px', + md: { min: '100px' }, + lg: { max: '200px' }, + xl: { min: '300px', max: '400px' }, + '2xl': { raw: 'print' }, }, }, - base: '/root', - path: '', - }), + }, }, - ), - ).toMatchInlineSnapshot(` - " - .container { - width: 100%; - } + base: '/root', + }), + }) - @media (min-width: 40rem) { - .container { + expect(compiler.build(['container'])).toMatchInlineSnapshot(` + ".container { + width: 100%; + @media (width >= 40rem) { max-width: 40rem; } - } - - @media (min-width: 48rem) { - .container { + @media (width >= 48rem) { max-width: 48rem; } - } - - @media (min-width: 64rem) { - .container { + @media (width >= 64rem) { max-width: 64rem; } - } - - @media (min-width: 80rem) { - .container { + @media (width >= 80rem) { max-width: 80rem; } - } - - @media (min-width: 96rem) { - .container { + @media (width >= 96rem) { max-width: 96rem; } } - .container { margin-inline: auto; padding-inline: 2rem; - } - - @media (min-width: 40rem) { - .container { + @media (width >= 40rem) { max-width: none; } - } - - @media (min-width: 20px) { - .container { + @media (width >= 20px) { max-width: 20px; } - } - - @media (min-width: 100px) { - .container { + @media (width >= 100px) { max-width: 100px; } - } - - @media (min-width: 300px) { - .container { + @media (width >= 300px) { max-width: 300px; } } diff --git a/packages/tailwindcss/src/compat/container.ts b/packages/tailwindcss/src/compat/container.ts index 98f23ce7a..3cf72c4db 100644 --- a/packages/tailwindcss/src/compat/container.ts +++ b/packages/tailwindcss/src/compat/container.ts @@ -1,4 +1,4 @@ -import { atRule, cloneAstNode, decl, type AstNode, type AtRule } from '../ast' +import { atRule, decl, type AstNode, type AtRule } from '../ast' import type { DesignSystem } from '../design-system' import { compareBreakpoints } from '../utils/compare-breakpoints' import type { ResolvedConfig } from './config/types' @@ -16,7 +16,7 @@ export function registerContainerCompat(userConfig: ResolvedConfig, designSystem return } - designSystem.utilities.static('container', () => rules.map(cloneAstNode)) + designSystem.utilities.static('container', () => structuredClone(rules)) } export function buildCustomContainerUtilityRules( @@ -50,7 +50,7 @@ export function buildCustomContainerUtilityRules( if (typeof screens === 'object' && screens !== null) { breakpointOverwrites = new Map() - // When setting the `screens` in v3, you were overwriting the default + // When setting a the `screens` in v3, you were overwriting the default // screens config. To do this in v4, you have to manually unset all core // screens. diff --git a/packages/tailwindcss/src/compat/default-theme.ts b/packages/tailwindcss/src/compat/default-theme.ts index 90e699c1c..f540e63a2 100644 --- a/packages/tailwindcss/src/compat/default-theme.ts +++ b/packages/tailwindcss/src/compat/default-theme.ts @@ -353,13 +353,8 @@ export default { }, fontFamily: { sans: [ - '-apple-system', - 'BlinkMacSystemFont', - '"Segoe UI"', - 'Roboto', - '"Helvetica Neue"', - '"Noto Sans"', - 'Arial', + 'ui-sans-serif', + 'system-ui', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', diff --git a/packages/tailwindcss/src/compat/legacy-utilities.test.ts b/packages/tailwindcss/src/compat/legacy-utilities.test.ts index c90eae845..9a828d673 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.test.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.test.ts @@ -1,23 +1,27 @@ import { expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compileCss, run } from '../test-utils/run' const css = String.raw test('bg-gradient-*', async () => { expect( - await run([ - 'bg-gradient-to-t', - 'bg-gradient-to-tr', - 'bg-gradient-to-r', - 'bg-gradient-to-br', - 'bg-gradient-to-b', - 'bg-gradient-to-bl', - 'bg-gradient-to-l', - 'bg-gradient-to-tl', - ]), + await compileCss( + css` + @tailwind utilities; + `, + [ + 'bg-gradient-to-t', + 'bg-gradient-to-tr', + 'bg-gradient-to-r', + 'bg-gradient-to-br', + 'bg-gradient-to-b', + 'bg-gradient-to-bl', + 'bg-gradient-to-l', + 'bg-gradient-to-tl', + ], + ), ).toMatchInlineSnapshot(` - " - .bg-gradient-to-b { + ".bg-gradient-to-b { --tw-gradient-position: to bottom in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); } @@ -55,21 +59,13 @@ test('bg-gradient-*', async () => { .bg-gradient-to-tr { --tw-gradient-position: to top right in oklab; background-image: linear-gradient(var(--tw-gradient-stops)); - } - " + }" `) }) test('max-w-screen', async () => { expect( - await run( - [ - 'max-w-screen-sm', - 'max-w-screen-md', - 'max-w-screen-lg', - 'max-w-screen-xl', - 'max-w-screen-2xl', - ], + await compileCss( css` @theme { --breakpoint-sm: 40rem; @@ -80,10 +76,16 @@ test('max-w-screen', async () => { } @tailwind utilities; `, + [ + 'max-w-screen-sm', + 'max-w-screen-md', + 'max-w-screen-lg', + 'max-w-screen-xl', + 'max-w-screen-2xl', + ], ), ).toMatchInlineSnapshot(` - " - :root, :host { + ":root, :host { --breakpoint-sm: 40rem; --breakpoint-md: 48rem; --breakpoint-lg: 64rem; @@ -109,8 +111,7 @@ test('max-w-screen', async () => { .max-w-screen-xl { max-width: var(--breakpoint-xl); - } - " + }" `) expect( await run([ @@ -133,8 +134,7 @@ test('max-w-screen', async () => { test('box-decoration', async () => { expect(await run(['decoration-slice', 'decoration-clone'])).toMatchInlineSnapshot(` - " - .decoration-clone { + ".decoration-clone { -webkit-box-decoration-break: clone; box-decoration-break: clone; } @@ -142,25 +142,21 @@ test('box-decoration', async () => { .decoration-slice { -webkit-box-decoration-break: slice; box-decoration-break: slice; - } - " + }" `) }) test('overflow-ellipsis', async () => { expect(await run(['overflow-ellipsis'])).toMatchInlineSnapshot(` - " - .overflow-ellipsis { + ".overflow-ellipsis { text-overflow: ellipsis; - } - " + }" `) }) test('flex-grow', async () => { expect(await run(['flex-grow', 'flex-grow-0', 'flex-grow-[123]'])).toMatchInlineSnapshot(` - " - .flex-grow { + ".flex-grow { flex-grow: 1; } @@ -170,8 +166,7 @@ test('flex-grow', async () => { .flex-grow-\\[123\\] { flex-grow: 123; - } - " + }" `) expect( await run([ @@ -190,8 +185,7 @@ test('flex-grow', async () => { test('flex-shrink', async () => { expect(await run(['flex-shrink', 'flex-shrink-0', 'flex-shrink-[123]'])).toMatchInlineSnapshot(` - " - .flex-shrink { + ".flex-shrink { flex-shrink: 1; } @@ -201,8 +195,7 @@ test('flex-shrink', async () => { .flex-shrink-\\[123\\] { flex-shrink: 123; - } - " + }" `) expect( await run([ @@ -218,181 +211,3 @@ test('flex-shrink', async () => { ]), ).toEqual('') }) - -test('start', async () => { - expect( - await run( - [ - 'start-shadowned', - 'start-auto', - '-start-full', - 'start-full', - 'start-3/4', - 'start-4', - '-start-4', - 'start-[4px]', - ], - css` - @theme { - --spacing: 0.25rem; - --inset-shadowned: 1940px; - } - @tailwind utilities; - `, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --spacing: .25rem; - --inset-shadowned: 1940px; - } - - .-start-4 { - inset-inline-start: calc(var(--spacing) * -4); - } - - .-start-full { - inset-inline-start: -100%; - } - - .start-3\\/4 { - inset-inline-start: 75%; - } - - .start-4 { - inset-inline-start: calc(var(--spacing) * 4); - } - - .start-\\[4px\\] { - inset-inline-start: 4px; - } - - .start-auto { - inset-inline-start: auto; - } - - .start-full { - inset-inline-start: 100%; - } - - .start-shadowned { - inset-inline-start: var(--inset-shadowned); - } - " - `) - expect( - await run( - [ - 'start-shadow-sm', - 'start', - 'start--1', - 'start--1/2', - 'start--1/-2', - 'start-1/-2', - 'start-auto/foo', - '-start-full/foo', - 'start-full/foo', - 'start-3/4/foo', - 'start-4/foo', - '-start-4/foo', - 'start-[4px]/foo', - ], - css` - @theme reference { - --spacing: 0.25rem; - --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05); - } - @tailwind utilities; - `, - ), - ).toEqual('') -}) - -test('end', async () => { - expect( - await run( - [ - 'end-shadowned', - 'end-auto', - '-end-full', - 'end-full', - 'end-3/4', - 'end-4', - '-end-4', - 'end-[4px]', - ], - css` - @theme { - --spacing: 0.25rem; - --inset-shadowned: 1940px; - } - @tailwind utilities; - `, - ), - ).toMatchInlineSnapshot(` - " - :root, :host { - --spacing: .25rem; - --inset-shadowned: 1940px; - } - - .-end-4 { - inset-inline-end: calc(var(--spacing) * -4); - } - - .-end-full { - inset-inline-end: -100%; - } - - .end-3\\/4 { - inset-inline-end: 75%; - } - - .end-4 { - inset-inline-end: calc(var(--spacing) * 4); - } - - .end-\\[4px\\] { - inset-inline-end: 4px; - } - - .end-auto { - inset-inline-end: auto; - } - - .end-full { - inset-inline-end: 100%; - } - - .end-shadowned { - inset-inline-end: var(--inset-shadowned); - } - " - `) - expect( - await run( - [ - 'end-shadow-sm', - 'end', - 'end--1', - 'end--1/2', - 'end--1/-2', - 'end-1/-2', - 'end-auto/foo', - '-end-full/foo', - 'end-full/foo', - 'end-3/4/foo', - 'end-4/foo', - '-end-4/foo', - 'end-[4px]/foo', - ], - css` - @theme reference { - --spacing: 0.25rem; - --inset-shadow-sm: inset 0 1px 1px rgb(0 0 0 / 0.05); - } - @tailwind utilities; - `, - ), - ).toEqual('') -}) diff --git a/packages/tailwindcss/src/compat/legacy-utilities.ts b/packages/tailwindcss/src/compat/legacy-utilities.ts index 8e7829035..e3f9436d1 100644 --- a/packages/tailwindcss/src/compat/legacy-utilities.ts +++ b/packages/tailwindcss/src/compat/legacy-utilities.ts @@ -1,7 +1,6 @@ import { decl } from '../ast' import type { DesignSystem } from '../design-system' -import { isPositiveInteger, isValidSpacingMultiplier } from '../utils/infer-data-type' -import { segment } from '../utils/segment' +import { isPositiveInteger } from '../utils/infer-data-type' export function registerLegacyUtilities(designSystem: DesignSystem) { for (let [value, direction] of [ @@ -14,7 +13,6 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { ['l', 'left'], ['tl', 'top left'], ]) { - designSystem.utilities.suggest(`bg-gradient-to-${value}`, () => []) designSystem.utilities.static(`bg-gradient-to-${value}`, () => [ decl('--tw-gradient-position', `to ${direction} in oklab`), decl('background-image', `linear-gradient(var(--tw-gradient-stops))`), @@ -22,34 +20,25 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { } // Legacy `background-position` utilities for compatibility with v4.0 and earlier - designSystem.utilities.suggest('bg-left-top', () => []) designSystem.utilities.static('bg-left-top', () => [decl('background-position', 'left top')]) - designSystem.utilities.suggest('bg-right-top', () => []) designSystem.utilities.static('bg-right-top', () => [decl('background-position', 'right top')]) - designSystem.utilities.suggest('bg-left-bottom', () => []) designSystem.utilities.static('bg-left-bottom', () => [ decl('background-position', 'left bottom'), ]) - designSystem.utilities.suggest('bg-right-bottom', () => []) designSystem.utilities.static('bg-right-bottom', () => [ decl('background-position', 'right bottom'), ]) // Legacy `object-position` utilities for compatibility with v4.0 and earlier - designSystem.utilities.suggest('object-left-top', () => []) designSystem.utilities.static('object-left-top', () => [decl('object-position', 'left top')]) - designSystem.utilities.suggest('object-right-top', () => []) designSystem.utilities.static('object-right-top', () => [decl('object-position', 'right top')]) - designSystem.utilities.suggest('object-left-bottom', () => []) designSystem.utilities.static('object-left-bottom', () => [ decl('object-position', 'left bottom'), ]) - designSystem.utilities.suggest('object-right-bottom', () => []) designSystem.utilities.static('object-right-bottom', () => [ decl('object-position', 'right bottom'), ]) - designSystem.utilities.suggest('max-w-screen', () => []) designSystem.utilities.functional('max-w-screen', (candidate) => { if (!candidate.value) return if (candidate.value.kind === 'arbitrary') return @@ -58,22 +47,18 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { return [decl('max-width', value)] }) - designSystem.utilities.suggest('overflow-ellipsis', () => []) designSystem.utilities.static(`overflow-ellipsis`, () => [decl('text-overflow', `ellipsis`)]) - designSystem.utilities.suggest('decoration-slice', () => []) designSystem.utilities.static(`decoration-slice`, () => [ decl('-webkit-box-decoration-break', `slice`), decl('box-decoration-break', `slice`), ]) - designSystem.utilities.suggest('decoration-clone', () => []) designSystem.utilities.static(`decoration-clone`, () => [ decl('-webkit-box-decoration-break', `clone`), decl('box-decoration-break', `clone`), ]) - designSystem.utilities.suggest('flex-shrink', () => []) designSystem.utilities.functional('flex-shrink', (candidate) => { if (candidate.modifier) return @@ -90,7 +75,6 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { } }) - designSystem.utilities.suggest('flex-grow', () => []) designSystem.utilities.functional('flex-grow', (candidate) => { if (candidate.modifier) return @@ -106,69 +90,4 @@ export function registerLegacyUtilities(designSystem: DesignSystem) { return [decl('flex-grow', candidate.value.value)] } }) - - designSystem.utilities.suggest('order-none', () => []) - designSystem.utilities.static('order-none', () => [decl('order', '0')]) - - designSystem.utilities.suggest('break-words', () => []) - designSystem.utilities.static('break-words', () => [decl('overflow-wrap', 'break-word')]) - - // Legacy `start` and `end` inset utilities, replaced by `inset-s` and `inset-e` - for (let [name, property] of [ - ['start', 'inset-inline-start'], - ['end', 'inset-inline-end'], - ] as const) { - designSystem.utilities.static(`${name}-auto`, () => [decl(property, 'auto')]) - designSystem.utilities.static(`${name}-full`, () => [decl(property, '100%')]) - designSystem.utilities.static(`-${name}-full`, () => [decl(property, '-100%')]) - designSystem.utilities.static(`${name}-px`, () => [decl(property, '1px')]) - designSystem.utilities.static(`-${name}-px`, () => [decl(property, '-1px')]) - - function handleInset({ negative }: { negative: boolean }) { - return (candidate: Extract) => { - if (candidate.value === null) return - - if (candidate.value.kind === 'arbitrary') { - if (candidate.modifier) return - let value = candidate.value.value - return [decl(property, negative ? `calc(${value} * -1)` : value)] - } - - let value = designSystem.theme.resolve(candidate.value.fraction ?? candidate.value.value, [ - '--inset', - '--spacing', - ]) - - // Handle fractions like `start-1/2` - if (value === null && candidate.value.fraction) { - let [lhs, rhs] = segment(candidate.value.fraction, '/') - if (!isPositiveInteger(lhs) || !isPositiveInteger(rhs)) return - value = `calc(${candidate.value.fraction} * 100%)` - } - - // Handle bare spacing multiplier values like `start-4` - if (value === null && negative) { - let multiplier = designSystem.theme.resolve(null, ['--spacing']) - if (multiplier && isValidSpacingMultiplier(candidate.value.value)) { - value = `calc(${multiplier} * -${candidate.value.value})` - if (value !== null) return [decl(property, value)] - } - } - - if (value === null) { - let multiplier = designSystem.theme.resolve(null, ['--spacing']) - if (multiplier && isValidSpacingMultiplier(candidate.value.value)) { - value = `calc(${multiplier} * ${candidate.value.value})` - } - } - - if (value === null) return - - return [decl(property, negative ? `calc(${value} * -1)` : value)] - } - } - - designSystem.utilities.functional(`-${name}`, handleInset({ negative: true })) - designSystem.utilities.functional(name, handleInset({ negative: false })) - } } diff --git a/packages/tailwindcss/src/compat/plugin-api.test.ts b/packages/tailwindcss/src/compat/plugin-api.test.ts index 5fcacb5cd..465974679 100644 --- a/packages/tailwindcss/src/compat/plugin-api.test.ts +++ b/packages/tailwindcss/src/compat/plugin-api.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest' import { compile } from '..' import plugin from '../plugin' -import { compileCss, run } from '../test-utils/run' +import { optimizeCss } from '../test-utils/run' import defaultTheme from './default-theme' import type { CssInJs, PluginAPI } from './plugin-api' @@ -9,61 +9,58 @@ const css = String.raw describe('theme', async () => { test('plugin theme can contain objects', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ addBase, theme }) { - addBase({ - '@keyframes enter': theme('keyframes.enter'), - '@keyframes exit': theme('keyframes.exit'), - }) - }, - { - theme: { - extend: { - keyframes: { - enter: { - from: { - opacity: 'var(--tw-enter-opacity, 1)', - transform: - 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))', - }, - }, - exit: { - to: { - opacity: 'var(--tw-exit-opacity, 1)', - transform: - 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))', - }, - }, + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ addBase, theme }) { + addBase({ + '@keyframes enter': theme('keyframes.enter'), + '@keyframes exit': theme('keyframes.exit'), + }) + }, + { + theme: { + extend: { + keyframes: { + enter: { + from: { + opacity: 'var(--tw-enter-opacity, 1)', + transform: + 'translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))', + }, + }, + exit: { + to: { + opacity: 'var(--tw-exit-opacity, 1)', + transform: + 'translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))', }, }, }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + }, + }, + ), + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { @keyframes enter { from { opacity: var(--tw-enter-opacity, 1); transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0)); } } - @keyframes exit { to { opacity: var(--tw-exit-opacity, 1); @@ -76,29 +73,31 @@ describe('theme', async () => { }) test('keyframes added via addUtilities are appended to the AST', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities }) { - addUtilities({ - '@keyframes enter': { from: { opacity: 'var(--tw-enter-opacity, 1)' } }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @keyframes enter { + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ addUtilities, theme }) { + addUtilities({ + '@keyframes enter': { + from: { + opacity: 'var(--tw-enter-opacity, 1)', + }, + }, + }) + }), + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@keyframes enter { from { opacity: var(--tw-enter-opacity, 1); } @@ -108,40 +107,48 @@ describe('theme', async () => { }) test('plugin theme can extend colors', async () => { - expect( - await run( - ['scrollbar-red-500', 'scrollbar-russet-700'], - css` - @theme reference { - --color-red-500: #ef4444; - } - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { values: theme('colors') }, - ) + let input = css` + @theme reference { + --color-red-500: #ef4444; + } + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), }, - { theme: { extend: { colors: { 'russet-700': '#7a4724' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .scrollbar-red-500 { + { + values: theme('colors'), + }, + ) + }, + { + theme: { + extend: { + colors: { + 'russet-700': '#7a4724', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['scrollbar-red-500', 'scrollbar-russet-700'])).toMatchInlineSnapshot(` + ".scrollbar-red-500 { scrollbar-color: #ef4444; } - .scrollbar-russet-700 { scrollbar-color: #7a4724; } @@ -152,43 +159,46 @@ describe('theme', async () => { test('plugin theme values can reference legacy theme keys that have been replaced with bare value support', async ({ expect, }) => { - expect( - await run( - ['animate-duration-316'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-duration': value }) }, - { values: theme('animationDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-duration': value }), }, { - theme: { - extend: { - animationDuration: ({ theme }: { theme: (path: string) => any }) => { - return { ...theme('transitionDuration') } - }, - }, + values: theme('animationDuration'), + }, + ) + }, + { + theme: { + extend: { + animationDuration: ({ theme }: { theme: (path: string) => any }) => { + return { + ...theme('transitionDuration'), + } }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration-316 { - animation-duration: .316s; + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration-316'])).toMatchInlineSnapshot(` + ".animate-duration-316 { + animation-duration: 316ms; } " `) @@ -197,92 +207,98 @@ describe('theme', async () => { test('plugin theme values that support bare values are merged with other values for that theme key', async ({ expect, }) => { - expect( - await run( - ['animate-duration-316', 'animate-duration-slow'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-duration': value }) }, - { values: theme('animationDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-duration': value }), }, { - theme: { - extend: { - transitionDuration: { slow: '800ms' }, - animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('transitionDuration'), - }), - }, - }, + values: theme('animationDuration'), }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration-316 { - animation-duration: .316s; - } + ) + }, + { + theme: { + extend: { + transitionDuration: { + slow: '800ms', + }, - .animate-duration-slow { - animation-duration: .8s; - } - " - `) + animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('transitionDuration'), + }), + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration-316', 'animate-duration-slow'])) + .toMatchInlineSnapshot(` + ".animate-duration-316 { + animation-duration: 316ms; + } + .animate-duration-slow { + animation-duration: 800ms; + } + " + `) }) test('plugin theme can have opacity modifiers', async () => { - expect( - await run( - ['percentage', 'fraction', 'variable'], - css` - @tailwind utilities; - @theme { - --color-red-500: #ef4444; - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.percentage': { color: theme('colors.red.500 / 50%') }, - '.fraction': { color: theme('colors.red.500 / 0.5') }, - '.variable': { color: theme('colors.red.500 / var(--opacity)') }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .fraction, .percentage { - color: oklab(63.6834% .187 .088 / .5); + let input = css` + @tailwind utilities; + @theme { + --color-red-500: #ef4444; } + @plugin "my-plugin"; + ` + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ addUtilities, theme }) { + addUtilities({ + '.percentage': { + color: theme('colors.red.500 / 50%'), + }, + '.fraction': { + color: theme('colors.red.500 / 0.5'), + }, + '.variable': { + color: theme('colors.red.500 / var(--opacity)'), + }, + }) + }), + } + }, + }) + + expect(compiler.build(['percentage', 'fraction', 'variable'])).toMatchInlineSnapshot(` + ".fraction { + color: color-mix(in oklab, #ef4444 50%, transparent); + } + .percentage { + color: color-mix(in oklab, #ef4444 50%, transparent); + } .variable { color: #ef4444; - } - - @supports (color: color-mix(in lab, red, red)) { - .variable { + @supports (color: color-mix(in lab, red, red)) { color: color-mix(in oklab, #ef4444 var(--opacity), transparent); } } @@ -291,81 +307,89 @@ describe('theme', async () => { }) test('plugin theme colors can use ', async () => { + let input = css` + @tailwind utilities; + @theme { + /* This should not work */ + --color-custom-css: rgba(255 0 0 / ); + } + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ addUtilities, theme }) { + addUtilities({ + '.css-percentage': { + color: theme('colors.custom-css / 50%'), + }, + '.css-fraction': { + color: theme('colors.custom-css / 0.5'), + }, + '.css-variable': { + color: theme('colors.custom-css / var(--opacity)'), + }, + '.js-percentage': { + color: theme('colors.custom-js / 50%'), + }, + '.js-fraction': { + color: theme('colors.custom-js / 0.5'), + }, + '.js-variable': { + color: theme('colors.custom-js / var(--opacity)'), + }, + }) + }, + { + theme: { + colors: { + /* This should work */ + 'custom-js': 'rgb(255 0 0 / )', + }, + }, + }, + ), + } + }, + }) + expect( - await run( - [ - 'bg-custom', - 'css-percentage', - 'css-fraction', - 'css-variable', - 'js-percentage', - 'js-fraction', - 'js-variable', - ], - css` - @tailwind utilities; - @theme { - /* This should not work */ - --color-custom-css: rgba(255 0 0 / ); - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ addUtilities, theme }) { - addUtilities({ - '.css-percentage': { color: theme('colors.custom-css / 50%') }, - '.css-fraction': { color: theme('colors.custom-css / 0.5') }, - '.css-variable': { color: theme('colors.custom-css / var(--opacity)') }, - '.js-percentage': { color: theme('colors.custom-js / 50%') }, - '.js-fraction': { color: theme('colors.custom-js / 0.5') }, - '.js-variable': { color: theme('colors.custom-js / var(--opacity)') }, - }) - }, - { - theme: { - colors: { - /* This should work */ - 'custom-js': 'rgb(255 0 0 / )', - }, - }, - }, - ), - } - }, - }, - ), + compiler.build([ + 'bg-custom', + 'css-percentage', + 'css-fraction', + 'css-variable', + 'js-percentage', + 'js-fraction', + 'js-variable', + ]), ).toMatchInlineSnapshot(` - " - .css-fraction, .css-percentage { + ".css-fraction { + color: color-mix(in oklab, rgba(255 0 0 / ) 50%, transparent); + } + .css-percentage { color: color-mix(in oklab, rgba(255 0 0 / ) 50%, transparent); } - .css-variable { color: rgba(255 0 0 / ); - } - - @supports (color: color-mix(in lab, red, red)) { - .css-variable { + @supports (color: color-mix(in lab, red, red)) { color: color-mix(in oklab, rgba(255 0 0 / ) var(--opacity), transparent); } } - - .js-fraction, .js-percentage { - color: oklab(62.7955% .224 .125 / .5); + .js-fraction { + color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent); + } + .js-percentage { + color: color-mix(in oklab, rgb(255 0 0 / 1) 50%, transparent); } - .js-variable { - color: red; - } - - @supports (color: color-mix(in lab, red, red)) { - .js-variable { - color: color-mix(in oklab, red var(--opacity), transparent); + color: rgb(255 0 0 / 1); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, rgb(255 0 0 / 1) var(--opacity), transparent); } } " @@ -373,226 +397,260 @@ describe('theme', async () => { }) test('theme value functions are resolved correctly regardless of order', async () => { - expect( - await run( - ['animate-delay-316', 'animate-delay-slow'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-delay': (value) => ({ 'animation-delay': value }) }, - { values: theme('animationDelay') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-delay': (value) => ({ 'animation-delay': value }), }, { - theme: { - extend: { - animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('transitionDuration'), - }), - animationDelay: ({ theme }: { theme: (path: string) => any }) => ({ - ...theme('animationDuration'), - }), - transitionDuration: { slow: '800ms' }, - }, + values: theme('animationDelay'), + }, + ) + }, + { + theme: { + extend: { + animationDuration: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('transitionDuration'), + }), + + animationDelay: ({ theme }: { theme: (path: string) => any }) => ({ + ...theme('animationDuration'), + }), + + transitionDuration: { + slow: '800ms', }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-delay-316 { - animation-delay: .316s; - } + }, + }, + ), + } + }, + }) + expect(compiler.build(['animate-delay-316', 'animate-delay-slow'])).toMatchInlineSnapshot(` + ".animate-delay-316 { + animation-delay: 316ms; + } .animate-delay-slow { - animation-delay: .8s; + animation-delay: 800ms; } " `) }) test('plugins can override the default key', async () => { - expect( - await run( - ['animate-duration'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'animate-duration': (value) => ({ 'animation-delay': value }) }, - { values: theme('transitionDuration') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + 'animate-duration': (value) => ({ 'animation-delay': value }), }, - { theme: { extend: { transitionDuration: { DEFAULT: '1500ms' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animate-duration { - animation-delay: 1.5s; + { + values: theme('transitionDuration'), + }, + ) + }, + { + theme: { + extend: { + transitionDuration: { + DEFAULT: '1500ms', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animate-duration'])).toMatchInlineSnapshot(` + ".animate-duration { + animation-delay: 1500ms; } " `) }) test('plugins can read CSS theme keys using the old theme key notation', async () => { - expect( - await run( - ['animation-spin', 'animation', 'animation2', 'animation2-twist'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ animation: value }) }, - { values: theme('animation') }, - ) - - matchUtilities( - { animation2: (value) => ({ animation: value }) }, - { - values: { DEFAULT: theme('animation.DEFAULT'), twist: theme('animation.spin') }, - }, - ) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { - animation: 1s linear infinite pulse; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; } + ` - .animation-spin { - animation: 1s linear infinite spin; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ animation: value }), + }, + { + values: theme('animation'), + }, + ) - .animation2 { - animation: 1s linear infinite pulse; - } + matchUtilities( + { + animation2: (value) => ({ animation: value }), + }, + { + values: { + DEFAULT: theme('animation.DEFAULT'), + twist: theme('animation.spin'), + }, + }, + ) + }), + } + }, + }) - .animation2-twist { - animation: 1s linear infinite spin; - } - " - `) + expect(compiler.build(['animation-spin', 'animation', 'animation2', 'animation2-twist'])) + .toMatchInlineSnapshot(` + ".animation { + animation: pulse 1s linear infinite; + } + .animation-spin { + animation: spin 1s linear infinite; + } + .animation2 { + animation: pulse 1s linear infinite; + } + .animation2-twist { + animation: spin 1s linear infinite; + } + " + `) }) test('CSS theme values are merged with JS theme values', async () => { - expect( - await run( - ['animation', 'animation-spin', 'animation-bounce'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ '--animation': value }) }, - { values: theme('animation') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; + } + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ '--animation': value }), }, - { theme: { extend: { animation: { bounce: 'bounce 1s linear infinite' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { - --animation: pulse 1s linear infinite; - } + { + values: theme('animation'), + }, + ) + }, + { + theme: { + extend: { + animation: { + bounce: 'bounce 1s linear infinite', + }, + }, + }, + }, + ), + } + }, + }) - .animation-bounce { - --animation: bounce 1s linear infinite; - } - - .animation-spin { - --animation: spin 1s linear infinite; - } - " - `) + expect(compiler.build(['animation', 'animation-spin', 'animation-bounce'])) + .toMatchInlineSnapshot(` + ".animation { + --animation: pulse 1s linear infinite; + } + .animation-bounce { + --animation: bounce 1s linear infinite; + } + .animation-spin { + --animation: spin 1s linear infinite; + } + " + `) }) test('CSS theme defaults take precedence over JS theme defaults', async () => { - expect( - await run( - ['animation'], - css` - @tailwind utilities; - @plugin "my-plugin"; - @theme reference { - --animate: pulse 1s linear infinite; - --animate-spin: spin 1s linear infinite; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { animation: (value) => ({ '--animation': value }) }, - { values: theme('animation') }, - ) + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + @theme reference { + --animate: pulse 1s linear infinite; + --animate-spin: spin 1s linear infinite; + } + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { + animation: (value) => ({ '--animation': value }), }, - { theme: { extend: { animation: { DEFAULT: 'twist 1s linear infinite' } } } }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .animation { + { + values: theme('animation'), + }, + ) + }, + { + theme: { + extend: { + animation: { + DEFAULT: 'twist 1s linear infinite', + }, + }, + }, + }, + ), + } + }, + }) + + expect(compiler.build(['animation'])).toMatchInlineSnapshot(` + ".animation { --animation: pulse 1s linear infinite; } " @@ -609,10 +667,10 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { path: '', base, @@ -621,7 +679,13 @@ describe('theme', async () => { fn(theme('animation.simple')) }, { - theme: { extend: { animation: { simple: 'simple 1s linear' } } }, + theme: { + extend: { + animation: { + simple: 'simple 1s linear', + }, + }, + }, }, ), } @@ -635,259 +699,241 @@ describe('theme', async () => { }) }) - test('theme() resolves the DEFAULT value when a bare CSS theme key shares a prefix with a sibling key', async () => { - expect( - await run( - ['example-foo', 'example-foo-bar'], - css` - @tailwind utilities; - @theme { - --color-foo: red; - --color-foo-bar: blue; - } - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ addUtilities, theme }) { - addUtilities({ - '.example-foo': { color: theme('colors.foo') }, - '.example-foo-bar': { color: theme('colors.foo-bar') }, - }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .example-foo { - color: red; - } - - .example-foo-bar { - color: #00f; - } - " - `) - }) - test('all necessary theme keys support bare values', async () => { - expect( - await run( - [ - 'my-aspect-2/5', - 'my-backdrop-brightness-1', - 'my-backdrop-contrast-1', - 'my-backdrop-grayscale-1', - 'my-backdrop-hue-rotate-1', - 'my-backdrop-invert-1', - 'my-backdrop-opacity-1', - 'my-backdrop-saturate-1', - 'my-backdrop-sepia-1', - 'my-border-width-1', - 'my-brightness-1', - 'my-columns-1', - 'my-contrast-1', - 'my-divide-width-1', - 'my-flex-grow-1', - 'my-flex-shrink-1', - 'my-gradient-color-stop-positions-1', - 'my-grayscale-1', - 'my-grid-row-end-1', - 'my-grid-row-start-1', - 'my-grid-template-columns-1', - 'my-grid-template-rows-1', - 'my-hue-rotate-1', - 'my-invert-1', - 'my-line-clamp-1', - 'my-opacity-1', - 'my-order-1', - 'my-outline-offset-1', - 'my-outline-width-1', - 'my-ring-offset-width-1', - 'my-ring-width-1', - 'my-rotate-1', - 'my-saturate-1', - 'my-scale-1', - 'my-sepia-1', - 'my-skew-1', - 'my-stroke-width-1', - 'my-text-decoration-thickness-1', - 'my-text-underline-offset-1', - 'my-transition-delay-1', - 'my-transition-duration-1', - 'my-z-index-1', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - function utility(name: string, themeKey: string) { - matchUtilities( - { [name]: (value) => ({ '--value': value }) }, - { values: theme(themeKey) }, - ) - } + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` - utility('my-aspect', 'aspectRatio') - utility('my-backdrop-brightness', 'backdropBrightness') - utility('my-backdrop-contrast', 'backdropContrast') - utility('my-backdrop-grayscale', 'backdropGrayscale') - utility('my-backdrop-hue-rotate', 'backdropHueRotate') - utility('my-backdrop-invert', 'backdropInvert') - utility('my-backdrop-opacity', 'backdropOpacity') - utility('my-backdrop-saturate', 'backdropSaturate') - utility('my-backdrop-sepia', 'backdropSepia') - utility('my-border-width', 'borderWidth') - utility('my-brightness', 'brightness') - utility('my-columns', 'columns') - utility('my-contrast', 'contrast') - utility('my-divide-width', 'divideWidth') - utility('my-flex-grow', 'flexGrow') - utility('my-flex-shrink', 'flexShrink') - utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') - utility('my-grayscale', 'grayscale') - utility('my-grid-row-end', 'gridRowEnd') - utility('my-grid-row-start', 'gridRowStart') - utility('my-grid-template-columns', 'gridTemplateColumns') - utility('my-grid-template-rows', 'gridTemplateRows') - utility('my-hue-rotate', 'hueRotate') - utility('my-invert', 'invert') - utility('my-line-clamp', 'lineClamp') - utility('my-opacity', 'opacity') - utility('my-order', 'order') - utility('my-outline-offset', 'outlineOffset') - utility('my-outline-width', 'outlineWidth') - utility('my-ring-offset-width', 'ringOffsetWidth') - utility('my-ring-width', 'ringWidth') - utility('my-rotate', 'rotate') - utility('my-saturate', 'saturate') - utility('my-scale', 'scale') - utility('my-sepia', 'sepia') - utility('my-skew', 'skew') - utility('my-stroke-width', 'strokeWidth') - utility('my-text-decoration-thickness', 'textDecorationThickness') - utility('my-text-underline-offset', 'textUnderlineOffset') - utility('my-transition-delay', 'transitionDelay') - utility('my-transition-duration', 'transitionDuration') - utility('my-z-index', 'zIndex') - }), + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ matchUtilities, theme }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + { values: theme(themeKey) }, + ) } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-aspect-2\\/5 { + + utility('my-aspect', 'aspectRatio') + utility('my-backdrop-brightness', 'backdropBrightness') + utility('my-backdrop-contrast', 'backdropContrast') + utility('my-backdrop-grayscale', 'backdropGrayscale') + utility('my-backdrop-hue-rotate', 'backdropHueRotate') + utility('my-backdrop-invert', 'backdropInvert') + utility('my-backdrop-opacity', 'backdropOpacity') + utility('my-backdrop-saturate', 'backdropSaturate') + utility('my-backdrop-sepia', 'backdropSepia') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-divide-width', 'divideWidth') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }), + } + }, + }) + + let output = build([ + 'my-aspect-2/5', + 'my-backdrop-brightness-1', + 'my-backdrop-contrast-1', + 'my-backdrop-grayscale-1', + 'my-backdrop-hue-rotate-1', + 'my-backdrop-invert-1', + 'my-backdrop-opacity-1', + 'my-backdrop-saturate-1', + 'my-backdrop-sepia-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-divide-width-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { --value: 2/5; } - - .my-backdrop-brightness-1, .my-backdrop-contrast-1, .my-backdrop-grayscale-1 { + .my-backdrop-brightness-1 { + --value: 1%; + } + .my-backdrop-contrast-1 { + --value: 1%; + } + .my-backdrop-grayscale-1 { --value: 1%; } - .my-backdrop-hue-rotate-1 { --value: 1deg; } - - .my-backdrop-invert-1, .my-backdrop-opacity-1, .my-backdrop-saturate-1, .my-backdrop-sepia-1 { + .my-backdrop-invert-1 { + --value: 1%; + } + .my-backdrop-opacity-1 { + --value: 1%; + } + .my-backdrop-saturate-1 { + --value: 1%; + } + .my-backdrop-sepia-1 { --value: 1%; } - .my-border-width-1 { --value: 1px; } - .my-brightness-1 { --value: 1%; } - .my-columns-1 { --value: 1; } - .my-contrast-1 { --value: 1%; } - .my-divide-width-1 { --value: 1px; } - - .my-flex-grow-1, .my-flex-shrink-1 { + .my-flex-grow-1 { --value: 1; } - - .my-gradient-color-stop-positions-1, .my-grayscale-1 { + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { --value: 1%; } - - .my-grid-row-end-1, .my-grid-row-start-1 { + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { --value: 1; } - - .my-grid-template-columns-1, .my-grid-template-rows-1 { + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { --value: repeat(1, minmax(0, 1fr)); } - .my-hue-rotate-1 { --value: 1deg; } - .my-invert-1 { --value: 1%; } - .my-line-clamp-1 { --value: 1; } - .my-opacity-1 { --value: 1%; } - .my-order-1 { --value: 1; } - - .my-outline-offset-1, .my-outline-width-1, .my-ring-offset-width-1, .my-ring-width-1 { + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { --value: 1px; } - .my-rotate-1 { --value: 1deg; } - - .my-saturate-1, .my-scale-1, .my-sepia-1 { + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { --value: 1%; } - .my-skew-1 { --value: 1deg; } - .my-stroke-width-1 { --value: 1; } - - .my-text-decoration-thickness-1, .my-text-underline-offset-1 { + .my-text-decoration-thickness-1 { --value: 1px; } - - .my-transition-delay-1, .my-transition-duration-1 { + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { --value: 1ms; } - .my-z-index-1 { --value: 1; } @@ -905,10 +951,10 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { path: '', base, @@ -948,10 +994,10 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { path: '', base, @@ -980,10 +1026,10 @@ describe('theme', async () => { } ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { path: '', base, @@ -1009,10 +1055,10 @@ describe('theme', async () => { @plugin "my-plugin"; ` - using fn = vi.fn() + let fn = vi.fn() await compile(input, { - loadModule: async (_id, base) => { + loadModule: async (id, base) => { return { path: '', base, @@ -1033,242 +1079,273 @@ describe('theme', async () => { }) test('Candidates can match multiple utility definitions', async () => { - expect( - await run( - ['foo-bar'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(({ addUtilities, matchUtilities }) => { - addUtilities({ '.foo-bar': { color: 'red' } }) - matchUtilities( - { foo: (value) => ({ '--my-prop': value }) }, - { values: { bar: 'bar-valuer', baz: 'bar-valuer' } }, - ) - addUtilities({ '.foo-bar': { backgroundColor: 'red' } }) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(({ addUtilities, matchUtilities }) => { + addUtilities({ + '.foo-bar': { + color: 'red', + }, + }) + + matchUtilities( + { + foo: (value) => ({ + '--my-prop': value, + }), + }, + { + values: { + bar: 'bar-valuer', + baz: 'bar-valuer', + }, + }, + ) + + addUtilities({ + '.foo-bar': { + backgroundColor: 'red', + }, + }) + }), + } + }, + }) + + expect(build(['foo-bar'])).toMatchInlineSnapshot(` + ".foo-bar { + background-color: red; + } .foo-bar { color: red; + } + .foo-bar { --my-prop: bar-valuer; - background-color: red; } " `) }) test('spreading `tailwindcss/defaultTheme` exports keeps bare values', async () => { - expect( - await run( - [ - 'my-aspect-2/5', - // 'my-backdrop-brightness-1', - // 'my-backdrop-contrast-1', - // 'my-backdrop-grayscale-1', - // 'my-backdrop-hue-rotate-1', - // 'my-backdrop-invert-1', - // 'my-backdrop-opacity-1', - // 'my-backdrop-saturate-1', - // 'my-backdrop-sepia-1', - // 'my-divide-width-1', - 'my-border-width-1', - 'my-brightness-1', - 'my-columns-1', - 'my-contrast-1', - 'my-flex-grow-1', - 'my-flex-shrink-1', - 'my-gradient-color-stop-positions-1', - 'my-grayscale-1', - 'my-grid-row-end-1', - 'my-grid-row-start-1', - 'my-grid-template-columns-1', - 'my-grid-template-rows-1', - 'my-hue-rotate-1', - 'my-invert-1', - 'my-line-clamp-1', - 'my-opacity-1', - 'my-order-1', - 'my-outline-offset-1', - 'my-outline-width-1', - 'my-ring-offset-width-1', - 'my-ring-width-1', - 'my-rotate-1', - 'my-saturate-1', - 'my-scale-1', - 'my-sepia-1', - 'my-skew-1', - 'my-stroke-width-1', - 'my-text-decoration-thickness-1', - 'my-text-underline-offset-1', - 'my-transition-delay-1', - 'my-transition-duration-1', - 'my-z-index-1', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities }) { - function utility(name: string, themeKey: string) { - matchUtilities( - { [name]: (value) => ({ '--value': value }) }, - // @ts-ignore - { values: defaultTheme[themeKey] }, - ) - } + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` - utility('my-aspect', 'aspectRatio') - // The following keys deliberately doesn't work as these are exported - // as functions from the compat config. - // - // utility('my-backdrop-brightness', 'backdropBrightness') - // utility('my-backdrop-contrast', 'backdropContrast') - // utility('my-backdrop-grayscale', 'backdropGrayscale') - // utility('my-backdrop-hue-rotate', 'backdropHueRotate') - // utility('my-backdrop-invert', 'backdropInvert') - // utility('my-backdrop-opacity', 'backdropOpacity') - // utility('my-backdrop-saturate', 'backdropSaturate') - // utility('my-backdrop-sepia', 'backdropSepia') - // utility('my-divide-width', 'divideWidth') - utility('my-border-width', 'borderWidth') - utility('my-brightness', 'brightness') - utility('my-columns', 'columns') - utility('my-contrast', 'contrast') - utility('my-flex-grow', 'flexGrow') - utility('my-flex-shrink', 'flexShrink') - utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') - utility('my-grayscale', 'grayscale') - utility('my-grid-row-end', 'gridRowEnd') - utility('my-grid-row-start', 'gridRowStart') - utility('my-grid-template-columns', 'gridTemplateColumns') - utility('my-grid-template-rows', 'gridTemplateRows') - utility('my-hue-rotate', 'hueRotate') - utility('my-invert', 'invert') - utility('my-line-clamp', 'lineClamp') - utility('my-opacity', 'opacity') - utility('my-order', 'order') - utility('my-outline-offset', 'outlineOffset') - utility('my-outline-width', 'outlineWidth') - utility('my-ring-offset-width', 'ringOffsetWidth') - utility('my-ring-width', 'ringWidth') - utility('my-rotate', 'rotate') - utility('my-saturate', 'saturate') - utility('my-scale', 'scale') - utility('my-sepia', 'sepia') - utility('my-skew', 'skew') - utility('my-stroke-width', 'strokeWidth') - utility('my-text-decoration-thickness', 'textDecorationThickness') - utility('my-text-underline-offset', 'textUnderlineOffset') - utility('my-transition-delay', 'transitionDelay') - utility('my-transition-duration', 'transitionDuration') - utility('my-z-index', 'zIndex') - }), + let { build } = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ matchUtilities }) { + function utility(name: string, themeKey: string) { + matchUtilities( + { [name]: (value) => ({ '--value': value }) }, + // @ts-ignore + { values: defaultTheme[themeKey] }, + ) } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-aspect-2\\/5 { + + utility('my-aspect', 'aspectRatio') + // The following keys deliberately doesn't work as these are exported + // as functions from the compat config. + // + // utility('my-backdrop-brightness', 'backdropBrightness') + // utility('my-backdrop-contrast', 'backdropContrast') + // utility('my-backdrop-grayscale', 'backdropGrayscale') + // utility('my-backdrop-hue-rotate', 'backdropHueRotate') + // utility('my-backdrop-invert', 'backdropInvert') + // utility('my-backdrop-opacity', 'backdropOpacity') + // utility('my-backdrop-saturate', 'backdropSaturate') + // utility('my-backdrop-sepia', 'backdropSepia') + // utility('my-divide-width', 'divideWidth') + utility('my-border-width', 'borderWidth') + utility('my-brightness', 'brightness') + utility('my-columns', 'columns') + utility('my-contrast', 'contrast') + utility('my-flex-grow', 'flexGrow') + utility('my-flex-shrink', 'flexShrink') + utility('my-gradient-color-stop-positions', 'gradientColorStopPositions') + utility('my-grayscale', 'grayscale') + utility('my-grid-row-end', 'gridRowEnd') + utility('my-grid-row-start', 'gridRowStart') + utility('my-grid-template-columns', 'gridTemplateColumns') + utility('my-grid-template-rows', 'gridTemplateRows') + utility('my-hue-rotate', 'hueRotate') + utility('my-invert', 'invert') + utility('my-line-clamp', 'lineClamp') + utility('my-opacity', 'opacity') + utility('my-order', 'order') + utility('my-outline-offset', 'outlineOffset') + utility('my-outline-width', 'outlineWidth') + utility('my-ring-offset-width', 'ringOffsetWidth') + utility('my-ring-width', 'ringWidth') + utility('my-rotate', 'rotate') + utility('my-saturate', 'saturate') + utility('my-scale', 'scale') + utility('my-sepia', 'sepia') + utility('my-skew', 'skew') + utility('my-stroke-width', 'strokeWidth') + utility('my-text-decoration-thickness', 'textDecorationThickness') + utility('my-text-underline-offset', 'textUnderlineOffset') + utility('my-transition-delay', 'transitionDelay') + utility('my-transition-duration', 'transitionDuration') + utility('my-z-index', 'zIndex') + }), + } + }, + }) + + let output = build([ + 'my-aspect-2/5', + // 'my-backdrop-brightness-1', + // 'my-backdrop-contrast-1', + // 'my-backdrop-grayscale-1', + // 'my-backdrop-hue-rotate-1', + // 'my-backdrop-invert-1', + // 'my-backdrop-opacity-1', + // 'my-backdrop-saturate-1', + // 'my-backdrop-sepia-1', + // 'my-divide-width-1', + 'my-border-width-1', + 'my-brightness-1', + 'my-columns-1', + 'my-contrast-1', + 'my-flex-grow-1', + 'my-flex-shrink-1', + 'my-gradient-color-stop-positions-1', + 'my-grayscale-1', + 'my-grid-row-end-1', + 'my-grid-row-start-1', + 'my-grid-template-columns-1', + 'my-grid-template-rows-1', + 'my-hue-rotate-1', + 'my-invert-1', + 'my-line-clamp-1', + 'my-opacity-1', + 'my-order-1', + 'my-outline-offset-1', + 'my-outline-width-1', + 'my-ring-offset-width-1', + 'my-ring-width-1', + 'my-rotate-1', + 'my-saturate-1', + 'my-scale-1', + 'my-sepia-1', + 'my-skew-1', + 'my-stroke-width-1', + 'my-text-decoration-thickness-1', + 'my-text-underline-offset-1', + 'my-transition-delay-1', + 'my-transition-duration-1', + 'my-z-index-1', + ]) + + expect(output).toMatchInlineSnapshot(` + ".my-aspect-2\\/5 { --value: 2/5; } - .my-border-width-1 { --value: 1px; } - .my-brightness-1 { --value: 1%; } - .my-columns-1 { --value: 1; } - .my-contrast-1 { --value: 1%; } - - .my-flex-grow-1, .my-flex-shrink-1 { + .my-flex-grow-1 { --value: 1; } - - .my-gradient-color-stop-positions-1, .my-grayscale-1 { + .my-flex-shrink-1 { + --value: 1; + } + .my-gradient-color-stop-positions-1 { --value: 1%; } - - .my-grid-row-end-1, .my-grid-row-start-1 { + .my-grayscale-1 { + --value: 1%; + } + .my-grid-row-end-1 { --value: 1; } - - .my-grid-template-columns-1, .my-grid-template-rows-1 { + .my-grid-row-start-1 { + --value: 1; + } + .my-grid-template-columns-1 { + --value: repeat(1, minmax(0, 1fr)); + } + .my-grid-template-rows-1 { --value: repeat(1, minmax(0, 1fr)); } - .my-hue-rotate-1 { --value: 1deg; } - .my-invert-1 { --value: 1%; } - .my-line-clamp-1 { --value: 1; } - .my-opacity-1 { --value: 1%; } - .my-order-1 { --value: 1; } - - .my-outline-offset-1, .my-outline-width-1, .my-ring-offset-width-1, .my-ring-width-1 { + .my-outline-offset-1 { + --value: 1px; + } + .my-outline-width-1 { + --value: 1px; + } + .my-ring-offset-width-1 { + --value: 1px; + } + .my-ring-width-1 { --value: 1px; } - .my-rotate-1 { --value: 1deg; } - - .my-saturate-1, .my-scale-1, .my-sepia-1 { + .my-saturate-1 { + --value: 1%; + } + .my-scale-1 { + --value: 1%; + } + .my-sepia-1 { --value: 1%; } - .my-skew-1 { --value: 1deg; } - .my-stroke-width-1 { --value: 1; } - - .my-text-decoration-thickness-1, .my-text-underline-offset-1 { + .my-text-decoration-thickness-1 { --value: 1px; } - - .my-transition-delay-1, .my-transition-duration-1 { + .my-text-underline-offset-1 { + --value: 1px; + } + .my-transition-delay-1 { + --value: 1ms; + } + .my-transition-duration-1 { --value: 1ms; } - .my-z-index-1 { --value: 1; } @@ -1277,211 +1354,196 @@ describe('theme', async () => { }) test('can use escaped JS variables in theme values', async () => { - expect( - await run( - ['my-width-1', 'my-width-1/2', 'my-width-1.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('width') }, - ) - }, - { - theme: { - extend: { - width: { - '1': '0.25rem', - // Purposely setting to something different from the v3 default - '1/2': '60%', - '1.5': '0.375rem', - }, - }, + let input = css` + @tailwind utilities; + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('width') }, + ) + }, + { + theme: { + extend: { + width: { + '1': '0.25rem', + // Purposely setting to something different from the v3 default + '1/2': '60%', + '1.5': '0.375rem', }, }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; - } + }, + }, + ), + } + }, + }) + expect(compiler.build(['my-width-1', 'my-width-1/2', 'my-width-1.5'])).toMatchInlineSnapshot( + ` + ".my-width-1 { + width: 0.25rem; + } .my-width-1\\.5 { - width: .375rem; + width: 0.375rem; } - .my-width-1\\/2 { width: 60%; } " - `) + `, + ) }) test('can use escaped CSS variables in theme values', async () => { - expect( - await run( - ['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; - @theme { - --width-1: 0.25rem; - /* Purposely setting to something different from the v3 default */ - --width-1\/2: 60%; - --width-1\.5: 0.375rem; - --width-2_5: 0.625rem; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('width') }, - ) - }), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; + @theme { + --width-1: 0.25rem; + /* Purposely setting to something different from the v3 default */ + --width-1\/2: 60%; + --width-1\.5: 0.375rem; + --width-2_5: 0.625rem; } + ` - .my-width-1\\.5 { - width: .375rem; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin(function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('width') }, + ) + }), + } + }, + }) - .my-width-1\\/2 { - width: 60%; - } - - .my-width-2\\.5 { - width: .625rem; - } - " - `) + expect(compiler.build(['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'])) + .toMatchInlineSnapshot(` + ".my-width-1 { + width: 0.25rem; + } + .my-width-1\\.5 { + width: 0.375rem; + } + .my-width-1\\/2 { + width: 60%; + } + .my-width-2\\.5 { + width: 0.625rem; + } + " + `) }) test('can use escaped CSS variables in referenced theme namespace', async () => { - expect( - await run( - ['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'], - css` - @tailwind utilities; - @plugin "my-plugin"; + let input = css` + @tailwind utilities; + @plugin "my-plugin"; - @theme { - --width-1: 0.25rem; - /* Purposely setting to something different from the v3 default */ - --width-1\/2: 60%; - --width-1\.5: 0.375rem; - --width-2_5: 0.625rem; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin( - function ({ matchUtilities, theme }) { - matchUtilities( - { 'my-width': (value) => ({ width: value }) }, - { values: theme('myWidth') }, - ) - }, - { - theme: { myWidth: ({ theme }) => theme('width') }, - }, - ), - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .my-width-1 { - width: .25rem; + @theme { + --width-1: 0.25rem; + /* Purposely setting to something different from the v3 default */ + --width-1\/2: 60%; + --width-1\.5: 0.375rem; + --width-2_5: 0.625rem; } + ` - .my-width-1\\.5 { - width: .375rem; - } + let compiler = await compile(input, { + loadModule: async (id, base) => { + return { + path: '', + base, + module: plugin( + function ({ matchUtilities, theme }) { + matchUtilities( + { 'my-width': (value) => ({ width: value }) }, + { values: theme('myWidth') }, + ) + }, + { + theme: { myWidth: ({ theme }) => theme('width') }, + }, + ), + } + }, + }) - .my-width-1\\/2 { - width: 60%; - } - - .my-width-2\\.5 { - width: .625rem; - } - " - `) + expect(compiler.build(['my-width-1', 'my-width-1.5', 'my-width-1/2', 'my-width-2.5'])) + .toMatchInlineSnapshot(` + ".my-width-1 { + width: 0.25rem; + } + .my-width-1\\.5 { + width: 0.375rem; + } + .my-width-1\\/2 { + width: 60%; + } + .my-width-2\\.5 { + width: 0.625rem; + } + " + `) }) }) describe('addBase', () => { test('does not create rules when imported via `@import "…" reference`', async () => { - expect( - await compileCss( - css` - @tailwind utilities; - @plugin "outside"; - @import './inside.css' reference; - `, - { - loadModule: async (_id, base) => { - if (_id === 'inside') { - return { - path: '', - base, - module: plugin(function ({ addBase }) { - addBase({ inside: { color: 'red' } }) - }), - } - } - return { - path: '', - base, - module: plugin(function ({ addBase }) { - addBase({ outside: { color: 'red' } }) - }), - } - }, - async loadStylesheet() { - return { - path: '', - base: '', - content: css` - @plugin "inside"; - `, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + let input = css` + @tailwind utilities; + @plugin "outside"; + @import './inside.css' reference; + ` + + let compiler = await compile(input, { + loadModule: async (id, base) => { + if (id === 'inside') { + return { + path: '', + base, + module: plugin(function ({ addBase }) { + addBase({ inside: { color: 'red' } }) + }), + } + } + return { + path: '', + base, + module: plugin(function ({ addBase }) { + addBase({ outside: { color: 'red' } }) + }), + } + }, + async loadStylesheet() { + return { + path: '', + base: '', + content: css` + @plugin "inside"; + `, + } + }, + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { outside { color: red; } @@ -1491,24 +1553,28 @@ describe('addBase', () => { }) test('does not modify CSS variables', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ ':root': { '--PascalCase': '1', '--camelCase': '1', '--UPPERCASE': '1' } }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { + let input = css` + @plugin "my-plugin"; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + path: '', + base: '/root', + module: plugin(function ({ addBase }) { + addBase({ + ':root': { + '--PascalCase': '1', + '--camelCase': '1', + '--UPPERCASE': '1', + }, + }) + }), + }), + }) + + expect(compiler.build([])).toMatchInlineSnapshot(` + "@layer base { :root { --PascalCase: 1; --camelCase: 1; @@ -1518,321 +1584,154 @@ describe('addBase', () => { " `) }) - - test('@variant is replaced inside rules using addBase', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - @theme { - --breakpoint-supertiny: 128px; - } - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ - ':root': { - '@variant supertiny': { - '--PascalCase': '1', - '--camelCase': '1', - '--UPPERCASE': '1', - }, - '@variant data-enabled:focus, disabled': { - '--x': '1', - }, - }, - }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { - @media (min-width: 128px) { - :root { - --PascalCase: 1; - --camelCase: 1; - --UPPERCASE: 1; - } - } - - :root[data-enabled]:focus, :root:disabled { - --x: 1; - } - } - " - `) - }) - - test('@variant inside addBase works with custom variants', async () => { - expect( - await compileCss( - css` - @plugin "my-plugin"; - - @custom-variant custom (&.custom); - `, - { - loadModule: async () => ({ - path: '', - base: '/root', - module: plugin(function ({ addBase }) { - addBase({ - ':root': { - '@variant custom': { - '--x': '1', - }, - }, - }) - }), - }), - }, - ), - ).toMatchInlineSnapshot(` - " - @layer base { - :root.custom { - --x: 1; - } - } - " - `) - }) }) describe('addVariant', () => { test('addVariant with string selector', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', '&:hover, &:focus') + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', '&:hover, &:focus') - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:is(:where(.group):hover, :where(.group):focus) .group-hocus\\:flex) { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:is(:where(.group):hover, :where(.group):focus) *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " - `) - }) - - test('addVariant with `@scope`', async () => { - expect( - await run( - ['blue:underline', 'green:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('blue', '@scope ([data-theme=blue]) to ([data-theme])') - addVariant('green', '@scope ([data-theme=green]) to ([data-theme]) { & }') - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope ([data-theme="blue"]) to ([data-theme]) { - .blue\\:underline { - text-decoration-line: underline; - } - } - - @scope ([data-theme="green"]) to ([data-theme]) { - .green\\:underline { - text-decoration-line: underline; - } - } - } - " - `) - }) - - test('matchVariant with `@scope`', async () => { - expect( - await run( - ['theme-[blue]:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('theme', (value) => `@scope ([data-theme=${value}]) to ([data-theme])`) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - @scope ([data-theme="blue"]) to ([data-theme]) { - .theme-\\[blue\\]\\:underline { - text-decoration-line: underline; - } - } - } - " + }" `) }) test('addVariant with array of selectors', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', ['&:hover', '&:focus']) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', ['&:hover', '&:focus']) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) { + }, + ) + + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with object syntax and @slot', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '&:hover': '@slot', + '&:focus': '@slot', + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '&:hover': '@slot', - '&:focus': '@slot', - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):hover .group-hocus\\:flex), :is(:where(.group):focus .group-hocus\\:flex) { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { + .group-hocus\\:flex:is(:where(.group):hover *), .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with object syntax, media, nesting and multiple @slot', async () => { - expect( - await run( - ['hocus:underline', 'group-hocus:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '@media (hover: hover)': { + '&:hover': '@slot', + }, + '&:focus': '@slot', + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '@media (hover: hover)': { - '&:hover': '@slot', - }, - '&:focus': '@slot', - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['hocus:underline', 'group-hocus:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (hover: hover) { - :is(:where(.group):hover .group-hocus\\:flex) { + .group-hocus\\:flex:is(:where(.group):hover *) { display: flex; } } - :is(:where(.group):focus .group-hocus\\:flex) { + .group-hocus\\:flex:is(:where(.group):focus *) { display: flex; } @@ -1845,39 +1744,37 @@ describe('addVariant', () => { .hocus\\:underline:focus { text-decoration-line: underline; } - } - " + }" `) }) test('addVariant with at-rules and placeholder', async () => { - expect( - await run( - ['potato:underline', 'potato:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant( + 'potato', + '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }', + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant( - 'potato', - '@media (max-width: 400px) { @supports (font:bold) { &:large-potato } }', - ) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato:underline', 'potato:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (max-width: 400px) { @supports (font: bold) { .potato\\:flex:large-potato { @@ -1889,42 +1786,40 @@ describe('addVariant', () => { } } } - } - " + }" `) }) test('@slot is preserved when used as a custom property value', async () => { - expect( - await run( - ['hocus:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ addVariant }: PluginAPI) => { + addVariant('hocus', { + '&': { + '--custom-property': '@slot', + '&:hover': '@slot', + '&:focus': '@slot', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('hocus', { - '&': { - '--custom-property': '@slot', - '&:hover': '@slot', - '&:focus': '@slot', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['hocus:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .hocus\\:underline { --custom-property: @slot; } @@ -1932,73 +1827,36 @@ describe('addVariant', () => { .hocus\\:underline:hover, .hocus\\:underline:focus { text-decoration-line: underline; } - } - " - `) - }) - - test('ignores variants that use :merge(…) and ensures `peer-*` and `group-*` rules work out of the box', async () => { - expect( - await run( - ['optional:flex', 'group-optional:flex', 'peer-optional:flex', 'group-optional/foo:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ addVariant }: PluginAPI) => { - addVariant('optional', '&:optional') - addVariant('group-optional', { ':merge(.group):optional &': '@slot' }) - addVariant('peer-optional', { '&': { ':merge(.peer):optional ~ &': '@slot' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):optional .group-optional\\:flex), :is(:where(.group\\/foo):optional .group-optional\\/foo\\:flex), :is(:where(.peer):optional ~ .peer-optional\\:flex), .optional\\:flex:optional { - display: flex; - } - } - " + }" `) }) }) describe('matchVariant', () => { test('partial arbitrary variants', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('potato', (flavor) => `.potato-${flavor} &`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => `.potato-${flavor} &`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .potato-baked .potato-\\[baked\\]\\:flex { display: flex; } @@ -2006,36 +1864,34 @@ describe('matchVariant', () => { .potato-yellow .potato-\\[yellow\\]\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('partial arbitrary variants with at-rules', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (potato: baked) { .potato-\\[baked\\]\\:flex { display: flex; @@ -2047,38 +1903,38 @@ describe('matchVariant', () => { text-decoration-line: underline; } } - } - " + }" `) }) test('partial arbitrary variants with at-rules and placeholder', async () => { - expect( - await run( - ['potato-[yellow]:underline', 'potato-[baked]:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant( + 'potato', + (flavor) => + `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }`, + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('potato', (flavor) => { - return `@media (potato: ${flavor}) { @supports (font:bold) { &:large-potato } }` - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['potato-[yellow]:underline', 'potato-[baked]:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (potato: baked) { @supports (font: bold) { .potato-\\[baked\\]\\:flex:large-potato { @@ -2094,41 +1950,39 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('partial arbitrary variants with default values', async () => { - expect( - await run( - ['tooltip-bottom:underline', 'tooltip-top:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('tooltip', (side) => `&${side}`, { + values: { + bottom: '[data-location="bottom"]', + top: '[data-location="top"]', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('tooltip', (side) => `&${side}`, { - values: { - bottom: '[data-location="bottom"]', - top: '[data-location="top"]', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['tooltip-bottom:underline', 'tooltip-top:flex']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .tooltip-bottom\\:underline[data-location="bottom"] { text-decoration-line: underline; } @@ -2136,120 +1990,118 @@ describe('matchVariant', () => { .tooltip-top\\:flex[data-location="top"] { display: flex; } - } - " + }" `) }) test('matched variant values maintain the sort order they are registered in', async () => { - expect( - await run( - [ - 'alphabet-c:underline', - 'alphabet-a:underline', - 'alphabet-d:underline', - 'alphabet-b:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('alphabet', (side) => `&${side}`, { + values: { + d: '[data-order="1"]', + a: '[data-order="2"]', + c: '[data-order="3"]', + b: '[data-order="4"]', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('alphabet', (side) => `&${side}`, { - values: { - d: '[data-order="1"]', - a: '[data-order="2"]', - c: '[data-order="3"]', - b: '[data-order="4"]', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'alphabet-c:underline', + 'alphabet-a:underline', + 'alphabet-d:underline', + 'alphabet-b:underline', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .alphabet-d\\:underline[data-order="1"], .alphabet-a\\:underline[data-order="2"], .alphabet-c\\:underline[data-order="3"], .alphabet-b\\:underline[data-order="4"] { text-decoration-line: underline; } - } - " + }" `) }) test('matchVariant can return an array of format strings from the function', async () => { - expect( - await run( - ['test-[a,b,c]:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('test', (selector) => + selector.split(',').map((selector) => `&.${selector} > *`), + ) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('test', (selector) => - selector.split(',').map((selector) => `&.${selector} > *`), - ) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['test-[a,b,c]:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .test-\\[a\\,b\\,c\\]\\:underline.a > *, .test-\\[a\\,b\\,c\\]\\:underline.b > *, .test-\\[a\\,b\\,c\\]\\:underline.c > * { text-decoration-line: underline; } - } - " + }" `) }) test('should be possible to sort variants', async () => { - expect( - await run( - ['testmin-[600px]:flex', 'testmin-[500px]:underline', 'testmin-[700px]:italic'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmin-[600px]:flex', + 'testmin-[500px]:underline', + 'testmin-[700px]:italic', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 500px) { .testmin-\\[500px\\]\\:underline { text-decoration-line: underline; @@ -2267,41 +2119,45 @@ describe('matchVariant', () => { font-style: italic; } } - } - " + }" `) }) test('should be possible to compare arbitrary variants and hardcoded variants', async () => { - expect( - await run( - ['testmin-[700px]:italic', 'testmin-example:italic', 'testmin-[500px]:italic'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + values: { + example: '600px', + }, + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - values: { example: '600px' }, - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmin-[700px]:italic', + 'testmin-example:italic', + 'testmin-[500px]:italic', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 500px) { .testmin-\\[500px\\]\\:italic { font-style: italic; @@ -2319,51 +2175,50 @@ describe('matchVariant', () => { font-style: italic; } } - } - " + }" `) }) test('should be possible to sort stacked arbitrary variants correctly', async () => { - expect( - await run( - [ - 'testmin-[150px]:testmax-[400px]:order-2', - 'testmin-[100px]:testmax-[350px]:order-3', - 'testmin-[100px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + let compiled = build([ + 'testmin-[150px]:testmax-[400px]:order-2', + 'testmin-[100px]:testmax-[350px]:order-3', + 'testmin-[100px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 100px) { @media (max-width: 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { @@ -2393,50 +2248,48 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should maintain sort from other variants, if sort functions of arbitrary variants return 0', async () => { - // Expect :focus to come after :hover - expect( - await run( - [ - 'testmin-[100px]:testmax-[200px]:focus:underline', - 'testmin-[100px]:testmax-[200px]:hover:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmin-[100px]:testmax-[200px]:focus:underline', + 'testmin-[100px]:testmax-[200px]:hover:underline', + ]) + + // Expect :focus to come after :hover + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 100px) { @media (max-width: 200px) { @media (hover: hover) { @@ -2450,50 +2303,48 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should sort arbitrary variants left to right (1)', async () => { - expect( - await run( - [ - 'testmin-[200px]:testmax-[400px]:order-2', - 'testmin-[200px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - 'testmin-[100px]:testmax-[300px]:order-3', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmin-[200px]:testmax-[400px]:order-2', + 'testmin-[200px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + 'testmin-[100px]:testmax-[300px]:order-3', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 100px) { @media (max-width: 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { @@ -2525,50 +2376,48 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should sort arbitrary variants left to right (2)', async () => { - expect( - await run( - [ - 'testmax-[400px]:testmin-[200px]:underline', - 'testmax-[300px]:testmin-[200px]:underline', - 'testmax-[400px]:testmin-[100px]:underline', - 'testmax-[300px]:testmin-[100px]:underline', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmax-[400px]:testmin-[200px]:underline', + 'testmax-[300px]:testmin-[200px]:underline', + 'testmax-[400px]:testmin-[100px]:underline', + 'testmax-[300px]:testmin-[100px]:underline', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (max-width: 400px) { @media (min-width: 100px) { .testmax-\\[400px\\]\\:testmin-\\[100px\\]\\:underline { @@ -2596,58 +2445,56 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should guarantee that we are not passing values from other variants to the wrong function', async () => { - expect( - await run( - [ - 'testmin-[200px]:testmax-[400px]:order-2', - 'testmin-[200px]:testmax-[300px]:order-4', - 'testmin-[100px]:testmax-[400px]:order-1', - 'testmin-[100px]:testmax-[300px]:order-3', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + let lookup = ['100px', '200px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') + } + return lookup.indexOf(a.value) - lookup.indexOf(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + let lookup = ['300px', '400px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') + } + return lookup.indexOf(z.value) - lookup.indexOf(a.value) + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - let lookup = ['100px', '200px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(a.value) - lookup.indexOf(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - let lookup = ['300px', '400px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(z.value) - lookup.indexOf(a.value) - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build([ + 'testmin-[200px]:testmax-[400px]:order-2', + 'testmin-[200px]:testmax-[300px]:order-4', + 'testmin-[100px]:testmax-[400px]:order-1', + 'testmin-[100px]:testmax-[300px]:order-3', + ]) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { @media (min-width: 100px) { @media (max-width: 400px) { .testmin-\\[100px\\]\\:testmax-\\[400px\\]\\:order-1 { @@ -2679,178 +2526,170 @@ describe('matchVariant', () => { } } } - } - " + }" `) }) test('should default to the DEFAULT value for variants', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value} &`, { + values: { + DEFAULT: '.bar', + }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value} &`, { - values: { DEFAULT: '.bar' }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo.bar .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should not generate anything if the matchVariant does not have a DEFAULT value configured', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value} &`) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value} &`) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities; - " - `) + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(`"@layer utilities;"`) }) test('should be possible to use `null` as a DEFAULT value', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { + values: { DEFAULT: null }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { - values: { DEFAULT: null }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo-good .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should be possible to use `undefined` as a DEFAULT value', async () => { - expect( - await run( - ['foo:underline'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let { build } = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { + values: { DEFAULT: undefined }, + }) + }, } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { - values: { DEFAULT: undefined }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + let compiled = build(['foo:underline']) + + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo-good .foo\\:underline { text-decoration-line: underline; } - } - " + }" `) }) test('should be called with eventual modifiers', async () => { - expect( - await run( - ['my-container-[250px]:underline', 'my-container-[250px]/placement:underline'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('my-container', (value, { modifier }) => { - function parseValue(value: string) { - const numericValue = value.match(/^(\d+\.\d+|\d+|\.\d+)\D+/)?.[1] ?? null - if (numericValue === null) return null + let { build } = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + loadModule: async (id, base) => { + return { + path: '', + base, + module: ({ matchVariant }: PluginAPI) => { + matchVariant('my-container', (value, { modifier }) => { + function parseValue(value: string) { + const numericValue = value.match(/^(\d+\.\d+|\d+|\.\d+)\D+/)?.[1] ?? null + if (numericValue === null) return null - return parseFloat(value) - } + return parseFloat(value) + } - const parsed = parseValue(value) - return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : [] - }) - }, - } - }, + const parsed = parseValue(value) + return parsed !== null ? `@container ${modifier ?? ''} (min-width: ${value})` : [] + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - @container (min-width: 250px) { + }, + ) + let compiled = build([ + 'my-container-[250px]:underline', + 'my-container-[250px]/placement:underline', + ]) + expect(optimizeCss(compiled).trim()).toMatchInlineSnapshot(` + "@container (min-width: 250px) { .my-container-\\[250px\\]\\:underline { text-decoration-line: underline; } @@ -2860,372 +2699,236 @@ describe('matchVariant', () => { .my-container-\\[250px\\]\\/placement\\:underline { text-decoration-line: underline; } - } - " - `) - }) - - test('ignores variants that use :merge(…)', async () => { - expect( - await run( - [ - 'optional-[test]:flex', - 'group-optional-[test]:flex', - 'peer-optional-[test]:flex', - 'group-optional-[test]/foo:flex', - ], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('optional', (flavor) => `&:optional:has(${flavor}) &`) - matchVariant( - 'group-optional', - (flavor) => `:merge(.group):optional:has(${flavor}) &`, - ) - matchVariant( - 'peer-optional', - (flavor) => `:merge(.peer):optional:has(${flavor}) ~ &`, - ) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - :is(:where(.group):optional:has(test) :where(.group) .group-optional-\\[test\\]\\:flex), :is(:where(.group\\/foo):optional:has(test) :where(.group\\/foo) .group-optional-\\[test\\]\\/foo\\:flex), :is(:where(.peer):optional:has(test) :where(.peer) ~ .peer-optional-\\[test\\]\\:flex), .optional-\\[test\\]\\:flex:optional:has(test) .optional-\\[test\\]\\:flex { - display: flex; - } - } - " - `) - }) - - test('ignores variants that use unknown values', async () => { - expect( - await run( - ['foo-[test]:flex', 'foo-known:flex', 'foo-unknown:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (flavor) => `&:is(${flavor})`, { - values: { known: 'known' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo-known\\:flex:is(known), .foo-\\[test\\]\\:flex:is(test) { - display: flex; - } - } - " - `) - }) - - test('ignores variants that produce non-string values', async () => { - expect( - await run( - ['foo-[test]:flex', 'foo-string:flex', 'foo-object:flex'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: ({ matchVariant }: PluginAPI) => { - matchVariant('foo', (flavor) => `&:is(${flavor})`, { - values: { - string: 'some string', - object: { some: 'object' }, - }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo-string\\:flex:is(some string), .foo-\\[test\\]\\:flex:is(test) { - display: flex; - } - } - " + }" `) }) }) describe('addUtilities()', () => { test('custom static utility', async () => { - expect( - await run( - ['text-trim', 'lg:text-trim'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - - @theme reference { - --breakpoint-lg: 1024px; - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.text-trim': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .text-trim { - text-box-trim: both; - text-box-edge: cap alphabetic; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; } - @media (min-width: 1024px) { - .lg\\:text-trim { + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.text-trim': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', + }, + }) + }, + } + }, + }, + ) + + expect(optimizeCss(compiled.build(['text-trim', 'lg:text-trim'])).trim()) + .toMatchInlineSnapshot(` + "@layer utilities { + .text-trim { text-box-trim: both; text-box-edge: cap alphabetic; } - } - } - " - `) + + @media (min-width: 1024px) { + .lg\\:text-trim { + text-box-trim: both; + text-box-edge: cap alphabetic; + } + } + }" + `) }) test('return multiple rule objects from a custom utility', async () => { - expect( - await run( - ['text-trim'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.text-trim': [ - { 'text-box-trim': 'both' }, - { 'text-box-edge': 'cap alphabetic' }, - ], - }, - ]) - }, - } - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.text-trim': [ + { 'text-box-trim': 'both' }, + { 'text-box-edge': 'cap alphabetic' }, + ], + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .text-trim { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim'])).trim()).toMatchInlineSnapshot(` + ".text-trim { text-box-trim: both; text-box-edge: cap alphabetic; - } - " + }" `) }) test('define multiple utilities with array syntax', async () => { - expect( - await run( - ['text-trim', 'text-trim-2'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.text-trim': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.text-trim': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', }, - { - '.text-trim-2': { - 'text-box-trim': 'both', - 'text-box-edge': 'cap alphabetic', - }, + }, + { + '.text-trim-2': { + 'text-box-trim': 'both', + 'text-box-edge': 'cap alphabetic', }, - ]) - }, - } - }, + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .text-trim, .text-trim-2 { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim', 'text-trim-2'])).trim()).toMatchInlineSnapshot(` + ".text-trim, .text-trim-2 { text-box-trim: both; text-box-edge: cap alphabetic; - } - " + }" `) }) test('utilities can use arrays for fallback declaration values', async () => { - expect( - await run( - ['outlined'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities([ - { - '.outlined': { - outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'], - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities([ + { + '.outlined': { + outline: ['1px solid ButtonText', '1px auto -webkit-focus-ring-color'], }, - ]) - }, - } - }, + }, + ]) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .outlined { + }, + ) + + expect(optimizeCss(compiled.build(['outlined'])).trim()).toMatchInlineSnapshot(` + ".outlined { outline: 1px solid buttontext; outline: 1px auto -webkit-focus-ring-color; - } - " + }" `) }) test('camel case properties are converted to kebab-case', async () => { - expect( - await run( - ['text-trim'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.text-trim': { + WebkitAppearance: 'none', + textBoxTrim: 'both', + textBoxEdge: 'cap alphabetic', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.text-trim': { - WebkitAppearance: 'none', - textBoxTrim: 'both', - textBoxEdge: 'cap alphabetic', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + expect(optimizeCss(compiled.build(['text-trim'])).trim()).toMatchInlineSnapshot(` + "@layer utilities { .text-trim { -webkit-appearance: none; text-box-trim: both; text-box-edge: cap alphabetic; } - } - " + }" `) }) test('custom static utilities support `@apply`', async () => { - expect( - await run( - ['foo', 'lg:foo'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.foo': { + '@apply flex dark:underline': {}, + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo': { - '@apply flex dark:underline': {}, - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { + }, + ) + + expect(optimizeCss(compiled.build(['foo', 'lg:foo'])).trim()).toMatchInlineSnapshot(` + "@layer utilities { .foo { display: flex; } @@ -3247,8 +2950,7 @@ describe('addUtilities()', () => { } } } - } - " + }" `) }) @@ -3266,7 +2968,7 @@ describe('addUtilities()', () => { } `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -3282,336 +2984,360 @@ describe('addUtilities()', () => { }, }, ) - }).rejects.toThrow(/invalid utility selector/) + }).rejects.toThrowError(/invalid utility selector/) }) test('supports multiple selector names', async () => { - expect( - await run( - ['form-input', 'lg:form-textarea'], - css` - @plugin "my-plugin"; - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.form-input, .form-textarea': { + appearance: 'none', + 'background-color': '#fff', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.form-input, .form-textarea': { - appearance: 'none', - 'background-color': '#fff', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .form-input { - appearance: none; - background-color: #fff; - } + }, + ) - @media (min-width: 1024px) { - .lg\\:form-textarea { + expect(optimizeCss(compiled.build(['form-input', 'lg:form-textarea'])).trim()) + .toMatchInlineSnapshot(` + ".form-input { appearance: none; background-color: #fff; } - } - " - `) + + @media (min-width: 1024px) { + .lg\\:form-textarea { + appearance: none; + background-color: #fff; + } + }" + `) }) test('supports pseudo classes and pseudo elements', async () => { - expect( - await run( - ['form-input', 'lg:form-textarea'], - css` - @plugin "my-plugin"; - @tailwind utilities; + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.form-input, .form-input::placeholder, .form-textarea:hover:focus': { + 'background-color': 'red', + }, + }) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.form-input, .form-input::placeholder, .form-textarea:hover:focus': { - 'background-color': 'red', - }, - }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .form-input, .form-input::placeholder { - background-color: red; - } + }, + ) - @media (min-width: 1024px) { - .lg\\:form-textarea:hover:focus { + expect(compiled.build(['form-input', 'lg:form-textarea']).trim()).toMatchInlineSnapshot(` + ".form-input { + background-color: red; + &::placeholder { background-color: red; } } - " + .lg\\:form-textarea { + @media (width >= 1024px) { + &:hover:focus { + background-color: red; + } + } + }" `) }) test('nests complex utility names', async () => { - expect( - await run( - ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.a .b:hover .c': { color: 'red' }, - '.d > *': { color: 'red' }, - '.e .bar:not(.f):has(.g)': { color: 'red' }, - '.h~.i': { color: 'red' }, - '.j.j': { color: 'red' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .j.j, .a .b:hover .c, .d > *, .e .bar:not(.f):has(.g), .h ~ .i { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; } - } - " - `) - }) + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.a .b:hover .c': { + color: 'red', + }, + '.d > *': { + color: 'red', + }, + '.e .bar:not(.f):has(.g)': { + color: 'red', + }, + '.h~.i': { + color: 'red', + }, + '.j.j': { + color: 'red', + }, + }) + }, + } + }, + }, + ) - test('does not register class names inside `:nth-child(… of …)` as utilities', async () => { expect( - await run( - ['foo', 'mark'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; + compiled.build(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']).trim(), + ).toMatchInlineSnapshot( + ` + "@layer utilities { + .j { + &.j { + color: red; + } + .j& { + color: red; } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo:nth-child(2 of .mark)': { color: 'red' }, - }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .foo:nth-child(2 of .mark) { - color: red; } - } - " - `) + .a { + & .b:hover .c { + color: red; + } + } + .b { + .a &:hover .c { + color: red; + } + } + .c { + .a .b:hover & { + color: red; + } + } + .d { + & > * { + color: red; + } + } + .e { + & .bar:not(.f):has(.g) { + color: red; + } + } + .g { + .e .bar:not(.f):has(&) { + color: red; + } + } + .h { + &~.i { + color: red; + } + } + .i { + .h~& { + color: red; + } + } + }" + `, + ) }) test('prefixes nested class names with the configured theme prefix', async () => { - expect( - await run( - ['tw:a', 'tw:b', 'tw:c', 'tw:d'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } - @theme prefix(tw) { - } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ '.a .b:hover .c.d': { color: 'red' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - @layer utilities { - .tw\\:a .tw\\:b:hover .tw\\:c.tw\\:d { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; } - } - " - `) + @theme prefix(tw) { + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.a .b:hover .c.d': { + color: 'red', + }, + }) + }, + } + }, + }, + ) + + expect(compiled.build(['tw:a', 'tw:b', 'tw:c', 'tw:d']).trim()).toMatchInlineSnapshot( + ` + "@layer utilities { + .tw\\:a { + & .tw\\:b:hover .tw\\:c.tw\\:d { + color: red; + } + } + .tw\\:b { + .tw\\:a &:hover .tw\\:c.tw\\:d { + color: red; + } + } + .tw\\:c { + .tw\\:a .tw\\:b:hover &.tw\\:d { + color: red; + } + } + .tw\\:d { + .tw\\:a .tw\\:b:hover .tw\\:c& { + color: red; + } + } + }" + `, + ) }) test('replaces the class name with variants in nested selectors', async () => { - expect( - await run( - ['foo', 'md:foo', 'not-hover:md:foo'], - css` - @plugin "my-plugin"; - @theme { - --breakpoint-md: 768px; + let compiled = await compile( + css` + @plugin "my-plugin"; + @theme { + --breakpoint-md: 768px; + } + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addUtilities }: PluginAPI) => { + addUtilities({ + '.foo': { + ':where(.foo > :first-child)': { + color: 'red', + }, + }, + }) + }, } - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ '.foo': { ':where(.foo > :first-child)': { color: 'red' } } }) - }, - } - }, }, - ), - ).toMatchInlineSnapshot(` - " - .foo :where(.foo > :first-child) { - color: red; - } + }, + ) - @media (min-width: 768px) { - .md\\:foo :where(.md\\:foo > :first-child), .not-hover\\:md\\:foo:not(:hover) :where(.not-hover\\:md\\:foo > :first-child) { + expect(compiled.build(['foo', 'md:foo', 'not-hover:md:foo']).trim()).toMatchInlineSnapshot(` + ".foo { + :where(.foo > :first-child) { color: red; } } - - @media not all and (hover: hover) { - @media (min-width: 768px) { - .not-hover\\:md\\:foo :where(.not-hover\\:md\\:foo > :first-child) { + .md\\:foo { + @media (width >= 768px) { + :where(.md\\:foo > :first-child) { color: red; } } } - " - `) - }) - - test('values that are `false`, `null`, or `undefined` are discarded from CSS object ASTs', async () => { - expect( - await run( - ['foo'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addUtilities }: PluginAPI) => { - addUtilities({ - '.foo': { - a: 'red', - // @ts-ignore: While this isn't valid per the types this did work in v3 - 'z-index': 0, - // @ts-ignore - '.bar': false, - // @ts-ignore - '.baz': null, - // @ts-ignore - '.qux': undefined, - }, - }) - }, + .not-hover\\:md\\:foo { + &:not(*:hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo > :first-child) { + color: red; } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .foo { - a: red; - z-index: 0; - } - " + } + } + @media not (hover: hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo > :first-child) { + color: red; + } + } + } + }" `) }) }) describe('matchUtilities()', () => { test('custom functional utility', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, - expect( - await run( - [ - 'border-block', - 'border-block-2', - 'border-block-[35px]', - 'border-block-[var(--foo)]', - 'lg:border-block-2', - ], - input, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { 'border-block': (value) => ({ 'border-block-width': value }) }, - { values: { DEFAULT: '1px', '2': '2px' } }, + { + 'border-block': (value) => ({ 'border-block-width': value }), + }, + { + values: { + DEFAULT: '1px', + '2': '2px', + }, + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run([ + 'border-block', + 'border-block-2', + 'border-block-[35px]', + 'border-block-[var(--foo)]', + 'lg:border-block-2', + ]), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { border-block-width: 1px; } @@ -3631,61 +3357,55 @@ describe('matchUtilities()', () => { .lg\\:border-block-2 { border-block-width: 2px; } - } - " + }" `) expect( - await run( - [ + optimizeCss( + await run([ '-border-block', '-border-block-2', 'lg:-border-block-2', 'border-block-unknown', 'border-block/1', - ], - input, + ]), + ).trim(), + ).toEqual('') + }) + + test('custom functional utilities can start with @', async () => { + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { 'border-block': (value) => ({ 'border-block-width': value }) }, - { values: { DEFAULT: '1px', '2': '2px' } }, + { '@w': (value) => ({ width: value }) }, + { + values: { + 1: '1px', + }, + }, ) }, } }, }, - ), - ).toEqual('') - }) + ) - test('custom functional utilities can start with @', async () => { - expect( - await run( - ['@w-1', 'hover:@w-1'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities({ '@w': (value) => ({ width: value }) }, { values: { 1: '1px' } }) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .\\@w-1 { + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['@w-1', 'hover:@w-1'])).trim()).toMatchInlineSnapshot(` + ".\\@w-1 { width: 1px; } @@ -3693,56 +3413,56 @@ describe('matchUtilities()', () => { .hover\\:\\@w-1:hover { width: 1px; } - } - " + }" `) }) test('custom functional utilities can return an array of rules', async () => { - expect( - await run( - ['all-but-order-bottom-left-radius'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { - 'all-but-order-bottom-left-radius': (value) => - [ - { 'border-top-left-radius': value }, - { 'border-top-right-radius': value }, - { 'border-bottom-right-radius': value }, - ] as CssInJs[], + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + 'all-but-order-bottom-left-radius': (value) => + [ + { 'border-top-left-radius': value }, + { 'border-top-right-radius': value }, + { 'border-bottom-right-radius': value }, + ] as CssInJs[], + }, + { + values: { + DEFAULT: '1px', }, - { values: { DEFAULT: '1px' } }, - ) - }, - } - }, + }, + ) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .all-but-order-bottom-left-radius { - border-top-left-radius: 1px; - border-top-right-radius: 1px; - border-bottom-right-radius: 1px; - } - " - `) + }, + ) + + expect(optimizeCss(compiled.build(['all-but-order-bottom-left-radius'])).trim()) + .toMatchInlineSnapshot(` + ".all-but-order-bottom-left-radius { + border-top-left-radius: 1px; + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; + }" + `) }) test('custom functional utility with any modifier', async () => { - expect( - await run( - ['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo'], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -3752,8 +3472,9 @@ describe('matchUtilities()', () => { --breakpoint-lg: 1024px; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -3765,16 +3486,30 @@ describe('matchUtilities()', () => { 'border-block-width': value, }), }, - { values: { DEFAULT: '1px', '2': '2px' }, modifiers: 'any' }, + { + values: { + DEFAULT: '1px', + '2': '2px', + }, + + modifiers: 'any', + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo']), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { --my-modifier: none; border-block-width: 1px; } @@ -3792,28 +3527,25 @@ describe('matchUtilities()', () => { .border-block\\/foo { --my-modifier: foo; border-block-width: 1px; - } - " + }" `) }) test('custom functional utility with known modifier', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, - expect( - await run( - ['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo'], - input, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -3826,18 +3558,31 @@ describe('matchUtilities()', () => { }), }, { - values: { DEFAULT: '1px', '2': '2px' }, - modifiers: { foo: 'foo' }, + values: { + DEFAULT: '1px', + '2': '2px', + }, + + modifiers: { + foo: 'foo', + }, }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['border-block', 'border-block-2', 'border-block/foo', 'border-block-2/foo']), + ).trim(), ).toMatchInlineSnapshot(` - " - .border-block { + ".border-block { --my-modifier: none; border-block-width: 1px; } @@ -3855,33 +3600,11 @@ describe('matchUtilities()', () => { .border-block\\/foo { --my-modifier: foo; border-block-width: 1px; - } - " + }" `) expect( - await run(['border-block/unknown', 'border-block-2/unknown'], input, { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { - 'border-block': (value, { modifier }) => ({ - '--my-modifier': modifier ?? 'none', - 'border-block-width': value, - }), - }, - { - values: { DEFAULT: '1px', '2': '2px' }, - modifiers: { foo: 'foo' }, - }, - ) - }, - } - }, - }), + optimizeCss(await run(['border-block/unknown', 'border-block-2/unknown'])).trim(), ).toEqual('') }) @@ -3889,127 +3612,194 @@ describe('matchUtilities()', () => { // future don't be afraid to change what should happen in this scenario. describe('plugins that handle a specific arbitrary value type prevent falling through to other plugins if the result is invalid for that plugin', () => { test('implicit color modifier', async () => { - let input = css` - @tailwind utilities; - @plugin "my-plugin"; - ` - - let options: Partial[1]> = { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'] }, - ) - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'] }, - ) - }, - } - }, - } - - expect( - await run(['scrollbar-[2px]', 'scrollbar-[#08c]', 'scrollbar-[#08c]/50'], input, options), - ).toMatchInlineSnapshot(` - " - .scrollbar-\\[2px\\] { - scrollbar-width: 2px; - } - - .scrollbar-\\[\\#08c\\] { - scrollbar-color: #08c; - } - - .scrollbar-\\[\\#08c\\]\\/50 { - scrollbar-color: oklab(59.9824% -.067 -.124 / .5); - } - " - `) - expect(await run(['scrollbar-[2px]/50'], input, options)).toEqual('') - }) - - test('no modifiers are supported by the plugins', async () => { - expect( - await run( - ['scrollbar-[2px]/50'], + async function run(candidates: string[]) { + let compiled = await compile( css` @tailwind utilities; @plugin "my-plugin"; `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ '--scrollbar-angle': value }) }, - { type: ['angle', 'any'] }, + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { type: ['color', 'any'] }, ) matchUtilities( - { scrollbar: (value) => ({ '--scrollbar-width': value }) }, + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, { type: ['length'] }, ) }, } }, }, - ), - ).toEqual('') + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['scrollbar-[2px]', 'scrollbar-[#08c]', 'scrollbar-[#08c]/50']), + ).trim(), + ).toMatchInlineSnapshot(` + ".scrollbar-\\[\\#08c\\] { + scrollbar-color: #08c; + } + + .scrollbar-\\[\\#08c\\]\\/50 { + scrollbar-color: oklab(59.9824% -.067 -.124 / .5); + } + + .scrollbar-\\[2px\\] { + scrollbar-width: 2px; + }" + `) + expect(optimizeCss(await run(['scrollbar-[2px]/50'])).trim()).toEqual('') }) - test('invalid named modifier', async () => { - expect( - await run( - ['scrollbar-[2px]/foo'], + test('no modifiers are supported by the plugins', async () => { + async function run(candidates: string[]) { + let compiled = await compile( css` @tailwind utilities; @plugin "my-plugin"; `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, + { + scrollbar: (value) => ({ '--scrollbar-angle': value }), + }, + { type: ['angle', 'any'] }, + ) + + matchUtilities( + { + scrollbar: (value) => ({ '--scrollbar-width': value }), + }, + { type: ['length'] }, + ) + }, + } + }, + }, + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['scrollbar-[2px]/50'])).trim()).toEqual('') + }) + + test('invalid named modifier', async () => { + async function run(candidates: string[]) { + let compiled = await compile( + css` + @tailwind utilities; + @plugin "my-plugin"; + `, + + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, { type: ['color', 'any'], modifiers: { foo: 'foo' } }, ) matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, { type: ['length'], modifiers: { bar: 'bar' } }, ) }, } }, }, - ), - ).toEqual('') + ) + + return compiled.build(candidates) + } + + expect(optimizeCss(await run(['scrollbar-[2px]/foo'])).trim()).toEqual('') }) }) test('custom functional utilities with different types', async () => { - let input = css` - @plugin "my-plugin"; + async function run(candidates: string[]) { + let compiled = await compile( + css` + @plugin "my-plugin"; - @tailwind utilities; + @tailwind utilities; - @theme reference { - --breakpoint-lg: 1024px; - } - ` + @theme reference { + --breakpoint-lg: 1024px; + } + `, + + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { + type: ['color', 'any'], + values: { + black: 'black', + }, + }, + ) + + matchUtilities( + { + scrollbar: (value) => ({ 'scrollbar-width': value }), + }, + { + type: ['length'], + values: { + 2: '2px', + }, + }, + ) + }, + } + }, + }, + ) + + return compiled.build(candidates) + } expect( - await run( - [ + optimizeCss( + await run([ 'scrollbar-black', 'scrollbar-black/50', 'scrollbar-2', @@ -4021,38 +3811,13 @@ describe('matchUtilities()', () => { 'scrollbar-[color:var(--my-color)]', 'scrollbar-[color:var(--my-color)]/50', 'scrollbar-[length:var(--my-width)]', - ], - input, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, - ) - - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'], values: { 2: '2px' } }, - ) - }, - } - }, - }, - ), + ]), + ).trim(), ).toMatchInlineSnapshot(` - " - .scrollbar-2, .scrollbar-\\[2px\\] { + ".scrollbar-2 { scrollbar-width: 2px; } - .scrollbar-\\[length\\:var\\(--my-width\\)\\] { - scrollbar-width: var(--my-width); - } - .scrollbar-\\[\\#fff\\] { scrollbar-color: #fff; } @@ -4061,6 +3826,10 @@ describe('matchUtilities()', () => { scrollbar-color: oklab(100% 0 5.96046e-8 / .5); } + .scrollbar-\\[2px\\] { + scrollbar-width: 2px; + } + .scrollbar-\\[color\\:var\\(--my-color\\)\\], .scrollbar-\\[color\\:var\\(--my-color\\)\\]\\/50 { scrollbar-color: var(--my-color); } @@ -4071,6 +3840,10 @@ describe('matchUtilities()', () => { } } + .scrollbar-\\[length\\:var\\(--my-width\\)\\] { + scrollbar-width: var(--my-width); + } + .scrollbar-\\[var\\(--my-color\\)\\], .scrollbar-\\[var\\(--my-color\\)\\]\\/50 { scrollbar-color: var(--my-color); } @@ -4087,48 +3860,23 @@ describe('matchUtilities()', () => { .scrollbar-black\\/50 { scrollbar-color: oklab(0% none none / .5); - } - " + }" `) expect( - await run( - ['scrollbar-2/50', 'scrollbar-[2px]/50', 'scrollbar-[length:var(--my-width)]/50'], - input, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, - ) - - matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-width': value }) }, - { type: ['length'], values: { 2: '2px' } }, - ) - }, - } - }, - }, - ), + optimizeCss( + await run([ + 'scrollbar-2/50', + 'scrollbar-[2px]/50', + 'scrollbar-[length:var(--my-width)]/50', + ]), + ).trim(), ).toEqual('') }) test('functional utilities with `type: color` automatically support opacity', async () => { - expect( - await run( - [ - 'scrollbar-current', - 'scrollbar-current/45', - 'scrollbar-black', - 'scrollbar-black/33', - 'scrollbar-black/[50%]', - 'scrollbar-[var(--my-color)]/[25%]', - ], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -4138,24 +3886,46 @@ describe('matchUtilities()', () => { --breakpoint-lg: 1024px; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, module: ({ matchUtilities }: PluginAPI) => { matchUtilities( - { scrollbar: (value) => ({ 'scrollbar-color': value }) }, - { type: ['color', 'any'], values: { black: 'black' } }, + { + scrollbar: (value) => ({ 'scrollbar-color': value }), + }, + { + type: ['color', 'any'], + values: { + black: 'black', + }, + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run([ + 'scrollbar-current', + 'scrollbar-current/45', + 'scrollbar-black', + 'scrollbar-black/33', + 'scrollbar-black/[50%]', + 'scrollbar-[var(--my-color)]/[25%]', + ]), + ).trim(), ).toMatchInlineSnapshot(` - " - .scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] { + ".scrollbar-\\[var\\(--my-color\\)\\]\\/\\[25\\%\\] { scrollbar-color: var(--my-color); } @@ -4185,15 +3955,13 @@ describe('matchUtilities()', () => { .scrollbar-current\\/45 { scrollbar-color: color-mix(in oklab, currentcolor 45%, transparent); } - } - " + }" `) }) test('functional utilities with explicit modifiers', async () => { - expect( - await run( - ['scrollbar-[12px]', 'scrollbar-[12px]/foo', 'scrollbar-[12px]/bar'], + async function run(candidates: string[]) { + let compiled = await compile( css` @plugin "my-plugin"; @@ -4204,8 +3972,9 @@ describe('matchUtilities()', () => { --opacity-my-opacity: 0.5; } `, + { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4217,16 +3986,29 @@ describe('matchUtilities()', () => { 'scrollbar-width': value, }), }, - { type: ['any'], values: {}, modifiers: { foo: 'foo' } }, + { + type: ['any'], + values: {}, + modifiers: { + foo: 'foo', + }, + }, ) }, } }, }, - ), + ) + + return compiled.build(candidates) + } + + expect( + optimizeCss( + await run(['scrollbar-[12px]', 'scrollbar-[12px]/foo', 'scrollbar-[12px]/bar']), + ).trim(), ).toMatchInlineSnapshot(` - " - .scrollbar-\\[12px\\] { + ".scrollbar-\\[12px\\] { --modifier: none; scrollbar-width: 12px; } @@ -4234,43 +4016,51 @@ describe('matchUtilities()', () => { .scrollbar-\\[12px\\]\\/foo { --modifier: foo; scrollbar-width: 12px; - } - " + }" `) }) test('functional utilities support `@apply`', async () => { - expect( - await run( - ['foo-bar', 'lg:foo-bar', 'foo-[12px]', 'lg:foo-[12px]'], - css` - @plugin "my-plugin"; - @layer utilities { - @tailwind utilities; - } + let compiled = await compile( + css` + @plugin "my-plugin"; + @layer utilities { + @tailwind utilities; + } - @theme reference { - --breakpoint-lg: 1024px; + @theme reference { + --breakpoint-lg: 1024px; + } + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + foo: (value) => ({ + '--foo': value, + [`@apply flex`]: {}, + }), + }, + { + values: { + bar: 'bar', + }, + }, + ) + }, } - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { foo: (value) => ({ '--foo': value, [`@apply flex`]: {} }) }, - { values: { bar: 'bar' } }, - ) - }, - } - }, }, - ), + }, + ) + + expect( + optimizeCss(compiled.build(['foo-bar', 'lg:foo-bar', 'foo-[12px]', 'lg:foo-[12px]'])).trim(), ).toMatchInlineSnapshot(` - " - @layer utilities { + "@layer utilities { .foo-\\[12px\\] { --foo: 12px; display: flex; @@ -4292,8 +4082,7 @@ describe('matchUtilities()', () => { display: flex; } } - } - " + }" `) }) @@ -4312,7 +4101,7 @@ describe('matchUtilities()', () => { `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4328,179 +4117,202 @@ describe('matchUtilities()', () => { }, }, ) - }).rejects.toThrow(/invalid utility name/) + }).rejects.toThrowError(/invalid utility name/) }) test('replaces the class name with variants in nested selectors', async () => { - expect( - await run( - ['foo-red', 'md:foo-red', 'not-hover:md:foo-red'], - css` - @plugin "my-plugin"; - @theme { - --breakpoint-md: 768px; - } - @tailwind utilities; - `, - { - async loadModule(base) { - return { - path: '', - base, - module: ({ matchUtilities }: PluginAPI) => { - matchUtilities( - { foo: (value) => ({ ':where(.foo > :first-child)': { color: value } }) }, - { values: { red: 'red' } }, - ) - }, - } - }, - }, - ), - ).toMatchInlineSnapshot(` - " - .foo-red :where(.foo-red > :first-child) { - color: red; - } - - @media (min-width: 768px) { - .md\\:foo-red :where(.md\\:foo-red > :first-child), .not-hover\\:md\\:foo-red:not(:hover) :where(.not-hover\\:md\\:foo-red > :first-child) { - color: red; + let compiled = await compile( + css` + @plugin "my-plugin"; + @theme { + --breakpoint-md: 768px; } - } + @tailwind utilities; + `, + { + async loadModule(base) { + return { + path: '', + base, + module: ({ matchUtilities }: PluginAPI) => { + matchUtilities( + { + foo: (value) => ({ + ':where(.foo > :first-child)': { + color: value, + }, + }), + }, + { + values: { + red: 'red', + }, + }, + ) + }, + } + }, + }, + ) - @media not all and (hover: hover) { - @media (min-width: 768px) { - .not-hover\\:md\\:foo-red :where(.not-hover\\:md\\:foo-red > :first-child) { + expect(compiled.build(['foo-red', 'md:foo-red', 'not-hover:md:foo-red']).trim()) + .toMatchInlineSnapshot(` + ".foo-red { + :where(.foo-red > :first-child) { color: red; } } - } - " - `) + .md\\:foo-red { + @media (width >= 768px) { + :where(.md\\:foo-red > :first-child) { + color: red; + } + } + } + .not-hover\\:md\\:foo-red { + &:not(*:hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo-red > :first-child) { + color: red; + } + } + } + @media not (hover: hover) { + @media (width >= 768px) { + :where(.not-hover\\:md\\:foo-red > :first-child) { + color: red; + } + } + } + }" + `) }) }) describe('addComponents()', () => { test('is an alias for addUtilities', async () => { - expect( - await run( - ['btn', 'btn-blue', 'btn-red'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ addComponents }: PluginAPI) => { - addComponents({ - '.btn': { - padding: '.5rem 1rem', - borderRadius: '.25rem', - fontWeight: '600', + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ addComponents }: PluginAPI) => { + addComponents({ + '.btn': { + padding: '.5rem 1rem', + borderRadius: '.25rem', + fontWeight: '600', + }, + '.btn-blue': { + backgroundColor: '#3490dc', + color: '#fff', + '&:hover': { + backgroundColor: '#2779bd', }, - '.btn-blue': { - backgroundColor: '#3490dc', - color: '#fff', - '&:hover': { - backgroundColor: '#2779bd', - }, + }, + '.btn-red': { + backgroundColor: '#e3342f', + color: '#fff', + '&:hover': { + backgroundColor: '#cc1f1a', }, - '.btn-red': { - backgroundColor: '#e3342f', - color: '#fff', - '&:hover': { - backgroundColor: '#cc1f1a', - }, - }, - }) - }, - } - }, + }, + }) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .btn { - border-radius: .25rem; - padding: .5rem 1rem; - font-weight: 600; - } + }, + ) - .btn-blue { - color: #fff; - background-color: #3490dc; - } + expect(optimizeCss(compiled.build(['btn', 'btn-blue', 'btn-red'])).trim()) + .toMatchInlineSnapshot(` + ".btn { + border-radius: .25rem; + padding: .5rem 1rem; + font-weight: 600; + } - .btn-blue:hover { - background-color: #2779bd; - } + .btn-blue { + color: #fff; + background-color: #3490dc; + } - .btn-red { - color: #fff; - background-color: #e3342f; - } + .btn-blue:hover { + background-color: #2779bd; + } - .btn-red:hover { - background-color: #cc1f1a; - } - " - `) + .btn-red { + color: #fff; + background-color: #e3342f; + } + + .btn-red:hover { + background-color: #cc1f1a; + }" + `) }) }) describe('matchComponents()', () => { test('is an alias for matchUtilities', async () => { - expect( - await run( - ['prose', 'sm:prose-sm', 'hover:prose-lg'], - css` - @plugin "my-plugin"; - @tailwind utilities; - `, - { - async loadModule(_id, base) { - return { - path: '', - base, - module: ({ matchComponents }: PluginAPI) => { - matchComponents( - { prose: (value) => ({ '--container-size': value }) }, - { values: { DEFAULT: 'normal', sm: 'sm', lg: 'lg' } }, - ) - }, - } - }, + let compiled = await compile( + css` + @plugin "my-plugin"; + @tailwind utilities; + `, + { + async loadModule(id, base) { + return { + path: '', + base, + module: ({ matchComponents }: PluginAPI) => { + matchComponents( + { + prose: (value) => ({ '--container-size': value }), + }, + { + values: { + DEFAULT: 'normal', + sm: 'sm', + lg: 'lg', + }, + }, + ) + }, + } }, - ), - ).toMatchInlineSnapshot(` - " - .prose { - --container-size: normal; - } + }, + ) - @media (hover: hover) { - .hover\\:prose-lg:hover { - --container-size: lg; + expect(optimizeCss(compiled.build(['prose', 'sm:prose-sm', 'hover:prose-lg'])).trim()) + .toMatchInlineSnapshot(` + ".prose { + --container-size: normal; } - } - " - `) + + @media (hover: hover) { + .hover\\:prose-lg:hover { + --container-size: lg; + } + }" + `) }) }) describe('prefix()', () => { test('is an identity function', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4518,13 +4330,13 @@ describe('prefix()', () => { describe('config()', () => { test('can return the resolved config when passed no arguments', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4546,13 +4358,13 @@ describe('config()', () => { }) test('can return part of the config', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4572,13 +4384,13 @@ describe('config()', () => { }) test('falls back to default value if requested path does not exist', async () => { - using fn = vi.fn() + let fn = vi.fn() await compile( css` @plugin "my-plugin"; `, { - async loadModule(_id, base) { + async loadModule(id, base) { return { path: '', base, @@ -4592,40 +4404,4 @@ describe('config()', () => { expect(fn).toHaveBeenCalledWith('defaultvalue') }) - - // https://github.com/tailwindlabs/tailwindcss/issues/19721 - test('matchUtilities does not match Object.prototype properties as values', async ({ - expect, - }) => { - // These should not crash or produce output - expect( - await run( - [ - 'test-constructor', - 'test-hasOwnProperty', - 'test-toString', - 'test-valueOf', - 'test-__proto__', - ], - css` - @tailwind utilities; - @plugin "my-plugin"; - `, - { - loadModule: async (_id, base) => { - return { - path: '', - base, - module: plugin(function ({ matchUtilities }) { - matchUtilities( - { test: (value) => ({ '--test': value }) }, - { values: { foo: 'bar' } }, - ) - }), - } - }, - }, - ), - ).toEqual('') - }) }) diff --git a/packages/tailwindcss/src/compat/plugin-api.ts b/packages/tailwindcss/src/compat/plugin-api.ts index 11878fb30..dc27993cb 100644 --- a/packages/tailwindcss/src/compat/plugin-api.ts +++ b/packages/tailwindcss/src/compat/plugin-api.ts @@ -1,12 +1,10 @@ import type { Features } from '..' import { substituteAtApply } from '../apply' -import { atRule, cloneAstNode, context, decl, rule, type AstNode } from '../ast' +import { atRule, decl, rule, walk, type AstNode } from '../ast' import type { Candidate, CandidateModifier, NamedUtilityValue } from '../candidate' import { substituteFunctions } from '../css-functions' import * as CSS from '../css-parser' import type { DesignSystem } from '../design-system' -import * as SelectorParser from '../selector-parser' -import type { SourceLocation } from '../source-maps/source' import { withAlpha } from '../utilities' import { DefaultMap } from '../utils/default-map' import { escape } from '../utils/escape' @@ -14,9 +12,9 @@ import { inferDataType } from '../utils/infer-data-type' import { segment } from '../utils/segment' import { toKeyPath } from '../utils/to-key-path' import { compoundsForSelectors, IS_VALID_VARIANT_NAME, substituteAtSlot } from '../variants' -import { walk, WalkAction } from '../walk' import type { ResolvedConfig, UserConfig } from './config/types' import { createThemeFn } from './plugin-functions' +import * as SelectorParser from './selector-parser' export type Config = UserConfig export type PluginFn = (api: PluginAPI) => void @@ -26,7 +24,6 @@ export type PluginWithConfig = { /** @internal */ reference?: boolean - src?: SourceLocation | undefined } export type PluginWithOptions = { (options?: T): PluginWithConfig @@ -96,50 +93,28 @@ export function buildPluginApi({ resolvedConfig, featuresRef, referenceMode, - src, }: { designSystem: DesignSystem ast: AstNode[] resolvedConfig: ResolvedConfig featuresRef: { current: Features } referenceMode: boolean - src: SourceLocation | undefined }): PluginAPI { let api: PluginAPI = { addBase(css) { if (referenceMode) return let baseNodes = objectToAst(css) featuresRef.current |= substituteFunctions(baseNodes, designSystem) - let rule = atRule('@layer', 'base', baseNodes) - walk([rule], (node) => { - node.src = src - }) - ast.push(rule) + ast.push(atRule('@layer', 'base', baseNodes)) }, addVariant(name, variant) { if (!IS_VALID_VARIANT_NAME.test(name)) { throw new Error( - `\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes, or underscore characters and start with a lowercase letter or number.`, + `\`addVariant('${name}')\` defines an invalid variant name. Variants should only contain alphanumeric, dashes or underscore characters.`, ) } - // Ignore variants emitting v3 `:merge(…)` rules. In v4, the `group-*` and `peer-*` variants - // compound automatically. - if (typeof variant === 'string') { - if (variant.includes(':merge(')) return - } else if (Array.isArray(variant)) { - if (variant.some((v) => v.includes(':merge('))) return - } else if (typeof variant === 'object') { - function keyIncludes(object: Record, search: string): boolean { - return Object.entries(object).some( - ([key, value]) => - key.includes(search) || (typeof value === 'object' && keyIncludes(value, search)), - ) - } - if (keyIncludes(variant, ':merge(')) return - } - // Single selector or multiple parallel selectors if (typeof variant === 'string' || Array.isArray(variant)) { designSystem.variants.static( @@ -155,7 +130,7 @@ export function buildPluginApi({ // CSS-in-JS object else if (typeof variant === 'object') { - designSystem.variants.fromAst(name, objectToAst(variant), designSystem) + designSystem.variants.fromAst(name, objectToAst(variant)) } }, matchVariant(name, fn, options) { @@ -168,17 +143,6 @@ export function buildPluginApi({ return parseVariantValue(resolved, nodes) } - try { - // Sample variant value and ignore variants emitting v3 `:merge` rules. In - // v4, the `group-*` and `peer-*` variants compound automatically. - let sample = fn('a', { modifier: null }) - if (typeof sample === 'string' && sample.includes(':merge(')) { - return - } else if (Array.isArray(sample) && sample.some((r) => r.includes(':merge('))) { - return - } - } catch {} - let defaultOptionKeys = Object.keys(options?.values ?? {}) designSystem.variants.group( () => { @@ -202,17 +166,12 @@ export function buildPluginApi({ ruleNodes.nodes, ) } else if (variant.value.kind === 'named' && options?.values) { - if (!Object.hasOwn(options.values, variant.value.value)) { - return null - } let defaultValue = options.values[variant.value.value] if (typeof defaultValue !== 'string') { - return null + return } ruleNodes.nodes = resolveVariantValue(defaultValue, variant.modifier, ruleNodes.nodes) - } else { - return null } }) }, @@ -226,14 +185,8 @@ export function buildPluginApi({ let aValueKey = a.value ? a.value.value : 'DEFAULT' let zValueKey = z.value ? z.value.value : 'DEFAULT' - let aValue = - (options?.values && Object.hasOwn(options.values, aValueKey) - ? options.values[aValueKey] - : undefined) ?? aValueKey - let zValue = - (options?.values && Object.hasOwn(options.values, zValueKey) - ? options.values[zValueKey] - : undefined) ?? zValueKey + let aValue = options?.values?.[aValueKey] ?? aValueKey + let zValue = options?.values?.[zValueKey] ?? zValueKey if (options && typeof options.sort === 'function') { return options.sort( @@ -257,10 +210,6 @@ export function buildPluginApi({ return aValue < zValue ? -1 : 1 }, ) - - designSystem.variants.suggest(name, () => - Object.keys(options?.values ?? {}).filter((v) => v !== 'DEFAULT'), - ) }, addUtilities(utilities) { @@ -279,11 +228,7 @@ export function buildPluginApi({ for (let [name, css] of entries) { if (name.startsWith('@keyframes ')) { if (!referenceMode) { - let keyframes = rule(name, objectToAst(css)) - walk([keyframes], (node) => { - node.src = src - }) - ast.push(keyframes) + ast.push(rule(name, objectToAst(css))) } continue } @@ -291,7 +236,7 @@ export function buildPluginApi({ let selectorAst = SelectorParser.parse(name) let foundValidUtility = false - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if ( node.kind === 'selector' && node.value[0] === '.' && @@ -310,15 +255,8 @@ export function buildPluginApi({ return } - if ( - node.kind === 'function' && - (node.value === ':not' || - // A class inside `:nth-child(… of )` is part of the - // condition, not a utility being defined. - node.value === ':nth-child' || - node.value === ':nth-last-child') - ) { - return WalkAction.Skip + if (node.kind === 'function' && node.value === ':not') { + return SelectorParser.SelectorWalkAction.Skip } }) @@ -335,7 +273,7 @@ export function buildPluginApi({ walk(ast, (node) => { if (node.kind === 'rule') { let selectorAst = SelectorParser.parse(node.selector) - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if (node.kind === 'selector' && node.value[0] === '.') { node.value = `.${designSystem.theme.prefix}\\:${node.value.slice(1)}` } @@ -346,7 +284,7 @@ export function buildPluginApi({ } designSystem.utilities.static(className, (candidate) => { - let clonedAst = ast.map(cloneAstNode) + let clonedAst = structuredClone(ast) replaceNestedClassNameReferences(clonedAst, className, candidate.raw) featuresRef.current |= substituteAtApply(clonedAst, designSystem) return clonedAst @@ -422,13 +360,10 @@ export function buildPluginApi({ value = values.DEFAULT ?? null } else if (candidate.value.kind === 'arbitrary') { value = candidate.value.value - } else if ( - candidate.value.fraction && - Object.hasOwn(values, candidate.value.fraction) - ) { + } else if (candidate.value.fraction && values[candidate.value.fraction]) { value = values[candidate.value.fraction] ignoreModifier = true - } else if (Object.hasOwn(values, candidate.value.value)) { + } else if (values[candidate.value.value]) { value = values[candidate.value.value] } else if (values.__BARE_VALUE__) { value = values.__BARE_VALUE__(candidate.value) ?? null @@ -449,7 +384,7 @@ export function buildPluginApi({ modifier = null } else if (modifiers === 'any' || candidate.modifier.kind === 'arbitrary') { modifier = candidate.modifier.value - } else if (modifiers && Object.hasOwn(modifiers, candidate.modifier.value)) { + } else if (modifiers?.[candidate.modifier.value]) { modifier = modifiers[candidate.modifier.value] } else if (isColor && !Number.isNaN(Number(candidate.modifier.value))) { modifier = `${candidate.modifier.value}%` @@ -492,10 +427,6 @@ export function buildPluginApi({ // work even with legacy configs and plugins valueKeys.delete('__BARE_VALUE__') - // The `__CSS_VALUES__` key is a special key used by the theme function - // to transport `@theme` metadata about values from CSS - valueKeys.delete('__CSS_VALUES__') - // The `DEFAULT` key is represented as `null` in the utility API if (valueKeys.has('DEFAULT')) { valueKeys.delete('DEFAULT') @@ -570,13 +501,6 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { let entries = rules.flatMap((rule) => Object.entries(rule)) for (let [name, value] of entries) { - if (value === null || value === undefined) continue - - // @ts-expect-error - // We do not want `false` present in the types but still need to discard these nodes for - // compatibility purposes - if (value === false) continue - if (typeof value !== 'object') { if (!name.startsWith('--')) { if (value === '@slot') { @@ -598,7 +522,7 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { ast.push(rule(name, objectToAst(item))) } } - } else { + } else if (value !== null) { ast.push(rule(name, objectToAst(value))) } } @@ -608,28 +532,14 @@ export function objectToAst(rules: CssInJs | CssInJs[]): AstNode[] { function parseVariantValue(resolved: string | string[], nodes: AstNode[]): AstNode[] { let resolvedArray = typeof resolved === 'string' ? [resolved] : resolved - return resolvedArray.flatMap((r): AstNode | AstNode[] => { + return resolvedArray.flatMap((r) => { if (r.trim().endsWith('}')) { - let usesAtScope = r.includes('@scope') let updatedCSS = r.replace('}', '{@slot}}') let ast = CSS.parse(updatedCSS) - - if (usesAtScope) { - substituteAtSlot(ast, [context({ source: 'user' }, nodes)]) - return context({ source: 'variant' }, ast) - } - substituteAtSlot(ast, nodes) return ast } else { - let node = rule(r, nodes) - - if (node.kind === 'at-rule' && node.name === '@scope') { - node.nodes = [context({ source: 'user' }, nodes)] - return context({ source: 'variant' }, [node]) - } - - return node + return rule(r, nodes) } }) } @@ -646,7 +556,7 @@ function replaceNestedClassNameReferences( walk(ast, (node) => { if (node.kind === 'rule') { let selectorAst = SelectorParser.parse(node.selector) - walk(selectorAst, (node) => { + SelectorParser.walk(selectorAst, (node) => { if (node.kind === 'selector' && node.value === `.${utilityName}`) { node.value = `.${escape(rawCandidate)}` } diff --git a/packages/tailwindcss/src/compat/plugin-functions.ts b/packages/tailwindcss/src/compat/plugin-functions.ts index bbd5fac46..339bd11c6 100644 --- a/packages/tailwindcss/src/compat/plugin-functions.ts +++ b/packages/tailwindcss/src/compat/plugin-functions.ts @@ -100,28 +100,6 @@ export function createThemeFn( return [base, extra] } - // If `--color-foo` and `--color-foo-bar` are both defined, `colors.foo` - // produces a synthetic object: - // - // ```ts - // { DEFAULT: 'red', bar: 'blue', __CSS_VALUES__: { DEFAULT: 0, bar: 0 } } - // ``` - // - // Prefer `DEFAULT` instead of exposing the object to plugin code. - // - // This only applies to lookups of a specific key (e.g. `colors.foo`). A - // namespace root lookup (e.g. `theme('shadow')`) resolves to the whole - // namespace as an object, like in v3. - if ( - keypath.length > 1 && - cssValue !== null && - typeof cssValue === 'object' && - !Array.isArray(cssValue) && - 'DEFAULT' in cssValue - ) { - return cssValue.DEFAULT - } - // Values from CSS take precedence over values from the config return cssValue ?? configValue })() @@ -230,12 +208,7 @@ function readFromCss( // The request looked like `theme('animation.spin')` and was turned into a // lookup for `--animation-spin-*` which had only one entry which means it // should be returned directly. - // - // This only applies to lookups of a specific key (`path.length > 1`). A - // namespace root lookup like `theme('shadow')` resolves to the whole - // namespace as an object, like in v3, so it must keep the object shape (`{ - // DEFAULT: … }`). - if (path.length > 1 && 'DEFAULT' in obj && Object.keys(obj).length === 1) { + if ('DEFAULT' in obj && Object.keys(obj).length === 1) { return [obj.DEFAULT as any, optionsObj.DEFAULT ?? ThemeOptions.NONE] as const } @@ -250,10 +223,8 @@ function get(obj: any, path: string[]) { for (let i = 0; i < path.length; ++i) { let key = path[i] - // The key does not exist so concatenate it with the next key. - // We use Object.hasOwn to avoid matching inherited prototype properties - // (e.g. "constructor", "toString") when traversing config objects. - if (obj === null || obj === undefined || typeof obj !== 'object' || !Object.hasOwn(obj, key)) { + // The key does not exist so concatenate it with the next key + if (obj?.[key] === undefined) { if (path[i + 1] === undefined) { return undefined } diff --git a/packages/tailwindcss/src/compat/screens-config.test.ts b/packages/tailwindcss/src/compat/screens-config.test.ts index d81cd8de6..ac73d8216 100644 --- a/packages/tailwindcss/src/compat/screens-config.test.ts +++ b/packages/tailwindcss/src/compat/screens-config.test.ts @@ -1,89 +1,85 @@ import { describe, expect, test } from 'vitest' -import { run } from '../test-utils/run' +import { compile } from '..' const css = String.raw test('CSS `--breakpoint-*` merge with JS config `screens`', async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'lg:flex', - 'min-sm:max-md:underline', - 'min-md:max-lg:underline', - 'max-w-screen-sm', - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '44rem', - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '44rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'lg:flex', + 'min-sm:max-md:underline', + 'min-md:max-lg:underline', + 'max-w-screen-sm', + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - .max-w-screen-sm { + ".max-w-screen-sm { max-width: 44rem; } - - @media (min-width: 44rem) { - .sm\\:flex { + .sm\\:flex { + @media (width >= 44rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 44rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 50rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 50rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 50rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -92,98 +88,100 @@ test('CSS `--breakpoint-*` merge with JS config `screens`', async () => { }) test('JS config `screens` extend CSS `--breakpoint-*`', async () => { - expect( - await run( - [ - // Order is messed up on purpose - 'md:flex', - 'sm:flex', - 'lg:flex', - 'xs:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', - 'min-xs:flex', - 'min-xs:max-md:underline', + let input = css` + @theme default { + --breakpoint-xs: 39rem; + --breakpoint-md: 49rem; + } + @theme { + --breakpoint-md: 50rem; + } + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-xs: 39rem; - --breakpoint-md: 49rem; - } - @theme { - --breakpoint-md: 50rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - xs: '30rem', - sm: '40rem', - md: '48rem', - lg: '60rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + xs: '30rem', + sm: '40rem', + md: '48rem', + lg: '60rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + // Order is messed up on purpose + 'md:flex', + 'sm:flex', + 'lg:flex', + 'xs:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + 'min-xs:flex', + 'min-xs:max-md:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 30rem) { - .min-xs\\:flex, .xs\\:flex { + ".min-xs\\:flex { + @media (width >= 30rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-xs\\:max-md\\:underline { + } + .xs\\:flex { + @media (width >= 30rem) { + display: flex; + } + } + .min-xs\\:max-md\\:underline { + @media (width >= 30rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 40rem) { - .sm\\:flex { + .sm\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 50rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 50rem) { text-decoration-line: underline; } } } - - @media (min-width: 50rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 50rem) { display: flex; } - - @media not all and (min-width: 60rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 50rem) { + @media (width < 60rem) { text-decoration-line: underline; } } } - - @media (min-width: 60rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 60rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -192,73 +190,70 @@ test('JS config `screens` extend CSS `--breakpoint-*`', async () => { }) test('JS config `screens` only setup, even if those match the default-theme export', async () => { - expect( - await run( - [ - // Order is messed up on purpose - 'md:flex', - 'sm:flex', - 'lg:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', + let input = css` + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - screens: { - sm: '40rem', - md: '48rem', - lg: '64rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + screens: { + sm: '40rem', + md: '48rem', + lg: '64rem', }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + // Order is messed up on purpose + 'md:flex', + 'sm:flex', + 'lg:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .sm\\:flex { + ".sm\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-sm\\:max-md\\:underline { + } + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .md\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline { + } + .min-md\\:max-lg\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .lg\\:flex { + .lg\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -267,85 +262,106 @@ test('JS config `screens` only setup, even if those match the default-theme expo }) test('JS config `screens` overwrite CSS `--breakpoint-*`', async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'mini:flex', - 'midi:flex', - 'maxi:flex', - 'min-md:max-lg:underline', - 'min-sm:max-md:underline', - 'min-midi:max-maxi:underline', - 'min-mini:max-midi:underline', + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'mini:flex', + 'midi:flex', + 'maxi:flex', + 'min-md:max-lg:underline', + 'min-sm:max-md:underline', + 'min-midi:max-maxi:underline', + 'min-mini:max-midi:underline', + + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .mini\\:flex, .sm\\:flex { + ".mini\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-mini\\:max-midi\\:underline, .min-sm\\:max-md\\:underline { + } + .sm\\:flex { + @media (width >= 40rem) { + display: flex; + } + } + .min-mini\\:max-midi\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .md\\:flex, .midi\\:flex { - display: flex; - } - - @media not all and (min-width: 64rem) { - .min-md\\:max-lg\\:underline, .min-midi\\:max-maxi\\:underline { + .min-sm\\:max-md\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .maxi\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } } - - @media print { - .print\\:items-end { + .midi\\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .min-md\\:max-lg\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { + text-decoration-line: underline; + } + } + } + .min-midi\\:max-maxi\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { + text-decoration-line: underline; + } + } + } + .maxi\\:flex { + @media (width >= 64rem) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -359,91 +375,70 @@ test('JS config with `theme: { extends }` should not include the `default-config @tailwind utilities; ` - expect( - await run(['sm:flex', 'md:flex', 'min-md:max-lg:underline', 'min-sm:max-md:underline'], input, { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', }, }, }, - base: '/root', - path: '', - }), + }, + base: '/root', }), + }) + + expect( + compiler.build(['sm:flex', 'md:flex', 'min-md:max-lg:underline', 'min-sm:max-md:underline']), ).toBe('') expect( - await run( - [ - 'mini:flex', - 'midi:flex', - 'maxi:flex', - 'min-midi:max-maxi:underline', - 'min-mini:max-midi:underline', + compiler.build([ + 'mini:flex', + 'midi:flex', + 'maxi:flex', + 'min-midi:max-maxi:underline', + 'min-mini:max-midi:underline', - // Ensure other core variants appear at the end - 'print:items-end', - ], - input, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .mini\\:flex { + ".mini\\:flex { + @media (width >= 40rem) { display: flex; } - - @media not all and (min-width: 48rem) { - .min-mini\\:max-midi\\:underline { + } + .min-mini\\:max-midi\\:underline { + @media (width >= 40rem) { + @media (width < 48rem) { text-decoration-line: underline; } } } - - @media (min-width: 48rem) { - .midi\\:flex { + .midi\\:flex { + @media (width >= 48rem) { display: flex; } - - @media not all and (min-width: 64rem) { - .min-midi\\:max-maxi\\:underline { + } + .min-midi\\:max-maxi\\:underline { + @media (width >= 48rem) { + @media (width < 64rem) { text-decoration-line: underline; } } } - - @media (min-width: 64rem) { - .maxi\\:flex { + .maxi\\:flex { + @media (width >= 64rem) { display: flex; } } - - @media print { - .print\\:items-end { + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -458,104 +453,76 @@ describe('complex screen configs', () => { @tailwind utilities; ` - expect( - await run(['min-sm:flex', 'min-md:flex', 'min-xl:flex', 'min-tall:flex'], input, { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: { max: '639px' }, - md: [ - // - { min: '668px', max: '767px' }, - '868px', - ], - lg: { min: '868px' }, - xl: { min: '1024px', max: '1279px' }, - tall: { raw: '(min-height: 800px)' }, - }, + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: { max: '639px' }, + md: [ + // + { min: '668px', max: '767px' }, + '868px', + ], + lg: { min: '868px' }, + xl: { min: '1024px', max: '1279px' }, + tall: { raw: '(min-height: 800px)' }, }, }, }, - base: '/root', - path: '', - }), + }, + base: '/root', }), - ).toBe('') + }) + + expect(compiler.build(['min-sm:flex', 'min-md:flex', 'min-xl:flex', 'min-tall:flex'])).toBe('') expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'lg:flex', - 'min-lg:flex', - 'xl:flex', - 'tall:flex', + compiler.build([ + 'sm:flex', + 'md:flex', + 'lg:flex', + 'min-lg:flex', + 'xl:flex', + 'tall:flex', - // Ensure other core variants appear at the end - 'print:items-end', - ], - input, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: { max: '639px' }, - md: [ - // - { min: '668px', max: '767px' }, - '868px', - ], - lg: { min: '868px' }, - xl: { min: '1024px', max: '1279px' }, - tall: { raw: '(min-height: 800px)' }, - }, - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 868px) { - .lg\\:flex, .min-lg\\:flex { + ".lg\\:flex { + @media (width >= 868px) { display: flex; } } - - @media (max-width: 639px) { - .sm\\:flex { + .min-lg\\:flex { + @media (width >= 868px) { display: flex; } } - - @media (max-width: 767px) and (min-width: 668px), (min-width: 868px) { - .md\\:flex { + .sm\\:flex { + @media (639px >= width) { display: flex; } } - - @media (max-width: 1279px) and (min-width: 1024px) { - .xl\\:flex { + .md\\:flex { + @media (767px >= width >= 668px), (width >= 868px) { display: flex; } } - - @media (min-height: 800px) { - .tall\\:flex { + .xl\\:flex { + @media (1279px >= width >= 1024px) { display: flex; } } - - @media print { - .print\\:items-end { + .tall\\:flex { + @media (min-height: 800px) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -564,65 +531,70 @@ describe('complex screen configs', () => { }) test("don't interfere with `min-*` and `max-*` variants of non-complex screen configs", async () => { - expect( - await run( - [ - 'sm:flex', - 'md:flex', - 'portrait:flex', - 'min-sm:flex', - 'min-md:flex', - 'min-portrait:flex', - // Ensure other core variants appear at the end - 'print:items-end', - ], - css` - @theme default { - --breakpoint-sm: 39rem; - --breakpoint-md: 48rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - extend: { - screens: { - sm: '40rem', - portrait: { raw: 'screen and (orientation: portrait)' }, - }, - }, + let input = css` + @theme default { + --breakpoint-sm: 39rem; + --breakpoint-md: 48rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + extend: { + screens: { + sm: '40rem', + portrait: { raw: 'screen and (orientation: portrait)' }, }, }, - base: '/root', - path: '', - }), + }, }, - ), + base: '/root', + }), + }) + + expect( + compiler.build([ + 'sm:flex', + 'md:flex', + 'portrait:flex', + 'min-sm:flex', + 'min-md:flex', + 'min-portrait:flex', + // Ensure other core variants appear at the end + 'print:items-end', + ]), ).toMatchInlineSnapshot(` - " - @media (min-width: 40rem) { - .min-sm\\:flex, .sm\\:flex { + ".min-sm\\:flex { + @media (width >= 40rem) { display: flex; } } - - @media (min-width: 48rem) { - .md\\:flex, .min-md\\:flex { + .sm\\:flex { + @media (width >= 40rem) { display: flex; } } - - @media screen and (orientation: portrait) { - .portrait\\:flex { + .md\\:flex { + @media (width >= 48rem) { display: flex; } } - - @media print { - .print\\:items-end { + .min-md\\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .portrait\\:flex { + @media screen and (orientation: portrait) { + display: flex; + } + } + .print\\:items-end { + @media print { align-items: flex-end; } } @@ -632,38 +604,37 @@ describe('complex screen configs', () => { }) test('JS config `screens` can overwrite default CSS `--breakpoint-*`', async () => { + let input = css` + @theme default { + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + } + @config "./config.js"; + @tailwind utilities; + ` + + let compiler = await compile(input, { + loadModule: async () => ({ + module: { + theme: { + screens: { + mini: '40rem', + midi: '48rem', + maxi: '64rem', + }, + }, + }, + base: '/root', + }), + }) + // Note: The `sm`, `md`, and other variants are still there because they are // created before the compat layer can intercept. We do not remove them // currently. expect( - await run( - ['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex'], - css` - @theme default { - --breakpoint-sm: 40rem; - --breakpoint-md: 48rem; - --breakpoint-lg: 64rem; - --breakpoint-xl: 80rem; - --breakpoint-2xl: 96rem; - } - @config "./config.js"; - @tailwind utilities; - `, - { - loadModule: async () => ({ - module: { - theme: { - screens: { - mini: '40rem', - midi: '48rem', - maxi: '64rem', - }, - }, - }, - base: '/root', - path: '', - }), - }, - ), - ).toEqual('') + compiler.build(['min-sm:flex', 'min-md:flex', 'min-lg:flex', 'min-xl:flex', 'min-2xl:flex']), + ).toMatchInlineSnapshot(`""`) }) diff --git a/packages/tailwindcss/src/compat/selector-parser.test.ts b/packages/tailwindcss/src/compat/selector-parser.test.ts new file mode 100644 index 000000000..f995c9de7 --- /dev/null +++ b/packages/tailwindcss/src/compat/selector-parser.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, it } from 'vitest' +import { parse, toCss, walk } from './selector-parser' + +describe('parse', () => { + it('should parse a simple selector', () => { + expect(parse('.foo')).toEqual([{ kind: 'selector', value: '.foo' }]) + }) + + it('should parse a compound selector', () => { + expect(parse('.foo.bar:hover#id')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: '.bar' }, + { kind: 'selector', value: ':hover' }, + { kind: 'selector', value: '#id' }, + ]) + }) + + it('should parse a selector list', () => { + expect(parse('.foo,.bar')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'separator', value: ',' }, + { kind: 'selector', value: '.bar' }, + ]) + }) + + it('should combine everything within attribute selectors', () => { + expect(parse('.foo[bar="baz"]')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: '[bar="baz"]' }, + ]) + }) + + it('should parse functions', () => { + expect(parse('.foo:hover:not(.bar:focus)')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'selector', value: ':hover' }, + { + kind: 'function', + nodes: [ + { + kind: 'selector', + value: '.bar', + }, + { + kind: 'selector', + value: ':focus', + }, + ], + value: ':not', + }, + ]) + }) + + it('should handle next-children combinator', () => { + expect(parse('.foo + p')).toEqual([ + { kind: 'selector', value: '.foo' }, + { kind: 'combinator', value: ' + ' }, + { kind: 'selector', value: 'p' }, + ]) + }) + + it('should handle escaped characters', () => { + expect(parse('foo\\.bar')).toEqual([{ kind: 'selector', value: 'foo\\.bar' }]) + }) + + it('parses :nth-child()', () => { + expect(parse(':nth-child(n+1)')).toEqual([ + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: 'n+1', + }, + ], + }, + ]) + }) + + it('parses &:has(.child:nth-child(2))', () => { + expect(parse('&:has(.child:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'selector', + value: '.child', + }, + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) + + it('parses &:has(:nth-child(2))', () => { + expect(parse('&:has(:nth-child(2))')).toEqual([ + { + kind: 'selector', + value: '&', + }, + { + kind: 'function', + value: ':has', + nodes: [ + { + kind: 'function', + value: ':nth-child', + nodes: [ + { + kind: 'value', + value: '2', + }, + ], + }, + ], + }, + ]) + }) +}) + +describe('toCss', () => { + it('should print a simple selector', () => { + expect(toCss(parse('.foo'))).toBe('.foo') + }) + + it('should print a compound selector', () => { + expect(toCss(parse('.foo.bar:hover#id'))).toBe('.foo.bar:hover#id') + }) + + it('should print a selector list', () => { + expect(toCss(parse('.foo,.bar'))).toBe('.foo,.bar') + }) + + it('should print an attribute selectors', () => { + expect(toCss(parse('.foo[bar="baz"]'))).toBe('.foo[bar="baz"]') + }) + + it('should print a function', () => { + expect(toCss(parse('.foo:hover:not(.bar:focus)'))).toBe('.foo:hover:not(.bar:focus)') + }) + + it('should print escaped characters', () => { + expect(toCss(parse('foo\\.bar'))).toBe('foo\\.bar') + }) + + it('should print :nth-child()', () => { + expect(toCss(parse(':nth-child(n+1)'))).toBe(':nth-child(n+1)') + }) +}) + +describe('walk', () => { + it('can be used to replace a function call', () => { + const ast = parse('.foo:hover:not(.bar:focus)') + walk(ast, (node, { replaceWith }) => { + if (node.kind === 'function' && node.value === ':not') { + replaceWith({ kind: 'selector', value: '.inverted-bar' }) + } + }) + expect(toCss(ast)).toBe('.foo:hover.inverted-bar') + }) +}) diff --git a/packages/tailwindcss/src/compat/selector-parser.ts b/packages/tailwindcss/src/compat/selector-parser.ts new file mode 100644 index 000000000..77a1ce4d4 --- /dev/null +++ b/packages/tailwindcss/src/compat/selector-parser.ts @@ -0,0 +1,467 @@ +export type SelectorCombinatorNode = { + kind: 'combinator' + value: string +} + +export type SelectorFunctionNode = { + kind: 'function' + value: string + nodes: SelectorAstNode[] +} + +export type SelectorNode = { + kind: 'selector' + value: string +} + +export type SelectorValueNode = { + kind: 'value' + value: string +} + +export type SelectorSeparatorNode = { + kind: 'separator' + value: string +} + +export type SelectorAstNode = + | SelectorCombinatorNode + | SelectorFunctionNode + | SelectorNode + | SelectorSeparatorNode + | SelectorValueNode +type SelectorParentNode = SelectorFunctionNode | null + +function combinator(value: string): SelectorCombinatorNode { + return { + kind: 'combinator', + value, + } +} + +function fun(value: string, nodes: SelectorAstNode[]): SelectorFunctionNode { + return { + kind: 'function', + value: value, + nodes, + } +} + +function selector(value: string): SelectorNode { + return { + kind: 'selector', + value, + } +} + +function separator(value: string): SelectorSeparatorNode { + return { + kind: 'separator', + value, + } +} + +function value(value: string): SelectorValueNode { + return { + kind: 'value', + value, + } +} + +export const enum SelectorWalkAction { + /** Continue walking, which is the default */ + Continue, + + /** Skip visiting the children of this node */ + Skip, + + /** Stop the walk entirely */ + Stop, +} + +export function walk( + ast: SelectorAstNode[], + visit: ( + node: SelectorAstNode, + utils: { + parent: SelectorParentNode + replaceWith(newNode: SelectorAstNode | SelectorAstNode[]): void + }, + ) => void | SelectorWalkAction, + parent: SelectorParentNode = null, +) { + for (let i = 0; i < ast.length; i++) { + let node = ast[i] + let replacedNode = false + let replacedNodeOffset = 0 + let status = + visit(node, { + parent, + replaceWith(newNode) { + if (replacedNode) return + replacedNode = true + + if (Array.isArray(newNode)) { + if (newNode.length === 0) { + ast.splice(i, 1) + replacedNodeOffset = 0 + } else if (newNode.length === 1) { + ast[i] = newNode[0] + replacedNodeOffset = 1 + } else { + ast.splice(i, 1, ...newNode) + replacedNodeOffset = newNode.length + } + } else { + ast[i] = newNode + replacedNodeOffset = 1 + } + }, + }) ?? SelectorWalkAction.Continue + + // We want to visit or skip the newly replaced node(s), which start at the + // current index (i). By decrementing the index here, the next loop will + // process this position (containing the replaced node) again. + if (replacedNode) { + if (status === SelectorWalkAction.Continue) { + i-- + } else { + i += replacedNodeOffset - 1 + } + continue + } + + // Stop the walk entirely + if (status === SelectorWalkAction.Stop) return SelectorWalkAction.Stop + + // Skip visiting the children of this node + if (status === SelectorWalkAction.Skip) continue + + if (node.kind === 'function') { + if (walk(node.nodes, visit, node) === SelectorWalkAction.Stop) { + return SelectorWalkAction.Stop + } + } + } +} + +export function toCss(ast: SelectorAstNode[]) { + let css = '' + for (const node of ast) { + switch (node.kind) { + case 'combinator': + case 'selector': + case 'separator': + case 'value': { + css += node.value + break + } + case 'function': { + css += node.value + '(' + toCss(node.nodes) + ')' + } + } + } + return css +} + +const BACKSLASH = 0x5c +const CLOSE_BRACKET = 0x5d +const CLOSE_PAREN = 0x29 +const COLON = 0x3a +const COMMA = 0x2c +const DOUBLE_QUOTE = 0x22 +const FULL_STOP = 0x2e +const GREATER_THAN = 0x3e +const NEWLINE = 0x0a +const NUMBER_SIGN = 0x23 +const OPEN_BRACKET = 0x5b +const OPEN_PAREN = 0x28 +const PLUS = 0x2b +const SINGLE_QUOTE = 0x27 +const SPACE = 0x20 +const TAB = 0x09 +const TILDE = 0x7e + +export function parse(input: string) { + input = input.replaceAll('\r\n', '\n') + + let ast: SelectorAstNode[] = [] + + let stack: (SelectorFunctionNode | null)[] = [] + + let parent = null as SelectorFunctionNode | null + + let buffer = '' + + let peekChar + + for (let i = 0; i < input.length; i++) { + let currentChar = input.charCodeAt(i) + + switch (currentChar) { + // E.g.: + // + // ```css + // .foo .bar + // ^ + // + // .foo > .bar + // ^^^ + // ``` + case COMMA: + case GREATER_THAN: + case NEWLINE: + case SPACE: + case PLUS: + case TAB: + case TILDE: { + // 1. Handle everything before the combinator as a selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + buffer = '' + } + + // 2. Look ahead and find the end of the combinator + let start = i + let end = i + 1 + for (; end < input.length; end++) { + peekChar = input.charCodeAt(end) + if ( + peekChar !== COMMA && + peekChar !== GREATER_THAN && + peekChar !== NEWLINE && + peekChar !== SPACE && + peekChar !== PLUS && + peekChar !== TAB && + peekChar !== TILDE + ) { + break + } + } + i = end - 1 + + let contents = input.slice(start, end) + let node = contents.trim() === ',' ? separator(contents) : combinator(contents) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + + break + } + + // Start of a function call. + // + // E.g.: + // + // ```css + // .foo:not(.bar) + // ^ + // ``` + case OPEN_PAREN: { + let node = fun(buffer, []) + buffer = '' + + // If the function is not one of the following, we combine all it's + // contents into a single value node + if ( + node.value !== ':not' && + node.value !== ':where' && + node.value !== ':has' && + node.value !== ':is' + ) { + // Find the end of the function call + let start = i + 1 + let nesting = 0 + + // Find the closing bracket. + for (let j = i + 1; j < input.length; j++) { + peekChar = input.charCodeAt(j) + if (peekChar === OPEN_PAREN) { + nesting++ + continue + } + if (peekChar === CLOSE_PAREN) { + if (nesting === 0) { + i = j + break + } + nesting-- + } + } + let end = i + + node.nodes.push(value(input.slice(start, end))) + buffer = '' + i = end + + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + + break + } + + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + stack.push(node) + parent = node + + break + } + + // End of a function call. + // + // E.g.: + // + // ```css + // foo(bar, baz) + // ^ + // ``` + case CLOSE_PAREN: { + let tail = stack.pop() + + // Handle everything before the closing paren a selector + if (buffer.length > 0) { + let node = selector(buffer) + tail!.nodes.push(node) + buffer = '' + } + + if (stack.length > 0) { + parent = stack[stack.length - 1] + } else { + parent = null + } + + break + } + + // Split compound selectors. + // + // E.g.: + // + // ```css + // .foo.bar + // ^ + // ``` + case FULL_STOP: + case COLON: + case NUMBER_SIGN: { + // Handle everything before the combinator as a selector and + // start a new selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + } + buffer = String.fromCharCode(currentChar) + break + } + + // Start of an attribute selector. + case OPEN_BRACKET: { + // Handle everything before the combinator as a selector + if (buffer.length > 0) { + let node = selector(buffer) + if (parent) { + parent.nodes.push(node) + } else { + ast.push(node) + } + } + buffer = '' + + let start = i + let nesting = 0 + + // Find the closing bracket. + for (let j = i + 1; j < input.length; j++) { + peekChar = input.charCodeAt(j) + if (peekChar === OPEN_BRACKET) { + nesting++ + continue + } + if (peekChar === CLOSE_BRACKET) { + if (nesting === 0) { + i = j + break + } + nesting-- + } + } + + // Adjust `buffer` to include the string. + buffer += input.slice(start, i + 1) + break + } + + // Start of a string. + case SINGLE_QUOTE: + case DOUBLE_QUOTE: { + let start = i + + // We need to ensure that the closing quote is the same as the opening + // quote. + // + // E.g.: + // + // ```css + // "This is a string with a 'quote' in it" + // ^ ^ -> These are not the end of the string. + // ``` + for (let j = i + 1; j < input.length; j++) { + peekChar = input.charCodeAt(j) + // Current character is a `\` therefore the next character is escaped. + if (peekChar === BACKSLASH) { + j += 1 + } + + // End of the string. + else if (peekChar === currentChar) { + i = j + break + } + } + + // Adjust `buffer` to include the string. + buffer += input.slice(start, i + 1) + break + } + + // Escaped characters. + case BACKSLASH: { + let nextChar = input.charCodeAt(i + 1) + buffer += String.fromCharCode(currentChar) + String.fromCharCode(nextChar) + i += 1 + break + } + + // Everything else will be collected in the buffer + default: { + buffer += String.fromCharCode(currentChar) + } + } + } + + // Collect the remainder as a word + if (buffer.length > 0) { + ast.push(selector(buffer)) + } + + return ast +} diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 46f562918..6530541a0 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -1,20 +1,25 @@ -import { atRule, context, decl, rule, type AstNode, type Rule, type StyleRule } from './ast' +import { + atRule, + decl, + rule, + walk, + WalkAction, + type AstNode, + type Rule, + type StyleRule, +} from './ast' import { type Candidate, type Variant } from './candidate' -import { CompileAstFlags, type DesignSystem } from './design-system' +import { type DesignSystem } from './design-system' import GLOBAL_PROPERTY_ORDER from './property-order' import { asColor, type Utility } from './utilities' import { compare } from './utils/compare' import { escape } from './utils/escape' import type { Variants } from './variants' -import { walk, WalkAction } from './walk' export function compileCandidates( rawCandidates: Iterable, designSystem: DesignSystem, - { - onInvalidCandidate, - respectImportant, - }: { onInvalidCandidate?: (candidate: string) => void; respectImportant?: boolean } = {}, + { onInvalidCandidate }: { onInvalidCandidate?: (candidate: string) => void } = {}, ) { let nodeSorting = new Map< AstNode, @@ -39,12 +44,6 @@ export function compileCandidates( matches.set(rawCandidate, candidates) } - let flags = CompileAstFlags.None - - if (respectImportant ?? true) { - flags |= CompileAstFlags.RespectImportant - } - let variantOrderMap = designSystem.getVariantOrder() // Create the AST @@ -52,7 +51,7 @@ export function compileCandidates( let found = false for (let candidate of candidates) { - let rules = designSystem.compileAstNodes(candidate, flags) + let rules = designSystem.compileAstNodes(candidate) if (rules.length === 0) continue found = true @@ -120,16 +119,10 @@ export function compileCandidates( } } -export function compileAstNodes( - candidate: Candidate, - designSystem: DesignSystem, - flags: CompileAstFlags, -) { +export function compileAstNodes(candidate: Candidate, designSystem: DesignSystem) { let asts = compileBaseUtility(candidate, designSystem) if (asts.length === 0) return [] - let respectImportant = designSystem.important && Boolean(flags & CompileAstFlags.RespectImportant) - let rules: { node: AstNode propertySort: { @@ -143,10 +136,7 @@ export function compileAstNodes( for (let nodes of asts) { let propertySort = getPropertySort(nodes) - // If the candidate itself is important then we want to always mark - // the utility as important. However, at a design system level we want - // to be able to opt-out when using things like `@apply` - if (candidate.important || respectImportant) { + if (candidate.important || designSystem.important) { applyImportant(nodes) } @@ -187,18 +177,7 @@ export function applyVariant( // E.g. `[>img]:flex` is not valid, but `has-[>img]:flex` is if (variant.relative && depth === 0) return null - let child: AstNode = rule(variant.selector, node.nodes) - - // Wrap variant-generated `@scope` rules in a `context` marker, so they are - // hoisted with the variant semantics instead of their native CSS nesting - // semantics. The slotted content is marked as `user` content, so `@scope` - // rules defined there keep their native CSS nesting semantics. - if (child.kind === 'at-rule' && child.name === '@scope') { - child.nodes = [context({ source: 'user' }, node.nodes)] - child = context({ source: 'variant' }, [child]) - } - - node.nodes = [child] + node.nodes = [rule(variant.selector, node.nodes)] return } @@ -298,19 +277,7 @@ function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) { let compiledNodes = utility.compileFn(candidate) if (compiledNodes === undefined) continue - if (compiledNodes === null) { - // `null` means that the result is invalid and that this plugin should not - // result in any CSS, but that doesn't mean that subsequent plugins are - // invalid as well. - // - // However, for backwards compatibility with `matchUtilities` this means - // that we do need to bail entirely: plugins that handle a specific - // arbitrary value type prevent falling through to other plugins if the - // result is invalid for that plugin - if (utility.options?.types?.length) return asts - - continue - } + if (compiledNodes === null) return asts asts.push(compiledNodes) } @@ -322,19 +289,7 @@ function compileBaseUtility(candidate: Candidate, designSystem: DesignSystem) { let compiledNodes = utility.compileFn(candidate) if (compiledNodes === undefined) continue - if (compiledNodes === null) { - // `null` means that the result is invalid and that this plugin should not - // result in any CSS, but that doesn't mean that subsequent plugins are - // invalid as well. - // - // However, for backwards compatibility with `matchUtilities` this means - // that we do need to bail entirely: plugins that handle a specific - // arbitrary value type prevent falling through to other plugins if the - // result is invalid for that plugin - if (utility.options?.types?.length) return asts - - continue - } + if (compiledNodes === null) return asts asts.push(compiledNodes) } diff --git a/packages/tailwindcss/src/constant-fold-declaration.test.ts b/packages/tailwindcss/src/constant-fold-declaration.test.ts deleted file mode 100644 index 5a0c867b6..000000000 --- a/packages/tailwindcss/src/constant-fold-declaration.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { expect, it } from 'vitest' -import { constantFoldDeclaration } from './constant-fold-declaration' - -it.each([ - // Simple expression - ['calc(1 + 1)', '2'], - ['calc(3 - 2)', '1'], - ['calc(2 * 3)', '6'], - ['calc(8 / 2)', '4'], - - // Nested - ['calc(1 + calc(1 + 1))', '3'], - ['calc(3 - calc(1 + 2))', '0'], - ['calc(2 * calc(1 + 3))', '8'], - ['calc(8 / calc(2 + 2))', '2'], - ['calc(1 + (1 + 1))', '3'], - ['calc(3 - (1 + 2))', '0'], - ['calc(2 * (1 + 3))', '8'], - ['calc(8 / (2 + 2))', '2'], - - // With units - ['calc(1rem * 2)', '2rem'], - ['calc(2rem - 0.5rem)', '1.5rem'], - ['calc(3rem * 6)', '18rem'], - ['calc(5rem / 2)', '2.5rem'], - - // Negating with units - ['calc(2rem * -1)', '-2rem'], - ['calc(-1 * 2rem)', '-2rem'], - - // Nested partial evaluation - ['calc(calc(1 + 2) + 2rem)', 'calc(3 + 2rem)'], - - // Nested multiplication with unknown values - ['calc(2 * calc(3 * var(--foo)))', 'calc(6 * var(--foo))'], - ['calc(calc(3 * var(--foo)) * 2)', 'calc(6 * var(--foo))'], - ['calc(2rem * calc(3 * var(--foo)))', 'calc(6rem * var(--foo))'], - - // Nested addition with unknown values - ['calc(1rem + calc(2rem + var(--foo)))', 'calc(3rem + var(--foo))'], - ['calc(calc(2rem + var(--foo)) + 1rem)', 'calc(3rem + var(--foo))'], - - // Nested multiplication can collapse away entirely - ['calc(-1 * calc(-1 * var(--foo)))', 'var(--foo)'], - ['calc(calc(-1 * var(--foo)) * -1)', 'var(--foo)'], - ['calc(-1 * calc(var(--foo) * -1))', 'var(--foo)'], - ['calc(-1 * (-1 * var(--foo)))', 'var(--foo)'], - ['calc(1 * var(--foo))', 'var(--foo)'], - ['calc(var(--foo) * 1)', 'var(--foo)'], - - // Evaluation only handles two operands right now, this can change in the future - ['calc(1 + 2 + 3)', 'calc(1 + 2 + 3)'], -])('should constant fold `%s` into `%s`', (input, expected) => { - expect(constantFoldDeclaration(input)).toBe(expected) -}) - -it.each([ - ['calc(1rem * 2%)'], - ['calc(1rem * 2px)'], - ['calc(2rem - 6)'], - ['calc(3rem * 3dvw)'], - ['calc(3rem * 2dvh)'], - ['calc(5rem / 17px)'], - ['calc(2rem * calc(3px * var(--foo)))'], - ['calc(1rem + 0px + var(--foo))'], -])('should not constant fold different units `%s`', (input) => { - expect(constantFoldDeclaration(input)).toBe(input) -}) - -it.each([ - ['calc(0 * 100vw)'], - ['calc(0 * calc(1 * 2))'], - ['calc(0 * var(--foo))'], - ['calc(0 * calc(var(--spacing) * 32))'], - - ['calc(100vw * 0)'], - ['calc(calc(1 * 2) * 0)'], - ['calc(var(--foo) * 0)'], - ['calc(calc(var(--spacing, 0.25rem) * 32) * 0)'], - ['calc(var(--spacing, 0.25rem) * -0)'], - - // Zeroes - ['0px'], - ['0rem'], - ['0em'], - ['0dvh'], - ['-0'], - ['+0'], - ['-0.0rem'], - ['+0.00rem'], - - // Expressions - ['calc(-0px * -1)'], - ['calc(-1 * -0px)'], -])('should constant fold `%s` to `0` (%#)', (input) => { - expect(constantFoldDeclaration(input)).toBe('0') -}) - -it.each([ - // Expressions, keep unit when they are nested - // - // TODO: We might be able to fold this further to just `1rem`, but we can't do - // that for any `0`. E.g.: `calc(0s + 1rem)` which is invalid, would - // become valid if we just use `1rem`. - ['calc(calc(0px * -1) + 1rem)', 'calc(0px + 1rem)'], - ['calc(calc(-1 * 0px) + 1rem)', 'calc(0px + 1rem)'], - - // Non-foldable units - ['0deg', '0deg'], - ['0rad', '0deg'], - ['0%', '0%'], - ['0turn', '0deg'], - ['0fr', '0fr'], - ['0ms', '0s'], - ['0s', '0s'], - ['-0.0deg', '0deg'], - ['-0.0rad', '0deg'], - ['-0.0%', '0%'], - ['-0.0turn', '0deg'], - ['-0.0fr', '0fr'], - ['-0.0ms', '0s'], - ['-0.0s', '0s'], -])('should not fold non-foldable units to `0`. Constant fold `%s` into `%s`', (input, expected) => { - expect(constantFoldDeclaration(input)).toBe(expected) -}) - -it('should not constant fold when dividing by `0`', () => { - expect(constantFoldDeclaration('calc(123rem / 0)')).toBe('calc(123rem / 0)') -}) - -it('should not constant fold when a computation has a high-precision result', () => { - expect(constantFoldDeclaration('calc(100% / 3.5)')).toBe('calc(100% / 3.5)') -}) - -it('should constant fold division results with floating-point error after scaling', () => { - expect(constantFoldDeclaration('calc(29% / 100)')).toBe('0.29%') -}) diff --git a/packages/tailwindcss/src/constant-fold-declaration.ts b/packages/tailwindcss/src/constant-fold-declaration.ts deleted file mode 100644 index c15420150..000000000 --- a/packages/tailwindcss/src/constant-fold-declaration.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { dimensions } from './utils/dimensions' -import { isLength } from './utils/infer-data-type' -import * as ValueParser from './value-parser' -import { walk, WalkAction } from './walk' - -// Assumption: We already assume that we receive somewhat valid `calc()` -// expressions. So we will see `calc(1 + 1)` and not `calc(1+1)` -export function constantFoldDeclaration( - input: string, - rem: number | null = null, - normalizeUnit = true, -): string { - let [folded, valueAst] = constantFoldDeclarationAst(ValueParser.parse(input), rem, normalizeUnit) - - return folded ? ValueParser.toCss(valueAst) : input -} - -export function constantFoldDeclarationAst( - ast: ValueParser.ValueAstNode[], - rem: number | null = null, - normalizeUnit = true, -): [folded: boolean, ast: ValueParser.ValueAstNode[]] { - let folded = false - - walk(ast, { - exit(valueNode, ctx) { - // Canonicalize dimensions to their simplest form. This includes: - // - Convert `-0`, `+0`, `0.0`, … to `0` - // - Convert `-0px`, `+0em`, `0.0rem`, … to `0` - // - Convert units to an equivalent unit - if ( - valueNode.kind === 'word' && - valueNode.value !== '0' // Already `0`, nothing to do - ) { - let canonical = canonicalizeDimension(valueNode.value, rem, normalizeUnit) - if (canonical === null) return // Couldn't be canonicalized, nothing to do - if (canonical === valueNode.value) return // Already in canonical form, nothing to do - - // We need to be careful with `0` because `0` can only be - // converted to `0` if we're dealing with a `` type. - if (canonical === '0') { - // When used inside of a function such as `calc(…)`, then this isn't - // always safe to convert to `0`. - // - // E.g.: - // - `calc(0px + 1rem)` → `calc(0 + 1rem)` this goes from valid to invalid - // - `calc(0px * 1rem)` → `calc(0 * 1rem)` this goes from invalid to valid - if (ctx.parent?.kind === 'function') { - let withUnit = canonicalizeDimension(valueNode.value, rem, false) - if (withUnit === null) return - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(withUnit)) - } - } - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(canonical)) - } - - // Constant fold `calc()` expressions with two operands and one operator - else if ( - valueNode.kind === 'function' && - (valueNode.value === 'calc' || valueNode.value === '') - ) { - // [ - // { kind: 'word', value: '0.25rem' }, 0 - // { kind: 'separator', value: ' ' }, 1 - // { kind: 'word', value: '*' }, 2 - // { kind: 'separator', value: ' ' }, 3 - // { kind: 'word', value: '256' } 4 - // ] - if (valueNode.nodes.length !== 5) return - if (valueNode.nodes[2].kind !== 'word') return - - let lhsNode = valueNode.nodes[0] - let operator = valueNode.nodes[2].value - let rhsNode = valueNode.nodes[4] - - let lhs = lhsNode.kind === 'word' ? dimensions.get(lhsNode.value) : null - let rhs = rhsNode.kind === 'word' ? dimensions.get(rhsNode.value) : null - - // Nullify entire expression when multiplying by `0`, e.g.: `calc(0 * 100vw)` -> `0` - // - // TODO: Ensure it's safe to do so based on the data types? - if ( - operator === '*' && - ((lhs?.[0] === 0 && lhs?.[1] === null) || // 0 * something - (rhs?.[0] === 0 && rhs?.[1] === null)) // something * 0 - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - - // Fold `0 * something-without-unit` to just `0`, inside of a function such as `calc(…)` - if (operator === '*' && lhs?.[0] === 0 && lhs?.[1] !== null && rhs?.[1] === null) { - folded = true - if (ctx.parent?.kind === 'function') { - return WalkAction.ReplaceSkip(ValueParser.word(`0${lhs[1]}`)) - } else { - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - } - - // Fold `something-without-unit * 0` to just `0`, inside of a function such as `calc(…)` - if (operator === '*' && rhs?.[0] === 0 && rhs?.[1] !== null && lhs?.[1] === null) { - folded = true - if (ctx.parent?.kind === 'function') { - return WalkAction.ReplaceSkip(ValueParser.word(`0${rhs[1]}`)) - } else { - return WalkAction.ReplaceSkip(ValueParser.word('0')) - } - } - - if (operator === '*') { - // Multiplying by `1` can always unwrap the other side, even when that - // side is an expression like `var(--foo)` that we can't fully fold. - if (lhs?.[0] === 1 && lhs?.[1] === null) { - folded = true - return WalkAction.ReplaceSkip(rhsNode) - } - - if (rhs?.[0] === 1 && rhs?.[1] === null) { - folded = true - return WalkAction.ReplaceSkip(lhsNode) - } - } - - if (operator === '*' || operator === '+') { - // If only one side is known, and the unknown side is itself another - // binary expression with the same operator, try to combine the two - // known parts and keep the unknown part in place. - // - // E.g.: - // - // - `calc(2rem * calc(3 * var(--foo)))` → `calc(6rem * var(--foo))` - // - `calc(2rem + calc(3rem + var(--foo)))` → `calc(5rem + var(--foo))` - // - // At this point `lhs` and `rhs` are dimensions ([value, unit] | - // null). The `null` one is the unknown node. - let constant = lhs ?? rhs - let nestedNode = lhs === null ? lhsNode : rhs === null ? rhsNode : null - - if ( - constant !== null && - nestedNode !== null && - nestedNode.kind === 'function' && - (nestedNode.value === 'calc' || nestedNode.value === '') && - nestedNode.nodes.length === 5 && - nestedNode.nodes[2].kind === 'word' && - nestedNode.nodes[2].value === operator - ) { - let nestedLhsNode = nestedNode.nodes[0] - let nestedRhsNode = nestedNode.nodes[4] - let nestedLhs = - nestedLhsNode.kind === 'word' ? dimensions.get(nestedLhsNode.value) : null - let nestedRhs = - nestedRhsNode.kind === 'word' ? dimensions.get(nestedRhsNode.value) : null - - let known = nestedLhs ?? nestedRhs - let unknown = - nestedLhs === null ? nestedLhsNode : nestedRhs === null ? nestedRhsNode : null - - if (known !== null && unknown !== null) { - // `*` requires both values being unitless, or one of them. Both - // values having a unit is invalid. - // - // - `2 * 3` → valid - // - `4rem * 5` → valid - // - `6 * 7rem` → valid - // - `8rem * 9rem` → invalid - if ( - operator === '*' && - !( - (constant[1] === null && known[1] === null) || // Both can be unitless - (constant[1] === null && known[1] !== null) || // One of them can be unitless, but the other can't - (constant[1] !== null && known[1] === null) // One of them can be unitless, but the other can't - ) - ) { - return - } - - // `+` requires that both values being unitless, or both values - // have a unit. Mixed units are valid, but we won't fold these. - // - // - `2 + 3` → valid - // - `4rem + 5` → invalid - // - `6 + 7rem` → invalid - // - `8rem + 9rem` → valid - if ( - operator === '+' && - !( - constant[1] === known[1] // Only same unit is allowed - ) - ) { - return - } - - // Re-associate nested expressions so we can still fold the known - // part of `x op (y op z)` when `z` is unknown. - // - // Examples: - // - `calc(2 * calc(3 * var(--foo)))` -> `calc(6 * var(--foo))` - // - `calc(1rem + calc(2rem + var(--foo)))` -> `calc(3rem + var(--foo))` - let combined: string - switch (operator) { - case '*': { - combined = `${constant[0] * known[0]}${constant[1] ?? known[1] ?? ''}` - break - } - case '+': { - combined = `${constant[0] + known[0]}${constant[1] ?? known[1] ?? ''}` - break - } - - default: - return - } - - folded = true - - if (operator === '*' && combined === '1') { - return WalkAction.ReplaceSkip(unknown) - } - - let replacement: ValueParser.ValueFunctionNode = { - kind: 'function', - value: valueNode.value, - nodes: [ - ValueParser.word(combined), - valueNode.nodes[1], - valueNode.nodes[2], - valueNode.nodes[3], - unknown, - ], - } - - return WalkAction.ReplaceSkip(replacement) - } - } - } - - // We're not dealing with dimensions, so we can't fold this - if (lhs === null || rhs === null) { - return - } - - switch (operator) { - case '*': { - if ( - lhs[1] === rhs[1] || // Same Units, e.g.: `1rem * 2rem`, `8 * 6` - (lhs[1] === null && rhs[1] !== null) || // Unitless * Unit, e.g.: `2 * 1rem` - (lhs[1] !== null && rhs[1] === null) // Unit * Unitless, e.g.: `1rem * 2` - ) { - folded = true - return WalkAction.ReplaceSkip( - ValueParser.word(`${lhs[0] * rhs[0]}${lhs[1] ?? rhs[1] ?? ''}`), - ) - } - break - } - - case '+': { - if ( - lhs[1] === rhs[1] // Same unit or unitless, e.g.: `1rem + 2rem`, `8 + 6` - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] + rhs[0]}${lhs[1] ?? ''}`)) - } - break - } - - case '-': { - if ( - lhs[1] === rhs[1] // Same unit or unitless, e.g.: `2rem - 1rem`, `8 - 6` - ) { - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${lhs[0] - rhs[0]}${lhs[1] ?? ''}`)) - } - break - } - - case '/': { - if ( - rhs[0] !== 0 && // Don't divide by zero - ((lhs[1] === null && rhs[1] === null) || // Unitless / Unitless, e.g.: `8 / 2` - (lhs[1] !== null && rhs[1] === null)) // Unit / Unitless, e.g.: `1rem / 2` - ) { - let computed = lhs[0] / rhs[0] - - // Only fold with .xx precision, anything beyond that might be a bit too much. - // - // E.g. 100% / 3.5 = 28.571428571428573%, which is correct but not - // as user friendly. Especially when going from - // `w-[calc(100%/3.5)]` → `w-[28.571428571428573%]` - if (Math.round(computed * 100) / 100 !== computed) { - break - } - - folded = true - return WalkAction.ReplaceSkip(ValueParser.word(`${computed}${lhs[1] ?? ''}`)) - } - break - } - } - } - }, - }) - - return [folded, ast] -} - -function canonicalizeDimension( - input: string, - rem: number | null = null, - normalizeUnit = true, -): string | null { - let dimension = dimensions.get(input) - if (dimension === null) return null // This shouldn't happen - - let [value, unit] = dimension - if (unit === null) return `${value}` // Already unitless, nothing to do - - // Replace `0` units with just `0` - if (value === 0 && isLength(input)) { - if (normalizeUnit) return '0' - else return `0${unit}` // Keep unit - } - - // Only normalize into base units when necessary - if (!normalizeUnit) return `${input}` - - // prettier-ignore - switch (unit.toLowerCase()) { - // to px, https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Values_and_units#lengths - case 'in': return `${value * 96}px` // 1in = 96.000px - case 'cm': return `${value * 96 / 2.54}px` // 1cm = 37.795px - case 'mm': return `${value * 96 / 2.54 / 10}px` // 1mm = 3.779px - case 'q': return `${value * 96 / 2.54 / 10 / 4}px` // 1q = 0.945px - case 'pc': return `${value * 96 / 6}px` // 1pc = 16.000px - case 'pt': return `${value * 96 / 72}px` // 1pt = 1.333px - case 'rem': return rem !== null ? `${value * rem}px` : null // 1rem = 16.000px (Assuming root font-size is 16px) - - // to deg, https://developer.mozilla.org/en-US/docs/Web/CSS/angle - case 'grad': return `${value * 0.9}deg` // 1grad = 0.900deg - case 'rad': return `${value * 180 / Math.PI}deg` // 1rad = 57.296deg - case 'turn': return `${value * 360}deg` // 1turn = 360.000deg - - //
`, ) - expect(await getPropertyValue('#x', 'outline')).toEqual('2px dotted rgb(255, 255, 255)') + expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 2px') await page.locator('#x').hover() - expect(await getPropertyValue('#x', 'outline')).toEqual('4px dotted rgb(255, 255, 255)') + expect(await getPropertyValue('#x', 'outline')).toEqual('rgb(255, 255, 255) dotted 4px') }) test('borders can be added without a border-style utility', async ({ page }) => { @@ -2177,330 +2177,6 @@ test('shadow DOM has access to variables', async ({ page }) => { expect(gap).toBe('8px') }) -// @scope related tests -{ - test('custom variant using `@scope`', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
In scope
-
-
Beyond the limit
-
-
-
Not in scope
- `, - css` - @custom-variant in-checkout { - @scope (.checkout) to (.payment) { - @slot; - } - } - `, - ) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - expect(await getPropertyValue('#in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#beyond', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#out', 'text-decoration-line')).toEqual('none') - }) - - test('custom variant using `@scope` behaves like the equivalent authored CSS', async ({ - page, - }) => { - // Render the same structure twice: - // - Once styled by authored CSS using `@scope`, like the "normal CSS" - // reference in https://github.com/tailwindlabs/tailwindcss/issues/18961 - // - Once styled by utilities using an equivalent custom variant - function structure(prefix: string, probe: string) { - return html` -
-
In scope
-
-
Beyond the limit
-
-
-
Not in scope
- ` - } - - let { getPropertyValue } = await render( - page, - structure('a', 'a-probe') + structure('c', 'in-checkout:underline'), - css` - @custom-variant in-checkout { - @scope (.checkout) to (.payment) { - @slot; - } - } - `, - ) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ - content: css` - @scope (.checkout) to (.payment) { - .a-probe { - text-decoration-line: underline; - } - } - `, - }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - for (let prefix of ['a', 'c']) { - expect(await getPropertyValue(`#${prefix}-in`, 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue(`#${prefix}-beyond`, 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue(`#${prefix}-out`, 'text-decoration-line')).toEqual('none') - } - }) - - test('user-authored nested `@scope` behaves like the authored CSS', async ({ page }) => { - // User-authored `@scope` rules are compiled to their native CSS nesting - // semantics. Rendering the same structure twice: - // - Once styled by the authored (nested) CSS as-is - // - Once styled by the compiled output - function structure(prefix: string) { - return html` -
-
- in scope -
- beyond the limit -
-
-
-
- scope not inside parent -
- ` - } - - function scopeCss(prefix: string) { - return css` - .${prefix}-parent { - @scope (& > .${prefix}-scope) to (& .${prefix}-limit) { - .${prefix}-content { - text-decoration-line: underline; - } - } - } - ` - } - - // The `c-*` structure is styled by the compiled CSS - let { getPropertyValue } = await render(page, structure('a') + structure('c'), scopeCss('c')) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ content: scopeCss('a') }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - // The compiled output only applies within the scope - expect(await getPropertyValue('#c-in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#c-beyond', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#c-out', 'text-decoration-line')).toEqual('none') - - // The authored CSS behaves the same - expect(await getPropertyValue('#a-in', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#a-out', 'text-decoration-line')).toEqual('none') - - // WebKit currently drops the `` limit entirely when it contains - // `&`. Since we resolve the `&` at build time, the compiled output works - // even though the authored CSS doesn't. - if (test.info().project.name !== 'webkit') { - expect(await getPropertyValue('#a-beyond', 'text-decoration-line')).toEqual('none') - } - }) - - test('user-authored `@scope` nested inside another `@scope`', async ({ page }) => { - function structure(prefix: string) { - return html` - -
-
-
ctrl
-
-
- - -
-
-
ancestor
-
-
- - -
-
-
-
beyond
-
-
-
- ` - } - - function scopeCss(prefix: string) { - return css` - @scope (.${prefix}-outer) to (.${prefix}-outer-limit) { - .${prefix}-middle { - @scope (.${prefix}-inner) to (.${prefix}-inner-limit) { - text-decoration-line: underline; - } - } - } - ` - } - - // The `c-*` structure is styled by the compiled CSS - let { getPropertyValue } = await render(page, structure('a') + structure('c'), scopeCss('c')) - - // The `a-*` structure is styled by the authored CSS as-is - await page.addStyleTag({ content: scopeCss('a') }) - - test.skip(!(await supportsAtScope(page)), '`@scope` is not supported in this browser') - - // The compiled output behaves the same in every browser - expect(await getPropertyValue('#c-ctrl', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#c-ancestor', 'text-decoration-line')).toEqual('none') - expect(await getPropertyValue('#c-beyond', 'text-decoration-line')).toEqual('none') - - // The authored CSS behaves the same … - expect(await getPropertyValue('#a-ctrl', 'text-decoration-line')).toEqual('underline') - expect(await getPropertyValue('#a-beyond', 'text-decoration-line')).toEqual('none') - - // … except in Chromium, which resolves the nesting context of a - // rule-nested `@scope` without the enclosing scope's constraint (Safari - // and Firefox both constrain it). The compiled output normalizes this - // browser difference. - if (test.info().project.name !== 'chromium') { - expect(await getPropertyValue('#a-ancestor', 'text-decoration-line')).toEqual('none') - } - }) - - function supportsAtScope(page: Page) { - return page.evaluate(() => typeof CSSScopeRule !== 'undefined') - } -} - -for (let kind of ['group', 'peer']) { - test(`${kind}-* variants`, async ({ page }) => { - let targets = html` -
-
- ` - - let { getPropertyValue } = await render( - page, - kind === 'group' - ? html`
${targets}
` - : html`
-
- ${targets} -
`, - css` - .owner > .applied { - @apply ${kind}-focus:z-20; - } - `, - ) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto') - - await page.locator('#trigger').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('20') - - await page.locator('#trigger').blur() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#applied', 'z-index')).toEqual('auto') - }) -} - -test('group-* variants do not change specificity', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
-
- `, - css` - @layer utilities { - .target { - z-index: 20; - } - .target.equal { - z-index: 30; - } - } - `, - ) - - await page.locator('#trigger').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - - await page.locator('#target').evaluate((element) => element.classList.add('equal')) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('30') -}) - -test('stacked group-* variants match regardless of the order of the groups', async ({ page }) => { - for (let [outer, inner] of [ - ['a', 'b'], - ['b', 'a'], - ]) { - let { getPropertyValue } = await render( - page, - html` -
-
-
-
-
- `, - ) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - - await page.locator('#inner').evaluate((element) => element.removeAttribute('data-active')) - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - } -}) - -test('compound group-* and peer-* variants', async ({ page }) => { - let { getPropertyValue } = await render( - page, - html` -
-
-
-
-
-
- `, - ) - - await page.locator('#peer').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('10') - expect(await getPropertyValue('#middle', 'z-index')).toEqual('auto') - - await page.locator('#outer').focus() - - expect(await getPropertyValue('#target', 'z-index')).toEqual('auto') - expect(await getPropertyValue('#middle', 'z-index')).toEqual('20') -}) - // --- const preflight = fs.readFileSync(path.resolve(__dirname, '..', 'preflight.css'), 'utf-8') @@ -2573,7 +2249,7 @@ async function getPropertyValue( selector: [string, string | undefined], property: string, ) { - let value = await page.evaluate( + return page.evaluate( ([[selector, pseudo], property]) => { return window .getComputedStyle(document.querySelector(selector)!, pseudo) @@ -2581,16 +2257,4 @@ async function getPropertyValue( }, [selector, property] as const, ) - - switch (property) { - case 'outline': - // Order in webkit vs chromium is different. Sort to normalize. - return segment(value, ' ') - .map((part) => part.trim()) - .sort((a, z) => a.length - z.length) - .join(' ') - - default: - return value - } } diff --git a/packages/tailwindcss/theme.css b/packages/tailwindcss/theme.css index 05364f5c2..52d447f7f 100644 --- a/packages/tailwindcss/theme.css +++ b/packages/tailwindcss/theme.css @@ -1,7 +1,7 @@ @theme default { --font-sans: - -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', @@ -235,7 +235,7 @@ --color-gray-900: oklch(21% 0.034 264.665); --color-gray-950: oklch(13% 0.028 261.692); - --color-zinc-50: oklch(98.5% 0 none); + --color-zinc-50: oklch(98.5% 0 0); --color-zinc-100: oklch(96.7% 0.001 286.375); --color-zinc-200: oklch(92% 0.004 286.32); --color-zinc-300: oklch(87.1% 0.006 286.286); @@ -247,17 +247,17 @@ --color-zinc-900: oklch(21% 0.006 285.885); --color-zinc-950: oklch(14.1% 0.005 285.823); - --color-neutral-50: oklch(98.5% 0 none); - --color-neutral-100: oklch(97% 0 none); - --color-neutral-200: oklch(92.2% 0 none); - --color-neutral-300: oklch(87% 0 none); - --color-neutral-400: oklch(70.8% 0 none); - --color-neutral-500: oklch(55.6% 0 none); - --color-neutral-600: oklch(43.9% 0 none); - --color-neutral-700: oklch(37.1% 0 none); - --color-neutral-800: oklch(26.9% 0 none); - --color-neutral-900: oklch(20.5% 0 none); - --color-neutral-950: oklch(14.5% 0 none); + --color-neutral-50: oklch(98.5% 0 0); + --color-neutral-100: oklch(97% 0 0); + --color-neutral-200: oklch(92.2% 0 0); + --color-neutral-300: oklch(87% 0 0); + --color-neutral-400: oklch(70.8% 0 0); + --color-neutral-500: oklch(55.6% 0 0); + --color-neutral-600: oklch(43.9% 0 0); + --color-neutral-700: oklch(37.1% 0 0); + --color-neutral-800: oklch(26.9% 0 0); + --color-neutral-900: oklch(20.5% 0 0); + --color-neutral-950: oklch(14.5% 0 0); --color-stone-50: oklch(98.5% 0.001 106.423); --color-stone-100: oklch(97% 0.001 106.424); @@ -271,54 +271,6 @@ --color-stone-900: oklch(21.6% 0.006 56.043); --color-stone-950: oklch(14.7% 0.004 49.25); - --color-mauve-50: oklch(98.5% 0 none); - --color-mauve-100: oklch(96% 0.003 325.6); - --color-mauve-200: oklch(92.2% 0.005 325.62); - --color-mauve-300: oklch(86.5% 0.012 325.68); - --color-mauve-400: oklch(71.1% 0.019 323.02); - --color-mauve-500: oklch(54.2% 0.034 322.5); - --color-mauve-600: oklch(43.5% 0.029 321.78); - --color-mauve-700: oklch(36.4% 0.029 323.89); - --color-mauve-800: oklch(26.3% 0.024 320.12); - --color-mauve-900: oklch(21.2% 0.019 322.12); - --color-mauve-950: oklch(14.5% 0.008 326); - - --color-olive-50: oklch(98.8% 0.003 106.5); - --color-olive-100: oklch(96.6% 0.005 106.5); - --color-olive-200: oklch(93% 0.007 106.5); - --color-olive-300: oklch(88% 0.011 106.6); - --color-olive-400: oklch(73.7% 0.021 106.9); - --color-olive-500: oklch(58% 0.031 107.3); - --color-olive-600: oklch(46.6% 0.025 107.3); - --color-olive-700: oklch(39.4% 0.023 107.4); - --color-olive-800: oklch(28.6% 0.016 107.4); - --color-olive-900: oklch(22.8% 0.013 107.4); - --color-olive-950: oklch(15.3% 0.006 107.1); - - --color-mist-50: oklch(98.7% 0.002 197.1); - --color-mist-100: oklch(96.3% 0.002 197.1); - --color-mist-200: oklch(92.5% 0.005 214.3); - --color-mist-300: oklch(87.2% 0.007 219.6); - --color-mist-400: oklch(72.3% 0.014 214.4); - --color-mist-500: oklch(56% 0.021 213.5); - --color-mist-600: oklch(45% 0.017 213.2); - --color-mist-700: oklch(37.8% 0.015 216); - --color-mist-800: oklch(27.5% 0.011 216.9); - --color-mist-900: oklch(21.8% 0.008 223.9); - --color-mist-950: oklch(14.8% 0.004 228.8); - - --color-taupe-50: oklch(98.6% 0.002 67.8); - --color-taupe-100: oklch(96% 0.002 17.2); - --color-taupe-200: oklch(92.2% 0.005 34.3); - --color-taupe-300: oklch(86.8% 0.007 39.5); - --color-taupe-400: oklch(71.4% 0.014 41.2); - --color-taupe-500: oklch(54.7% 0.021 43.1); - --color-taupe-600: oklch(43.8% 0.017 39.3); - --color-taupe-700: oklch(36.7% 0.016 35.7); - --color-taupe-800: oklch(26.8% 0.011 36.5); - --color-taupe-900: oklch(21.4% 0.009 43.1); - --color-taupe-950: oklch(14.7% 0.004 49.3); - --color-black: #000; --color-white: #fff; diff --git a/packages/tailwindcss/tsconfig.json b/packages/tailwindcss/tsconfig.json index d2131cbeb..6ae022f65 100644 --- a/packages/tailwindcss/tsconfig.json +++ b/packages/tailwindcss/tsconfig.json @@ -1,6 +1,3 @@ { "extends": "../tsconfig.base.json", - "compilerOptions": { - "verbatimModuleSyntax": false, - }, } diff --git a/packages/tailwindcss/vitest.config.mts b/packages/tailwindcss/vitest.config.ts similarity index 100% rename from packages/tailwindcss/vitest.config.mts rename to packages/tailwindcss/vitest.config.ts diff --git a/patches/@napi-rs__cli@3.7.4.patch b/patches/@napi-rs__cli@3.7.4.patch deleted file mode 100644 index e72212499..000000000 --- a/patches/@napi-rs__cli@3.7.4.patch +++ /dev/null @@ -1,80 +0,0 @@ -diff --git a/dist/cli.js b/dist/cli.js -index c4c2d568bc070c07dbf3a1f9f4a0c946466668cf..353bbca86e3261a2ba9a8fc56dc612c7c84dab6d 100755 ---- a/dist/cli.js -+++ b/dist/cli.js -@@ -1,4 +1,13 @@ - #!/usr/bin/env node -+// PATCHED (see patches/@napi-rs__cli@3.7.4.patch): the embedded wasi loader -+// and worker templates below retry `new WASI(...)` with narrower preopens, -+// because preopening `/` throws `UVWASI_EACCES` on sandboxed platforms (e.g. -+// OpenHarmony), which would make the generated wasm binding fail to load. -+// -+// The same templates also exist in `dist/index.js` and `dist/index.cjs` (the -+// programmatic API). Those are intentionally NOT patched because we only -+// build through the `napi` bin, which runs this file. If we ever start using -+// the programmatic API, update the patch to cover those bundles too. - import { createRequire } from "node:module"; - import { Cli, Command, Option } from "clipanion"; - import path, { basename, dirname, isAbsolute, join, parse, resolve } from "node:path"; -@@ -1021,13 +1030,25 @@ const { - - const __rootDir = __nodePath.parse(process.cwd()).root - --const __wasi = new __nodeWASI({ -- version: 'preview1', -- env: process.env, -- preopens: { -- [__rootDir]: __rootDir, -+const __wasi = (() => { -+ // Preopening '/' fails with UVWASI_EACCES in sandboxed environments (e.g. -+ // OpenHarmony, Android), which would prevent the wasm binding from loading -+ // at all. Retry with narrower preopens instead. Without any preopens the -+ // binding still loads; only file system access is unavailable. -+ let lastError = null -+ for (const dir of [__rootDir, process.cwd(), null]) { -+ try { -+ return new __nodeWASI({ -+ version: 'preview1', -+ env: process.env, -+ preopens: dir === null ? {} : { [dir]: dir }, -+ }) -+ } catch (error) { -+ lastError = error -+ } - } --}) -+ throw lastError -+})() - - const __emnapiContext = __emnapiGetDefaultContext() - -@@ -1151,13 +1172,22 @@ const __rootDir = parse(process.cwd()).root; - - const handler = new MessageHandler({ - onLoad({ wasmModule, wasmMemory }) { -- const wasi = new WASI({ -- version: 'preview1', -- env: process.env, -- preopens: { -- [__rootDir]: __rootDir, -- }, -- }); -+ // Keep in sync with the preopen fallback in the main-thread loader. -+ const wasi = (() => { -+ let lastError = null; -+ for (const dir of [__rootDir, process.cwd(), null]) { -+ try { -+ return new WASI({ -+ version: 'preview1', -+ env: process.env, -+ preopens: dir === null ? {} : { [dir]: dir }, -+ }); -+ } catch (error) { -+ lastError = error; -+ } -+ } -+ throw lastError; -+ })(); - - return instantiateNapiModuleSync(wasmModule, { - childThread: true, diff --git a/patches/@parcel__watcher@2.6.0.patch b/patches/@parcel__watcher@2.5.1.patch similarity index 52% rename from patches/@parcel__watcher@2.6.0.patch rename to patches/@parcel__watcher@2.5.1.patch index 37ff83f04..ed9fa9040 100644 --- a/patches/@parcel__watcher@2.6.0.patch +++ b/patches/@parcel__watcher@2.5.1.patch @@ -1,22 +1,20 @@ diff --git a/index.js b/index.js -index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb5219a181a02 100644 +index 8afb2b1126dcc687b7ff9b631589da252c1f9c22..398af238a439912d150b3573367873d2a9a311e3 100644 --- a/index.js +++ b/index.js -@@ -1,43 +1,32 @@ - const {createWrapper} = require('./wrapper'); +@@ -1,41 +1,34 @@ +-const {createWrapper} = require('./wrapper'); ++const { createWrapper } = require('./wrapper') -let name = `@parcel/watcher-${process.platform}-${process.arch}`; -if (process.platform === 'linux') { -- const {MUSL, familySync} = require('detect-libc'); -- const family = familySync(); +- const { MUSL, family } = require('detect-libc'); - if (family === MUSL) { - name += '-musl'; - } else { - name += '-glibc'; - } -} -+// We need to take the `process.env.PLATFORM_LIBC` into account, and we also -+// need static analysis, so we can't make use of the `parts.push(…)` approach. +function loadPackage() { + if (process.platform === 'linux') { + if (process.env.PLATFORM_LIBC === "musl") { @@ -24,8 +22,7 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 + } else if (process.env.PLATFORM_LIBC === "glibc") { + return require(`@parcel/watcher-${process.platform}-${process.arch}-glibc`) + } else { -+ let { MUSL, GLIBC, familySync } = require('detect-libc') -+ let family = familySync() ++ let { MUSL, GLIBC, family, familySync } = require('detect-libc') -let binding; -try { @@ -40,12 +37,14 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 - binding = require('./build/Debug/watcher.node'); - } catch (err) { - handleError(err); -- throw new Error( -- `No prebuild or local build of @parcel/watcher found. Tried ${name}. Please ensure it is installed (don't use --no-optional when installing with npm). Otherwise it is possible we don't support your platform yet. If this is the case, please report an issue to https://github.com/parcel-bundler/watcher.`, -- ); +- throw new Error(`No prebuild or local build of @parcel/watcher found. Tried ${name}. Please ensure it is installed (don't use --no-optional when installing with npm). Otherwise it is possible we don't support your platform yet. If this is the case, please report an issue to https://github.com/parcel-bundler/watcher.`); - } - } -} ++ // Bun polyfills `detect-libc` in compiled binaries. We rely on ++ // detect-libc@1.0.3 but the polyfilled version is 2.x. In detect-libc@2x ++ // there is a `familySync` function that we can use instead. ++ if (typeof familySync === 'function') family = familySync() -function handleError(err) { - if (err?.code !== 'MODULE_NOT_FOUND') { @@ -64,7 +63,12 @@ index 78ee744d74900c39a839cafa46b295258fc86307..f28fd092644ccfb8aff2618b966cb521 } -const wrapper = createWrapper(binding); -+const wrapper = createWrapper(loadPackage()); - exports.writeSnapshot = wrapper.writeSnapshot; - exports.getEventsSince = wrapper.getEventsSince; - exports.subscribe = wrapper.subscribe; +-exports.writeSnapshot = wrapper.writeSnapshot; +-exports.getEventsSince = wrapper.getEventsSince; +-exports.subscribe = wrapper.subscribe; +-exports.unsubscribe = wrapper.unsubscribe; ++const wrapper = createWrapper(loadPackage()) ++exports.writeSnapshot = wrapper.writeSnapshot ++exports.getEventsSince = wrapper.getEventsSince ++exports.subscribe = wrapper.subscribe ++exports.unsubscribe = wrapper.unsubscribe diff --git a/patches/lightningcss@1.33.0.patch b/patches/lightningcss@1.29.2.patch similarity index 58% rename from patches/lightningcss@1.33.0.patch rename to patches/lightningcss@1.29.2.patch index e70be2315..204f06ab3 100644 --- a/patches/lightningcss@1.33.0.patch +++ b/patches/lightningcss@1.29.2.patch @@ -1,8 +1,8 @@ diff --git a/node/index.js b/node/index.js -index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d2b35df47 100644 +index 011d04b450687083e7a0ec9023c41ee79104831c..ef26db49d98c60b5b31f3601f6ad045f0b0e8f4f 100644 --- a/node/index.js +++ b/node/index.js -@@ -1,25 +1,40 @@ +@@ -1,28 +1,41 @@ -let parts = [process.platform, process.arch]; -if (process.platform === 'linux') { - const { MUSL, familySync } = require('detect-libc'); @@ -11,8 +11,12 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d - parts.push('musl'); - } else if (process.arch === 'arm') { - parts.push('gnueabihf'); -+// We need to take the `process.env.PLATFORM_LIBC` into account, and we also -+// need static analysis, so we can't make use of the `parts.push(…)` approach. +- } else { +- parts.push('gnu'); +- } +-} else if (process.platform === 'win32') { +- parts.push('msvc'); +-} +function loadPackage() { + if (process.platform === 'linux') { + if (process.env.PLATFORM_LIBC === 'musl') { @@ -24,8 +28,19 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d + return require(`lightningcss-${process.platform}-${process.arch}-gnu`) + } + } else { -+ let { MUSL, GLIBC, familySync } = require('detect-libc') -+ let family = familySync() ++ let { MUSL, GLIBC, family, familySync } = require('detect-libc') + +-if (process.env.CSS_TRANSFORMER_WASM) { +- module.exports = require(`../pkg`); +-} else { +- try { +- module.exports = require(`lightningcss-${parts.join('-')}`); +- } catch (err) { +- module.exports = require(`../lightningcss.${parts.join('-')}.node`); ++ // Bun polyfills `detect-libc` in compiled binaries. We rely on ++ // detect-libc@1.0.3 but the polyfilled version is 2.x. In detect-libc@2x ++ // there is a `familySync` function that we can use instead. ++ if (typeof familySync === 'function') family = familySync() + + if (family === MUSL) { + return require(`lightningcss-${process.platform}-${process.arch}-musl`) @@ -41,22 +56,15 @@ index 6fe25aef457aa5448f9ee6204da1e253f5534800..742be0229b94450b631b6e069ffc372d + } + } else if (process.platform === 'win32') { + return require(`lightningcss-${process.platform}-${process.arch}-msvc`) - } else { -- parts.push('gnu'); ++ } else { + return require(`lightningcss-${process.platform}-${process.arch}`) } --} else if (process.platform === 'win32') { -- parts.push('msvc'); } - --let native; --try { -- native = require(`lightningcss-${parts.join('-')}`); --} catch (err) { -- native = require(`../lightningcss.${parts.join('-')}.node`); --} - -+let native = loadPackage(); - module.exports.transform = wrap(native.transform); - module.exports.transformStyleAttribute = wrap(native.transformStyleAttribute); - module.exports.bundle = wrap(native.bundle); + +-module.exports.browserslistToTargets = require('./browserslistToTargets'); +-module.exports.composeVisitors = require('./composeVisitors'); +-module.exports.Features = require('./flags').Features; ++module.exports = loadPackage() ++module.exports.browserslistToTargets = require('./browserslistToTargets') ++module.exports.composeVisitors = require('./composeVisitors') ++module.exports.Features = require('./flags').Features diff --git a/playgrounds/nextjs/app/page.module.css b/playgrounds/nextjs/app/page.module.css index b0117f44c..4c5bdd253 100644 --- a/playgrounds/nextjs/app/page.module.css +++ b/playgrounds/nextjs/app/page.module.css @@ -1,5 +1,5 @@ @reference "./globals.css"; .heading { - @apply text-red-500; + @apply skew-7; } diff --git a/playgrounds/nextjs/package.json b/playgrounds/nextjs/package.json index 5487af5a5..f089b13a5 100644 --- a/playgrounds/nextjs/package.json +++ b/playgrounds/nextjs/package.json @@ -5,20 +5,23 @@ "scripts": { "dev": "next dev", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { "@tailwindcss/postcss": "workspace:^", "fast-glob": "^3.3.3", - "next": "16.2.7", - "react": "19.2.7", - "react-dom": "19.2.7", + "next": "15.3.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "tailwindcss": "workspace:^" }, "devDependencies": { "@types/node": "catalog:", - "@types/react": "19.2.15", - "@types/react-dom": "19.2.3", - "typescript": "^6.0.3" + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "eslint": "^9.25.1", + "eslint-config-next": "^15.3.1", + "typescript": "^5.5.4" } } diff --git a/playgrounds/nextjs/tsconfig.json b/playgrounds/nextjs/tsconfig.json index 27414aae7..862143548 100644 --- a/playgrounds/nextjs/tsconfig.json +++ b/playgrounds/nextjs/tsconfig.json @@ -10,7 +10,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -20,14 +20,7 @@ "paths": { "@/*": ["./*"], }, - "target": "ES2017", }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"], } diff --git a/playgrounds/v3/package.json b/playgrounds/v3/package.json index 8c08310db..42c52f127 100644 --- a/playgrounds/v3/package.json +++ b/playgrounds/v3/package.json @@ -9,16 +9,18 @@ "upgrade": "node scripts/upgrade.mjs" }, "dependencies": { - "next": "16.2.7", - "react": "19.2.7", - "react-dom": "19.2.7", + "next": "15.3.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "tailwindcss": "^3" }, "devDependencies": { - "@types/node": "catalog:", - "@types/react": "19.2.15", - "@types/react-dom": "19.2.3", - "autoprefixer": "^10.5.0", - "typescript": "^6.0.3" + "@types/node": "^20.14.8", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "autoprefixer": "^10.4.21", + "eslint": "^9.25.1", + "eslint-config-next": "^15.3.1", + "typescript": "^5.5.4" } } diff --git a/playgrounds/v3/tsconfig.json b/playgrounds/v3/tsconfig.json index 27414aae7..862143548 100644 --- a/playgrounds/v3/tsconfig.json +++ b/playgrounds/v3/tsconfig.json @@ -10,7 +10,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -20,14 +20,7 @@ "paths": { "@/*": ["./*"], }, - "target": "ES2017", }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"], } diff --git a/playgrounds/vite/package.json b/playgrounds/vite/package.json index 25ab1a33b..a462a8e0d 100644 --- a/playgrounds/vite/package.json +++ b/playgrounds/vite/package.json @@ -10,15 +10,15 @@ }, "dependencies": { "@tailwindcss/vite": "workspace:^", - "@vitejs/plugin-react": "^6.0.2", - "react": "^19.2.6", - "react-dom": "^19.2.6", + "@vitejs/plugin-react": "^4.4.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "tailwindcss": "workspace:^" }, "devDependencies": { - "@types/react": "^19.2.15", - "@types/react-dom": "^19.2.3", - "bun": "^1.3.14", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "bun": "^1.2.11", "vite": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8382b2ba5..b1eb22cd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,136 +6,94 @@ settings: catalogs: default: - '@parcel/watcher': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-darwin-arm64': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-darwin-x64': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-arm64-glibc': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-arm64-musl': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-x64-glibc': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-linux-x64-musl': - specifier: 2.6.0 - version: 2.6.0 - '@parcel/watcher-win32-x64': - specifier: 2.6.0 - version: 2.6.0 '@types/node': - specifier: 22.20.1 - version: 22.20.1 - dedent: - specifier: 1.7.2 - version: 1.7.2 - enhanced-resolve: - specifier: ^5.24.5 - version: 5.24.5 + specifier: ^20.14.8 + version: 20.14.13 lightningcss: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-darwin-arm64: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-darwin-x64: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-arm64-gnu: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-arm64-musl: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-x64-gnu: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-linux-x64-musl: - specifier: 1.33.0 - version: 1.33.0 + specifier: 1.29.2 + version: 1.29.2 lightningcss-win32-x64-msvc: - specifier: 1.33.0 - version: 1.33.0 - postcss: - specifier: ^8.5.25 - version: 8.5.25 + specifier: 1.29.2 + version: 1.29.2 prettier: - specifier: 3.9.6 - version: 3.9.6 + specifier: 3.5.0 + version: 3.5.0 vite: - specifier: ^8.2.0 - version: 8.2.0 - webpack: - specifier: ^5.109.2 - version: 5.109.2 + specifier: ^6.0.0 + version: 6.0.0 patchedDependencies: - '@napi-rs/cli@3.7.4': 9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed - '@parcel/watcher@2.6.0': 705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da - lightningcss@1.33.0: 1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930 + '@parcel/watcher@2.5.1': + hash: zs2vvlrje3h42xp5ed2v44fep4 + path: patches/@parcel__watcher@2.5.1.patch + lightningcss@1.29.2: + hash: tzyxy3asfxcqc7ihrooumyi5fm + path: patches/lightningcss@1.29.2.patch importers: .: devDependencies: '@playwright/test': - specifier: ^1.62.1 - version: 1.62.1 + specifier: ^1.52.0 + version: 1.52.0 '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 postcss: - specifier: 'catalog:' - version: 8.5.25 + specifier: 8.5.3 + version: 8.5.3 postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.5.3) prettier: specifier: 'catalog:' - version: 3.9.6 + version: 3.5.0 prettier-plugin-embed: - specifier: ^0.5.1 - version: 0.5.1 + specifier: ^0.5.0 + version: 0.5.0 prettier-plugin-organize-imports: - specifier: ^4.3.0 - version: 4.3.0(prettier@3.9.6)(typescript@5.9.3) + specifier: ^4.0.0 + version: 4.0.0(prettier@3.5.0)(typescript@5.5.4) tsup: - specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.25)(typescript@5.9.3)(yaml@2.9.0) + specifier: ^8.4.0 + version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0) turbo: - specifier: ^2.10.8 - version: 2.10.8 + specifier: ^2.5.2 + version: 2.5.2 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^5.5.4 + version: 5.5.4 vitest: - specifier: ^4.1.10 - version: 4.1.10(@types/node@22.20.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + specifier: ^2.0.5 + version: 2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) crates/node: - devDependencies: - '@emnapi/core': - specifier: 1.11.3 - version: 1.11.3 - '@emnapi/runtime': - specifier: 1.11.3 - version: 1.11.3 - '@napi-rs/cli': - specifier: 3.7.4 - version: 3.7.4(patch_hash=9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed)(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@25.9.1)(node-addon-api@8.7.0) - '@napi-rs/wasm-runtime': - specifier: ^1.2.2 - version: 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - emnapi: - specifier: 1.11.3 - version: 1.11.3(node-addon-api@8.7.0) + dependencies: + detect-libc: + specifier: ^2.0.4 + version: 2.0.4 + tar: + specifier: ^7.4.3 + version: 7.4.3 optionalDependencies: '@tailwindcss/oxide-android-arm64': specifier: workspace:* @@ -173,6 +131,16 @@ importers: '@tailwindcss/oxide-win32-x64-msvc': specifier: workspace:* version: link:npm/win32-x64-msvc + devDependencies: + '@napi-rs/cli': + specifier: ^3.0.0-alpha.78 + version: 3.0.0-alpha.78(@emnapi/runtime@1.4.3)(@types/node@20.14.13)(emnapi@1.4.3(node-addon-api@8.3.0)) + '@napi-rs/wasm-runtime': + specifier: ^0.2.9 + version: 0.2.9 + emnapi: + specifier: 1.4.3 + version: 1.4.3(node-addon-api@8.3.0) crates/node/npm/android-arm-eabi: {} @@ -197,23 +165,23 @@ importers: crates/node/npm/wasm32-wasi: dependencies: '@emnapi/core': - specifier: ^1.11.3 - version: 1.11.3 + specifier: ^1.4.3 + version: 1.4.3 '@emnapi/runtime': - specifier: ^1.11.3 - version: 1.11.3 + specifier: ^1.4.3 + version: 1.4.3 '@emnapi/wasi-threads': - specifier: ^1.2.3 - version: 1.2.3 + specifier: ^1.0.2 + version: 1.0.2 '@napi-rs/wasm-runtime': - specifier: ^1.2.2 - version: 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + specifier: ^0.2.9 + version: 0.2.9 '@tybys/wasm-util': - specifier: ^0.10.3 - version: 0.10.3 + specifier: ^0.9.0 + version: 0.9.0 tslib: - specifier: ^2.8.1 - version: 2.8.1 + specifier: ^2.8.0 + version: 2.8.0 crates/node/npm/win32-arm64-msvc: {} @@ -222,26 +190,23 @@ importers: integrations: devDependencies: dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 fast-glob: specifier: ^3.3.3 version: 3.3.3 source-map-js: specifier: ^1.2.1 version: 1.2.1 - yaml: - specifier: ^2.9.0 - version: 2.9.0 packages/@tailwindcss-browser: devDependencies: h3: - specifier: ^1.15.11 - version: 1.15.11 + specifier: ^1.15.3 + version: 1.15.3 listhen: - specifier: ^1.10.1 - version: 1.10.1(@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)) + specifier: ^1.9.0 + version: 1.9.0 tailwindcss: specifier: workspace:* version: link:../tailwindcss @@ -249,8 +214,8 @@ importers: packages/@tailwindcss-cli: dependencies: '@parcel/watcher': - specifier: 'catalog:' - version: 2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da) + specifier: ^2.5.1 + version: 2.5.1(patch_hash=zs2vvlrje3h42xp5ed2v44fep4) '@tailwindcss/node': specifier: workspace:* version: link:../@tailwindcss-node @@ -258,8 +223,8 @@ importers: specifier: workspace:* version: link:../../crates/node enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 mri: specifier: ^1.2.0 version: 1.2.0 @@ -272,21 +237,21 @@ importers: packages/@tailwindcss-node: dependencies: - '@jridgewell/remapping': - specifier: ^2.3.5 - version: 2.3.5 + '@ampproject/remapping': + specifier: ^2.3.0 + version: 2.3.0 enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 jiti: - specifier: ^2.7.0 - version: 2.7.0 + specifier: ^2.4.2 + version: 2.4.2 lightningcss: specifier: 'catalog:' - version: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) magic-string: - specifier: ^1.1.0 - version: 1.1.0 + specifier: ^0.30.17 + version: 0.30.17 source-map-js: specifier: ^1.2.1 version: 1.2.1 @@ -306,27 +271,27 @@ importers: specifier: workspace:* version: link:../../crates/node postcss: - specifier: 'catalog:' - version: 8.5.25 + specifier: ^8.4.41 + version: 8.4.41 tailwindcss: specifier: workspace:* version: link:../tailwindcss devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/postcss-import': specifier: 14.0.3 version: 14.0.3 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 internal-example-plugin: specifier: workspace:* version: link:../internal-example-plugin postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.4.41) packages/@tailwindcss-standalone: dependencies: @@ -337,91 +302,69 @@ importers: specifier: workspace:* version: link:../@tailwindcss-cli '@tailwindcss/forms': - specifier: ^0.5.11 - version: 0.5.11(tailwindcss@packages+tailwindcss) + specifier: ^0.5.10 + version: 0.5.10(tailwindcss@packages+tailwindcss) '@tailwindcss/typography': - specifier: ^0.5.20 - version: 0.5.20(tailwindcss@packages+tailwindcss) + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@packages+tailwindcss) detect-libc: specifier: 1.0.3 version: 1.0.3 enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 tailwindcss: specifier: workspace:* version: link:../tailwindcss devDependencies: '@parcel/watcher-darwin-arm64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-darwin-x64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-arm64-glibc': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-arm64-musl': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-x64-glibc': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-linux-x64-musl': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@parcel/watcher-win32-x64': - specifier: 'catalog:' - version: 2.6.0 + specifier: ^2.5.1 + version: 2.5.1 '@types/bun': - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.11 + version: 1.2.11 bun: - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.11 + version: 1.2.11 lightningcss-darwin-arm64: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-darwin-x64: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-arm64-gnu: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-arm64-musl: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-x64-gnu: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-linux-x64-musl: specifier: 'catalog:' - version: 1.33.0 + version: 1.29.2 lightningcss-win32-x64-msvc: specifier: 'catalog:' - version: 1.33.0 - - packages/@tailwindcss-turbopack: - dependencies: - '@alloc/quick-lru': - specifier: ^5.2.0 - version: 5.2.0 - '@tailwindcss/node': - specifier: workspace:* - version: link:../@tailwindcss-node - '@tailwindcss/oxide': - specifier: workspace:* - version: link:../../crates/node - tailwindcss: - specifier: workspace:* - version: link:../tailwindcss - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 22.20.1 - webpack: - specifier: 'catalog:' - version: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) + version: 1.29.2 packages/@tailwindcss-upgrade: dependencies: @@ -431,18 +374,21 @@ importers: '@tailwindcss/oxide': specifier: workspace:* version: link:../../crates/node + braces: + specifier: ^3.0.3 + version: 3.0.3 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 enhanced-resolve: - specifier: 'catalog:' - version: 5.24.5 + specifier: ^5.18.1 + version: 5.18.1 globby: - specifier: ^16.2.2 - version: 16.2.2 + specifier: ^14.1.0 + version: 14.1.0 jiti: - specifier: ^2.7.0 - version: 2.7.0 + specifier: ^2.0.0-beta.3 + version: 2.4.2 mri: specifier: ^1.2.0 version: 1.2.0 @@ -450,39 +396,42 @@ importers: specifier: ^1.1.1 version: 1.1.1 postcss: - specifier: 'catalog:' - version: 8.5.25 + specifier: ^8.4.41 + version: 8.4.41 postcss-import: - specifier: ^16.1.1 - version: 16.1.1(postcss@8.5.25) + specifier: ^16.1.0 + version: 16.1.0(postcss@8.4.41) postcss-selector-parser: - specifier: ^7.1.4 - version: 7.1.4 + specifier: ^7.1.0 + version: 7.1.0 prettier: specifier: 'catalog:' - version: 3.9.6 + version: 3.5.0 semver: - specifier: ^7.8.5 - version: 7.8.5 + specifier: ^7.7.1 + version: 7.7.1 tailwindcss: specifier: workspace:* version: link:../tailwindcss tree-sitter: - specifier: ^0.25.1 - version: 0.25.1 + specifier: ^0.22.4 + version: 0.22.4 tree-sitter-typescript: specifier: ^0.23.2 - version: 0.23.2(tree-sitter@0.25.1) + version: 0.23.2(tree-sitter@0.22.4) devDependencies: + '@types/braces': + specifier: ^3.0.5 + version: 3.0.5 '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/postcss-import': specifier: ^14.0.3 version: 14.0.3 '@types/semver': - specifier: ^7.8.0 - version: 7.8.0 + specifier: ^7.7.0 + version: 7.7.0 packages/@tailwindcss-vite: dependencies: @@ -498,58 +447,33 @@ importers: devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - - packages/@tailwindcss-webpack: - dependencies: - '@alloc/quick-lru': - specifier: ^5.2.0 - version: 5.2.0 - '@rspack/core': - specifier: ^1.0.0 || ^2.0.0 - version: 2.0.2(@swc/helpers@0.5.15) - '@tailwindcss/node': - specifier: workspace:* - version: link:../@tailwindcss-node - '@tailwindcss/oxide': - specifier: workspace:* - version: link:../../crates/node - tailwindcss: - specifier: workspace:* - version: link:../tailwindcss - devDependencies: - '@types/node': - specifier: 'catalog:' - version: 22.20.1 - webpack: - specifier: 'catalog:' - version: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) + version: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) packages/internal-example-plugin: {} packages/tailwindcss: devDependencies: - '@jridgewell/remapping': - specifier: ^2.3.5 - version: 2.3.5 + '@ampproject/remapping': + specifier: ^2.3.0 + version: 2.3.0 '@tailwindcss/oxide': specifier: workspace:^ version: link:../../crates/node '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 dedent: - specifier: 'catalog:' - version: 1.7.2 + specifier: 1.5.3 + version: 1.5.3 lightningcss: specifier: 'catalog:' - version: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) + version: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) magic-string: - specifier: ^1.1.0 - version: 1.1.0 + specifier: ^0.30.17 + version: 0.30.17 source-map-js: specifier: ^1.2.1 version: 1.2.1 @@ -563,61 +487,73 @@ importers: specifier: ^3.3.3 version: 3.3.3 next: - specifier: 16.2.7 - version: 16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 15.3.1 + version: 15.3.1(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: - specifier: 19.2.7 - version: 19.2.7 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) tailwindcss: specifier: workspace:^ version: link:../../packages/tailwindcss devDependencies: '@types/node': specifier: 'catalog:' - version: 22.20.1 + version: 20.14.13 '@types/react': - specifier: 19.2.15 - version: 19.2.15 + specifier: ^19.1.2 + version: 19.1.2 '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.1.2 + version: 19.1.2(@types/react@19.1.2) + eslint: + specifier: ^9.25.1 + version: 9.25.1(jiti@2.4.2) + eslint-config-next: + specifier: ^15.3.1 + version: 15.3.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^5.5.4 + version: 5.5.4 playgrounds/v3: dependencies: next: - specifier: 16.2.7 - version: 16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 15.3.1 + version: 15.3.1(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: - specifier: 19.2.7 - version: 19.2.7 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) tailwindcss: specifier: ^3 - version: 3.4.19(yaml@2.9.0) + version: 3.4.14 devDependencies: '@types/node': - specifier: 'catalog:' - version: 22.20.1 + specifier: ^20.14.8 + version: 20.14.13 '@types/react': - specifier: 19.2.15 - version: 19.2.15 + specifier: ^19.1.2 + version: 19.1.2 '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.1.2 + version: 19.1.2(@types/react@19.1.2) autoprefixer: - specifier: ^10.5.0 - version: 10.5.0(postcss@8.5.25) + specifier: ^10.4.21 + version: 10.4.21(postcss@8.4.47) + eslint: + specifier: ^9.25.1 + version: 9.25.1(jiti@2.4.2) + eslint-config-next: + specifier: ^15.3.1 + version: 15.3.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^5.5.4 + version: 5.6.3 playgrounds/vite: dependencies: @@ -625,30 +561,30 @@ importers: specifier: workspace:^ version: link:../../packages/@tailwindcss-vite '@vitejs/plugin-react': - specifier: ^6.0.2 - version: 6.0.2(vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + specifier: ^4.4.1 + version: 4.4.1(vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0)) react: - specifier: ^19.2.6 - version: 19.2.6 + specifier: ^19.1.0 + version: 19.1.0 react-dom: - specifier: ^19.2.6 - version: 19.2.6(react@19.2.6) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) tailwindcss: specifier: workspace:^ version: link:../../packages/tailwindcss devDependencies: '@types/react': - specifier: ^19.2.15 - version: 19.2.15 + specifier: ^19.1.2 + version: 19.1.2 '@types/react-dom': - specifier: ^19.2.3 - version: 19.2.3(@types/react@19.2.15) + specifier: ^19.1.2 + version: 19.1.2(@types/react@19.1.2) bun: - specifier: ^1.3.14 - version: 1.3.14 + specifier: ^1.2.11 + version: 1.2.11 vite: specifier: 'catalog:' - version: 8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + version: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) packages: @@ -656,906 +592,1366 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} - '@emnapi/core@1.11.3': - resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} - '@emnapi/core@2.0.0-alpha.3': - resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} + '@babel/compat-data@7.27.1': + resolution: {integrity: sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@babel/core@7.27.1': + resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@babel/generator@7.27.1': + resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==} + engines: {node: '>=6.9.0'} - '@emnapi/runtime@2.0.0-alpha.3': - resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==} + '@babel/helper-compilation-targets@7.27.1': + resolution: {integrity: sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==} + engines: {node: '>=6.9.0'} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} - '@emnapi/wasi-threads@1.2.3': - resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + '@babel/helper-module-transforms@7.27.1': + resolution: {integrity: sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 - '@emnapi/wasi-threads@2.0.1': - resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==} + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.1': + resolution: {integrity: sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.1': + resolution: {integrity: sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.1': + resolution: {integrity: sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.1': + resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.1': + resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [openbsd] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} + cpu: [x64] + os: [win32] - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.25.1': + resolution: {integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@img/sharp-darwin-arm64@0.34.1': + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + '@img/sharp-darwin-x64@0.34.1': + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} cpu: [ppc64] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - libc: [musl] - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + '@img/sharp-linux-arm64@0.34.1': + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + '@img/sharp-linux-arm@0.34.1': + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + '@img/sharp-linux-s390x@0.34.1': + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + '@img/sharp-linux-x64@0.34.1': + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + '@img/sharp-wasm32@0.34.1': + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + '@img/sharp-win32-ia32@0.34.1': + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + '@img/sharp-win32-x64@0.34.1': + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - '@inquirer/ansi@2.0.7': - resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - - '@inquirer/checkbox@5.2.1': - resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/checkbox@4.1.5': + resolution: {integrity: sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/confirm@6.1.1': - resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/confirm@5.1.9': + resolution: {integrity: sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@11.2.1': - resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/core@10.1.10': + resolution: {integrity: sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/editor@5.2.2': - resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/editor@4.2.10': + resolution: {integrity: sha512-5GVWJ+qeI6BzR6TIInLP9SXhWCEcvgFQYmcRG6d6RIlhFjM5TyG18paTGBgRYyEouvCmzeco47x9zX9tQEofkw==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@5.1.1': - resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/expand@4.0.12': + resolution: {integrity: sha512-jV8QoZE1fC0vPe6TnsOfig+qwu7Iza1pkXoUJ3SroRagrt2hxiL+RbM432YAihNR7m7XnU0HWl/WQ35RIGmXHw==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/external-editor@3.0.3': - resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/figures@1.0.11': + resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} + engines: {node: '>=18'} + + '@inquirer/input@4.1.9': + resolution: {integrity: sha512-mshNG24Ij5KqsQtOZMgj5TwEjIf+F2HOESk6bjMwGWgcH5UBe8UoljwzNFHqdMbGYbgAf6v2wU/X9CAdKJzgOA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@2.0.7': - resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - - '@inquirer/input@5.1.2': - resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/number@3.0.12': + resolution: {integrity: sha512-7HRFHxbPCA4e4jMxTQglHJwP+v/kpFsCf2szzfBHy98Wlc3L08HL76UDiA87TOdX5fwj2HMOLWqRWv9Pnn+Z5Q==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/number@4.1.1': - resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/password@4.0.12': + resolution: {integrity: sha512-FlOB0zvuELPEbnBYiPaOdJIaDzb2PmJ7ghi/SVwIHDDSQ2K4opGBkF+5kXOg6ucrtSUQdLhVVY5tycH0j0l+0g==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/password@5.1.1': - resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/prompts@7.4.1': + resolution: {integrity: sha512-UlmM5FVOZF0gpoe1PT/jN4vk8JmpIWBlMvTL8M+hlvPmzN89K6z03+IFmyeu/oFCenwdwHDr2gky7nIGSEVvlA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/prompts@8.5.2': - resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/rawlist@4.0.12': + resolution: {integrity: sha512-wNPJZy8Oc7RyGISPxp9/MpTOqX8lr0r+lCCWm7hQra+MDtYRgINv1hxw7R+vKP71Bu/3LszabxOodfV/uTfsaA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/rawlist@5.3.1': - resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/search@3.0.12': + resolution: {integrity: sha512-H/kDJA3kNlnNIjB8YsaXoQI0Qccgf0Na14K1h8ExWhNmUg2E941dyFPrZeugihEa9AZNW5NdsD/NcvUME83OPQ==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/search@4.2.1': - resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/select@4.1.1': + resolution: {integrity: sha512-IUXzzTKVdiVNMA+2yUvPxWsSgOG4kfX93jOM4Zb5FgujeInotv5SPIJVeXQ+fO4xu7tW8VowFhdG5JRmmCyQ1Q==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/select@5.2.1': - resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + '@inquirer/type@3.0.6': + resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} + engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/type@4.0.7': - resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@napi-rs/cli@3.7.4': - resolution: {integrity: sha512-idELIUceJ9zPgx/shcMt1fSSsteFG68v56RIXi4qUm7OYuJOt7CoMj2KnHVmbOqXgUHWQU1lCULrtQdZDG4F5A==} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@napi-rs/cli@3.0.0-alpha.78': + resolution: {integrity: sha512-x+38HFEotUu5qPVs8xqOWOaZNdyqhc30zLFQkWJmVzsh4LU0MuYlnF7R4acH4ZB3kyhYoxiNfUQc2d9hUUbPlQ==} engines: {node: '>= 16'} hasBin: true peerDependencies: - '@emnapi/runtime': ^1.7.1 + '@emnapi/runtime': ^1.1.0 + emnapi: ^1.1.0 peerDependenciesMeta: '@emnapi/runtime': optional: true + emnapi: + optional: true - '@napi-rs/cross-toolchain@1.0.3': - resolution: {integrity: sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==} + '@napi-rs/cross-toolchain@0.0.19': + resolution: {integrity: sha512-StHXqYANdTaMFqJJ3JXHqKQMylOzOJPcrOCd9Nt2NIGfvfaXK3SzpmNfkJimkOAYfTsfpfuRERsML0bUZCpHBQ==} peerDependencies: - '@napi-rs/cross-toolchain-arm64-target-aarch64': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-armv7': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-ppc64le': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-s390x': ^1.0.3 - '@napi-rs/cross-toolchain-arm64-target-x86_64': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-aarch64': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-armv7': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-ppc64le': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-s390x': ^1.0.3 - '@napi-rs/cross-toolchain-x64-target-x86_64': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-aarch64': ^0.0.19 + '@napi-rs/cross-toolchain-arm64-target-armv7': ^0.0.19 + '@napi-rs/cross-toolchain-arm64-target-x86_64': ^0.0.19 + '@napi-rs/cross-toolchain-x64-target-aarch64': ^0.0.19 + '@napi-rs/cross-toolchain-x64-target-armv7': ^0.0.19 + '@napi-rs/cross-toolchain-x64-target-x86_64': ^0.0.19 peerDependenciesMeta: '@napi-rs/cross-toolchain-arm64-target-aarch64': optional: true '@napi-rs/cross-toolchain-arm64-target-armv7': optional: true - '@napi-rs/cross-toolchain-arm64-target-ppc64le': - optional: true - '@napi-rs/cross-toolchain-arm64-target-s390x': - optional: true '@napi-rs/cross-toolchain-arm64-target-x86_64': optional: true '@napi-rs/cross-toolchain-x64-target-aarch64': optional: true '@napi-rs/cross-toolchain-x64-target-armv7': optional: true - '@napi-rs/cross-toolchain-x64-target-ppc64le': - optional: true - '@napi-rs/cross-toolchain-x64-target-s390x': - optional: true '@napi-rs/cross-toolchain-x64-target-x86_64': optional: true - '@napi-rs/lzma-android-arm-eabi@1.4.5': - resolution: {integrity: sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==} + '@napi-rs/lzma-android-arm-eabi@1.4.1': + resolution: {integrity: sha512-yenreSpZ9IrqppJOiWDqWMmja7XtSgio9LhtxYwgdILmy/OJTe/mlTYv+FhJBf7hIV9Razu5eBuEa3zKri81IA==} engines: {node: '>= 10'} cpu: [arm] os: [android] - '@napi-rs/lzma-android-arm64@1.4.5': - resolution: {integrity: sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==} + '@napi-rs/lzma-android-arm64@1.4.1': + resolution: {integrity: sha512-piutVBz5B1TNxXeEjub0n/IKI6dMaXPPRbVSXuc4gnZgzcihNDUh68vcLZgYd+IMiACZvBxvx2O3t5nthtph3A==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/lzma-darwin-arm64@1.4.5': - resolution: {integrity: sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==} + '@napi-rs/lzma-darwin-arm64@1.4.1': + resolution: {integrity: sha512-sDfOhQQFqV8lGbpgJN9DqNLBPR7QOfYjcWUv8FOGPaVP1LPJDnrc5uCpRWWEa2zIKmTiO8P9xzIl0TDzrYmghg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/lzma-darwin-x64@1.4.5': - resolution: {integrity: sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==} + '@napi-rs/lzma-darwin-x64@1.4.1': + resolution: {integrity: sha512-S5/RbC6EP4QkYy2xhxbfm48ZD9FkysfpWY4Slve0nj5RGGsHvcJBg2Pi69jrTPB/zLKz2SUa0i+RfUt9zvZNaw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/lzma-freebsd-x64@1.4.5': - resolution: {integrity: sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==} + '@napi-rs/lzma-freebsd-x64@1.4.1': + resolution: {integrity: sha512-4AFnq6aZnclwameSBkDWu5Ftb8y4GwvVXeQXJKbN7hf7O5GG/8QpQB1R1NJw2QORUhpKwjAQUpbkTyhL2GFWWw==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': - resolution: {integrity: sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==} + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.1': + resolution: {integrity: sha512-j5rL1YRIm6rWmmGAvN6DPX6QuRjvFGB93xJ7DTRB47GXW4zHekXae6ivowjJ95vT4Iz4hSWkZbuwAy95eFrWRA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/lzma-linux-arm64-gnu@1.4.5': - resolution: {integrity: sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==} + '@napi-rs/lzma-linux-arm64-gnu@1.4.1': + resolution: {integrity: sha512-1XdFGKyTS9m+VrRQYs9uz+ToHf4Jwm0ejHU48k9lT9MPl8jSqzKdVtFzZBPzronHteSynBfKmUq0+HeWmjrsOQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-arm64-musl@1.4.5': - resolution: {integrity: sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==} + '@napi-rs/lzma-linux-arm64-musl@1.4.1': + resolution: {integrity: sha512-9d09tYS0/rBwIk1QTcO2hMZEB/ZpsG2+uFW5am1RHElSWMclObirB1An7b6AMDJcRvcomkOg2GZ9COzrvHKwEA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] - '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': - resolution: {integrity: sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==} + '@napi-rs/lzma-linux-ppc64-gnu@1.4.1': + resolution: {integrity: sha512-UzEkmsgoJ3IOGIRb6kBzNiw+ThUpiighop7dVYfSqlF5juGzwf7YewC57RGn4FoJCvadOCrSm5VikAcgrwVgAw==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': - resolution: {integrity: sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==} + '@napi-rs/lzma-linux-riscv64-gnu@1.4.1': + resolution: {integrity: sha512-9dUKlZ1PdwxTaFF+j3oc+xjlk9nqFwo1NWWOH30uwjl4Rm5Gkv+Fx0pHrzu4kR/iVA+oyQqa9/2uDYnGSTijBA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-s390x-gnu@1.4.5': - resolution: {integrity: sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==} + '@napi-rs/lzma-linux-s390x-gnu@1.4.1': + resolution: {integrity: sha512-MOVXUWJSLLCJDCCAlGa39sh7nv9XjvXzCf7QJus7rD8Ciz0mpXNXF9mg0ji7/MZ7pZlKPlXjXDnpVCfFdSEaFQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-x64-gnu@1.4.5': - resolution: {integrity: sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==} + '@napi-rs/lzma-linux-x64-gnu@1.4.1': + resolution: {integrity: sha512-Sxu7aJxU1sDbUTqjqLVDV3DCOAlbsFKvmuCN/S5uXBJd1IF2wJ9jK3NbFzfqTAo5Hudx8Y7kOb6+3K+fYPI1KQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] - '@napi-rs/lzma-linux-x64-musl@1.4.5': - resolution: {integrity: sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==} + '@napi-rs/lzma-linux-x64-musl@1.4.1': + resolution: {integrity: sha512-4I3BeKBQJSE5gF2/VTEv7wCLLjhapeutbCGpZPmDiLHZ74rm9edmNXAlKpdjADQ4YDLJ2GIBzttvwLXkJ9U+cw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] - '@napi-rs/lzma-wasm32-wasi@1.4.5': - resolution: {integrity: sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==} + '@napi-rs/lzma-wasm32-wasi@1.4.1': + resolution: {integrity: sha512-s32HdKqQWbohf6DGWpG9YMODaBdbKJ++JpNr6Ii7821sKf4h/o+p8IRFTOaWdmdJdllEWlRirnd5crA29VivJQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@napi-rs/lzma-win32-arm64-msvc@1.4.5': - resolution: {integrity: sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==} + '@napi-rs/lzma-win32-arm64-msvc@1.4.1': + resolution: {integrity: sha512-ISz+v7ML5mKnjEZ7Kk4Z1BIn411r/fz3tDy9j5yDnwQI0MgTsUQFrIQElGUpULWYs2aYc6EZ9PhECbLBfSjh7A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/lzma-win32-ia32-msvc@1.4.5': - resolution: {integrity: sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==} + '@napi-rs/lzma-win32-ia32-msvc@1.4.1': + resolution: {integrity: sha512-3WKuCpZBrd7Jrw+h1jSu5XAsRWepMJu0sYuRoA4Y4Cwfu9gI7p5Z5Bc510nfjg7M7xvdpkI4UoW2WY7kBFRYrQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@napi-rs/lzma-win32-x64-msvc@1.4.5': - resolution: {integrity: sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==} + '@napi-rs/lzma-win32-x64-msvc@1.4.1': + resolution: {integrity: sha512-0ixRo5z1zFXdh62hlrTV+QCTKHK0te5NHKaExOluhtcc6AdpMmpslvM9JhUxNHI+zM46w/DmmcvcOtqsaTmHgg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/lzma@1.4.5': - resolution: {integrity: sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==} + '@napi-rs/lzma@1.4.1': + resolution: {integrity: sha512-5f8K9NHjwHjZKGm3SS+7CFxXQhz8rbg2umBm/9g0xQRXBdYEI31N5z1ACuk9bmBQOusXAq9CArGfs/ZQso2rUA==} engines: {node: '>= 10'} - '@napi-rs/tar-android-arm-eabi@1.1.0': - resolution: {integrity: sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==} + '@napi-rs/tar-android-arm-eabi@0.1.4': + resolution: {integrity: sha512-LMSysWp5AmZj1NOCB2jshc9KCvp4gm7vm0Cra5U2crMvlj/fwGrvv6+EzSw49y8wCkNEcQ8QaGq5NBQKiLogSg==} engines: {node: '>= 10'} cpu: [arm] os: [android] - '@napi-rs/tar-android-arm64@1.1.0': - resolution: {integrity: sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==} + '@napi-rs/tar-android-arm64@0.1.4': + resolution: {integrity: sha512-A/2rl8xr7F5yOtHVARROoSwjRRDNL1RlXCsg/K+RE5/V9iPBojsJsLpFPilp7InF6bi+z7aYn+yWCD6wSwfF4A==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/tar-darwin-arm64@1.1.0': - resolution: {integrity: sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==} + '@napi-rs/tar-darwin-arm64@0.1.4': + resolution: {integrity: sha512-mnGBswBRtxDqUwXUZx+f9Uuy2uPssxuvcWFTYgUSZqlS2pg/XIWZdHZhbqWqKGpjpZrYcr+42roytbWlZ+epMA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/tar-darwin-x64@1.1.0': - resolution: {integrity: sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==} + '@napi-rs/tar-darwin-x64@0.1.4': + resolution: {integrity: sha512-I81Fvl/cfnFVBET49xywNd57dXLJPO7jqrjD9Z2bKeXA0v0Zt1cwV1IOOTihFZXJv7kgu6EfNB7oumoLOTqq5A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/tar-freebsd-x64@1.1.0': - resolution: {integrity: sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==} + '@napi-rs/tar-freebsd-x64@0.1.4': + resolution: {integrity: sha512-p5OFr3MqidZHkt9bHV2FgeS6k06g+s0GR2kvj/wm2mIxr7u3/sj3+RTr7GHz5DQq08T7uH85HhsrGYWN3vxmSg==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': - resolution: {integrity: sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==} + '@napi-rs/tar-linux-arm-gnueabihf@0.1.4': + resolution: {integrity: sha512-POp2zb/Yuw6taEmrTlEoQI5S+2HmgfV1VtqGQZgRmCa85NlWLDsR6vbW9euu/6NbLj3ld15cCeJC2oJkv9k/xg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@napi-rs/tar-linux-arm64-gnu@1.1.0': - resolution: {integrity: sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==} + '@napi-rs/tar-linux-arm64-gnu@0.1.4': + resolution: {integrity: sha512-TV+2AppSgJx5U9nk1C9bh1afWcBVnnANJ4SmtqUF6ediHcDS2rLebeI8BGljfnX9F149qbT9gOGN+R8tofpCsg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@napi-rs/tar-linux-arm64-musl@1.1.0': - resolution: {integrity: sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==} + '@napi-rs/tar-linux-arm64-musl@0.1.4': + resolution: {integrity: sha512-UFBvKpYmuQRbgmXuSSPb8mRjq4JRZLYJhqwrWWnlfQP13xK2WB7mL2GhewBgynSH4YKDm6biKhK6U5RrSWEDgw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] - '@napi-rs/tar-linux-ppc64-gnu@1.1.0': - resolution: {integrity: sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==} + '@napi-rs/tar-linux-ppc64-gnu@0.1.4': + resolution: {integrity: sha512-HT+h6Wv51SKXqks8UBF+KVuNJ09fM1GyO+SvCnDB5MF66tGiI2C6/MSX69zf1ZeqjACds1K/UwKiZCmE76/j9A==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] - '@napi-rs/tar-linux-s390x-gnu@1.1.0': - resolution: {integrity: sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==} + '@napi-rs/tar-linux-s390x-gnu@0.1.4': + resolution: {integrity: sha512-SJ+HSr281Y6cgJrQ4nkYbXaTHAmTLv/FZm5k9ZRA6Khml//ZoWi7CiT8dnPeD4QxYwCzAFA4aYMUOQJM/mk2/w==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] - '@napi-rs/tar-linux-x64-gnu@1.1.0': - resolution: {integrity: sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==} + '@napi-rs/tar-linux-x64-gnu@0.1.4': + resolution: {integrity: sha512-LrF0lRFiFOkO40jfgTdF8dRTvYOLV52fdZ/YnJuBodNcxqEl9rChO3v5Uag//sy0me85FjqtobQNRQP8Nd80dA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] - '@napi-rs/tar-linux-x64-musl@1.1.0': - resolution: {integrity: sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==} + '@napi-rs/tar-linux-x64-musl@0.1.4': + resolution: {integrity: sha512-etGUWbs+Tk1PtzgyWrVzXa2fQrHNKSc/whHm+4x1Num8Oz+wGdjCDTUktYxAVy33PKZhdblVxxE83QXxkgjneQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] - '@napi-rs/tar-wasm32-wasi@1.1.0': - resolution: {integrity: sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==} + '@napi-rs/tar-wasm32-wasi@0.1.4': + resolution: {integrity: sha512-mANkm93AKy+OspkOBAC5WI64SopXT0VawdTjpeGW1OgyUSJWdUB5rhs3I7B/HW1bi5tsUoZOZQe3rVgYdfzA6g==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@napi-rs/tar-win32-arm64-msvc@1.1.0': - resolution: {integrity: sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==} + '@napi-rs/tar-win32-arm64-msvc@0.1.4': + resolution: {integrity: sha512-/5/gp6WR9b36CysJDe8AdyjNeje+NqCniYJz/AZc+UvpKwG8MG9nS6TMpJ9IgrQacJXvc4lWXxYyn6uuPQVvaQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/tar-win32-ia32-msvc@1.1.0': - resolution: {integrity: sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==} + '@napi-rs/tar-win32-ia32-msvc@0.1.4': + resolution: {integrity: sha512-HnQi0op9BqJqPekKXhEAI1TWkLtavxKDBDGXNhUSm2//jriMeRykahUcKoUUxr1UGrmtxpc5dx0cThBt13paEw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@napi-rs/tar-win32-x64-msvc@1.1.0': - resolution: {integrity: sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==} + '@napi-rs/tar-win32-x64-msvc@0.1.4': + resolution: {integrity: sha512-vy2ebEXerblni6XOgi3a27+CnI6PdQ9Phy/ru5HM4bVd/oulAJohmynAmBAB1AmXg1NLbAEWu43nATEDp5O2hA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/tar@1.1.0': - resolution: {integrity: sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==} + '@napi-rs/tar@0.1.4': + resolution: {integrity: sha512-hDsvmMZY8tl2CcLfjnTeE1o5W1eGTSL+ZIX8YEybtcJwA+Cc8SNHb7l6JqMnGcjOrWBZbHt8tzTN+W7qHS5Wmg==} engines: {node: '>= 10'} - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@0.2.9': + resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==} - '@napi-rs/wasm-runtime@1.2.2': - resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - peerDependencies: - '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3 - '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3 - - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': - resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} + '@napi-rs/wasm-tools-android-arm-eabi@0.0.3': + resolution: {integrity: sha512-T2tme8w5jZ/ZCjJurqNtKCxYtGoDjW9v2rn1bfI60ewCfkYXNpxrTURdkOib85sz+BcwmOfXn0enbg5W9KohoQ==} engines: {node: '>= 10'} cpu: [arm] os: [android] - '@napi-rs/wasm-tools-android-arm64@1.0.1': - resolution: {integrity: sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==} + '@napi-rs/wasm-tools-android-arm64@0.0.3': + resolution: {integrity: sha512-siHTjrxxBrvsVty5X2jI5waAyzJpr756GqGVUqxqS2eoTuqYRfgaFNvX8asp9LAagFtOojfD0fZfuvxK7dc4Rw==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': - resolution: {integrity: sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==} + '@napi-rs/wasm-tools-darwin-arm64@0.0.3': + resolution: {integrity: sha512-0MqsSOYJ4jXcLv/nAInS8nwU+/hL0rSEJo7JXKj3dhkT9UNSj4zfidcOaIb05O9VskJBPmV040+edtWPHXNt2Q==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@napi-rs/wasm-tools-darwin-x64@1.0.1': - resolution: {integrity: sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==} + '@napi-rs/wasm-tools-darwin-x64@0.0.3': + resolution: {integrity: sha512-yXAK2mrlBMZZYK/59JRHZu/c683HFpr5ork1cn++fy8gqUBRLbjuq47VDjA7oyLW5SmWqNDhmhjFTDGvfIvcUg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': - resolution: {integrity: sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==} + '@napi-rs/wasm-tools-freebsd-x64@0.0.3': + resolution: {integrity: sha512-K1rne814utBd9Zo9LCggQ5h0TSnzGPzA+sG78Qr7KfFz8XQxEGDRH5wpzXyF1KaKav2RmO6wGMXlasDgIcq7GA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': - resolution: {integrity: sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==} + '@napi-rs/wasm-tools-linux-arm64-gnu@0.0.3': + resolution: {integrity: sha512-Yu3gtpvGc2+hcay3SU5MK7EMrGPBq/V4i8mpw/MEYUCzOb7Vd9aL8CryElzlk0SIbktG08VYMdhFFFoJAjlYtg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': - resolution: {integrity: sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==} + '@napi-rs/wasm-tools-linux-arm64-musl@0.0.3': + resolution: {integrity: sha512-XN+sPgEwFw3P47wDvtcQyOoZNghIL8gaiRjEGzprB+kE9N21GkuMbk3kdjiBBJkjqKF25f4fbOvNAY0jQEAO3A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': - resolution: {integrity: sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==} + '@napi-rs/wasm-tools-linux-x64-gnu@0.0.3': + resolution: {integrity: sha512-mfMvMEqn33YtEjIyLPguZ6yDsNtF5zV7mqc99620YDyj2SLa0aI35TNTc7Dm+/hlgqHRKhdudsWGfYc4dBND2Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': - resolution: {integrity: sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==} + '@napi-rs/wasm-tools-linux-x64-musl@0.0.3': + resolution: {integrity: sha512-KXMsXWGELoN5xgPCoRHbgt5TScSx8BK2GcCHKJ9OPZ2HMfsXbLgS/SNi6vz1CbLMZMLPBY2G6HAk0gzLGyS0mQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': - resolution: {integrity: sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==} + '@napi-rs/wasm-tools-wasm32-wasi@0.0.3': + resolution: {integrity: sha512-v3iMHnAfMteogpbqHTFeLXPeAzL5AhpDJLvZvLXbuRiMsMRL0dn8CbcEnYja2P/Ui6Xlyky6PcaUsepOUTNb7A==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': - resolution: {integrity: sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==} + '@napi-rs/wasm-tools-win32-arm64-msvc@0.0.3': + resolution: {integrity: sha512-HWrg9cW+u+rQKL9XCQILaGGs6mDYdwX9nwcTIvJAjrpGWu8Dp4wz6i66w6YKHqVng1suGYjjr+LH4/1e0tDaAg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': - resolution: {integrity: sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==} + '@napi-rs/wasm-tools-win32-ia32-msvc@0.0.3': + resolution: {integrity: sha512-h99hAWvQKhcloyPfPi0IjrvKRToTE9Z4UVXoXZhcjpCGmr3o1qW+1FAupRy/TcVdMjUJNLE/aenml3UPqzQEQw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': - resolution: {integrity: sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==} + '@napi-rs/wasm-tools-win32-x64-msvc@0.0.3': + resolution: {integrity: sha512-7/6IpzMi9VGYxLcc9SJyu9ZIdbDwyyb09glVF/2SFEgke9F5H46XzRrAdSoRnjfcq/tdLyHKJbnpCIB257qVYg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@napi-rs/wasm-tools@1.0.1': - resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} + '@napi-rs/wasm-tools@0.0.3': + resolution: {integrity: sha512-p7NT5wnOIwmP0f3KbXlMabeld5dPFsADpHMWJaBodTSmnPE8P4msguxKJLKWquqAS1FY2dsjBZ62K0/hfiqAUg==} engines: {node: '>= 10'} - '@next/env@16.2.7': - resolution: {integrity: sha512-tMJizPlj6ZYpBMMdK8S0LJufrP4QTdR6pcv9KQ/bVETPAmg0j1mlHE9G2c38UyGHxoBapgwuj7XjbGJ2RcDFOg==} + '@next/env@15.3.1': + resolution: {integrity: sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==} - '@next/swc-darwin-arm64@16.2.7': - resolution: {integrity: sha512-vm1EDI/pVaBNNiychmxk3fft+OhQPVD9cIM/tReLZIQ3TfQ4kqI9DwKk00dzuS1ulC7icbrzCFrmRRlk9PfNdw==} + '@next/eslint-plugin-next@15.3.1': + resolution: {integrity: sha512-oEs4dsfM6iyER3jTzMm4kDSbrQJq8wZw5fmT6fg2V3SMo+kgG+cShzLfEV20senZzv8VF+puNLheiGPlBGsv2A==} + + '@next/swc-darwin-arm64@15.3.1': + resolution: {integrity: sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.7': - resolution: {integrity: sha512-O3IRSv1ZBL1zs0WrIgefTEcTKFVn+ryxBNe54erJ6KsD+2f/Mmt7g2jOYh8PSBdUwPtKQJuCsTMlZ7tIu2AcsQ==} + '@next/swc-darwin-x64@15.3.1': + resolution: {integrity: sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.7': - resolution: {integrity: sha512-Re6PZtjBDd0aMU+VcZcC/PrIvj4WhrjDYtMhhCVQamWN4L90EVP0pcEOBQD25prSlw7OzNw5QpHLWMilRLsRNw==} + '@next/swc-linux-arm64-gnu@15.3.1': + resolution: {integrity: sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.7': - resolution: {integrity: sha512-qyogG9QtBzWxgJfeGBvOEHI3851gTfCF3wLZ5RDLTBJGAmE9p1qDwKCOdrBrvBzRvYDT+gUDp72pzlSEfAXgNA==} + '@next/swc-linux-arm64-musl@15.3.1': + resolution: {integrity: sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] - '@next/swc-linux-x64-gnu@16.2.7': - resolution: {integrity: sha512-Vhe4ZDuBpmMogrGi5D4R2Kq4JAQlj6+wvgaFYy31zfES0zPmt6TLA+cuYpM/OLrPZjo2MYQTHVqNUSCR6+fDZQ==} + '@next/swc-linux-x64-gnu@15.3.1': + resolution: {integrity: sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] - '@next/swc-linux-x64-musl@16.2.7': - resolution: {integrity: sha512-srvian89JahFLw1YLBEuhvPJ0DO5lpUeJQMXy4xYo7g628ZlNgXdNkqoxSAv9OYrBfByh6vxISMwW/mRbzCY+g==} + '@next/swc-linux-x64-musl@15.3.1': + resolution: {integrity: sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.7': - resolution: {integrity: sha512-GX3wvLpULFuRFJzwHaKfm7QZJ18F4ZSuxlPJ96BoBglCzBmdSjyeBKF+ZhWhvL/ckxNfLnNa7bsObO2ipYpszw==} + '@next/swc-win32-arm64-msvc@15.3.1': + resolution: {integrity: sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.7': - resolution: {integrity: sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw==} + '@next/swc-win32-x64-msvc@15.3.1': + resolution: {integrity: sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1572,539 +1968,396 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-token@6.0.0': - resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} - engines: {node: '>= 20'} + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} - '@octokit/core@7.0.6': - resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} - engines: {node: '>= 20'} + '@octokit/auth-token@5.1.2': + resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} + engines: {node: '>= 18'} - '@octokit/endpoint@11.0.3': - resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} - engines: {node: '>= 20'} + '@octokit/core@6.1.4': + resolution: {integrity: sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==} + engines: {node: '>= 18'} - '@octokit/graphql@9.0.3': - resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} - engines: {node: '>= 20'} + '@octokit/endpoint@10.1.3': + resolution: {integrity: sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==} + engines: {node: '>= 18'} - '@octokit/openapi-types@27.0.0': - resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + '@octokit/graphql@8.2.1': + resolution: {integrity: sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==} + engines: {node: '>= 18'} - '@octokit/plugin-paginate-rest@14.0.0': - resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} - engines: {node: '>= 20'} + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + + '@octokit/plugin-paginate-rest@11.6.0': + resolution: {integrity: sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==} + engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-request-log@6.0.0': - resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} - engines: {node: '>= 20'} + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-rest-endpoint-methods@17.0.0': - resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} - engines: {node: '>= 20'} + '@octokit/plugin-rest-endpoint-methods@13.5.0': + resolution: {integrity: sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==} + engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' - '@octokit/request-error@7.1.0': - resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} - engines: {node: '>= 20'} + '@octokit/request-error@6.1.7': + resolution: {integrity: sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==} + engines: {node: '>= 18'} - '@octokit/request@10.0.9': - resolution: {integrity: sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==} - engines: {node: '>= 20'} + '@octokit/request@9.2.2': + resolution: {integrity: sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==} + engines: {node: '>= 18'} - '@octokit/rest@22.0.1': - resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} - engines: {node: '>= 20'} + '@octokit/rest@21.1.1': + resolution: {integrity: sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==} + engines: {node: '>= 18'} - '@octokit/types@16.0.0': - resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} - '@oven/bun-darwin-aarch64@1.3.14': - resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==} + '@oven/bun-darwin-aarch64@1.2.11': + resolution: {integrity: sha512-LN7gshxqEHJ1LA0B43Z56Ts3LxRjp2LrIz45FIgeKWE81PYglR9NMNou90gFuYlUtMyarSk6MV9Jdvo2kFnn8Q==} cpu: [arm64] os: [darwin] - '@oven/bun-darwin-x64-baseline@1.3.14': - resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==} + '@oven/bun-darwin-x64-baseline@1.2.11': + resolution: {integrity: sha512-ksZJzq0Id3YsGcs++QnL1oGYjqY5XNihR0MscZ+M4YYruvVKHKim9Aql46+TI55YmKD810HVpeLolIeeTl2JUg==} cpu: [x64] os: [darwin] - '@oven/bun-darwin-x64@1.3.14': - resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==} + '@oven/bun-darwin-x64@1.2.11': + resolution: {integrity: sha512-AmmlGdQmzORFeUZRa2Ki9UWYTt5ZGUwTp0z+H0uR4bYHy7sQhpDbNKtZcfKKuLRQ6P23DKc3Lpo+yhR0cg7prg==} cpu: [x64] os: [darwin] - '@oven/bun-freebsd-aarch64@1.3.14': - resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==} - cpu: [arm64] - os: [freebsd] + '@oven/bun-linux-aarch64-musl@1.2.11': + resolution: {integrity: sha512-A/h7WxC2GbNus8fupz5EHsD8YugG19uW9GQWDf4tk0YWYuSaUOCP14JYbXbiJ48OFELWNchEc/2azajH+votbA==} + cpu: [aarch64] + os: [linux] - '@oven/bun-freebsd-x64@1.3.14': - resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==} - cpu: [x64] - os: [freebsd] - - '@oven/bun-linux-aarch64-android@1.3.14': - resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==} - cpu: [arm64] - os: [android] - - '@oven/bun-linux-aarch64-musl@1.3.14': - resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==} + '@oven/bun-linux-aarch64@1.2.11': + resolution: {integrity: sha512-jNQ5F6o9qC/KNhBnyxnjVRfiTxZB7thvZ7T+oPZSNh1IV8UWDunEGmUdOqSqoPsigpXHRjbV3DKJKN+YaPQCCA==} cpu: [arm64] os: [linux] - '@oven/bun-linux-aarch64@1.3.14': - resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==} - cpu: [arm64] - os: [linux] - - '@oven/bun-linux-x64-android@1.3.14': - resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==} - cpu: [x64] - os: [android] - - '@oven/bun-linux-x64-baseline@1.3.14': - resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==} + '@oven/bun-linux-x64-baseline@1.2.11': + resolution: {integrity: sha512-xswex8EPmzX8BqbNXpiwgrpDkaD/FMIaxD85EIBaaksi6I8nln2kzEmlO/uVhbFAn9PPWydi3Q+FCcNnJGRazQ==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64-musl-baseline@1.3.14': - resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==} + '@oven/bun-linux-x64-musl-baseline@1.2.11': + resolution: {integrity: sha512-YYglLgFkHwNMjECCyM/kZDUZa6UW3ssVtg5k6+8QU1Rp5cPF+0SBlGGTr5ho2lFkNn/ylKmwyuqE1tB45zgVvA==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64-musl@1.3.14': - resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==} + '@oven/bun-linux-x64-musl@1.2.11': + resolution: {integrity: sha512-yF1Urgsy8xkQu8iAd03ORxuZ/DWrNPCST2r1EVIwjHuzpZ3o2odKjnE9Hnwsq+wcWdCRsakqL/muILB/9aCxeQ==} cpu: [x64] os: [linux] - '@oven/bun-linux-x64@1.3.14': - resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==} + '@oven/bun-linux-x64@1.2.11': + resolution: {integrity: sha512-6pH556SgOR2UOgWQfEiYI3DWgkm7T+L9fmBe2dfEYrKCEu/TbXZXOJeMi943NSMIiT3q2gVwlCEdoxZwedQs4A==} cpu: [x64] os: [linux] - '@oven/bun-windows-aarch64@1.3.14': - resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==} - cpu: [arm64] - os: [win32] - - '@oven/bun-windows-x64-baseline@1.3.14': - resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==} + '@oven/bun-windows-x64-baseline@1.2.11': + resolution: {integrity: sha512-uzappBblyA3GlX0PDENU1HTPZR82tzPuNd5jumTiDQ22FZmc2ZqCYTIZ/ngwHKOHDD6Rxdsux8+9EXgVS51G2w==} cpu: [x64] os: [win32] - '@oven/bun-windows-x64@1.3.14': - resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==} + '@oven/bun-windows-x64@1.2.11': + resolution: {integrity: sha512-5mGVsGSS8JWBq8IHXGvFJ7rlET4XCUdDTKEaLLq3O1YaqyZZiPMQFnU70MfkpjPe4stRDZ+/NWBRNDMaNTYQxw==} cpu: [x64] os: [win32] - '@oxc-project/types@0.142.0': - resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} - - '@parcel/watcher-android-arm64@2.6.0': - resolution: {integrity: sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==} + '@parcel/watcher-android-arm64@2.5.0': + resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.6.0': - resolution: {integrity: sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==} + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.0': + resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.6.0': - resolution: {integrity: sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==} + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.0': + resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.6.0': - resolution: {integrity: sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==} + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.0': + resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.6.0': - resolution: {integrity: sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==} + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.0': + resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] - '@parcel/watcher-linux-arm-musl@2.6.0': - resolution: {integrity: sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==} + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] - '@parcel/watcher-linux-arm64-glibc@2.6.0': - resolution: {integrity: sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==} + '@parcel/watcher-linux-arm-musl@2.5.0': + resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-arm64-musl@2.6.0': - resolution: {integrity: sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==} + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.0': + resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-x64-glibc@2.6.0': - resolution: {integrity: sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==} + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.0': + resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-linux-x64-musl@2.6.0': - resolution: {integrity: sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==} + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.0': + resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-wasm@2.5.6': - resolution: {integrity: sha512-byAiBZ1t3tXQvc8dMD/eoyE7lTXYorhn+6uVW5AC+JGI1KtJC/LvDche5cfUE+qiefH+Ybq0bUCJU0aB1cSHUA==} + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + os: [linux] + + '@parcel/watcher-wasm@2.5.0': + resolution: {integrity: sha512-Z4ouuR8Pfggk1EYYbTaIoxc+Yv4o7cGQnH0Xy8+pQ+HbiW+ZnwhcD2LPf/prfq1nIWpAxjOkQ8uSMFWMtBLiVQ==} engines: {node: '>= 10.0.0'} bundledDependencies: - napi-wasm - '@parcel/watcher-win32-arm64@2.6.0': - resolution: {integrity: sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==} + '@parcel/watcher-win32-arm64@2.5.0': + resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-x64@2.6.0': - resolution: {integrity: sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==} + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.0': + resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.0': + resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.6.0': - resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + os: [win32] + + '@parcel/watcher@2.5.0': + resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} engines: {node: '>= 10.0.0'} - '@playwright/test@1.62.1': - resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} - engines: {node: '>=20'} + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.52.0': + resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==} + engines: {node: '>=18'} hasBin: true - '@rolldown/binding-android-arm64@1.2.1': - resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.1': - resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.1': - resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.1': - resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-arm64-musl@1.2.1': - resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-gnu@1.2.1': - resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rolldown/binding-linux-x64-musl@1.2.1': - resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rolldown/binding-openharmony-arm64@1.2.1': - resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.2.1': - resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} - engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.1': - resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@rollup/rollup-android-arm-eabi@4.60.4': - resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.4': - resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.4': - resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.4': - resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.4': - resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.4': - resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} cpu: [arm] os: [linux] - libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.4': - resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} cpu: [arm64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.4': - resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} cpu: [arm64] os: [linux] - libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.4': - resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} cpu: [loong64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.60.4': - resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} cpu: [ppc64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.4': - resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} cpu: [riscv64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.4': - resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} cpu: [s390x] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.4': - resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} cpu: [x64] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.4': - resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} cpu: [x64] os: [linux] - libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.4': - resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.4': - resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.4': - resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.4': - resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.4': - resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} - cpu: [x64] - os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@rspack/binding-darwin-arm64@2.0.2': - resolution: {integrity: sha512-0o7lbgBBsDlICWdjIH0q3e0BsSco4GRiImHWVfZSVEG+q2+ykZJvSvYCVhPM1Co375Z0S3VMPa/8SjcY1FHwlw==} - cpu: [arm64] - os: [darwin] + '@rushstack/eslint-patch@1.10.4': + resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} - '@rspack/binding-darwin-x64@2.0.2': - resolution: {integrity: sha512-tOwxZpoPlTlRs/w6UyUinXJ4TYRVHMlR7+eQxO1R3muKpixvhXQjtvoaY16HuFyTVky5F0IfOoWr3x9FEsgdLg==} - cpu: [x64] - os: [darwin] - - '@rspack/binding-linux-arm64-gnu@2.0.2': - resolution: {integrity: sha512-1ZD4YFhG1rmgqj+W8hfwHyKV8xDxGsc/3KgU0FwmiVEX7JfzhCkgBO/xlCG79kRKSrzuVzt4icO/G3cCKn0pag==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-arm64-musl@2.0.2': - resolution: {integrity: sha512-/PtTkM/DsDLjeuXTmeJeRfbjCDbcL9jvoVgZrgxYFZ28y2cdLvbChbW9uigOzs5dQEs1CIBQXMTTj7KhdBTuQg==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rspack/binding-linux-x64-gnu@2.0.2': - resolution: {integrity: sha512-bBjsZxMHRaPo6X9SokApm6ucs+UhXtAJFyJJyuk2BH4XJsLeCU9Dz1vMwioeohFbJUUeTASVPm6/BL+RhSaunw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rspack/binding-linux-x64-musl@2.0.2': - resolution: {integrity: sha512-HjlpInqzabDNkhVsUJpsHPqa9QYVWBViJoyWNjzXCAW0vKMDvwaphyUvokSinX8FGTlZi/sr5UEaHJo6XtQ35g==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rspack/binding-wasm32-wasi@2.0.2': - resolution: {integrity: sha512-YaRYNFLJRpkGfYjSWR7n9f+nQKtrlmrrffpAn/blc2geHcRvXoBc5SCs1idPtsLhj7H9qWWhs7ucjyHy4csWFg==} - cpu: [wasm32] - - '@rspack/binding-win32-arm64-msvc@2.0.2': - resolution: {integrity: sha512-d/3kTEKq+asLjRFPO96t+wfWiM7DLN76VQEPDD9bc1kdsZXlVJBuvyXfsgK8bbEvKplWXYcSsokhmEnuXrLOpg==} - cpu: [arm64] - os: [win32] - - '@rspack/binding-win32-ia32-msvc@2.0.2': - resolution: {integrity: sha512-161cWineq3RW+Jdm1FAfSpXeUtYWvhB3kAbm46vNT9h/YYz+spwsFMvveAZ1nsVSVL0IC5lDBGUte7yUAY8K2g==} - cpu: [ia32] - os: [win32] - - '@rspack/binding-win32-x64-msvc@2.0.2': - resolution: {integrity: sha512-y7Q0S1FE+OlkL5GMqLG0PwxrPw6E1r892KhGrGKE1Vdufe5YTEx6xTPxzZ+b7N2KPD7s9G1/iJmWHQxb1+Bjkg==} - cpu: [x64] - os: [win32] - - '@rspack/binding@2.0.2': - resolution: {integrity: sha512-0kZPplW9GWx8mfC6DfsaRY3QBIYPuUs42JfmSM6aSb8tMHZAXQeLeMB8M+h8i4SeI+aFtCgO6UuYGtyWf7+L+A==} - - '@rspack/core@2.0.2': - resolution: {integrity: sha512-VM3UHOo26uC+4QSqY5tU1ybI7KuXY5rTof8nhFOaBY9SYau0Smvr+hMSAPmrmHwknB6dXT8yaNVxrj7I+qxE1Q==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@module-federation/runtime-tools': ^0.24.1 || ^2.0.0 - '@swc/helpers': '>=0.5.1' - peerDependenciesMeta: - '@module-federation/runtime-tools': - optional: true - '@swc/helpers': - optional: true - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} engines: {node: '>=18'} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -2114,200 +2367,176 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' - '@tailwindcss/forms@0.5.11': - resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} + '@tailwindcss/forms@0.5.10': + resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} peerDependencies: tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' - '@tailwindcss/typography@0.5.20': - resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} peerDependencies: - tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' - '@turbo/darwin-64@2.10.8': - resolution: {integrity: sha512-po+7rfJfUnFXjWlcoN2RwhErgzCdRtBc1T26vYPcywHlggmCQiQe1uWaE4j+BibI2uY9/2pDoFzMN0rmSaPFOw==} - cpu: [x64] - os: [darwin] + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - '@turbo/darwin-arm64@2.10.8': - resolution: {integrity: sha512-+zB2btDJ00lnPRuqOvpVvgl4x34k/djZQGZTTCfjn7JgNCl8QFY5Njo5+dqkY1g/+9gbbsnAvWm9CmJg9ebcXA==} - cpu: [arm64] - os: [darwin] + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@turbo/linux-64@2.10.8': - resolution: {integrity: sha512-K1dxqiVisyN7cViVsfQLs6xscQbYuI8aO2nbUhFURDACgEDfZRdP/b4CCxeosBJpcMfhYyiibWqJorCnvz9kKg==} - cpu: [x64] - os: [android, linux] + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - '@turbo/linux-arm64@2.10.8': - resolution: {integrity: sha512-Gi77ibVnrE1fEmvr+/wBD/yvRqhwp/RQuCp2+//lv1U1wNFFyVg0V7Wj8FG9FXPFAw5QHReo8rxc9+wBSDZjzA==} - cpu: [arm64] - os: [android, linux] + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@turbo/windows-64@2.10.8': - resolution: {integrity: sha512-znnLO1haJPYTHoKMKwlAvlkjRiYbbhBzME6wIGaMd+fwir23U6jVd1ecaTWWi1fbnRVqxMfgDBKseQ/hLKb83g==} - cpu: [x64] - os: [win32] + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@turbo/windows-arm64@2.10.8': - resolution: {integrity: sha512-VN30vh3b3Czh2WzYHNTfF1FE0YMZ5aHsLO8dBMGHJewA6792wX6iJR8ZxlzFW6WdOu0gEAKIvlYhfyT81Wkm4Q==} - cpu: [arm64] - os: [win32] + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} - '@tybys/wasm-util@0.10.3': - resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/bun@1.2.11': + resolution: {integrity: sha512-ZLbbI91EmmGwlWTRWuV6J19IUiUC5YQ3TCEuSHI3usIP75kuoA8/0PVF+LTrbEnVc8JIhpElWOxv1ocI1fJBbw==} - '@types/bun@1.3.14': - resolution: {integrity: sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==} - - '@types/chai@5.2.3': - resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.20.1': - resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@25.9.1': - resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/node@20.14.13': + resolution: {integrity: sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==} '@types/postcss-import@14.0.3': resolution: {integrity: sha512-raZhRVTf6Vw5+QbmQ7LOHSDML71A5rj4+EqDzAbrZPfxfoGzFxMHRCq16VlddGIZpHELw0BG4G0YE2ANkdZiIQ==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + '@types/react-dom@19.1.2': + resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^19.0.0 - '@types/react@19.2.15': - resolution: {integrity: sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==} + '@types/react@19.1.2': + resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==} - '@types/semver@7.8.0': - resolution: {integrity: sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==} + '@types/semver@7.7.0': + resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} - '@vitejs/plugin-react@6.0.2': - resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} - engines: {node: ^20.19.0 || >=22.12.0} + '@typescript-eslint/eslint-plugin@8.11.0': + resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: + typescript: optional: true - '@vitest/expect@4.1.10': - resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - - '@vitest/mocker@4.1.10': - resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + '@typescript-eslint/parser@8.11.0': + resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' peerDependenciesMeta: - msw: - optional: true - vite: + typescript: optional: true - '@vitest/pretty-format@4.1.10': - resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@typescript-eslint/scope-manager@8.11.0': + resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/runner@4.1.10': - resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + '@typescript-eslint/type-utils@8.11.0': + resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/snapshot@4.1.10': - resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + '@typescript-eslint/types@8.11.0': + resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/spy@4.1.10': - resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@typescript-eslint/typescript-estree@8.11.0': + resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - '@vitest/utils@4.1.10': - resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@typescript-eslint/utils@8.11.0': + resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + '@typescript-eslint/visitor-keys@8.11.0': + resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + '@vitejs/plugin-react@4.4.1': + resolution: {integrity: sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + '@vitest/expect@2.0.5': + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + '@vitest/pretty-format@2.0.5': + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + '@vitest/runner@2.0.5': + resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + '@vitest/snapshot@2.0.5': + resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + '@vitest/spy@2.0.5': + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + '@vitest/utils@2.0.5': + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv-keywords@5.1.0: - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2322,48 +2551,102 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.5: + resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - autoprefixer@10.5.0: - resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - baseline-browser-mapping@2.10.31: - resolution: {integrity: sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==} - engines: {node: '>=6.0.0'} - hasBin: true + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} - before-after-hook@4.0.0: - resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + axe-core@4.10.2: + resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.28.2: - resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bun-types@1.3.14: - resolution: {integrity: sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==} + bun-types@1.2.11: + resolution: {integrity: sha512-dbkp5Lo8HDrXkLrONm6bk+yiiYQSntvFUzQp0v3pzTAsXk6FtgVMjdQ+lzFNVAmQFUkPQZ3WMZqH5tTo+Dp/IA==} - bun@1.3.14: - resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==} - cpu: [arm64, x64] - os: [darwin, linux, android, freebsd, win32] + bun@1.2.11: + resolution: {integrity: sha512-9brVfsp6/TYVsE3lCl1MUxoyKhvljqyL1MNPErgwsOaS9g4Gzi2nY+W5WtRAXGzLrgz5jzsoGHHwyH/rTeRCIg==} + os: [darwin, linux, win32] hasBin: true bundle-require@5.1.0: @@ -2372,23 +2655,43 @@ packages: peerDependencies: esbuild: '>=0.18' + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001793: - resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + caniuse-lite@1.0.30001705: + resolution: {integrity: sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==} - chai@6.2.2: - resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} - engines: {node: '>=18'} + chai@5.1.1: + resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + engines: {node: '>=12'} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -2398,12 +2701,12 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} @@ -2417,6 +2720,24 @@ packages: peerDependencies: typanion: '*' + clipboardy@4.0.0: + resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -2427,44 +2748,69 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} - content-type@2.0.0: - resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} - engines: {node: '>=18'} + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.3: - resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - crossws@0.3.5: - resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} - crossws@0.4.10: - resolution: {integrity: sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==} - peerDependencies: - srvx: '>=0.11.5' - peerDependenciesMeta: - srvx: - optional: true + crossws@0.3.1: + resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} + + crossws@0.3.4: + resolution: {integrity: sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2472,16 +2818,40 @@ packages: supports-color: optional: true - dedent@1.7.2: - resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - defu@6.1.7: - resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} @@ -2491,8 +2861,8 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} didyoumean@1.2.2: @@ -2501,33 +2871,82 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - electron-to-chromium@1.5.361: - resolution: {integrity: sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} - emnapi@1.11.3: - resolution: {integrity: sha512-+/ZS90YK/rYfVOHtGLHkGffVsnmD/MAKaBHio+Y4XAtg75RLr4cveV/w0jTkUdLM1CcAlaRgG76mpIemWAlk0A==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.119: + resolution: {integrity: sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==} + + emnapi@1.4.3: + resolution: {integrity: sha512-qeLT07Xt4xNjlDJYtwtO664e44HXDaHnja7vWIy/g3xzZiNOcQh02gNblR6tkqnDzPPhc+dUeSYWbt6PoiYZKQ==} peerDependencies: node-addon-api: '>= 6.1.0' peerDependenciesMeta: node-addon-api: optional: true - enhanced-resolve@5.24.5: - resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.1.0: - resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + es-iterator-helpers@1.1.0: + resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} + engines: {node: '>= 0.4'} - es-toolkit@1.50.0: - resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} engines: {node: '>=18'} hasBin: true @@ -2535,18 +2954,118 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@15.3.1: + resolution: {integrity: sha512-GnmyVd9TE/Ihe3RrvcafFhXErErtr2jS0JDeCSp3vWvy86AXwHsRBt0E3MqP/m8ACS1ivcsi5uaqjbhsG18qKw==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.3: + resolution: {integrity: sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.1: + resolution: {integrity: sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.0.0: + resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.2: + resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.25.1: + resolution: {integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2554,68 +3073,81 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} - engines: {node: '>=12.0.0'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} - fast-content-type-parse@3.0.0: - resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-equals@5.4.0: - resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} - engines: {node: '>=6.0.0'} + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-string-truncated-width@3.0.3: - resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-string-width@3.0.2: - resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-stringify@4.0.0: - resolution: {integrity: sha512-lE2DIivBaLysf6hK5WH/VfMgqRbvBVHcpGVVTmA5Zi8oWIjq9YxIt6lYGdUgP1HNSXxTIat7HEIDnrSvXSeKQw==} + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fast-uri@3.1.2: - resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} - - fast-wrap-ansi@0.2.2: - resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} engines: {node: '>=18'} - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -2630,8 +3162,37 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - get-port-please@3.2.0: - resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-port-please@3.1.2: + resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -2641,184 +3202,419 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - globby@16.2.2: - resolution: {integrity: sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==} - engines: {node: '>=20'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - h3@1.15.11: - resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + h3@1.15.3: + resolution: {integrity: sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.3: + resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} + is-bun-module@1.2.1: + resolution: {integrity: sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.0: + resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + is64bit@2.0.0: + resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} + engines: {node: '>=18'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.3: + resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.6: + resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true - json-with-bigint@3.5.8: - resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.29.2: + resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + lightningcss-darwin-x64@1.29.2: + resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + lightningcss-freebsd-x64@1.29.2: + resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + lightningcss-linux-arm-gnueabihf@1.29.2: + resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + lightningcss-linux-arm64-gnu@1.29.2: + resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==} engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + lightningcss-linux-arm64-musl@1.29.2: + resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} engines: {node: '>= 12.0.0'} - cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + lightningcss-linux-x64-gnu@1.29.2: + resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + lightningcss-linux-x64-musl@1.29.2: + resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + lightningcss-win32-arm64-msvc@1.29.2: + resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + lightningcss-win32-x64-msvc@1.29.2: + resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==} engines: {node: '>= 12.0.0'} - cpu: [x64] os: [win32] - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + lightningcss@1.29.2: + resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} engines: {node: '>=14'} lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - listhen@1.10.1: - resolution: {integrity: sha512-6nt/86SkqUQSLW1ofz8MxC6RhRMqOl3ONISe6qqvJ3xj09aJWQx6DhgSZpugs3PX4PXdOas/WD6A9jx6J2N19A==} + listhen@1.9.0: + resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} hasBin: true - peerDependencies: - '@parcel/watcher': ^2.5.6 - peerDependenciesMeta: - '@parcel/watcher': - optional: true load-tsconfig@0.2.5: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} - magic-string@1.1.0: - resolution: {integrity: sha512-kS3VHe0nEPST2saQV4Rbkchcd3UBRkVTQHo1D3h/ZTwFDhai/mfKkmtPAtD129EOI7K3HlHIsFOt0WrI2/oU9g==} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.1.1: + resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2827,101 +3623,88 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micro-memoize@5.1.1: - resolution: {integrity: sha512-QDwluos8YeMijiKxZGwaV4f4tzj0soS6+xcsJhJ3+4wdEIHMyKbIKVUziebOgWX3e6yiijdoaHo+9tyhbnaWXA==} + micro-memoize@4.1.3: + resolution: {integrity: sha512-DzRMi8smUZXT7rCGikRwldEh6eO6qzKiPPopcr1+2EY3AYKpy5fu159PKWwIS9A6IWnrvPKDMcuFtyrroZa8Bw==} + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} mini-svg-data-uri@1.4.4: resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} hasBin: true - minimizer-webpack-plugin@5.6.1: - resolution: {integrity: sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@minify-html/node': '*' - '@swc/core': '*' - '@swc/css': '*' - '@swc/html': '*' - clean-css: '*' - cssnano: '*' - csso: '*' - esbuild: '*' - html-minifier-terser: '*' - lightningcss: '*' - postcss: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@minify-html/node': - optional: true - '@swc/core': - optional: true - '@swc/css': - optional: true - '@swc/html': - optional: true - clean-css: - optional: true - cssnano: - optional: true - csso: - optional: true - esbuild: - optional: true - html-minifier-terser: - optional: true - lightningcss: - optional: true - postcss: - optional: true - uglify-js: - optional: true + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.7.3: + resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@16.2.7: - resolution: {integrity: sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w==} - engines: {node: '>=20.9.0'} + next@15.3.1: + resolution: {integrity: sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 + '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -2939,29 +3722,44 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-addon-api@8.7.0: - resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + node-addon-api@8.2.2: + resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} engines: {node: ^18 || ^20 || >= 21} - node-forge@1.4.0: - resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + node-addon-api@8.3.0: + resolution: {integrity: sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==} + engines: {node: ^18 || ^20 || >= 21} + + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-mock-http@1.0.4: - resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + node-mock-http@1.0.0: + resolution: {integrity: sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==} - node-releases@2.0.46: - resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} - engines: {node: '>=18'} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2970,77 +3768,161 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} - obug@2.1.4: - resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} - engines: {node: '>=12.20.0'} + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + engines: {node: '>= 0.4'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} package-up@5.0.0: resolution: {integrity: sha512-MQEgDUvXCa3sGvqHg3pzHO8e9gqTCMPVrWUko3vPQGntwegmFo52mZb2abIVTjFnUcW0BcPz0D93jV5Cas1DWA==} engines: {node: '>=18'} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pkg-types@1.3.0: + resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} - playwright-core@1.62.1: - resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} - engines: {node: '>=20'} + playwright-core@1.52.0: + resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} + engines: {node: '>=18'} hasBin: true - playwright@1.62.1: - resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} - engines: {node: '>=20'} + playwright@1.52.0: + resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} + engines: {node: '>=18'} hasBin: true + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 - postcss-import@16.1.1: - resolution: {integrity: sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==} + postcss-import@16.1.0: + resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} engines: {node: '>=18.0.0'} peerDependencies: postcss: ^8.0.0 - postcss-js@4.1.0: - resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.4.21 + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + postcss-load-config@6.0.1: resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} engines: {node: '>= 18'} @@ -3073,8 +3955,8 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss-selector-parser@7.1.4: - resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} engines: {node: '>=4'} postcss-value-parser@4.2.0: @@ -3084,54 +3966,70 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.16: - resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + postcss@8.4.41: + resolution: {integrity: sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - prettier-plugin-embed@0.5.1: - resolution: {integrity: sha512-2Ege8gIlLNTvHElUeU5XcFsD7/dbDXkQA6H9TczHSJAGxB58nNjjifk/dlRMS5E29eqQx/z+ToA4ZVMWzjME/A==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} - prettier-plugin-organize-imports@4.3.0: - resolution: {integrity: sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-embed@0.5.0: + resolution: {integrity: sha512-A5nzX8U9x+FJdpOKrDrH9eq86xHZNiGguWpphS6chTME0OK1bDgH1X+WLtZq7qV3kUEMkL/dHkr6C1NLdUA7RQ==} + + prettier-plugin-organize-imports@4.0.0: + resolution: {integrity: sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==} peerDependencies: + '@vue/language-plugin-pug': ^2.0.24 prettier: '>=2.0' typescript: '>=2.9' - vue-tsc: ^2.1.0 || 3 + vue-tsc: ^2.0.24 peerDependenciesMeta: + '@vue/language-plugin-pug': + optional: true vue-tsc: optional: true - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + prettier@3.5.0: + resolution: {integrity: sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==} engines: {node: '>=14'} hasBin: true + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - react-dom@19.2.6: - resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: - react: ^19.2.6 + react: ^19.1.0 - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} - peerDependencies: - react: ^19.2.7 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -3141,59 +4039,96 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + readdirp@4.1.1: + resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==} engines: {node: '>= 14.18.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} + reflect.getprototypeof@1.0.6: + resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rolldown@1.2.1: - resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rollup@4.60.4: - resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - schema-utils@4.3.3: - resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} - engines: {node: '>= 10.13.0'} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -3201,6 +4136,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -3216,18 +4154,69 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - std-env@4.2.0: - resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.11: + resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} @@ -3242,34 +4231,38 @@ packages: babel-plugin-macros: optional: true - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} + system-architecture@0.1.0: + resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} + engines: {node: '>=18'} - tailwindcss@3.4.19: - resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + tailwindcss@3.4.14: + resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} hasBin: true - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - terser@5.48.0: - resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + terser@5.31.6: + resolution: {integrity: sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==} engines: {node: '>=10'} hasBin: true @@ -3286,33 +4279,39 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyclip@0.1.15: - resolution: {integrity: sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==} - engines: {node: ^16.14.0 || >= 17.3.0} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} - engines: {node: '>=18'} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} + tinypool@1.0.0: + resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} + engines: {node: ^18.0.0 || >=20.0.0} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} + tinyspy@3.0.0: + resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + engines: {node: '>=14.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3333,17 +4332,26 @@ packages: tree-sitter: optional: true - tree-sitter@0.25.1: - resolution: {integrity: sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==} + tree-sitter@0.22.4: + resolution: {integrity: sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + tslib@2.8.0: + resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + + tsup@8.4.0: + resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -3361,90 +4369,188 @@ packages: typescript: optional: true - turbo@2.10.8: - resolution: {integrity: sha512-9+8YX5QOkGXzZxcIykTHgaooRHGMWO+jfdyRK0o+rN0U7hBIig2MrJ8r/aNzIPDPhdA73SGb0O+tIztaModTMg==} + tsx@4.19.1: + resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo-darwin-64@2.5.2: + resolution: {integrity: sha512-2aIl0Sx230nLk+Cg2qSVxvPOBWCZpwKNuAMKoROTvWKif6VMpkWWiR9XEPoz7sHeLmCOed4GYGMjL1bqAiIS/g==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.5.2: + resolution: {integrity: sha512-MrFYhK/jYu8N6QlqZtqSHi3e4QVxlzqU3ANHTKn3/tThuwTLbNHEvzBPWSj5W7nZcM58dCqi6gYrfRz6bJZyAA==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.5.2: + resolution: {integrity: sha512-LxNqUE2HmAJQ/8deoLgMUDzKxd5bKxqH0UBogWa+DF+JcXhtze3UTMr6lEr0dEofdsEUYK1zg8FRjglmwlN5YA==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.5.2: + resolution: {integrity: sha512-0MI1Ao1q8zhd+UUbIEsrM+yLq1BsrcJQRGZkxIsHFlGp7WQQH1oR3laBgfnUCNdCotCMD6w4moc9pUbXdOR3bg==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.5.2: + resolution: {integrity: sha512-hOLcbgZzE5ttACHHyc1ajmWYq4zKT42IC3G6XqgiXxMbS+4eyVYTL+7UvCZBd3Kca1u4TLQdLQjeO76zyDJc2A==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.5.2: + resolution: {integrity: sha512-fMU41ABhSLa18H8V3Z7BMCGynQ8x+wj9WyBMvWm1jeyRKgkvUYJsO2vkIsy8m0vrwnIeVXKOIn6eSe1ddlBVqw==} + cpu: [arm64] + os: [win32] + + turbo@2.5.2: + resolution: {integrity: sha512-Qo5lfuStr6LQh3sPQl7kIi243bGU4aHGDQJUf6ylAdGwks30jJFloc9NYHP7Y373+gGU9OS0faA4Mb5Sy8X9Xw==} hasBin: true typanion@3.14.0: resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} - type-fest@5.6.0: - resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==} - engines: {node: '>=20'} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.36.0: + resolution: {integrity: sha512-3T/PUdKTCnkUmhQU6FFJEHsLwadsRegktX3TNHk+2JJB9HlA8gp1/VXblXVDI93kSnXF2rdPx0GMbHtJIV2LPg==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.4: - resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} - unicorn-magic@0.4.0: - resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} - engines: {node: '>=20'} + universal-user-agent@7.0.2: + resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} - - untun@0.2.2: - resolution: {integrity: sha512-+NnOJcSiEtYsVgJmXUzQbJeRAFXJC4yPJYuh6kF9B0Rm6zunXcs/3GZOTllyocSbUDIxD6Bj7e/4ATw7sph1Sw==} + untun@0.1.3: + resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} hasBin: true - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - uqr@0.1.3: - resolution: {integrity: sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==} + uqr@0.1.2: + resolution: {integrity: sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite@8.2.0: - resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==} - engines: {node: ^20.19.0 || >=22.12.0} + vite-node@2.0.5: + resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.0: + resolution: {integrity: sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - esbuild: ^0.27.0 || ^0.28.0 + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vite@6.0.0: + resolution: {integrity: sha512-Q2+5yQV79EdnpbNxjD3/QHVMCBaQ3Kpd4/uL51UGuh38bIIM+s4o3FqyCzRvTRwFb+cWIUeZvaWwS9y2LD2qeQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true jiti: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true sass-embedded: @@ -3460,39 +4566,23 @@ packages: yaml: optional: true - vitest@4.1.10: - resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@2.0.5: + resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.10 - '@vitest/browser-preview': 4.1.10 - '@vitest/browser-webdriverio': 4.1.10 - '@vitest/coverage-istanbul': 4.1.10 - '@vitest/coverage-v8': 4.1.10 - '@vitest/ui': 4.1.10 + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.0.5 + '@vitest/ui': 2.0.5 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@opentelemetry/api': - optional: true '@types/node': optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': + '@vitest/browser': optional: true '@vitest/ui': optional: true @@ -3501,704 +4591,1052 @@ packages: jsdom: optional: true - watchpack@2.5.2: - resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} - engines: {node: '>=10.13.0'} + wasm-sjlj@1.0.6: + resolution: {integrity: sha512-pjaKtLJejlWm6+okPV2X1A6nIsRDD4qeK97eCh8DP8KXi3Nzn/HY01vpHhZHlhDri12eZqipjm8HhdTVw+ATxw==} - webpack-sources@3.5.1: - resolution: {integrity: sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==} - engines: {node: '>=10.13.0'} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - webpack@5.109.2: - resolution: {integrity: sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==} - engines: {node: '>=10.13.0'} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.4: + resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - yaml@2.9.0: - resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} - engines: {node: '>= 14.6'} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + snapshots: '@alloc/quick-lru@5.2.0': {} - '@emnapi/core@1.10.0': + '@ampproject/remapping@2.3.0': dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 - '@emnapi/core@1.11.3': + '@babel/code-frame@7.27.1': dependencies: - '@emnapi/wasi-threads': 1.2.3 - tslib: 2.8.1 + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@emnapi/core@2.0.0-alpha.3': + '@babel/compat-data@7.27.1': {} + + '@babel/core@7.27.1': dependencies: - '@emnapi/wasi-threads': 2.0.1 - tslib: 2.8.1 - optional: true + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.1 + '@babel/helper-compilation-targets': 7.27.1 + '@babel/helper-module-transforms': 7.27.1(@babel/core@7.27.1) + '@babel/helpers': 7.27.1 + '@babel/parser': 7.27.1 + '@babel/template': 7.27.1 + '@babel/traverse': 7.27.1 + '@babel/types': 7.27.1 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@emnapi/runtime@1.10.0': + '@babel/generator@7.27.1': dependencies: - tslib: 2.8.1 - optional: true + '@babel/parser': 7.27.1 + '@babel/types': 7.27.1 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 - '@emnapi/runtime@1.11.3': + '@babel/helper-compilation-targets@7.27.1': dependencies: - tslib: 2.8.1 + '@babel/compat-data': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 - '@emnapi/runtime@2.0.0-alpha.3': + '@babel/helper-module-imports@7.27.1': dependencies: - tslib: 2.8.1 - optional: true + '@babel/traverse': 7.27.1 + '@babel/types': 7.27.1 + transitivePeerDependencies: + - supports-color - '@emnapi/wasi-threads@1.2.1': + '@babel/helper-module-transforms@7.27.1(@babel/core@7.27.1)': dependencies: - tslib: 2.8.1 - optional: true + '@babel/core': 7.27.1 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.1 + transitivePeerDependencies: + - supports-color - '@emnapi/wasi-threads@1.2.3': + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.1': dependencies: - tslib: 2.8.1 + '@babel/template': 7.27.1 + '@babel/types': 7.27.1 - '@emnapi/wasi-threads@2.0.1': + '@babel/parser@7.27.1': dependencies: - tslib: 2.8.1 + '@babel/types': 7.27.1 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/template@7.27.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.1 + '@babel/types': 7.27.1 + + '@babel/traverse@7.27.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.1 + '@babel/parser': 7.27.1 + '@babel/template': 7.27.1 + '@babel/types': 7.27.1 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.1': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.0 + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.0 + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.0 + + '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/aix-ppc64@0.23.1': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/aix-ppc64@0.24.0': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/aix-ppc64@0.25.0': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/android-arm64@0.23.1': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/android-arm64@0.24.0': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/android-arm64@0.25.0': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/android-arm@0.23.1': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/android-arm@0.24.0': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/android-arm@0.25.0': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/android-x64@0.23.1': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/android-x64@0.24.0': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/android-x64@0.25.0': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/darwin-arm64@0.23.1': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/darwin-arm64@0.24.0': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/darwin-arm64@0.25.0': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/darwin-x64@0.23.1': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/darwin-x64@0.24.0': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/darwin-x64@0.25.0': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/freebsd-arm64@0.23.1': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/freebsd-arm64@0.24.0': optional: true - '@img/colour@1.1.0': + '@esbuild/freebsd-arm64@0.25.0': optional: true - '@img/sharp-darwin-arm64@0.34.5': + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.24.0': + optional: true + + '@esbuild/freebsd-x64@0.25.0': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.24.0': + optional: true + + '@esbuild/linux-arm64@0.25.0': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-arm@0.24.0': + optional: true + + '@esbuild/linux-arm@0.25.0': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.24.0': + optional: true + + '@esbuild/linux-ia32@0.25.0': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.24.0': + optional: true + + '@esbuild/linux-loong64@0.25.0': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.24.0': + optional: true + + '@esbuild/linux-mips64el@0.25.0': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.24.0': + optional: true + + '@esbuild/linux-ppc64@0.25.0': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.24.0': + optional: true + + '@esbuild/linux-riscv64@0.25.0': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.24.0': + optional: true + + '@esbuild/linux-s390x@0.25.0': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/linux-x64@0.24.0': + optional: true + + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.24.0': + optional: true + + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.24.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.24.0': + optional: true + + '@esbuild/openbsd-x64@0.25.0': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.24.0': + optional: true + + '@esbuild/sunos-x64@0.25.0': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.24.0': + optional: true + + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.24.0': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@esbuild/win32-x64@0.24.0': + optional: true + + '@esbuild/win32-x64@0.25.0': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.25.1(jiti@2.4.2))': + dependencies: + eslint: 9.25.1(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.25.1': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@img/sharp-darwin-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.34.5': + '@img/sharp-darwin-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.2.4': + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.2.4': + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.2.4': + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.2.4': + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - '@img/sharp-libvips-linux-ppc64@1.2.4': + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-riscv64@1.2.4': + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.2.4': + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.2.4': + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': + '@img/sharp-linux-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.34.5': + '@img/sharp-linux-arm@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-ppc64@0.34.5': + '@img/sharp-linux-s390x@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-riscv64@0.34.5': + '@img/sharp-linux-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.34.5': + '@img/sharp-linuxmusl-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linux-x64@0.34.5': + '@img/sharp-linuxmusl-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': + '@img/sharp-wasm32@0.34.1': dependencies: - '@emnapi/runtime': 1.11.3 + '@emnapi/runtime': 1.4.3 optional: true - '@img/sharp-win32-arm64@0.34.5': + '@img/sharp-win32-ia32@0.34.1': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@img/sharp-win32-x64@0.34.1': optional: true - '@img/sharp-win32-x64@0.34.5': - optional: true - - '@inquirer/ansi@2.0.7': {} - - '@inquirer/checkbox@5.2.1(@types/node@25.9.1)': + '@inquirer/checkbox@4.1.5(@types/node@20.14.13)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@20.14.13) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/confirm@6.1.1(@types/node@25.9.1)': + '@inquirer/confirm@5.1.9(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/core@11.2.1(@types/node@25.9.1)': + '@inquirer/core@10.1.10(@types/node@20.14.13)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@20.14.13) + ansi-escapes: 4.3.2 cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 + mute-stream: 2.0.0 signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/editor@5.2.2(@types/node@25.9.1)': + '@inquirer/editor@4.2.10(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/external-editor': 3.0.3(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) + external-editor: 3.1.0 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/expand@5.1.1(@types/node@25.9.1)': + '@inquirer/expand@4.0.12(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/external-editor@3.0.3(@types/node@25.9.1)': + '@inquirer/figures@1.0.11': {} + + '@inquirer/input@4.1.9(@types/node@20.14.13)': dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/figures@2.0.7': {} - - '@inquirer/input@5.1.2(@types/node@25.9.1)': + '@inquirer/number@3.0.12(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/number@4.1.1(@types/node@25.9.1)': + '@inquirer/password@4.0.12(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) + ansi-escapes: 4.3.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/password@5.1.1(@types/node@25.9.1)': + '@inquirer/prompts@7.4.1(@types/node@20.14.13)': dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/checkbox': 4.1.5(@types/node@20.14.13) + '@inquirer/confirm': 5.1.9(@types/node@20.14.13) + '@inquirer/editor': 4.2.10(@types/node@20.14.13) + '@inquirer/expand': 4.0.12(@types/node@20.14.13) + '@inquirer/input': 4.1.9(@types/node@20.14.13) + '@inquirer/number': 3.0.12(@types/node@20.14.13) + '@inquirer/password': 4.0.12(@types/node@20.14.13) + '@inquirer/rawlist': 4.0.12(@types/node@20.14.13) + '@inquirer/search': 3.0.12(@types/node@20.14.13) + '@inquirer/select': 4.1.1(@types/node@20.14.13) optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/prompts@8.5.2(@types/node@25.9.1)': + '@inquirer/rawlist@4.0.12(@types/node@20.14.13)': dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@25.9.1) - '@inquirer/confirm': 6.1.1(@types/node@25.9.1) - '@inquirer/editor': 5.2.2(@types/node@25.9.1) - '@inquirer/expand': 5.1.1(@types/node@25.9.1) - '@inquirer/input': 5.1.2(@types/node@25.9.1) - '@inquirer/number': 4.1.1(@types/node@25.9.1) - '@inquirer/password': 5.1.1(@types/node@25.9.1) - '@inquirer/rawlist': 5.3.1(@types/node@25.9.1) - '@inquirer/search': 4.2.1(@types/node@25.9.1) - '@inquirer/select': 5.2.1(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/type': 3.0.6(@types/node@20.14.13) + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/rawlist@5.3.1(@types/node@25.9.1)': + '@inquirer/search@3.0.12(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@20.14.13) + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/search@4.2.1(@types/node@25.9.1)': + '@inquirer/select@4.1.1(@types/node@20.14.13)': dependencies: - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/core': 10.1.10(@types/node@20.14.13) + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@20.14.13) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/select@5.2.1(@types/node@25.9.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@25.9.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@25.9.1) + '@inquirer/type@3.0.6(@types/node@20.14.13)': optionalDependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - '@inquirer/type@4.0.7(@types/node@25.9.1)': - optionalDependencies: - '@types/node': 25.9.1 - - '@jridgewell/gen-mapping@0.3.13': + '@isaacs/cliui@8.0.2': dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/remapping@2.3.5': + '@isaacs/fs-minipass@4.0.1': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.11': + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/trace-mapping@0.3.31': + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.5.0 - '@napi-rs/cli@3.7.4(patch_hash=9912bf0a9c2cef8329d11c41fbce4d33ce2bdcf660a630b258c962c0a3c44bed)(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)(@types/node@25.9.1)(node-addon-api@8.7.0)': + '@napi-rs/cli@3.0.0-alpha.78(@emnapi/runtime@1.4.3)(@types/node@20.14.13)(emnapi@1.4.3(node-addon-api@8.3.0))': dependencies: - '@inquirer/prompts': 8.5.2(@types/node@25.9.1) - '@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@octokit/rest': 22.0.1 + '@inquirer/prompts': 7.4.1(@types/node@20.14.13) + '@napi-rs/cross-toolchain': 0.0.19 + '@napi-rs/wasm-tools': 0.0.3 + '@octokit/rest': 21.1.1 clipanion: 4.0.0-rc.4(typanion@3.14.0) colorette: 2.0.20 - emnapi: 1.11.3(node-addon-api@8.7.0) - es-toolkit: 1.50.0 - js-yaml: 4.3.1 - obug: 2.1.4 - semver: 7.8.5 + debug: 4.4.0 + js-yaml: 4.1.0 + lodash-es: 4.17.21 + semver: 7.7.1 + toml: 3.0.0 typanion: 3.14.0 + wasm-sjlj: 1.0.6 optionalDependencies: - '@emnapi/runtime': 1.11.3 + '@emnapi/runtime': 1.4.3 + emnapi: 1.4.3(node-addon-api@8.3.0) transitivePeerDependencies: - - '@emnapi/core' - '@napi-rs/cross-toolchain-arm64-target-aarch64' - '@napi-rs/cross-toolchain-arm64-target-armv7' - - '@napi-rs/cross-toolchain-arm64-target-ppc64le' - - '@napi-rs/cross-toolchain-arm64-target-s390x' - '@napi-rs/cross-toolchain-arm64-target-x86_64' - '@napi-rs/cross-toolchain-x64-target-aarch64' - '@napi-rs/cross-toolchain-x64-target-armv7' - - '@napi-rs/cross-toolchain-x64-target-ppc64le' - - '@napi-rs/cross-toolchain-x64-target-s390x' - '@napi-rs/cross-toolchain-x64-target-x86_64' - '@types/node' - - node-addon-api - supports-color - '@napi-rs/cross-toolchain@1.0.3(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/cross-toolchain@0.0.19': dependencies: - '@napi-rs/lzma': 1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/tar': 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - debug: 4.4.3 + '@napi-rs/lzma': 1.4.1 + '@napi-rs/tar': 0.1.4 + debug: 4.4.0 transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - supports-color - '@napi-rs/lzma-android-arm-eabi@1.4.5': + '@napi-rs/lzma-android-arm-eabi@1.4.1': optional: true - '@napi-rs/lzma-android-arm64@1.4.5': + '@napi-rs/lzma-android-arm64@1.4.1': optional: true - '@napi-rs/lzma-darwin-arm64@1.4.5': + '@napi-rs/lzma-darwin-arm64@1.4.1': optional: true - '@napi-rs/lzma-darwin-x64@1.4.5': + '@napi-rs/lzma-darwin-x64@1.4.1': optional: true - '@napi-rs/lzma-freebsd-x64@1.4.5': + '@napi-rs/lzma-freebsd-x64@1.4.1': optional: true - '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.1': optional: true - '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + '@napi-rs/lzma-linux-arm64-gnu@1.4.1': optional: true - '@napi-rs/lzma-linux-arm64-musl@1.4.5': + '@napi-rs/lzma-linux-arm64-musl@1.4.1': optional: true - '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': + '@napi-rs/lzma-linux-ppc64-gnu@1.4.1': optional: true - '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': + '@napi-rs/lzma-linux-riscv64-gnu@1.4.1': optional: true - '@napi-rs/lzma-linux-s390x-gnu@1.4.5': + '@napi-rs/lzma-linux-s390x-gnu@1.4.1': optional: true - '@napi-rs/lzma-linux-x64-gnu@1.4.5': + '@napi-rs/lzma-linux-x64-gnu@1.4.1': optional: true - '@napi-rs/lzma-linux-x64-musl@1.4.5': + '@napi-rs/lzma-linux-x64-musl@1.4.1': optional: true - '@napi-rs/lzma-wasm32-wasi@1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/lzma-wasm32-wasi@1.4.1': dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/wasm-runtime': 0.2.9 optional: true - '@napi-rs/lzma-win32-arm64-msvc@1.4.5': + '@napi-rs/lzma-win32-arm64-msvc@1.4.1': optional: true - '@napi-rs/lzma-win32-ia32-msvc@1.4.5': + '@napi-rs/lzma-win32-ia32-msvc@1.4.1': optional: true - '@napi-rs/lzma-win32-x64-msvc@1.4.5': + '@napi-rs/lzma-win32-x64-msvc@1.4.1': optional: true - '@napi-rs/lzma@1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/lzma@1.4.1': optionalDependencies: - '@napi-rs/lzma-android-arm-eabi': 1.4.5 - '@napi-rs/lzma-android-arm64': 1.4.5 - '@napi-rs/lzma-darwin-arm64': 1.4.5 - '@napi-rs/lzma-darwin-x64': 1.4.5 - '@napi-rs/lzma-freebsd-x64': 1.4.5 - '@napi-rs/lzma-linux-arm-gnueabihf': 1.4.5 - '@napi-rs/lzma-linux-arm64-gnu': 1.4.5 - '@napi-rs/lzma-linux-arm64-musl': 1.4.5 - '@napi-rs/lzma-linux-ppc64-gnu': 1.4.5 - '@napi-rs/lzma-linux-riscv64-gnu': 1.4.5 - '@napi-rs/lzma-linux-s390x-gnu': 1.4.5 - '@napi-rs/lzma-linux-x64-gnu': 1.4.5 - '@napi-rs/lzma-linux-x64-musl': 1.4.5 - '@napi-rs/lzma-wasm32-wasi': 1.4.5(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/lzma-win32-arm64-msvc': 1.4.5 - '@napi-rs/lzma-win32-ia32-msvc': 1.4.5 - '@napi-rs/lzma-win32-x64-msvc': 1.4.5 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/lzma-android-arm-eabi': 1.4.1 + '@napi-rs/lzma-android-arm64': 1.4.1 + '@napi-rs/lzma-darwin-arm64': 1.4.1 + '@napi-rs/lzma-darwin-x64': 1.4.1 + '@napi-rs/lzma-freebsd-x64': 1.4.1 + '@napi-rs/lzma-linux-arm-gnueabihf': 1.4.1 + '@napi-rs/lzma-linux-arm64-gnu': 1.4.1 + '@napi-rs/lzma-linux-arm64-musl': 1.4.1 + '@napi-rs/lzma-linux-ppc64-gnu': 1.4.1 + '@napi-rs/lzma-linux-riscv64-gnu': 1.4.1 + '@napi-rs/lzma-linux-s390x-gnu': 1.4.1 + '@napi-rs/lzma-linux-x64-gnu': 1.4.1 + '@napi-rs/lzma-linux-x64-musl': 1.4.1 + '@napi-rs/lzma-wasm32-wasi': 1.4.1 + '@napi-rs/lzma-win32-arm64-msvc': 1.4.1 + '@napi-rs/lzma-win32-ia32-msvc': 1.4.1 + '@napi-rs/lzma-win32-x64-msvc': 1.4.1 - '@napi-rs/tar-android-arm-eabi@1.1.0': + '@napi-rs/tar-android-arm-eabi@0.1.4': optional: true - '@napi-rs/tar-android-arm64@1.1.0': + '@napi-rs/tar-android-arm64@0.1.4': optional: true - '@napi-rs/tar-darwin-arm64@1.1.0': + '@napi-rs/tar-darwin-arm64@0.1.4': optional: true - '@napi-rs/tar-darwin-x64@1.1.0': + '@napi-rs/tar-darwin-x64@0.1.4': optional: true - '@napi-rs/tar-freebsd-x64@1.1.0': + '@napi-rs/tar-freebsd-x64@0.1.4': optional: true - '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': + '@napi-rs/tar-linux-arm-gnueabihf@0.1.4': optional: true - '@napi-rs/tar-linux-arm64-gnu@1.1.0': + '@napi-rs/tar-linux-arm64-gnu@0.1.4': optional: true - '@napi-rs/tar-linux-arm64-musl@1.1.0': + '@napi-rs/tar-linux-arm64-musl@0.1.4': optional: true - '@napi-rs/tar-linux-ppc64-gnu@1.1.0': + '@napi-rs/tar-linux-ppc64-gnu@0.1.4': optional: true - '@napi-rs/tar-linux-s390x-gnu@1.1.0': + '@napi-rs/tar-linux-s390x-gnu@0.1.4': optional: true - '@napi-rs/tar-linux-x64-gnu@1.1.0': + '@napi-rs/tar-linux-x64-gnu@0.1.4': optional: true - '@napi-rs/tar-linux-x64-musl@1.1.0': + '@napi-rs/tar-linux-x64-musl@0.1.4': optional: true - '@napi-rs/tar-wasm32-wasi@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/tar-wasm32-wasi@0.1.4': dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/wasm-runtime': 0.2.9 optional: true - '@napi-rs/tar-win32-arm64-msvc@1.1.0': + '@napi-rs/tar-win32-arm64-msvc@0.1.4': optional: true - '@napi-rs/tar-win32-ia32-msvc@1.1.0': + '@napi-rs/tar-win32-ia32-msvc@0.1.4': optional: true - '@napi-rs/tar-win32-x64-msvc@1.1.0': + '@napi-rs/tar-win32-x64-msvc@0.1.4': optional: true - '@napi-rs/tar@1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/tar@0.1.4': optionalDependencies: - '@napi-rs/tar-android-arm-eabi': 1.1.0 - '@napi-rs/tar-android-arm64': 1.1.0 - '@napi-rs/tar-darwin-arm64': 1.1.0 - '@napi-rs/tar-darwin-x64': 1.1.0 - '@napi-rs/tar-freebsd-x64': 1.1.0 - '@napi-rs/tar-linux-arm-gnueabihf': 1.1.0 - '@napi-rs/tar-linux-arm64-gnu': 1.1.0 - '@napi-rs/tar-linux-arm64-musl': 1.1.0 - '@napi-rs/tar-linux-ppc64-gnu': 1.1.0 - '@napi-rs/tar-linux-s390x-gnu': 1.1.0 - '@napi-rs/tar-linux-x64-gnu': 1.1.0 - '@napi-rs/tar-linux-x64-musl': 1.1.0 - '@napi-rs/tar-wasm32-wasi': 1.1.0(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/tar-win32-arm64-msvc': 1.1.0 - '@napi-rs/tar-win32-ia32-msvc': 1.1.0 - '@napi-rs/tar-win32-x64-msvc': 1.1.0 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/tar-android-arm-eabi': 0.1.4 + '@napi-rs/tar-android-arm64': 0.1.4 + '@napi-rs/tar-darwin-arm64': 0.1.4 + '@napi-rs/tar-darwin-x64': 0.1.4 + '@napi-rs/tar-freebsd-x64': 0.1.4 + '@napi-rs/tar-linux-arm-gnueabihf': 0.1.4 + '@napi-rs/tar-linux-arm64-gnu': 0.1.4 + '@napi-rs/tar-linux-arm64-musl': 0.1.4 + '@napi-rs/tar-linux-ppc64-gnu': 0.1.4 + '@napi-rs/tar-linux-s390x-gnu': 0.1.4 + '@napi-rs/tar-linux-x64-gnu': 0.1.4 + '@napi-rs/tar-linux-x64-musl': 0.1.4 + '@napi-rs/tar-wasm32-wasi': 0.1.4 + '@napi-rs/tar-win32-arm64-msvc': 0.1.4 + '@napi-rs/tar-win32-ia32-msvc': 0.1.4 + '@napi-rs/tar-win32-x64-msvc': 0.1.4 - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@0.2.9': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.3 + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + + '@napi-rs/wasm-tools-android-arm-eabi@0.0.3': optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/wasm-tools-android-arm64@0.0.3': + optional: true + + '@napi-rs/wasm-tools-darwin-arm64@0.0.3': + optional: true + + '@napi-rs/wasm-tools-darwin-x64@0.0.3': + optional: true + + '@napi-rs/wasm-tools-freebsd-x64@0.0.3': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-gnu@0.0.3': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-musl@0.0.3': + optional: true + + '@napi-rs/wasm-tools-linux-x64-gnu@0.0.3': + optional: true + + '@napi-rs/wasm-tools-linux-x64-musl@0.0.3': + optional: true + + '@napi-rs/wasm-tools-wasm32-wasi@0.0.3': dependencies: - '@emnapi/core': 1.11.3 - '@emnapi/runtime': 1.11.3 - '@tybys/wasm-util': 0.10.3 - - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@tybys/wasm-util': 0.10.3 + '@napi-rs/wasm-runtime': 0.2.9 optional: true - '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + '@napi-rs/wasm-tools-win32-arm64-msvc@0.0.3': optional: true - '@napi-rs/wasm-tools-android-arm64@1.0.1': + '@napi-rs/wasm-tools-win32-ia32-msvc@0.0.3': optional: true - '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + '@napi-rs/wasm-tools-win32-x64-msvc@0.0.3': optional: true - '@napi-rs/wasm-tools-darwin-x64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-freebsd-x64@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': - optional: true - - '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': - optional: true - - '@napi-rs/wasm-tools-wasm32-wasi@1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': - dependencies: - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' - optional: true - - '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': - optional: true - - '@napi-rs/wasm-tools@1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': + '@napi-rs/wasm-tools@0.0.3': optionalDependencies: - '@napi-rs/wasm-tools-android-arm-eabi': 1.0.1 - '@napi-rs/wasm-tools-android-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-arm64': 1.0.1 - '@napi-rs/wasm-tools-darwin-x64': 1.0.1 - '@napi-rs/wasm-tools-freebsd-x64': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-arm64-musl': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-gnu': 1.0.1 - '@napi-rs/wasm-tools-linux-x64-musl': 1.0.1 - '@napi-rs/wasm-tools-wasm32-wasi': 1.0.1(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) - '@napi-rs/wasm-tools-win32-arm64-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 - '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 - transitivePeerDependencies: - - '@emnapi/core' - - '@emnapi/runtime' + '@napi-rs/wasm-tools-android-arm-eabi': 0.0.3 + '@napi-rs/wasm-tools-android-arm64': 0.0.3 + '@napi-rs/wasm-tools-darwin-arm64': 0.0.3 + '@napi-rs/wasm-tools-darwin-x64': 0.0.3 + '@napi-rs/wasm-tools-freebsd-x64': 0.0.3 + '@napi-rs/wasm-tools-linux-arm64-gnu': 0.0.3 + '@napi-rs/wasm-tools-linux-arm64-musl': 0.0.3 + '@napi-rs/wasm-tools-linux-x64-gnu': 0.0.3 + '@napi-rs/wasm-tools-linux-x64-musl': 0.0.3 + '@napi-rs/wasm-tools-wasm32-wasi': 0.0.3 + '@napi-rs/wasm-tools-win32-arm64-msvc': 0.0.3 + '@napi-rs/wasm-tools-win32-ia32-msvc': 0.0.3 + '@napi-rs/wasm-tools-win32-x64-msvc': 0.0.3 - '@next/env@16.2.7': {} + '@next/env@15.3.1': {} - '@next/swc-darwin-arm64@16.2.7': + '@next/eslint-plugin-next@15.3.1': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@15.3.1': optional: true - '@next/swc-darwin-x64@16.2.7': + '@next/swc-darwin-x64@15.3.1': optional: true - '@next/swc-linux-arm64-gnu@16.2.7': + '@next/swc-linux-arm64-gnu@15.3.1': optional: true - '@next/swc-linux-arm64-musl@16.2.7': + '@next/swc-linux-arm64-musl@15.3.1': optional: true - '@next/swc-linux-x64-gnu@16.2.7': + '@next/swc-linux-x64-gnu@15.3.1': optional: true - '@next/swc-linux-x64-musl@16.2.7': + '@next/swc-linux-x64-musl@15.3.1': optional: true - '@next/swc-win32-arm64-msvc@16.2.7': + '@next/swc-win32-arm64-msvc@15.3.1': optional: true - '@next/swc-win32-x64-msvc@16.2.7': + '@next/swc-win32-x64-msvc@15.3.1': optional: true '@nodelib/fs.scandir@2.1.5': @@ -4211,665 +5649,780 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + fastq: 1.17.1 - '@octokit/auth-token@6.0.0': {} + '@nolyfill/is-core-module@1.0.39': {} - '@octokit/core@7.0.6': + '@octokit/auth-token@5.1.2': {} + + '@octokit/core@6.1.4': dependencies: - '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.3 - '@octokit/request': 10.0.9 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - before-after-hook: 4.0.0 - universal-user-agent: 7.0.3 + '@octokit/auth-token': 5.1.2 + '@octokit/graphql': 8.2.1 + '@octokit/request': 9.2.2 + '@octokit/request-error': 6.1.7 + '@octokit/types': 13.10.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.2 - '@octokit/endpoint@11.0.3': + '@octokit/endpoint@10.1.3': dependencies: - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 + '@octokit/types': 13.10.0 + universal-user-agent: 7.0.2 - '@octokit/graphql@9.0.3': + '@octokit/graphql@8.2.1': dependencies: - '@octokit/request': 10.0.9 - '@octokit/types': 16.0.0 - universal-user-agent: 7.0.3 + '@octokit/request': 9.2.2 + '@octokit/types': 13.10.0 + universal-user-agent: 7.0.2 - '@octokit/openapi-types@27.0.0': {} + '@octokit/openapi-types@24.2.0': {} - '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-paginate-rest@11.6.0(@octokit/core@6.1.4)': dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 + '@octokit/core': 6.1.4 + '@octokit/types': 13.10.0 - '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.4)': dependencies: - '@octokit/core': 7.0.6 + '@octokit/core': 6.1.4 - '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + '@octokit/plugin-rest-endpoint-methods@13.5.0(@octokit/core@6.1.4)': dependencies: - '@octokit/core': 7.0.6 - '@octokit/types': 16.0.0 + '@octokit/core': 6.1.4 + '@octokit/types': 13.10.0 - '@octokit/request-error@7.1.0': + '@octokit/request-error@6.1.7': dependencies: - '@octokit/types': 16.0.0 + '@octokit/types': 13.10.0 - '@octokit/request@10.0.9': + '@octokit/request@9.2.2': dependencies: - '@octokit/endpoint': 11.0.3 - '@octokit/request-error': 7.1.0 - '@octokit/types': 16.0.0 - content-type: 2.0.0 - fast-content-type-parse: 3.0.0 - json-with-bigint: 3.5.8 - universal-user-agent: 7.0.3 + '@octokit/endpoint': 10.1.3 + '@octokit/request-error': 6.1.7 + '@octokit/types': 13.10.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.2 - '@octokit/rest@22.0.1': + '@octokit/rest@21.1.1': dependencies: - '@octokit/core': 7.0.6 - '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) - '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) - '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/core': 6.1.4 + '@octokit/plugin-paginate-rest': 11.6.0(@octokit/core@6.1.4) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.4) + '@octokit/plugin-rest-endpoint-methods': 13.5.0(@octokit/core@6.1.4) - '@octokit/types@16.0.0': + '@octokit/types@13.10.0': dependencies: - '@octokit/openapi-types': 27.0.0 + '@octokit/openapi-types': 24.2.0 - '@oven/bun-darwin-aarch64@1.3.14': + '@oven/bun-darwin-aarch64@1.2.11': optional: true - '@oven/bun-darwin-x64-baseline@1.3.14': + '@oven/bun-darwin-x64-baseline@1.2.11': optional: true - '@oven/bun-darwin-x64@1.3.14': + '@oven/bun-darwin-x64@1.2.11': optional: true - '@oven/bun-freebsd-aarch64@1.3.14': + '@oven/bun-linux-aarch64-musl@1.2.11': optional: true - '@oven/bun-freebsd-x64@1.3.14': + '@oven/bun-linux-aarch64@1.2.11': optional: true - '@oven/bun-linux-aarch64-android@1.3.14': + '@oven/bun-linux-x64-baseline@1.2.11': optional: true - '@oven/bun-linux-aarch64-musl@1.3.14': + '@oven/bun-linux-x64-musl-baseline@1.2.11': optional: true - '@oven/bun-linux-aarch64@1.3.14': + '@oven/bun-linux-x64-musl@1.2.11': optional: true - '@oven/bun-linux-x64-android@1.3.14': + '@oven/bun-linux-x64@1.2.11': optional: true - '@oven/bun-linux-x64-baseline@1.3.14': + '@oven/bun-windows-x64-baseline@1.2.11': optional: true - '@oven/bun-linux-x64-musl-baseline@1.3.14': + '@oven/bun-windows-x64@1.2.11': optional: true - '@oven/bun-linux-x64-musl@1.3.14': + '@parcel/watcher-android-arm64@2.5.0': optional: true - '@oven/bun-linux-x64@1.3.14': + '@parcel/watcher-android-arm64@2.5.1': optional: true - '@oven/bun-windows-aarch64@1.3.14': + '@parcel/watcher-darwin-arm64@2.5.0': optional: true - '@oven/bun-windows-x64-baseline@1.3.14': + '@parcel/watcher-darwin-arm64@2.5.1': {} + + '@parcel/watcher-darwin-x64@2.5.0': optional: true - '@oven/bun-windows-x64@1.3.14': + '@parcel/watcher-darwin-x64@2.5.1': {} + + '@parcel/watcher-freebsd-x64@2.5.0': optional: true - '@oxc-project/types@0.142.0': {} - - '@parcel/watcher-android-arm64@2.6.0': + '@parcel/watcher-freebsd-x64@2.5.1': optional: true - '@parcel/watcher-darwin-arm64@2.6.0': {} - - '@parcel/watcher-darwin-x64@2.6.0': {} - - '@parcel/watcher-freebsd-x64@2.6.0': + '@parcel/watcher-linux-arm-glibc@2.5.0': optional: true - '@parcel/watcher-linux-arm-glibc@2.6.0': + '@parcel/watcher-linux-arm-glibc@2.5.1': optional: true - '@parcel/watcher-linux-arm-musl@2.6.0': + '@parcel/watcher-linux-arm-musl@2.5.0': optional: true - '@parcel/watcher-linux-arm64-glibc@2.6.0': {} + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true - '@parcel/watcher-linux-arm64-musl@2.6.0': {} + '@parcel/watcher-linux-arm64-glibc@2.5.0': + optional: true - '@parcel/watcher-linux-x64-glibc@2.6.0': {} + '@parcel/watcher-linux-arm64-glibc@2.5.1': {} - '@parcel/watcher-linux-x64-musl@2.6.0': {} + '@parcel/watcher-linux-arm64-musl@2.5.0': + optional: true - '@parcel/watcher-wasm@2.5.6': + '@parcel/watcher-linux-arm64-musl@2.5.1': {} + + '@parcel/watcher-linux-x64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': {} + + '@parcel/watcher-linux-x64-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': {} + + '@parcel/watcher-wasm@2.5.0': dependencies: is-glob: 4.0.3 - picomatch: 4.0.5 + micromatch: 4.0.7 - '@parcel/watcher-win32-arm64@2.6.0': + '@parcel/watcher-win32-arm64@2.5.0': optional: true - '@parcel/watcher-win32-x64@2.6.0': {} + '@parcel/watcher-win32-arm64@2.5.1': + optional: true - '@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)': + '@parcel/watcher-win32-ia32@2.5.0': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.0': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': {} + + '@parcel/watcher@2.5.0': dependencies: - detect-libc: 2.1.2 + detect-libc: 1.0.3 is-glob: 4.0.3 + micromatch: 4.0.7 node-addon-api: 7.1.1 - picomatch: 4.0.5 optionalDependencies: - '@parcel/watcher-android-arm64': 2.6.0 - '@parcel/watcher-darwin-arm64': 2.6.0 - '@parcel/watcher-darwin-x64': 2.6.0 - '@parcel/watcher-freebsd-x64': 2.6.0 - '@parcel/watcher-linux-arm-glibc': 2.6.0 - '@parcel/watcher-linux-arm-musl': 2.6.0 - '@parcel/watcher-linux-arm64-glibc': 2.6.0 - '@parcel/watcher-linux-arm64-musl': 2.6.0 - '@parcel/watcher-linux-x64-glibc': 2.6.0 - '@parcel/watcher-linux-x64-musl': 2.6.0 - '@parcel/watcher-win32-arm64': 2.6.0 - '@parcel/watcher-win32-x64': 2.6.0 + '@parcel/watcher-android-arm64': 2.5.0 + '@parcel/watcher-darwin-arm64': 2.5.0 + '@parcel/watcher-darwin-x64': 2.5.0 + '@parcel/watcher-freebsd-x64': 2.5.0 + '@parcel/watcher-linux-arm-glibc': 2.5.0 + '@parcel/watcher-linux-arm-musl': 2.5.0 + '@parcel/watcher-linux-arm64-glibc': 2.5.0 + '@parcel/watcher-linux-arm64-musl': 2.5.0 + '@parcel/watcher-linux-x64-glibc': 2.5.0 + '@parcel/watcher-linux-x64-musl': 2.5.0 + '@parcel/watcher-win32-arm64': 2.5.0 + '@parcel/watcher-win32-ia32': 2.5.0 + '@parcel/watcher-win32-x64': 2.5.0 - '@playwright/test@1.62.1': + '@parcel/watcher@2.5.1(patch_hash=zs2vvlrje3h42xp5ed2v44fep4)': dependencies: - playwright: 1.62.1 - - '@rolldown/binding-android-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.1': - optional: true - - '@rolldown/binding-darwin-x64@1.2.1': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.1': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.1': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.1': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.1': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.1': - optional: true - - '@rolldown/binding-wasm32-wasi@1.2.1': - dependencies: - '@emnapi/core': 2.0.0-alpha.3 - '@emnapi/runtime': 2.0.0-alpha.3 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.1': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.1': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@rollup/rollup-android-arm-eabi@4.60.4': - optional: true - - '@rollup/rollup-android-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.4': - optional: true - - '@rollup/rollup-darwin-x64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.4': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.4': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.4': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.4': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.4': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.4': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.4': - optional: true - - '@rspack/binding-darwin-arm64@2.0.2': - optional: true - - '@rspack/binding-darwin-x64@2.0.2': - optional: true - - '@rspack/binding-linux-arm64-gnu@2.0.2': - optional: true - - '@rspack/binding-linux-arm64-musl@2.0.2': - optional: true - - '@rspack/binding-linux-x64-gnu@2.0.2': - optional: true - - '@rspack/binding-linux-x64-musl@2.0.2': - optional: true - - '@rspack/binding-wasm32-wasi@2.0.2': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true - - '@rspack/binding-win32-arm64-msvc@2.0.2': - optional: true - - '@rspack/binding-win32-ia32-msvc@2.0.2': - optional: true - - '@rspack/binding-win32-x64-msvc@2.0.2': - optional: true - - '@rspack/binding@2.0.2': + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 optionalDependencies: - '@rspack/binding-darwin-arm64': 2.0.2 - '@rspack/binding-darwin-x64': 2.0.2 - '@rspack/binding-linux-arm64-gnu': 2.0.2 - '@rspack/binding-linux-arm64-musl': 2.0.2 - '@rspack/binding-linux-x64-gnu': 2.0.2 - '@rspack/binding-linux-x64-musl': 2.0.2 - '@rspack/binding-wasm32-wasi': 2.0.2 - '@rspack/binding-win32-arm64-msvc': 2.0.2 - '@rspack/binding-win32-ia32-msvc': 2.0.2 - '@rspack/binding-win32-x64-msvc': 2.0.2 + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 - '@rspack/core@2.0.2(@swc/helpers@0.5.15)': + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/test@1.52.0': dependencies: - '@rspack/binding': 2.0.2 - optionalDependencies: - '@swc/helpers': 0.5.15 + playwright: 1.52.0 - '@sindresorhus/merge-streams@4.0.0': {} + '@rollup/rollup-android-arm-eabi@4.34.9': + optional: true - '@standard-schema/spec@1.1.0': {} + '@rollup/rollup-android-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-arm64@4.34.9': + optional: true + + '@rollup/rollup-darwin-x64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-arm64@4.34.9': + optional: true + + '@rollup/rollup-freebsd-x64@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.34.9': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.34.9': + optional: true + + '@rollup/rollup-linux-x64-musl@4.34.9': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.34.9': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.34.9': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.10.4': {} + + '@sindresorhus/merge-streams@2.3.0': {} + + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': dependencies: - tslib: 2.8.1 + tslib: 2.8.0 '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@packages+tailwindcss)': dependencies: tailwindcss: link:packages/tailwindcss - '@tailwindcss/forms@0.5.11(tailwindcss@packages+tailwindcss)': + '@tailwindcss/forms@0.5.10(tailwindcss@packages+tailwindcss)': dependencies: mini-svg-data-uri: 1.4.4 tailwindcss: link:packages/tailwindcss - '@tailwindcss/typography@0.5.20(tailwindcss@packages+tailwindcss)': + '@tailwindcss/typography@0.5.16(tailwindcss@packages+tailwindcss)': dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 tailwindcss: link:packages/tailwindcss - '@turbo/darwin-64@2.10.8': - optional: true - - '@turbo/darwin-arm64@2.10.8': - optional: true - - '@turbo/linux-64@2.10.8': - optional: true - - '@turbo/linux-arm64@2.10.8': - optional: true - - '@turbo/windows-64@2.10.8': - optional: true - - '@turbo/windows-arm64@2.10.8': - optional: true - - '@tybys/wasm-util@0.10.3': + '@tybys/wasm-util@0.9.0': dependencies: - tslib: 2.8.1 + tslib: 2.8.0 - '@types/bun@1.3.14': + '@types/babel__core@7.20.5': dependencies: - bun-types: 1.3.14 + '@babel/parser': 7.27.1 + '@babel/types': 7.27.1 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 - '@types/chai@5.2.3': + '@types/babel__generator@7.6.8': dependencies: - '@types/deep-eql': 4.0.2 - assertion-error: 2.0.1 + '@babel/types': 7.27.1 - '@types/deep-eql@4.0.2': {} + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.1 + '@babel/types': 7.27.1 - '@types/estree@1.0.8': {} + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.27.1 - '@types/estree@1.0.9': {} + '@types/braces@3.0.5': {} + + '@types/bun@1.2.11': + dependencies: + bun-types: 1.2.11 + + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} - '@types/node@22.20.1': - dependencies: - undici-types: 6.21.0 + '@types/json5@0.0.29': {} - '@types/node@25.9.1': + '@types/node@20.14.13': dependencies: - undici-types: 7.24.6 + undici-types: 5.26.5 '@types/postcss-import@14.0.3': dependencies: - postcss: 8.5.25 + postcss: 8.4.41 - '@types/react-dom@19.2.3(@types/react@19.2.15)': + '@types/react-dom@19.1.2(@types/react@19.1.2)': dependencies: - '@types/react': 19.2.15 + '@types/react': 19.1.2 - '@types/react@19.2.15': + '@types/react@19.1.2': dependencies: - csstype: 3.2.3 + csstype: 3.1.3 - '@types/semver@7.8.0': {} + '@types/semver@7.7.0': {} - '@vitejs/plugin-react@6.0.2(vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4)': dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/type-utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.11.0 + eslint: 9.25.1(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color - '@vitest/expect@4.1.10': + '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3)': dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - chai: 6.2.2 - tinyrainbow: 3.1.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/type-utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.11.0 + eslint: 9.25.1(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color - '@vitest/mocker@4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4)': dependencies: - '@vitest/spy': 4.1.10 + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + eslint: 9.25.1(jiti@2.4.2) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + eslint: 9.25.1(jiti@2.4.2) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.11.0': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + + '@typescript-eslint/type-utils@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4)': + dependencies: + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + '@typescript-eslint/utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + debug: 4.4.0 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/type-utils@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + debug: 4.4.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.11.0': {} + + '@typescript-eslint/typescript-estree@8.11.0(typescript@5.5.4)': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 1.3.0(typescript@5.5.4) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@8.11.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/visitor-keys': 8.11.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.25.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.5.4) + eslint: 9.25.1(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.25.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.11.0 + '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) + eslint: 9.25.1(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.11.0': + dependencies: + '@typescript-eslint/types': 8.11.0 + eslint-visitor-keys: 3.4.3 + + '@vitejs/plugin-react@4.4.1(vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0))': + dependencies: + '@babel/core': 7.27.1 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.27.1) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.27.1) + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.0.5': + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + tinyrainbow: 1.2.0 + + '@vitest/pretty-format@2.0.5': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.0.5': + dependencies: + '@vitest/utils': 2.0.5 + pathe: 1.1.2 + + '@vitest/snapshot@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 + magic-string: 0.30.17 + pathe: 1.1.2 + + '@vitest/spy@2.0.5': + dependencies: + tinyspy: 3.0.0 + + '@vitest/utils@2.0.5': + dependencies: + '@vitest/pretty-format': 2.0.5 estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + loupe: 3.1.1 + tinyrainbow: 1.2.0 - '@vitest/pretty-format@4.1.10': + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - tinyrainbow: 3.1.0 + acorn: 8.14.0 - '@vitest/runner@4.1.10': - dependencies: - '@vitest/utils': 4.1.10 - pathe: 2.0.3 + acorn@8.14.0: {} - '@vitest/snapshot@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - '@vitest/utils': 4.1.10 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.10': {} - - '@vitest/utils@4.1.10': - dependencies: - '@vitest/pretty-format': 4.1.10 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - '@webassemblyjs/ast@1.14.1': - dependencies: - '@webassemblyjs/helper-numbers': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - - '@webassemblyjs/helper-api-error@1.13.2': {} - - '@webassemblyjs/helper-buffer@1.14.1': {} - - '@webassemblyjs/helper-numbers@1.13.2': - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.13.2 - '@webassemblyjs/helper-api-error': 1.13.2 - '@xtuc/long': 4.2.2 - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - - '@webassemblyjs/helper-wasm-section@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/wasm-gen': 1.14.1 - - '@webassemblyjs/ieee754@1.13.2': - dependencies: - '@xtuc/ieee754': 1.2.0 - - '@webassemblyjs/leb128@1.13.2': - dependencies: - '@xtuc/long': 4.2.2 - - '@webassemblyjs/utf8@1.13.2': {} - - '@webassemblyjs/wasm-edit@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/helper-wasm-section': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-opt': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wast-printer': 1.14.1 - - '@webassemblyjs/wasm-gen@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wasm-opt@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - - '@webassemblyjs/wasm-parser@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-api-error': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - - '@webassemblyjs/wast-printer@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@xtuc/long': 4.2.2 - - '@xtuc/ieee754@1.2.0': {} - - '@xtuc/long@4.2.2': {} - - acorn@8.16.0: {} - - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-keywords@5.1.0(ajv@8.20.0): - dependencies: - ajv: 8.20.0 - fast-deep-equal: 3.1.3 - - ajv@8.20.0: + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.2 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} any-promise@1.3.0: {} anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.2 + picomatch: 2.3.1 arg@5.0.2: {} argparse@2.0.1: {} + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + assertion-error@2.0.1: {} - autoprefixer@10.5.0(postcss@8.5.25): + ast-types-flow@0.0.8: {} + + autoprefixer@10.4.21(postcss@8.4.47): dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001793 - fraction.js: 5.3.4 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001705 + fraction.js: 4.3.7 + normalize-range: 0.1.2 picocolors: 1.1.1 - postcss: 8.5.25 + postcss: 8.4.47 postcss-value-parser: 4.2.0 - baseline-browser-mapping@2.10.31: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 - before-after-hook@4.0.0: {} + axe-core@4.10.2: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + before-after-hook@3.0.2: {} binary-extensions@2.3.0: {} + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.28.2: + browserslist@4.24.4: dependencies: - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 - electron-to-chromium: 1.5.361 - node-releases: 2.0.46 - update-browserslist-db: 1.2.3(browserslist@4.28.2) + caniuse-lite: 1.0.30001705 + electron-to-chromium: 1.5.119 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.4) - buffer-from@1.1.2: {} + buffer-from@1.1.2: + optional: true - bun-types@1.3.14: + bun-types@1.2.11: dependencies: - '@types/node': 25.9.1 + '@types/node': 20.14.13 - bun@1.3.14: + bun@1.2.11: optionalDependencies: - '@oven/bun-darwin-aarch64': 1.3.14 - '@oven/bun-darwin-x64': 1.3.14 - '@oven/bun-darwin-x64-baseline': 1.3.14 - '@oven/bun-freebsd-aarch64': 1.3.14 - '@oven/bun-freebsd-x64': 1.3.14 - '@oven/bun-linux-aarch64': 1.3.14 - '@oven/bun-linux-aarch64-android': 1.3.14 - '@oven/bun-linux-aarch64-musl': 1.3.14 - '@oven/bun-linux-x64': 1.3.14 - '@oven/bun-linux-x64-android': 1.3.14 - '@oven/bun-linux-x64-baseline': 1.3.14 - '@oven/bun-linux-x64-musl': 1.3.14 - '@oven/bun-linux-x64-musl-baseline': 1.3.14 - '@oven/bun-windows-aarch64': 1.3.14 - '@oven/bun-windows-x64': 1.3.14 - '@oven/bun-windows-x64-baseline': 1.3.14 + '@oven/bun-darwin-aarch64': 1.2.11 + '@oven/bun-darwin-x64': 1.2.11 + '@oven/bun-darwin-x64-baseline': 1.2.11 + '@oven/bun-linux-aarch64': 1.2.11 + '@oven/bun-linux-aarch64-musl': 1.2.11 + '@oven/bun-linux-x64': 1.2.11 + '@oven/bun-linux-x64-baseline': 1.2.11 + '@oven/bun-linux-x64-musl': 1.2.11 + '@oven/bun-linux-x64-musl-baseline': 1.2.11 + '@oven/bun-windows-x64': 1.2.11 + '@oven/bun-windows-x64-baseline': 1.2.11 - bundle-require@5.1.0(esbuild@0.27.7): + bundle-require@5.1.0(esbuild@0.25.0): dependencies: - esbuild: 0.27.7 + esbuild: 0.25.0 load-tsconfig: 0.2.5 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + cac@6.7.14: {} + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001793: {} + caniuse-lite@1.0.30001705: {} - chai@6.2.2: {} + chai@5.1.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.1 + pathval: 2.0.0 - chardet@2.1.1: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chardet@0.7.0: {} + + check-error@2.1.1: {} chokidar@3.6.0: dependencies: @@ -4885,11 +6438,13 @@ snapshots: chokidar@4.0.3: dependencies: - readdirp: 4.1.2 + readdirp: 4.1.1 - chrome-trace-event@1.0.4: {} + chownr@3.0.0: {} - citty@0.2.2: {} + citty@0.1.6: + dependencies: + consola: 3.2.3 cli-width@4.1.0: {} @@ -4899,124 +6454,669 @@ snapshots: dependencies: typanion: 3.14.0 + clipboardy@4.0.0: + dependencies: + execa: 8.0.1 + is-wsl: 3.1.0 + is64bit: 2.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + colorette@2.0.20: {} - commander@2.20.3: {} + commander@2.20.3: + optional: true commander@4.1.1: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} - consola@3.4.2: {} + consola@3.2.3: {} - content-type@2.0.0: {} + consola@3.4.0: {} convert-source-map@2.0.0: {} - cookie-es@1.2.3: {} + cookie-es@1.2.2: {} - crossws@0.3.5: + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.1: dependencies: uncrypto: 0.1.3 - crossws@0.4.10: {} + crossws@0.3.4: + dependencies: + uncrypto: 0.1.3 cssesc@3.0.0: {} - csstype@3.2.3: {} + csstype@3.1.3: {} - debug@4.4.3: + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@3.2.7: dependencies: ms: 2.1.3 - dedent@1.7.2: {} + debug@4.3.6: + dependencies: + ms: 2.1.2 - defu@6.1.7: {} + debug@4.4.0: + dependencies: + ms: 2.1.3 + + dedent@1.5.3: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + defu@6.1.4: {} destr@2.0.5: {} detect-libc@1.0.3: {} - detect-libc@2.1.2: {} + detect-libc@2.0.4: {} didyoumean@1.2.2: {} dlv@1.1.3: {} - electron-to-chromium@1.5.361: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 - emnapi@1.11.3(node-addon-api@8.7.0): + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.119: {} + + emnapi@1.4.3(node-addon-api@8.3.0): optionalDependencies: - node-addon-api: 8.7.0 + node-addon-api: 8.3.0 - enhanced-resolve@5.24.5: + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.3 + tapable: 2.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 es-errors@1.3.0: {} - es-module-lexer@2.1.0: {} + es-iterator-helpers@1.1.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.3 + safe-array-concat: 1.1.2 - es-toolkit@1.50.0: {} + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 - esbuild@0.27.7: + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + esbuild@0.21.5: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + optional: true + + esbuild@0.24.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 + + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 escalade@3.2.0: {} - eslint-scope@5.1.1: + escape-string-regexp@4.0.0: {} + + eslint-config-next@15.3.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4): + dependencies: + '@next/eslint-plugin-next': 15.3.1 + '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.1(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-react: 7.37.2(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.0.0(eslint@9.25.1(jiti@2.4.2)) + optionalDependencies: + typescript: 5.5.4 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-config-next@15.3.1(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3): + dependencies: + '@next/eslint-plugin-next': 15.3.1 + '@rushstack/eslint-patch': 1.10.4 + '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.1(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-react: 7.37.2(eslint@9.25.1(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.0.0(eslint@9.25.1(jiti@2.4.2)) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.15.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + enhanced-resolve: 5.18.1 + eslint: 9.25.1(jiti@2.4.2) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)) + fast-glob: 3.3.3 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint@9.25.1(jiti@2.4.2)) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + enhanced-resolve: 5.18.1 + eslint: 9.25.1(jiti@2.4.2) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)) + fast-glob: 3.3.3 + get-tsconfig: 4.8.1 + is-bun-module: 1.2.1 + is-glob: 4.0.3 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2)) + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint@9.25.1(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.5.4) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.25.1(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.25.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.25.1(jiti@2.4.2)))(eslint@9.25.1(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.11.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.6.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.1(eslint@9.25.1(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.10.2 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.1.0 + eslint: 9.25.1(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.0.3 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.0.0(eslint@9.25.1(jiti@2.4.2)): + dependencies: + eslint: 9.25.1(jiti@2.4.2) + + eslint-plugin-react@7.37.2(eslint@9.25.1(jiti@2.4.2)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.1.0 + eslint: 9.25.1(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 - estraverse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.25.1(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@9.25.1(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.25.1 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - estraverse@4.3.0: {} - estraverse@5.3.0: {} estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.9 + '@types/estree': 1.0.6 - events@3.3.0: {} + esutils@2.0.3: {} - expect-type@1.3.0: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 - fast-content-type-parse@3.0.0: {} + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-content-type-parse@2.0.1: {} fast-deep-equal@3.1.3: {} - fast-equals@5.4.0: {} + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 fast-glob@3.3.3: dependencies: @@ -5026,45 +7126,50 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-string-truncated-width@3.0.3: {} + fast-json-stable-stringify@2.1.0: {} - fast-string-width@3.0.2: + fast-levenshtein@2.0.6: {} + + fastq@1.17.1: dependencies: - fast-string-truncated-width: 3.0.3 + reusify: 1.0.4 - fast-stringify@4.0.0: {} - - fast-uri@3.1.2: {} - - fast-wrap-ansi@0.2.2: - dependencies: - fast-string-width: 3.0.2 - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.4): + fdir@6.4.3(picomatch@4.0.2): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.2 - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-up-simple@1.0.1: {} + find-up-simple@1.0.0: {} - fix-dts-default-cjs-exports@1.0.1: + find-up@5.0.0: dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.4 + locate-path: 6.0.0 + path-exists: 4.0.0 - fraction.js@5.3.4: {} + flat-cache@4.0.1: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + + flatted@3.3.1: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@4.3.7: {} fsevents@2.3.2: optional: true @@ -5074,7 +7179,40 @@ snapshots: function-bind@1.1.2: {} - get-port-please@3.2.0: {} + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-func-name@2.0.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-port-please@3.1.2: {} + + get-stream@8.0.1: {} + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 glob-parent@5.1.2: dependencies: @@ -5084,203 +7222,440 @@ snapshots: dependencies: is-glob: 4.0.3 - globby@16.2.2: + glob@10.4.5: dependencies: - '@sindresorhus/merge-streams': 4.0.0 + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 fast-glob: 3.3.3 - ignore: 7.0.5 - is-path-inside: 4.0.0 + ignore: 7.0.3 + path-type: 6.0.0 slash: 5.1.0 - unicorn-magic: 0.4.0 + unicorn-magic: 0.3.0 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 graceful-fs@4.2.11: {} - h3@1.15.11: + graphemer@1.4.0: {} + + h3@1.15.3: dependencies: - cookie-es: 1.2.3 - crossws: 0.3.5 - defu: 6.1.7 + cookie-es: 1.2.2 + crossws: 0.3.4 + defu: 6.1.4 destr: 2.0.5 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.4 + node-mock-http: 1.0.0 radix3: 1.1.2 - ufo: 1.6.4 + ufo: 1.6.1 uncrypto: 0.1.3 + has-bigints@1.0.2: {} + has-flag@4.0.0: {} - hasown@2.0.3: + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: dependencies: function-bind: 1.1.2 http-shutdown@1.2.2: {} - iconv-lite@0.7.2: + human-signals@5.0.0: {} + + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - ignore@7.0.5: {} + ignore@5.3.2: {} + + ignore@7.0.3: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 iron-webcrypto@1.2.1: {} + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.3.2: + optional: true + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.2: + is-boolean-object@1.1.2: dependencies: - hasown: 2.0.3 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-bun-module@1.2.1: + dependencies: + semver: 7.7.1 + + is-callable@1.2.7: {} + + is-core-module@2.15.0: + dependencies: + hasown: 2.0.2 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-docker@3.0.0: {} is-extglob@2.1.1: {} + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + is-number@7.0.0: {} - is-path-inside@4.0.0: {} - - jest-worker@27.5.1: + is-regex@1.1.4: dependencies: - '@types/node': 25.9.1 - merge-stream: 2.0.0 - supports-color: 8.1.1 + call-bind: 1.0.7 + has-tostringtag: 1.0.2 - jiti@1.21.7: {} + is-set@2.0.3: {} - jiti@2.7.0: {} + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@3.0.0: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + is64bit@2.0.0: + dependencies: + system-architecture: 0.1.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.3: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.6: {} + + jiti@2.4.2: {} joycon@3.1.1: {} - js-yaml@4.3.1: + js-tokens@4.0.0: {} + + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - json-schema-traverse@1.0.0: {} + jsesc@3.1.0: {} - json-with-bigint@3.5.8: {} + json-buffer@3.0.1: {} - lightningcss-android-arm64@1.33.0: - optional: true + json-schema-traverse@0.4.1: {} - lightningcss-darwin-arm64@1.33.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} - lightningcss-darwin-x64@1.33.0: {} - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: {} - - lightningcss-linux-arm64-musl@1.33.0: {} - - lightningcss-linux-x64-gnu@1.33.0: {} - - lightningcss-linux-x64-musl@1.33.0: {} - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: {} - - lightningcss@1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930): + json5@1.0.2: dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 + minimist: 1.2.8 - lilconfig@3.1.3: {} + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.2.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.29.2: {} + + lightningcss-darwin-x64@1.29.2: {} + + lightningcss-freebsd-x64@1.29.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.2: + optional: true + + lightningcss-linux-arm64-gnu@1.29.2: {} + + lightningcss-linux-arm64-musl@1.29.2: {} + + lightningcss-linux-x64-gnu@1.29.2: {} + + lightningcss-linux-x64-musl@1.29.2: {} + + lightningcss-win32-arm64-msvc@1.29.2: + optional: true + + lightningcss-win32-x64-msvc@1.29.2: {} + + lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm): + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-freebsd-x64: 1.29.2 + lightningcss-linux-arm-gnueabihf: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-arm64-msvc: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} lines-and-columns@1.2.4: {} - listhen@1.10.1(@parcel/watcher@2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da)): + listhen@1.9.0: dependencies: - '@parcel/watcher-wasm': 2.5.6 - citty: 0.2.2 - consola: 3.4.2 - crossws: 0.4.10 - defu: 6.1.7 - get-port-please: 3.2.0 - h3: 1.15.11 + '@parcel/watcher': 2.5.0 + '@parcel/watcher-wasm': 2.5.0 + citty: 0.1.6 + clipboardy: 4.0.0 + consola: 3.2.3 + crossws: 0.3.1 + defu: 6.1.4 + get-port-please: 3.1.2 + h3: 1.15.3 http-shutdown: 1.2.2 - jiti: 2.7.0 - node-forge: 1.4.0 - pathe: 2.0.3 - std-env: 4.2.0 - tinyclip: 0.1.15 - ufo: 1.6.4 - untun: 0.2.2 - uqr: 0.1.3 - optionalDependencies: - '@parcel/watcher': 2.6.0(patch_hash=705ce75ccea54337110c4d7fe0a8b421658ea0c23e1a77c1fa890a86041179da) - transitivePeerDependencies: - - srvx + jiti: 2.4.2 + mlly: 1.7.3 + node-forge: 1.3.1 + pathe: 1.1.2 + std-env: 3.7.0 + ufo: 1.5.4 + untun: 0.1.3 + uqr: 0.1.2 load-tsconfig@0.2.5: {} - magic-string@0.30.21: + locate-path@6.0.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + p-locate: 5.0.0 - magic-string@1.1.0: + lodash-es@4.17.21: {} + + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.merge@4.6.2: {} + + lodash.sortby@4.7.0: {} + + loose-envify@1.4.0: dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + js-tokens: 4.0.0 + + loupe@3.1.1: + dependencies: + get-func-name: 2.0.2 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 merge-stream@2.0.0: {} merge2@1.4.1: {} - micro-memoize@5.1.1: + micro-memoize@4.1.3: {} + + micromatch@4.0.7: dependencies: - fast-equals: 5.4.0 - fast-stringify: 4.0.0 + braces: 3.0.3 + picomatch: 2.3.1 micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.2 + picomatch: 2.3.1 - mime-db@1.54.0: {} + mimic-fn@4.0.0: {} mini-svg-data-uri@1.4.4: {} - minimizer-webpack-plugin@5.6.1(esbuild@0.27.7)(postcss@8.5.25)(webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25)): + minimatch@3.1.2: dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.48.0 - webpack: 5.109.2(esbuild@0.27.7)(postcss@8.5.25) - optionalDependencies: - esbuild: 0.27.7 - postcss: 8.5.25 + brace-expansion: 1.1.11 - mlly@1.8.2: + minimatch@9.0.5: dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.4 + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + mlly@1.7.3: + dependencies: + acorn: 8.14.0 + pathe: 1.1.2 + pkg-types: 1.3.0 + ufo: 1.5.4 mri@1.2.0: {} + ms@2.1.2: {} + ms@2.1.3: {} - mute-stream@3.0.0: {} + mute-stream@2.0.0: {} mz@2.7.0: dependencies: @@ -5288,131 +7663,225 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.12: {} + nanoid@3.3.7: {} - nanoid@3.3.16: {} + nanoid@3.3.8: {} - neo-async@2.6.2: {} + natural-compare@1.4.0: {} - next@16.2.7(@playwright/test@1.62.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + next@15.3.1(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 16.2.7 + '@next/env': 15.3.1 + '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.31 - caniuse-lite: 1.0.30001793 + busboy: 1.6.0 + caniuse-lite: 1.0.30001705 postcss: 8.4.31 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - styled-jsx: 5.1.6(react@19.2.7) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.7 - '@next/swc-darwin-x64': 16.2.7 - '@next/swc-linux-arm64-gnu': 16.2.7 - '@next/swc-linux-arm64-musl': 16.2.7 - '@next/swc-linux-x64-gnu': 16.2.7 - '@next/swc-linux-x64-musl': 16.2.7 - '@next/swc-win32-arm64-msvc': 16.2.7 - '@next/swc-win32-x64-msvc': 16.2.7 - '@playwright/test': 1.62.1 - sharp: 0.34.5 + '@next/swc-darwin-arm64': 15.3.1 + '@next/swc-darwin-x64': 15.3.1 + '@next/swc-linux-arm64-gnu': 15.3.1 + '@next/swc-linux-arm64-musl': 15.3.1 + '@next/swc-linux-x64-gnu': 15.3.1 + '@next/swc-linux-x64-musl': 15.3.1 + '@next/swc-win32-arm64-msvc': 15.3.1 + '@next/swc-win32-x64-msvc': 15.3.1 + '@playwright/test': 1.52.0 + sharp: 0.34.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros node-addon-api@7.1.1: {} - node-addon-api@8.7.0: {} + node-addon-api@8.2.2: {} - node-forge@1.4.0: {} + node-addon-api@8.3.0: {} + + node-forge@1.3.1: {} + + node-gyp-build@4.8.2: {} node-gyp-build@4.8.4: {} - node-mock-http@1.0.4: {} + node-mock-http@1.0.0: {} - node-releases@2.0.46: {} + node-releases@2.0.19: {} normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + object-assign@4.1.1: {} object-hash@3.0.0: {} - obug@2.1.1: {} + object-inspect@1.13.2: {} - obug@2.1.4: {} + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-tmpdir@1.0.2: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} package-up@5.0.0: dependencies: - find-up-simple: 1.0.1 + find-up-simple: 1.0.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} path-parse@1.0.7: {} - pathe@2.0.3: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@6.0.0: {} + + pathe@1.1.2: {} + + pathval@2.0.0: {} picocolors@1.1.1: {} - picomatch@2.3.2: {} + picomatch@2.3.1: {} - picomatch@4.0.4: {} - - picomatch@4.0.5: {} + picomatch@4.0.2: {} pify@2.3.0: {} - pirates@4.0.7: {} + pirates@4.0.6: {} - pkg-types@1.3.1: + pkg-types@1.3.0: dependencies: confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 + mlly: 1.7.3 + pathe: 1.1.2 - playwright-core@1.62.1: {} + playwright-core@1.52.0: {} - playwright@1.62.1: + playwright@1.52.0: dependencies: - playwright-core: 1.62.1 + playwright-core: 1.52.0 optionalDependencies: fsevents: 2.3.2 - postcss-import@15.1.0(postcss@8.5.16): + possible-typed-array-names@1.0.0: {} + + postcss-import@15.1.0(postcss@8.4.47): dependencies: - postcss: 8.5.16 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.12 + resolve: 1.22.8 - postcss-import@16.1.1(postcss@8.5.25): + postcss-import@16.1.0(postcss@8.4.41): dependencies: - postcss: 8.5.25 + postcss: 8.4.41 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.12 + resolve: 1.22.8 - postcss-js@4.1.0(postcss@8.5.16): + postcss-import@16.1.0(postcss@8.5.3): + dependencies: + postcss: 8.5.3 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.8 + + postcss-js@4.0.1(postcss@8.4.47): dependencies: camelcase-css: 2.0.1 - postcss: 8.5.16 + postcss: 8.4.47 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.16)(yaml@2.9.0): + postcss-load-config@4.0.2(postcss@8.4.47): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.2 + yaml: 2.6.0 optionalDependencies: - jiti: 1.21.7 - postcss: 8.5.16 - yaml: 2.9.0 + postcss: 8.4.47 - postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.25)(yaml@2.9.0): + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(yaml@2.6.0): dependencies: - lilconfig: 3.1.3 + lilconfig: 3.1.2 optionalDependencies: - jiti: 2.7.0 - postcss: 8.5.25 - yaml: 2.9.0 + jiti: 2.4.2 + postcss: 8.5.3 + tsx: 4.19.1 + yaml: 2.6.0 - postcss-nested@6.2.0(postcss@8.5.16): + postcss-nested@6.2.0(postcss@8.4.47): dependencies: - postcss: 8.5.16 + postcss: 8.4.47 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.0.10: @@ -5425,7 +7894,7 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-selector-parser@7.1.4: + postcss-selector-parser@7.1.0: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 @@ -5434,57 +7903,70 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.16: + postcss@8.4.41: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.7 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.25: + postcss@8.4.47: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.7 picocolors: 1.1.1 source-map-js: 1.2.1 - prettier-plugin-embed@0.5.1: + postcss@8.5.3: dependencies: - '@types/estree': 1.0.9 - dedent: 1.7.2 - micro-memoize: 5.1.1 + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-plugin-embed@0.5.0: + dependencies: + '@types/estree': 1.0.6 + dedent: 1.5.3 + micro-memoize: 4.1.3 package-up: 5.0.0 tiny-jsonc: 1.0.2 - type-fest: 5.6.0 + type-fest: 4.36.0 transitivePeerDependencies: - babel-plugin-macros - prettier-plugin-organize-imports@4.3.0(prettier@3.9.6)(typescript@5.9.3): + prettier-plugin-organize-imports@4.0.0(prettier@3.5.0)(typescript@5.5.4): dependencies: - prettier: 3.9.6 - typescript: 5.9.3 + prettier: 3.5.0 + typescript: 5.5.4 - prettier@3.9.6: {} + prettier@3.5.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} queue-microtask@1.2.3: {} radix3@1.1.2: {} - react-dom@19.2.6(react@19.2.6): + react-dom@19.1.0(react@19.1.0): dependencies: - react: 19.2.6 - scheduler: 0.27.0 + react: 19.1.0 + scheduler: 0.26.0 - react-dom@19.2.7(react@19.2.7): - dependencies: - react: 19.2.7 - scheduler: 0.27.0 + react-is@16.13.1: {} - react@19.2.6: {} + react-refresh@0.17.0: {} - react@19.2.7: {} + react@19.1.0: {} read-cache@1.0.0: dependencies: @@ -5492,128 +7974,163 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.2 + picomatch: 2.3.1 - readdirp@4.1.2: {} + readdirp@4.1.1: {} - require-from-string@2.0.2: {} + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + globalthis: 1.0.4 + which-builtin-type: 1.1.4 + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} resolve-from@5.0.0: {} - resolve@1.22.12: + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.8: dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 + is-core-module: 2.15.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - reusify@1.1.0: {} - - rolldown@1.2.1: + resolve@2.0.0-next.5: dependencies: - '@oxc-project/types': 0.142.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.1 - '@rolldown/binding-darwin-arm64': 1.2.1 - '@rolldown/binding-darwin-x64': 1.2.1 - '@rolldown/binding-freebsd-x64': 1.2.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.1 - '@rolldown/binding-linux-arm64-gnu': 1.2.1 - '@rolldown/binding-linux-arm64-musl': 1.2.1 - '@rolldown/binding-linux-ppc64-gnu': 1.2.1 - '@rolldown/binding-linux-s390x-gnu': 1.2.1 - '@rolldown/binding-linux-x64-gnu': 1.2.1 - '@rolldown/binding-linux-x64-musl': 1.2.1 - '@rolldown/binding-openharmony-arm64': 1.2.1 - '@rolldown/binding-wasm32-wasi': 1.2.1 - '@rolldown/binding-win32-arm64-msvc': 1.2.1 - '@rolldown/binding-win32-x64-msvc': 1.2.1 + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 - rollup@4.60.4: + reusify@1.0.4: {} + + rollup@4.34.9: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.4 - '@rollup/rollup-android-arm64': 4.60.4 - '@rollup/rollup-darwin-arm64': 4.60.4 - '@rollup/rollup-darwin-x64': 4.60.4 - '@rollup/rollup-freebsd-arm64': 4.60.4 - '@rollup/rollup-freebsd-x64': 4.60.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 - '@rollup/rollup-linux-arm-musleabihf': 4.60.4 - '@rollup/rollup-linux-arm64-gnu': 4.60.4 - '@rollup/rollup-linux-arm64-musl': 4.60.4 - '@rollup/rollup-linux-loong64-gnu': 4.60.4 - '@rollup/rollup-linux-loong64-musl': 4.60.4 - '@rollup/rollup-linux-ppc64-gnu': 4.60.4 - '@rollup/rollup-linux-ppc64-musl': 4.60.4 - '@rollup/rollup-linux-riscv64-gnu': 4.60.4 - '@rollup/rollup-linux-riscv64-musl': 4.60.4 - '@rollup/rollup-linux-s390x-gnu': 4.60.4 - '@rollup/rollup-linux-x64-gnu': 4.60.4 - '@rollup/rollup-linux-x64-musl': 4.60.4 - '@rollup/rollup-openbsd-x64': 4.60.4 - '@rollup/rollup-openharmony-arm64': 4.60.4 - '@rollup/rollup-win32-arm64-msvc': 4.60.4 - '@rollup/rollup-win32-ia32-msvc': 4.60.4 - '@rollup/rollup-win32-x64-gnu': 4.60.4 - '@rollup/rollup-win32-x64-msvc': 4.60.4 + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + safer-buffer@2.1.2: {} - scheduler@0.27.0: {} + scheduler@0.26.0: {} - schema-utils@4.3.3: + semver@6.3.1: {} + + semver@7.7.1: {} + + set-function-length@1.2.2: dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 - semver@7.8.5: {} - - sharp@0.34.5: + set-function-name@2.0.2: dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + sharp@0.34.1: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.1 optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + slash@5.1.0: {} source-map-js@1.2.1: {} @@ -5622,41 +8139,116 @@ snapshots: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + optional: true - source-map@0.6.1: {} + source-map@0.6.1: + optional: true - source-map@0.7.6: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 stackback@0.0.2: {} - std-env@4.1.0: {} + std-env@3.7.0: {} - std-env@4.2.0: {} + streamsearch@1.1.0: {} - styled-jsx@5.1.6(react@19.2.7): + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@3.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 - react: 19.2.7 + react: 19.1.0 - sucrase@3.35.1: + sucrase@3.35.0: dependencies: - '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 + pirates: 4.0.6 ts-interface-checker: 0.1.13 - supports-color@8.1.1: + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} - tagged-tag@1.0.0: {} + system-architecture@0.1.0: {} - tailwindcss@3.4.19(yaml@2.9.0): + tailwindcss@3.4.14: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -5666,32 +8258,41 @@ snapshots: fast-glob: 3.3.3 glob-parent: 6.0.2 is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 + jiti: 1.21.6 + lilconfig: 2.1.0 + micromatch: 4.0.7 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.5.16 - postcss-import: 15.1.0(postcss@8.5.16) - postcss-js: 4.1.0(postcss@8.5.16) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.16)(yaml@2.9.0) - postcss-nested: 6.2.0(postcss@8.5.16) + postcss: 8.4.47 + postcss-import: 15.1.0(postcss@8.4.47) + postcss-js: 4.0.1(postcss@8.4.47) + postcss-load-config: 4.0.2(postcss@8.4.47) + postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 - resolve: 1.22.12 - sucrase: 3.35.1 + resolve: 1.22.8 + sucrase: 3.35.0 transitivePeerDependencies: - - tsx - - yaml + - ts-node - tapable@2.3.3: {} + tapable@2.2.1: {} - terser@5.48.0: + tar@7.4.3: dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + terser@5.31.6: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 + optional: true thenify-all@1.6.0: dependencies: @@ -5705,227 +8306,380 @@ snapshots: tinybench@2.9.0: {} - tinyclip@0.1.15: {} - tinyexec@0.3.2: {} - tinyexec@1.1.2: {} - - tinyglobby@0.2.16: + tinyglobby@0.2.12: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 - tinyglobby@0.2.17: + tinypool@1.0.0: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.0: {} + + tmp@0.0.33: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - - tinyrainbow@3.1.0: {} + os-tmpdir: 1.0.2 to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toml@3.0.0: {} + + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} - tree-sitter-javascript@0.23.1(tree-sitter@0.25.1): + tree-sitter-javascript@0.23.1(tree-sitter@0.22.4): dependencies: - node-addon-api: 8.7.0 - node-gyp-build: 4.8.4 + node-addon-api: 8.2.2 + node-gyp-build: 4.8.2 optionalDependencies: - tree-sitter: 0.25.1 + tree-sitter: 0.22.4 - tree-sitter-typescript@0.23.2(tree-sitter@0.25.1): + tree-sitter-typescript@0.23.2(tree-sitter@0.22.4): dependencies: - node-addon-api: 8.7.0 - node-gyp-build: 4.8.4 - tree-sitter-javascript: 0.23.1(tree-sitter@0.25.1) + node-addon-api: 8.2.2 + node-gyp-build: 4.8.2 + tree-sitter-javascript: 0.23.1(tree-sitter@0.22.4) optionalDependencies: - tree-sitter: 0.25.1 + tree-sitter: 0.22.4 - tree-sitter@0.25.1: + tree-sitter@0.22.4: dependencies: - node-addon-api: 8.7.0 + node-addon-api: 8.3.0 node-gyp-build: 4.8.4 + ts-api-utils@1.3.0(typescript@5.5.4): + dependencies: + typescript: 5.5.4 + + ts-api-utils@1.3.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + ts-interface-checker@0.1.13: {} - tslib@2.8.1: {} - - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.25)(typescript@5.9.3)(yaml@2.9.0): + tsconfig-paths@3.15.0: dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.0: {} + + tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(typescript@5.5.4)(yaml@2.6.0): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.0) cac: 6.7.14 chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 + consola: 3.4.0 + debug: 4.4.0 + esbuild: 0.25.0 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.25)(yaml@2.9.0) + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.1)(yaml@2.6.0) resolve-from: 5.0.0 - rollup: 4.60.4 - source-map: 0.7.6 - sucrase: 3.35.1 + rollup: 4.34.9 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 tinyexec: 0.3.2 - tinyglobby: 0.2.16 + tinyglobby: 0.2.12 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.25 - typescript: 5.9.3 + postcss: 8.5.3 + typescript: 5.5.4 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - turbo@2.10.8: + tsx@4.19.1: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 optionalDependencies: - '@turbo/darwin-64': 2.10.8 - '@turbo/darwin-arm64': 2.10.8 - '@turbo/linux-64': 2.10.8 - '@turbo/linux-arm64': 2.10.8 - '@turbo/windows-64': 2.10.8 - '@turbo/windows-arm64': 2.10.8 + fsevents: 2.3.3 + optional: true + + turbo-darwin-64@2.5.2: + optional: true + + turbo-darwin-arm64@2.5.2: + optional: true + + turbo-linux-64@2.5.2: + optional: true + + turbo-linux-arm64@2.5.2: + optional: true + + turbo-windows-64@2.5.2: + optional: true + + turbo-windows-arm64@2.5.2: + optional: true + + turbo@2.5.2: + optionalDependencies: + turbo-darwin-64: 2.5.2 + turbo-darwin-arm64: 2.5.2 + turbo-linux-64: 2.5.2 + turbo-linux-arm64: 2.5.2 + turbo-windows-64: 2.5.2 + turbo-windows-arm64: 2.5.2 typanion@3.14.0: {} - type-fest@5.6.0: + type-check@0.4.0: dependencies: - tagged-tag: 1.0.0 + prelude-ls: 1.2.1 - typescript@5.9.3: {} + type-fest@0.21.3: {} - typescript@6.0.3: {} + type-fest@4.36.0: {} - ufo@1.6.4: {} + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@5.5.4: {} + + typescript@5.6.3: {} + + ufo@1.5.4: {} + + ufo@1.6.1: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 uncrypto@0.1.3: {} - undici-types@6.21.0: {} + undici-types@5.26.5: {} - undici-types@7.24.6: {} + unicorn-magic@0.3.0: {} - unicorn-magic@0.4.0: {} + universal-user-agent@7.0.2: {} - universal-user-agent@7.0.3: {} - - untun@0.2.2: {} - - update-browserslist-db@1.2.3(browserslist@4.28.2): + untun@0.1.3: dependencies: - browserslist: 4.28.2 + citty: 0.1.6 + consola: 3.2.3 + pathe: 1.1.2 + + update-browserslist-db@1.1.1(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 escalade: 3.2.0 picocolors: 1.1.1 - uqr@0.1.3: {} + uqr@0.1.2: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 util-deprecate@1.0.2: {} - vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0): + vite-node@2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): dependencies: - lightningcss: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 22.20.1 - esbuild: 0.27.7 - fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.48.0 - yaml: 2.9.0 + cac: 6.7.14 + debug: 4.4.0 + pathe: 1.1.2 + tinyrainbow: 1.2.0 + vite: 5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser - vite@8.2.0(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0): + vite@5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): dependencies: - lightningcss: 1.33.0(patch_hash=1d4a8800d60d13d42887b88b3a86576df4b451670308145fb432ec8abbf40930) - picomatch: 4.0.5 - postcss: 8.5.25 - rolldown: 1.2.1 - tinyglobby: 0.2.17 + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.34.9 optionalDependencies: - '@types/node': 25.9.1 - esbuild: 0.27.7 + '@types/node': 20.14.13 fsevents: 2.3.3 - jiti: 2.7.0 - terser: 5.48.0 - yaml: 2.9.0 + lightningcss: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + terser: 5.31.6 - vitest@4.1.10(@types/node@22.20.1)(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + vite@6.0.0(@types/node@20.14.13)(jiti@2.4.2)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6)(tsx@4.19.1)(yaml@2.6.0): dependencies: - '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) - '@vitest/pretty-format': 4.1.10 - '@vitest/runner': 4.1.10 - '@vitest/snapshot': 4.1.10 - '@vitest/spy': 4.1.10 - '@vitest/utils': 4.1.10 - es-module-lexer: 2.1.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 + esbuild: 0.24.0 + postcss: 8.5.3 + rollup: 4.34.9 + optionalDependencies: + '@types/node': 20.14.13 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm) + terser: 5.31.6 + tsx: 4.19.1 + yaml: 2.6.0 + + vitest@2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6): + dependencies: + '@ampproject/remapping': 2.3.0 + '@vitest/expect': 2.0.5 + '@vitest/pretty-format': 2.0.5 + '@vitest/runner': 2.0.5 + '@vitest/snapshot': 2.0.5 + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.1.1 + debug: 4.3.6 + execa: 8.0.1 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.7.0 tinybench: 2.9.0 - tinyexec: 1.1.2 - tinyglobby: 0.2.17 - tinyrainbow: 3.1.0 - vite: 8.2.0(@types/node@22.20.1)(esbuild@0.27.7)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) + tinypool: 1.0.0 + tinyrainbow: 1.2.0 + vite: 5.4.0(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) + vite-node: 2.0.5(@types/node@20.14.13)(lightningcss@1.29.2(patch_hash=tzyxy3asfxcqc7ihrooumyi5fm))(terser@5.31.6) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 20.14.13 transitivePeerDependencies: - - msw - - watchpack@2.5.2: - dependencies: - graceful-fs: 4.2.11 - - webpack-sources@3.5.1: {} - - webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25): - dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.16.0 - browserslist: 4.28.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.24.5 - es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 - events: 3.3.0 - graceful-fs: 4.2.11 - mime-db: 1.54.0 - minimizer-webpack-plugin: 5.6.1(esbuild@0.27.7)(postcss@8.5.25)(webpack@5.109.2(esbuild@0.27.7)(postcss@8.5.25)) - neo-async: 2.6.2 - schema-utils: 4.3.3 - tapable: 2.3.3 - watchpack: 2.5.2 - webpack-sources: 3.5.1 - transitivePeerDependencies: - - '@minify-html/node' - - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - - esbuild - - html-minifier-terser + - less - lightningcss - - postcss - - uglify-js + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + wasm-sjlj@1.0.6: {} + + webidl-conversions@4.0.2: {} + + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.4: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - yaml@2.9.0: {} + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + yaml@2.6.0: {} + + yocto-queue@0.1.0: {} + + yoctocolors-cjs@2.1.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bcddef126..98329b69e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,50 +5,15 @@ packages: - 'playgrounds/*' - 'integrations' -patchedDependencies: - '@napi-rs/cli@3.7.4': patches/@napi-rs__cli@3.7.4.patch - '@parcel/watcher@2.6.0': patches/@parcel__watcher@2.6.0.patch - lightningcss@1.33.0: patches/lightningcss@1.33.0.patch - catalog: - '@types/node': 22.20.1 - dedent: 1.7.2 - enhanced-resolve: ^5.24.5 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - lightningcss: 1.33.0 - '@parcel/watcher-darwin-arm64': 2.6.0 - '@parcel/watcher-darwin-x64': 2.6.0 - '@parcel/watcher-linux-arm64-glibc': 2.6.0 - '@parcel/watcher-linux-arm64-musl': 2.6.0 - '@parcel/watcher-linux-x64-glibc': 2.6.0 - '@parcel/watcher-linux-x64-musl': 2.6.0 - '@parcel/watcher-win32-x64': 2.6.0 - '@parcel/watcher': 2.6.0 - postcss: ^8.5.25 - prettier: 3.9.6 - vite: ^8.2.0 - webpack: ^5.109.2 - -onlyBuiltDependencies: - - '@parcel/watcher' - - bun - - esbuild - - sharp - - tree-sitter - - tree-sitter-javascript - - tree-sitter-typescript - -allowBuilds: - '@parcel/watcher': true - bun: true - esbuild: true - sharp: true - tree-sitter: true - tree-sitter-javascript: true - tree-sitter-typescript: true + '@types/node': ^20.14.8 + prettier: 3.5.0 + vite: ^6.0.0 + lightningcss: 1.29.2 + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f92020c65..aaba1588e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.95.0" +channel = "1.85.0" profile = "default" diff --git a/scripts/pack-packages.mjs b/scripts/pack-packages.mjs index 2b0b9f15d..7bec3eda4 100644 --- a/scripts/pack-packages.mjs +++ b/scripts/pack-packages.mjs @@ -21,25 +21,18 @@ let workspaces = new Map() for (let path of paths) { let pkg = await fs.readFile(path, 'utf8').then(JSON.parse) if (pkg.private) continue - workspaces.set(pkg.name, { - version: pkg.version ?? '', - dir: dirname(path), - hasBundledDependencies: pkg.bundledDependencies?.length > 0 ?? false, - }) + workspaces.set(pkg.name, { version: pkg.version ?? '', dir: dirname(path) }) } // Clean dist folder await fs.rm(path.join(root, 'dist'), { recursive: true, force: true }) Promise.all( - [...workspaces.entries()].map(async ([name, { dir, hasBundledDependencies }]) => { + [...workspaces.entries()].map(async ([name, { version, dir }]) => { function pack() { return new Promise((resolve) => { - // `bundledDependencies` can only be packed with the hoisted node linker - // since pnpm v10. We opt into it for those packages only, instead of - // changing the node linker for the entire workspace. exec( - `pnpm pack --pack-gzip-level=0 --pack-destination="${path.join(root, 'dist').replace(/\\/g, '\\\\')}"${hasBundledDependencies ? ' --config.node-linker=hoisted' : ''}`, + `pnpm pack --pack-gzip-level=0 --pack-destination="${path.join(root, 'dist').replace(/\\/g, '\\\\')}"`, { cwd: dir }, (err, stdout, stderr) => { if (err) { diff --git a/turbo.json b/turbo.json index c48410b29..87eba2732 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,5 @@ { - "$schema": "https://v2-10-8.turborepo.dev/schema.json", + "$schema": "https://turbo.build/schema.json", "ui": "tui", "tasks": { "@tailwindcss/oxide#build": { diff --git a/vitest.config.mts b/vitest.config.mts deleted file mode 100644 index 3ef999be2..000000000 --- a/vitest.config.mts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - projects: ['./packages/*', '!./packages/tsconfig.base.json'], - exclude: ['**/*.spec.?(c|m)[jt]s?(x)', 'integrations/**/*'], - }, -}) diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 000000000..4ccde0667 --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1 @@ +export default ['packages/*']