Unit testing using Jasmine and TypeScript - javascript

I am trying to get a unit test written in Typescript using Jasmine to compile. With the following in my unit-test file, Resharper prompts me with a link to import types from jasmine.d.ts.
/// <reference path="sut.ts" />
/// <reference path="../../../scripts/typings/jasmine/jasmine.d.ts" />
describe("Person FullName", function () {
var person;
BeforeEach(function () {
person = new Person();
person.setFirstName("Joe");
person.setLastName("Smith");
});
It("should concatenate first and last names", function () {
Expect(person.getFullName()).toBe("Joe, Smith");
});
});
So I click on the link and end up with the following (actually resharper only prefixed the describe function with "Jasmine.", so I manually prefixed the other Jasmine calls):
/// <reference path="sut.ts" />
/// <reference path="../../../scripts/typings/jasmine/jasmine.d.ts" />
import Jasmine = require("../../../Scripts/typings/jasmine/jasmine");
Jasmine.describe("Person FullName", function () {
var person;
Jasmine.BeforeEach(function () {
person = new Person();
person.setFirstName("Joe");
person.setLastName("Smith");
});
Jasmine.It("should concatenate first and last names", function () {
Jasmine.Expect(person.getFullName()).toBe("Joe, Smith");
});
});
However the import statement has a red squiggly line with error message "Unable to resolve external module ../../../scripts/typings/jasmine/jasmine. Module cannot be aliased to a non-module type"
Any idea what is causing this error? I've checked that the "Module System" option is set to AMD in my project build settings. I've also checked that the jasmine module is defined in jasmine.d.ts. I downloaded this file from DefinitelyTyped site.
declare module jasmine {
...
}

Here's (in my opinion) the best way to test a ts-node app as of 2018:
npm install --save-dev typescript jasmine #types/jasmine ts-node
In package.json:
{
"scripts": {
"test": "ts-node node_modules/jasmine/bin/jasmine"
}
}
In jasmine.json change file pattern to *.ts
"spec_files": ["**/*[sS]pec.ts"],
In your spec files:
import "jasmine";
import something from "../src/something";
describe("something", () => {
it("should work", () => {
expect(something.works()).toBe(true);
});
});
To run the tests:
npm test
This will use the locally installed versions of ts-node and jasmine. This is better than using globally installed versions, because with local versions, you can be sure that everyone is using the same version.
Note: if you have a web app instead of a node app, you should probably run your tests using Karma instead of the Jasmine CLI.

Put this at the top of your typescript spec file:
/// <reference path="../../node_modules/#types/jasmine/index.d.ts" />
let Jasmine = require('jasmine');
You must install the following Jasmine modules for that to work:
$ npm install jasmine-core jasmine #types/jasmine #ert78gb/jasmine-ts --save-dev
Once you do that, the IDE (such as WebStorm) will recognize Jasmine and its functions such as describe(), it(), and expect().. So you don't need to prefix them with "Jasmine." Also, you can run your spec files from the command line using the jasmine-ts module. Install these command line tools globally:
$ npm install -g jasmine #ert78gb/jasmine-ts
Then configure the "jasmine" command line module so that Jasmine can find its configuration file. Then you should be able to run jasmine-ts and your spec file should run fine from the command line:
./node_modules/.bin/jasmine-ts src/something.spec.ts
.. and, you can configure your IDE to run it like that as well, and debug runs that way should also work (works for me).
Writing your tests this way, you can run a Jasmine test spec on the server side without Karma, or run it in a web browser using Karma. Same typescript code.

If you have issues with imports, use tsconfig-paths
npm i ts-node tsconfig-paths types/jasmine jasmine --save-dev
Run typescript-enabled jasmine:
ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine.js
Ensure that your jasmine will search .ts files:
"spec_files": [
"**/*[sS]pec.ts"
],
"helpers": [
"helpers/**/*.ts"
],
To test your scripts you may also need polyfills if you use them in your project. Create a helper file with required imports, like helpers/global/polifill.ts
import 'core-js';

For me I did the following:
Install Typings
npm install typings --global
Then add the typings in for jasmine
typings install dt~jasmine --save --global

