DEV Community

Kinga
Kinga

Posted on

5 4

Rush custom commands: execute custom script with autoinstaller dependencies

When writing custom commands for rush, package dependencies used by your script may be automatically installed using autoinstaller.

Autoinstallers provide a way to manage a set of related dependencies that are used for
scripting scenarios outside of the usual "rush install" context

One example of such a configuration is Enabling Prettier.

But what will happen if you want to use these dependencies in your script? For example, instead of this:

 "commands": [
    {
      "name": "prettier",
      "commandKind": "global",
      "autoinstallerName": "rush-prettier",
      // This will invoke common/autoinstallers/rush-prettier/node_modules/.bin/pretty-quick
      "shellCommand": "pretty-quick --staged"
    }
Enter fullscreen mode Exit fullscreen mode

you want to execute this:

 "commands": [
    {
      "name": "prettier",
      "commandKind": "global",
      "autoinstallerName": "rush-prettier",
      "shellCommand": "node common/scripts/run-pretty-quick-and-some-other-scripts.js"
    }
Enter fullscreen mode Exit fullscreen mode

The command

My new rush command rush print-arguments should parse and print arguments provided during the command invocation. The argument parsing is done with minimist.

Create autoinstaller

# create the autoinstaller
rush init-autoinstaller --name rush-minimist
# install minimist as a dependency 
cd common/autoinstallers/rush-minimist
pnpm i minimist
# ensure that the common/autoinstallers/rush-minimist/ppnpm-lock.yaml file is up to date
rush update-autoinstaller --name rush-minimist
Enter fullscreen mode Exit fullscreen mode

Create the command

common/config/rush/command-line.json

{
  "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json",
  "commands": [
    {
      "name": "print-arguments",
      "commandKind": "global",
      "summary": "Prints provided arguments to the output",
      "autoinstallerName": "rush-minimist",
      "shellCommand": "node common/scripts/print-arguments.js"
    }
  ],
  "parameters": [
    {
      "parameterKind": "string",
      "argumentName": "ARGUMENT1",
      "longName": "--arg1",
      "description": "",
      "associatedCommands": ["print-arguments"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Create the script

Create your script in common/scripts folder:

common/scripts/print-arguments.js

var minimist = require('minimist');
var args = minimist(process.argv.slice(2));
Enter fullscreen mode Exit fullscreen mode

Run the command

rush install
rush print-arguments --arg1 "Hello world!"
Enter fullscreen mode Exit fullscreen mode

The error

Acquiring lock for "common\autoinstallers\rush-minimist" folder...
Autoinstaller folder is already up to date

internal/modules/cjs/loader.js:883
  throw err;
  ^

Error: Cannot find module 'minimist'
Require stack:
- [...]\common\scripts\print-arguments.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:880:15)
    at Function.Module._load (internal/modules/cjs/loader.js:725:27)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    ...
    (internal/modules/run_main.js:72:12) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '[...]\\common\\scripts\\print-arguments.js'
  ]
}

The script failed with exit code 1
Enter fullscreen mode Exit fullscreen mode

The root cause

According to the documentation:

The autoinstallerName [...] "my-task" would map to "common/autoinstallers/my-task/package.json", and the "common/autoinstallers/my-task/node_modules/.bin" folder would be added to the shell PATH.

And indeed, the minimist is in common/autoinstallers/rush-minimist/node_modules, and process.env.PATH does include common/autoinstallers/my-task/node_modules/.bin.

What is the issue then?

When requiring a module without specifying a path, Node will look for it in all the paths specified by module.paths:

[    
  'C:\\folder1\\folder2\\project\\common\\scripts\\node_modules',
  'C:\\folder1\\folder2\\project\\common\\node_modules',
  'C:\\folder1\\folder2\\project\\node_modules',
  'C:\\folder1\\folder2\\node_modules',
  'C:\\folder1\\node_modules',
  'C:\\node_modules'
]
Enter fullscreen mode Exit fullscreen mode

The common/autoinstallers/my-task/node_modules/ is not on the list and in effect, node is throwing a "cannot find module error."

The solution

Modules that are outside of the above node_modules directories can be found using either relative or absolute paths. All we need to do is to find it.

common/scripts/print-arguments.js

//1. See current location: this would be {repo}/common/scripts path
//   console.log(__dirname )
//2. Packages installed by autoinstaller are saved to common/autoinstallers/autoinstaller-name/ and added to the shell PATH
//   console.dir(process.env.PATH);
//3. Knowing current location, and location of the node_modules with packages, path will be ../autoinstallers/autoinstaller-name/node_modules/
//  Get node_modules location

const path = require('path');
const node_modules = path.join(__dirname, '..', 'autoinstallers/rush-minimist/node_modules');

var argv = require(path.join(node_modules, 'minimist'))(process.argv.slice(2));

Enter fullscreen mode Exit fullscreen mode

E Voila! Works like a charm =)

Acquiring lock for "common\autoinstallers\rush-minimist" folder...
Autoinstaller folder is already up to date

{ _: [], edit: 'Hello world!' }
Enter fullscreen mode Exit fullscreen mode

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (3)

Collapse
 
novaastra profile image
NovaAstra

And I use fnm or vnm to manage the node version, nvmrc cannot take effect on the entire rush directory

Collapse
 
novaastra profile image
NovaAstra

I want to execute rush add to install dependencies for subprojects in the root directory, just like pnpm --filter, or enhance the rush add --to function. Is there a reliable implementation?

Collapse
 
shafferchance profile image
Chanceler Shaffer

What you're talking about is a little against what pnpm and rush does though. Maybe rush isn't the right solution for your repository. PNPM workspaces alone might be better based on that requirement.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more