[Part 1] How to install npm packages programmatically?

In this article, we review how Father, an NPM package development tool is installs npm dependencies programmatically.

How did I find this code snippet?

I wrote an article about Father and in its README.md, I found that it supports micro generators that adds commonly used engineering capabilities to projects, such as setting up Jest for testing. This is similar to the CLI tool I am building, to generate code for authentication or S3 upload in Next.js.

I started searching for the micro generators code and found a folder named commands/generators.

Common pattern in the generators

You will see that there is a common pattern in the way these generators are defined.

generators/eslint.ts

eslint.ts has the below code

import { GeneratorType } from '@umijs/core';
import { logger } from '../../utils';
import fg from 'fast-glob';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { IApi } from '../../types';
import { GeneratorHelper } from './utils';

export default (api: IApi) => {
  api.describe({
    key: 'generator:eslint',
  });

  api.registerGenerator({
    key: 'eslint',
    name: 'Enable ESLint',
    description: 'Setup ESLint Configuration',
    type: GeneratorType.enable,
    checkEnable: () => {
      ...
    },
    disabledDescription:
      'ESLint has already enabled. You can remove .eslintrc, then run this again to re-setup.',
    fn: async () => {
      ....
    },
  });
};

generators/jest.ts

jest.ts has the below definition:

import { GeneratorType } from '@umijs/core';
import { logger } from '../../utils';
import { existsSync, writeFileSync } from 'fs';
import { join } from 'path';
import { IApi } from '../../types';
import { GeneratorHelper, promptsExitWhenCancel } from './utils';

export default (api: IApi) => {
  api.describe({
    key: 'generator:jest',
  });

  api.registerGenerator({
    key: 'jest',
    name: 'Enable Jest',
    description: 'Setup Jest Configuration',
    type: GeneratorType.enable,
    checkEnable: () => {
      ...
    },
    disabledDescription:
      'Jest has already enabled. You can remove jest.config.{ts,js}, then run this again to re-setup.',
    fn: async () => {
      ...
    },
  });
};

Do you see the common pattern in these two definitions above? there is

  • api.describe

This accepts an object that has the below properties:

  1. key
  • api.registerGenerator

This accepts an object that has the below properties:

  1. key

  2. name

  3. description

  4. type

  5. checkEnable

  6. disabledDescription

  7. fn

h.installDeps()

At line 97 in generators/jest.ts, you find this below code snippet

h.installDeps();

What is h here? At line 27, you will see h is initialized as shown below:

const h = new GeneratorHelper(api);

To see how the installDeps is defined, we need to review GeneratorHelper

GeneratorHelper

GeneratorHelper has the below shown functions at the time of writing this article

import { getNpmClient, installWithNpmClient, prompts } from '@umijs/utils';
import { writeFileSync } from 'fs';
import { IApi } from '../../types';
import { logger } from '../../utils';

export class GeneratorHelper {
  constructor(readonly api: IApi) {}

  addDevDeps(deps: Record<string, string>) {
    ...
  }

  addScript(name: string, cmd: string) {
    ...
  }

  private addScriptToPkg(name: string, cmd: string) {
    ...
  }

  installDeps() {
    ...  
  }
}

installDeps

installDeps is defined as shown below in GeneratorHelper.

installDeps() {
    const { api } = this;
    const npmClient = getNpmClient({ cwd: api.cwd });
    installWithNpmClient({
      npmClient,
    });
    logger.quietExpect.info(`Install dependencies with ${npmClient}`);
  }

There are two functions that we need to learn about to understand how Father installs npm deps programmatically.

  • getNpmClient

  • installWithNpmClient

This will be discussed in part 2, i.e., in the next article.

About me:

Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.

I am open to work on interesting projects. Send me an email at

My Github — https://github.com/ramu-narasinga

My website — https://ramunarasinga.com

My Youtube channel — https://www.youtube.com/@thinkthroo

Learning platform — https://thinkthroo.com

Codebase Architecture — https://app.thinkthroo.com/architecture

Best practices — https://app.thinkthroo.com/best-practices

Production-grade projects — https://app.thinkthroo.com/production-grade-projects

References:

  1. https://github.com/umijs/father/blob/a95a4ead36ecb4788728348fcb45a83507a0fb17/src/commands/generators/jest.ts#L97

  2. https://github.com/umijs/father/blob/master/src/commands/generators/utils.ts#L1

  3. https://github.com/search?q=repo%3Aumijs%2Ffather%20%40umijs%2Futils&type=code

  4. https://github.com/orgs/umijs/repositories?q=utils

  5. https://www.npmjs.com/package/@umijs/utils

  6. https://github.com/umijs/umi/blob/master/package.json#L82

  7. https://github.com/umijs/umi/tree/master/packages/utils

  8. https://github.com/umijs/umi/blob/9c3194d0617276fbecd09d19a8cff606fcaac82d/packages/utils/src/npmClient.ts#L13

  9. https://github.com/umijs/umi/tree/9c3194d0617276fbecd09d19a8cff606fcaac82d/packages/utils/compiled