You could try a side-effect only import which brings in the #types/jasmine declaration and places the jasmine functions into the global scope so you don't need to prefix each call with jasmine. allowing a quick port from existing unit tests and still plays nice with webpack.
// tslint:disable-next-line:no-import-side-effect
import "jasmine";
describe("My Unit Test", () => { /* ... */ } );
Of course you still need to install jasmine and the typings:
$ npm i jasmine #types/jasmine --save-dev
But no need for specialized jasmine loaders for ts or node. Just run jasmine against the compiled js files:
$ node ./node_modules/jasmine/bin/jasmine.js --config=test/support/jasmine.json
Assuming your typescript files are within a "test" subdirectory compiling to bin/test and you have a test/support/jasmine.json with something like this:
{
"spec_dir": "bin/test",
"spec_files": [
"**/*[sS]pec.js"
],
"stopSpecOnExpectationFailure": false,
"random": false
}
P.S. all of the above works on Windows too

Include this to your jasmine html file,...
<script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine.js"></script>
...or install the npm jasmine package:
npm install --save-dev jasmine
when you are using the second way (jasmine as module) you have to import it:
var jasmine = require('jasmine');
or
import jasmine from 'jasmine';
then change the other code:
jasmine.describe("Person FullName", function () {
var person;
jasmine.beforeEach(function () {
person = new Person();
person.setFirstName("Joe");
person.setLastName("Smith");
});
jasmine.it("should concatenate first and last names", function () {
jasmine.expect(person.getFullName()).toBe("Joe, Smith");
});
});
Personally i would prefer the first way without using the jasmine npm module. (I didn't test the module yet)

You didn't ask for this, but for bonus points: once you get AJ's answer up and running (using ts-node to invoke the Jasmine startup script), you can add a new task:
"scripts": {
"watch": "ts-node-dev --respawn -- ./node_modules/jasmine/bin/jasmine src/**.spec.ts"
}
Of course, you can pass your specs or any other arguments using Jasmine's config file instead, if you like. Now, Jasmine will run all your specs once, then ts-node-dev will sit in the background waiting for your tests or anything they might have require'd to change, at which point jasmine will be run again. I haven't worked out a way to only run the tests that have changed (or tests whose imports have changed) yet -- as far as I can tell, that's not supported anyway;

My folder structure
Spec folder is on the root of project
spec
\- dist // compiled tests
\- helpers // files modified testing env
\- ts-console.ts // pretty prints of results
\- support
\- jasmine.json
\- YourTestHere.spec.ts
\- tsconfig.json // tsconfig for your tests
Files content
ts-console.ts
const TSConsoleReporter = require("jasmine-console-reporter");
jasmine.getEnv().clearReporters();
jasmine.getEnv().addReporter(new TSConsoleReporter());
jasmine.json
{
"spec_dir": "spec/dist",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"spec/helpers/**/*.js"
],
"stopSpecOnExpectationFailure": false,
"random": true
}
With extra script in package.json
"scripts": {
"test": "rm -rf ./spec/dist && tsc -p ./spec && jasmine"
}
and add line "/spec/dist" to .gitignore
Run your tests!
Run your tests with npm test.
How does it work?
Directory for tests is cleaned.
Tests are compiled to spec/dist folder to JS.
Tests are runned from this location.
I hope it will help you. Good coding.

Related

Run single test of a specific test suite in Jest

