Check file exists before launch it in webpack npm scripts - node.js

I have a package.json like this:
...
"scripts": {
"dev": "webpack --config webpack.dev.config.js --mode development --progress --colors",
"postdev": "if (Test-Path \"./postdev.sh\" ) { echo \"file exists\"; ./postdev.sh }"
},
...
How can I check if file "postdev.sh" exists and then launch it in NPM-scripts section?
I run that command in the terminal and it goes correctly, but if I try to launch that npm-script it says "Unexpected appearance: "./postdev.sh"."

on macos or linux try this one for postdev:
"postdev": "test -f ./postdev.sh && echo 'file exisits' && ./postdev.sh",

Finnally found a solution (maybe it works only on Windows, but it is enough for me):
"postdev": "if exist postdev.sh ( postdev.sh )",

You can use path-exists-cli package, a cross-platform tool, to check if a file/directory exists and use && or || after to run the next command if exists or not, respectively:
{
"scripts": {
// other scripts...
"postdev": "path-exists ./postdev.sh && echo 'Exists' || echo 'Does not exists'"
}
}

Related

Propagate arguments to two commands combined with &&

I have two commands in package.json combined with '&&':
"scripts": {
"someAction": "node dist/scripts/actionOne && node -r dist/scripts/actionTwo"
},
Is it possible to call this script from cli, passing arguments to both 'actionOne' and 'actionTwo' ?
When calling
npm run someAction -- firstArg, secondArg args are passed only to 'actionOne' script.
*Number of args expected by actionOne and actionTwo are identical.
After looking at the docs, it looks like npm-run-all would work with argument placeholders.
We can use placeholders to give the arguments preceded by -- to scripts.
$ npm-run-all build "start-server -- --port {1}" -- 8080
This is useful to pass through arguments from npm run command.
{
"scripts": {
"start": "npm-run-all build \"start-server -- --port {1}\" --"
}
}
$ npm run start 8080
> example#0.0.0 start /path/to/package.json
> npm-run-all build "start-server -- --port {1}" -- "8080"
So you could do something like this:
{
"scripts": {
"start": "npm-run-all dist/scripts/actionOne -- --arg {1} && dist/scripts/actionTwo -- --arg2 {2}"
}
}
Then:
npm run start arg1 arg2
If there is a real answer, I wanna know. But also, you can make a script like this
some-action.sh
set -e
dist/scripts/ActionOne $#
dist/scripts/ActionTwo $#
and then put this in your package.json
"scripts": {
"someAction": "bash some-action.sh"
},

Compare proccess.env.NODE_ENV with String

Why when I compare those two I get false
const production = process.env.NODE_ENV === 'production';
Even when I set process.env.NODE_ENV to production I still get false value.
Why?
Example:
package.json SCRIPTS:
"scripts": {
"start:prod": "set NODE_ENV=production && nodemon server.js"
}
VS CODE
const production = process.env.NODE_ENV === 'production';
console.log(process.env.NODE_ENV); // production
console.log(typeof process.env.NODE_ENV); // string
console.log(typeof 'production'); // string
console.log(production) // false
Why production returns false even though the values are exactly the same?
To set node environment in powershell use below:
$env:NODE_ENV = 'production'
drop the spaces before and after &&
"scripts": {
"start:prod": "set NODE_ENV=production&&nodemon server.js"
}
You should do something like this
"scripts": {
"start:prod": "NODE_ENV=production nodemon server.js"
}
set command is used to set or unset values of shell options and positional parameters and not the environment variables
I met the similar issue and it took me a couple of hours to realize this.
Your package.json has this script
"scripts": {
"start:prod": "set NODE_ENV=production && nodemon server.js"
}
However you should not that your comparison should return false becouse your env you set in the script contains space here production &&
To be able to return true you should remove that space and your script should look like this.
"scripts": {
"start:prod": "set NODE_ENV=production&& nodemon server.js"
}
Keep your comparison expression it is ok for it.
It works if you use includes instead of the equality operator
process.env.NODE_ENV.includes('production') // true

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.

How to fix node.js using sails error: Failed to lift app

