Set node environment variable to dynamic value in npm script - node.js

I would like to set an environment variable dynamically in an npm script.
I'm using cross-env as I'm developing on Windows and the server is Unix-based. I want to initialize an environment variable with the current date (new Date()) so I can access and render it in my create-react-app:
This works (hard-coded string):
"scripts": {
"start": "cross-env-shell REACT_APP_BUILD_DATE=\"currentDate\" react-scripts-ts start",
}
Obviously, currentDate shouldn't be a string but the result of following expression: new Date().
How can I achieve that? In other words: How can evaluate some regular JavaScript and use its result an npm script? Or is this not possible?

I am using simple node script for passing environment variables into called script. It uses child_process.execSync.
// File name: ./build.js
/* eslint-env node */
const execSync = require('child_process').execSync;
const env = Object.create(process.env);
env.REACT_APP_BUILD_DATE= Date.now();
console.log('Used env variables: ' + JSON.stringify(env));
console.log('Run command: react-scripts start');
execSync('react-scripts-ts start', { env: env, stdio: 'inherit' });
Update start command in package.json scripts. like this:
"scripts": {"start": "node ./build.js"}

Just for the record, I'm now using following approach: Write current date to a custom property in package.json and read that value in the app by importing package.json
package.json
"scripts": {
"start": "react-scripts-ts start",
"build": "node ./update-packagejson.js && react-scripts-ts build"
}
update-packagejson.js
const fs = require("fs");
const filePath = "./package.json";
const packageJson = JSON.parse(fs.readFileSync(filePath).toString());
packageJson.ngrvd.buildDate = new Date().toUTCString();
fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));
Component
import { ngrvd, version } from "../../package.json";
// ...
private static getAppInfo(): string {
const buildDate = process.env.NODE_ENV === "development" ? new Date() : ngrvd.buildDate;
return "Version " + version + " - Built " + moment(buildDate).fromNow();
}
This works on any environment, is simple and understandable and could be extended to also contain other information. When in dev mode I don't write to package.json to prevent having local changes everytime.

For example you want to put build time to your reactjs app. Edit package.json like that:
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) react-app-rewired start",
"build": "REACT_APP_BUILD_TIME=$(date +%s) react-app-rewired build"
}
You can use REACT_APP_BUILD_TIME  variable in public/index.html file. For example:
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico?%REACT_APP_BUILD_TIME%" />
You must wrap env variable with % characters. And there is another rule: you must add REACT_APP_ to your env variable. You can not add other env variables to react app.
How can you add all .env variables to reactjs app?
You can use env-cmd package for this.
yarn add env-cmd
"scripts": {
"start": "REACT_APP_BUILD_TIME=$(date +%s) ./node_modules/.bin/env-cmd react-app-rewired start",
"build": "REACT_APP_BUILD_TIME=$(date +%s) ./node_modules/.bin/env-cmd react-app-rewired build"
}
Example .env content:
REACT_APP_NAME="My React App"
REACT_APP_API_ENDPOINT=https://127.0.0.1:8080/api
REACT_APP_SOCKETIO_ENDPOINT=http://127.0.0.1:3333
After that you can add these variables to your public/index.html file like that:
<script>
window.env.REACT_APP_NAME = "%REACT_APP_NAME%";
window.env.REACT_APP_API_ENDPOINT = "%REACT_APP_API_ENDPOINT%";
window.env.REACT_APP_SOCKETIO_ENDPOINT = "%REACT_APP_SOCKETIO_ENDPOINT%";
</script>
In reactjs side you can use these variables like that:
alert(window.env.REACT_APP_SOCKETIO_ENDPOINT);
That's all.
Edit: Normally there is no this property: window.env but we set this now for easy to use. You can assign your env variables to anywhere in index.html file.

In this particular case you'd be better off using shell command instead of JavaScript, so it should be something like the following:
"scripts": {
"start": "cross-env-shell REACT_APP_BUILD_DATE=$(date '+%F %H:%M:%S') react-scripts-ts start",
}

I'd create a custom javascript script doing it for you:
execute.js
var spawn = require('child_process').spawn;
// because first arg will actually be something like "./execute.js"
// this is the "regular javascript" you want to evaluate
var arg1 = process.argv[1];
// so lets eval it
var res = eval(arg1);
// this is the remaining args, that is the command you want to run (and its args)
var command = process.argv[2];
var commandArgs = process.argv.slice(3);
// if arg1 evaluation resulted in a value, append this value to the list of args
if (res) {
commandArgs.push(res);
}
// execute the command
var prc = spawn(command, commandArgs);
and your script definition will become:
"scripts": {
"start": "cross-env-shell ./execute.js \"process.env.REACT_APP_BUILD_DATE = new Date();\" react-scripts-ts start",
}
Or something similar.
This is untested but should get you started on a solution for "evaluate some regular JavaScript and use its result an npm script"
But if you only want to set a date in a env variable, the solution from #bredikhin is better.
ALTERNATIVE SOLUTION TO DEAL WITH ENVIRONMENT VARIABLES
If you can afford to write into an .envfile at the root of your project (by hand or programatically), you can then use dotenv to populate environment variables with it (from dotenv documentation):
// Usage
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
/* Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
*/
const db = require('db');
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
});

