import { isPlainObject } from '@dazl/resolve-directory-context'; import http from 'node:http'; import https from 'node:https'; import { FetchError, fetchText, isSecureUrl } from './http.ts'; export const officialNpmRegistryUrl = 'https://registry.npmjs.org/'; export interface NpmRegistryDistTags { latest: string; [tag: string]: string | undefined; } export class NpmRegistry { agent?: http.Agent | https.Agent; public url; private token; constructor(url: string, token?: string) { this.url = url; this.token = token; } public async fetchDistTags(packageName: string): Promise { this.ensureAgent(); const { url, token, agent } = this; const options: https.RequestOptions = { agent }; if (token) { options.headers = { authorization: `Bearer ${token}` }; } const responseText = await fetchText(new URL(`-/package/${packageName}/dist-tags`, url), options); const distTags = JSON.parse(responseText) as unknown; if (!isPlainObject(distTags)) { throw new Error(`expected an object response, but got ${String(distTags)}`); } return distTags as NpmRegistryDistTags; } public async fetchVersions(packageName: string): Promise { this.ensureAgent(); const { url, token, agent } = this; try { const options: https.RequestOptions = { agent }; if (token) { options.headers = { authorization: `Bearer ${token}` }; } const responseText = await fetchText(new URL(packageName, url), options); try { const packument = JSON.parse(responseText) as { versions: Record }; if (!isPlainObject(packument)) { throw new Error(`expected an object response, but got ${String(packument)}`); } if (!isPlainObject(packument.versions)) { throw new Error(`expected "versions" to be an object, but got ${String(packument.versions)}`); } const versions = Object.keys(packument.versions); return versions; } catch (parseError) { throw new Error(`Error while parsing registry response.\nResponse is:\n${responseText}`, { cause: parseError }); } } catch (error) { if ((error as FetchError)?.statusCode === 404) { return []; } else { throw error; } } } public dispose(): void { if (this.agent) { this.agent.destroy(); } this.agent = undefined; } private ensureAgent() { if (!this.agent) { this.agent = isSecureUrl(this.url) ? new https.Agent({ keepAlive: true }) : new http.Agent({ keepAlive: true }); } } } // https://github.com/npm/cli/blob/v10.5.0/workspaces/config/lib/nerf-dart.js export function uriToIdentifier(uri: string): string { const parsed = new URL(uri); const from = `${parsed.protocol}//${parsed.host}${parsed.pathname}`; const rel = new URL('.', from); const res = `//${rel.host}${rel.pathname}`; return res; }