What did you just install? · slopware 101

slopware 101 · step 12 of 35

What did you just install?

by · 3 min read

Once a program can read a key, the code running inside that program matters too. Installing a package brings other people's code into the project, and some packages run scripts during installation. The package may depend on others, so the decision reaches beyond the one name you typed.

The tree under one name

Say the to-do app wants friendlier due dates and a model suggests a date library.

npm install date-fns

package.json gains one line. node_modules gains that package, everything it depends on, and everything those depend on. To see why something you never asked for is there, ask npm.

npm explain some-package
npm ls --all

The first shows the chain that pulled a package in, and the second prints the whole tree. In a typical project, the packages you chose are a small fraction of the entries.

Ranges are promises about the future

The line in package.json looks like this.

"date-fns": "^3.6.0"

The caret means any 3.x version at or above 3.6.0. It is a promise about the future: whatever these people publish next is welcome, as long as they number it as a minor or patch release. Semantic versioning is a convention, and a minor release can still break you.

The lockfile

package-lock.json records the exact version, location and integrity hash of every package in the tree. Commit it.

With a lockfile present, npm install installs the locked versions as long as they still satisfy package.json. It changes the lockfile when you add or update a package, or when package.json asks for something the lock no longer satisfies. So installs are not random. Without a committed lockfile, though, every fresh machine resolves the ranges on the day it installs and gets whatever is newest within them. That is one way a project that works on your laptop fails on the deploy.

For deploys and CI, use npm ci. It requires a lockfile, removes node_modules, and installs exactly what the lock says. If package.json and the lockfile disagree, it fails instead of quietly fixing the lock. A mismatch failure is useful: it exposes inconsistent dependency metadata instead of silently producing a new resolution.

A few other differences show up between your machine and the server: a different Node version, which you can declare in package.json's engines field and select through the runtime manager or CI configuration (the declaration alone need not enforce it); packages that ship separate binaries for macOS and Linux; and dev dependencies that a production install leaves out on purpose.

Installing is a trust decision

A package can declare install scripts, preinstall, install and postinstall, that run during npm install, as you, with access to your files, your environment variables and the network. Even with no scripts, the package's code runs when your app imports it, on your server, next to your secrets.

So every install trusts the author, the authors of every dependency, and anyone who could publish under their accounts. npm install --ignore-scripts skips install scripts, though some packages genuinely need them to build. It narrows one door and leaves the import open, which is why the choice of what to install matters more than any flag.

Names that come from a model

For years the classic trick relied on typos: register a name one letter off a popular one and wait. Now names also arrive from models, spelled confidently, inside working code. Models sometimes suggest packages that do not exist, plausible names that sound right for the job, and the same invented names can come back across many conversations. Anyone can register such a name and wait for the installs. This has been named slopsquatting. Spelling carefully does not help when the name itself was invented.

Before installing a name you have not seen before, look it up.

npm view date-fns

Check that it exists, who publishes it, when it was first published, how many versions it has, and whether it links to a repository. A package created last month, with no repository, for a job that well known libraries already do, deserves a second look. When you are integrating a service, its own documentation should name the package. Use that name, not the one in a code sample from somewhere else.

Your lockfile

Open package-lock.json and find the package you installed most recently. Run npm explain on something you do not recognise and see who brought it in. Then check what your deploy actually runs. For an npm project with a committed compatible lockfile, use npm ci in CI. Preserve any resolution options the lockfile requires, and check the project's package manager before changing its install command.

Further reading: npm: clean installs.