Yarn Scripts: Fixing the Windows-to-Linux Gap
Actually, I should clarify – I spent last Tuesday night staring at a CI pipeline that refused to turn green. It’s the classic developer nightmare: everything works perfectly on my machine, but the moment it hits the staging environment, it implodes. Specifically, I was getting a cryptic permission denied error on a shell script that I knew was executable.
The culprit? My coworker committed the file from Windows.
But if you’ve ever worked in a mixed-OS team—some on Mac, some on WSL (Windows Subsystem for Linux), and the CI running on Ubuntu—you know this pain. Yarn is supposed to abstract this away, though it needs a little help at times. After fixing this for the third time in a month, I decided to stop patching leaks and actually fix the plumbing.
The CRLF and Permission Headache
Here’s the thing. Windows uses Carriage Return + Line Feed (CRLF) for line breaks. Linux uses just Line Feed (LF). When a script written in Windows gets pushed to a Linux CI runner, the interpreter often chokes. Add to that the fact that Windows file systems don’t handle executable permissions (chmod) the same way Linux does, and you have a recipe for a broken build.
I used to manually run chmod +x every time I pulled a branch. It was tedious. Then I realized I could probably bake this hygiene into my Yarn workflow.
I added a “pre-commit” or “setup” script to my package.json. It forces consistency regardless of the OS the developer is using. If you’re running WSL, this is generally mandatory.
{
"scripts": {
"fix:scripts": "find ./scripts -name '*.sh' -exec dos2unix {} \\; && chmod +x ./scripts/*.sh",
"build": "yarn fix:scripts && next build",
"deploy": "yarn fix:scripts && yarn vercel --prod"
}
}
Now, whenever I run a build or a deploy, Yarn ensures the scripts are sanitized first. No more /bin/bash^M: bad interpreter errors at 2 AM.
Smart Caching for Heavy Builds
We’re building a dApp that interacts with Sepolia (an Ethereum testnet), and our contract compilation step was eating up half our CI minutes. It felt like burning money. Every time we pushed a PR, we were recompiling Solidity contracts that hadn’t changed.
Yarn’s cache is great for dependencies, but for build artifacts, you need to get creative. I started using a smart-caching approach. Instead of just running the compile command blindly, I wrapped it in a script that checks a hash of the contract files.
If the hash matches the last build? Skip it. If not? Compile and update the hash.
#!/bin/bash
# scripts/smart-compile.sh
CURRENT_HASH=$(find contracts -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum | awk '{print $1}')
CACHE_FILE=".build-cache"
if [ -f "$CACHE_FILE" ] && [ "$(cat $CACHE_FILE)" == "$CURRENT_HASH" ]; then
echo "✅ Contracts unchanged. Skipping compilation."
exit 0
fi
echo "🔄 Changes detected. Compiling..."
yarn hardhat compile
echo "$CURRENT_HASH" > "$CACHE_FILE"
This dropped our average PR build time from 6 minutes to about 45 seconds. When you’re waiting on a leaderboard of PRs to get merged, that speed difference is everything. It also saved us a ton on “gas” (metaphorically speaking regarding CI credits, though literally saving gas on unnecessary testnet deploys too).
The Live URL Shortcut
Well, I hate context switching. If I’m in the terminal, I want to stay in the terminal. Opening a browser to log into a dashboard just to click “Promote to Production” feels like a failure of automation.
I mapped my production deployment directly to a Yarn command. It sounds simple, but wrapping the Vercel CLI inside Yarn allows me to enforce those script fixes I mentioned earlier before the code ever leaves my machine.
My workflow is now just:
yarn deploy:prod
Under the hood, this runs yarn vercel --prod, but only after running my linting and test suites. It’s a small efficiency hack, but it prevents that embarrassing moment where you push to prod and immediately realize you broke the layout.
Why I’m Still on Yarn in 2026
Look, I know pnpm is fast. And Bun was the shiny new toy back in ’24. But Yarn’s Plug’n’Play (PnP) feature—once I finally wrapped my head around it—has been the most stable experience for our monorepo.
We have a repo with a Next.js frontend, a Hardhat backend, and a shared UI library. Npm struggled with the hoisting. Pnpm was better, but we hit weird edge cases with ghost dependencies in our Docker builds. Yarn Berry (v4.6 at this point) just handles the workspace constraints better, probably.
For example, enforcing consistent versions across the monorepo is a one-liner in the configuration:
constraints:
- dependency_name: "react"
version: "19.0.0"
workspace: "*"
Try doing that cleanly in npm without third-party tools.
Is Yarn perfect? No. The migration from v1 to Berry was painful enough that I still have scars. But right now, for a mixed-OS team trying to ship fast without breaking the build on Windows machines, it’s the tool that gets out of my way the most. And really, that’s all I want from a package manager.
Common questions
How do I fix /bin/bash^M: bad interpreter errors in a Yarn project shared between Windows and Linux?
Windows uses CRLF line endings while Linux uses LF, which breaks shell scripts on Linux CI runners. Add a fix:scripts command to package.json that runs dos2unix on your .sh files and chmod +x to make them executable, then chain it before your build and deploy scripts. Yarn will sanitize the scripts automatically every time, eliminating permission-denied and bad-interpreter errors.
How can I skip recompiling Solidity contracts in CI when nothing has changed?
Wrap your hardhat compile step in a shell script that generates a sha1sum hash of every file in the contracts directory and compares it to a stored .build-cache file. If the hash matches, exit early and skip compilation. If it differs, compile and overwrite the cache. This approach dropped average PR build time from 6 minutes to roughly 45 seconds.
How do I deploy to Vercel production from the terminal using a Yarn script?
Map a yarn deploy:prod command in package.json that wraps yarn vercel –prod. Chain your script-fixing, linting, and test suites in front of it so they run before the code leaves your machine. This avoids context-switching to the Vercel dashboard to click Promote to Production and catches issues like broken layouts before they reach prod.
Why choose Yarn Berry over pnpm or Bun for a mixed Next.js and Hardhat monorepo in 2026?
In the author’s experience with a Next.js frontend, Hardhat backend, and shared UI library, npm struggled with hoisting and pnpm hit ghost-dependency edge cases in Docker builds. Yarn Berry v4.6 with Plug’n’Play handles workspace constraints more stably and lets you enforce consistent versions (like pinning react to 19.0.0 across all workspaces) in a single configuration block without third-party tools.
