Art-Monorepo
Art-monorepo is a tool for managing a JavaScript monorepo's dependencies. It is an opinionated tool with one core design principle:
- All packages in the monorepo should use the same version of any external dependencies.
Given that principle, it follows that there should only be one package-lock.json file and one node_modules/ folder - in the root of the entire monorepo.
Unlike other monorepo tools, Art-monorepo is flexible about folder structure. It simply scans for package.json files in any subfolder, allowing you to organize packages however you want. This makes it easy to group related packages together and selectively run commands on specific package groups based on their paths.
The main usage of art-monorepo is running the art-monorepo sync-install command.
Art-monorepo also has handy tools for running commands on all packages such as tests, any npm-script or any shell-command.
Quick Start Guide
Add to Existing Repo
You can start adding sub-packages right away to an existing repo without any major refactor. Just be sure to use the --preserveRootDependencies option when running art-monorepo sync, or set it once in an art-monorepo.config.json file (see Config File). Here's how:
Install art-monorepo:
npm install art-monorepo --save-dev
Add scripts to your package.json:
{
"scripts": {
"monorepo:sync": "npx art-monorepo sync --preserveRootDependencies",
"monorepo:sync:install": "npx art-monorepo sync-install --preserveRootDependencies",
"monorepo:sync:reset:install": "npx art-monorepo sync-reset-install --preserveRootDependencies",
// optional shortcuts to test, build and clean both your core package and all sub-packages
"monorepo:test": "npm run test && npx art-monorepo test", // test all packages
"monorepo:build": "npm run build && npx art-monorepo build", // build all packages
"monorepo:run": "npx art-monorepo run -- ", // npm run XXX on all packages with that script defined
"monorepo:exec": "npx art-monorepo exec -- " // run shell command XXX on all packages
}
}
Fresh Mono Repo Quick Start (Recommended)
Although you can use art-monorepo where the root package is also a "code" package. I recommend that the root package should ONLY be fore managing the monorepo. It is generally cleaner and more flexible as it doesn't give one package (the root package) special consideration compared to other, sub-packages.
Install art-monorepo:
npm install art-monorepo --save-dev
Add scripts to your package.json:
{
"scripts": {
"sync": "npx art-monorepo sync",
"sync:install": "npx art-monorepo sync-install",
"sync:reset:install": "npx art-monorepo sync-reset-install",
// optional shortcuts to test, build and clean both your core package and all sub-packages
"test": "npx art-monorepo test", // test all packages
"build": "npx art-monorepo build", // build all packages
"monorepo:run": "npx art-monorepo run -- ", // npm run XXX on all packages with that script defined
"monorepo:exec": "npx art-monorepo exec -- " // run shell command XXX on all packages
}
}
Running Art-Monorepo
You can run Art-Monorepo anywhere with:
npx art-monorepo
If you install it globally:
npm install -g art-monorepo
You can run it with just:
art-monorepo
Commands Overview
Extended help is available via the --help flag for all commands. Not all options available are listed here, but the command-line help will always be up to date and complete.
Settings you use on every run, such as --preserveRootDependencies and the list of folders to ignore, can be set once in an art-monorepo.config.json file. See Config File.
Core Commands
art-monorepo sync [--preserveRootDependencies, --quiet]: Syncs the root package.json with all sub-packages.art-monorepo sync-install [--preserveRootDependencies, --quiet]: Runsart-monorepo syncand then runsnpm install.art-monorepo sync-reset-install [--preserveRootDependencies, --quiet]: Full, fresh rebuild of all installed packages in the monorepo.
Utility Commands
art-monorepo build [--folder, --sync, --verbose]: Runsnpm run buildin every subfolder with a package.json file.art-monorepo test [--folder, --sync, --verbose]: Runsnpm testin every subfolder with a package.json file.art-monorepo run X [--folder, --sync, --verbose]: Runsnpm run Xin all packages.art-monorepo exec X [--folder, --sync, --verbose]: Run shell command X in every subfolder with a package.json file.art-monorepo versions [--verbose]: Show all package versions (local and published).art-monorepo check: Checks that the monorepo is clean (only one package-lock.json and one node_modules folder in the root).art-monorepo clean: Advanced: Delete all package-lock.json files and node_modules folders in all subfolders.
Example Usage
Synchronizing Dependencies
The main use of art-monorepo is to synchronize your monorepo's dependencies. Simply run:
art-monorepo sync-install
Or you can do this in two steps:
art-monorepo sync
# review how sync updated your package.json files
npm install
This ensures all your packages are aligned in terms of dependency versions. It also allows you to develop multiple packages in parallel as any cross-dependencies within the monorepo will be bound to local versions.
How Sync Works
Sync ensures:
- All sub-packages use the same version of each external dependency.
- All internal cross-package references are updated to the latest version and linked to use the local packages instead of published npm versions.
- The root package.json includes all external dependencies used in all sub-packages.
Sync is done in three steps:
- Clean: Removes all package-lock.json files and node_modules folders in sub-packages. The root should be the only place with a lockfile and installed modules.
- Update sub-packages: Aligns all sub-package dependency versions with each other and the root. Version resolution:
- If the package is already present in the root package.json file, that version is used.
- If the package exists in multiple sub-packages and the versions are different, an error is reported.
- Update root: Merges in any new or updated dependencies from sub-packages and ensures all cross-package dependencies use the "file:..." npm protocol (i.e. all packages are linked to use the local packages instead of published npm versions).
About the preserveRootDependencies Option
By default, the root package.json is treated as a monorepo manager only—not an active code package. Its dependencies field is fully regenerated to represent the union of all sub-package dependencies and devDependencies. Its devDependencies field is untouched (reserved for monorepo tools like art-monorepo). Any root dependency not used in a sub-package will be removed.
If the root is also used for coding, use the --preserveRootDependencies option. This prevents pruning of root dependencies. Instead, the tool merges sub-package dependencies into the root. You are responsible for removing obsolete dependencies manually.
Config File
Settings you would otherwise repeat on every command line can live in an art-monorepo.config.json file in the root of the monorepo:
{
"preserveRootDependencies": true,
"ignore": ["packages/legacy", "experiments"]
}
preserveRootDependencies
Default false. Setting it to true is the same as passing --preserveRootDependencies to sync, sync-install and sync-reset-install, which keeps root dependencies that no sub-package uses (see About the preserveRootDependencies Option). An explicit command-line value still wins, so --preserveRootDependencies=false overrides the config for one run.
ignore
Default []. A list of folder paths, relative to the monorepo root, whose packages art-monorepo should leave alone. Each entry names the folder that contains a package.json, not the package.json file itself. The folder and everything below it is skipped when art-monorepo searches for packages, so it applies to every command: sync, test, build, run, exec, versions, check and clean.
Ignored folders are outside the monorepo as far as art-monorepo is concerned. They may keep their own node_modules/ and package-lock.json, check does not count them as dirty, and clean never deletes inside them. Their dependency versions are not unified with the rest of the monorepo and their names are not added to the root package.json.
Paths are matched as written, with a leading ./ and any trailing / tolerated: "./packages/legacy/" and "packages/legacy" mean the same thing. Because the entries are handed to the file search as patterns, glob syntax such as "packages/legacy-*" also works, but plain paths are the intended use.
Behavior
- When the file is missing, all defaults apply.
- Unknown keys produce a warning and are otherwise ignored.
- Invalid JSON, or a value of the wrong type, stops the command with an error.
- When the file sets anything, each command prints a one-line summary of the active settings.
--quietsuppresses it.
Batch Commands
Art-monorepo provide several tools for batch running commands on all your packages. For each of these tools, you can optionally run them on only packages within a given sub-folder (with --folder XYZ) and you can optionally run them synchronously, one at a time (with --sync). By default they will all run in parallel.
By default, the outputs of commands that succeed are not shown. Use --verbose to show the outputs of all commands.
Running Tests Across all Sub Packages
art-monorepo test
This command executes npm run test in every subfolder containing a package.json file.
Running Scripts on all Sub Packages
art-monorepo run script-name
If a package doesn't have a script with the matching name, it'll just be skipped.
> art-monorepo run foobar
FOOBARING: 36 packages
SKIPPED: packages/neptune-namespaces-runtime (package.json does not have a 'foobar' script)
SKIPPED: packages/neptune-namespaces (package.json does not have a 'foobar' script)
RESULTS:
succeeded: 0
skipped: 2
Running Arbitrary Commands
art-monorepo exec "ls -la" [--folder, --sync, --verbose]
Execute any shell command in all packages of the monorepo. If you provide the --folder argument, it will only run the command on packages within that subpath. Any options you wish to pass to your command should be within the quoted command itself. E.g.: "ls -la".
Full Reset and Rebuild
If you encounter issues with npm creating multiple package-lock.json files or node_modules folders, you can perform a complete reset:
art-monorepo sync-reset-install
This command:
- Synchronizes: all package.json files in the monorepo
- Resets: removes all
./package-lock.jsonand./node_modules/in the monorepo, including the root - Installs: Runs
npm installin the root - Checks: that the monorepo is now clean (only one package-lock.json and one node_modules folder in the root)
NOTE 1: This rebuilds the root
./package-lock.json, so some dependencies may be updated. NOTE 2: This triggers a fresh install of all packages in the monorepo, so it may be slow if you have a lot of packages.
Common Activities
Add a Dependency to a Sub-Package
cd packages/foointo the sub-package's foldernpm install xyzyour dependency - this will create a temporary node_modules/ and package-lock.json, but that's OKcd ../..back to the rootart-monorepo sync-install- syncs the dependency into the root package.json
- removes the temporary sub-package's node_modules/ and package-lock.json
- installs the new dependency in the root node_modules/ and package-lock.json
Add a new Sub-Package
- Create a new folder and create files following the pattern of one of the other sub-packages. Most importantly, the
namein the package.json file must be unique and the name you plan to use to import/require that package elsewhere. - In the root:
art-monorepo sync-install
Exclude a Folder from the Monorepo
Sometimes a folder holds a package you don't want managed: a vendored project with its own lockfile, an experiment that pins incompatible versions, or a package mid-migration. List it under ignore in art-monorepo.config.json in the root:
{
"ignore": ["vendor/legacy-app", "experiments"]
}
From then on every command skips those folders and everything below them. See Config File for details.
Check Monorepo Health
art-monorepo check
This verifies that your monorepo follows the core principle - only one package-lock.json and one node_modules folder in the root.
View Package Versions
art-monorepo versions
This shows all local and published versions of all packages, and if they are out of sync, a brief status on what is different. This is a powerful tool if you are publishing many packages and are trying to keep them all in sync.
Tricks
Publish all Packages in Sync
Sometimes I just want a quick way to publish all of my packages up to date and in sync with each other. This short script does that fairly well and works in most cases.
art-monorepo exec `npm version patch` # version bump everything - just in case
art-monorepo sync # sync all cross-dependencies to use the latest version
art-monorepo exec `npm publish` # publish!
git commit . -m 'patched, synced and published all packages'