Skip to content

Milestone: Eliminate reliance on npm #160

@savetheclocktower

Description

@savetheclocktower

Have you checked for existing feature requests?

  • Completed

Summary

ppm relies on npm to do a lot of grunt work. But since ppm bundles its own Node, it also must bundle its own npm.

For reasons that I’m sure are complex and better left to @DeeDeeG to explain (or decide not to explain to avoid exhaustion), we maintain our own fork of the npm-cli repository. Of the commits that exist in that repo, I can identify ten that are definitely unique to our fork, and they suggest that the reasons to maintain an ongoing fork of npm-cli include

  • bumping node-gyp occasionally, and
  • bumping libnpm, libcipm, and npm-lifecycle (three dependencies of npm-cli that are themselves deprecated in favor of packages I'll mention below).

This is not the world’s most onerous chore, but there’s only one of @DeeDeeG, and I’d love to use their talents as politely as possible.

Can we outright eliminate dependence on npm? Let’s think it through.

How do we use NPM?

These are the places in the codebase I can identify where we explicitly shell out to npm:

  • ci.js shells out to npm ci
  • clean.js shells out to npm prune
  • config.js shells out to npm config
  • dedupe.js shells out to npm dedupe
  • install.js shells out to npm install
  • publish.js shells out to npm version
  • rebuild.js shells out to npm rebuild

There are other places where we use npm more like a library — require('npm'), then call methods on it — but these mainly involve reading configuration. Using npm in this manner is also deprecated and is worth replacing if we can.

So we can group these into tasks and then figure out other ways to accomplish the same tasks:

Dependency installation/cleanup

ci.js and install.js use npm for managing node_modules and reconciling it with package.json. We can use arborist for this; see #159.

arborist can also probably do the stuff done by npm prune, and I imagine by npm dedupe as well.

Reading NPM configuration

config.js delegates to npm for reading from and writing to configuration, since ppm has a need for many of the configuration keys that npm does, and wants to manage them the same way.

We could instead manage this with @npmcli/config.

Building/rebuilding modules

ppm famously needs to compile packages with native module dependencies, so that’s why it shells out to npm rebuild. But npm itself mainly delegates this task to node-gyp, so we could do the same.

That said: build scripts don’t actually have to use node-gyp. So what we really want here is the ability to run arbitrary package scripts, including build and rebuild — hence we also want the @npmcli/run-script package.

Creating tags and commits

Publishing a Pulsar package is quite different from publishing an NPM package, so publish.js doesn’t actually call npm publish. But it does leverage npm version to handle the chore of bumping a version number and generating a Git commit and tag.

This doesn’t have a direct analog in library form. But the version incrementing is simple enough as to be achievable via the semver library, and the Git-related tasks are straightforward as well. We could just shell out to git for those, since we require it for package publishing anyway.

What benefits does this feature provide?

Atom never seemed to have their own fork of npm — so why did we have to fork it? The original PR says it was to allow us to bump node-gyp to 9.4.

I don't have enough context to know why forking was necessary to achieve that goal. But I do know that ppm also depends directly on node-gyp, so it would make things simpler if we didn't have to bump it in two different places.

Any alternatives?

Not realistically. The options are the status quo (ad-hoc usage of npm for all its management tasks) and the more modern approach of relying on single-purpose libraries that offer more hygienic access to the same functionality.

Other examples:

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions