OT
OneTechly
Plan accordingly  •  Build confidently

Cross-Platform npm Scripts: Why Your Windows Commands Fail on macOS/Linux (and How to Fix Them)

I'll be honest — this one caught me completely off guard. I was deep into building PixelPerfect Screenshot API, everything was running beautifully on my Windows machine, and then I tried to spin up the frontend on a Linux server and got slapped with this:

'set' is not recognized as an internal or external command

At first I thought something was broken with my Node installation. Then I thought it was a Render.com configuration issue. It took me an embarrassingly long time to realize the problem was sitting right there in my package.json — a single word: set.

If you've hit this wall, this article is for you. And if you haven't hit it yet, read this anyway — because you will.

The Platform Compatibility Problem
✅ Windows (Works)
"start": "set REACT_APP_API_URL=http://localhost:8000 && react-scripts start"
Why it works:
Windows Command Prompt understands set for environment variables.
✓ Executes Successfully
❌ macOS/Linux (Fails)
"start": "set REACT_APP_API_URL=http://localhost:8000 && react-scripts start"
Why it fails:
Unix shells don't recognize set as a variable assignment command.
✗ Command Not Found

The Core Issue: Windows uses set VAR=value, while Unix systems use VAR=value or export VAR=value. They don't speak the same shell language.

Why This Happens at All

The short version: Windows and Unix-based operating systems (macOS, Linux) evolved completely separately, and they handle environment variables differently at the shell level.

On Windows

The traditional approach in Command Prompt is:

set VARIABLE=value && npm start

This creates a temporary variable that lives only for that command's execution. Works perfectly — on Windows.

On macOS and Linux

Bash and Zsh use a completely different syntax:

VARIABLE=value npm start
# or
export VARIABLE=value && npm start

When a Unix shell sees set VARIABLE=value at the start of a command, it doesn't recognize set as a variable assignment — it looks for a program called set, can't find one, and crashes. That's your error.

The practical impact: In a team with mixed operating systems, a developer can commit working npm scripts, and their teammate — running macOS or Linux — literally cannot start the project. I've seen this stall entire sprints while everyone points fingers at "environment issues." It's always this.

The Fix: cross-env

The cross-env package is the cleanest solution. Instead of writing platform-specific syntax, you prefix your variables with cross-env and let the package figure out the translation.

cross-env VARIABLE=value npm start

cross-env detects your OS and emits the right syntax under the hood — set on Windows, inline assignment on Unix. You never think about it again.

How cross-env Works Across Platforms
Your Script
cross-env
VAR=value
npm start
cross-env
Detects OS
&
Translates Syntax
Platform Output
Windows: set VAR=value
macOS/Linux: VAR=value

Key Benefit: Write the syntax once. cross-env handles the translation. No conditional logic, no separate scripts per OS.

Getting It Installed

npm install --save-dev cross-env

What I Actually Changed in PixelPerfect

Before: what I had (Windows only)

{
  "scripts": {
    "start": "set GENERATE_SOURCEMAP=false && react-scripts start",
    "build": "set GENERATE_SOURCEMAP=false && set ESLINT_NO_DEV_ERRORS=true && react-scripts build"
  }
}
Before and After: Script Transformation
❌ Before (Windows Only)
"scripts": { "start": "set GENERATE_SOURCEMAP=false && react-scripts start", "start:lan": "set REACT_APP_API_URL=http://192.168.1.185:8000 && set REACT_APP_API_BASE_URL=http://192.168.1.185:8000 && react-scripts start", "build": "set GENERATE_SOURCEMAP=false && set ESLINT_NO_DEV_ERRORS=true && react-scripts build" }
Problems:
• Windows-only set command
• Fails on macOS and Linux
• Production builds broken on Render
• CI/CD pipelines fail
Platform-Specific
✅ After (Cross-Platform)
"scripts": { "start": "cross-env GENERATE_SOURCEMAP=false react-scripts start", "start:lan": "cross-env GENERATE_SOURCEMAP=false REACT_APP_API_URL=http://192.168.1.185:8000 REACT_APP_API_BASE_URL=http://192.168.1.185:8000 react-scripts start", "build": "cross-env GENERATE_SOURCEMAP=false ESLINT_NO_DEV_ERRORS=true react-scripts build" }
Benefits:
• Works on Windows, macOS, Linux
• Cleaner — no && between variables
• Render.com builds work immediately
• CI/CD just works
Universal Compatibility

