@heroku/plugin-ai
Version:
Heroku CLI plugin for Heroku AI add-on
98 lines (97 loc) • 4.2 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatState = exports.grandfatheredPrice = exports.formatPriceText = exports.formatPrice = exports.trapConfirmationRequired = void 0;
exports.handlePlatformApiErrors = handlePlatformApiErrors;
const tslib_1 = require("tslib");
/* eslint-disable no-return-await */
const color_1 = tslib_1.__importDefault(require("@heroku-cli/color"));
const api_client_1 = require("@heroku-cli/command/lib/api-client");
const core_1 = require("@oclif/core");
const printf_1 = tslib_1.__importDefault(require("printf"));
const confirmCommand_1 = tslib_1.__importDefault(require("../../confirmCommand"));
const trapConfirmationRequired = async function (app, confirm, fn) {
return await fn(confirm)
.catch(async (error) => {
if (!error.body || error.body.id !== 'confirmation_required')
throw error;
await (0, confirmCommand_1.default)(app, confirm, error.body.message);
return await fn(app);
});
};
exports.trapConfirmationRequired = trapConfirmationRequired;
/**
* Error handler
* @param error Error thrown when attempting to create the model resource.
* @param cmdContext Context of the command that failed.
* @returns never
*
* There's a problem with this error handler implementation, because it relies on the specific error message
* returned from API in order to format the error correctly. This is prone to fail if changes are introduced
* upstream on error messages. We should rely on the error `id` but API returns a generic `invalid_params`.
*/
function handlePlatformApiErrors(error, cmdContext = {}) {
if (error instanceof api_client_1.HerokuAPIError && error.body.id === 'invalid_params') {
if (cmdContext.as && error.body.message?.includes('start with a letter')) {
core_1.ux.error(`${cmdContext.as} is an invalid alias. Alias must start with a letter and can only contain uppercase letters, numbers, and underscores.`, { exit: 1 });
}
if (cmdContext.modelName && error.body.message?.includes('add-on plan')) {
core_1.ux.error(`${cmdContext.modelName} is an invalid model name. Run ${color_1.default.cmd('heroku ai:models:list')} for a list of valid models per region.`, { exit: 1 });
}
}
throw error;
}
// This function assumes that price.cents will reflect price per month.
// If the API returns any unit other than month
// this function will need to be updated.
const formatPrice = function ({ price, hourly }) {
if (!price)
return;
if (price.contract)
return 'contract';
if (price.cents === 0)
return 'free';
// we are using a standardized 720 hours/month
if (hourly)
return `~$${((price.cents / 100) / 720).toFixed(3)}/hour`;
const fmt = price.cents % 100 === 0 ? '$%.0f/%s' : '$%.02f/%s';
return (0, printf_1.default)(fmt, price.cents / 100, price.unit);
};
exports.formatPrice = formatPrice;
const formatPriceText = function (price) {
const priceHourly = (0, exports.formatPrice)({ price, hourly: true });
const priceMonthly = (0, exports.formatPrice)({ price, hourly: false });
if (!priceHourly)
return '';
if (priceHourly === 'free' || priceHourly === 'contract')
return `${color_1.default.green(priceHourly)}`;
return `${color_1.default.green(priceHourly)} (max ${priceMonthly})`;
};
exports.formatPriceText = formatPriceText;
const grandfatheredPrice = function (addon) {
const price = addon.plan?.price;
return Object.assign({}, price, {
cents: addon.billed_price?.cents,
contract: addon.billed_price?.contract,
});
};
exports.grandfatheredPrice = grandfatheredPrice;
const formatState = function (state) {
switch (state) {
case 'provisioned':
state = 'created';
break;
case 'provisioning':
state = 'creating';
break;
case 'deprovisioning':
state = 'destroying';
break;
case 'deprovisioned':
state = 'errored';
break;
default:
state = '';
}
return state;
};
exports.formatState = formatState;
;