UNPKG

heroku

Version:

CLI to interact with Heroku

172 lines (169 loc) 6.21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const color_1 = require("@heroku-cli/color"); const command_1 = require("@heroku-cli/command"); const core_1 = require("@oclif/core"); const heroku_cli_util_1 = require("@heroku/heroku-cli-util"); const lodash_1 = require("lodash"); const tsheredoc_1 = require("tsheredoc"); const COST_MONTHLY = { Free: 0, Eco: 0, Hobby: 7, Basic: 7, 'Standard-1X': 25, 'Standard-2X': 50, 'Performance-M': 250, Performance: 500, 'Performance-L': 500, '1X': 36, '2X': 72, PX: 576, 'Performance-L-RAM': 500, 'Performance-XL': 750, 'Performance-2XL': 1500, 'Private-S': 225, 'Private-M': 450, 'Private-L': 900, 'Shield-M': 540, 'Shield-L': 1080, 'Shield-S': 270, 'Private-Memory-L': 500, 'Private-Memory-XL': 750, 'Private-Memory-2XL': 1500, 'Shield-Memory-L': 600, 'Shield-Memory-XL': 900, 'Shield-Memory-2XL': 1800, 'dyno-1c-0.5gb': 25, 'dyno-2c-1gb': 50, 'dyno-1c-4gb': 80, 'dyno-2c-8gb': 160, 'dyno-4c-16gb': 320, 'dyno-8c-32gb': 640, 'dyno-16c-64gb': 1000, 'dyno-2c-4gb': 150, 'dyno-4c-8gb': 300, 'dyno-8c-16gb': 600, 'dyno-16c-32gb': 1200, 'dyno-32c-64gb': 2400, 'dyno-1c-8gb': 100, 'dyno-2c-16gb': 250, 'dyno-4c-32gb': 500, 'dyno-8c-64gb': 750, 'dyno-16c-128gb': 1500, }; const calculateHourly = (size) => COST_MONTHLY[size] / 720; const emptyFormationErr = (app) => { return new Error(`No process types on ${app}.\nUpload a Procfile to add process types.\nhttps://devcenter.heroku.com/articles/procfile`); }; const displayFormation = async (heroku, app) => { const { body: formation } = await heroku.get(`/apps/${app}/formation`); const { body: appProps } = await heroku.get(`/apps/${app}`); const shielded = appProps.space && appProps.space.shield; const dynoTotals = {}; let isShowingEcoCostMessage = false; const formationTableData = (0, lodash_1.sortBy)(formation, 'type') // this filter shouldn't be necessary, but it makes TS happy .filter((f) => typeof f.size === 'string' && typeof f.quantity === 'number') .map((d => { if (d.size === 'Eco') { isShowingEcoCostMessage = true; } if (shielded) { d.size = d.size.replace('Private-', 'Shield-'); } if (d.size in dynoTotals) { dynoTotals[d.size] += d.quantity; } else { dynoTotals[d.size] = d.quantity; } return { // this rule does not realize `size` isn't used on an array type: color_1.default.green(d.type || ''), size: color_1.default.cyan(d.size), qty: color_1.default.yellow(`${d.quantity}`), 'cost/hour': calculateHourly(d.size) ? '~$' + (calculateHourly(d.size) * (d.quantity || 1)).toFixed(3) .toString() : '', 'max cost/month': COST_MONTHLY[d.size] ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore '$' + (COST_MONTHLY[d.size] * d.quantity).toString() : '', }; })); const dynoTotalsTableData = Object.keys(dynoTotals) .map(k => ({ type: color_1.default.green(k), total: color_1.default.yellow((dynoTotals[k]).toString()), })); if (formation.length === 0) { throw emptyFormationErr(app); } heroku_cli_util_1.hux.styledHeader('Process Types'); heroku_cli_util_1.hux.table(formationTableData, { type: {}, size: {}, qty: {}, 'cost/hour': {}, 'max cost/month': {}, }); core_1.ux.log(); heroku_cli_util_1.hux.styledHeader('Dyno Totals'); heroku_cli_util_1.hux.table(dynoTotalsTableData, { type: {}, total: {}, }); if (isShowingEcoCostMessage) { core_1.ux.log('\n$5 (flat monthly fee, shared across all Eco dynos)'); } }; class Type extends command_1.Command { async run() { const _a = await this.parse(Type), { flags } = _a, restParse = tslib_1.__rest(_a, ["flags"]); const argv = restParse.argv; const { app } = flags; const parse = async () => { if (!argv || argv.length === 0) return []; const { body: formation } = await this.heroku.get(`/apps/${app}/formation`); if (argv.some(a => a.match(/=/))) { return (0, lodash_1.compact)(argv.map(arg => { const match = arg.match(/^([a-zA-Z0-9_]+)=([\w-]+)$/); const type = match && match[1]; const size = match && match[2]; if (!type || !size || !formation.some(p => p.type === type)) { throw new Error(`Type ${color_1.default.red(type || '')} not found in process formation.\nTypes: ${color_1.default.yellow(formation.map(f => f.type) .join(', '))}`); } return { type, size }; })); } return formation.map(p => ({ type: p.type, size: argv[0] })); }; const changes = await parse(); if (changes.length > 0) { core_1.ux.action.start(`Scaling dynos on ${color_1.default.magenta(app)}`); await this.heroku.patch(`/apps/${app}/formation`, { body: { updates: changes } }); core_1.ux.action.stop(); } await displayFormation(this.heroku, app); } } exports.default = Type; Type.strict = false; Type.description = (0, tsheredoc_1.default) ` manage dyno sizes Called with no arguments shows the current dyno size. Called with one argument sets the size. Where SIZE is one of eco|basic|standard-1x|standard-2x|performance Called with 1..n TYPE=SIZE arguments sets the quantity per type. `; Type.aliases = ['ps:resize', 'dyno:resize']; Type.hiddenAliases = ['resize', 'dyno:type']; Type.flags = { app: command_1.flags.app({ required: true }), remote: command_1.flags.remote(), };