I have a file called "test-file-1", within it i have a few describes (test suites) with unique names and inside them i have tests which may have similar names across test suites.
To run a single test suite or test i type the command below.
npm test -- -t "Test Suite or Test"
I'm trying to figure out how to run a single test of a specific test suite.
Give the path of the file you want to test :
npm test path/of/your/testfile.js -t "test name"
will run only the test (or group) named "test name" of this specific file.
After trying a lot of different things i found out that it was simpler than i thought.
You simply have to put the test after the test suite in the same string:
npm test -- -t "<Test Suite> <Test>"
import LoggerService from '../LoggerService ';
describe('Method called****', () => {
it('00000000', () => {
const logEvent = jest.spyOn(LoggerService , 'logEvent');
expect(logEvent).toBeDefined();
});
});
run below command
npm test -- tests/LoggerService.test.ts -t '00000000'
Point to the test suite in jest config file via "testMatch":["**/path/testSuite/*.js"]
and now execute by giving unique testname by jest testName
Here you have to update the testMatch every time which is your testSuite
If you are using a jest config file, you can use the testNamePattern key to configure this. So your config file could look something like
jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".spec.ts$",
"testNamePattern": "login",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"bail": true,
"verbose": true,
"testTimeout": 30000
}
This will only run test that has "login" in one of the test pattern
Reference:
https://jestjs.io/docs/cli#--testnamepatternregex
For me, it only works with:
npm run test -- tests/01_login.js --testcase "Should login into Dashboard"
npm run <script> -- <test suite path> --testcase "<test case>"
my script in package.json:
"test": "env-cmd -f ./.env nightwatch --retries 2 --env selenium.chrome",
at nightwatch version 1.3.4

JavaScript export / import doesn't work

it might be a silly question but I can't fix it anyway. I have a JavaScript file with various functions I'd like to export.
export function AddNumbers(...numbers)
{
let value = 0;
for(var i = 0;i < numbers.length;i++)
{
value += numbers[i];
}
return value;
}
When I call this method (using mocha) I get an error message "export function AddNumbers(...numbers) Unexpected token export". The project is build as ES6. Does anybody know what I'm doing wrong?
Best regards,
Torsten
You need to use module.exports as NodeJS uses CommonJS Module syntax which requires to use module.exports and not just export which is defined by ES6 module syntax. So, make sure CommonJS is also configured properly in your project.
Another solution is to use Babel. Install it with
npm install babel-core --save-dev
npm install babel-preset-es2015 --save-dev
Create in root directory a file .babelrc with following content
{
"preset" : ["es2015"]
}
and finally change the script in package.json to run into:
"scripts": {
"test": "mocha Tests --require babel-core/register"
}
and now export / import works.

pass variable to webpack to build different bundle

