import assert from 'assert'; import buildDebug from 'debug'; import got, { HTTPAlias, Response, Headers as gotHeaders } from 'got-cjs'; import { isNil, isObject, isRegExp } from 'lodash'; import { API_MESSAGE, HEADERS, HTTP_STATUS } from '@verdaccio/core'; import { generatePackageMetadata } from '../helpers/generatePackageMetadata'; const debug = buildDebug('verdaccio:registry:request'); export interface ResponseAssert { status(reason: any): any; body_ok(reason: any): any; body_error(reason: any): any; request(reason: any): any; response(reason: any): any; send(reason: any): any; } type Options = { url: string; method: HTTPAlias; headers: gotHeaders; encoding?: string; json: boolean; body?: any; }; type RegistryResponse = { ok: string; error: null | string; }; class RequestAssert { private response: Response; public constructor(response: Response) { this.response = response; } public status(code: number) { debug('expected check status %s vs response code %s', code, this.response.statusCode); assert(code === this.response.statusCode); return this; } public equal_body(expected: string | RegExp) { assert.strictEqual(expected, this.response.body); } public body_ok(expected: string | RegExp) { debug('body expect ok %s', expected); if (isRegExp(expected)) { assert(this.response.body?.ok?.match(expected)); assert( this.response.body?.ok.match(expected), `'${this.response.body.ok}' doesn't match " ${expected}` ); } else if (typeof expected === 'string') { assert.equal(this.response.body?.ok, expected); } else { assert.deepEqual(this.response.body, expected); } } public body_error(expected: string | RegExp) { debug('body expect error %s', expected); if (isRegExp(expect)) { assert( this.response?.body?.error?.match(expected), `${this.response.body?.error} doesn't match ${expected}` ); } assert.equal(this.response.body?.ok, null); } } export async function createRequest(options: Options): Promise { debug('options %s', JSON.stringify(options)); let body = undefined; if (isNil(options.body) === false) { body = isObject(options.body) === false ? JSON.stringify(options.body) : options.body; } const method = options?.method?.toLocaleLowerCase(); debug('method %s', method); debug('url %s', options?.url); debug('headers %s', options?.headers); if (method === 'get') { return got(options.url, { isStream: false, resolveBodyOnly: false, throwHttpErrors: false, // @ts-ignore responseType: options.encoding ?? 'json', headers: options.headers, method: options.method, body, retry: { limit: 0 }, // @ts-ignore }).then((response) => { return new RequestAssert(response as any); }); } else if (method === 'put') { return ( got .put(options.url, { throwHttpErrors: false, responseType: 'json', headers: options.headers, json: options.body ? options.body : undefined, retry: { limit: 0 }, }) // @ts-ignore .then((response) => { return new RequestAssert(response as any); }) ); } else if (method === 'delete') { return ( got .delete(options.url, { throwHttpErrors: false, responseType: 'json', headers: options.headers, retry: { limit: 0 }, }) // @ts-ignore .then((response) => { return new RequestAssert(response as any); }) ); } } export class ServerQuery { private userAgent: string; private url: string; public constructor(url) { this.url = url.replace(/\/$/, ''); debug('server url %s', this.url); this.userAgent = 'node/v18.0.0 linux x64'; } private request(options: any): Promise { return createRequest({ ...options, url: `${this.url}${options.uri}`, }); } public debug(): Promise { return this.request({ uri: '/-/_debug', method: 'get', headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON, }, }); } /** * * * @param {{ name: string; password: string }} { name, password } * @return {*} {Promise} * @memberof ServerQuery * @deprecated use createUser instead */ public auth({ name, password }: { name: string; password: string }): Promise { return this.createUser(name, password); } public createUser(name, password): Promise { return this.request({ uri: `/-/user/org.couchdb.user:${encodeURIComponent(name)}`, method: 'PUT', body: { name, password, _id: `org.couchdb.user:${name}`, type: 'user', roles: [], date: new Date(), }, }); } public logout(token: string) { return this.request({ uri: `/-/user/token/${encodeURIComponent(token)}`, method: 'DELETE', }); } public getPackage(name: string) { return this.request({ uri: `/${encodeURIComponent(name)}`, method: 'get', }); } public getTarball(name: string, filename: string) { return this.request({ uri: `/${encodeURIComponent(name)}/-/${encodeURIComponent(filename)}`, method: 'GET', encoding: 'buffer', }); } /** * Remove entire package. * @param name package name * @param rev revision id * @returns */ public removePackage(name: string, rev) { return this.request({ uri: `/${encodeURIComponent(name)}/-rev/${rev}`, method: 'DELETE', headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON_CHARSET, }, }); } public removeSingleTarball(name: string, filename: string) { return this.request({ uri: `/${encodeURIComponent(name)}/-/${filename}/-rev/whatever`, method: 'DELETE', headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON_CHARSET, }, }); } /** * * @param name * @param tag * @param version * @returns */ public addTag(name: string, tag: string, version: string) { return this.request({ uri: `/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`, method: 'PUT', body: version, headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON, }, }); } public putVersion(name: string, version: string, data: any, headers) { return this.request({ uri: `/${encodeURIComponent(name)}/${encodeURIComponent(version)}/-tag/latest`, method: 'PUT', body: data, headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON, ...headers, }, }); } public putPackage(name: string, data, headers = {}) { return this.request({ uri: `/${encodeURIComponent(name)}`, method: 'PUT', body: data, headers: { [HEADERS.CONTENT_TYPE]: HEADERS.JSON, ...headers, }, }); } public async addPackage( name: string, version: string = '1.0.0', message = API_MESSAGE.PKG_CREATED ): Promise { return (await this.putPackage(name, generatePackageMetadata(name, version))) .status(HTTP_STATUS.CREATED) .body_ok(message); } public async addPackageAssert(name: string, version: string = '1.0.0'): Promise { return this.putPackage(name, generatePackageMetadata(name, version)); } public async whoami() { debug('request whoami'); return await this.request({ uri: '/-/whoami', method: 'get', }); } public async ping() { return ( await this.request({ uri: '/-/ping', method: 'get', }) ).status(HTTP_STATUS.OK); } }