UNPKG

@omlet/cli

Version:

Omlet (https://omlet.dev) is a component analytics tool that uses a CLI to scan your codebase to detect components and their usage. Get real usage insights from customizable charts to measure adoption across all projects and identify opportunities to impr

299 lines (298 loc) • 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.trackEvent = exports.initWorkspace = exports.postAnalysis = exports.getWorkspace = exports.getDefaultWorkspace = exports.getMe = exports.RequestTimeoutError = exports.UnknownRequestError = exports.RequestFailedError = exports.LoginRequiredError = exports.ApiError = exports.ErrorResponseCode = void 0; const clr = __importStar(require("colorette")); const http_status_codes_1 = require("http-status-codes"); const https_proxy_agent_1 = __importDefault(require("https-proxy-agent")); const node_fetch_1 = __importStar(require("node-fetch")); const os_1 = __importDefault(require("os")); const perf_hooks_1 = require("perf_hooks"); const uuid_1 = require("uuid"); const zlib_1 = require("zlib"); const package_json_1 = require("../package.json"); const auth_1 = require("./auth"); const config_1 = require("./config"); const error_1 = require("./error"); const jsonStream_1 = require("./jsonStream"); const logger_1 = require("./logger"); function generateUserAgent() { return `OmletCLI/${package_json_1.version} (${os_1.default.type()}; ${os_1.default.arch()}; ${os_1.default.release()})`; } function parseAPIError(responseBody) { if (!responseBody) { return; } try { return JSON.parse(responseBody); } catch (_a) { logger_1.logger.error("Error response couldn't be parsed"); logger_1.logger.error("Response:", responseBody); return; } } var ErrorResponseCode; (function (ErrorResponseCode) { ErrorResponseCode["UNAUTHORIZED"] = "unauthorized"; ErrorResponseCode["BAD_REQUEST"] = "bad-request"; ErrorResponseCode["REQUEST_TOO_LONG"] = "request-too-long"; ErrorResponseCode["CLI_NOT_SUPPORTED"] = "cli-not-supported"; ErrorResponseCode["USER_NOT_SUBSCRIBED"] = "user-not-subscribed"; ErrorResponseCode["WORKSPACE_NOT_FOUND"] = "workspace-not-found"; ErrorResponseCode["WORKSPACE_SLUG_NOT_AVAILABLE"] = "workspace-slug-not-available"; ErrorResponseCode["WORKSPACE_ALREADY_SETUP"] = "workspace-already-setup"; ErrorResponseCode["USER_NOT_HAVE_WORKSPACE"] = "user-not-have-workspace"; ErrorResponseCode["USER_ALREADY_HAS_WORKSPACE"] = "user-already-has-workspace"; ErrorResponseCode["USER_ALREADY_MEMBER"] = "user-already-member"; ErrorResponseCode["TAG_NOT_FOUND"] = "tag-not-found"; ErrorResponseCode["SAVED_CHARTS_FEATURE_DISABLED"] = "saved-charts-feature-disabled"; ErrorResponseCode["SAVED_CHART_NOT_FOUND"] = "saved-chart-not-found"; ErrorResponseCode["SAVED_CHART_LIMIT_REACHED"] = "saved-chart-limit-reached"; ErrorResponseCode["LOCKED"] = "locked"; ErrorResponseCode["UNSUPPORTED_MEDIA_TYPE"] = "unsupported-media-type"; })(ErrorResponseCode = exports.ErrorResponseCode || (exports.ErrorResponseCode = {})); class ApiError extends error_1.CliError { constructor(message, { context }) { var _a; // Clone the headers to prevent mutation context.requestHeaders = { ...context.requestHeaders }; super(message, { context }); if (context.requestHeaders.cookie) { context.requestHeaders.cookie = "[REDACTED]"; } this.context = context; this.name = this.constructor.name; this._errorInfo = (_a = parseAPIError(context.responseBody)) !== null && _a !== void 0 ? _a : { title: "Analysis failed with an unexpected error", detail: `Details: ${message}\n` + `Try again and if the issue continues, ping us at ${config_1.OMLET_EMAIL} with the error logs:\n` + (0, logger_1.getLogFilePath)(), }; } get errorInfo() { return this._errorInfo; } } exports.ApiError = ApiError; class LoginRequiredError extends ApiError { constructor({ context }) { var _a; super("Login required", { context }); this.name = this.constructor.name; this._errorInfo = (_a = parseAPIError(context.responseBody)) !== null && _a !== void 0 ? _a : { code: ErrorResponseCode.UNAUTHORIZED, title: "Authentication failed!", detail: "Please make sure you have a working Omlet token set as the OMLET_TOKEN variable.\n" + "Run `omlet login --print-token` to obtain a token.", }; } } exports.LoginRequiredError = LoginRequiredError; class RequestFailedError extends ApiError { constructor(statusCode, { context }) { var _a; super("API request failed", { context }); this.name = this.constructor.name; this.statusCode = statusCode; this._errorInfo = (_a = parseAPIError(context === null || context === void 0 ? void 0 : context.responseBody)) !== null && _a !== void 0 ? _a : { title: "Analysis failed with an unexpected error", detail: `Details: API request failed\nTry again and if the issue continues, ping us at ${config_1.OMLET_EMAIL} with the error logs:\n${(0, logger_1.getLogFilePath)()}`, }; } } exports.RequestFailedError = RequestFailedError; class UnknownRequestError extends ApiError { constructor(reason, { context }) { super(`HTTP request error: ${reason.message}`, { context }); this.name = this.constructor.name; } } exports.UnknownRequestError = UnknownRequestError; class RequestTimeoutError extends ApiError { constructor(message, { context }) { super(message, { context }); this.name = this.constructor.name; } } exports.RequestTimeoutError = RequestTimeoutError; const CLI_STATUS_HEADER = "omlet-cli-status"; var CLIStatus; (function (CLIStatus) { CLIStatus["Deprecated"] = "deprecated"; CLIStatus["Obsolete"] = "obsolete"; })(CLIStatus || (CLIStatus = {})); var CLIStatusMetaKey; (function (CLIStatusMetaKey) { CLIStatusMetaKey["MinVersion"] = "min-version"; })(CLIStatusMetaKey || (CLIStatusMetaKey = {})); async function checkCLIStatus(response) { var _a, _b; const cliStatus = response.headers.get(CLI_STATUS_HEADER); if (cliStatus === null) { return; } // Omlet-CLI-Status: deprecated // Omlet-CLI-Status: deprecated; min-version=v2.0.0 // Omlet-CLI-Status: obsolete const { status, meta } = (_b = (_a = cliStatus.match(/^(?<status>deprecated|obsolete)(\s*;\s*(?<meta>.*))?$/)) === null || _a === void 0 ? void 0 : _a.groups) !== null && _b !== void 0 ? _b : {}; let metaObj = {}; if (meta) { try { metaObj = Object.fromEntries(meta.split(/\s*;\s*/).map(kv => kv.split("=")).map(([k, v]) => [k.toLowerCase(), v])); } catch (_c) { // Error parsing header } } if (status === CLIStatus.Deprecated) { console.log(""); console.error(clr.yellow(clr.bold("This CLI version is deprecated"))); console.error(clr.yellow(`The support for version ${package_json_1.version} will be dropped in near future`)); const minVersion = metaObj[CLIStatusMetaKey.MinVersion]; console.error(clr.yellow(`For an uninterrupted workflow please update Omlet CLI${minVersion ? ` to at least ${minVersion}` : "."}`)); return; } } const REQUEST_TIMEOUT_MSEC = 150000; const REQUEST_ID_HEADER = "omlet-request-id"; async function apiRequest(pathname, options = {}) { var _a; try { const token = await (0, auth_1.getToken)(); const url = new URL(pathname, config_1.BASE_URL).toString(); const requestId = (0, uuid_1.v4)(); const headers = new node_fetch_1.Headers(options.headers); headers.set("User-Agent", generateUserAgent()); headers.set("Content-Type", "application/json"); headers.set("Cookie", `omlet-auth-token=${token}`); headers.set(REQUEST_ID_HEADER, requestId); let response; const requestTime = perf_hooks_1.performance.now(); const context = { url, method: (_a = options.method) !== null && _a !== void 0 ? _a : "GET", requestHeaders: Object.fromEntries(headers), cli_version: package_json_1.version, node_version: process.version, device_info: { os: os_1.default.type(), arch: os_1.default.arch(), version: os_1.default.release(), }, duration: 0, }; if (!token) { throw new LoginRequiredError({ context }); } try { const proxyAgent = config_1.HTTP_PROXY_URL ? (0, https_proxy_agent_1.default)(config_1.HTTP_PROXY_URL) : null; logger_1.logger.debug(`API request: ${context.method} ${context.url}`); response = await (0, node_fetch_1.default)(url, { timeout: REQUEST_TIMEOUT_MSEC, ...options, headers, ...(proxyAgent ? { agent: proxyAgent } : {}), }); } catch (e) { const responseTime = perf_hooks_1.performance.now(); context.duration = responseTime - requestTime; if (e instanceof node_fetch_1.FetchError && e.type === "request-timeout") { throw new RequestTimeoutError(`"Request timed out": ${e.message}`, { context }); } throw new UnknownRequestError(e, { context }); } const responseTime = perf_hooks_1.performance.now(); context.duration = responseTime - requestTime; context.responseHeaders = Object.fromEntries(response.headers); context.responseStatus = response.status; await checkCLIStatus(response); if (response.status === http_status_codes_1.StatusCodes.UNAUTHORIZED) { context.responseBody = await response.text(); throw new LoginRequiredError({ context }); } else if (response.status === http_status_codes_1.StatusCodes.REQUEST_TIMEOUT || response.status === http_status_codes_1.StatusCodes.GATEWAY_TIMEOUT) { context.responseBody = await response.text(); throw new RequestTimeoutError("Request timed out", { context }); } if (!response.ok) { context.responseBody = await response.text(); throw new RequestFailedError(response.status, { context }); } logger_1.logger.debug(`API request successful: ${context.method} ${context.url}`); logger_1.logger.debug(`${response.status} ${response.statusText} Headers: ${JSON.stringify(response.headers)}`); return { data: await response.json(), response }; } catch (e) { if (e instanceof Error) { (0, logger_1.logError)(e); } throw e; } } async function getMe() { const { data } = await apiRequest("/api/users/me"); return data; } exports.getMe = getMe; async function getDefaultWorkspace() { const { data } = await apiRequest("/api/workspaces/default"); return data; } exports.getDefaultWorkspace = getDefaultWorkspace; async function getWorkspace(slug) { const { data: { workspace } } = await apiRequest(`/api/workspaces/${slug}`); return workspace; } exports.getWorkspace = getWorkspace; async function postAnalysis(workspace, analysisData) { const { data } = await apiRequest(`/api/workspaces/${workspace.slug}/analyses`, { method: "POST", body: (0, jsonStream_1.toJsonStringStream)(analysisData).pipe((0, zlib_1.createGzip)()), headers: { "content-encoding": "gzip", }, }); return data; } exports.postAnalysis = postAnalysis; async function initWorkspace(workspace, analyses) { const { data } = await apiRequest(`/api/workspaces/${workspace.slug}/init`, { method: "POST", body: (0, jsonStream_1.toJsonStringStream)({ analyses }).pipe((0, zlib_1.createGzip)()), headers: { "content-encoding": "gzip", }, }); return data; } exports.initWorkspace = initWorkspace; function trackEvent(eventName, props) { return apiRequest("/api/cli-tracking/events", { method: "POST", body: JSON.stringify({ eventName, props }), }); } exports.trackEvent = trackEvent;