Related

How to pass file file path as arguement to the script?

I want to send the data from
package.json:
"scripts": {
"start": "node list.js",
}
list.js:
const serviceAccount = require(path);
when I execute command npm start ./style.json I wanted to pass the path to to path variable in list.js
How can I do that?
Basically, you need to use process.argv, the third item is ./style.json. inside list.js use this:
const path = process.argv[2]
const serviceAccount = require(path);
console.log(serviceAccount)

How to handle different .env files in Next?

What I want
I have created a new Next project, and I want to manage app behavior according to the NODE_ENV variable. The application must load different variables located in different .env files. eje. if I load NODE_ENV=development, the application should to load the variables located in .env.development file. What is the most efficient and safe way to do it in Next.
What I have
package.json
In the dev script I pass the environment type:
"scripts": {
"dev": "cross-env NODE_ENV=development next",
"build": "next build",
"start": "next start",
},
next.config.js
In the next configuration I load environment variables from correct .env file with dotenv library according to NODE_ENV variable pass in devscript in package.json.
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
webpack: (config) => {
// Returns environment variables as an object
const env = Object.keys(process.env).reduce((acc, curr) => {
acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
return acc;
}, {});
// Allows you to create global constants which can be configured
// at compile time, which in our case is our environment variables
config.plugins.push(new webpack.DefinePlugin(env));
return config;
},
});
.env.development
TITLE=modo development
pages/index.js
function HomePage() {
return <div>{process.env.TITLE}</div>
}
export default HomePage
With this aproach...
This is the most efficient and safe way to handle diferent .env files in Next?
Nextjs supports env by default without the need to use of webpack.DefinePlugin, just pass it to the env property of next.config.js.
So your code will become:
// next.conf.js
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
env: {
VAR_1: process.env.VAR_1
...
// List all the variables that you want to expose to the client
}
});
PAY ATTENTION: these env variables may be exposed to the client side (if you use them in one of your app page).
For example, if your process.env is containing secrets, and by mistake you are using one of them in one of the pages / components that are used by pages, they will be inside js files that are downloaded to the client side.
Since Next.js 9.4, there is a built in .env loading functionality, read about it here https://nextjs.org/docs/basic-features/environment-variables
Agree with the #felixmosh that Nextjs supports env by default without the need to use of webpack.DefinePlugin, but...
It may be seen as limited and confused while loading different configurations on each environment. You can see common problem here
enter link description here
You can solve this problem easily by following these small steps.
You’ll need to create a folder config on the root, with all environment stages you’d like to have.
You can add the initial/common configuration in default.js like this.
API: {
API_URL: process.env.API_URL || '<http://localhost:4000>',
ENDPOINT: '********',
IS_MOCKING_ENABLED: false,
},
}```
Include above config files in the Next.config.js file by publicRuntimeConfig. If you’d like to have it just on the server-side use just serverRuntimeConfig like this.
const APIConfig = config.get('API')
const nextConfig = {
publicRuntimeConfig: {
APIConfig,
},
}
module.exports = nextConfig ```
Usage in any file.
const { publicRuntimeConfig } = getConfig()
const APIConfig = publicRuntimeConfig.APIConfig
[...] ```
Finally, in your package.json. You can inject the environment variables to load the appropriate configuration.
"start:local": "NODE_ENV=development run-p dev"
Reference: enter link description here for detail explanation.

Create two different outputs from the same file in webpack

I need to change the build process of my react application, so that it creates two different outputs of the same file. At this time it only creates a index.js which is minified but i want another output which is basically the same as the index.js but gziped.
Is it possible to change the webpack config to get 2 files (one minified and the other gziped) from the same entry ?
If you want to edit the Webpack config of an app built with create-react-app you have to eject the app, then write your own Webpack config files.. This can be extremely painful, and essentially nullifies (one of) the biggest reasons to use create-react-app..
I think it would be easiest to write a script that does the gzipping for you, after the build is finished.
I put this together real quick to show how you can gzip a file with command line arguments...
How to use: node gzipper.js %SOURCE_FILE_PATH% %DEST_DIR_PATH%
ex: node gzipper.js ./build/index.js ./build
Supports relative paths and absolute paths.
Uses 'standard libraries' - requires no additional packages
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const SOURCE = path.resolve(__dirname, process.argv[2]);
const DESTINATION = path.resolve(__dirname, process.argv[3]);
const SOURCE_STATS = fs.lstatSync(SOURCE);
const SOURCE_FILE_NAME = path.basename(SOURCE);
const DESTINATION_STATS = fs.lstatSync(DESTINATION);
if(!SOURCE_STATS.isFile() || !DESTINATION_STATS.isDirectory()) {
throw new Error("[ERROR]::SOURCE must be a file, DESTINATION must be a directory!");
}
const SOURCE_CONTENTS = Buffer.from(fs.readFileSync(SOURCE, 'utf8'));
zlib.gzip(SOURCE_CONTENTS, (error, result) => {
if(error) throw error;
const OUT_PATH = `${DESTINATION}\\${SOURCE_FILE_NAME}.gz`;
fs.writeFileSync(OUT_PATH, result);
});
You could add a script to your package.json file, something like gzip that you add onto the npm build (or yarn build) script, so it runs after build..:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && npm run gzip",
"gzip": "node gzipper.js ./build/index.js ./build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},

Run tests using different env. variables depending on a command in package.json

Trying to find a common practice solution of how to run tests depending on test command in package.json with corresponding environment variables from .env file.
.env. //file
BASE_URL=dev
BASE_URL=stage
API_DEV=api-dev
API_STG=api-stage
package.json //file
"test:dev": "mocha ...",
"test:stage": "mocha ...",
Launching test:dev I want to fetch for my tests all variables for DEV environment.
Can I configure it like that?
Lets say your package.json contains the following scripts property-
"test:local": "APP_ENV=local mocha ...",
"test:dev": "APP_ENV=dev mocha ...",
"test:stage": "APP_ENV=stage mocha ...",
"test:prod": "APP_ENV=prod mocha ..."
And lets assume you have the following files: .env for prod, .env.stage for stage and .env.dev for dev environment.
Now in your script you can dynamically load the env files using dotenv package.
switch (process.env.APP_ENV) {
case 'dev':
env_suffix = ".dev";
break;
case 'stage':
env_suffix = ".stage";
break;
case 'local':
env_suffix = ".local";
break;
}
require("dotenv").config({
path: path.join(__dirname + "/../", ".env" + env_suffix)
});
In this way you can have the environment variables in your process.env object
Ok, I solved it like that:
In config folder I have
.env
stage.env
prod.env
and have index.js file where:
const {NODE_ENV = 'dev'} = process.env;
const dotenv = require('dotenv').config({path: `${__dirname}/${NODE_ENV}.env`});
module.exports = {
api: {
url: dotenv.parsed.API_BASE || ''
},
elastic: {
host: dotenv.parsed.ELASTIC_HOST || ''
}
};
and in package json I have:
"elastic:dev": "mocha ./test/elasticsearch/*.js",
"elastic:stage": "NODE_ENV=stage mocha ./test/elasticsearch/*.js",
"elastic:prod": "NODE_ENV=prod mocha ./test/elasticsearch/*.js"
Easy and pretty dry way to start tests aka like a boss depending on the environment.

Is it possible to run package.json scripts from a Node script?

I have several task in my package.json like:
"scripts": {
"test": "jest",
"test:ci": "jest --runInBand --no-cache --watch false --coverage true",
"test:codecov": "codecov",
"tsc:check": "tsc --noEmit",
"prettier:check": "pretty-quick --staged"
.
.
. // a lot more here
}
I am trying to build a build script that depends on those tasks but write it as a new script in package.json is too verbose and hard to read.
Is there some way to run those scripts from a build.js file? so I can chain/redo those tasks and also get some error handling.
Based on #anh-nguyen comment I did this initial raw structure on how to be able to do what I wanted I hope this helps somebody.
Notice that instead of process and process.exec I am using shelljs because I already had it as dependency but you could change them if needed.
// tslint:disable:no-string-literal
const shell = require('shelljs');
const path = require('path');
const rootDir = process.cwd();
const distBundlesDir = path.join(rootDir, 'dist-bundles');
const objectWithRawScripts = require(path.join(rootDir, 'package.json')).scripts;
const packageScripts = {
build: objectWithRawScripts['build'],
prettierCheck: objectWithRawScripts['prettier:check'],
tscCheck: objectWithRawScripts['tsc:check'],
};
function runScript(scriptToRun) {
try {
shell.echo(`Running ${scriptToRun}`);
shell.exec(scriptToRun);
} catch (e) {
shell.echo('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
shell.echo(`there was an error with ${scriptToRun}`);
console.error(e);
shell.echo('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
return false;
}
return true;
}
shell.echo('Init Tasks');
runScript(packageScripts.prettierCheck);
runScript(packageScripts.tscCheck);

Resources