This doc aims to provide guidelines for standardization of NPM scripts. Standardized approaches for building and testing let us run workspace commands across mono repo packages more effectively.
We have decided to use NX as our build system. For NX to cache effectively, it becomes even more important that we need to standardize our script names.
Cacheable NX Script Names
The following script names should be supported, when applicable, by our packages. They are considered cacheable and therefore once executed, if there are no changes in the package’s source, they should effectively run immediately when re-executed.
NX Task Dependencies
It’s important to know that NX allows npm scripts to depend on one another. You can always generate the most up to date view of all known NX dependencies by running
The dependencies between common targets can be seen in the
nx.json file in the project root. At the time of writing, the tree looks something like this, with children depend on parents:
For example, if someone were to execute
nx run fxa-auth-server:start, which starts the auth server, then the
prebuild tasks would be run automatically. Ideally their states are already in the NX cache, and these tasks come with zero penalty, however, if they are not in the NX cache, NX will execute these tasks and make sure they have completed prior to running the start script!
These dependencies also work across packages. So if fxa-shared has changed, and we execute
nx run fxa-auth-server:restart, a
build will happen on
fxa-shared, prior to auth server being restarted!
Creating a library in NX
The following command can be used to create a Nx library in the monorepo:
nx g @nx/node:library <name of library> --directory=<path/to> --buildable
nx g @nx/node:library capability --directory=libs/payments --buildablewill create a Nx library with the directory
You can use the
--dry-run flag to see what will be generated.
After generating the Nx library, you will also need to make the following changes in the library (see other libraries in the monorepo for examples):
- Rename the job in
test-integrationdepending on the library, and update the command to run unit tests in the README as well
- For more information, see FXA-8120
- Add the open source legal blurb to the top of source code and test TS files
- Rename TS files to better reflect the module in question (e.g. if adding a library in
At the moment (Sept 2023), we see a validation warning regarding
coverageDirectory when running the test command. This is a bug in Nx and does not affect our potential use of code coverage analysis tools. This warning can be safely ignored for now.
General Formatting Of Script Names
We will use dashes to separate subtasks. i.e. build-ts or test-unit. We want to group similar tasks together, so use prefixes accordingly. For example build-ts is preferred over ts-build. This way if other types of assets need to be built, they will be structured in a similar way and will be grouped logically when put into alphabetical order.
We should be intentional with the use of
: characters in script names. For example, test:watch isn't a good name. It turns out that adding a colon character makes the command globally runnable. So in general, we should not use this notation. Furthermore nx uses colons to help target projects and project configs, so we should avoid using them to avoid confusion there.
General Guidelines for Npm Scripts
Follow Naming Conventions
This has already been mentioned, but in a mono repo consistency is king. We want npm scripts names to be consistent and predictable across all packages. If you need a new type of script, ping the fxa-team channel and get feedback on what to call it.
Avoid Shell Scripts When Possible
We’d like to avoid shell scripts from our npm scripts. There are few reasons for this:
- Shell scripts are difficult to test
- Shell scripts don’t work on all operating systems, or even worse from shell to shell.
- Shell scripts encourage branching logic and special cases that have to be understood
- Oftentimes shell scripts obscure standard functionality of the underlying library they invoke.
In the event a script like thing is needed we should look to:
- See if there’s something else that can facilitate, ie grunt, rescripts, ect…
- Write a .cjs or .mjs script that can be put under test and is system agnostic.
Keep Scripts Small and Targeted
We’d like to keep scripts targeted. Lots of dependencies between scripts is not necessary, keeping scripts small and to the point makes it easier to compose actions. Ideally each script has a single purpose. Note, that now that we are using NX, we can also leverage nx to manage script dependencies.
For example, we can require that all build operations run the prebuild operation first, and furthermore require that all downstream projects run first. This extra ability to define dependencies between scripts really helps keeping the scripts small and targeted! Take advantage of this whenever possible.
Avoid custom scripts for existing options
We’d like to avoid creating scripts for specific options. For example, consider this:
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
This could simply be
test, and then the user could supply the extra arguments. For example with yarn:
yarn test – –watch or with nx:
nx run fxa-settings:test --skip-nx-cache -- --watchAll=true. This keeps our scripts simple and flexible. Common usages should be documented in the readme file for those who aren’t as familiar with the cli options of the underlying packages. Most packages have pretty decent behaviors out of the box. If a bunch of options are required, a step back might be necessary to ask why.
Use environment variables and config to target dev no dev operations. For example rather than having a start-prod script, simply make sure that the start script can be configured.
Avoid relying on
preinstall. These are very specific tasks that get run automatically by yarn / npm. The danger here is two fold.
First, since these tasks happen implicitly, they are often overlooked or forgotten. There were a lot of extra build operations occurring in our CI pipelines because of his. Second, because they happen automatically, we don’t have a ton of control over them. There are situations where a postinstall makes sense, but this is primarily for third party packages that need to compile system libraries after installation, not for simply setting of string events in a mono repo after install. See this for more info about not abusing post install.
Rather relying on post install, we should simply run
nx run-many -t build –all after switching branches. This can even be done with a git hook if a seamless behavior is desired when switching between branches.
Don’t cross reference scripts directly
Now that we have NX, we should not see something like this
"build": "tsc --build ../fxa-react &&.... NX can wrangle these dependencies for us!
Stick with existing script names
Here’s a run down of our current sanctioned script names. Before coming up with your own script name, check this list. If you need a new script name, that’s fine, but add it to the list and document the purpose.
- build - Top level build command, executes build-* commands.
- build-ts - Build typescript
- build-css - Builds tailwind, sass, etc… and produced css bundles
- build-js - Builds / bundles js
- build-react - Builds / bundles react
- build-storybook - Generates storybooks
- build-l10n - Creates l10n assets and places them in correct location
- build-l10n-for-test - Creates l10n assets specific for testing new language strings
- build-legal - Creates legal assets and places them in the correct location
- build-finalize - Any extra post build steps that are required
- format - Audio formats / pretties code
- l10n-prime - Pulls latest l10n resources
- l10n-bundle - Bundles l10n files files and places them in the correct location
- l10n-merge - Merges in l10n files with existing l10n strings
- l10n-tos-pp - Legacy l10n process
- legal-prime - Pulls latest legal resources
- lint - Lints code
- test - Runs all test suites
- test-unit - Runs unit tests
- test-integration - Runs integration tests