Nightly builds fail for boring reasons far more often than interesting ones. The Node version on the runner drifts from what the project expects, a stale node_modules hides a dependency change, or a build log gets overwritten before anyone reads it. Shell aliases are a lightweight way to fix this: you encode the steps once, in your shell config, and every run follows the same sequence.
This article teaches one complete technique: building a small set of aliases that pin the environment, run the build with timing and a persistent log, and fail loudly when something goes wrong. You can use it without any external tooling beyond bash or zsh, nvm, and npm.
The goal: one command, one predictable result
A good nightly alias should do four things:
- Select the same Node version every time.
- Start from a clean dependency state, so results don't depend on leftovers.
- Record timing, exit status, and output to a dated file.
- Propagate the real exit code so cron, CI wrappers, or your own scripts can react.
Put the aliases in ~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish (fish uses alias with slightly different quoting, so test it). After editing, run source ~/.bashrc or open a new terminal.
Step 1: Pin the Node version
The most common nightly failure is an environment mismatch. Commit an .nvmrc file to the project root containing the exact version you target, for example 20.11.1. Then write:
alias nvm-use='nvm use $(cat .nvmrc 2>/dev/null || echo 20)'
The fallback matters. If .nvmrc is missing, the alias silently chooses a default, so it is worth adding a check that prints a warning in that case. A simple version:
nvm-use() {
if [ ! -f .nvmrc ]; then
echo "warning: no .nvmrc found, falling back to Node 20" >&2
fi
nvm use "$(cat .nvmrc 2>/dev/null || echo 20)"
}
Using a function instead of an alias gives you room for logic. Prerequisites: nvm installed, and the version listed in .nvmrc actually available locally (nvm install it once).
Step 2: Clean install for reproducibility
A nightly build should not trust whatever is sitting in node_modules. A clean install with the lockfile is the standard approach:
npm-clean-install() {
rm -rf node_modules
npm ci
}
Note the choice of npm ci over npm install. npm ci installs exactly what package-lock.json specifies and fails if the lockfile and package.json disagree, which is what you want from an unattended job. Avoid deleting the lockfile in the nightly path; if you delete it, you lose the reproducibility you are trying to build. Clearing the npm cache is rarely necessary and can slow runs down, so reserve it for debugging a corrupted cache.
Step 3: Build with timing and a dated log
The build alias should run the script, capture its real exit status, and write everything to a file named by date. Writing a function keeps the logic readable:
build-nightly() {
local logfile="nightly-build-$(date +%Y%m%d).log"
local start end status
start=$(date +%s)
{
echo "=== Nightly build started $(date) ==="
npm run build
status=$?
end=$(date +%s)
echo "=== Finished $(date) (duration: $((end - start))s, exit: ${status}) ==="
} 2>&1 | tee -a "$logfile"
# PIPESTATUS keeps the npm exit code, not tee's
return ${PIPESTATUS[0]:-$status}
}
Two details are easy to get wrong. First, tee masks failures in pipelines, so PIPESTATUS is needed to recover the exit code of the command that actually ran. Second, log to a dated file with tee -a (append) rather than overwriting, so a rerun on the same day does not destroy earlier evidence. If your build tool writes its own log file, such as build.log, append its tail to the nightly file explicitly, and check that the file exists first.
Step 4: Run tests and fail on any problem
The test alias follows the same pattern, but the important rule is that it must return a non-zero status when tests fail, so that schedulers notice:
test-nightly() {
npm test
local status=$?
if [ "$status" -ne 0 ]; then
echo "Nightly tests failed on $(hostname) at $(date)" >&2
fi
return $status
}
If you want notifications, keep the endpoint out of your config file. Read it from an environment variable such as SLACK_WEBHOOK_URL and skip the call when it is unset, so the alias works on machines without that variable.
Step 5: Compose the nightly run
With the pieces in place, a single entry point ties them together and stops at the first failure:
nightly() {
nvm-use || return 1
npm-clean-install || return 1
build-nightly || return 1
test-nightly || return 1
echo "Nightly run completed successfully at $(date)"
}
The || return 1 chain is the key safety property. Without it, a failed install would still attempt a build against a broken tree and produce misleading logs.
Schedule it with cron if you want it unattended. Cron does not load your interactive shell config by default, so call the function through a login shell:
0 2 * * * bash -lc 'cd /path/to/project && source ~/.bashrc && nightly' >> ~/nightly-cron.log 2>&1
Common pitfalls
- Interactive-only assumptions. Aliases do not expand inside scripts run by cron unless you source the config. Use functions and source explicitly.
- Silent successes. Any step that swallows an exit code (a pipe without
PIPESTATUS,|| true, or--force) will let a broken build look green. Audit every line that could hide failure. - Hard-coded paths and secrets. Keep project paths relative to the working directory and secrets in environment variables.
- Forgetting the lockfile. If
npm cicomplains, fix the mismatch inpackage.jsonandpackage-lock.jsonrather than switching tonpm installin the nightly path.
Checklist before you rely on it
Run the deliberate-failure check once. A nightly job you have never seen fail is one you cannot trust. Once the aliases behave correctly on your own machine, the same file can be copied to a second machine and should produce the same sequence of steps and the same logs, which is the real measure of reproducibility.
If you are setting this up for a Node project for the first time, it helps to audit the project's scripts, lockfile health, and Node version policy before you automate anything. The steps above assume those basics are already sound.
Written with AI assistance and reviewed for accuracy before publishing.
This guide was researched and written with the help of AI and reviewed by an automated quality check. It is general educational information, not professional advice.