Ignore the standalone CLI (#20139)

This PR fixes an issue where if you use the standalone CLI, and you move
the standalone CLI into the current project, then we would scan that
standalone CLI as-if it contains Tailwind CSS classes. Since the CLI
contains actual Tailwind CSS classes, and is in fact readable text, this
binary would've been used as a source.

There are a few ways of fixing this, we could hardcode all the known
names, but that would result in an issue if you rename the CLI. We could
check whether it's a binary format and look for magic numbers at the
top. We could also check for a shebang at the top of the file and skip
it that way.

While some of these solutions might still be useful for the future. For
now I fixed it by essentially always ignoring `process.execPath`. That
way we never ever scan the actual executable regardless of whether you
renamed it or not.

Fixes: #20134

## Test plan

- Added an integration tests
- Works on every OS [ci-all]
This commit is contained in:
Robin Malfait 2026-06-02 12:49:53 +02:00 • committed by GitHub
parent 44818a6cf6
commit 0c5f8bfd88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 1 deletions

View file

@ -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

View file

@ -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',
{

View file

@ -250,6 +250,16 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
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')