heroku
Version:
CLI to interact with Heroku
203 lines (196 loc) • 8.29 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const yaml = require("js-yaml");
const fs_extra_1 = require("fs-extra");
const command_1 = require("@heroku-cli/command");
const completions_1 = require("@heroku-cli/command/lib/completions");
const core_1 = require("@oclif/core");
const heroku_cli_util_1 = require("@heroku/heroku-cli-util");
const color_1 = require("@heroku-cli/color");
const lodash_1 = require("lodash");
const git_1 = require("../../lib/git/git");
const git = new git_1.default();
function createText(name, space) {
let text = `Creating ${name ? color_1.default.app(name) : 'app'}`;
if (space) {
text += ` in space ${space}`;
}
return text;
}
async function createApp(context, heroku, name, stack) {
var _a, _b;
const { flags } = context;
const params = {
name,
team: flags.team,
region: flags.region,
space: flags.space,
stack,
internal_routing: flags['internal-routing'],
feature_flags: flags.features,
kernel: flags.kernel,
locked: flags.locked,
};
const requestPath = (params.space || params.team) ? '/teams/apps' : '/apps';
const { body: app } = await heroku.post(requestPath, {
body: params,
});
let status = name ? 'done' : `done, ${color_1.default.app(app.name || '')}`;
if (flags.region) {
status += `, region is ${color_1.default.yellow(((_a = app.region) === null || _a === void 0 ? void 0 : _a.name) || '')}`;
}
if (stack) {
status += `, stack is ${color_1.default.yellow(((_b = app.stack) === null || _b === void 0 ? void 0 : _b.name) || '')}`;
}
core_1.ux.action.stop(status);
return app;
}
async function addAddons(heroku, app, addons) {
for (const addon of addons) {
const body = {
plan: addon.plan,
attachment: addon.as ? { name: addon.as } : undefined,
};
core_1.ux.action.start(`Adding ${color_1.default.green(addon.plan)}`);
await heroku.post(`/apps/${app.name}/addons`, { body });
core_1.ux.action.stop();
}
}
async function addConfigVars(heroku, app, configVars) {
if (Object.keys(configVars).length > 0) {
core_1.ux.action.start('Setting config vars');
await heroku.patch(`/apps/${app.name}/config-vars`, {
body: configVars,
});
core_1.ux.action.stop();
}
}
function addonsFromPlans(plans) {
return plans.map(plan => ({
plan: plan.trim(),
}));
}
async function configureGitRemote(context, app) {
const remoteUrl = git.httpGitUrl(app.name || '');
if (!context.flags['no-remote'] && git.inGitRepo()) {
await git.createRemote(context.flags.remote || 'heroku', remoteUrl);
}
return remoteUrl;
}
function printAppSummary(context, app, remoteUrl) {
if (context.flags.json) {
heroku_cli_util_1.hux.styledJSON(app);
}
else {
core_1.ux.log(`${color_1.default.cyan(app.web_url || '')} | ${color_1.default.green(remoteUrl)}`);
}
}
async function runFromFlags(context, heroku, config) {
const { flags, args } = context;
if (flags['internal-routing'] && !flags.space) {
throw new Error('Space name required.\nInternal Web Apps are only available for Private Spaces.\nUSAGE: heroku apps:create --space my-space --internal-routing');
}
const name = flags.app || args.app || process.env.HEROKU_APP;
async function addBuildpack(app, buildpack) {
core_1.ux.action.start(`Setting buildpack to ${color_1.default.cyan(buildpack)}`);
await heroku.put(`/apps/${app.name}/buildpack-installations`, {
headers: { Range: '' },
body: { updates: [{ buildpack: buildpack }] },
});
core_1.ux.action.stop();
}
core_1.ux.action.start(createText(name, flags.space));
const app = await createApp(context, heroku, name, flags.stack);
core_1.ux.action.stop();
if (flags.addons) {
const plans = flags.addons.split(',');
const addons = addonsFromPlans(plans);
await addAddons(heroku, app, addons);
}
if (flags.buildpack) {
await addBuildpack(app, flags.buildpack);
}
const remoteUrl = await configureGitRemote(context, app);
await config.runHook('recache', { type: 'app', app: app.name });
printAppSummary(context, app, remoteUrl);
}
async function readManifest() {
const buffer = await (0, fs_extra_1.readFile)('heroku.yml');
return yaml.load(buffer.toString(), { filename: 'heroku.yml' });
}
async function runFromManifest(context, heroku) {
const { flags, args } = context;
const name = flags.app || args.app || process.env.HEROKU_APP;
core_1.ux.action.start('Reading heroku.yml manifest');
const manifest = await readManifest();
core_1.ux.action.stop();
core_1.ux.action.start(createText(name, flags.space));
const app = await createApp(context, heroku, name, 'container');
core_1.ux.action.stop();
// _.get used here to avoid type guards when working with `unknown`
const setup = (0, lodash_1.get)(manifest, 'setup', {});
const addons = setup.addons || [];
const configVars = setup.config || {};
await addAddons(heroku, app, addons);
await addConfigVars(heroku, app, configVars);
const remoteUrl = await configureGitRemote(context, app);
printAppSummary(context, app, remoteUrl);
}
class Create extends command_1.Command {
async run() {
const context = await this.parse(Create);
const { flags } = context;
if (this.config.channel === 'beta' && flags.manifest) {
return runFromManifest(context, this.heroku);
}
await runFromFlags(context, this.heroku, this.config);
}
}
exports.default = Create;
Create.description = 'creates a new app';
Create.hiddenAliases = ['create'];
Create.examples = [
`$ heroku apps:create
Creating app... done, stack is heroku-24
https://floating-dragon-42.heroku.com/ | https://git.heroku.com/floating-dragon-42.git
# or just
$ heroku create
# use a heroku.yml manifest file
$ heroku apps:create --manifest
# specify a buildpack
$ heroku apps:create --buildpack https://github.com/some/buildpack.git
# specify a name
$ heroku apps:create example
# create a staging app
$ heroku apps:create example-staging --remote staging
# create an app in the eu region
$ heroku apps:create --region eu`,
];
Create.args = {
app: core_1.Args.string({ description: 'name of app to create', required: false }),
};
Create.flags = {
// `app` set to `flags.string` instead of `flags.app` to maintain original v5 functionality and avoid a default value from the git remote set when used without an app
app: command_1.flags.string({ hidden: true }),
addons: command_1.flags.string({ description: 'comma-delimited list of addons to install' }),
buildpack: command_1.flags.string({
char: 'b',
description: 'buildpack url to use for this app',
completion: completions_1.BuildpackCompletion,
}),
manifest: command_1.flags.boolean({ char: 'm', description: 'use heroku.yml settings for this app', hidden: true }),
'no-remote': command_1.flags.boolean({ char: 'n', description: 'do not create a git remote' }),
remote: command_1.flags.remote({ description: 'the git remote to create, default "heroku"', default: 'heroku' }),
stack: command_1.flags.string({ char: 's', description: 'the stack to create the app on', completion: completions_1.StackCompletion }),
space: command_1.flags.string({ description: 'the private space to create the app in', completion: completions_1.SpaceCompletion }),
region: command_1.flags.string({ description: 'specify region for the app to run in', completion: completions_1.RegionCompletion }),
'internal-routing': command_1.flags.boolean({
hidden: true,
description: 'private space-only. create as an Internal Web App that is only routable in the local network.',
}),
features: command_1.flags.string({ hidden: true }),
kernel: command_1.flags.string({ hidden: true }),
locked: command_1.flags.boolean({ hidden: true }),
json: command_1.flags.boolean({ description: 'output in json format' }),
team: command_1.flags.team(),
};
;