Cross platform NPM start script - npm

I'm building out an Electron app that will be developed by folks on both Windows and OS X. I'd like to create a cross-platform start script. So far, I've had exactly zero luck getting something that works. The issue, I think, is that I need to set the NODE_ENV environment variable and the syntax is slightly different.
I'm hoping there's a way that I just haven't found yet. My current scripts section follows:
"scripts": {
"start:osx": "NODE_ENV=development electron ./app/",
"start:win": "set NODE_ENV=development && electron ./app/"
}
I'd really like to create a single "start" script and eliminate the platform-specific variants. Is it possible?

Environment variables are a problem in Windows.
As stated Domenic Denicola (one of the main contributors to npm) :
This is not npm's job. You can run custom Node scripts to set environment variables using process.env if you'd like, or use something that isn't environment variables (like JSON).
...
You can write custom scripts to work around connect's limitations, e.g. in your tests modify process.env.
(Reference : this issue)
So we'll manage through a JS script (Solution inspired on this commit) :
Create a exec.js file in a scripts directory
Copy the following code in exec.js :
var exec = require('child_process').exec;
var command_line = 'electron ./app/';
var environ = (!process.argv[2].indexOf('development')) ? 'development' : 'production';
if(process.platform === 'win32') {
// tricks : https://github.com/remy/nodemon/issues/184#issuecomment-87378478 (Just don't add the space after the NODE_ENV variable, just straight to &&:)
command_line = 'set NODE_ENV=' + environ + '&& ' + command_line;
} else {
command_line = 'NODE_ENV=' + environ + ' ' + command_line;
}
var command = exec(command_line);
command.stdout.on('data', function(data) {
process.stdout.write(data);
});
command.stderr.on('data', function(data) {
process.stderr.write(data);
});
command.on('error', function(err) {
process.stderr.write(err);
});
Update your package.json :
"scripts": {
"start": "node scripts/exec.js development",
}
Run npm script : npm run start
Edit 05.04.2016
There is a very useful npm package that allows manages this problem : cross-env. Run commands that set environment variables across platforms

Related

Accessing command line arguments to npm script

This is what i have in my package.json file, in the scripts section:
{
"version": "0.0.1",
},
"scripts": {
"dev-wip": "environment=DEV --tags=#wip"
}
This is how my command line:
npm run dev-wip
I have no problem accessing variables in the package.json file, by doing this:
var pjson = require('../../../package.json');
console.log('version - ' + pjson.version);
I want to be able to access the environment parameter (print the value, for example) that i got from the command line.
I found the solution.
Using this will give my the value of the "environment" parameter (which is "DEV" in my example).
process.env.environment

Environment variable in Vercel redirects

I want to deploy my create-react-app to Vercel.
I define my redirects in my now.json as follows:
{
"redirects": [
{ "source": "/api/(.*)", "destination": "BACKEND_URL/$1", "statusCode": 200 }
]
}
The destination URL depends on the environment variable BACKEND_URL, which is defined in the Vercel dashboard.
I am trying to replace the environment variable in the redirects in the following build command:
sed -i "s|BACKEND_URL|${BACKEND_URL}|g" now.json && yarn build
But unfortunately now.json doesn't seem to be available at build time:
09:54:44.243 sed: can't read now.json: No such file or directory
How to enable dynamic redirects in Vercel?
This is not possible since now.json is read to determine how to build so you can't dynamically generate it during a build.
Instead, consider using a framework like Next.js which provides a next.config.js that can read environment variables as defined in RFC 9081.
npm install next#canary react react-dom
// next.config.js
module.exports = {
experimental: {
async redirects() {
return [{
source: "/api/:path*",
destination: `${process.env.BACKEND_URL}/:path*`,
permanent: false,
}]
}
}
};
https://github.com/zeit/now/discussions/4351
I think this actually is possible.
If you are using create-react-app then you just need to preface your env var name with REACT_APP_. See here: https://create-react-app.dev/docs/adding-custom-environment-variables/
I am currently doing this with a websocket URL env var. I have named it REACT_APP_WS_URL and here is how I use it.
I have 2 different vercel files in the project:
vercel.staging.json which has this section:
"build": {
"env": {
"REACT_APP_WS_URL": "wss:staging.my-backend.com/socket"
}
}
vercel.live.json which has this section:
"build": {
"env": {
"REACT_APP_WS_URL": "wss:my-backend.com/socket"
}
}
I deploy to them with either of these commands:
vercel deploy --prod -A vercel.staging.json
vercel deploy --prod -A vercel.live.json
and in my code I can access process.env.REACT_APP_WS_URL anywhere.
I have not tried doing this with the Vercel dashboard env vars but it might be worth trying your original approach except rename your env var to REACT_APP_BACKEND_URL.
Note: my deployment commands only work when I don't assign domains to the project. If I assign domains to a project, they are automatically used for ALL --prod deploys, no matter what is in my alias field in the json config file.

