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

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);

Related

How to get arguements from CLI with process.argv and pass it to npm?

I want to pass arguements to npm script
package.json:
"scripts": {
"start": "node list.js <path>",
{
when I execute command
npm start "C:\Users\santo\Downloads\service_account.json"
I want to pass the path to the list.js
list.js:
const process = require('process');
var args = process.argv;
args.forEach((val, index) => {
console.log(`${index}: ${val}`);
});
I am getting erroe saying Error: Cannot find module
How can I solve it and pass the data?

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"
},

Set node environment variable to dynamic value in npm script

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
});

Run ava test.before() just once for all tests

I would like to use test.before() to bootstrap my tests. The setup I have tried does not work:
// bootstrap.js
const test = require('ava')
test.before(t => {
// do this exactly once for all tests
})
module.exports = { test }
// test1.js
const { test } = require('../bootstrap')
test(t => { ... {)
AVA will run the before() function before each test file. I could make a check within the before call to check if it has been called but I'd like to find a cleaner process. I have tried using the require parameter with:
"ava": {
"require": [
"./test/run.js"
]
}
With:
// bootstrap,js
const test = require('ava')
module.exports = { test }
// run.js
const { test } = require('./bootstrap')
test.before(t => { })
// test1.js
const { test } = require('../bootstrap')
test(t => { ... {)
But that just breaks with worker.setRunner is not a function. Not sure what it expects there.
AVA runs each test file in its own process. test.before() should be used to set up fixtures that are used just by the process it's called in.
It sounds like you want to do setup that is reused across your test files / processes. Ideally that's avoided since you can end up creating hard-to-detect dependencies between the execution of different tests.
Still, if this is what you need then I'd suggest using a pretest npm script, which is run automatically when you do npm test.
In your package.json you could run a setup script first...
"scripts": {
"test": "node setup-test-database.js && ava '*.test.js'"
}
Then...
In that setup-test-database.js file, have it do all your bootstrappy needs, and save a test-config.json file with whatever you need to pass to the tests.
In each test you just need to add const config = require('./test-config.json'); and you'll have access to the data you need.

I want to download some resources upon 'npm install', presumably using a 'prepublish' script

Specifically, I want to download angular.min.js when running 'npm install'.
From what I read here the place to do such things is in a prepublish script. It also mentions I don't need to rely on having wget/curl installed on the system.
However it doesn't go into any detail on how to go about doing it.
Anybody knows how to do it?
Ok, solved it myself.
I added a small javascript file (prepublish.js):
var http = require('http');
var fs = require('fs');
function download(filename, url) {
var file = fs.createWriteStream(filename);
var request = http.get(url, function(response) {
response.pipe(file);
});
}
console.log('Downloading angular');
download('public/scripts/angular.min.js', 'http://code.angularjs.org/1.0.7/angular.min.js');
download('public/scripts/angular.js', 'http://code.angularjs.org/1.0.7/angular.js');
console.log('Downloading angular-ui-router');
download('public/scripts/angular-ui-router.min.js', 'http://angular-ui.github.io/ui-router/release/angular-ui-router.min.js');
download('public/scripts/angular-ui-router.js', 'http://angular-ui.github.io/ui-router/release/angular-ui-router.js');
and I modified my package.json:
...
"scripts": {
"start": "node app.js",
"prepublish": "node prepublish.js"
},
Now when running 'npm install' it downloads my dependencies!

Resources