firebase / firebase/superstatic

Replace update-notifier-cjs with a zero-dependency equivalent (removes 46 packages)

Open
#573 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
1.1k
Forks
88
Avg merge
17h 43m
Merged PRs (30d)
2

Description

`update-notifier-cjs` pulls 46 packages into superstatic's dependency tree to check
whether a newer version has been published. That is 46 packages to resolve, audit and
trust, in service of one request to the registry and a box drawn on stderr. It is also
a fork that exists only because upstream `update-notifier` went ESM-only, and its last
release was January 2025.

I would like to propose replacing it with `nano-update-notifier`, which implements the
same public API — same options, same `notifier.update` shape, same suppression rules —
on Node.js built-ins alone, with no runtime dependencies. It ships both ESM and CommonJS
entry points, so the existing `require` keeps working.

The change is two lines: the manifest entry, and

```diff
-const updateNotifier = require("update-notifier-cjs");
+const updateNotifier = require("nano-update-notifier");
```

in `src/bin/server.ts`.

## Measured

Against `nano-update-notifier@1.1.0` at superstatic `4bd82b9`, on Node 22.14.0 and
npm 10.9.2. Every row is a real run — `npm ci` on a clean checkout, then the two-line
change, then `npm install`:

| | |
| --- | --- |
| Dependency delta | **`added 1 package, and removed 46 packages`** — npm's own words. 536 installed entries down to 491 |
| `npm run build` | clean, exit 0 |
| `npm run lint` | 994 problems, 0 errors — identical to the same run before the change |
| `npm run test-unit` | 181 passing, 10 pending, 19 failing — identical to the run before the change, and the set of failing test titles diffs empty against it |

Those 46 entries are 39 distinct package names. Five of them — `camelcase`,
`graceful-fs`, `semver`, `strip-json-comments` and `type-fest` — are nested copies whose
name survives elsewhere in the tree, and `ini` goes from three paths at once. I lead
with 46 because that is what `npm install` prints; the 39 below is the list of names.

The 19 failures are pre-existing on my machine, all `trailing slash` cases, which look
like Windows path handling rather than anything related to this change. What matters is
that the swap does not move them.

The 39 names: `@pnpm/config.env-replace`, `@pnpm/network.ca-file`, `@pnpm/npm-conf`,
`ansi-align`, `boxen`, `ci-info`, `cli-boxes`, `config-chain`, `configstore`,
`crypto-random-string`, `deep-extend`, `dot-prop`, `escape-goat`, `global-dirs`,
`has-yarn`, `import-lazy`, `ini`, `is-ci`, `is-installed-globally`, `is-npm`, `is-obj`,
`is-yarn-global`, `isomorphic-fetch`, `minimist`, `node-fetch`, `proto-list`, `pupa`,
`rc`, `registry-auth-token`, `registry-url`, `semver-diff`, `tr46`, `unique-string`,
`update-notifier-cjs`, `webidl-conversions`, `whatwg-fetch`, `whatwg-url`,
`widest-line`, `xdg-basedir`.

## The rendered notification is not byte-identical, and here is exactly how

I rendered the notification through superstatic's own call — `updateCheckInterval` of
one week, `shouldNotifyInNpmScript: true` — under both libraries and diffed the output.
Eight lines on each side. Lines 2 through 7, the borders, the padding and both text
lines, are byte-identical. Lines 1 and 8 are the blank margin lines, and they differ:

- `update-notifier-cjs` renders through boxen 5, which emits a yellow-open escape
immediately followed by a yellow-close on those lines.
- `nano-update-notifier` replicates boxen 7, which emits nothing.

Each of those two lines is 10 bytes under `update-notifier-cjs` — the escape pair
`ESC[33m` `ESC[39m` with nothing between them — and 0 bytes here, so the whole output
differs by 20 bytes and nothing else. On the run I measured that was 601 bytes against
581. Both render as a blank line in any terminal, and stripping ANSI makes the two
byte-equal. I would rather state that than claim an equality I cannot support.

## How compatibility is held

Differential test suites run `update-notifier`, `boxen`, `wrap-ansi`, `semver` and
`string-width` as oracles: the full box option matrix, notification rendering with
colour enabled, ANSI-aware wrapping, and fuzzed version comparison. 300 tests, green on
Ubuntu, Windows and macOS across Node 18, 22 and 24. The package is published from a
GitHub Actions release through npm trusted publishing, with SLSA provenance attached.

## Not verified

`test-integration`, and any run on Linux or macOS — I measured on Windows.

## Two things worth raising

**Disclosure: I wrote `nano-update-notifier`.** That is why this is an issue rather than
a pull request. CONTRIBUTING asks for discussion before a larger contribution, and a
proposal to adopt my own package deserves that more than most. If this is not something
you want to take on, saying so costs you nothing and I will not push it.

I understand the Google CLA is needed before anything can merge, and I am happy to sign
it. CONTRIBUTING notes it is not required until after review, so I am raising the idea
first rather than signing speculatively.

Contributor guide

Open the contributing guide

Research direction

Review the manifest entry and the existing require call in src/bin/server.ts, then compare nano-update-notifier's API with the current usage. Run npm ci followed by npm run build, npm run lint, and npm run test-unit; done means the dependency tree loses the reported packages and the existing build, lint, and test results do not regress.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend, tooling
Issue type
Refactor
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.