heroku-fork
Version:
Heroku CLI plugin to fork an existing app into a new app.
133 lines (111 loc) • 4.08 kB
JavaScript
;
let co = require('co');
let Apps = require('../lib/apps');
let Addons = require('../lib/addons');
let Postgres = require('../lib/postgres');
let cli = require('heroku-cli-util');
let stopping;
let fromAppName;
let toAppName;
let deleteAppOnFailure;
function wait(ms) {
return function(done) {
setTimeout(done, ms);
};
}
function deleteApp(app, heroku) {
co(function* () {
console.error(`\nIn order to avoid being charged for any resources on ${app}, it is being destroyed...`);
yield cli.action(`Destroying app ${app}`, heroku.delete(`/apps/${app}`));
process.exit(1);
});
}
function handleErr(context, heroku) {
return function (err) {
cli.errorHandler({
exit: false,
logPath: context.herokuDir + '/error.log',
})(err);
if (deleteAppOnFailure) {
console.error(`\nThere was an error forking to ${deleteAppOnFailure}.`);
deleteApp(deleteAppOnFailure, heroku);
} else {
process.exit(1);
}
};
}
function getFromApp(context) {
let fromAppName = context.flags.from || context.flags.app;
if (context.flags.app) {
cli.warn('Specifying the source app without --from APP is deprecated');
}
if (!fromAppName) {
cli.error('No source app specified.\nSpecify an app to fork from with --from APP');
return;
}
context.app = fromAppName;
return fromAppName;
}
function getToApp(context) {
if (context.args.NEWNAME) {
cli.warn('Specifying the new app without --to APP is deprecated');
}
return context.flags.to || context.args.NEWNAME;
}
function* fork (context, heroku) {
let apps = new Apps(heroku);
let postgres = new Postgres(heroku);
let addons = new Addons(heroku, postgres);
let oldApp = yield apps.getApp(fromAppName);
let slug = yield apps.getLastSlug(oldApp);
if (stopping) { return; }
let newApp = yield apps.createNewApp(oldApp, toAppName, context.flags.region);
deleteAppOnFailure = newApp.name;
if (stopping) { return; }
yield cli.action('Setting buildpacks', apps.setBuildpacks(oldApp, newApp));
if (stopping) { return; }
yield addons.copyAddons(oldApp, newApp, context.flags['skip-pg'], context.flags.confirm);
if (stopping) { return; }
yield addons.copyConfigVars(oldApp, newApp, context.flags['skip-pg']);
if (stopping) { return; }
yield apps.copySlug(oldApp, newApp, slug);
yield wait(2000); // TODO remove this after api #4022
console.log(`Fork complete. View it at ${cli.color.cyan(newApp.web_url)}`);
}
function* run (context, heroku) {
fromAppName = getFromApp(context);
if (!fromAppName) { return; }
toAppName = getToApp(context);
if (!toAppName) { return; }
process.once('SIGINT', function () {
stopping = true;
if (deleteAppOnFailure) { deleteApp(toAppName, heroku); }
});
try {
yield fork(context, heroku);
} catch (err) {
if (err.body && err.body.id === 'two_factor') throw err;
handleErr(context, heroku)(err);
}
}
const cmd = {
needsAuth: true,
hidden: true,
description: 'fork an existing app into a new one',
help: `Copy config vars and Heroku Postgres data, and re-provision add-ons to a new app.
New app name should not be an existing app. The new app will be created as part of the forking process.
Example:
$ heroku fork --from my-production-app --to my-development-app`,
flags: [
{name: 'confirm', description: 'overwrite existing config vars or existing add-on attachments', hasValue: true},
{name: 'region', description: 'specify a region', hasValue: true},
{name: 'skip-pg', description: 'skip postgres databases', hasValue: false},
{name: 'from', description: 'app to fork from', hasValue: true},
{name: 'to', description: 'app to create', hasValue: true},
{name: 'app', char: 'a', hasValue: true, hidden: true}
],
args: [{name: 'NEWNAME', optional: true, hidden: true}],
run: cli.command({preauth: true}, co.wrap(run))
};
exports.apps = Object.assign({topic: 'fork', hidden: true}, cmd);
exports.root = Object.assign({topic: 'apps', command: 'fork'}, cmd);