stop pnpm churning package.json line endings
I work on a Windows repo from my Mac. The tree is CRLF everywhere, pinned by
.editorconfig and prettier. I ran pnpm add for a single dependency and
git diff lit up the entire package.json.
$ pnpm add zod$ git diff --stat package.json | 95 +++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 47 deletions(-)
One new line, the whole file changed. Did I break something?
After a short investigation, I figured out pnpm writes package.json with LF
endings and ignores both .editorconfig and prettier. And the repo’s
.gitattributes is:
$ cat .gitattributes* -text # do no line-ending conversion, store bytes as-is*.xlsx diff=zip
-text tells git to never normalize line endings. So git faithfully recorded
that every CRLF line was now an LF line. The whole file.
Turns out .gitattributes can pin one
file against that. I added a line:
package.json text eol=crlf # store LF in git, check out as CRLF
Then renormalized once:
git add --renormalize package.json # re-stage as LF (this commit is EOL-only)git commit -m "normalize package.json line endings"
Now pnpm add diffs to only the line I actually added:
$ pnpm add zod$ git diff+ "zod": "^3.23.0"
The one gotcha: right after pnpm add, git still marks the file modified and
warns LF will be replaced by CRLF the next time Git touches it. The diff stays
clean. Git just has not rewritten the bytes on disk yet. My prettier hook does
that on commit, and a fresh checkout does too.
down the rabbit hole
That is the practical bit. What made it click: with text eol=crlf, git keeps
two copies of the file. LF in its index, CRLF on my disk. It normalizes both to
LF before diffing, so it stops caring which one pnpm wrote.
git ls-files --eol shows both states at once:
$ git ls-files --eol package.jsoni/lf w/crlf attr/text eol=crlf package.json# i/lf = stored in git as LF# w/crlf = checked out on disk as CRLF