From 59615e897364640413d4b8394beb7ca604f83c25 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 5 Aug 2026 10:30:30 -0400 Subject: [PATCH] Add npm packages to info.json and latest.json Each plugin published to npm now declares its package, which the dprint CLI uses to write an npm specifier into config files instead of a url. Verified for each package that it resolves on the registry, that its tarball contains the plugin.wasm/plugin.json the CLI resolves out of it, and that its latest version matches the plugin's latest GitHub release. No package exists for jolars/panache or drluckyspin/swift. latest.json serves the same property, mapped from info.json rather than duplicated. --- info.json | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ plugins.test.ts | 15 ++++++++++++ plugins.ts | 30 +++++++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/info.json b/info.json index 7c4dfd9..be3c354 100644 --- a/info.json +++ b/info.json @@ -6,6 +6,9 @@ "name": "dprint-plugin-typescript", "description": "TypeScript/JavaScript code formatter.", "website": "https://dprint.dev/plugins/typescript/", + "npm": { + "name": "@dprint/typescript" + }, "selected": true, "configKey": "typescript", "keywords": [ @@ -33,6 +36,9 @@ "name": "dprint-plugin-json", "description": "JSON/JSONC code formatter.", "website": "https://dprint.dev/plugins/json/", + "npm": { + "name": "@dprint/json" + }, "selected": true, "configKey": "json", "keywords": [ @@ -51,6 +57,9 @@ "name": "dprint-plugin-markdown", "description": "Markdown code formatter.", "website": "https://dprint.dev/plugins/markdown/", + "npm": { + "name": "@dprint/markdown" + }, "selected": true, "configKey": "markdown", "keywords": [ @@ -72,6 +81,9 @@ "name": "dprint-plugin-toml", "description": "TOML code formatter.", "website": "https://dprint.dev/plugins/toml/", + "npm": { + "name": "@dprint/toml" + }, "selected": true, "configKey": "toml", "keywords": [ @@ -87,6 +99,9 @@ "name": "dprint-plugin-dockerfile", "description": "Dockerfile code formatter.", "website": "https://dprint.dev/plugins/dockerfile/", + "npm": { + "name": "@dprint/dockerfile" + }, "selected": false, "configKey": "dockerfile", "keywords": [ @@ -106,6 +121,9 @@ "name": "dprint-plugin-biome", "description": "Biome (JS/TS/JSON) wrapper plugin.", "website": "https://dprint.dev/plugins/biome/", + "npm": { + "name": "@dprint/biome" + }, "selected": false, "configKey": "biome", "keywords": [ @@ -135,6 +153,9 @@ "name": "dprint-plugin-oxc", "description": "Oxc (JS/TS) wrapper plugin.", "website": "https://dprint.dev/plugins/oxc/", + "npm": { + "name": "@dprint/oxc" + }, "selected": false, "configKey": "oxc", "keywords": [ @@ -161,6 +182,9 @@ "name": "dprint-plugin-mago", "description": "Mago (PHP) wrapper plugin.", "website": "https://dprint.dev/plugins/mago/", + "npm": { + "name": "@dprint/mago" + }, "selected": false, "configKey": "mago", "keywords": [ @@ -177,6 +201,9 @@ "name": "dprint-plugin-ruff", "description": "Ruff (Python) wrapper plugin.", "website": "https://dprint.dev/plugins/ruff/", + "npm": { + "name": "@dprint/ruff" + }, "selected": false, "configKey": "ruff", "keywords": [ @@ -195,6 +222,9 @@ "name": "dprint-plugin-jupyter", "description": "Jupyter notebook code block formatter.", "website": "https://dprint.dev/plugins/jupyter/", + "npm": { + "name": "@dprint/jupyter" + }, "selected": false, "configKey": "jupyter", "keywords": [ @@ -212,6 +242,9 @@ "name": "jakebailey/gofumpt", "description": "Gofumpt (Go) wrapper plugin.", "website": "https://dprint.dev/plugins/gofumpt/", + "npm": { + "name": "@jakebailey/dprint-plugin-gofumpt" + }, "selected": false, "configKey": "gofumpt", "keywords": [ @@ -229,6 +262,9 @@ "name": "g-plane/malva", "description": "CSS, SCSS, Sass and Less formatter.", "website": "https://dprint.dev/plugins/malva/", + "npm": { + "name": "dprint-plugin-malva" + }, "selected": true, "configKey": "malva", "keywords": [ @@ -252,6 +288,9 @@ "name": "g-plane/markup_fmt", "description": "HTML, Vue, Svelte, Astro, Angular, Jinja, Twig, Nunjucks, and Vento formatter.", "website": "https://dprint.dev/plugins/markup_fmt/", + "npm": { + "name": "dprint-plugin-markup" + }, "selected": true, "configKey": "markup", "keywords": [ @@ -298,6 +337,9 @@ "name": "g-plane/pretty_yaml", "description": "YAML formatter.", "website": "https://dprint.dev/plugins/pretty_yaml/", + "npm": { + "name": "dprint-plugin-yaml" + }, "selected": true, "configKey": "yaml", "keywords": [ @@ -316,6 +358,9 @@ "name": "g-plane/pretty_graphql", "description": "GraphQL formatter.", "website": "https://dprint.dev/plugins/pretty_graphql/", + "npm": { + "name": "dprint-plugin-graphql" + }, "selected": false, "configKey": "graphql", "keywords": [ @@ -332,6 +377,9 @@ { "name": "bartlomieju/lax-css", "description": "CSS, SCSS, and Less formatter that never reinterprets your styles.", + "npm": { + "name": "lax-css" + }, "selected": false, "configKey": "css", "keywords": [ @@ -352,6 +400,9 @@ { "name": "bartlomieju/lax-markup", "description": "HTML, XML, SVG, Vue, Svelte, and Astro formatter that preserves rendering-critical whitespace.", + "npm": { + "name": "lax-markup" + }, "selected": false, "configKey": "markup", "keywords": [ @@ -377,6 +428,9 @@ { "name": "bartlomieju/lax-sql", "description": "Dialect-agnostic SQL formatter.", + "npm": { + "name": "lax-sql" + }, "selected": false, "configKey": "sql", "keywords": [ @@ -438,6 +492,9 @@ "name": "dprint-plugin-prettier", "description": "Prettier wrapper plugin (consider faster dedicated plugins for JS/TS, CSS, etc.).", "website": "https://dprint.dev/plugins/prettier/", + "npm": { + "name": "@dprint/prettier" + }, "selected": false, "configKey": "prettier", "keywords": [ @@ -483,6 +540,9 @@ "name": "dprint-plugin-roslyn", "description": "C# and Visual Basic code formatter (Roslyn).", "website": "https://dprint.dev/plugins/roslyn/", + "npm": { + "name": "@dprint/roslyn" + }, "selected": false, "configKey": "roslyn", "keywords": [ @@ -504,6 +564,9 @@ "name": "dprint-plugin-exec", "description": "Formats code using formatting CLIs installed on the host machine (rustfmt, clang-format, shfmt, etc.).", "website": "https://dprint.dev/plugins/exec/", + "npm": { + "name": "@dprint/exec" + }, "selected": false, "configKey": "exec", "keywords": [ diff --git a/plugins.test.ts b/plugins.test.ts index d02952b..1511fc3 100644 --- a/plugins.test.ts +++ b/plugins.test.ts @@ -86,6 +86,7 @@ it("tryResolveUserLatestJson", async () => { url: `https://plugins.dprint.dev/typescript-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName, checksum: releaseInfo!.checksum, + npm: { name: "@dprint/typescript" }, }); } // dprint repo full name @@ -100,6 +101,7 @@ it("tryResolveUserLatestJson", async () => { url: `https://plugins.dprint.dev/typescript-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName, checksum: releaseInfo!.checksum, + npm: { name: "@dprint/typescript" }, }); } // non-dprint repo @@ -134,6 +136,19 @@ it("tryResolveUserLatestJson", async () => { url: `https://plugins.dprint.dev/prettier-${releaseInfo!.tagName}.json`, version: releaseInfo!.tagName, checksum: releaseInfo!.checksum, + npm: { name: "@dprint/prettier" }, + }); + } + // community repo published to npm + { + const result = await getValidResultForUrl("https://plugins.dprint.dev/g-plane/malva/latest.json"); + const releaseInfo = await getLatestReleaseInfo("g-plane", "malva"); + expect(result).toEqual({ + schemaVersion: 1, + url: `https://plugins.dprint.dev/g-plane/malva-${releaseInfo!.tagName}.wasm`, + version: releaseInfo!.tagName.replace(/^v/, ""), + checksum: releaseInfo!.checksum, + npm: { name: "dprint-plugin-malva" }, }); } diff --git a/plugins.ts b/plugins.ts index 47a161e..0027e33 100644 --- a/plugins.ts +++ b/plugins.ts @@ -1,3 +1,4 @@ +import infoJson from "./info.json" with { type: "json" }; import { checkGithubRepoExists, getLatestReleaseInfo } from "./utils/mod.js"; const tagPattern = "([A-Za-z0-9\._]+)"; @@ -57,6 +58,16 @@ const KNOWN_NON_PREFIXED_REPOS = new Set([ "lucacasonato/mf2-tools", ]); +/** The npm package a plugin is distributed as. */ +export interface PluginNpmInfo { + name: string; +} + +// the npm packages declared in info.json, keyed by `username/repo`. both the +// prefixed and unprefixed repo names are registered because either may be the +// resolved repo name (ex. `dprint/dprint-plugin-typescript` and `g-plane/malva`) +const npmPackagesByRepo = buildNpmPackagesByRepo(); + const APPROVED_ASSET_REPOS = new Set([ "drluckyspin/dprint-plugin-swift", ]); @@ -128,6 +139,8 @@ export async function tryResolveLatestJson(url: URL) { url: latestInfo.url, version: latestInfo.version, checksum: latestInfo.checksum, + // when set, the cli writes an npm specifier into config files instead of the url + npm: latestInfo.npm, }; } @@ -155,6 +168,8 @@ export async function getLatestInfo(username: string, repoName: string, origin: // downloads are recorded under and the tag of the latest release downloadKey: `${username}/${repoName}`, tag: releaseInfo.tagName, + // the npm package this plugin is published to, when it has one + npm: npmPackagesByRepo.get(`${username}/${repoName}`), }; } @@ -204,6 +219,21 @@ async function userRepoTagPatternMapper( return undefined; } +function buildNpmPackagesByRepo() { + const result = new Map(); + for (const plugin of infoJson.latest as { name: string; npm?: PluginNpmInfo }[]) { + if (plugin.npm == null) { + continue; + } + const slashIndex = plugin.name.indexOf("/"); + const username = slashIndex === -1 ? "dprint" : plugin.name.slice(0, slashIndex); + const shortName = plugin.name.slice(slashIndex + 1).replace(/^dprint-plugin-/, ""); + result.set(`${username}/${shortName}`, plugin.npm); + result.set(`${username}/dprint-plugin-${shortName}`, plugin.npm); + } + return result; +} + async function getFullRepoName(username: string, repoName: string) { if (repoName.startsWith("dprint-plugin-")) { return repoName;