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"
}
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"
}
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
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"]
}
]
}
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));
Run the command
rush install
rush print-arguments --arg1 "Hello world!"
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
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 shellPATH
.
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'
]
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));
E Voila! Works like a charm =)
Acquiring lock for "common\autoinstallers\rush-minimist" folder...
Autoinstaller folder is already up to date
{ _: [], edit: 'Hello world!' }
Top comments (3)
And I use fnm or vnm to manage the node version, nvmrc cannot take effect on the entire rush directory
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?
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.