After: what actually works everywhere

{
  "scripts": {
    "start": "cross-env GENERATE_SOURCEMAP=false react-scripts start",

    "start:lan": "cross-env GENERATE_SOURCEMAP=false REACT_APP_API_URL=http://192.168.1.185:8000 REACT_APP_API_BASE_URL=http://192.168.1.185:8000 react-scripts start",

    "build": "cross-env GENERATE_SOURCEMAP=false ESLINT_NO_DEV_ERRORS=true react-scripts build"
  }
}

Notice something else changed: I dropped the && separators between variables. With cross-env, you just list multiple variables inline one after another. The syntax is cleaner, and there's less surface area for typos.

A Note on the start:lan Script

When building PixelPerfect, I needed to test the screenshot functionality on real mobile devices — not just browser dev tools' device emulation, but an actual iPhone and Android phone on my home network. The problem is that localhost:3000 only works on the machine running the server. From your phone, localhost means the phone itself.

The solution is to start the React dev server pointing at your machine's LAN IP instead of localhost. That's what start:lan does — it tells the frontend to talk to the backend at 192.168.1.185:8000 (my computer's IP on the local network), which both my development machine and my phone can reach.

One gotcha: Your LAN IP (the 192.168.x.x address) can change if your router reassigns it, especially after a reboot. If the mobile connection suddenly stops working, check your IP first. On Windows: ipconfig. On macOS/Linux: ifconfig or ip addr. Update the script accordingly.

Platform Compatibility Matrix
Method
Windows
macOS
Linux
set VAR=value &&
export VAR=value &&
VAR=value (inline)
cross-env VAR=value RECOMMENDED

Only cross-env works across all three major operating systems without modification.

Things That Will Still Trip You Up

Forgetting it's a devDependency

If you run npm install --production on your server and cross-env is only in devDependencies, the build command will fail because cross-env won't be installed. Either move cross-env to dependencies or run npm install without the --production flag.

Mixing old and new syntax

If you fix three scripts and leave one with set, that one will still break. Go through every script in your package.json when you make this change.

Other shell-specific things

cross-env only solves the environment variable problem. If you've got other shell-specific commands — rm -rf (fails on Windows), cp -r — those are separate issues. For file deletion, rimraf is the cross-platform equivalent of rm -rf.

"clean": "rimraf build coverage"
Cross-Platform Development: What I Actually Do Now
1
Install cross-env in every new project
Even if you're currently working solo on Windows. The day you deploy to a Linux server, or someone else joins the project, you'll be glad it's already there.
2
Test your build script on Render/Vercel early
Don't wait until you're ready to launch to discover your build command doesn't work in a Linux container. Run a test deploy early — it takes 5 minutes and can save hours later.
3
Add a note in your README
Something like "This project uses cross-env for cross-platform npm scripts" saves the next person from spending 45 minutes debugging the same issue you already solved.
4
Audit your scripts when onboarding someone new
The first time a new team member on a different OS runs the project is a great forcing function for finding platform-specific scripts you missed.

Wrapping Up

This is one of those problems that's genuinely frustrating the first time you hit it because the error message doesn't point you at the real cause. 'set' is not recognized doesn't say "your npm script uses Windows-only syntax." You have to know to look there.

Once you know, the fix is five minutes: install cross-env, replace set VAR=value && with cross-env VAR=value in every script, commit it. From that point on, anyone on any OS can run your project without touching the configuration.

For PixelPerfect, the production build that was silently broken started working on the first deploy after this change. No other configuration needed.

The one-line takeaway: If your npm scripts set environment variables, use cross-env. Install it today, use it everywhere, never think about it again.


Comments

Popular posts from this blog