简体   繁体   中英

Run another yarn/npm task within a package.json, without specifying yarn or npm

I have a task in my package.json "deploy", which needs to first call "build". I have specified it like this:

"deploy": "yarn run build; ./deploy.sh",

The problem is that this hard codes yarn as the package manager. So if someone doesn't use yarn , it doesn't work. Switching to npm causes a similar issue.

What's a good way to achieve this while remaining agnostic to the choice of npm or yarn ?

One simple approach is to use the npm-run-all package, whose documentation states:

Yarn Compatibility

If a script is invoked with Yarn, npm-run-all will correctly use Yarn to execute the plan's child scripts.

So you can do this:

"predeploy": "run-s build",
"deploy": "./deploy.sh",

And the predeploy step will use either npm or yarn depending on how you invoked the deploy task.

I think it is good to have the runs in package.json remain package manager agnostic so that they aren't tied to a specific package manager, but within a project, it is probably prudent to agree on the use of a single package manager so that you're not dealing with conflicting lockfiles.

It's probably not ideal, but you could run a .js file at your project root to make these checks...

You could create a file at your project root called yarnpm.js (or whatever), and call said file in your package.json deploy command..

// package.json (trimmed)
"scripts": {
  "deploy": "node yarnpm",
  "build": "whatever build command you use"
},
// yarnpm.js
const fs = require('fs');

const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');

// Command you wish to run with `{{}}` in place of `npm` or `yarn'
// This would allow you to easily run multiple `npm`/`yarn` commands without much work
// For example, `{{}} run one && {{}} run two
const COMMAND_TO_RUN = '{{}} run build; ./deploy.sh';

try {
  if (fs.existsSync('./package-lock.json')) {  // Check for `npm`
    execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
  } else if (fs.existsSync('./yarn.lock')) {   // Check for `yarn`
    execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
  } else {
    console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
  }
} catch (err) {
  console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
}

function execute(command) { // Helper function, to make running `exec` easier
  require('child_process').exec(command,
    (error, stdout, stderr) => {
      if (error) {
        console.log(`error: ${error.message}`);
        return;
      }
      if (stderr) {
        console.log(`stderr: ${stderr}`);
        return;
      }
      console.log(stdout);
    });
}

Hope this helps in some way! Cheers.


EDIT:

...or if you wanted to parameterize the yarnpm.js script, to make it easily reusable, and to keep all "commands" inside the package.json file, you could do something like this..

// package.json (trimmed, parameterized)
"scripts": {
    "deploy": "node yarnpm '{{}} run build; ./deploy.sh'",
    "build": "node build.js"
},
// yarnpm.js (parameterized)
const COMMAND_TO_RUN = process.argv[2]; // Technically, the first 'parameter' is the third index
const FILE_NAME = process.argv[1].replace(/^.*[\\\/]/, '');

if (COMMAND_TO_RUN) {
  const fs = require('fs');

  try {
    if (fs.existsSync('./package-lock.json')) {  // Check for `npm`
      execute(COMMAND_TO_RUN.replace('{{}}', 'npm'));
    } else if (fs.existsSync('./yarn.lock')) {   // Check for `yarn`
      execute(COMMAND_TO_RUN.replace('{{}}', 'yarn'));
    } else {
      console.log('\x1b[33m', `[${FILE_NAME}] Unable to locate either npm or yarn!`, '\033[0m');
    }
  } catch (err) {
    console.log('\x1b[31m', `[${FILE_NAME}] Unable to deploy!`, '\033[0m');
  }

  function execute(command) { // Helper function, to make running `exec` easier
    require('child_process').exec(command,
      (error, stdout, stderr) => {
        if (error) {
          console.log(`error: ${error.message}`);
          return;
        }
        if (stderr) {
          console.log(`stderr: ${stderr}`);
          return;
        }
        console.log(stdout);
      });
  }
} else {
  console.log('\x1b[31m', `[${FILE_NAME}] Requires a single argument!`, '\033[0m')
}

What if check before run?

You can create a new file called build.sh , and it's content below:

# check if current user installed node environment, if not, auto install it.
if command -v node >/dev/null 2>&1; then
    echo "version of node: $(node -v)"
    echo "version of npm: $(npm -v)"
else
    # auto install node environment, suppose platform is centos, 
    # need change this part to apply other platform.
    curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
    yum -y install nodejs
fi

npm run build

Then your script will be:

{
  "deploy": "./build.sh && ./deploy.sh"
}

So I think I have a much simpler solution:

"deploy": "yarn run build || npm run build; ./deploy.sh",

Its only real downside is in the case where yarn exists, but the build fails, then npm run build will also take place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM