registry, verdaccio, portainer: full nox modules (ADR 0044/0046)
registry (docker v2): catalog/tags/delete tools, emits image.pushed (a build's image is now pullable). verdaccio (npm): list/info tools, emits package.published. portainer: endpoints/stacks/containers tools — tools-only, since its only events are the underlying containers' lifecycle, which the host owns. Typecheck; manifests parse.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
// 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<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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user