heroku 9.0.0 → 9.1.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.
@@ -36,5 +36,9 @@ export default class DomainsIndex extends Command {
36
36
  extended: boolean;
37
37
  };
38
38
  };
39
+ getFilteredDomains: (filterKeyValue: string, domains: Array<Heroku.Domain>) => {
40
+ size: number;
41
+ filteredDomains: Heroku.Domain[];
42
+ };
39
43
  run(): Promise<void>;
40
44
  }
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const command_1 = require("@heroku-cli/command");
4
+ const color_1 = require("@heroku-cli/color");
4
5
  const core_1 = require("@oclif/core");
5
6
  const Uri = require("urijs");
7
+ const prompts_1 = require("@inquirer/prompts");
8
+ const paginator_1 = require("../../lib/utils/paginator");
9
+ const keyValueParser_1 = require("../../lib/utils/keyValueParser");
6
10
  function isApexDomain(hostname) {
7
11
  if (hostname.includes('*'))
8
12
  return false;
@@ -44,12 +48,44 @@ class DomainsIndex extends command_1.Command {
44
48
  }
45
49
  return tableConfig;
46
50
  };
51
+ this.getFilteredDomains = (filterKeyValue, domains) => {
52
+ const filteredInfo = { size: 0, filteredDomains: domains };
53
+ const { key: filterName, value } = (0, keyValueParser_1.default)(filterKeyValue);
54
+ if (!value) {
55
+ throw new Error('Filter flag has an invalid value');
56
+ }
57
+ if (filterName === 'Domain Name') {
58
+ filteredInfo.filteredDomains = domains.filter(domain => domain.hostname.includes(value));
59
+ }
60
+ if (filterName === 'DNS Record Type') {
61
+ filteredInfo.filteredDomains = domains.filter(domain => {
62
+ const kind = isApexDomain(domain.hostname) ? 'ALIAS or ANAME' : 'CNAME';
63
+ return kind.includes(value);
64
+ });
65
+ }
66
+ if (filterName === 'DNS Target') {
67
+ filteredInfo.filteredDomains = domains.filter(domain => domain.cname.includes(value));
68
+ }
69
+ if (filterName === 'SNI Endpoint') {
70
+ filteredInfo.filteredDomains = domains.filter(domain => {
71
+ if (!domain.sni_endpoint)
72
+ domain.sni_endpoint = '';
73
+ return domain.sni_endpoint.includes(value);
74
+ });
75
+ }
76
+ filteredInfo.size = filteredInfo.filteredDomains.length;
77
+ return filteredInfo;
78
+ };
47
79
  }