I’m trying to deploy a node.js app using the framework Sails in production mode.
I’m using the command npm start which runs NODE_ENV=production node app.js. In package.json I have the following content:
{
"name": "myapp",
"private": true,
"version": "0.0.0",
"description": "a Sails application",
"keywords": [],
"dependencies": {
"sails": "^1.2.3",
"grunt": "1.0.4",
"sails-hook-apianalytics": "^2.0.3",
"sails-hook-grunt": "^3.0.2",
"sails-hook-organics": "^0.16.0",
"sails-hook-orm": "^2.1.1",
"sails-hook-sockets": "^2.0.0",
"#sailshq/connect-redis": "^3.2.1",
"#sailshq/socket.io-redis": "^5.2.0",
"#sailshq/lodash": "^3.10.3"
},
"devDependencies": {
"eslint": "5.16.0",
"htmlhint": "0.11.0",
"lesshint": "6.3.6",
"#sailshq/eslint": "^4.19.3",
"sails-hook-grunt": "^4.0.0"
},
"scripts": {
"start": "NODE_ENV=production node app.js",
"test": "npm run lint && npm run custom-tests && echo 'Done.'",
"lint": "./node_modules/eslint/bin/eslint.js . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look so good.' && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/**/**/**/**/*.ejs && echo '✔ So do your .ejs files.' && ./node_modules/lesshint/bin/lesshint assets/styles/ --max-warnings=0 && echo '✔ Your .less files look good, too.'",
"custom-tests": "echo \"(No other custom tests yet.)\" && echo",
"deploy": "echo 'This script assumes a dead-simple, opinionated setup on Heroku.' && echo 'But, of course, you can deploy your app anywhere you like.' && echo '(Node.js/Sails.js apps are supported on all modern hosting platforms.)' && echo && echo 'Warning: Specifically, this script assumes you are on the master branch, and that your app can be deployed simply by force-pushing on top of the *deploy* branch. It will also temporarily use a local *predeploy* branch for preparing assets, that it will delete after it finishes. Please make sure there is nothing you care about on either of these two branches!!!' && echo '' && echo '' && echo 'Preparing to deploy...' && echo '--' && git status && echo '' && echo '--' && echo 'I hope you are on the master branch and have everything committed/pulled/pushed and are completely up to date and stuff.' && echo '********************************************' && echo '** IF NOT THEN PLEASE PRESS <CTRL+C> NOW! **' && echo '********************************************' && echo 'Press CTRL+C to cancel.' && echo '(you have five seconds)' && sleep 1 && echo '...4' && sleep 1 && echo '...3' && sleep 1 && echo '...2' && sleep 1 && echo '...1' && sleep 1 && echo '' && echo 'Alright, here we go. No turning back now!' && echo 'Trying to switch to master branch...' && git checkout master && echo && echo 'OK. Now wiping node_modules/ and running npm install...' && rm -rf node_modules && rm -rf package-lock.json && npm install && (git add package-lock.json && git commit -am 'AUTOMATED COMMIT: Did fresh npm install before deploying, and it caused something relevant (probably the package-lock.json file) to change! This commit tracks that change.' || true) && echo 'Deploying as version:' && npm version patch && echo '' && git push origin master && git push --tags && (git branch -D predeploy > /dev/null 2>&1 || true) && git checkout -b predeploy && (echo 'Now building+minifying assets for production...' && echo '(Hang tight, this could take a while.)' && echo && node node_modules/grunt/bin/grunt buildProd || (echo && echo '------------------------------------------' && echo 'IMPORTANT! IMPORTANT! IMPORTANT!' && echo 'ERROR: Could not compile assets for production!' && echo && echo 'Attempting to recover automatically by stashing, ' && echo 'switching back to the master branch, and then ' && echo 'deleting the predeploy branch... ' && echo && echo 'After this, please fix the issues logged above' && echo 'and push that up. Then, try deploying again.' && echo '------------------------------------------' && echo && echo 'Staging, deleting the predeploy branch, and switching back to master...' && git stash && git checkout master && git branch -D predeploy && false)) && mv www .www && git add .www && node -e 'sailsrc = JSON.parse(require(\"fs\").readFileSync(\"./.sailsrc\", \"utf8\")); if (sailsrc.paths&&sailsrc.paths.public !== undefined || sailsrc.hooks&&sailsrc.hooks.grunt !== undefined) { throw new Error(\"Cannot complete deployment script: .sailsrc file has conflicting contents! Please throw away this midway-complete deployment, switch back to your original branch (master), remove the conflicting stuff from .sailsrc, then commit and push that up.\"); } sailsrc.paths = sailsrc.paths || {}; sailsrc.paths.public = \"./.www\"; sailsrc.hooks = sailsrc.hooks || {}; sailsrc.hooks.grunt = false; require(\"fs\").writeFileSync(\"./.sailsrc\", JSON.stringify(sailsrc))' && git commit -am 'AUTOMATED COMMIT: Automatically bundling compiled assets as part of deploy, updating the EJS layout and .sailsrc file accordingly.' && git push origin predeploy && git checkout master && git push origin +predeploy:deploy && git push --tags && git branch -D predeploy && git push origin :predeploy && echo '' && echo '--' && echo 'OK, done. It should be live momentarily on your staging environment.' && echo '(if you get impatient, check the Heroku dashboard for status)' && echo && echo 'Staging environment:' && echo ' 🌐–• https://staging.example.com' && echo ' (hold ⌘ and click to open links in the terminal)' && echo && echo 'Please review that to make sure it looks good.' && echo 'When you are ready to go to production, visit your pipeline on Heroku and press the PROMOTE TO PRODUCTION button.'"
},
"main": "app.js",
"repository": {
"type": "git",
"url": "git://github.com/xxxxxx.git"
},
"author": "me",
"license": "",
"engines": {
"node": "^10.16"
}
}
If I run sails lift all gone right.
And when run npm start I get following error:
error: Failed to lift app: Error: Should not specify a trailing slash, but instead got: https://myapp.herokuapp.com/
at checkOriginUrl (/home/me/Documents/prog/myapp/node_modules/sails-hook-sockets/lib/util/check-origin-url.js:57:40)
at /home/me/Documents/prog/myapp/node_modules/sails-hook-sockets/lib/configure.js:86:9
at arrayEach (/home/me/Documents/prog/myapp/node_modules/#sailshq/lodash/lib/index.js:1470:13)
at Function.<anonymous> (/home/me/Documents/prog/myapp/node_modules/#sailshq/lodash/lib/index.js:3532:13)
at Hook.configure (/home/me/Documents/prog/myapp/node_modules/sails-hook-sockets/lib/configure.js:85:9)
at Hook.wrapper [as configure] (/home/me/Documents/prog/myapp/node_modules/#sailshq/lodash/lib/index.js:3282:19)
at /home/me/Documents/prog/myapp/node_modules/sails/lib/app/private/loadHooks.js:331:20
at /home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:3083:16
at eachOfArrayLike (/home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:1003:9)
at eachOf (/home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:1051:5)
at Object.eachLimit (/home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:3145:5)
at configure (/home/me/Documents/prog/myapp/node_modules/sails/lib/app/private/loadHooks.js:328:17)
at /home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:3853:24
at replenish (/home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:946:17)
at iterateeCallback (/home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:931:17)
at /home/me/Documents/prog/myapp/node_modules/sails/node_modules/async/dist/async.js:906:16
I’m trying to use heroku to deploy the app. For this reason I added this in production.js inside sockets object:
onlyAllowOrigins:[
'https://myapp.herokuapp.com/',
],
I think this problem could be caused by any missed configuration in production.js but I couldn’t identify what.

How to use environment variables in package.json

Because we don't want sensitive data in the project code, including the package.json file, using environment variables would be a logical choice in my opinion.
Example package.json:
"dependencies": {
"accounting": "~0.4.0",
"async": "~1.4.2",
"my-private-module":"git+https://${BB_USER}:${BB_PASS}#bitbucket.org/foo/bar.git"
Is this possible?
The question is not if this is wise or not good, just if it's possible.
In case you use .env file, let's use grep or eval to get a value environment variable from the .env file.
Updated start2 as #Paul suggested:
"scripts": {
"start": "NODE_ENV=$(grep NODE_ENV .env | cut -d '=' -f2) some_script",
"start2": "eval $(grep '^NODE_ENV' .env) && some_script"
}
I have similar but different requirement. For me, I want to use environment variables in the scripts.
Instead of using the environment variables directly in package.json, I do:
"some-script": "./scripts/some-script.sh",
And in some-script.sh:
#!/bin/sh
npm run some-other-script -- --prop=$SOME_ENV_VAR
Here's how I managed to work around package.json to achieve the same purpose. It uses a script that reads from a custom section of package.json for URL modules, interpolates environment variables in them, and installs them with npm install --no-save (the --no-save could be omitted, depending on the usecase).
As a bonus: it tries to read the env variable from .env.json, which can be gitignore'd, and very useful for development.
Create a script that will read from a custom section of package.json
env-dependencies.js
const execSync = require('child_process').execSync
const pkg = require('./package.json')
if (!pkg.envDependencies) {
return process.exit(0)
}
let env = Object.assign({}, process.env)
if (typeof pkg.envDependencies.localJSON === 'string') {
try {
Object.assign(env, require(pkg.envDependencies.localJSON))
} catch (err) {
console.log(`Could not read or parse pkg.envDependencies.localJSON. Processing with env only.`)
}
}
if (typeof pkg.envDependencies.urls === 'undefined') {
console.log(`pkg.envDependencies.urls not found or empty. Passing.`)
process.exit(0)
}
if (
!Array.isArray(pkg.envDependencies.urls) ||
!(pkg.envDependencies.urls.every(url => typeof url === 'string'))
) {
throw new Error(`pkg.envDependencies.urls should have a signature of String[]`)
}
const parsed = pkg.envDependencies.urls
.map(url => url.replace(/\${([0-9a-zA-Z_]*)}/g, (_, varName) => {
if (typeof env[varName] === 'string') {
return env[varName]
} else {
throw new Error(`Could not read env variable ${varName} in url ${url}`)
}
}))
.join(' ')
try {
execSync('npm install --no-save ' + parsed, { stdio: [0, 1, 2] })
process.exit(0)
} catch (err) {
throw new Error('Could not install pkg.envDependencies. Are you sure the remote URLs all have a package.json?')
}
Add a "postinstall": "node env-dependencies.js" to your package.json, that way it will be run on every npm install
Add your private git repos to package.json using the URLs you want (note: they all must have a package.json at root!):
"envDependencies": {
"localJSON": "./.env.json",
"urls": [
"git+https://${GITHUB_PERSONAL_ACCESS_TOKEN}#github.com/user/repo#semver:^2.0.0"
]
},
(the semver specifier #semver:^2.0.0 can be omitted, but refers to a git tag, which can be very useful, as it makes your git server a fully-fledge package manager)
npm install
No, it's not possible. You should access the repo using git+ssh, and store a private key in ~/.ssh.
Your line then looks like:
"my-private-module":"git+ssh://git#bitbucket.org/foo/bar.git"
Which doesn't contain anything sensitive.
No it isn't possible as npm does not treat any string values as any kind of templates.
It may be better to just use git+ssh (if your provider supports it) with an ssh agent.
You can use environment values to inject in your package.json like this:
Any environment variables that start with npm_config_ will be interpreted as a configuration parameter. For example, putting npm_config_foo=bar in your environment will set the foo configuration parameter to bar. Any environment configurations that are not given a value will be given the value of true. Config values are case-insensitive, so NPM_CONFIG_FOO=bar will work the same.
https://docs.npmjs.com/misc/config#environment-variables
I had the same need and my solution was based on #Long Nguyen's response. This way, I can only rely on what's defined on the .env file.
.env
...
SKIP_PREFLIGHT_CHECK=true
...
package.json
...
"scripts": {
"test": "yarn cross-env $(grep SKIP_PREFLIGHT_CHECK ../../.env) react-app-rewired test --watchAll=false"
}
...
You can install package https://www.npmjs.com/package/env-cmd
and all your envs from .env file will be visible
ie:
./.env:
ENV1=THANKS
ENV2=FOR ALL
ENV3=THE FISH
Package.json:
"scripts": {
"test": "env-cmd pact-broker can-i-deploy --broker-token=${ENV1}"
}
or another example from your question:
"my-private-module":"env-cmd git+https://${BB_USER}:${BB_PASS}#bitbucket.org/foo/bar.git"
For complicated environment variables, you can use
https://stedolan.github.io/jq/
to access JSON file (env file at your case)
JSON file could be something like
{
"env" :
{
"username" : "1345345",
"Groups" : [],
"arraytest" : [
{
"yes" : "1",
"no" : "0"
}
]
}
}
so the script could be something like this to access yes value
"scripts": {
"yes": "jq [].arraytest[0].yes?"
}
If you're running node inside a Docker container
Use Docker Compose to inject the env variable
app:
environment:
- NODE_ENV=staging
Run your package.json script from your Dockerfile
CMD [ "npm", "run", "start" ]
Use echo or printenv
"scripts": {
"start": "node -r dotenv/config app.js dotenv_config_path=/run/secrets/$(echo $NODE_ENV)"
"start": "node -r dotenv/config app.js dotenv_config_path=/run/secrets/$(printenv NODE_ENV)"
}
Don't use this for sensitive env variables. It's a really good way to point to a Docker secrets file (like this example shows).

Resources