Show npm specifiers and count npm downloads - #72
Merged
Conversation
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.
Deploying with
|
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
npmproperty:The object carries over whatever
info.jsondeclared rather than being rebuilt, so the other properties the CLI reads — currentlypath— survive. Serving{ name, version }alone would droppathfrominfo.jsonwhilelatest.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::parsetakesnameandpath, andresolve_latestalways asks npm fordist-tags.latestitself, 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.devurl otherwise:This mirrors
NpmSpecifier::display: the path within the package is omitted only when it's theplugin.wasmdefault, so a process plugin names its manifest and an explicitpathrenders 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 tolatestReferenceplus 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'slast-monthpoint 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).
currentVersionis left registry-only, since npm has no per-version breakdown over this window — so thecurrentVersion / allVersionsratio no longer means anything. If you'd rather keep the two readings separable I can emitdownloadCount.npmas 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
withTimeoutat 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.rsrequires 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.tscovers versions and download counts over scoped, unscoped, unknown, and invalid names.homeView.test.tscovers every reference case — wasm, process, explicit path, uppercase extension, missing version, no npm package — asserting on the rendered<code>cells anddata-urlvalues so the displayed and copied strings can't drift apart. I mutation-tested it: pointingdata-urlback atplugin.urlfails the suite.tsc --noEmitis clean. 34 passed, with the 4 pre-existing failures from the expired GitHub token in.dev.vars(401 Bad credentials) — those fail onmaintoo, and they're also why I couldn't exercise the full build end to end locally.