How to modify npm scripts in package.json for Windows [duplicate]

issue:
in script:
we want to check env. variable {dev/test/mock} and do following script run based on it.
if $mock is true the run script start-mock else go on reach real test server
scenario 1:
we added commands aggregated in package.json script section
e.g. : "test": "export NODE_ENV=dev; grunt", [on linux]
which is "test": "(SET NODE_ENV=dev) & (grunt)", [on win32]
scenario 2:
could be bat/sh script sitting in package and we called them out from package.json
scenario 3: (permanent solution)
not sure if its already available out there
something like
get arguments from script section: to give flexibility and freedom to end user.
e.g. : "test": "solution.env NODE_ENV=dev; solution grunt"
where we can have script to process (input with process.platform) out put depends on OS.
"start-pm2": "if \"%MOCK%\" == \"true\" ( npm run mock & pm2 start process.json --env test ) else ( pm2 start process.json )", [windows] for linux if.. fi
Use: run-script-os
For example:
// from pacakge.json
"scripts": {
// ...
"dist": "run-script-os",
"dist:win32": "tar -C dist -cvzf %npm_package_name%-%npm_package_version%.tgz .",
"dist:linux": "tar -C dist -cvzf $npm_package_name-$npm_package_version.tgz ."
},
Lets consider implementation of 3-th solution like e.g.
package.json
"scripts": {
"command" : "node bin/command.js"
}
bin/command.js
const spawn = require("child_process").spawn
const platform = require("os").platform()
const cmd = /^win/.test(platform)
? `${process.cwd()}\\bin\\command.bat`
: `${process.cwd()}/bin/command.sh`
spawn(cmd, [], { stdio: "inherit" }).on("exit", code => process.exit(code))
depends on environments script will execute command.bat or command.sh
You will need to implement solution 3.
You can use cross-env package that does it for you.

Bundle npm module 'cheerio' in K6 test

I am trying to create some tests using K6 framework from LoadImpact, but I am struggelig with including external NPM module following the instructions on their documentation site.
On loadImpacts documentations site they include a detailed example on just what I am after, modules that enable me to parse xml from a soap service response. But, I am unable to get this working! Now, I am a total javascript newbie, but I have been coding for many years and would really like to solve this.
The can be found here: https://docs.k6.io/docs/modules#section-npm-modules
can anyone get this working? I need to run this on servers isolated from the Internet, so I am totaly dependent on creating the packages and transfer the required files.
According to the documentation a package is created like this
-- bundle `cheerio` npm module
git clone git#github.com:cheeriojs/cheerio.git
npm install browserify index.js -s cheerio > cheerio.js
My first question: In the folder I am residing when running this command a 'cheerio.js' file is created along with a a 'cheerio' folder and a 'node_modules' folder.
the cheerio.js in my "root" directory only contains the following:
+ cheerio#0.22.0
+ index.js#0.0.3
+ browserify#16.2.3
updated 3 packages and audited 2829 packages in 2.221s
found 0 vulnerabilities
Back to LoadImpacts example on how to reference this package in a k6 javascript:
import cheerio from "./vendor/cheerio.js";
export default function()
{
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
What file is this, and where in the structure generated by browserify can I find it? I have tried to change this to point to 'index.js' in the 'cheerio' folder or cheerio.js found in 'cheerio/lib'. I will then receive a complaint about the first line in cheerio.js which defines a "parse" variable it cannot find:
var parse = require("./parse'),
if I change this to
var parse = require("./parse.js')
it goes on to complain about missing 'htmlparser2' which I can also find in this structure, but it seems like the entire dependency structure is not working.
Can anybody give me some guidance on how to create a browserify package with dependencies for cheerio and how/what I need to copy to my k6 project to make this work like on the loadImpact site.
The k6 docs for this definitely need some clarification, which I'll later do. The vendor folder currently mentioned there isn't something special, the docs are just missing a step to copy the cheerio.js and xml2js.js files that were generated by browserify to a new vendor folder in your k6 project.
For now, I'll try to offer a simplified explanation on how to achieve the same thing in a simpler way:
Create a new empty folder and go to it in a terminal
Run npm install browserify cheerio there (ignore the npm warnings about missing package.json or description)
Run ./node_modules/.bin/browserify ./node_modules/cheerio/ -s cheerio > cheerio.js in that folder
The resulting cheerio.js file in the folder root should be the file you import from the k6 script:
import http from "k6/http";
import cheerio from "./cheerio.js";
export default function () {
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
console.log($('head title').text())
}
That should be it for a single npm library.
And if you need to use multiple npm packages, it might be better to invest some time into bundling them in a single browserified .js file. For example, if you need both the cheerio and the xml2js libraries mentioned in the k6 docs, you can do something like this:
Create a new empty folder
Add something like the following package.json file in it:
{
"name": "k6-npm-libs-demo",
"version": "0.0.1",
"description": "just a simple demo of how to use multiple npm libs in k6",
"main": "npm-main.js",
"dependencies": {},
"devDependencies": {
"browserify": "*",
"cheerio": "*",
"xml2js": "*"
},
"scripts": {
"install": "./node_modules/.bin/browserify npm-main.js -s npmlibs > vendored-libs.js"
},
"author": "",
"license": "ISC"
}
Of course, if you need different libraries than cheerio and xml2js, you need to adjust the devDependencies options.
Add an npm-main.js file like this (again, adjusting for the libraries you want):
exports.xml2js = require('xml2js');
exports.cheerio = require('cheerio');
Open that folder in a terminal and run npm install. That should result in the creation of a vendored-libs.js file in the root of the folder, which you can use in k6 like this:
import http from "k6/http";
import { cheerio, xml2js } from "./vendored-libs.js";
export default function () {
const res = http.get("https://loadimpact.com/");
const $ = cheerio.load(res.body);
console.log($('head title').text())
var xmlString = '<?xml version="1.0" ?>' +
'<items xmlns="http://foo.com">' +
' <item>Foo</item>' +
' <item color="green">Bar</item>' +
'</items>'
xml2js.parseString(xmlString, function (err, result) {
console.log(JSON.stringify(result));
});
}

How do I loop through files in npm in a way that works on Windows & linux?

I'm trying to run a single command (jshint), on multiple files. My package.json contains
"lint": "jshint *.js **/*.js"
However this fails miserable on Windows. On Windows the syntax to iterate on multiple files is
for %%f in (*.in) do (
echo %%~nf
)
Is there a simple, platform-agnostic way to run a single npm script (e.g. jshint) on multiple files?
(I'm interested in the general solution. There's a references here to using node-jslint instead of jshint, which does support multiple files ... but IMO jshint >> jslint).
I'm also not aware of a platform agnostic way to loop in the shell.
However, a platform agnostic solution to running a single npm-script on multiple files with jshint, as per your example, is to:
Utilize cli-glob to find the .js files.
Pipe the results/paths from the globbing pattern to a custom utility node script.
Then within the node script:
Read the paths piped to stdin using nodes readline module.
Create an Array of each path and subsequently convert that to a String.
Run the jshint executable, (including the String of all paths), using nodes child_process.exec() module.
Whilst this solution is not particularly "simple", the following gists demonstrate this process:
npm-script
"scripts": {
"jshint": "glob \"src/js/**/*.js\" | node .scripts/jshint.js"
},
Note cli-glob, (added to package.json), obtains the paths and pipes them to jshint.js.
jshint.js
#!/usr/bin/env node
'use strict';
var path = require('path');
var readline = require('readline');
var exec = require('child_process').exec;
var rl = readline.createInterface({
input: process.stdin,
output: null,
terminal: false
});
// Normalise path to local jshint executable.
var jshintExec = ['.', 'node_modules', '.bin', 'jshint '].join(path.sep);
var paths = [];
function jshint(paths) {
var command = [jshintExec, paths].join('');
exec(command, function(error, stdout, stderr) {
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.log(stderr);
}
});
}
rl.on('line', function(srcPath) {
paths.push(srcPath);
});
rl.on('close', function() {
jshint(paths.join(' '));
});
Note
Line 16 reading:
var jshintExec = ['.', 'node_modules', '.bin', 'jshint '].join(path.sep);
The script assumes jshint has been installed locally and added to the "devDependencies": {} section of the package.json. I.e. Its pointing to the local jhint executable found in the node_modules/.bin folder and not the globally installed one.
If your preference is to run the globally installed jshint then change line 16 to:
var jshintExec = 'jshint ';
Personally, having it installed locally is the preferred option IMHO for this scenario!
Multiple globbing patterns
Your example provided includes multiple glob patterns.
"lint": "jshint *.js **/*.js"
One limitation of cli-glob is that it doesn't accept multiple glob patterns. So one workaround is to do something like this:
"scripts": {
"jshint": "npm run jshint-a && npm run jshint-b",
"jshint-a": "glob \"*.js\" | node .scripts/jshint.js",
"jshint-b": "glob \"**/*.js\" | node .scripts/jshint.js"
},
Yeah, not particularly terse - but works!
To the best of my knowledge, you can't loop in the shell in a cross-platform way.
However, you can use https://www.npmjs.com/package/catw and do something like this:
catw "**/*.js" | jshint -
catw will expand the glob(s) itself without relying on the shell and write the files to stdout. jshint - will read from stdin. The pipe (|) works cross-platform.