diff --git a/CHANGELOG.md b/CHANGELOG.md index d17e911a8..759bf95be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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)) ## [4.3.0] - 2026-05-08 diff --git a/integrations/cli/standalone.test.ts b/integrations/cli/standalone.test.ts index b378535c0..f84bf4511 100644 --- a/integrations/cli/standalone.test.ts +++ b/integrations/cli/standalone.test.ts @@ -1,6 +1,7 @@ +import nodeFs from 'node:fs/promises' import os from 'node:os' import path from 'node:path' -import { candidate, css, html, js, json, test, ts } from '../utils' +import { candidate, css, html, IS_WINDOWS, js, json, test, ts } from '../utils' const STANDALONE_BINARY = (() => { switch (os.platform()) { @@ -15,6 +16,41 @@ const STANDALONE_BINARY = (() => { } })() +test( + 'does not scan itself for candidates', + { + fs: { + 'package.json': json` + { + "dependencies": {} + } + `, + 'src/index.css': css` @import 'tailwindcss'; `, + }, + }, + async ({ root, fs, exec }) => { + let sourceBinary = path.resolve( + __dirname, + `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`, + ) + let binary = IS_WINDOWS ? 'tailwindcss.exe' : 'tailwindcss' + let localBinary = path.join(root, binary) + await nodeFs.copyFile(sourceBinary, localBinary) + + if (!IS_WINDOWS) { + await nodeFs.chmod(localBinary, 0o755) + } + + await exec(`${IS_WINDOWS ? binary : `./${binary}`} --input src/index.css --output dist/out.css`) + + await fs.expectFileNotToContain('dist/out.css', [ + candidate`flex`, + candidate`grid`, + candidate`underline`, + ]) + }, +) + test( 'includes first-party plugins', { diff --git a/packages/@tailwindcss-cli/src/commands/build/index.ts b/packages/@tailwindcss-cli/src/commands/build/index.ts index ef5ac20ac..913657633 100644 --- a/packages/@tailwindcss-cli/src/commands/build/index.ts +++ b/packages/@tailwindcss-cli/src/commands/build/index.ts @@ -250,6 +250,16 @@ export async function handle(args: Result>) { return [{ ...compiler.root, negated: false }] })().concat(compiler.sources) + // Do not scan the current executable. Otherwise when using the standalone + // CLI, if the CLI lives in the current repo we would be scanning that file. + // + // This is also immune against renames of the executable file. + sources.push({ + base: path.dirname(process.execPath), + pattern: path.basename(process.execPath), + negated: true, + }) + let scanner = new Scanner({ sources }) DEBUG && I.end('Setup compiler')