@controlplane/cli
Version:
Control Plane Corporation CLI
180 lines • 7.72 kB
JavaScript
;
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