48
80
  async run() {
49
81
  const { flags } = await this.parse(DomainsIndex);
50
- const { body: domains } = await this.heroku.get(`/apps/${flags.app}/domains`);
51
- const herokuDomain = domains.find(domain => domain.kind === 'heroku');
52
- const customDomains = domains.filter(domain => domain.kind === 'custom');
82
+ const domains = await (0, paginator_1.paginateRequest)(this.heroku, `/apps/${flags.app}/domains`, 1000);
83
+ const herokuDomain = domains.find((domain) => domain.kind === 'heroku');
84
+ let customDomains = domains.filter((domain) => domain.kind === 'custom');
85
+ let displayTotalDomains = false;
86
+ if (flags.filter) {
87
+ customDomains = this.getFilteredDomains(flags.filter, domains).filteredDomains;
88
+ }
53
89
  if (flags.json) {
54
90
  core_1.ux.styledJSON(domains);
55
91
  }
@@ -57,6 +93,14 @@ class DomainsIndex extends command_1.Command {
57
93
  core_1.ux.styledHeader(`${flags.app} Heroku Domain`);
58
94
  core_1.ux.log(herokuDomain && herokuDomain.hostname);
59
95
  if (customDomains && customDomains.length > 0) {
96
+ core_1.ux.log();
97
+ if (customDomains.length > 100 && !flags.csv) {
98
+ core_1.ux.warn(`This app has over 100 domains. Your terminal may not be configured to display the total amount of domains. You can export all domains into a CSV file with: ${color_1.default.cyan('heroku domains -a example-app --csv > example-file.csv')}`);
99
+ displayTotalDomains = await (0, prompts_1.confirm)({ default: false, message: `Display all ${customDomains.length} domains?`, theme: { prefix: '', style: { defaultAnswer: () => '(Y/N)' } } });
100
+ if (!displayTotalDomains) {
101
+ return;
102
+ }
103
+ }
60
104
  core_1.ux.log();
61
105
  core_1.ux.styledHeader(`${flags.app} Custom Domains`);
62
106
  core_1.ux.table(customDomains, this.tableConfig(true), Object.assign(Object.assign({}, flags), { 'no-truncate': true }));
@@ -13,12 +13,15 @@ const stackLabelMap = {
13
13
  * @returns {null} null
14
14
  */
15
15
  function ensureContainerStack(app, cmd) {
16
- var _a, _b, _c;
17
- if (((_a = app.stack) === null || _a === void 0 ? void 0 : _a.name) !== 'container') {
18
- const appLabel = (((_b = app.stack) === null || _b === void 0 ? void 0 : _b.name) && stackLabelMap[app.stack.name]) || ((_c = app.stack) === null || _c === void 0 ? void 0 : _c.name);
16
+ var _a, _b;
17
+ const buildStack = (_a = app.build_stack) === null || _a === void 0 ? void 0 : _a.name;
18
+ const appStack = (_b = app.stack) === null || _b === void 0 ? void 0 : _b.name;
19
+ const allowedStack = 'container';
20
+ // either can be container stack and are allowed
21
+ if (buildStack !== allowedStack && appStack !== allowedStack) {
19
22
  let message = 'This command is for Docker apps only.';
20
23
  if (['push', 'release'].includes(cmd)) {
21
- message += ` Run ${color_1.default.cyan('git push heroku main')} to deploy your ${color_1.default.cyan(app.name)} ${color_1.default.cyan(appLabel)} app instead.`;
24
+ message += ` Switch stacks by running ${color_1.default.cmd('heroku stack:set container')}. Or, to deploy ${color_1.default.app(app.name)} with ${color_1.default.yellow(appStack)}, run ${color_1.default.cmd('git push heroku main')} instead.`;
22
25
  }
23
26
  core_1.ux.error(message, { exit: 1 });
24
27
  }
@@ -86,7 +86,6 @@ async function getAttachment(heroku, app, db = 'DATABASE_URL', namespace = '') {
86
86
  if (attachments.length === 0) {
87
87
  throw new Error(`${color_1.default.app(app)} has no databases`);
88
88
  }
89
- console.log('hi', JSON.stringify(attachments));
90
89
  matches = attachments.filter(attachment => config[db] && config[db] === config[(0, util_1.getConfigVarName)(attachment.config_vars)]);
91
90
  if (matches.length === 0) {
92
91
  const validOptions = attachments.map(attachment => (0, util_1.getConfigVarName)(attachment.config_vars));
@@ -0,0 +1,4 @@
1
+ export default function parseKeyValue(input: string): {
2
+ key: string;
3
+ value: string;
4
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function parseKeyValue(input) {
4
+ let [key, value] = input.split(/=(.+)/);
5
+ key = key.trim();
6
+ value = value ? value.trim() : '';
7
+ return { key, value };
8
+ }
9
+ exports.default = parseKeyValue;
@@ -0,0 +1,2 @@
1
+ import { APIClient } from '@heroku-cli/command';
2
+ export declare function paginateRequest<T = unknown>(client: APIClient, url: string, pageSize?: number): Promise<T[]>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ // page size ranges from 200 - 1000 seen here
3
+ // https://devcenter.heroku.com/articles/platform-api-reference#ranges
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.paginateRequest = void 0;
6
+ async function paginateRequest(client, url, pageSize = 200) {
7
+ let isPartial = true;
8
+ let isFirstRequest = true;
9
+ let nextRange = '';
10
+ let aggregatedResponseBody = [];
11
+ while (isPartial) {
12
+ const response = await client.get(url, {
13
+ headers: {
14
+ Range: `${(isPartial && !isFirstRequest) ? `${nextRange}` : `id ..; max=${pageSize};`}`,
15
+ },
16
+ partial: true,
17
+ });
18
+ aggregatedResponseBody = [...response.body, ...aggregatedResponseBody];
19
+ isFirstRequest = false;
20
+ if (response.statusCode === 206) {
21
+ nextRange = response.headers['next-range'];
22
+ }
23
+ else {
24
+ isPartial = false;
25
+ }
26
+ }
27
+ return aggregatedResponseBody;
28
+ }
29
+ exports.paginateRequest = paginateRequest;