UNPKG

@redocly/cli

Version:

[@Redocly](https://redocly.com) CLI is your all-in-one API documentation utility. It builds, manages, improves, and quality-checks your API descriptions, all of which comes in handy for various phases of the API Lifecycle. Create your own rulesets to make

242 lines 9.64 kB
import { logger } from '@redocly/openapi-core'; import fetchWithTimeout from '../../utils/fetch-with-timeout.js'; import { DEFAULT_FETCH_TIMEOUT } from '../../utils/constants.js'; import { version } from '../../utils/package.js'; export class ReuniteApiError extends Error { constructor(message, status) { super(message); this.status = status; } } export class ReuniteApiClient { constructor(command) { this.command = command; this.sunsetWarnings = []; } async request(url, options) { const headers = { ...options.headers, 'user-agent': `redocly-cli/${version} ${this.command}`, }; try { const response = await fetchWithTimeout(url, { ...options, headers, }); this.collectSunsetWarning(response); return response; } catch (err) { let errorMessage = 'Failed to fetch.'; if (err.cause) { errorMessage += ` Caused by ${err.cause.message || err.cause.name}.`; } if (err.code || err.cause?.code) { errorMessage += ` Code: ${err.code || err.cause?.code}`; } throw new Error(errorMessage); } } collectSunsetWarning(response) { const sunsetTime = this.getSunsetDate(response); if (!sunsetTime) return; const sunsetDate = new Date(sunsetTime); if (sunsetTime > Date.now()) { this.sunsetWarnings.push({ sunsetDate, isSunsetExpired: false, }); } else { this.sunsetWarnings.push({ sunsetDate, isSunsetExpired: true, }); } } getSunsetDate(response) { const { headers } = response; if (!headers) { return; } const sunsetDate = headers.get('sunset') || headers.get('Sunset'); if (!sunsetDate) { return; } return Date.parse(sunsetDate); } } class RemotesApi { constructor(client, domain, apiKey) { this.client = client; this.domain = domain; this.apiKey = apiKey; } async getParsedResponse(response) { const responseBody = await response.json(); if (response.ok) { return responseBody; } throw new ReuniteApiError(`${responseBody.title || response.statusText || 'Unknown error'}.`, response.status); } async getDefaultBranch(organizationId, projectId) { try { const response = await this.client.request(`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/source`, { timeout: DEFAULT_FETCH_TIMEOUT, method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, }); const source = await this.getParsedResponse(response); return source.branchName; } catch (err) { const message = `Failed to fetch default branch. ${err.message}`; if (err instanceof ReuniteApiError) { throw new ReuniteApiError(message, err.status); } throw new Error(message); } } async upsert(organizationId, projectId, remote) { try { const response = await this.client.request(`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes`, { timeout: DEFAULT_FETCH_TIMEOUT, method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify({ mountPath: remote.mountPath, mountBranchName: remote.mountBranchName, type: 'CICD', autoMerge: true, }), }); return await this.getParsedResponse(response); } catch (err) { const message = `Failed to upsert remote. ${err.message}`; if (err instanceof ReuniteApiError) { throw new ReuniteApiError(message, err.status); } throw new Error(message); } } async push(organizationId, projectId, payload, files) { const formData = new globalThis.FormData(); formData.append('remoteId', payload.remoteId); formData.append('commit[message]', payload.commit.message); formData.append('commit[author][name]', payload.commit.author.name); formData.append('commit[author][email]', payload.commit.author.email); formData.append('commit[branchName]', payload.commit.branchName); payload.commit.url && formData.append('commit[url]', payload.commit.url); payload.commit.namespace && formData.append('commit[namespaceId]', payload.commit.namespace); payload.commit.sha && formData.append('commit[sha]', payload.commit.sha); payload.commit.repository && formData.append('commit[repositoryId]', payload.commit.repository); payload.commit.createdAt && formData.append('commit[createdAt]', payload.commit.createdAt); for (const file of files) { const blob = Buffer.isBuffer(file.stream) ? new Blob([file.stream]) : new Blob([(await streamToBuffer(file.stream))]); formData.append(`files[${file.path}]`, blob, file.path); } payload.isMainBranch && formData.append('isMainBranch', 'true'); try { const response = await this.client.request(`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes`, { method: 'POST', headers: { Authorization: `Bearer ${this.apiKey}`, }, body: formData, }); return await this.getParsedResponse(response); } catch (err) { const message = `Failed to push. ${err.message}`; if (err instanceof ReuniteApiError) { throw new ReuniteApiError(message, err.status); } throw new Error(message); } } async getRemotesList({ organizationId, projectId, mountPath, }) { try { const response = await this.client.request(`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/remotes?filter=mountPath:/${mountPath}/`, { timeout: DEFAULT_FETCH_TIMEOUT, method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, }); return await this.getParsedResponse(response); } catch (err) { const message = `Failed to get remote list. ${err.message}`; if (err instanceof ReuniteApiError) { throw new ReuniteApiError(message, err.status); } throw new Error(message); } } async getPush({ organizationId, projectId, pushId, }) { try { const response = await this.client.request(`${this.domain}/api/orgs/${organizationId}/projects/${projectId}/pushes/${pushId}`, { timeout: DEFAULT_FETCH_TIMEOUT, method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}`, }, }); return await this.getParsedResponse(response); } catch (err) { const message = `Failed to get push status. ${err.message}`; if (err instanceof ReuniteApiError) { throw new ReuniteApiError(message, err.status); } throw new Error(message); } } } export class ReuniteApi { constructor({ domain, apiKey, command, }) { this.command = command; this.apiClient = new ReuniteApiClient(this.command); this.remotes = new RemotesApi(this.apiClient, domain, apiKey); } reportSunsetWarnings() { const sunsetWarnings = this.apiClient.sunsetWarnings; if (sunsetWarnings.length) { const [{ isSunsetExpired, sunsetDate }] = sunsetWarnings.sort((a, b) => { // First, prioritize by expiration status if (a.isSunsetExpired !== b.isSunsetExpired) { return a.isSunsetExpired ? -1 : 1; } // If both are either expired or not, sort by sunset date return a.sunsetDate > b.sunsetDate ? 1 : -1; }); const updateVersionMessage = `Update to the latest version by running "npm install @redocly/cli@latest".`; if (isSunsetExpired) { logger.error(`The "${this.command}" command is not compatible with your version of Redocly CLI. ${updateVersionMessage}\n\n`); } else { logger.warn(`The "${this.command}" command will be incompatible with your version of Redocly CLI after ${sunsetDate.toLocaleString()}. ${updateVersionMessage}\n\n`); } } } } export async function streamToBuffer(stream) { const chunks = []; for await (const chunk of stream) { chunks.push(chunk); } return Buffer.concat(chunks); } //# sourceMappingURL=api-client.js.map