// The Verdaccio (npm registry) client — verdaccio's own code, living in the module (novox/hq // ADR 0044). Both this module's tools and its events entrypoint import it, and nothing outside // verdaccio does. export interface VerdaccioPackage { name: string; version?: string; description?: string; time?: string; } export interface PackageInfo { name: string; latest?: string; versions: string[]; description?: string; modified?: string; } export class VerdaccioClient { readonly baseUrl: string; // A bearer token is optional: package listing and reading are public on most registries, so the // token is sent only when configured, for a registry that gates reads behind auth. constructor( url: string, private readonly token?: string, ) { this.baseUrl = url.replace(/\/+$/, ""); } /** * Build from the module's resolved environment. The URL is MESH_VERDACCIO_URL (or the local * port); an optional MESH_VERDACCIO_TOKEN authenticates. Throws when no URL is configured. */ static fromEnv(env: NodeJS.ProcessEnv = process.env): VerdaccioClient { const url = env.MESH_VERDACCIO_URL ?? `http://127.0.0.1:${env.VERDACCIO_PORT ?? "4873"}`; if (!url) throw new Error("no verdaccio URL — set MESH_VERDACCIO_URL"); return new VerdaccioClient(url, env.MESH_VERDACCIO_TOKEN); } private async getJson(path: string): Promise { const res = await fetch(`${this.baseUrl}${path}`, { headers: { Accept: "application/json", ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), }, }); if (!res.ok) throw new Error(`Verdaccio ${path}: ${res.status} ${await res.text()}`); return res.json() as Promise; } /** * Every package the registry hosts, from Verdaccio's own web API — the same list its UI shows. * Each entry carries the latest version and the time it was last published. */ async listPackages(): Promise { const raw = await this.getJson("/-/verdaccio/data/packages"); return (raw ?? []).map((p) => ({ name: p.name, version: p.version ?? p["dist-tags"]?.latest, description: p.description, time: p.time?.modified ?? p.time, })); } /** * The full detail of one package — its dist-tags, every published version, and timestamps — * from the standard npm packument endpoint (`GET /`). */ async getPackageInfo(name: string): Promise { const doc = await this.getJson(`/${encodeURIComponent(name).replace(/%2F/g, "/")}`); return { name: doc.name ?? name, latest: doc["dist-tags"]?.latest, versions: Object.keys(doc.versions ?? {}), description: doc.description, modified: doc.time?.modified, }; } }