92 lines
3.2 KiB
TypeScript
92 lines
3.2 KiB
TypeScript
// The Verdaccio (npm registry) client — verdaccio's own code, living in the module (novox/hq
|
|
// ADR 0039). Both this module's tools and its events entrypoint import it, and nothing outside
|
|
// verdaccio does.
|
|
|
|
import { readFileSync } from "node:fs";
|
|
|
|
export interface VerdaccioPackage {
|
|
name: string;
|
|
version?: string;
|
|
description?: string;
|
|
time?: string;
|
|
}
|
|
|
|
export interface PackageInfo {
|
|
name: string;
|
|
latest?: string;
|
|
versions: string[];
|
|
description?: string;
|
|
modified?: string;
|
|
}
|
|
|
|
/** The settings-merged config the mesh delivers (novox/hq ADR 0046): { url, apiKey, token, password, user, ... }. */
|
|
function meshConfig(file?: string): Record<string, string> {
|
|
if (!file) return {};
|
|
try { return JSON.parse(readFileSync(file, "utf8")) as Record<string, string>; }
|
|
catch { return {}; }
|
|
}
|
|
|
|
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 cfg = meshConfig(env.MESH_VERDACCIO_CONFIG_FILE);
|
|
const url = cfg.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, cfg.token ?? env.MESH_VERDACCIO_TOKEN);
|
|
}
|
|
|
|
private async getJson<T>(path: string): Promise<T> {
|
|
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<T>;
|
|
}
|
|
|
|
/**
|
|
* 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<VerdaccioPackage[]> {
|
|
const raw = await this.getJson<any[]>("/-/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 /<name>`).
|
|
*/
|
|
async getPackageInfo(name: string): Promise<PackageInfo> {
|
|
const doc = await this.getJson<any>(`/${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,
|
|
};
|
|
}
|
|
}
|