heroku 11.1.1 → 11.2.0-beta.1

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.
Files changed (123) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/run.js +3 -6
  3. package/dist/commands/access/index.js +9 -8
  4. package/dist/commands/addons/index.d.ts +2 -1
  5. package/dist/commands/addons/index.js +70 -70
  6. package/dist/commands/apps/create.js +2 -1
  7. package/dist/commands/apps/diff.js +3 -2
  8. package/dist/commands/apps/index.js +4 -3
  9. package/dist/commands/apps/info.js +83 -83
  10. package/dist/commands/apps/transfer.d.ts +1 -4
  11. package/dist/commands/apps/transfer.js +4 -3
  12. package/dist/commands/auth/token.js +2 -1
  13. package/dist/commands/certs/add.d.ts +3 -12
  14. package/dist/commands/certs/add.js +8 -7
  15. package/dist/commands/certs/auto/index.js +2 -1
  16. package/dist/commands/certs/generate.d.ts +1 -1
  17. package/dist/commands/certs/generate.js +4 -3
  18. package/dist/commands/config/edit.d.ts +1 -1
  19. package/dist/commands/config/edit.js +52 -51
  20. package/dist/commands/dashboard.js +31 -30
  21. package/dist/commands/data/maintenances/info.d.ts +1 -1
  22. package/dist/commands/data/maintenances/info.js +4 -3
  23. package/dist/commands/data/maintenances/run.js +2 -2
  24. package/dist/commands/data/maintenances/schedule.d.ts +1 -1
  25. package/dist/commands/data/maintenances/schedule.js +4 -3
  26. package/dist/commands/data/pg/create.js +9 -9
  27. package/dist/commands/data/pg/credentials/create.js +2 -2
  28. package/dist/commands/data/pg/credentials/index.d.ts +1 -0
  29. package/dist/commands/data/pg/credentials/index.js +3 -3
  30. package/dist/commands/data/pg/fork.d.ts +1 -1
  31. package/dist/commands/data/pg/fork.js +4 -3
  32. package/dist/commands/data/pg/info.js +40 -40
  33. package/dist/commands/data/pg/settings.js +3 -3
  34. package/dist/commands/data/pg/update.js +8 -8
  35. package/dist/commands/domains/add.d.ts +2 -2
  36. package/dist/commands/domains/add.js +6 -5
  37. package/dist/commands/domains/index.d.ts +2 -1
  38. package/dist/commands/domains/index.js +13 -11
  39. package/dist/commands/keys/add.js +13 -12
  40. package/dist/commands/local/index.d.ts +2 -0
  41. package/dist/commands/local/index.js +46 -11
  42. package/dist/commands/local/run.js +10 -2
  43. package/dist/commands/local/version.js +10 -2
  44. package/dist/commands/notifications/index.js +14 -14
  45. package/dist/commands/pg/backups/download.js +12 -12
  46. package/dist/commands/pg/backups/restore.js +15 -15
  47. package/dist/commands/pg/backups/schedule.d.ts +6 -6
  48. package/dist/commands/pg/backups/schedule.js +18 -18
  49. package/dist/commands/pg/backups/url.js +2 -2
  50. package/dist/commands/pg/credentials/create.js +5 -5
  51. package/dist/commands/pg/credentials/destroy.js +2 -2
  52. package/dist/commands/pg/credentials/rotate.js +2 -2
  53. package/dist/commands/pg/credentials.d.ts +1 -0
  54. package/dist/commands/pg/credentials.js +3 -3
  55. package/dist/commands/pipelines/add.js +2 -1
  56. package/dist/commands/pipelines/create.js +2 -1
  57. package/dist/commands/ps/copy.js +3 -3
  58. package/dist/commands/ps/exec.js +2 -2
  59. package/dist/commands/ps/forward.js +1 -1
  60. package/dist/commands/ps/index.d.ts +1 -0
  61. package/dist/commands/ps/index.js +115 -115
  62. package/dist/commands/ps/scale.js +2 -1
  63. package/dist/commands/ps/type.js +6 -5
  64. package/dist/commands/releases/index.js +2 -1
  65. package/dist/commands/releases/rollback.js +2 -2
  66. package/dist/commands/spaces/destroy.js +7 -7
  67. package/dist/commands/spaces/drains/set.js +2 -2
  68. package/dist/commands/spaces/peerings/accept.js +1 -1
  69. package/dist/commands/spaces/ps.js +2 -2
  70. package/dist/commands/spaces/trusted-ips/add.js +2 -2
  71. package/dist/commands/spaces/trusted-ips/remove.js +1 -1
  72. package/dist/commands/status.js +2 -1
  73. package/dist/commands/version/info.js +2 -6
  74. package/dist/hooks/command_not_found/{performance_analytics.js → setup-otel-telemetry.js} +3 -2
  75. package/dist/hooks/init/{performance_analytics.js → setup-otel-telemetry.js} +3 -2
  76. package/dist/hooks/init/terms-of-service.js +1 -1
  77. package/dist/hooks/init/version.js +9 -6
  78. package/dist/hooks/postrun/{performance_analytics.js → send-otel-telemetry.js} +3 -2
  79. package/dist/hooks/prerun/collect-and-send-herokulytics.js +34 -0
  80. package/dist/{analytics.d.ts → lib/analytics-telemetry/backboard-herokulytics-client.d.ts} +20 -19
  81. package/dist/{analytics.js → lib/analytics-telemetry/backboard-herokulytics-client.js} +72 -54
  82. package/dist/lib/analytics-telemetry/backboard-otel-client.d.ts +16 -0
  83. package/dist/lib/analytics-telemetry/backboard-otel-client.js +121 -0
  84. package/dist/{user-config.d.ts → lib/analytics-telemetry/herokulytics-config.d.ts} +9 -9
  85. package/dist/{user-config.js → lib/analytics-telemetry/herokulytics-config.js} +40 -40
  86. package/dist/lib/analytics-telemetry/sentry-client.d.ts +14 -8
  87. package/dist/lib/analytics-telemetry/sentry-client.js +51 -50
  88. package/dist/lib/analytics-telemetry/telemetry-manager.d.ts +43 -0
  89. package/dist/lib/analytics-telemetry/telemetry-manager.js +133 -0
  90. package/dist/lib/analytics-telemetry/telemetry-utils.d.ts +18 -4
  91. package/dist/lib/analytics-telemetry/telemetry-utils.js +13 -9
  92. package/dist/lib/analytics-telemetry/telemetry-worker.js +56 -8
  93. package/dist/lib/analytics-telemetry/worker-client.d.ts +3 -2
  94. package/dist/lib/analytics-telemetry/worker-client.js +5 -16
  95. package/dist/lib/data/poolConfig.js +2 -2
  96. package/dist/lib/data/utils.d.ts +1 -1
  97. package/dist/lib/data/utils.js +1 -1
  98. package/dist/lib/lazy-module-loader.d.ts +58 -0
  99. package/dist/lib/lazy-module-loader.js +100 -0
  100. package/dist/lib/local/fork-foreman.d.ts +5 -0
  101. package/dist/lib/local/fork-foreman.js +18 -5
  102. package/dist/lib/local/load-foreman-procfile.js +3 -0
  103. package/dist/lib/ps-exec/exec.js +2 -2
  104. package/dist/lib/releases/status_helper.d.ts +1 -1
  105. package/dist/lib/run/colorize.js +10 -10
  106. package/dist/lib/utils/tableUtils.d.ts +4 -0
  107. package/dist/lib/utils/tableUtils.js +6 -0
  108. package/npm-shrinkwrap.json +336 -249
  109. package/oclif.manifest.json +3774 -3737
  110. package/package.json +9 -9
  111. package/dist/deps.d.ts +0 -9
  112. package/dist/deps.js +0 -14
  113. package/dist/hooks/prerun/analytics.js +0 -23
  114. package/dist/lib/analytics-telemetry/global-telemetry.d.ts +0 -30
  115. package/dist/lib/analytics-telemetry/global-telemetry.js +0 -103
  116. package/dist/lib/analytics-telemetry/honeycomb-client.d.ts +0 -15
  117. package/dist/lib/analytics-telemetry/honeycomb-client.js +0 -135
  118. /package/dist/hooks/command_not_found/{performance_analytics.d.ts → setup-otel-telemetry.d.ts} +0 -0
  119. /package/dist/hooks/finally/{sentry.d.ts → send-otel-and-sentry-errors.d.ts} +0 -0
  120. /package/dist/hooks/finally/{sentry.js → send-otel-and-sentry-errors.js} +0 -0
  121. /package/dist/hooks/init/{performance_analytics.d.ts → setup-otel-telemetry.d.ts} +0 -0
  122. /package/dist/hooks/postrun/{performance_analytics.d.ts → send-otel-telemetry.d.ts} +0 -0
  123. /package/dist/hooks/prerun/{analytics.d.ts → collect-and-send-herokulytics.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
6
 
7
+ ## [11.2.0-beta.1](https://github.com/heroku/cli/compare/v11.1.1...v11.2.0-beta.1) (2026-04-08)
8
+
9
+
10
+ ### Features
11
+
12
+ * adding --start-cmd flag to heroku local when no Procfile is present ([#3638](https://github.com/heroku/cli/issues/3638)) ([b9b8fed](https://github.com/heroku/cli/commit/b9b8fed8f44ad88946edc4141ec1fe41f157baac))
13
+ * include a --no-wrap flag for table displays ([#3613](https://github.com/heroku/cli/issues/3613)) ([1d27c09](https://github.com/heroku/cli/commit/1d27c09f3973c81bb610583c3afe418ff0c867a6))
14
+ * update color usage, telemetry activation logic, and command/util dependencies ([#3646](https://github.com/heroku/cli/issues/3646)) ([ede6655](https://github.com/heroku/cli/commit/ede665557ca3d387e3e57f5a438cd7ab7c27b1b3))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * flaky apps:diff tests ([#3649](https://github.com/heroku/cli/issues/3649)) ([8c4f8c3](https://github.com/heroku/cli/commit/8c4f8c3e5c99f688cbbefcf91a44ed234ba6c65b))
20
+ * improve telemetry worker stderr cleanup ([#3648](https://github.com/heroku/cli/issues/3648)) ([150e74a](https://github.com/heroku/cli/commit/150e74abe77b7c51b6b9ea84c8e246fe3165c6b5))
21
+ * procfile comment parsing ([#3641](https://github.com/heroku/cli/issues/3641)) ([aae3751](https://github.com/heroku/cli/commit/aae37512b54bdcf2b832429254f0d2c0fe1f42d3))
22
+
23
+
24
+ ### Code Refactoring
25
+
26
+ * unify telemetry architecture and use background workers for all telemetry ([#3642](https://github.com/heroku/cli/issues/3642)) ([c13074d](https://github.com/heroku/cli/commit/c13074da5f0892fa412ce6bc7c4b1d998eae3366))
27
+
28
+
29
+ ### Tests
30
+
31
+ * this fixes the mocking difference causing flappy tests and adds chore to PR release title ([#3640](https://github.com/heroku/cli/issues/3640)) ([32920f9](https://github.com/heroku/cli/commit/32920f9600cc6b06815aa7db532dd8bb352d90db))
32
+
7
33
  ## [11.1.1](https://github.com/heroku/cli/compare/v11.1.0...v11.1.1) (2026-04-01)
8
34
 
9
35
 
package/bin/run.js CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env -S node --no-deprecation
2
2
 
3
- /* eslint-disable n/no-unpublished-bin */
4
-
5
3
  import {execute, settings} from '@oclif/core'
6
4
 
7
5
  // Enable performance tracking when oclif:perf is specified in DEBUG
@@ -14,10 +12,9 @@ process.env.HEROKU_UPDATE_INSTRUCTIONS = process.env.HEROKU_UPDATE_INSTRUCTIONS
14
12
  const now = new Date()
15
13
  const cliStartTime = now.getTime()
16
14
 
17
- // Skip telemetry entirely on Windows for performance (unless explicitly enabled)
18
- const enableTelemetry = process.platform !== 'win32' || process.env.ENABLE_WINDOWS_TELEMETRY === 'true'
15
+ const {isTelemetryEnabled} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
19
16
 
20
- if (enableTelemetry) {
17
+ if (isTelemetryEnabled()) {
21
18
  // Dynamically import telemetry modules
22
19
  const {setupTelemetryHandlers} = await import('../dist/lib/analytics-telemetry/worker-client.js')
23
20
  const {computeDuration} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
@@ -26,7 +23,7 @@ if (enableTelemetry) {
26
23
  setupTelemetryHandlers({
27
24
  cliStartTime,
28
25
  computeDuration,
29
- enableTelemetry,
26
+ enableTelemetry: isTelemetryEnabled(),
30
27
  })
31
28
  }
32
29
 
@@ -2,7 +2,7 @@ import { Command, flags } from '@heroku-cli/command';
2
2
  import { HerokuAPIError } from '@heroku-cli/command/lib/api-client.js';
3
3
  import { color, hux } from '@heroku/heroku-cli-util';
4
4
  import { ux } from '@oclif/core/ux';
5
- import _ from 'lodash';
5
+ import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
6
6
  import { getOwner, isTeamApp } from '../../lib/teamUtils.js';
7
7
  export default class AccessIndex extends Command {
8
8
  static description = 'list who has access to an app';
@@ -13,6 +13,7 @@ export default class AccessIndex extends Command {
13
13
  };
14
14
  static topic = 'access';
15
15
  async run() {
16
+ const _ = await lazyModuleLoader.loadLodash();
16
17
  const { flags } = await this.parse(AccessIndex);
17
18
  const { app: appName, json } = flags;
18
19
  const { body: app } = await this.heroku.get(`/apps/${appName}`);
@@ -28,7 +29,7 @@ export default class AccessIndex extends Command {
28
29
  admin.permissions = adminPermissions;
29
30
  return admin;
30
31
  });
31
- collaborators = buildCollaboratorsArray(collaborators, admins);
32
+ collaborators = buildCollaboratorsArray(collaborators, admins, _);
32
33
  }
33
34
  catch (error) {
34
35
  if (!(error instanceof HerokuAPIError && error.http.statusCode === 403))
@@ -38,10 +39,10 @@ export default class AccessIndex extends Command {
38
39
  if (json)
39
40
  printJSON(collaborators);
40
41
  else
41
- printAccess(app, collaborators);
42
+ printAccess(app, collaborators, _);
42
43
  }
43
44
  }
44
- function buildCollaboratorsArray(collaboratorsRaw, admins) {
45
+ function buildCollaboratorsArray(collaboratorsRaw, admins, _) {
45
46
  const collaboratorsNoAdmins = _.reject(collaboratorsRaw, { role: 'admin' });
46
47
  return _.union(collaboratorsNoAdmins, admins);
47
48
  }
@@ -62,12 +63,12 @@ function buildTableColumns(showPermissions) {
62
63
  }
63
64
  return baseColumns;
64
65
  }
65
- function printAccess(app, collaborators) {
66
+ function printAccess(app, collaborators, _) {
66
67
  const showPermissions = isTeamApp(app.owner?.email);
67
68
  collaborators = _.chain(collaborators)
68
- .sortBy(c => c.email || c.user.email)
69
- .reject(c => /herokumanager\.com$/.test(c.user.email))
70
- .map(collab => {
69
+ .sortBy((c) => c.email || c.user.email)
70
+ .reject((c) => /herokumanager\.com$/.test(c.user.email))
71
+ .map((collab) => {
71
72
  const { email } = collab.user;
72
73
  const { permissions, role } = collab;
73
74
  const data = { email, role: role || 'collaborator' };
@@ -1,6 +1,5 @@
1
1
  import { Command } from '@heroku-cli/command';
2
2
  import * as Heroku from '@heroku-cli/schema';
3
- export declare function renderAttachment(attachment: Heroku.AddOnAttachment, app: string, isLast?: boolean): string;
4
3
  export default class Addons extends Command {
5
4
  static description: string;
6
5
  static examples: string[];
@@ -8,9 +7,11 @@ export default class Addons extends Command {
8
7
  all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
8
  app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  };
13
13
  static topic: string;
14
14
  static usage: string;
15
15
  run(): Promise<void>;
16
16
  }
17
+ export declare function renderAttachment(attachment: Heroku.AddOnAttachment, app: string, isLast?: boolean): string;
@@ -1,9 +1,55 @@
1
- import { color, hux } from '@heroku/heroku-cli-util';
2
1
  import { Command, flags } from '@heroku-cli/command';
2
+ import { color, hux } from '@heroku/heroku-cli-util';
3
3
  import { ux } from '@oclif/core/ux';
4
4
  import _ from 'lodash';
5
5
  import { formatPrice, formatState, grandfatheredPrice } from '../../lib/addons/util.js';
6
+ import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
6
7
  const topic = 'addons';
8
+ export default class Addons extends Command {
9
+ static description = `Lists your add-ons and attachments.
10
+
11
+ The default filter applied depends on whether you are in a Heroku app
12
+ directory. If so, the --app flag is implied. If not, the default of --all
13
+ is implied. Explicitly providing either flag overrides the default
14
+ behavior.
15
+ `;
16
+ static examples = [
17
+ `${color.command(`heroku ${topic} --all`)}`,
18
+ `${color.command(`heroku ${topic} --app acme-inc-www`)}`,
19
+ ];
20
+ static flags = {
21
+ all: flags.boolean({ char: 'A', description: 'show add-ons and attachments for all accessible apps' }),
22
+ app: flags.app(),
23
+ json: flags.boolean({ description: 'return add-ons in json format' }),
24
+ 'no-wrap': flags.noWrap(),
25
+ remote: flags.remote(),
26
+ };
27
+ static topic = topic;
28
+ static usage = 'addons [--all|--app APP]';
29
+ async run() {
30
+ const { flags } = await this.parse(Addons);
31
+ const { all, app, json } = flags;
32
+ if (!all && app) {
33
+ const addons = await addonGetter(this.heroku, app);
34
+ if (json)
35
+ displayJSON(addons);
36
+ else
37
+ displayForApp(app, addons, flags['no-wrap']);
38
+ }
39
+ else {
40
+ const addons = await addonGetter(this.heroku);
41
+ if (json)
42
+ displayJSON(addons);
43
+ else
44
+ displayAll(addons, flags['no-wrap']);
45
+ }
46
+ }
47
+ }
48
+ export function renderAttachment(attachment, app, isLast = false) {
49
+ const line = isLast ? '\u2514\u2500' : '\u251C\u2500';
50
+ const attName = formatAttachment(attachment, attachment.app?.name !== app);
51
+ return ` ${color.gray(line)} ${attName}`;
52
+ }
7
53
  async function addonGetter(api, app) {
8
54
  let attachmentsResponse = null;
9
55
  let addonsResponse;
@@ -67,7 +113,7 @@ async function addonGetter(api, app) {
67
113
  });
68
114
  return addons;
69
115
  }
70
- function displayAll(addons) {
116
+ function displayAll(addons, noWrap = false) {
71
117
  addons = _.sortBy(addons, 'app.name', 'plan.name', 'addon.name');
72
118
  if (addons.length === 0) {
73
119
  ux.stdout('No add-ons.');
@@ -84,21 +130,21 @@ function displayAll(addons) {
84
130
  Plan: {
85
131
  get({ plan }) {
86
132
  if (plan === undefined)
87
- return color.dim('?');
133
+ return color.inactive('?');
88
134
  return plan.name;
89
135
  },
90
136
  },
91
137
  Price: {
92
138
  get({ plan }) {
93
139
  if (plan?.price === undefined)
94
- return color.dim('?');
140
+ return color.inactive('?');
95
141
  return formatPrice({ hourly: true, price: plan?.price });
96
142
  },
97
143
  },
98
144
  'Max Price': {
99
145
  get({ plan }) {
100
146
  if (plan?.price === undefined)
101
- return color.dim('?');
147
+ return color.inactive('?');
102
148
  return formatPrice({ hourly: false, price: plan?.price });
103
149
  },
104
150
  },
@@ -121,26 +167,10 @@ function displayAll(addons) {
121
167
  return result;
122
168
  },
123
169
  },
124
- }, {
125
- overflow: 'wrap',
126
- });
170
+ }, huxTableNoWrapOptions(noWrap));
127
171
  /* eslint-enable perfectionist/sort-objects */
128
172
  }
129
- function formatAttachment(attachment, showApp = true) {
130
- const attName = color.attachment(attachment.name || '');
131
- const output = [color.dim('as'), attName];
132
- if (showApp) {
133
- const appInfo = `on ${color.app(attachment.app?.name || '')} app`;
134
- output.push(color.dim(appInfo));
135
- }
136
- return output.join(' ');
137
- }
138
- export function renderAttachment(attachment, app, isLast = false) {
139
- const line = isLast ? '\u2514\u2500' : '\u251C\u2500';
140
- const attName = formatAttachment(attachment, attachment.app?.name !== app);
141
- return ` ${color.dim(line)} ${attName}`;
142
- }
143
- function displayForApp(app, addons) {
173
+ function displayForApp(app, addons, noWrap = false) {
144
174
  if (addons.length === 0) {
145
175
  ux.stdout(`No add-ons for app ${app}.`);
146
176
  return;
@@ -150,17 +180,19 @@ function displayForApp(app, addons) {
150
180
  const name = color.addon(addon.name || '');
151
181
  let service = addon.addon_service?.name;
152
182
  if (service === undefined) {
153
- service = color.dim('?');
183
+ service = color.gray('?');
154
184
  }
155
185
  const addonLine = `${service} (${name})`;
156
186
  const atts = _.sortBy(addon.attachments, isForeignApp, 'app.name', 'name');
157
- // render each attachment under the add-on
158
187
  const attLines = atts.map((attachment, idx) => {
159
188
  const isLast = (idx === addon.attachments.length - 1);
160
189
  return renderAttachment(attachment, app, isLast);
161
190
  });
162
- return [addonLine].concat(attLines)
163
- .join('\n') + '\n'; // Separate each add-on row by a blank line
191
+ const lines = [addonLine, ...attLines];
192
+ if (noWrap) {
193
+ return lines.join(' ');
194
+ }
195
+ return `${lines.join('\n')}\n`;
164
196
  }
165
197
  addons = _.sortBy(addons, isForeignApp, 'plan.name', 'name');
166
198
  ux.stdout();
@@ -169,14 +201,14 @@ function displayForApp(app, addons) {
169
201
  Plan: {
170
202
  get: ({ plan }) => plan && plan.name !== undefined
171
203
  ? plan.name.replace(/^[^:]+:/, '')
172
- : color.dim('?'),
204
+ : color.inactive('?'),
173
205
  },
174
206
  Price: {
175
207
  get(addon) {
176
208
  if (addon.app?.name === app) {
177
209
  return formatPrice({ hourly: true, price: addon.plan?.price });
178
210
  }
179
- return color.dim(`(billed to ${color.app(addon.app?.name || '')} app)`);
211
+ return color.gray(`(billed to ${color.app(addon.app?.name || '')} app)`);
180
212
  },
181
213
  },
182
214
  // eslint-disable-next-line perfectionist/sort-objects
@@ -185,56 +217,24 @@ function displayForApp(app, addons) {
185
217
  if (addon.app?.name === app) {
186
218
  return formatPrice({ hourly: false, price: addon.plan?.price });
187
219
  }
188
- return color.dim(`(billed to ${color.app(addon.app?.name || '')} app)`);
220
+ return color.gray(`(billed to ${color.app(addon.app?.name || '')} app)`);
189
221
  },
190
222
  },
191
223
  State: {
192
224
  get: ({ state }) => formatState(state || ''),
193
225
  },
194
- }, {
195
- overflow: 'wrap',
196
- });
226
+ }, huxTableNoWrapOptions(noWrap));
197
227
  ux.stdout(`The table above shows add-ons and the attachments to the current app (${color.app(app)}) or other apps.\n `);
198
228
  }
199
229
  function displayJSON(addons) {
200
230
  ux.stdout(JSON.stringify(addons, null, 2));
201
231
  }
202
- export default class Addons extends Command {
203
- static description = `Lists your add-ons and attachments.
204
-
205
- The default filter applied depends on whether you are in a Heroku app
206
- directory. If so, the --app flag is implied. If not, the default of --all
207
- is implied. Explicitly providing either flag overrides the default
208
- behavior.
209
- `;
210
- static examples = [
211
- `${color.command(`heroku ${topic} --all`)}`,
212
- `${color.command(`heroku ${topic} --app acme-inc-www`)}`,
213
- ];
214
- static flags = {
215
- all: flags.boolean({ char: 'A', description: 'show add-ons and attachments for all accessible apps' }),
216
- app: flags.app(),
217
- json: flags.boolean({ description: 'return add-ons in json format' }),
218
- remote: flags.remote(),
219
- };
220
- static topic = topic;
221
- static usage = 'addons [--all|--app APP]';
222
- async run() {
223
- const { flags } = await this.parse(Addons);
224
- const { all, app, json } = flags;
225
- if (!all && app) {
226
- const addons = await addonGetter(this.heroku, app);
227
- if (json)
228
- displayJSON(addons);
229
- else
230
- displayForApp(app, addons);
231
- }
232
- else {
233
- const addons = await addonGetter(this.heroku);
234
- if (json)
235
- displayJSON(addons);
236
- else
237
- displayAll(addons);
238
- }
232
+ function formatAttachment(attachment, showApp = true) {
233
+ const attName = color.attachment(attachment.name || '');
234
+ const output = [color.gray('as'), attName];
235
+ if (showApp) {
236
+ const appInfo = `on ${color.app(attachment.app?.name || '')} app`;
237
+ output.push(color.gray(appInfo));
239
238
  }
239
+ return output.join(' ');
240
240
  }
@@ -3,8 +3,8 @@ import { Command, flags } from '@heroku-cli/command';
3
3
  import { BuildpackCompletion, RegionCompletion, SpaceCompletion, StackCompletion, } from '@heroku-cli/command/lib/completions.js';
4
4
  import { Args, ux } from '@oclif/core';
5
5
  import fs from 'fs-extra';
6
- import { parse } from 'yaml';
7
6
  import Git from '../../lib/git/git.js';
7
+ import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
8
8
  const git = new Git();
9
9
  function createText(name, space) {
10
10
  let text = `Creating ${name ? color.app(name) : 'app'}`;
@@ -157,6 +157,7 @@ ${color.command('heroku apps:create --region eu')}`];
157
157
  };
158
158
  static hiddenAliases = ['create'];
159
159
  async readManifest() {
160
+ const { parse } = await lazyModuleLoader.loadYaml();
160
161
  const buffer = await fs.readFile('heroku.yml');
161
162
  return parse(buffer.toString());
162
163
  }
@@ -28,8 +28,9 @@ async function checksum(heroku, app) {
28
28
  }
29
29
  }
30
30
  async function diffFiles(heroku, app1, app2) {
31
- const sums = await Promise.all([checksum(heroku, app1), checksum(heroku, app2)]);
32
- return sums[0] === sums[1] ? [] : [{ prop: 'slug (checksum)', app1: sums[0] ?? undefined, app2: sums[1] ?? undefined }];
31
+ const sum1 = await checksum(heroku, app1);
32
+ const sum2 = await checksum(heroku, app2);
33
+ return sum1 === sum2 ? [] : [{ prop: 'slug (checksum)', app1: sum1 ?? undefined, app2: sum2 ?? undefined }];
33
34
  }
34
35
  async function diffEnv(heroku, app1, app2) {
35
36
  const [res1, res2] = await Promise.all([
@@ -2,7 +2,7 @@ import { Command, flags } from '@heroku-cli/command';
2
2
  import { SpaceCompletion } from '@heroku-cli/command/lib/completions.js';
3
3
  import { color, hux } from '@heroku/heroku-cli-util';
4
4
  import { ux } from '@oclif/core/ux';
5
- import _ from 'lodash';
5
+ import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
6
6
  export default class AppsIndex extends Command {
7
7
  static description = 'list your apps';
8
8
  static examples = [
@@ -23,6 +23,7 @@ export default class AppsIndex extends Command {
23
23
  static hiddenAliases = ['list', 'apps:list'];
24
24
  static topic = 'apps';
25
25
  async run() {
26
+ const _ = await lazyModuleLoader.loadLodash();
26
27
  const { flags } = await this.parse(AppsIndex);
27
28
  const teamIdentifier = flags.team;
28
29
  let team = (!flags.personal && teamIdentifier) ? teamIdentifier : null;
@@ -54,7 +55,7 @@ export default class AppsIndex extends Command {
54
55
  hux.styledJSON(apps);
55
56
  }
56
57
  else {
57
- print(apps, user, space, team);
58
+ print(apps, user, space, team, _);
58
59
  }
59
60
  }
60
61
  }
@@ -74,7 +75,7 @@ function annotateAppName(app) {
74
75
  function listApps(apps) {
75
76
  apps.forEach((app) => ux.stdout(regionizeAppName(app)));
76
77
  }
77
- function print(apps, user, space, team) {
78
+ function print(apps, user, space, team, _) {
78
79
  if (apps.length === 0) {
79
80
  if (space)
80
81
  ux.stdout(`There are no apps in space ${color.space(space)}.`);
@@ -1,88 +1,10 @@
1
- import { color, hux } from '@heroku/heroku-cli-util';
2
1
  import { Command, flags } from '@heroku-cli/command';
2
+ import { color, hux } from '@heroku/heroku-cli-util';
3
3
  import { Args, ux } from '@oclif/core';
4
4
  import { filesize } from 'filesize';
5
- import _ from 'lodash';
6
5
  import * as util from 'util';
7
6
  import { getGeneration } from '../../lib/apps/generation.js';
8
- const { countBy, snakeCase } = _;
9
- function formatDate(date) {
10
- return date.toISOString();
11
- }
12
- async function getInfo(app, client, extended) {
13
- const promises = [
14
- client.heroku.get(`/apps/${app}/addons`),
15
- client.heroku.request(`/apps/${app}`),
16
- client.heroku.get(`/apps/${app}/dynos`).catch(() => ({ body: [] })),
17
- client.heroku.get(`/apps/${app}/collaborators`).catch(() => ({ body: [] })),
18
- client.heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => ({ body: null })),
19
- ];
20
- if (extended) {
21
- promises.push(client.heroku.get(`/apps/${app}?extended=true`));
22
- }
23
- const [{ body: addons }, { body: appWithMoreInfo }, { body: dynos }, { body: collaborators }, { body: pipelineCouplings }, appExtendedResponse,] = await Promise.all(promises);
24
- const data = {
25
- addons,
26
- app: appWithMoreInfo,
27
- collaborators,
28
- dynos,
29
- pipeline_coupling: pipelineCouplings,
30
- };
31
- if (appExtendedResponse) {
32
- data.appExtended = appExtendedResponse.body;
33
- }
34
- if (extended) {
35
- data.appExtended.acm = data.app.acm;
36
- data.app = data.appExtended;
37
- delete data.appExtended;
38
- }
39
- return data;
40
- }
41
- function print(info, addons, collaborators, extended) {
42
- const data = {};
43
- data.Addons = addons;
44
- data.Collaborators = collaborators;
45
- if (info.app.archived_at)
46
- data['Archived At'] = formatDate(new Date(info.app.archived_at));
47
- if (info.app.cron_finished_at)
48
- data['Cron Finished At'] = formatDate(new Date(info.app.cron_finished_at));
49
- if (info.app.cron_next_run)
50
- data['Cron Next Run'] = formatDate(new Date(info.app.cron_next_run));
51
- if (info.app.database_size)
52
- data['Database Size'] = filesize(info.app.database_size, { round: 0, standard: 'jedec' });
53
- if (info.app.create_status !== 'complete')
54
- data['Create Status'] = info.app.create_status;
55
- if (info.app.space)
56
- data.Space = color.space(info.app.space.name);
57
- if (info.app.space && info.app.internal_routing)
58
- data['Internal Routing'] = info.app.internal_routing;
59
- if (info.pipeline_coupling)
60
- data.Pipeline = `${color.pipeline(info.pipeline_coupling.pipeline.name)} - ${info.pipeline_coupling.stage}`;
61
- data['Auto Cert Mgmt'] = info.app.acm;
62
- data['Git URL'] = info.app.git_url;
63
- data['Web URL'] = color.info(info.app.web_url);
64
- data['Repo Size'] = filesize(info.app.repo_size, { round: 0, standard: 'jedec' });
65
- if (getGeneration(info.app) !== 'fir')
66
- data['Slug Size'] = filesize(info.app.slug_size, { round: 0, standard: 'jedec' });
67
- data.Owner = color.user(info.app.owner.email);
68
- data.Region = info.app.region.name;
69
- data.Dynos = countBy(info.dynos, 'type');
70
- data.Stack = (function (app) {
71
- let stack = info.app.stack.name;
72
- if (app.stack.name !== app.build_stack.name) {
73
- stack += ` (next build will use ${app.build_stack.name})`;
74
- }
75
- return stack;
76
- })(info.app);
77
- hux.styledHeader(color.app(info.app.name));
78
- hux.styledObject(data);
79
- if (extended) {
80
- ux.stdout('\n\n--- Extended Information ---\n\n');
81
- if (info.app.extended) {
82
- ux.stdout(util.inspect(info.app.extended));
83
- }
84
- }
85
- }
7
+ import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
86
8
  export default class AppsInfo extends Command {
87
9
  static args = {
88
10
  app: Args.string({ hidden: true }),
@@ -112,6 +34,7 @@ repo_size=5000000
112
34
  static hiddenAliases = ['info'];
113
35
  static topic = 'apps';
114
36
  async run() {
37
+ const _ = await lazyModuleLoader.loadLodash();
115
38
  const { args, flags } = await this.parse(AppsInfo);
116
39
  const app = args.app || flags.app;
117
40
  if (!app)
@@ -123,7 +46,7 @@ repo_size=5000000
123
46
  .sort();
124
47
  function shell() {
125
48
  function print(k, v) {
126
- ux.stdout(`${snakeCase(k)}=${v}`);
49
+ ux.stdout(`${_.snakeCase(k)}=${v}`);
127
50
  }
128
51
  print('auto_cert_mgmt', info.app.acm);
129
52
  print('addons', addons);
@@ -147,7 +70,7 @@ repo_size=5000000
147
70
  print('slug_size', filesize(info.app.slug_size, { round: 0, standard: 'jedec' }));
148
71
  print('owner', info.app.owner.email);
149
72
  print('region', info.app.region.name);
150
- print('dynos', util.inspect(countBy(info.dynos, 'type')));
73
+ print('dynos', util.inspect(_.countBy(info.dynos, 'type')));
151
74
  print('stack', info.app.stack.name);
152
75
  }
153
76
  if (flags.shell) {
@@ -157,7 +80,84 @@ repo_size=5000000
157
80
  hux.styledJSON(info);
158
81
  }
159
82
  else {
160
- print(info, addons, collaborators, flags.extended);
83
+ print(info, addons, collaborators, flags.extended, _);
84
+ }
85
+ }
86
+ }
87
+ function formatDate(date) {
88
+ return date.toISOString();
89
+ }
90
+ async function getInfo(app, client, extended) {
91
+ const promises = [
92
+ client.heroku.get(`/apps/${app}/addons`),
93
+ client.heroku.request(`/apps/${app}`),
94
+ client.heroku.get(`/apps/${app}/dynos`).catch(() => ({ body: [] })),
95
+ client.heroku.get(`/apps/${app}/collaborators`).catch(() => ({ body: [] })),
96
+ client.heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => ({ body: null })),
97
+ ];
98
+ if (extended) {
99
+ promises.push(client.heroku.get(`/apps/${app}?extended=true`));
100
+ }
101
+ const [{ body: addons }, { body: appWithMoreInfo }, { body: dynos }, { body: collaborators }, { body: pipelineCouplings }, appExtendedResponse,] = await Promise.all(promises);
102
+ const data = {
103
+ addons,
104
+ app: appWithMoreInfo,
105
+ collaborators,
106
+ dynos,
107
+ pipeline_coupling: pipelineCouplings,
108
+ };
109
+ if (appExtendedResponse) {
110
+ data.appExtended = appExtendedResponse.body;
111
+ }
112
+ if (extended) {
113
+ data.appExtended.acm = data.app.acm;
114
+ data.app = data.appExtended;
115
+ delete data.appExtended;
116
+ }
117
+ return data;
118
+ }
119
+ function print(info, addons, collaborators, extended, _) {
120
+ const data = {};
121
+ data.Addons = addons;
122
+ data.Collaborators = collaborators;
123
+ if (info.app.archived_at)
124
+ data['Archived At'] = formatDate(new Date(info.app.archived_at));
125
+ if (info.app.cron_finished_at)
126
+ data['Cron Finished At'] = formatDate(new Date(info.app.cron_finished_at));
127
+ if (info.app.cron_next_run)
128
+ data['Cron Next Run'] = formatDate(new Date(info.app.cron_next_run));
129
+ if (info.app.database_size)
130
+ data['Database Size'] = filesize(info.app.database_size, { round: 0, standard: 'jedec' });
131
+ if (info.app.create_status !== 'complete')
132
+ data['Create Status'] = info.app.create_status;
133
+ if (info.app.space)
134
+ data.Space = color.space(info.app.space.name);
135
+ if (info.app.space && info.app.internal_routing)
136
+ data['Internal Routing'] = info.app.internal_routing;
137
+ if (info.pipeline_coupling)
138
+ data.Pipeline = `${color.pipeline(info.pipeline_coupling.pipeline.name)} - ${info.pipeline_coupling.stage}`;
139
+ data['Auto Cert Mgmt'] = info.app.acm;
140
+ data['Git URL'] = info.app.git_url;
141
+ data['Web URL'] = color.info(info.app.web_url);
142
+ data['Repo Size'] = filesize(info.app.repo_size, { round: 0, standard: 'jedec' });
143
+ if (getGeneration(info.app) !== 'fir')
144
+ data['Slug Size'] = filesize(info.app.slug_size, { round: 0, standard: 'jedec' });
145
+ data.Owner = color.user(info.app.owner.email);
146
+ data.Region = info.app.region.name;
147
+ data.Dynos = _.countBy(info.dynos, 'type');
148
+ data.Stack = (function (app) {
149
+ let stack = info.app.stack.name;
150
+ if (app.stack.name !== app.build_stack.name) {
151
+ stack += ` (next build will use ${app.build_stack.name})`;
152
+ }
153
+ return stack;
154
+ })(info.app);
155
+ hux.styledHeader(color.app(info.app.name));
156
+ hux.styledObject(data);
157
+ if (extended) {
158
+ ux.stdout('\n\n--- Extended Information ---\n\n');
159
+ if (info.app.extended) {
160
+ ux.stdout(util.inspect(info.app.extended));
161
161
  }
162
162
  }
163
163
  }
@@ -1,6 +1,5 @@
1
1
  import { Command } from '@heroku-cli/command';
2
2
  import * as Heroku from '@heroku-cli/schema';
3
- import inquirer from 'inquirer';
4
3
  export default class AppsTransfer extends Command {
5
4
  static args: {
6
5
  recipient: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
@@ -15,8 +14,6 @@ export default class AppsTransfer extends Command {
15
14
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
15
  };
17
16
  static topic: string;
18
- getAppsToTransfer(apps: Heroku.App[]): Promise<any> & {
19
- ui: inquirer.ui.Prompt<any>;
20
- };
17
+ getAppsToTransfer(apps: Heroku.App[], inquirer: any): any;
21
18
  run(): Promise<void>;
22
19
  }