Sending command line arguments to a package.json script

Do you need to pass command line arguments dynamically to a package.json script? This is how to do it.

Static command line arguments can be set with the scripts.

  ...
  "scripts": {
    "cli": "node ./src/build-helper-cli.js -V"
  }
  ...

The script can be called as follows.

 $ npm run cli

Now lets pass it a command line argument dynamically.

$ npm run cli -- add-eslint-disable --dest build

The trick are the two dashes (–) which tell npm what follows are command line arguments for the script being executed. That is it. Easy right?

Local npm module (link) causes “Cannot assign to read only property ‘exports’ of object”

I’m creating a Vue plugin using Vue CLI 3. I was using the vue-cli-service build with target type lib to build the reusable plugin that could be imported into another project. While developing the plugin I was testing it as a local npm module (link) and I ran into this error “Cannot assign to read only property ‘exports’ of object”. I was using npm link to create a local module.

It took a lot of googling around to find the solution. It turns out it has to do with Babel and webpack. As always once you know the solution it is simple. I had to add sourceType unambiguous to my babel.config.js file. This is my babel.config.js file:

module.exports = {
  presets: [
    '@vue/app'
  ],
  sourceType: 'unambiguous'
}

This comment of a webpack issue contains the explanation. Please check it out to understand why this works.

I hope this helps someone and saves them (lots of) time.

Find the location of NPM global packages on Windows

Today I wanted to add a file from a package that I had installed globally with NPM to Webstorm. Where does NPM install the global packages on Windows? How to find the location?

As always, once you know how it is simple. Just execute the following command from cmd.

npm config ls -l | grep prefix

That will show you the location. Which is probably something like C:\Users\\AppData\Roaming\npm.

Override module loaded by require

I was using the module redact-secrets with Winston logger. This module makes sensitive data like passwords unreadable in logfiles. Very cool and handy module. It makes use of another module is-secret that contains a collection of patterns to determine what sensitive data is. One piece of sentive data was missing from is-secret: pass. I could fix it on my side, but I prefer the original GitHub project to be updated so others can also profit from it. So I submitted an issue on GitHub. While waiting for the fix I needed to continue with my development work. So I used another handy module override-require. This module overrides the resolution logic of require. So you can use it to override a dependency of a module. I used it in the following to overrule is-secret used by redact-secrets.

const overrideRequire = require('override-require');

// Check if a request needs to be overridden
const isOverride = (request) => {
return request === 'is-secret';
};

// If isOverride is true, load the module with the overridden module
const resolveRequest = (request) => {
return require('./overrule/is-secret');
};

// Initialize overide-require
const restoreOriginalModuleLoader = overrideRequire(isOverride, resolveRequest);

const { createLogger, format, transports } = require('winston');
// When redacts-secrets is loaded override-require will kickin and load our own module
const redact = require('redact-secrets')('******');
const fs = require('fs');
const path = require('path');

// Disable override require
restoreOriginalModuleLoader();

 

That’s it. Pretty cool isn’t it?

Testing Mongoose plugin with Jest and shared data

Grrrr just spent the better half of 2 hours trying to figure out why my Jest tests were not working for a Mongoose plugin I’m developing. It turns out that the schema configuration I was sharing between the tests was the culprit.

let Mongoose = require('mongoose');
let Schema = Mongoose.Schema;
const _ = require('lodash');
var mongooseI18n = require('../src/mongoose/mongoose-i18n-localize');

Mongoose.set('debug', true);

const simpleSchemaConfig = {
  firstName: { type: String, i18n: true, required: true },
  lastName: { type: String, i18n: false, required: true }
};

describe('Mongoose i18n localize', () => {
  beforeEach(() => {
    // Clear compiled models to avoid OverwriteModelError
    Mongoose.models = {};
  });
  
  it('should throw an exception if the locales array is not defined', () => {
    const SimpleTestSchema = new Schema(simpleSchemaConfig);
    expect(() => {
      SimpleTestSchema.plugin(mongooseI18n, {
        defaultLocale: 'nl',
        allLocalesRequired: false
      });
    }).toThrow('The required option locales array not provided or empty');
  });

  it('should throw an exception if the locales array is an empty array', () => {
    const SimpleTestSchema = new Schema(simpleSchemaConfig);
    expect(() => {
      SimpleTestSchema.plugin(mongooseI18n, {
        locales: [],
        defaultLocale: 'nl',
        allLocalesRequired: false
      });
    }).toThrow('The required option locales array not provided or empty');
  });

  ...

});

The plugin makes changes to the schema configuration and these changes were made to the shared config. The fix I used was to make a copy of the shared config with lodash’s cloneDeep function. The only change I needed to make was the schema initialization.

const SimpleTestSchema = new Schema(_.cloneDeep(simpleSchemaConfig));

Now my tests run like a charm 🙂

Bonus

When I initially ran into the above problem, I was also initializing the schema globally. I thought that was the problem, so I moved the following statement to each test case.

const SimpleTestSchema = new Schema(simpleSchemaConfig);

That caused the exception OverwriteModelError, because I was trying to initialize a compiled model. The fix for this problem was to remove all models between test cases using beforeEach. The following code does the trick.

beforeEach(() => {
  // Clear compiled models to avoid OverwriteModelError
  Mongoose.models = {};
});

Hope this helps someone.