I am trying to build a different bundle based on an argument passed to webpack.
I have a create-react-app that I have ejected from and currently currently if I do npm run build it builds a bundle using webpack. As I have both an english and spanish version of the site I was hoping that I could pass an argument here. i.e. to build a Spanish version something like npm run build:es.
My webpack file currently just builds the English bundle. There is a separate process during the application to pull in translations, but during the building of the bundle it would be great if I could stipulate which language to build the bundle for.
Anyone have any ideas.
The relevant webpack code is below:
//default messages for translations
var defaultMessages = require('/translations/en.json');
//more webpack stuff......
{
test: /\.(js|jsx)$/,
loader: require.resolve('string-replace-loader'),
query: {
multiple: Object.keys(defaultMessages).map(key => ({
search: `__${key}__`,
replace: defaultMessages[key]
}))
}
},
Webpack can receive a --env argument, which is then passed to the webpack.config file. The key is to export a function returning the configuration from your webpack.config.js, not the raw configuration.
$ webpack --env=lang_es
And in webpack.config.js:
module.exports = function(env) {
if (env === 'lang_es') {
// ...
}
return {
module: {
// ...
},
entry: {
// ...
}
}
}
And in package.json:
"scripts": {
"build_es": "webpack --env=lang_es",
}
This was originally really meant to distinguish between build types, e.g. development or production, but it's just a string passed into the config file - you can give it any meaning you want.
As hinted in the comments, using environment variables is the second, webpack-independent, approach. You can set the environment variable directly in package.json's scripts section:
"scripts": {
"build_es": "BUILD_LANG=es webpack",
}
(Use cross-env to set the environment when developing on Windows).
And in webpack.config.js:
if (process.env.BUILD_LANG === 'es') {
// ...
}
This environment-based approach has been used in a few places already (for example Babel's BABEL_ENV variable), so I'd say that it has gathered enough mileage to consider it proven and tested.
Edit: fixed the cross-env part to mention that it's only necessary on Windows.

How to cover React jsx files in Istanbul?

I'm trying to integrate my existing test processes to now include React, but am struggling on the code coverage part. I've been able to get my unit tests working fine by following this project/tutorial - https://github.com/danvk/mocha-react - http://www.hammerlab.org/2015/02/14/testing-react-web-apps-with-mocha/
I've been using Istanbul to cover my node code and it's working pretty well. However, I'm having trouble getting it to cover the jsx files that I'm using in my tests.
Here's an example of an existing Istanbul task, which also runs fine on vanilla js (node backend code)
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
gulp.task('test-api', function (cb) {
gulp.src(['api/**/*.js'])
.pipe(istanbul()) // Covering files
.pipe(istanbul.hookRequire()) // Force `require` to return covered files
.on('finish', function () {
gulp.src(['test/api/*.js'])
.pipe(mocha())
.pipe(istanbul.writeReports()) // Creating the reports after tests runned
.on('end', cb);
My issue ( i think ) is I can't get Istanbul to recognize the jsx files or they're not being compared to what was run in the tests. I tried using the gulp-react module to precompile the jsx to js so it can be used by Istanbul, but I'm not sure if it's working. It's not being covered somehow and I'm not sure where the issue is.
var mocha = require('gulp-mocha');
var istanbul = require('gulp-istanbul');
var react = require('gulp-react');
gulp.task('test-site-example', function (cb) {
gulp.src(["site/jsx/*.jsx"]) //Nothing is being reported by Istanbul (not being picked up)
.pipe(react()) //converts the jsx to js and I think pipes the output to Istanbul
.pipe(istanbul())
.pipe(istanbul.hookRequire()) // Force `require` to return covered files
.on('finish', function () {
gulp.src(['test/site/jsx/*.js'], { //tests run fine in mocha, but nothing being shown as reported by mocha (not covered)
read: false
})
.pipe(mocha({
reporter: 'spec'
}))
.pipe(istanbul.writeReports())
.on('end', cb);
});
;
});
Any ideas what I'm doing wrong? Or any clue on how to integrate a test runner (preferably Istanbul) into a Gulp-Mocha-React project?
Add a .istanbul.yml file to your app root and add .jsx to extensions "extension"...
Here is what I did.
// File .istanbul.yml
instrumentation:
root: .
extensions: ['.js', '.jsx']
To kickstart istanbul and mocha with jsx
$ istanbul test _mocha -- test/**/* --recursive --compilers js:babel/register
This will convert your .jsx files to .js and then istanbul will cover them.
I hope this helps. It worked for me.
There is a library you can take a look at, gulp-jsx-coverage (https://github.com/zordius/gulp-jsx-coverage).
In case someone else is having the same problem and solutions above don't work, I found that adding a simple "-e .jsx" tag worked:
"scripts": {
"start": "meteor run",
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-register --require tests/index.js ./tests/**/*.test.js",
"coverage": "NODE_ENV=test nyc -all -e .jsx npm test"
}
This solution was found at: http://www.2devs1stack.com/react/tape/testing/nyc/coverage/2016/03/05/simple-code-coverage-with-nyc.html
A great tutorial can be found at https://istanbul.js.org/docs/tutorials/es2015/
I just slightly modified it to include react. (I also used 'env' instead of 'es2015', but either should work.) Here are my configurations:
npm i babel-cli babel-register babel-plugin-istanbul babel-preset-env babel-preset-react cross-env mocha nyc --save-dev
.babelrc
{
"presets": ["env", "react"],
"env": {
"test": {
"plugins": [
"istanbul"
]
}
}
}
package.json
"scripts": {
"test": "cross-env NODE_ENV=test nyc mocha test/**/*.spec.js --compilers js:babel-register"
}
"nyc": {
"require": [
"babel-register"
],
"reporter": [
"lcov",
"text"
],
"sourceMap": false,
"instrument": false
}

Sending command line arguments to npm script

The scripts portion of my package.json currently looks like this:
"scripts": {
"start": "node ./script.js server"
}
...which means I can run npm start to start the server. So far so good.
However, I would like to be able to run something like npm start 8080 and have the argument(s) passed to script.js (e.g. npm start 8080 => node ./script.js server 8080). Is this possible?
npm 2 and newer
It's possible to pass args to npm run since npm 2 (2014). The syntax is as follows:
npm run <command> [-- <args>]
Note the -- separator, used to separate the params passed to npm command itself, and the params passed to your script.
With the example package.json:
"scripts": {
"grunt": "grunt",
"server": "node server.js"
}
here's how to pass the params to those scripts:
npm run grunt -- task:target // invokes `grunt task:target`
npm run server -- --port=1337 // invokes `node server.js --port=1337`
Note: If your param does not start with - or --, then having an explicit -- separator is not needed; but it's better to do it anyway for clarity.
npm run grunt task:target // invokes `grunt task:target`
Note below the difference in behavior (test.js has console.log(process.argv)): the params which start with - or -- are passed to npm and not to the script, and are silently swallowed there.
$ npm run test foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js', 'foobar']
$ npm run test -foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js']
$ npm run test --foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js']
$ npm run test -- foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js', 'foobar']
$ npm run test -- -foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js', '-foobar']
$ npm run test -- --foobar
['C:\\Program Files\\nodejs\\node.exe', 'C:\\git\\myrepo\\test.js', '--foobar']
The difference is clearer when you use a param actually used by npm:
$ npm test --help // this is disguised `npm --help test`
npm test [-- <args>]
aliases: tst, t
To get the parameter value, see this question. For reading named parameters, it's probably best to use a parsing library like yargs or minimist; nodejs exposes process.argv globally, containing command line parameter values, but this is a low-level API (whitespace-separated array of strings, as provided by the operating system to the node executable).
You asked to be able to run something like npm start 8080. This is possible without needing to modify script.js or configuration files as follows.
For example, in your "scripts" JSON value, include--
"start": "node ./script.js server $PORT"
And then from the command-line:
$ PORT=8080 npm start
I have confirmed that this works using bash and npm 1.4.23. Note that this work-around does not require GitHub npm issue #3494 to be resolved.
You could also do that:
In package.json:
"scripts": {
"cool": "./cool.js"
}
In cool.js:
console.log({ myVar: process.env.npm_config_myVar });
In CLI:
npm --myVar=something run-script cool
Should output:
{ myVar: 'something' }
Update: Using npm 3.10.3, it appears that it lowercases the process.env.npm_config_ variables? I'm also using better-npm-run, so I'm not sure if this is vanilla default behavior or not, but this answer is working. Instead of process.env.npm_config_myVar, try process.env.npm_config_myvar
jakub.g's answer is correct, however an example using grunt seems a bit complex.
So my simpler answer:
- Sending a command line argument to an npm script
Syntax for sending command line arguments to an npm script:
npm run [command] [-- <args>]
Imagine we have an npm start task in our package.json to kick off webpack dev server:
"scripts": {
"start": "webpack-dev-server --port 5000"
},
We run this from the command line with npm start
Now if we want to pass in a port to the npm script:
"scripts": {
"start": "webpack-dev-server --port process.env.port || 8080"
},
running this and passing the port e.g. 5000 via command line would be as follows:
npm start --port:5000
- Using package.json config:
As mentioned by jakub.g, you can alternatively set params in the config of your package.json
"config": {
"myPort": "5000"
}
"scripts": {
"start": "webpack-dev-server --port process.env.npm_package_config_myPort || 8080"
},
npm start will use the port specified in your config, or alternatively you can override it
npm config set myPackage:myPort 3000
- Setting a param in your npm script
An example of reading a variable set in your npm script. In this example NODE_ENV
"scripts": {
"start:prod": "NODE_ENV=prod node server.js",
"start:dev": "NODE_ENV=dev node server.js"
},
read NODE_ENV in server.js either prod or dev
var env = process.env.NODE_ENV || 'prod'
if(env === 'dev'){
var app = require("./serverDev.js");
} else {
var app = require("./serverProd.js");
}
As of npm 2.x, you can pass args into run-scripts by separating with --
Terminal
npm run-script start -- --foo=3
Package.json
"start": "node ./index.js"
Index.js
console.log('process.argv', process.argv);
I had been using this one-liner in the past, and after a bit of time away from Node.js had to try and rediscover it recently. Similar to the solution mentioned by #francoisrv, it utilizes the npm_config_* variables.
Create the following minimal package.json file:
{
"name": "argument",
"version": "1.0.0",
"scripts": {
"argument": "echo \"The value of --foo is '${npm_config_foo}'\""
}
}
Run the following command:
npm run argument --foo=bar
Observe the following output:
The value of --foo is 'bar'
All of this is nicely documented in the npm official documentation:
https://docs.npmjs.com/using-npm/config
Note: The Environment Variables heading explains that variables inside scripts do behave differently to what is defined in the documentation. This is true when it comes to case sensitivity, as well whether the argument is defined with a space or equals sign.
Note: If you are using an argument with hyphens, these will be replaced with underscores in the corresponding environment variable. For example, npm run example --foo-bar=baz would correspond to ${npm_config_foo_bar}.
Note: For non-WSL Windows users, see #Doctor Blue's comments below... TL;DR replace ${npm_config_foo} with %npm_config_foo%.
Use process.argv in your code then just provide a trailing $* to your scripts value entry.
As an example try it with a simple script which just logs the provided arguments to standard out echoargs.js:
console.log('arguments: ' + process.argv.slice(2));
package.json:
"scripts": {
"start": "node echoargs.js $*"
}
Examples:
> npm start 1 2 3
arguments: 1,2,3
process.argv[0] is the executable (node), process.argv[1] is your script.
Tested with npm v5.3.0 and node v8.4.0
Most of the answers above cover just passing the arguments into your NodeJS script, called by npm. My solution is for general use.
Just wrap the npm script with a shell interpreter (e.g. sh) call and pass the arguments as usual. The only exception is that the first argument number is 0.
For example, you want to add the npm script someprogram --env=<argument_1>, where someprogram just prints the value of the env argument:
package.json
"scripts": {
"command": "sh -c 'someprogram --env=$0'"
}
When you run it:
% npm run -s command my-environment
my-environment
If you want to pass arguments to the middle of an npm script, as opposed to just having them appended to the end, then inline environment variables seem to work nicely:
"scripts": {
"dev": "BABEL_ARGS=-w npm run build && cd lib/server && nodemon index.js",
"start": "npm run build && node lib/server/index.js",
"build": "mkdir -p lib && babel $BABEL_ARGS -s inline --stage 0 src -d lib",
},
Here, npm run dev passes the -w watch flag to babel, but npm run start just runs a regular build once.
For PowerShell users on Windows
The accepted answer did not work for me with npm 6.14. Neither adding no -- nor including it once does work. However, putting -- twice or putting "--" once before the arguments does the trick. Example:
npm run <my_script> -- -- <my arguments like --this>
Suspected reason
Like in bash, -- instructs PowerShell to treat all following arguments as literal strings, and not options (E.g see this answer). The issues seems to be that the command is interpreted one time more than expected, loosing the '--'. For instance, by doing
npm run <my_script> -- --option value
npm will run
<my_script> value
However, doing
npm run <my_script> "--" --option value
results in
<my_script> "--option" "value"
which works fine.
This doesn't really answer your question but you could always use environment variables instead:
"scripts": {
"start": "PORT=3000 node server.js"
}
Then in your server.js file:
var port = process.env.PORT || 3000;
I've found this question while I was trying to solve my issue with running sequelize seed:generate cli command:
node_modules/.bin/sequelize seed:generate --name=user
Let me get to the point. I wanted to have a short script command in my package.json file and to provide --name argument at the same time
The answer came after some experiments. Here is my command in package.json
"scripts: {
"seed:generate":"NODE_ENV=development node_modules/.bin/sequelize seed:generate"
}
... and here is an example of running it in terminal to generate a seed file for a user
> yarn seed:generate --name=user
> npm run seed:generate -- --name=user
FYI
yarn -v
1.6.0
npm -v
5.6.0
Note: This approach modifies your package.json on the fly, use it if you have no alternative.
I had to pass command line arguments to my scripts which were something like:
"scripts": {
"start": "npm run build && npm run watch",
"watch": "concurrently \"npm run watch-ts\" \"npm run watch-node\"",
...
}
So, this means I start my app with npm run start.
Now if I want to pass some arguments, I would start with maybe:
npm run start -- --config=someConfig
What this does is: npm run build && npm run watch -- --config=someConfig. Problem with this is, it always appends the arguments to the end of the script. This means all the chained scripts don't get these arguments(Args maybe or may not be required by all, but that's a different story.). Further when the linked scripts are called then those scripts won't get the passed arguments. i.e. The watch script won't get the passed arguments.
The production usage of my app is as an .exe, so passing the arguments in the exe works fine but if want to do this during development, it gets problamatic.
I couldn't find any proper way to achieve this, so this is what I have tried.
I have created a javascript file: start-script.js at the parent level of the application, I have a "default.package.json" and instead of maintaining "package.json", I maintain "default.package.json". The purpose of start-script.json is to read default.package.json, extract the scripts and look for npm run scriptname then append the passed arguments to these scripts. After this, it will create a new package.json and copy the data from default.package.json with modified scripts and then call npm run start.
const fs = require('fs');
const { spawn } = require('child_process');
// open default.package.json
const defaultPackage = fs.readFileSync('./default.package.json');
try {
const packageOb = JSON.parse(defaultPackage);
// loop over the scripts present in this object, edit them with flags
if ('scripts' in packageOb && process.argv.length > 2) {
const passedFlags = ` -- ${process.argv.slice(2).join(' ')}`;
// assuming the script names have words, : or -, modify the regex if required.
const regexPattern = /(npm run [\w:-]*)/g;
const scriptsWithFlags = Object.entries(packageOb.scripts).reduce((acc, [key, value]) => {
const patternMatches = value.match(regexPattern);
// loop over all the matched strings and attach the desired flags.
if (patternMatches) {
for (let eachMatchedPattern of patternMatches) {
const startIndex = value.indexOf(eachMatchedPattern);
const endIndex = startIndex + eachMatchedPattern.length;
// save the string which doen't fall in this matched pattern range.
value = value.slice(0, startIndex) + eachMatchedPattern + passedFlags + value.slice(endIndex);
}
}
acc[key] = value;
return acc;
}, {});
packageOb.scripts = scriptsWithFlags;
}
const modifiedJSON = JSON.stringify(packageOb, null, 4);
fs.writeFileSync('./package.json', modifiedJSON);
// now run your npm start script
let cmd = 'npm';
// check if this works in your OS
if (process.platform === 'win32') {
cmd = 'npm.cmd'; // https://github.com/nodejs/node/issues/3675
}
spawn(cmd, ['run', 'start'], { stdio: 'inherit' });
} catch(e) {
console.log('Error while parsing default.package.json', e);
}
Now, instead of doing npm run start, I do node start-script.js --c=somethis --r=somethingElse
The initial run looks fine, but haven't tested thoroughly. Use it, if you like for you app development.
I find it's possible to just pass variables exactly as you would to Node.js:
// index.js
console.log(process.env.TEST_ENV_VAR)
// package.json
...
"scripts": { "start": "node index.js" },
...
TEST_ENV_VAR=hello npm start
Prints out "hello"
Separate your arguments using -- from the script and add all the required arguments, we can later access them by index.
npm run start -- myemail#gmail.com 100
You can get params in node using
const params = process.argv.slice(2);
console.log(params);
Output
['myemail#gmail.com', '100']
From what I see, people use package.json scripts when they would like to run script in simpler way. For example, to use nodemon that installed in local node_modules, we can't call nodemon directly from the cli, but we can call it by using ./node_modules/nodemon/nodemon.js. So, to simplify this long typing, we can put this...
...
scripts: {
'start': 'nodemon app.js'
}
...
... then call npm start to use 'nodemon' which has app.js as the first argument.
What I'm trying to say, if you just want to start your server with the node command, I don't think you need to use scripts. Typing npm start or node app.js has the same effort.
But if you do want to use nodemon, and want to pass a dynamic argument, don't use script either. Try to use symlink instead.
For example using migration with sequelize. I create a symlink...
ln -s node_modules/sequelize/bin/sequelize sequelize
... And I can pass any arguement when I call it ...
./sequlize -h /* show help */
./sequelize -m /* upgrade migration */
./sequelize -m -u /* downgrade migration */
etc...
At this point, using symlink is the best way I could figure out, but I don't really think it's the best practice.
I also hope for your opinion to my answer.
I know there is an approved answer already, but I kinda like this JSON approach.
npm start '{"PROJECT_NAME_STR":"my amazing stuff", "CRAZY_ARR":[0,7,"hungry"], "MAGICAL_NUMBER_INT": 42, "THING_BOO":true}';
Usually I have like 1 var I need, such as a project name, so I find this quick n' simple.
Also I often have something like this in my package.json
"scripts": {
"start": "NODE_ENV=development node local.js"
}
And being greedy I want "all of it", NODE_ENV and the CMD line arg stuff.
You simply access these things like so in your file (in my case local.js)
console.log(process.env.NODE_ENV, starter_obj.CRAZY_ARR, starter_obj.PROJECT_NAME_STR, starter_obj.MAGICAL_NUMBER_INT, starter_obj.THING_BOO);
You just need to have this bit above it (I'm running v10.16.0 btw)
var starter_obj = JSON.parse(JSON.parse(process.env.npm_config_argv).remain[0]);
Anyhoo, question already answered. Thought I'd share, as I use this method a lot.
I settled for something like this, look at the test-watch script:
"scripts": {
"dev": "tsc-watch --onSuccess \"node ./dist/server.js\"",
"test": "tsc && cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest",
"test-watch": "cross-env NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 tsc-watch --onSuccess",
},
You invoke the test-watch script like this:
// Run all tests with odata in their name
npm run test-watch "jest odata"
npm run script_target -- < argument > Basically this is the way of passing the command line arguments but it will work only in case of when script have only one command running like I am running a command i.e. npm run start -- 4200
"script":{
"start" : "ng serve --port="
}
This will run for passing command line parameters but what if we run more then one command together like npm run build c:/workspace/file
"script":{
"build" : "copy c:/file <arg> && ng build"
}
but it will interpreter like this while running copy c:/file && ng build c:/work space/file
and we are expected something like this
copy c:/file c:/work space/file && ng build
Note :- so command line parameter only work ad expected in case of only one command in a script.
I read some answers above in which some of them are writing that you can access the command line parameter using $ symbol but this will not gonna work
Try cross-env NPM package.
Easy to use. Easy to install. Cross all platform.
Example:
set arguments for command
// package.json
"scripts": {
“test”: “node test.js”,
“test-with-env-arg”: “cross-env YourEnvVarName=strValue yarn test,
}
get arguments from process.env
// test.js
const getCommandLineArg = Boolean(process.env.YourEnvVarName === 'true') // Attention: value of process.env.* is String type, not number || boolean
i had the same issue when i need to deploy to different environments
here is the package.json pre and post the updates.
scripts:
{"deploy-sit": "sls deploy --config resources-sit.yml",
"deploy-uat": "sls deploy --config resources-uat.yml",
"deploy-dev": "sls deploy --config resources-dev.yml"}
but here is the correct method to adopt the environment variables rather than repeating ourselves
scripts:{"deploy-env": "sls deploy --config resources-$ENV_VAR.yml"}
finally you can deploy by running
ENV_VAR=dev npm run deploy-env
Using npm 9.3.1, you can pass variables to scripts by this way:
Inside "scripts" tag in package.json, put the variable with "$"
"scripts": {
...
"generate": "nest g controller $NAME && nest g service $NAME && nest g module $NAME"
},
When you call the script, just pass the variable with her value in the console before the script
NAME=auth npm run generate
or
NAME=auth yarn generate
In this example, the script will generate controllers, services e modules with the name passed for an Nest application.

Categories