64

I would like to create a package.json build script that executes slightly different set of commands when run from Windows, Linux, Mac.

The problem is that I cannot find a way to put it in package.json file that will run without problems at every system.

Here is an example that I would like to have:

"scripts" : {
    "build.windows" : "echo do windows specific stuff",
    "build.linux" : "echo do linux specific stuff",
    "build.mac" : "echo do mac specific stuff",
    "build" : "??????????????" <- what to put here to execute script designed for OS
                                  on which npm is running
}
gawi
  • 2,843
  • 4
  • 29
  • 44
  • [`shx`](https://www.npmjs.com/package/shx#install) (tiny) package is an easy way of running shell-specific commands without altering a script's name. – vsync Nov 28 '22 at 14:38

4 Answers4

104

There's an NPM package called run-script-os ( NPM | GitHub ) that doesn't require you to write any additional files, and this can be convenient if what you're trying to do is very simple. For example, in your package.json, you might have something like:

"scripts": {
    "test": "run-script-os",
    "test:darwin:linux": "export NODE_ENV=test && mocha",
    "test:win32": "SET NODE_ENV=test&& mocha"
}

Then you could run npm test on Windows, Mac, or Linux and get similar (or different!) results on each.

Andrew Lohr
  • 5,380
  • 1
  • 26
  • 38
Dave P
  • 1,445
  • 2
  • 7
  • 6
  • 1
    What is the equivalent syntax to use "test:unit" with "run-script-os"? – Bernd Prager Dec 14 '21 at 00:30
  • @BerndPrager were you able to find the equivalent of above ? – Prabhat Mishra Dec 14 '21 at 06:55
  • 1
    I used "prettier-check": "run-script-os" and it worked! – Prabhat Mishra Dec 14 '21 at 07:08
  • Hey @BerndPrager, @PrabhatMishra You would do `"test:unit": "run-script-os",` `"test:unit:win32": "..."` The idea is you have run-script-os called on the main thing you want (in this case test:unit) and then create the other versions with trailing operating systems (test:unit:win32, etc) – none Aug 10 '22 at 20:56
  • It should be **terminal**-based and not ***OS***-based – vsync Nov 28 '22 at 14:02
  • Also, this answer is not good when you need to write terminal-specific commands for a **specific** reserved-word script, for example the `prepare` stage, or `publish` – vsync Nov 28 '22 at 14:08
  • This could be the accepted answer. Thanks @Dave – Arjun G Aug 16 '23 at 10:00
40

You can use scripts with node run-script command. npm run is a shortcut of it.

Package json:

"scripts" : {
    "build-windows" : "node build-windows.js",
    "build-linux" : "node build-linux.js",
    "build-mac" : "node build-mac.js",
    "build" : "node build.js"
}

Command line:

npm run build-windows

If you don't like it, you can use commands inside node.js.

Package json:

"scripts" : {
    "build" : "node build.js"
}

Build.js

var sys = require('sys');
var exec = require('child_process').exec;
var os = require('os');

function puts(error, stdout, stderr) { sys.puts(stdout) }

// Run command depending on the OS
if (os.type() === 'Linux') 
   exec("node build-linux.js", puts); 
else if (os.type() === 'Darwin') 
   exec("node build-mac.js", puts); 
else if (os.type() === 'Windows_NT') 
   exec("node build-windows.js", puts);
else
   throw new Error("Unsupported OS found: " + os.type());
X X
  • 81
  • 7
hurricane
  • 6,521
  • 2
  • 34
  • 44
  • I think `exec("node build-windows.js", puts);` should be something dependent on `os`. – gawi Jul 13 '17 at 14:06
  • @gawi you are right. I wrote "control OS , then run command". – hurricane Jul 13 '17 at 14:17
  • I would put in the answer then: `if (os.type() === 'Linux') exec("node build-linux.js", puts); else if (os.type() === 'Darwin') exec("node build-mac.js", puts); else if (os.type() === 'Windows_NT') exec("node build-windows.js", puts);` – gawi Jul 13 '17 at 14:29
  • You can use `process.env.OS` instead of requiring `os` modules. Just thought to mention. – Tes3awy Sep 11 '18 at 15:14
  • @hurricane If i am executing a ps1 file by using exec('build-windows.ps1', puts), its not executing that file, rather just opening it. Any idea how that can be fixed – whyAto8 May 23 '19 at 10:51
  • @whyAto8 I think you are missing your run command for PowerShell. Try to use `spawn` instead of exec. https://stackoverflow.com/questions/10179114/execute-powershell-script-from-node-js – hurricane May 23 '19 at 11:11
  • @hurricane Well, i was able to run this , it seemed like a machine specific issue, as i tried on windows server and it worked. But, now the "node build.js" doesnt seem to run which is in package.json on the same windows server, do we need to add something for node to run on windows. – whyAto8 May 24 '19 at 07:01
  • What I liked about using build-prefix solution is that it also runs postbuild-prefix accordingly. – Farzan Aug 06 '21 at 17:58
4

Definitely not the most reliable way to do this, but you can technically accomplish this all in one npm script:

{
    "scripts": {
        "build": "( Write-Output 'Powershell' && ./tools/build-ps.ps1 ) || 
                  ( CALL ./tools/build-cmd.bat ) || 
                  ( bash -c 'uname -a | grep -q -i Linux' && bash -c ./tools/build-linux.sh ) ||
                  ( bash -c 'uname -a | grep -q -i Darwin' && bash -c ./tools/build-mac.sh )"
        }
}
Layne Bernardo
  • 676
  • 3
  • 12
  • This is nice because it doesn't require running an extra Node process which takes 40ms minimum on M1 Mac. – vaughan Apr 11 '22 at 15:09
2

It depends on exactly what you're trying to do in the scripts, but it's likely that you can use npm cli packages to effectively add cross-platform commands to any shell.

For example, if you wanted to delete a directory, you could use separate syntaxes for windows and linux:

rm -rf _site     # bash
rd /s /q _site   # cmd

Or instead, you could use the npm package rimraf which works cross platform:

npx rimraf _site

To take Dave P's example above, you could set environment variables with cross-env like this:

"scripts": {
    "test": "npx cross-env NODE_ENV=test mocha",
}

And if you don't want to use npx to install scripts live, you can install them globally ahead of time like this:

npm i cross-env -g

Here's a post I wrote on making NPM scripts work cross platform which explores some of these options

hlovdal
  • 26,565
  • 10
  • 94
  • 165
KyleMit
  • 30,350
  • 66
  • 462
  • 664