UNPKG

heroku

Version:

CLI to interact with Heroku

206 lines (205 loc) 9.94 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 time_1 = require("../../lib/time"); const tsheredoc_1 = require("tsheredoc"); function getProcessNumber(s) { const [processType, dynoNumber] = (s.match(/^([^.]+)\.(.*)$/) || []).slice(1, 3); if (!processType || !(dynoNumber === null || dynoNumber === void 0 ? void 0 : dynoNumber.match(/^\d+$/))) return 0; return Number.parseInt(dynoNumber, 10); } function uniqueValues(value, index, self) { return self.indexOf(value) === index; } function byProcessNumber(a, b) { return getProcessNumber(a.name) - getProcessNumber(b.name); } function byProcessName(a, b) { if (a.name > b.name) { return 1; } if (b.name > a.name) { return -1; } return 0; } function byProcessTypeAndNumber(a, b) { if (a.type > b.type) { return 1; } if (b.type > a.type) { return -1; } return getProcessNumber(a.name) - getProcessNumber(b.name); } function truncate(s) { return s.length > 35 ? `${s.slice(0, 34)}…` : s; } function printExtended(dynos) { const sortedDynos = dynos.sort(byProcessTypeAndNumber); heroku_cli_util_1.hux.table(sortedDynos, { ID: { get: (dyno) => dyno.id }, Process: { get: (dyno) => dyno.name }, State: { get: (dyno) => `${dyno.state} ${(0, time_1.ago)(new Date(dyno.updated_at))}` }, Region: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.region) ? dyno.extended.region : ''; } }, 'Execution Plane': { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.execution_plane) ? dyno.extended.execution_plane : ''; } }, Fleet: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.fleet) ? dyno.extended.fleet : ''; } }, Instance: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.instance) ? dyno.extended.instance : ''; } }, IP: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.ip) ? dyno.extended.ip : ''; } }, Port: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.port) ? dyno.extended.port.toString() : ''; } }, AZ: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.az) ? dyno.extended.az : ''; } }, Release: { get: (dyno) => dyno.release.version }, Command: { get: (dyno) => truncate(dyno.command) }, Route: { get: (dyno) => { var _a; return ((_a = dyno.extended) === null || _a === void 0 ? void 0 : _a.route) ? dyno.extended.route : ''; } }, Size: { get: (dyno) => dyno.size }, }, { 'no-truncate': true, }); } async function printAccountQuota(heroku, app, account) { if (app.process_tier !== 'free' && app.process_tier !== 'eco') { return; } if (app.owner.id !== account.id) { return; } const { body: quota } = await heroku.request(`/accounts/${account.id}/actions/get-quota`, { headers: { Accept: 'application/vnd.heroku+json; version=3.account-quotas' } }).catch(() => { return { body: null }; }); if (!quota || (quota.id && quota.id === 'not_found')) { return; } const remaining = (quota.account_quota === 0) ? 0 : quota.account_quota - quota.quota_used; const percentage = (quota.account_quota === 0) ? 0 : Math.floor(remaining / quota.account_quota * 100); const remainingMinutes = remaining / 60; const hours = Math.floor(remainingMinutes / 60); const minutes = Math.floor(remainingMinutes % 60); const appQuota = quota.apps.find(appQuota => { return appQuota.app_uuid === app.id; }); const appQuotaUsed = appQuota ? appQuota.quota_used / 60 : 0; const appPercentage = appQuota ? Math.floor(appQuota.quota_used * 100 / quota.account_quota) : 0; const appHours = Math.floor(appQuotaUsed / 60); const appMinutes = Math.floor(appQuotaUsed % 60); if (app.process_tier === 'eco') { core_1.ux.log(`Eco dyno hours quota remaining this month: ${hours}h ${minutes}m (${percentage}%)`); core_1.ux.log(`Eco dyno usage for this app: ${appHours}h ${appMinutes}m (${appPercentage}%)`); core_1.ux.log('For more information on Eco dyno hours, see:'); core_1.ux.log('https://devcenter.heroku.com/articles/eco-dyno-hours'); core_1.ux.log(); } if (app.process_tier === 'free') { core_1.ux.log(`Free dyno hours quota remaining this month: ${hours}h ${minutes}m (${percentage}%)`); core_1.ux.log(`Free dyno usage for this app: ${appHours}h ${appMinutes}m (${appPercentage}%)`); core_1.ux.log('For more information on dyno sleeping and how to upgrade, see:'); core_1.ux.log('https://devcenter.heroku.com/articles/dyno-sleeping'); core_1.ux.log(); } } function decorateOneOffDyno(dyno) { const since = (0, time_1.ago)(new Date(dyno.updated_at)); // eslint-disable-next-line unicorn/explicit-length-check const size = dyno.size || '1X'; const state = dyno.state === 'up' ? color_1.default.green(dyno.state) : color_1.default.yellow(dyno.state); return `${dyno.name} (${color_1.default.cyan(size)}): ${state} ${color_1.default.dim(since)}: ${dyno.command}`; } function decorateCommandDyno(dyno) { const since = (0, time_1.ago)(new Date(dyno.updated_at)); const state = dyno.state === 'up' ? color_1.default.green(dyno.state) : color_1.default.yellow(dyno.state); return `${dyno.name}: ${state} ${color_1.default.dim(since)}`; } function printDynos(dynos) { const oneOffs = dynos.filter(d => d.type === 'run').sort(byProcessNumber); const commands = dynos.filter(d => d.type !== 'run').map(d => d.command).filter(uniqueValues); // Print one-off dynos if (oneOffs.length > 0) { heroku_cli_util_1.hux.styledHeader(`${color_1.default.green('run')}: one-off processes (${color_1.default.yellow(oneOffs.length.toString())})`); oneOffs.forEach(dyno => core_1.ux.log(decorateOneOffDyno(dyno))); core_1.ux.log(); } // Print dynos grouped by command commands.forEach(function (command) { const commandDynos = dynos.filter(d => d.command === command).sort(byProcessNumber); const type = commandDynos[0].type; // eslint-disable-next-line unicorn/explicit-length-check const size = commandDynos[0].size || '1X'; heroku_cli_util_1.hux.styledHeader(`${color_1.default.green(type)} (${color_1.default.cyan(size)}): ${command} (${color_1.default.yellow(commandDynos.length.toString())})`); for (const dyno of commandDynos) core_1.ux.log(decorateCommandDyno(dyno)); core_1.ux.log(); }); } class Index extends command_1.Command { async run() { const _a = await this.parse(Index), { flags } = _a, restParse = tslib_1.__rest(_a, ["flags"]); const { app, json, extended } = flags; const types = restParse.argv; const suffix = extended ? '?extended=true' : ''; const promises = { dynos: this.heroku.request(`/apps/${app}/dynos${suffix}`, { headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' }, }), appInfo: this.heroku.request(`/apps/${app}`, { headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' }, }), accountInfo: this.heroku.request('/account', { headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' }, }), }; const [{ body: dynos }, { body: appInfo }, { body: accountInfo }] = await Promise.all([promises.dynos, promises.appInfo, promises.accountInfo]); const shielded = appInfo.space && appInfo.space.shield; if (shielded) { dynos.forEach(d => { d.size = d.size.replace('Private-', 'Shield-'); }); } let selectedDynos = dynos; if (types.length > 0) { selectedDynos = selectedDynos.filter(dyno => types.find((t) => dyno.type === t)); types.forEach(t => { if (!selectedDynos.some(d => d.type === t)) { throw new Error(`No ${color_1.default.cyan(t)} dynos on ${color_1.default.magenta(app)}`); } }); } selectedDynos = selectedDynos.sort(byProcessName); if (json) heroku_cli_util_1.hux.styledJSON(selectedDynos); else if (extended) printExtended(selectedDynos); else { await printAccountQuota(this.heroku, appInfo, accountInfo); if (selectedDynos.length === 0) core_1.ux.log(`No dynos on ${color_1.default.magenta(app)}`); else printDynos(selectedDynos); } } } exports.default = Index; Index.topic = 'ps'; Index.description = 'list dynos for an app'; Index.strict = false; Index.usage = 'ps [TYPE [TYPE ...]]'; Index.examples = [(0, tsheredoc_1.default) ` $ heroku ps === run: one-off dyno run.1: up for 5m: bash === web: bundle exec thin start -p $PORT web.1: created for 30s `, (0, tsheredoc_1.default) ` $ heroku ps run # specifying types === run: one-off dyno run.1: up for 5m: bash `]; Index.flags = { app: command_1.flags.app({ required: true }), remote: command_1.flags.remote(), json: command_1.flags.boolean({ description: 'display as json' }), extended: command_1.flags.boolean({ char: 'x', hidden: true }), // only works with sudo privileges };