Skip to content

Prevent @tailwindcss/upgrade from rewriting files inside node_modules when run from a workspace subpackage - #20329

Merged
RobinMalfait merged 6 commits into
tailwindlabs:mainfrom
benface:fix/upgrade-skip-node-modules-in-subpackage
Jul 14, 2026
Merged

Prevent @tailwindcss/upgrade from rewriting files inside node_modules when run from a workspace subpackage#20329
RobinMalfait merged 6 commits into
tailwindlabs:mainfrom
benface:fix/upgrade-skip-node-modules-in-subpackage

Conversation

@benface

@benface benface commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Fixes #20328.

What changed

When @tailwindcss/upgrade is invoked from a subpackage of a workspace (e.g. pnpm --filter … or cd packages/foo && pnpm exec upgrade), it traverses ../../node_modules/… and applies its v3 → v4 migration transform (@tailwind utilities;@import 'tailwindcss/utilities' layer(utilities);) to files inside the installed tailwindcss package itself — a self-referential import that later detonates as an infinite CSS-resolution loop and reads like a "cache corruption" bug (matches the report in #19726 / #20328).

Root cause: globby's isGitIgnored only walks .gitignore files at or below its cwd. When invoked from a subpackage, the workspace-root .gitignore (which almost always lists node_modules) is never consulted, and ../../node_modules/… paths come back as not ignored.

The fix anchors isGitIgnored at the git repository root instead of the subpackage:

  • New helper: gitRoot(cwd) in src/utils/git.ts — thin wrapper over git rev-parse --show-toplevel.
  • analyze(stylesheets, { base }) in src/codemods/css/analyze.ts now calls isGitIgnored({ cwd: gitRoot(base) ?? base }). Falls back to the previous behavior when not inside a git repository or when git is unavailable.

Test

Added an integration test that reproduces the exact scenario: a pnpm workspace with a subpackage that imports tailwindcss/utilities.css, upgrade run from the subpackage, then asserts packages/css/node_modules/tailwindcss/utilities.css still holds the pristine @tailwind utilities; directive.

Verified the test fails on main and passes with this change.

The 415 existing upgrade unit tests still pass. Two npm-related snapshot failures in upgrade-errors.test.ts (half-upgraded v3 project to v4 (bun|npm)) pre-exist this change — they're about newer npm's npm notice run output that isn't in the snapshots, unrelated to what this PR touches.


Edit by @RobinMalfait: going to run on all [ci-all] so we can make sure it works on Windows as well.

@benface
benface requested a review from a team as a code owner July 14, 2026 15:49
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The upgrade analyzer now accepts its working-directory base, resolves the Git repository root, and uses that root when checking .gitignore rules. A new gitRoot utility handles successful and failed Git root lookups. An integration test covers running the upgrade from a workspace subpackage and verifies that a stylesheet in node_modules is not rewritten. The changelog documents the fix.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #20328 by anchoring gitignore checks at the repo root and adding a regression test.
Out of Scope Changes check ✅ Passed The PR stays focused on the node_modules rewrite bug, with only a changelog note, test, helper, and call-site updates.
Title check ✅ Passed The title accurately summarizes the main fix: avoiding rewrites inside node_modules when upgrade runs from a workspace subpackage.
Description check ✅ Passed The description is detailed and directly describes the workspace subpackage node_modules rewrite bug and its fix.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The CSS analysis path is correctly fixed, but the identical isGitIgnored({ cwd: base }) call used for template file migration in index.ts is untouched, leaving the reported bug partially open for HTML/JS files.

The template migration code in index.ts still calls isGitIgnored({ cwd: base }) at line 46 and uses that function at lines 358 and 369 to gate which template files are migrated. When run from a workspace subpackage, the workspace-root .gitignore is never loaded, so template files from ../../node_modules/… pass the ignore check and are rewritten on disk.

packages/@tailwindcss-upgrade/src/index.ts — the isIgnored on line 46 still uses cwd: base instead of cwd: gitRoot(base) ?? base, leaving template migration vulnerable to the same workspace subpackage traversal issue.

Reviews (3): Last reviewed commit: "update CHANGELOG" | Re-trigger Greptile

Comment thread integrations/upgrade/index.test.ts Outdated
@benface

benface commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @greptile-apps — good catch on the self-documentation. Test now declares the root .gitignore explicitly (68bce62): even though the harness injects a default with the same content, making it visible in the test's fs block ties the assertion directly to the mechanism the fix exercises.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Agreed — the comment you added in the fs block makes the intent clear too:

// The root-level `.gitignore` that anchors the fix. The test harness
// injects a default `.gitignore` with `node_modules/` when none is
// provided, but declaring it explicitly here makes the mechanism the
// fix relies on part of the test's visible surface.

That's exactly the right call. A future reader who removes or modifies that .gitignore entry will immediately understand they're touching the condition the regression test is protecting — rather than having to know that the harness injects one implicitly and then go look up what its default content is. The test now reads as a complete specification of the scenario.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

benface and others added 3 commits July 14, 2026 18:20
Notes by Robin Malfait:

Split the commit in 2, because I want to be able to see the test fail
_before_ we apply the fix.
@RobinMalfait
RobinMalfait force-pushed the fix/upgrade-skip-node-modules-in-subpackage branch 2 times, most recently from cff1356 to b8ede71 Compare July 14, 2026 16:32
@RobinMalfait
RobinMalfait force-pushed the fix/upgrade-skip-node-modules-in-subpackage branch from b8ede71 to eb97c9a Compare July 14, 2026 16:32
@RobinMalfait
RobinMalfait enabled auto-merge (squash) July 14, 2026 16:34

@RobinMalfait RobinMalfait left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Made a few small changes, and split your original commit such that I could see the test fail before we apply the fix. The test did indeed fail and passed afterwards 💪

Once CI passes (running on Linux / macOS / Windows), this will be merged and will be available in the next release. Thanks!

@RobinMalfait
RobinMalfait merged commit a854b35 into tailwindlabs:main Jul 14, 2026
22 checks passed
RobinMalfait added a commit that referenced this pull request Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@tailwindcss/upgrade rewrites files in node_modules when run from a pnpm workspace subpackage

2 participants