heroku
Version:
CLI to interact with Heroku
134 lines (133 loc) • 4.79 kB
JavaScript
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' }),
};
;