Fall back to the plugin base when PostCSS has no from option#19980
Conversation
When PostCSS invokes the plugin without `result.opts.from` (some bundlers,
including Turbopack, do this for certain CSS inputs), `inputFile` defaults
to `''`, and `path.dirname(path.resolve(''))` resolves to the *parent* of
`process.cwd()` rather than CWD. That made `@import 'tailwindcss'` walk
above the project root and fail with "Can't resolve 'tailwindcss' in
'<parent of CWD>'".
The plugin already computes `base = opts.base ?? process.cwd()`. Use it as
the fallback so the project root is searched when `from` is missing.
WalkthroughThe PostCSS plugin now falls back to the configured 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/@tailwindcss-postcss/src/index.test.ts (1)
143-157: Optional: strengthen the regression assertion.
expect(result.css.length).toBeGreaterThan(0)would also pass if the plugin silently emitted, say, a comment or an unprocessed@importline. Asserting that a known utility from theexample-projectfixture appears (e.g..underline, which is referenced in the fixture'sindex.html) more directly proves that@import 'tailwindcss'resolved against the configuredbaseand that scanning ran. It also guards against future regressions where the import silently no-ops.♻️ Suggested assertion
- let result = await processor.process(`@import 'tailwindcss'`) - - expect(result.css.length).toBeGreaterThan(0) + let result = await processor.process(`@import 'tailwindcss'`) + + expect(result.css.length).toBeGreaterThan(0) + expect(result.css).toContain('.underline')Also, organizationally this case fits naturally inside the existing
describe('processing without specifying a base path', ...)block (or a sibling describe), but that's purely cosmetic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@tailwindcss-postcss/src/index.test.ts around lines 143 - 157, Update the test "processing input without a `from` option falls back to the plugin `base`" to assert a concrete utility from the example-project fixture rather than only checking result length: replace the loose assertion expect(result.css.length).toBeGreaterThan(0) with a check that the generated CSS contains a known class (for example assert result.css includes ".underline" or another utility present in the fixtures) so the test verifies that the import resolved and scanning ran; optionally move this test into the existing describe('processing without specifying a base path', ...) block for organization.packages/@tailwindcss-postcss/src/index.ts (1)
185-206: Optional: avoid forcing a full rebuild on every no-frominvocation.When
inputFile === '', line 189 still pushes the empty string intofiles. Then at line 192,fs.statSync('', { throwIfNoEntry: false })yieldsnull, thefile === inputFilebranch matches, andrebuildStrategyis forced to'full'on every subsequent invocation — defeating the mtime-based cache for exactly the Turbopack/no-frompath this PR enables. Pre-existing, but now reachable in a supported flow.♻️ Proposed tweak
- files.push(inputFile) + if (inputFile) files.push(inputFile)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@tailwindcss-postcss/src/index.ts around lines 185 - 206, The code currently pushes inputFile (which can be the empty string for no-`from` invocations) into files and then treats a stat failure of that empty path as forcing rebuildStrategy = 'full'; update the logic in the block that builds "files" (and/or just before iterating) to exclude empty/falsey filenames so that inputFile === '' is not pushed or processed: either only push inputFile when it is a non-empty string, or filter files to remove '' before the fs.statSync loop, leaving the existing file === inputFile branch unchanged for real files; reference symbols: files, inputFile, result.messages, context.mtimes, rebuildStrategy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/`@tailwindcss-postcss/src/index.test.ts:
- Around line 143-157: Update the test "processing input without a `from` option
falls back to the plugin `base`" to assert a concrete utility from the
example-project fixture rather than only checking result length: replace the
loose assertion expect(result.css.length).toBeGreaterThan(0) with a check that
the generated CSS contains a known class (for example assert result.css includes
".underline" or another utility present in the fixtures) so the test verifies
that the import resolved and scanning ran; optionally move this test into the
existing describe('processing without specifying a base path', ...) block for
organization.
In `@packages/`@tailwindcss-postcss/src/index.ts:
- Around line 185-206: The code currently pushes inputFile (which can be the
empty string for no-`from` invocations) into files and then treats a stat
failure of that empty path as forcing rebuildStrategy = 'full'; update the logic
in the block that builds "files" (and/or just before iterating) to exclude
empty/falsey filenames so that inputFile === '' is not pushed or processed:
either only push inputFile when it is a non-empty string, or filter files to
remove '' before the fs.statSync loop, leaving the existing file === inputFile
branch unchanged for real files; reference symbols: files, inputFile,
result.messages, context.mtimes, rebuildStrategy.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 2c32dbe5-6989-47a4-bd56-a1acedd1cd4f
📒 Files selected for processing (2)
packages/@tailwindcss-postcss/src/index.test.tspackages/@tailwindcss-postcss/src/index.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/@tailwindcss-postcss/src/index.test.ts (1)
143-151: Consider strengthening the assertion to lock in the actual fix.
expect(result.css.length).toBeGreaterThan(0)is a fairly weak guard for this regression: it would still pass if the plugin silently emitted partial output without resolvingtailwindcss. Asserting that the@importwas actually replaced (e.g. contains a known token from the generated reset/preflight or no longer contains the raw@import 'tailwindcss') more directly verifies thatinputBasePathcorrectly fell back tobaseand the resolver foundtailwindcss.♻️ Example: tighten the assertion
let result = await processor.process(`@import 'tailwindcss'`) - expect(result.css.length).toBeGreaterThan(0) + expect(result.css.length).toBeGreaterThan(0) + // Ensure the import was actually resolved (not left as-is) using `base` as fallback. + expect(result.css).not.toContain(`@import 'tailwindcss'`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@tailwindcss-postcss/src/index.test.ts around lines 143 - 151, The current assertion only checks result.css.length > 0 which is weak; update the test "fallback to `base` directory when `result.opts.from` is not provided" to assert that the import was actually resolved by checking result.css no longer contains the literal "@import 'tailwindcss'" (or that it contains a known token from Tailwind’s generated output such as the preflight reset like "*, ::before, ::after" or "preflight" content); modify the assertion on the result produced by processor.process(...) (variables: processor, result, tailwindcss) to use a more specific expect (e.g., expect(result.css).not.toContain("@import 'tailwindcss'") or expect(result.css).toContain("*, ::before, ::after")) so the test verifies the resolver fell back to the provided base directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/`@tailwindcss-postcss/src/index.test.ts:
- Around line 143-151: The current assertion only checks result.css.length > 0
which is weak; update the test "fallback to `base` directory when
`result.opts.from` is not provided" to assert that the import was actually
resolved by checking result.css no longer contains the literal "@import
'tailwindcss'" (or that it contains a known token from Tailwind’s generated
output such as the preflight reset like "*, ::before, ::after" or "preflight"
content); modify the assertion on the result produced by processor.process(...)
(variables: processor, result, tailwindcss) to use a more specific expect (e.g.,
expect(result.css).not.toContain("@import 'tailwindcss'") or
expect(result.css).toContain("*, ::before, ::after")) so the test verifies the
resolver fell back to the provided base directory.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d1fee6f7-0f9a-45b9-ba68-a4da39211cef
📒 Files selected for processing (3)
CHANGELOG.mdpackages/@tailwindcss-postcss/src/index.test.tspackages/@tailwindcss-postcss/src/index.ts
✅ Files skipped from review due to trivial changes (1)
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/@tailwindcss-postcss/src/index.ts
Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request. ### What changed? #### ✳️ eslint (9.32.0 → 9.33.0) · [Repo](https://github.com/eslint/eslint) · [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) <details> <summary>Release Notes</summary> <h4><a href="https://github.com/eslint/eslint/releases/tag/v9.33.0">9.33.0</a></h4> <blockquote><h2 dir="auto">Features</h2> <ul dir="auto"> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/e07820e66fd1fceaf2620dc931154955a706cc0f"><code class="notranslate">e07820e</code></a> feat: add global object access detection to no-restricted-globals (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19939">#19939</a>) (sethamus)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/90b050ec11557cab08b6be9f05fabf97dba6a63d"><code class="notranslate">90b050e</code></a> feat: support explicit resource management in <code class="notranslate">one-var</code> (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19941">#19941</a>) (Sweta Tanwar)</li> </ul> <h2 dir="auto">Bug Fixes</h2> <ul dir="auto"> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/732433c4fb023f45154b825cdc8cdaf1979d4336"><code class="notranslate">732433c</code></a> fix: allow any type for <code class="notranslate">meta.docs.recommended</code> in custom rules (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19995">#19995</a>) (Francesco Trotta)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/e8a6914a249d036e12494004e586b2a2b6e104d1"><code class="notranslate">e8a6914</code></a> fix: Fixed potential bug in check-emfile-handling.js (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19975">#19975</a>) (諏訪原慶斗)</li> </ul> <h2 dir="auto">Documentation</h2> <ul dir="auto"> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/34f0723e2d0faf8ac8dc95ec56e6d181bd6b67f2"><code class="notranslate">34f0723</code></a> docs: playground button for TypeScript code example (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19671">#19671</a>) (Tanuj Kanti)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/dc942a47daf41228d69072c52f1be20789426862"><code class="notranslate">dc942a4</code></a> docs: Update README (GitHub Actions Bot)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/5a4b6f74320b72f9b6ad8b30f5c463b2b71315af"><code class="notranslate">5a4b6f7</code></a> docs: Update no-multi-assign.md (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19979">#19979</a>) (Yuki Takada (Yukinosuke Takada))</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/247e15698e34919a0cd411842fb3e14ac7a8f1ba"><code class="notranslate">247e156</code></a> docs: add missing let declarations in <code class="notranslate">no-plusplus</code> (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19980">#19980</a>) (Yuki Takada (Yukinosuke Takada))</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/0d17242b3c25c2ddf8363f4560641acd1ae82ca9"><code class="notranslate">0d17242</code></a> docs: Update README (GitHub Actions Bot)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/fa20b9db8ff90ea9f0527118114dda17c656d095"><code class="notranslate">fa20b9d</code></a> docs: Clarify when to open an issue for a PR (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19974">#19974</a>) (Nicholas C. Zakas)</li> </ul> <h2 dir="auto">Build Related</h2> <ul dir="auto"> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/27fa86551bd173387e29a139293de78b0e14f0f3"><code class="notranslate">27fa865</code></a> build: use <code class="notranslate">ESLint</code> class to generate formatter examples (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19972">#19972</a>) (Milos Djermanovic)</li> </ul> <h2 dir="auto">Chores</h2> <ul dir="auto"> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/425804602ecb9ee5f54d1c38a473cf20538420c5"><code class="notranslate">4258046</code></a> chore: update dependency @eslint/js to v9.33.0 (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19998">#19998</a>) (renovate[bot])</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/ad283717ed4764a171120ca7c6cba82a78fa024c"><code class="notranslate">ad28371</code></a> chore: package.json update for @eslint/js release (Jenkins)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/06a22f154c08ea044b3172b357b226d34dfefc6a"><code class="notranslate">06a22f1</code></a> test: resolve flakiness in --mcp flag test (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19993">#19993</a>) (Pixel998)</li> <li> <a href="https://bounce.depfu.com/github.com/eslint/eslint/commit/54920ed229693f23650dace6e567bf44413aaf98"><code class="notranslate">54920ed</code></a> test: switch to <code class="notranslate">Linter.Config</code> in <code class="notranslate">ESLintRules</code> type tests (<a href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19977">#19977</a>) (Francesco Trotta)</li> </ul></blockquote> <p><em>Does any of this look wrong? <a href="https://depfu.com/packages/npm/eslint/feedback">Please let us know.</a></em></p> </details> <details> <summary>Commits</summary> <p><a href="https://github.com/eslint/eslint/compare/2364031090daafe34e80904c32065bfe4692d7a2...a90d7c4fe5ef83054e29d21d7ffb442103429d03">See the full diff on Github</a>. The new version differs by 17 commits:</p> <ul> <li><a href="https://github.com/eslint/eslint/commit/a90d7c4fe5ef83054e29d21d7ffb442103429d03"><code>9.33.0</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/9534b55372dbff3bf153384ed2aaab172f998175"><code>Build: changelog update for 9.33.0</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/425804602ecb9ee5f54d1c38a473cf20538420c5"><code>chore: update dependency @eslint/js to v9.33.0 (tailwindlabs#19998)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/ad283717ed4764a171120ca7c6cba82a78fa024c"><code>chore: package.json update for @eslint/js release</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/06a22f154c08ea044b3172b357b226d34dfefc6a"><code>test: resolve flakiness in --mcp flag test (tailwindlabs#19993)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/732433c4fb023f45154b825cdc8cdaf1979d4336"><code>fix: allow any type for `meta.docs.recommended` in custom rules (tailwindlabs#19995)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/34f0723e2d0faf8ac8dc95ec56e6d181bd6b67f2"><code>docs: playground button for TypeScript code example (tailwindlabs#19671)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/dc942a47daf41228d69072c52f1be20789426862"><code>docs: Update README</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/5a4b6f74320b72f9b6ad8b30f5c463b2b71315af"><code>docs: Update no-multi-assign.md (tailwindlabs#19979)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/247e15698e34919a0cd411842fb3e14ac7a8f1ba"><code>docs: add missing let declarations in `no-plusplus` (tailwindlabs#19980)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/0d17242b3c25c2ddf8363f4560641acd1ae82ca9"><code>docs: Update README</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/e07820e66fd1fceaf2620dc931154955a706cc0f"><code>feat: add global object access detection to no-restricted-globals (tailwindlabs#19939)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/fa20b9db8ff90ea9f0527118114dda17c656d095"><code>docs: Clarify when to open an issue for a PR (tailwindlabs#19974)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/54920ed229693f23650dace6e567bf44413aaf98"><code>test: switch to `Linter.Config` in `ESLintRules` type tests (tailwindlabs#19977)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/e8a6914a249d036e12494004e586b2a2b6e104d1"><code>fix: Fixed potential bug in check-emfile-handling.js (tailwindlabs#19975)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/90b050ec11557cab08b6be9f05fabf97dba6a63d"><code>feat: support explicit resource management in `one-var` (tailwindlabs#19941)</code></a></li> <li><a href="https://github.com/eslint/eslint/commit/27fa86551bd173387e29a139293de78b0e14f0f3"><code>build: use `ESLint` class to generate formatter examples (tailwindlabs#19972)</code></a></li> </ul> </details> ---  [Depfu](https://depfu.com) will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with `@depfu rebase`. <details><summary>All Depfu comment commands</summary> <blockquote><dl> <dt>@depfu rebase</dt><dd>Rebases against your default branch and redoes this update</dd> <dt>@depfu recreate</dt><dd>Recreates this PR, overwriting any edits that you've made to it</dd> <dt>@depfu merge</dt><dd>Merges this PR once your tests are passing and conflicts are resolved</dd> <dt>@depfu cancel merge</dt><dd>Cancels automatic merging of this PR</dd> <dt>@depfu close</dt><dd>Closes this PR and deletes the branch</dd> <dt>@depfu reopen</dt><dd>Restores the branch and reopens this PR (if it's closed)</dd> <dt>@depfu pause</dt><dd>Ignores all future updates for this dependency and closes this PR</dd> <dt>@depfu pause [minor|major]</dt><dd>Ignores all future minor/major updates for this dependency and closes this PR</dd> <dt>@depfu resume</dt><dd>Future versions of this dependency will create PRs again (leaves this PR as is)</dd> </dl></blockquote> </details> Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Summary
@tailwindcss/postcssderivesinputBasePathfromresult.opts.from:When PostCSS calls the plugin without
from(some bundlers, including Turbopack, do this for certain CSS inputs),inputFileis'',path.resolve('')returnsprocess.cwd(), andpath.dirname(...)therefore returns the parent of CWD. The downstreamcompileAst({ base: inputBasePath })call then asks the resolver to findtailwindcssfrom one level above the project root, which fails with:The plugin already computes
base = opts.base ?? process.cwd()near the top. Reusing that as the fallback gives a sensible default (CWD) and respects an explicitopts.basewhen set.Test plan
Added a test in
packages/@tailwindcss-postcss/src/index.test.tsthat processes@import 'tailwindcss'viaprocessor.process(input)with nofromoption. Before the fix, this throwsError: Can't resolve 'tailwindcss' in '<parent of CWD>'; after the fix, the import resolves and the processor returns non-empty CSS.I wasn't able to run the suite locally —
pnpm buildrequirescargofor@tailwindcss/oxideand I don't have a Rust toolchain set up — so the test has been written to match existing conventions inindex.test.ts(vitest, plainpostcss([tailwindcss({...})]).process(...)), and I'm relying on CI to verify.