heroku 10.1.0 → 10.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ const command_1 = require("@heroku-cli/command");
5
5
  const util = require("util");
6
6
  const _ = require("lodash");
7
7
  const filesize_1 = require("filesize");
8
+ const generation_1 = require("../../lib/apps/generation");
8
9
  const { countBy, snakeCase } = _;
9
10
  function formatDate(date) {
10
11
  return date.toISOString();
@@ -62,7 +63,7 @@ function print(info, addons, collaborators, extended) {
62
63
  data['Git URL'] = info.app.git_url;
63
64
  data['Web URL'] = info.app.web_url;
64
65
  data['Repo Size'] = (0, filesize_1.filesize)(info.app.repo_size, { standard: 'jedec', round: 0 });
65
- if (info.app.generation.name !== 'fir')
66
+ if ((0, generation_1.getGeneration)(info.app) !== 'fir')
66
67
  data['Slug Size'] = (0, filesize_1.filesize)(info.app.slug_size, { standard: 'jedec', round: 0 });
67
68
  data.Owner = info.app.owner.email;
68
69
  data.Region = info.app.region.name;
@@ -116,7 +117,7 @@ class AppsInfo extends command_1.Command {
116
117
  print('git_url', info.app.git_url);
117
118
  print('web_url', info.app.web_url);
118
119
  print('repo_size', (0, filesize_1.filesize)(info.app.repo_size, { standard: 'jedec', round: 0 }));
119
- if (info.app.generation.name !== 'fir')
120
+ if ((0, generation_1.getGeneration)(info.app) !== 'fir')
120
121
  print('slug_size', (0, filesize_1.filesize)(info.app.slug_size, { standard: 'jedec', round: 0 }));
121
122
  print('owner', info.app.owner.email);
122
123
  print('region', info.app.region.name);
@@ -4,6 +4,7 @@ const command_1 = require("@heroku-cli/command");
4
4
  const core_1 = require("@oclif/core");
5
5
  const color_1 = require("@heroku-cli/color");
6
6
  const buildpacks_1 = require("../../lib/buildpacks/buildpacks");
7
+ const generation_1 = require("../../lib/apps/generation");
7
8
  class Index extends command_1.Command {
8
9
  async run() {
9
10
  const { flags } = await this.parse(Index);
@@ -13,7 +14,7 @@ class Index extends command_1.Command {
13
14
  Accept: 'application/vnd.heroku+json; version=3.sdk',
14
15
  },
15
16
  });
16
- const buildpacks = await buildpacksCommand.fetch(flags.app, app.generation === 'fir');
17
+ const buildpacks = await buildpacksCommand.fetch(flags.app, (0, generation_1.getGeneration)(app) === 'fir');
17
18
  if (buildpacks.length === 0) {
18
19
  this.log(`${color_1.default.app(flags.app)} has no Buildpacks.`);
19
20
  }
@@ -34,8 +34,12 @@ class Add extends command_1.Command {
34
34
  await sshKeygen(path.join(sshdir, 'id_rsa'), flags.quiet);
35
35
  };
36
36
  const findKey = async function () {
37
- const defaultKey = path.join(sshdir, 'id_rsa.pub');
38
- if (!(await fs.pathExists(defaultKey))) {
37
+ const defaultKeyPath = path.join(sshdir, 'id_rsa.pub');
38
+ const defaultKeyExists = await fs.pathExists(defaultKeyPath);
39
+ const keys = (await fs.readdir(sshdir))
40
+ .map(k => path.join(sshdir, k))
41
+ .filter(k => path.extname(k) === '.pub');
42
+ if (!defaultKeyExists && keys.length === 0) {
39
43
  core_1.ux.warn('Could not find an existing SSH key at ' + path.join('~', '.ssh', 'id_rsa.pub'));
40
44
  if (!flags.yes) {
41
45
  const resp = await confirmPrompt('Would you like to generate a new one?');
@@ -43,14 +47,11 @@ class Add extends command_1.Command {
43
47
  return;
44
48
  }
45
49
  await generate();
46
- return defaultKey;
50
+ return defaultKeyPath;
47
51
  }
48
- let keys = await fs.readdir(sshdir);
49
- keys = keys.map(k => path.join(sshdir, k));
50
- keys = keys.filter(k => path.extname(k) === '.pub');
51
52
  if (keys.length === 1) {
52
53
  const key = keys[0];
53
- core_1.ux.warn(`Found an SSH public key at ${color_1.default.cyan(key)}`);
54
+ core_1.ux.info(`Found an SSH public key at ${color_1.default.cyan(key)}`);
54
55
  if (!flags.yes) {
55
56
  const resp = await confirmPrompt('Would you like to upload it to Heroku?');
56
57
  if (!resp.yes)
@@ -93,8 +93,9 @@ Restore.flags = {
93
93
  extensions: command_1.flags.string({
94
94
  char: 'e',
95
95
  description: (0, tsheredoc_1.default)(`
96
- comma-separated list of extensions to pre-install in the public schema
97
- defaults to saving the latest database to DATABASE_URL
96
+ comma-separated list of extensions to pre-install in the default
97
+ public schema or an optional custom schema
98
+ (for example: hstore or myschema.hstore)
98
99
  `),
99
100
  }),
100
101
  verbose: command_1.flags.boolean({ char: 'v' }),
@@ -24,6 +24,7 @@ class Wait extends command_1.Command {
24
24
  let status;
25
25
  let waiting = false;
26
26
  let retries = 20;
27
+ const notFoundMessage = 'Waiting to provision...';
27
28
  while (true) {
28
29
  try {
29
30
  ({ body: status } = await this.heroku.get(`/client/v11/databases/${db.id}/wait_status`, { hostname: (0, host_1.default)() }));
@@ -34,7 +35,7 @@ class Wait extends command_1.Command {
34
35
  if (!retries || httpError.statusCode !== 404)
35
36
  throw httpError;
36
37
  retries--;
37
- status = { 'waiting?': true };
38
+ status = { 'waiting?': true, message: notFoundMessage };
38
39
  }
39
40
  if (status['error?']) {
40
41
  (0, notify_1.default)('error', `${db.name} ${status.message}`, false);
@@ -1,5 +1,6 @@
1
1
  import { Command } from '@heroku-cli/command';
2
2
  import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api';
3
+ import { GenerationKind } from '../../lib/apps/generation';
3
4
  interface AppInfo {
4
5
  name: string;
5
6
  repo?: string;
@@ -13,7 +14,7 @@ export default class PipelinesDiff extends Command {
13
14
  remote: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
14
15
  };
15
16
  kolkrabbi: KolkrabbiAPI;
16
- getAppInfo: (appName: string, appId: string, generation: string) => Promise<AppInfo>;
17
+ getAppInfo: (appName: string, appId: string, generation: GenerationKind) => Promise<AppInfo>;
17
18
  run(): Promise<undefined>;
18
19
  }
19
20
  export {};
@@ -6,6 +6,7 @@ const core_1 = require("@oclif/core");
6
6
  const http_call_1 = require("@heroku/http-call");
7
7
  const api_1 = require("../../lib/api");
8
8
  const kolkrabbi_api_1 = require("../../lib/pipelines/kolkrabbi-api");
9
+ const generation_1 = require("../../lib/apps/generation");
9
10
  const PROMOTION_ORDER = ['development', 'staging', 'production'];
10
11
  async function diff(targetApp, downstreamApp, githubToken, herokuUserAgent) {
11
12
  if (!downstreamApp.repo) {
@@ -110,7 +111,7 @@ class PipelinesDiff extends command_1.Command {
110
111
  }
111
112
  const { body: pipeline } = await (0, api_1.getPipeline)(this.heroku, coupling.pipeline.id);
112
113
  const targetAppId = coupling.app.id;
113
- const generation = pipeline.generation.name;
114
+ const generation = (0, generation_1.getGeneration)(pipeline);
114
115
  core_1.ux.action.start('Fetching apps from pipeline');
115
116
  const allApps = await (0, api_1.listPipelineApps)(this.heroku, coupling.pipeline.id);
116
117
  core_1.ux.action.stop();
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const command_1 = require("@heroku-cli/command");
4
4
  const core_1 = require("@oclif/core");
5
+ const generation_1 = require("../../../lib/apps/generation");
5
6
  const METRICS_HOST = 'api.metrics.heroku.com';
6
7
  const isPerfOrPrivateTier = (size) => {
7
8
  const applicableTiers = ['performance', 'private', 'shield'];
@@ -26,7 +27,7 @@ class Enable extends command_1.Command {
26
27
  const app = appResponse.body;
27
28
  const formations = formationResponse.body;
28
29
  const webFormation = formations.find((f) => f.type === 'web');
29
- if (app.generation === 'fir') {
30
+ if ((0, generation_1.getGeneration)(app) === 'fir') {
30
31
  throw new Error('Autoscaling is unavailable for apps in this space. See https://devcenter.heroku.com/articles/generations.');
31
32
  }
32
33
  if (!webFormation)
@@ -7,6 +7,7 @@ const tsheredoc_1 = require("tsheredoc");
7
7
  const spaces_1 = require("../../lib/spaces/spaces");
8
8
  const completions_1 = require("../../lib/autocomplete/completions");
9
9
  const parsers_1 = require("../../lib/spaces/parsers");
10
+ const generation_1 = require("../../lib/apps/generation");
10
11
  class Create extends command_1.Command {
11
12
  async run() {
12
13
  const { flags, args } = await this.parse(Create);
@@ -59,7 +60,7 @@ class Create extends command_1.Command {
59
60
  core_1.ux.warn(`Use ${color_1.default.cmd('heroku spaces:wait')} to track allocation.`);
60
61
  core_1.ux.styledHeader(space.name);
61
62
  core_1.ux.styledObject({
62
- ID: space.id, Team: space.team.name, Region: space.region.name, CIDR: space.cidr, 'Data CIDR': space.data_cidr, State: space.state, Shield: (0, spaces_1.displayShieldState)(space), Generation: space.generation, 'Created at': space.created_at,
63
+ ID: space.id, Team: space.team.name, Region: space.region.name, CIDR: space.cidr, 'Data CIDR': space.data_cidr, State: space.state, Shield: (0, spaces_1.displayShieldState)(space), Generation: (0, generation_1.getGeneration)(space), 'Created at': space.created_at,
63
64
  }, ['ID', 'Team', 'Region', 'CIDR', 'Data CIDR', 'State', 'Shield', 'Generation', 'Created at']);
64
65
  }
65
66
  }
@@ -6,9 +6,10 @@ const tsheredoc_1 = require("tsheredoc");
6
6
  const confirmCommand_1 = require("../../lib/confirmCommand");
7
7
  const spaces_1 = require("../../lib/spaces/spaces");
8
8
  const color_1 = require("@heroku-cli/color");
9
+ const generation_1 = require("../../lib/apps/generation");
9
10
  class Destroy extends command_1.Command {
10
11
  async run() {
11
- var _a, _b;
12
+ var _a;
12
13
  const { flags, args } = await this.parse(Destroy);
13
14
  const { confirm } = flags;
14
15
  const spaceName = flags.space || args.space;
@@ -23,11 +24,11 @@ class Destroy extends command_1.Command {
23
24
  if (space.state === 'allocated') {
24
25
  ({ body: space.outbound_ips } = await this.heroku.get(`/spaces/${spaceName}/nat`));
25
26
  if (space.outbound_ips && space.outbound_ips.state === 'enabled') {
26
- const ipv6 = ((_a = space.generation) === null || _a === void 0 ? void 0 : _a.name) === 'fir' ? ' and IPv6' : '';
27
+ const ipv6 = (0, generation_1.getGeneration)(space) === 'fir' ? ' and IPv6' : '';
27
28
  natWarning = (0, tsheredoc_1.default) `
28
29
  ${color_1.default.dim('===')} ${color_1.default.bold('WARNING: Outbound IPs Will Be Reused')}
29
30
  ${color_1.default.yellow(`⚠️ Deleting this space frees up the following outbound IPv4${ipv6} IPs for reuse:`)}
30
- ${color_1.default.bold((_b = (0, spaces_1.displayNat)(space.outbound_ips)) !== null && _b !== void 0 ? _b : '')}
31
+ ${color_1.default.bold((_a = (0, spaces_1.displayNat)(space.outbound_ips)) !== null && _a !== void 0 ? _a : '')}
31
32
 
32
33
  ${color_1.default.dim('Update the following configurations:')}
33
34
  ${color_1.default.dim('=')} IP allowlists
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const color_1 = require("@heroku-cli/color");
4
4
  const command_1 = require("@heroku-cli/command");
5
5
  const core_1 = require("@oclif/core");
6
+ const generation_1 = require("../../lib/apps/generation");
6
7
  class Index extends command_1.Command {
7
8
  async run() {
8
9
  const { flags } = await this.parse(Index);
@@ -43,7 +44,7 @@ class Index extends command_1.Command {
43
44
  Team: { get: space => space.team.name },
44
45
  Region: { get: space => space.region.name },
45
46
  State: { get: space => space.state },
46
- Generation: { get: space => space.generation },
47
+ Generation: { get: space => (0, generation_1.getGeneration)(space) },
47
48
  createdAt: {
48
49
  header: 'Created At',
49
50
  get: space => space.created_at,
@@ -3,7 +3,7 @@ export declare const trapConfirmationRequired: <T>(app: string, confirm: string
3
3
  export declare const formatPrice: ({ price, hourly }: {
4
4
  price: Heroku.AddOn['price'] | number;
5
5
  hourly?: boolean | undefined;
6
- }) => any;
6
+ }) => string | undefined;
7
7
  export declare const formatPriceText: (price: Heroku.AddOn['price']) => string;
8
8
  export declare const grandfatheredPrice: (addon: Heroku.AddOn) => any;
9
9
  export declare const formatState: (state: string) => string;
@@ -3,17 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatState = exports.grandfatheredPrice = exports.formatPriceText = exports.formatPrice = exports.trapConfirmationRequired = void 0;
4
4
  const confirmCommand_1 = require("../confirmCommand");
5
5
  const color_1 = require("@heroku-cli/color");
6
- const printf = require('printf');
7
- const trapConfirmationRequired = async function (app, confirm, fn) {
8
- return await fn(confirm)
9
- .catch((error) => {
10
- if (!error.body || error.body.id !== 'confirmation_required')
6
+ const printf = require("printf");
7
+ const trapConfirmationRequired = async (app, confirm, fn) => {
8
+ var _a;
9
+ try {
10
+ return await fn(confirm);
11
+ }
12
+ catch (error) {
13
+ if (!isHttpError(error) || ((_a = error.body) === null || _a === void 0 ? void 0 : _a.id) !== 'confirmation_required') {
11
14
  throw error;
12
- return (0, confirmCommand_1.default)(app, confirm, error.body.message)
13
- .then(() => fn(app));
14
- });
15
+ }
16
+ await (0, confirmCommand_1.default)(app, confirm, error.body.message);
17
+ return fn(app);
18
+ }
15
19
  };
16
20
  exports.trapConfirmationRequired = trapConfirmationRequired;
21
+ function isHttpError(error) {
22
+ return Boolean(error) && error instanceof Error && Reflect.has(error, 'body');
23
+ }
17
24
  // This function assumes that price.cents will reflect price per month.
18
25
  // If the API returns any unit other than month
19
26
  // this function will need to be updated.
@@ -1,4 +1,21 @@
1
1
  import { APIClient } from '@heroku-cli/command';
2
- import { App } from '../types/fir';
3
- export declare function isFirApp(appOrName: App | string, herokuApi?: APIClient): Promise<boolean>;
4
- export declare function isCedarApp(appOrName: App | string, herokuApi?: APIClient): Promise<boolean>;
2
+ import { App, Space, DynoSize, TeamApp, Pipeline, Generation, AppGeneration, DynoSizeGeneration, PipelineGeneration } from '../types/fir';
3
+ import Dyno from '../run/dyno';
4
+ export declare type GenerationKind = 'fir' | 'cedar';
5
+ export declare type GenerationLike = Generation | AppGeneration | DynoSizeGeneration | PipelineGeneration | Dyno;
6
+ export declare type GenerationCapable = App | Space | DynoSize | TeamApp | Pipeline;
7
+ /**
8
+ * Get the generation of an object
9
+ *
10
+ * @param source The object to get the generation from
11
+ * @returns The generation of the object
12
+ */
13
+ export declare function getGeneration(source: GenerationLike | GenerationCapable | string): GenerationKind | undefined;
14
+ /**
15
+ * Get the generation of an app by id or name
16
+ *
17
+ * @param appIdOrName The id or name of the app to get the generation for
18
+ * @param herokuApi The Heroku API client to use
19
+ * @returns The generation of the app
20
+ */
21
+ export declare function getGenerationByAppId(appIdOrName: string, herokuApi: APIClient): Promise<GenerationKind | undefined>;
@@ -1,24 +1,52 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isCedarApp = exports.isFirApp = void 0;
4
- async function getApp(appOrName, herokuApi) {
5
- if (typeof appOrName === 'string') {
6
- if (herokuApi === undefined)
7
- throw new Error('herokuApi parameter is required when passing an app name');
8
- const { body: app } = await herokuApi.get(`/apps/${appOrName}`, {
9
- headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
10
- });
11
- return app;
3
+ exports.getGenerationByAppId = exports.getGeneration = void 0;
4
+ function getGenerationFromGenerationLike(generation) {
5
+ var _a;
6
+ let maybeGeneration = '';
7
+ if (typeof generation === 'string') {
8
+ maybeGeneration = generation;
12
9
  }
13
- return appOrName;
10
+ else if (generation && 'name' in generation) {
11
+ maybeGeneration = (_a = generation.name) !== null && _a !== void 0 ? _a : '';
12
+ }
13
+ if (/(fir|cedar)/.test(maybeGeneration)) {
14
+ return maybeGeneration;
15
+ }
16
+ // web-1234abcde44-123ab etc. fir
17
+ if (/^web-[0-9a-z]+-[0-9a-z]{5}$/.test(maybeGeneration)) {
18
+ return 'fir';
19
+ }
20
+ // web.n cedar
21
+ if (/^web\.[0-9]+$/.test(maybeGeneration)) {
22
+ return 'cedar';
23
+ }
24
+ return undefined;
14
25
  }
15
- async function isFirApp(appOrName, herokuApi) {
16
- const app = await getApp(appOrName, herokuApi);
17
- return app.generation.name === 'fir';
26
+ /**
27
+ * Get the generation of an object
28
+ *
29
+ * @param source The object to get the generation from
30
+ * @returns The generation of the object
31
+ */
32
+ function getGeneration(source) {
33
+ if (typeof source === 'object' && 'generation' in source) {
34
+ return getGenerationFromGenerationLike(source.generation);
35
+ }
36
+ return getGenerationFromGenerationLike(source);
18
37
  }
19
- exports.isFirApp = isFirApp;
20
- async function isCedarApp(appOrName, herokuApi) {
21
- const app = await getApp(appOrName, herokuApi);
22
- return app.generation.name === 'cedar';
38
+ exports.getGeneration = getGeneration;
39
+ /**
40
+ * Get the generation of an app by id or name
41
+ *
42
+ * @param appIdOrName The id or name of the app to get the generation for
43
+ * @param herokuApi The Heroku API client to use
44
+ * @returns The generation of the app
45
+ */
46
+ async function getGenerationByAppId(appIdOrName, herokuApi) {
47
+ const { body: app } = await herokuApi.get(`/apps/${appIdOrName}`, {
48
+ headers: { Accept: 'application/vnd.heroku+json; version=3.sdk' },
49
+ });
50
+ return getGeneration(app);
23
51
  }
24
- exports.isCedarApp = isCedarApp;
52
+ exports.getGenerationByAppId = getGenerationByAppId;
@@ -51,7 +51,7 @@ async function logDisplayer(heroku, options) {
51
51
  core_1.ux.error(err.stack, { exit: 1 });
52
52
  }
53
53
  });
54
- const firApp = await (0, generation_1.isFirApp)(options.app, heroku);
54
+ const firApp = (await (0, generation_1.getGenerationByAppId)(options.app, heroku)) === 'fir';
55
55
  const isTail = firApp || options.tail;
56
56
  const requestBodyParameters = {
57
57
  source: options.source,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderInfo = exports.displayNat = exports.displayShieldState = void 0;
4
4
  const core_1 = require("@oclif/core");
5
+ const generation_1 = require("../apps/generation");
5
6
  function displayShieldState(space) {
6
7
  return space.shield ? 'on' : 'off';
7
8
  }
@@ -30,7 +31,7 @@ function renderInfo(space, json) {
30
31
  State: space.state,
31
32
  Shield: displayShieldState(space),
32
33
  'Outbound IPs': displayNat(space.outbound_ips),
33
- Generation: space.generation.name,
34
+ Generation: (0, generation_1.getGeneration)(space),
34
35
  'Created at': space.created_at,
35
36
  }, ['ID', 'Team', 'Region', 'CIDR', 'Data CIDR', 'State', 'Shield', 'Outbound IPs', 'Generation', 'Created at']);
36
37
  }