UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

180 lines 7.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NotConnectedError = exports.RemoteBuildError = void 0; exports.requestFailure = requestFailure; exports.httpStatus = httpStatus; exports.describeRequestError = describeRequestError; exports.notConnectedPayload = notConnectedPayload; exports.buildFailure = buildFailure; exports.formatFailure = formatFailure; const config_1 = require("./config"); const log_filter_1 = require("./log-filter"); // ANCHOR - RemoteBuildError /** * A remote build failure with a stable kind and an optional next-step hint. * The message is a plain sentence; rendering (prefix, hint placement) is formatFailure's job. */ class RemoteBuildError extends Error { constructor(kind, message, hint) { super(message); this.kind = kind; this.hint = hint; } } exports.RemoteBuildError = RemoteBuildError; // ANCHOR - NotConnectedError /** Thrown when a repo build cannot start until the git provider is connected to the org. */ class NotConnectedError extends Error { constructor(pending) { super('provider not connected'); this.pending = pending; } } exports.NotConnectedError = NotConnectedError; // ANCHOR - Exported Functions // Request Failures /** * Classifies a failed build-service request into a RemoteBuildError with a next step. * * @param {unknown} e - The thrown request error. * @param {string} operation - What was being attempted (e.g. "start the build"). * @param {string} org - The target org, named in the credentials hint. * @returns {RemoteBuildError} The classified failure. */ function requestFailure(e, operation, org) { const status = httpStatus(e); if (status === 401 || status === 403) { return new RemoteBuildError('auth', 'the build service rejected your credentials', `Run "cpln login", and make sure you have access to org "${org}".`); } if (status === undefined) { return new RemoteBuildError('unreachable', 'the build service is unreachable', 'Check your connection and try again.'); } // Only failures that can heal on their own earn a retry hint. const retryable = status >= 500 || status === 408 || status === 429; return new RemoteBuildError('request-failed', `could not ${operation} (${describeRequestError(e)})`, retryable ? 'Please try again.' : undefined); } /** * Extracts the HTTP status from a request error. * * @param {unknown} e - The thrown error. * @returns {number | undefined} The status, when the error carries a response. */ function httpStatus(e) { var _a; return (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status; } /** * Describes a request error in one short clause for embedding in a message. * * @param {unknown} e - The thrown error. * @returns {string} An "HTTP <status>: <code>" clause, or the error's own message. */ function describeRequestError(e) { var _a, _b, _c, _d; const err = (_a = e) !== null && _a !== void 0 ? _a : {}; if ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) { const code = (_c = err.response.data) === null || _c === void 0 ? void 0 : _c.error; return `HTTP ${err.response.status}${code ? `: ${code}` : ''}`; } return (_d = err.message) !== null && _d !== void 0 ? _d : 'request failed'; } /** * Extracts the connect payload when a request error is a 409 not_connected reply. * * @param {unknown} e - The thrown request error. * @returns {NotConnectedPayload | undefined} The payload, or undefined for any other error. */ function notConnectedPayload(e) { var _a, _b, _c; const shape = (_a = e) !== null && _a !== void 0 ? _a : {}; if (((_b = shape.response) === null || _b === void 0 ? void 0 : _b.status) !== 409 || ((_c = shape.response.data) === null || _c === void 0 ? void 0 : _c.error) !== 'not_connected') { return undefined; } const body = shape.response.data; // A reply missing any connect field cannot drive the flow; the generic classifier handles it. if (typeof body.provider !== 'string' || typeof body.connectUrl !== 'string' || typeof body.nonce !== 'string') { return undefined; } return { provider: body.provider, connectUrl: body.connectUrl, nonce: body.nonce }; } // Build Failures /** * Builds the failure for a build the service reports as failed. When the log was * streamed live, the failure is already on screen, so the message just points at it; * otherwise it folds in the filtered log tail. A recognized push failure adds a hint. * * @param {BuildRecord} build - The failed build record. * @param {string} org - The target org, named in the push-permission hint. * @param {boolean} logShown - Whether the build log was streamed to the user already. * @returns {RemoteBuildError} The classified failure. */ function buildFailure(build, org, logShown) { var _a, _b; const hint = pushPermissionHint(`${(_a = build.error) !== null && _a !== void 0 ? _a : ''}\n${(_b = build.buildLog) !== null && _b !== void 0 ? _b : ''}`, org); if (logShown) { return new RemoteBuildError('build-failed', 'the build failed. See the build log above for the cause.', hint); } return new RemoteBuildError('build-failed', `the build failed: ${failureDetail(build)}`, hint); } // Rendering /** * Renders any thrown failure as the single message the command layer prints. * * @param {unknown} e - The thrown failure. * @returns {string} The display message, prefixed and with the hint appended. */ function formatFailure(e) { if (e instanceof RemoteBuildError) { return `ERROR: ${e.message}${e.hint ? `. ${e.hint}` : ''}`; } const message = e instanceof Error ? e.message : String(e); return message.startsWith('ERROR:') ? message : `ERROR: ${message}`; } // SECTION - Functions /** * Maps a recognized push failure to an actionable hint naming the target org. * * @param {string} detail - The build error and log to inspect. * @param {string} org - The target org. * @returns {string | undefined} The hint when the failure is a known one. */ function pushPermissionHint(detail, org) { if (/failed to push|denied: requested access|insufficient_scope|unauthorized: authentication required/i.test(detail)) { return `The image could not be pushed to your registry. Make sure you have image create permission in the target org "${org}".`; } return undefined; } /** * Assembles the failure detail for a build whose log was not streamed: the service's * error and the filtered tail of the build log, without repeating either. * * @param {BuildRecord} build - The failed build record. * @returns {string} The detail text. */ function failureDetail(build) { var _a; const error = ((_a = build.error) !== null && _a !== void 0 ? _a : '').trim(); const tail = filteredLogTail(build.buildLog); const parts = error === tail ? [error] : [error, tail]; return parts.filter((part) => part.length > 0).join('\n') || 'the service returned no error detail'; } /** * Returns the last lines of a build log with BuildKit noise removed, for error context. * * @param {string | undefined} log - The full build log. * @returns {string} The filtered trailing lines of the log. */ function filteredLogTail(log) { if (!log) { return ''; } const filter = new log_filter_1.RemoteBuildLogFilter(); const lines = []; for (const raw of log.split('\n')) { lines.push(...filter.accept(raw)); } lines.push(...filter.finish()); return lines.slice(-config_1.BUILD_LOG_TAIL_LINES).join('\n'); } // !SECTION //# sourceMappingURL=errors.js.map