Skip to content

Show npm specifiers and count npm downloads - #72

Merged
dsherret merged 3 commits into
mainfrom
npm-versions-and-downloads
Aug 5, 2026
Merged

Show npm specifiers and count npm downloads#72
dsherret merged 3 commits into
mainfrom
npm-versions-and-downloads

Conversation

@dsherret

@dsherret dsherret commented Aug 5, 2026

Copy link
Copy Markdown
Member

Builds on #71, which declared the npm package each plugin is published to.

npm versions

Each plugin's npm package now has its latest version resolved from the registry during the info file build and served under the npm property:

"npm": { "name": "@dprint/typescript", "version": "0.96.1" }

The object carries over whatever info.json declared rather than being rebuilt, so the other properties the CLI reads — currently path — survive. Serving { name, version } alone would drop path from info.json while latest.json, which passes the object through, kept it, and the same plugin would resolve to a different file depending on which endpoint the CLI read.

The version is additive for the CLI: PluginNpmInfo::parse takes name and path, and resolve_latest always asks npm for dist-tags.latest itself, since the registry file can lag behind what's actually published.

Site display

The "Latest URL" column and the copy button now show the npm specifier for a plugin published to npm, falling back to the plugins.dprint.dev url otherwise:

dprint-plugin-typescript   npm:@dprint/typescript@0.96.1
dprint-plugin-exec         npm:@dprint/exec@0.7.3/plugin.json
g-plane/malva              npm:dprint-plugin-malva@0.16.0
jolars/panache             https://plugins.dprint.dev/jolars/panache-0.1.0.wasm

This mirrors NpmSpecifier::display: the path within the package is omitted only when it's the plugin.wasm default, so a process plugin names its manifest and an explicit path renders in full (npm:@dprint/multi@2.0.0/json/plugin.wasm). Both references stay in the data and in the search text, so making this a user toggle later is a change to latestReference plus a control.

npm downloads

npm's trailing-30-day counts are added into downloadCount.allVersions, which previously counted only downloads of a plugin's url from the registry. The windows match — the analytics dataset is queried over 30 days and npm's last-month point endpoint is the trailing 30 days.

Worth knowing before merging: this changes the numbers by orders of magnitude and reorders the table. markdown (837k) and toml (794k) are inflated by Deno and CI installs and jump to the top, ahead of typescript (275k). currentVersion is left registry-only, since npm has no per-version breakdown over this window — so the currentVersion / allVersions ratio no longer means anything. If you'd rather keep the two readings separable I can emit downloadCount.npm as its own field instead of folding it in.

Failure behaviour

Every npm lookup resolves per package, so one package failing — or npm being down — leaves the rest of the build intact. A package that can't be resolved is left out of the map rather than counted as zero (npm answers 200 with an error body for a package it has no stats for, which would otherwise cache a real count as zero for an hour and persist it to R2). A plugin whose version lookup failed keeps its npm.name, so the CLI still knows it's on npm, and the site falls back to showing its url.

Lookups are wrapped in withTimeout at 10s, matching how GitHub calls are bounded, so a hung connection can't stall a request that had to rebuild the info file synchronously.

On a cold isolate with npm unreachable, a plugin's npm downloads drop out of its total for that build and are cached for up to 5 minutes before the next refresh corrects it. It self-heals, but the dip is visible. Carrying forward the last known-good counts would make it invisible — happy to add that if you want it.

Known gap

The specifier shown for the three process plugins (prettier, roslyn, exec) won't resolve as-is: npm_resolution.rs requires a checksum for a non-wasm plugin, and the required value is the npm tarball sha256, which nothing here fetches — the GitHub release checksum we already hold is a different artifact. This isn't a regression; the url shown for those three today fails the same way, and dprint's error prints the correct value to paste. Fixing it properly means downloading and hashing each package during the build.

Tests

utils/npm.test.ts covers versions and download counts over scoped, unscoped, unknown, and invalid names. homeView.test.ts covers every reference case — wasm, process, explicit path, uppercase extension, missing version, no npm package — asserting on the rendered <code> cells and data-url values so the displayed and copied strings can't drift apart. I mutation-tested it: pointing data-url back at plugin.url fails the suite.

tsc --noEmit is clean. 34 passed, with the 4 pre-existing failures from the expired GitHub token in .dev.vars (401 Bad credentials) — those fail on main too, and they're also why I couldn't exercise the full build end to end locally.

Each plugin published to npm now gets its latest version resolved from the
registry during the info file build, served as `npm.version`, and shown as
the plugin's reference on the site in place of its plugins.dprint.dev url.
The url stays in the data and in the search text so the two can become a
toggle later.

npm's trailing 30 day download counts are added into
`downloadCount.allVersions`, which previously counted only downloads of a
plugin's url from the registry. The windows match.

Every npm lookup resolves per package, so one package failing, or npm being
down, leaves the rest of the build intact. A package that can't be resolved
is left out rather than counted as zero, and a plugin whose version lookup
failed keeps its `npm.name` and falls back to displaying its url.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
dprint-plugins 93a62a7 Commit Preview URL

Branch Preview URL
Aug 05 2026, 04:36 PM

The per plugin GitHub release requests are serialized to stay within GitHub's
api guidelines, so they dominate a build's wall clock. Starting the analytics
and npm requests before that loop and awaiting them after hides their latency
behind it rather than adding to it.

Each falls back to an empty result, since a throw from the loop abandons them
unawaited. The merge those results feed is extracted out of the loop so the
shape of the served info.json can be tested directly.

Also bounds the npm retries the way the GitHub ones are — the default of 3
could spend over half the 10s timeout asleep between attempts.
The test builds the info file for real, which makes a serialized github
request per plugin, so it grows as plugins are registered and had reached
the 10s it was given.

Also registers the seven plugins whose repo names weren't in the known lists.
Each was costing an extra serialized github request per build to discover a
name we already know, on the site as well as in the test.
@dsherret
dsherret added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 8418d66 Aug 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant