How to execute npm script using grunt-run? - node.js

I have a npm task in my package.json file as follows to execute jest testing:
"scripts": {
"test-jest": "jest",
"jest-coverage": "jest --coverage"
},
"jest": {
"testEnvironment": "jsdom"
},
I want to execute this task npm run test-jest using grunt. I installed grunt-run for the same and added the run task, but how do I invoke this npm task there?
run: {
options: {
// Task-specific options go here.
},
your_target: {
cmd: 'node'
}
}

Configure your Gruntfile.js similar to the example shown in the docs.
Set the value for the cmd to npm.
Set run and test-jest in the args Array.
Gruntfile.js
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-run');
grunt.initConfig({
run: {
options: {
// ...
},
npm_test_jest: {
cmd: 'npm',
args: [
'run',
'test-jest',
'--silent'
]
}
}
});
grunt.registerTask('default', [ 'run:npm_test_jest' ]);
};
Running
Running $ grunt via your CLI using the configuration shown above will invoke the npm run test-jest command.
Note: Adding --silent (or it's shorthand equivalent -s) to the args Array simply helps avoids the additional npm log to the console.
EDIT:
Cross Platform
Using the grunt-run solution shown above failed on Windows OS when running via cmd.exe. The following error was thrown:
Error: spawn npm ENOENT Warning: non-zero exit code -4058 Use --force to continue.
For a cross-platform solution consider installing and utlizing grunt-shell to invoke the npm run test-jest instead.
npm i -D grunt-shell
Gruntfile.js
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt); // <-- uses `load-grunt-tasks`
grunt.initConfig({
shell: {
npm_test_jest: {
command: 'npm run test-jest --silent',
}
}
});
grunt.registerTask('default', [ 'shell:npm_test_jest' ]);
};
Notes
grunt-shell requires load-grunt-tasks for loading the Task instead of the typical grunt.loadNpmTasks(...), so you'll need to install that too:
npm i -D load-grunt-tasks
For older version of Windows I had to install an older version of grunt-shell, namely version 1.3.0, so I recommend installing an earlier version.
npm i -D grunt-shell#1.3.0
EDIT 2
grunt-run does seem to work on Windows if you use the exec key instead of the cmd and args keys...
For cross platform purposes... I found it necessary to specify the command as a single string using the exec key as per the documentation that reads:
If you would like to specify your command as a single string, useful
for specifying multiple commands in one task, use the exec: key
Gruntfile.js
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-run');
grunt.initConfig({
run: {
options: {
// ...
},
npm_test_jest: {
exec: 'npm run test-jest --silent' // <-- use the exec key.
}
}
});
grunt.registerTask('default', [ 'run:npm_test_jest' ]);
};

Related

Passing parameters from Jenkins CI to npm script

When I run Jenkins build, I would like to pass COMMIT_HASH and BRANCH_NAME to one of my javascript files: publish.js, so that I can remove hard-coded values for tags and consumerVersion.
Here is my code:
Jenkinsfile
stage('Publish Pacts') {
steps {
script {
sh 'npm run publish:pact -Dpact.consumer.version=${COMMIT_HASH} -Dpact.tag=${env.BRANCH_NAME}'
}
}
}
package.json
"scripts": {
"publish:pact": "node ./src/test/pact/publish.js"
}
./src/test/pact/publish.js
let publisher = require('#pact-foundation/pact-node');
let path = require('path');
let opts = {
providerBaseUrl: `http://localhost:${global.port}`,
pactFilesOrDirs: [path.resolve(process.cwd(), 'pacts')],
pactBroker: 'http://localhost:80',
tags: ["prod", "test"], // $BRANCH_NAME
consumerVersion: "2.0.0" // $COMMIT_HASH
};
publisher.publishPacts(opts).then(() => {
console.log("Pacts successfully published");
done()
});
Does anyone know how to do this?
You can pass cli arguments to your node script which end up in your process.argv.
Also npm passes on cli arguments via two dashes --.
To illustrate this consider this example:
Jenkinsfile
stage('Publish Pacts') {
steps {
script {
sh 'npm run publish:pact -- ${COMMIT_HASH} ${env.BRANCH_NAME}'
}
}
}
package.json
"scripts": {
"publish:pact": "node ./src/test/pact/publish.js"
}
publish.js
// process.argv[0] = path to node binary
// process.argv[1] = path to script
console.log('COMMIT_HASH:',process.argv[2]);
console.log('BRANCH_NAME:',process.argv[3]);
I left the cli flags out for simplicity.
Hope this helps

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.

jenkins pipeline nodeJs

My JenkinsFile script started throwing npm not found error. (it is working for maven but failing at npm)
pipeline {
environment {
JENKINS='true'
}
agent any
stages{
stage('change permissions') {
steps {
sh "chmod 777 ./mvnw "
}
}
stage('clean') {
steps {
sh './mvnw clean install'
}
}
stage('yarn install') {
steps{
sh 'npm install -g yarn'
sh 'yarn install'
}
}
stage('yarn webpack:build') {
steps {
sh 'yarn webpack:build'
}
}
stage('backend tests') {
steps {
sh './mvnw test'
}
}
stage('frontend tests') {
steps {
sh 'yarn test'
}
}
}
}
To fix that
I am trying to setup NodeJs on my jenkins node. I installed the nodejs plugin and wrote the script
pipeline {
agent any
stages {
stage('Build') {
steps {
nodejs(nodeJSInstallationName: 'Node 6.x', configId: '<config-file-provider-id>') {
sh 'npm config ls'
}
}
}
}
}
as shown in the https://wiki.jenkins.io/display/JENKINS/NodeJS+Plugin
I also setup nodejs on global tools config
I also tried the solution in the installing node on jenkins 2.0 using the pipeline plugin
and it throws
Expected to find ‘someKey "someValue"’ # line 4, column 7.
node {
error.
but I am still getting npm not found error on jenkins. I am new to jenkins so any help is appreciated.
Thanks in advance
I was able to fix the issues. Followed the following link and was able to fix the issue. https://medium.com/#gustavo.guss/jenkins-starting-with-pipeline-doing-a-node-js-test-72c6057b67d4
Its a puzzle. ;)
Has a little reference trick.
You need to configure your jenkins to see your nodejs config name.
At Global Tool Configuration, you need define your node config name. It has reference to your Jenkinsfile reference.
Look an Jenkingsfile adapted example with reference:
pipeline {
agent any
tools {nodejs "node"}
stages {
stage('Cloning Git') {
steps {
git 'https://github.com/xxxx'
}
}
stage('Install dependencies') {
steps {
sh 'npm i -save express'
}
}
stage('Test') {
steps {
sh 'node server.js'
}
}
}
}
Complete case to study: Post at Medium by Gustavo Apolinario
Hope it helps!
If you need different version of Node.js and npm, you can install NodeJS plugin for Jenkins.
Go to Manage Jenkins -> Global tools configuration and find NodeJS section.
Select the version you need and name it as you prefer. You can also add npm packages that needs to be installed globally.
In a declarative pipeline, just reference the correct version of node.js to use:
stage('Review node and npm installations') {
steps {
nodejs(nodeJSInstallationName: 'node13') {
sh 'npm -v' //substitute with your code
sh 'node -v'
}
}
}
Full example here: https://pillsfromtheweb.blogspot.com/2020/05/how-to-use-different-nodejs-versions-on.html

Access command line argument in scripts of package.json

I have created a command in package.json file
"create": "ng g component process.env.page --it false"
Now I want to access the passed page argument in the above command so that user can pass the component name to the npm command
I am running the above command as
npm run create --page login
and this runs the
ng g component process.env.page --it false
so new component is created with name process.
How can I access the passed page (login) in my script?
You can use the primitive process.argv or yargs which is a lot more powerful
here is a yargs example
const argv = require("yargs").argv;
let page = argv.page //get the page
The syntax of npm run is:
npm run <command> [-- <args>]
So you need to pass -- before your args. Your command should be as follows:
npm run create -- --page login
const minimist = require('minimist');
let args = minimist(process.argv.slice(2), {
default: {
port: 8080
},
});
run with
npm run start -- --port=8090
args contains
args: { _: [], port: 8090 }

Grunt and NPM, package all production dependencies

I am unsure when the way the NPM installs dependencies changed.
In the past I remember that if in my project.json I had a dependency on "abc", which in turn would depend on "xyz", a npm install would result in something like:
package.json
node_modules/
abc/
node_modules/
xyz/
some-dev-dep/
When packaging my node project to be used by AWS Lambda, I would have to include that node_modules structure (less any dev-dependencies that were there). I would use Grunt for my packaging, so I wrote this handy thing to help me get all production dependencies into this zip (extracting part of my gruntfile.js):
function getDependencies(pkg) {
return Object.keys(pkg.dependencies)
.map(function(val) { return val + '/**'; });
}
var config = {
compress: {
prod: {
options: {
archive: 'public/lambda.zip'
},
files: [
{ src: 'index.js', dest: '/' },
{ expand: true, cwd: 'node_modules/', src: getDependencies(pkg), dest: '/node_modules' }
]
}
}
};
This would work because dependencies of my dependencies were nested.
Recently (or maybe not-so-recently) this has changed (I am unsure when as I was using very old version of NPM and updated it recently).
Now if I depend on "abc" which in turn depends on "xyz" I will get:
node_modules/
abc/
xyz/
some-dev-dep/
As you can see, my way of getting only production dependencies just won't work.
Is there any easy way to get only list of production dependencies (together with sub-dependencies) within grunt job?
I could do it using recursive function scanning for my dependencies, and then checking project.json files of those and then searching for sub-dependencies etc. This approach seems like a lot of hassle that is possibly a common scenario for many projects...
Here is a function that returns an array of the production dependency module names. (Note: you might need to have the 'npm' module installed locally in your project for this to work.)
/**
* Returns an array of the node dependencies needed for production.
* See https://docs.npmjs.com/cli/ls for info on the 'npm ls' command.
*/
var getProdDependencies = function(callback) {
require('child_process').exec('npm ls --prod=true --parseable=true', undefined,
function(err, stdout, stderr) {
var array = stdout.split('\n');
var nodeModuleNames = [];
array.forEach(function(line) {
var index = line.indexOf('node_modules');
if (index > -1) {
nodeModuleNames.push(line.substr(index + 13));
}
});
callback(nodeModuleNames);
});
};
This change was introduced with the release of npm 3 (see npm v3 Dependency Resolution).
It's not exactly clear why you need to use Grunt at all. If what you want to do is get only production dependencies you can simply run:
npm install --production
With the --production flag, all dev dependencies will be ignored. The same is also true if the NODE_ENV environment variable is set to 'production'.

Resources