UNPKG

heroku

Version:

CLI to interact with Heroku

134 lines (133 loc) 4.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.stringToConfig = void 0; 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 _ = require("lodash"); const quote_1 = require("../../lib/config/quote"); const util_1 = require("../../lib/config/util"); const editor = new util_1.Editor(); function configToString(config) { return Object.keys(config) .sort() .map(key => { return `${key}=${(0, quote_1.quote)(config[key])}`; }) .join('\n'); } function removeDeleted(newConfig, original) { for (const k of Object.keys(original)) { // The api accepts empty strings // as valid env var values // In JS an empty string is false if (!newConfig[k] && newConfig[k] !== '') newConfig[k] = null; } } function stringToConfig(s) { return s.split('\n').reduce((config, line) => { const error = () => { throw new Error(`Invalid line: ${line}`); }; if (!line) return config; const i = line.indexOf('='); if (i === -1) error(); config[line.slice(0, i)] = (0, quote_1.parse)(line.slice(i + 1)) || ''; return config; }, {}); } exports.stringToConfig = stringToConfig; function allKeys(a, b) { return _.uniq([...Object.keys(a), ...Object.keys(b)].sort()); } function showDiff(from, to) { for (const k of allKeys(from, to)) { if (from[k] === to[k]) continue; if (k in from) { core_1.ux.log(color_1.color.red(`- ${k}=${(0, quote_1.quote)(from[k])}`)); } if (k in to) { core_1.ux.log(color_1.color.green(`+ ${k}=${(0, quote_1.quote)(to[k])}`)); } } } class ConfigEdit extends command_1.Command { async run() { const { flags: { app }, args: { key } } = await this.parse(ConfigEdit); this.app = app; core_1.ux.action.start('Fetching config'); const original = await this.fetchLatestConfig(); core_1.ux.action.stop(); let newConfig = Object.assign({}, original); const prefix = `heroku-${app}-config-`; if (key) { newConfig[key] = await editor.edit(original[key], { prefix }); if (!original[key].endsWith('\n') && newConfig[key].endsWith('\n')) newConfig[key] = newConfig[key].slice(0, -1); } else { const s = await editor.edit(configToString(original), { prefix, postfix: '.sh' }); newConfig = stringToConfig(s); } if (!await this.diffPrompt(original, newConfig)) return; core_1.ux.action.start('Verifying new config'); await this.verifyUnchanged(original); core_1.ux.action.start('Updating config'); removeDeleted(newConfig, original); await this.updateConfig(newConfig); core_1.ux.action.stop(); } async fetchLatestConfig() { const { body: original } = await this.heroku.get(`/apps/${this.app}/config-vars`); return original; } async diffPrompt(original, newConfig) { if (_.isEqual(original, newConfig)) { this.warn('no changes to config'); return false; } core_1.ux.log(); core_1.ux.log('Config Diff:'); showDiff(original, newConfig); core_1.ux.log(); return heroku_cli_util_1.hux.confirm(`Update config on ${color_1.color.app(this.app)} with these values?`); } async verifyUnchanged(original) { const latest = await this.fetchLatestConfig(); if (!_.isEqual(original, latest)) { throw new Error('Config changed on server. Refusing to update.'); } } async updateConfig(newConfig) { await this.heroku.patch(`/apps/${this.app}/config-vars`, { body: newConfig, }); } } exports.default = ConfigEdit; ConfigEdit.description = `interactively edit config vars This command opens the app config in a text editor set by $VISUAL or $EDITOR. Any variables added/removed/changed will be updated on the app after saving and closing the file.`; ConfigEdit.examples = [ `# edit with vim $ EDITOR="vim" heroku config:edit`, `# edit with emacs $ EDITOR="emacs" heroku config:edit`, `# edit with pico $ EDITOR="pico" heroku config:edit`, `# edit with atom editor $ VISUAL="atom --wait" heroku config:edit`, ]; ConfigEdit.flags = { app: command_1.flags.app({ required: true }), remote: command_1.flags.remote(), }; ConfigEdit.args = { key: core_1.Args.string({ optional: true, description: 'edit a single key' }), };