heroku 11.2.0-beta.0 → 11.2.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.
package/CHANGELOG.md CHANGED
@@ -4,17 +4,20 @@ 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.0](https://github.com/heroku/cli/compare/v11.1.1...v11.2.0-beta.0) (2026-04-06)
7
+ ## [11.2.0](https://github.com/heroku/cli/compare/v11.1.1...v11.2.0) (2026-04-08)
8
8
 
9
9
 
10
10
  ### Features
11
11
 
12
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))
13
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))
14
15
 
15
16
 
16
17
  ### Bug Fixes
17
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))
18
21
  * procfile comment parsing ([#3641](https://github.com/heroku/cli/issues/3641)) ([aae3751](https://github.com/heroku/cli/commit/aae37512b54bdcf2b832429254f0d2c0fe1f42d3))
19
22
 
20
23
 
@@ -26,6 +29,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
26
29
  ### Tests
27
30
 
28
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
+ * use Sinon stubs; parallel slug checksums ([#3650](https://github.com/heroku/cli/issues/3650)) ([73e4990](https://github.com/heroku/cli/commit/73e4990222d4a1b1dfec5fc64eae0c25f811c93c))
29
33
 
30
34
  ## [11.1.1](https://github.com/heroku/cli/compare/v11.1.0...v11.1.1) (2026-04-01)
31
35
 
@@ -7,6 +7,7 @@ export default class Addons extends Command {
7
7
  all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
12
13
  static topic: string;
@@ -3,6 +3,7 @@ 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';
7
8
  export default class Addons extends Command {
8
9
  static description = `Lists your add-ons and attachments.
@@ -20,6 +21,7 @@ export default class Addons extends Command {
20
21
  all: flags.boolean({ char: 'A', description: 'show add-ons and attachments for all accessible apps' }),
21
22
  app: flags.app(),
22
23
  json: flags.boolean({ description: 'return add-ons in json format' }),
24
+ 'no-wrap': flags.noWrap(),
23
25
  remote: flags.remote(),
24
26
  };
25
27
  static topic = topic;
@@ -32,14 +34,14 @@ export default class Addons extends Command {
32
34
  if (json)
33
35
  displayJSON(addons);
34
36
  else
35
- displayForApp(app, addons);
37
+ displayForApp(app, addons, flags['no-wrap']);
36
38
  }
37
39
  else {
38
40
  const addons = await addonGetter(this.heroku);
39
41
  if (json)
40
42
  displayJSON(addons);
41
43
  else
42
- displayAll(addons);
44
+ displayAll(addons, flags['no-wrap']);
43
45
  }
44
46
  }
45
47
  }
@@ -111,7 +113,7 @@ async function addonGetter(api, app) {
111
113
  });
112
114
  return addons;
113
115
  }
114
- function displayAll(addons) {
116
+ function displayAll(addons, noWrap = false) {
115
117
  addons = _.sortBy(addons, 'app.name', 'plan.name', 'addon.name');
116
118
  if (addons.length === 0) {
117
119
  ux.stdout('No add-ons.');
@@ -165,12 +167,10 @@ function displayAll(addons) {
165
167
  return result;
166
168
  },
167
169
  },
168
- }, {
169
- overflow: 'wrap',
170
- });
170
+ }, huxTableNoWrapOptions(noWrap));
171
171
  /* eslint-enable perfectionist/sort-objects */
172
172
  }
173
- function displayForApp(app, addons) {
173
+ function displayForApp(app, addons, noWrap = false) {
174
174
  if (addons.length === 0) {
175
175
  ux.stdout(`No add-ons for app ${app}.`);
176
176
  return;
@@ -184,13 +184,15 @@ function displayForApp(app, addons) {
184
184
  }
185
185
  const addonLine = `${service} (${name})`;
186
186
  const atts = _.sortBy(addon.attachments, isForeignApp, 'app.name', 'name');
187
- // render each attachment under the add-on
188
187
  const attLines = atts.map((attachment, idx) => {
189
188
  const isLast = (idx === addon.attachments.length - 1);
190
189
  return renderAttachment(attachment, app, isLast);
191
190
  });
192
- return [addonLine].concat(attLines)
193
- .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`;
194
196
  }
195
197
  addons = _.sortBy(addons, isForeignApp, 'plan.name', 'name');
196
198
  ux.stdout();
@@ -221,9 +223,7 @@ function displayForApp(app, addons) {
221
223
  State: {
222
224
  get: ({ state }) => formatState(state || ''),
223
225
  },
224
- }, {
225
- overflow: 'wrap',
226
- });
226
+ }, huxTableNoWrapOptions(noWrap));
227
227
  ux.stdout(`The table above shows add-ons and the attachments to the current app (${color.app(app)}) or other apps.\n `);
228
228
  }
229
229
  function displayJSON(addons) {
@@ -7,6 +7,7 @@ export default class DataPgCredentialsIndex extends BaseCommand {
7
7
  static examples: string[];
8
8
  static flags: {
9
9
  app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
12
13
  run(): Promise<void>;
@@ -4,6 +4,7 @@ import { Args, ux } from '@oclif/core';
4
4
  import BaseCommand from '../../../../lib/data/baseCommand.js';
5
5
  import { sortByOwnerAndName } from '../../../../lib/data/credentialUtils.js';
6
6
  import { presentCredentialAttachments } from '../../../../lib/pg/util.js';
7
+ import { huxTableNoWrapOptions } from '../../../../lib/utils/tableUtils.js';
7
8
  export default class DataPgCredentialsIndex extends BaseCommand {
8
9
  static args = {
9
10
  database: Args.string({
@@ -17,6 +18,7 @@ export default class DataPgCredentialsIndex extends BaseCommand {
17
18
  ];
18
19
  static flags = {
19
20
  app: Flags.app({ required: true }),
21
+ 'no-wrap': Flags.noWrap(),
20
22
  remote: Flags.remote(),
21
23
  };
22
24
  async run() {
@@ -55,8 +57,6 @@ export default class DataPgCredentialsIndex extends BaseCommand {
55
57
  State: {
56
58
  get: cred => cred.state,
57
59
  },
58
- }, {
59
- overflow: 'wrap',
60
- });
60
+ }, huxTableNoWrapOptions(flags['no-wrap']));
61
61
  }
62
62
  }
@@ -10,6 +10,7 @@ export default class DomainsIndex extends Command {
10
10
  extended: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
11
  filter: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
14
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
15
  sort: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
16
  };
@@ -5,6 +5,7 @@ import { orderBy } from 'natural-orderby';
5
5
  import Uri from 'urijs';
6
6
  import parseKeyValue from '../../lib/utils/keyValueParser.js';
7
7
  import { paginateRequest } from '../../lib/utils/paginator.js';
8
+ import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
8
9
  export default class DomainsIndex extends Command {
9
10
  static description = 'list domains for an app';
10
11
  static examples = [`${color.command('heroku domains')}
@@ -25,6 +26,7 @@ www.example.com CNAME www.example.herokudns.com`];
25
26
  extended: flags.boolean({ char: 'x', description: 'show extra columns' }),
26
27
  filter: flags.string({ description: 'filter property by partial string matching, ex: name=foo' }),
27
28
  json: flags.boolean({ char: 'j', description: 'output in json format' }),
29
+ 'no-wrap': flags.noWrap(),
28
30
  remote: flags.remote(),
29
31
  sort: flags.string({ description: 'sort by property' }),
30
32
  };
@@ -182,7 +184,7 @@ www.example.com CNAME www.example.herokudns.com`];
182
184
  }
183
185
  else {
184
186
  hux.table(customDomains, tableConfig, {
185
- overflow: 'wrap',
187
+ ...huxTableNoWrapOptions(flags['no-wrap']),
186
188
  sort: flags.sort ? { [sortProperty]: 'asc' } : undefined,
187
189
  });
188
190
  }
@@ -7,6 +7,7 @@ export default class Credentials extends Command {
7
7
  static description: string;
8
8
  static flags: {
9
9
  app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  };
12
13
  static topic: string;
@@ -2,6 +2,7 @@ import { hux, utils } from '@heroku/heroku-cli-util';
2
2
  import { Command, flags } from '@heroku-cli/command';
3
3
  import { Args } from '@oclif/core';
4
4
  import { presentCredentialAttachments } from '../../lib/pg/util.js';
5
+ import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
5
6
  import { nls } from '../../nls.js';
6
7
  export default class Credentials extends Command {
7
8
  static args = {
@@ -10,6 +11,7 @@ export default class Credentials extends Command {
10
11
  static description = 'show information on credentials in the database';
11
12
  static flags = {
12
13
  app: flags.app({ required: true }),
14
+ 'no-wrap': flags.noWrap(),
13
15
  remote: flags.remote(),
14
16
  };
15
17
  static topic = 'pg';
@@ -47,9 +49,7 @@ export default class Credentials extends Command {
47
49
  State: {
48
50
  get: cred => cred.state,
49
51
  },
50
- }, {
51
- overflow: 'wrap',
52
- });
52
+ }, huxTableNoWrapOptions(flags['no-wrap']));
53
53
  }
54
54
  sortByDefaultAndName(credentials) {
55
55
  return credentials.sort((a, b) => {
@@ -6,6 +6,7 @@ export default class Index extends Command {
6
6
  app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
7
  extended: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ 'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  };
11
12
  static strict: boolean;
@@ -3,6 +3,7 @@ import { color, hux } from '@heroku/heroku-cli-util';
3
3
  import { ux } from '@oclif/core/ux';
4
4
  import tsheredoc from 'tsheredoc';
5
5
  import { ago } from '../../lib/time.js';
6
+ import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
6
7
  const heredoc = tsheredoc.default;
7
8
  export default class Index extends Command {
8
9
  static description = 'list dynos for an app';
@@ -21,6 +22,7 @@ export default class Index extends Command {
21
22
  app: flags.app({ required: true }),
22
23
  extended: flags.boolean({ char: 'x', hidden: true }),
23
24
  json: flags.boolean({ description: 'display as json' }),
25
+ 'no-wrap': flags.noWrap(),
24
26
  remote: flags.remote(),
25
27
  };
26
28
  static strict = false;
@@ -62,7 +64,7 @@ export default class Index extends Command {
62
64
  if (json)
63
65
  hux.styledJSON(selectedDynos);
64
66
  else if (extended)
65
- printExtended(selectedDynos);
67
+ printExtended(selectedDynos, flags['no-wrap']);
66
68
  else {
67
69
  await printAccountQuota(this.heroku, appInfo, accountInfo);
68
70
  if (selectedDynos.length === 0)
@@ -159,7 +161,7 @@ function printDynos(dynos) {
159
161
  ux.stdout();
160
162
  });
161
163
  }
162
- function printExtended(dynos) {
164
+ function printExtended(dynos, noWrap = false) {
163
165
  const sortedDynos = dynos.sort(byProcessTypeAndNumber);
164
166
  /* eslint-disable perfectionist/sort-objects */
165
167
  hux.table(sortedDynos, {
@@ -177,9 +179,7 @@ function printExtended(dynos) {
177
179
  Command: { get: (dyno) => truncate(dyno.command) },
178
180
  Route: { get: (dyno) => dyno.extended?.route ?? '' },
179
181
  Size: { get: (dyno) => dyno.size },
180
- }, {
181
- overflow: 'wrap',
182
- });
182
+ }, huxTableNoWrapOptions(noWrap));
183
183
  /* eslint-enable perfectionist/sort-objects */
184
184
  }
185
185
  function truncate(s) {
@@ -10,21 +10,29 @@ import { telemetryDebug } from './telemetry-utils.js';
10
10
  const MAX_WORKER_LIFETIME_MS = 10000;
11
11
  /**
12
12
  * Close stderr before exiting to avoid keeping parent process alive
13
- * This is important when stderr is inherited in DEBUG mode
13
+ * This is only necessary when stderr is inherited in DEBUG mode
14
14
  */
15
15
  function exitWorker(code) {
16
- // Use setImmediate to allow any pending stderr writes to flush
17
- // before destroying the stream
18
- setImmediate(() => {
16
+ // Close stderr if it was inherited (DEBUG mode)
17
+ if (process.env.DEBUG) {
19
18
  try {
20
- // Close stderr to release the file descriptor reference to parent
21
- process.stderr.destroy();
19
+ // End stderr gracefully, flushing all pending writes
20
+ // This properly releases the file descriptor reference to parent
21
+ process.stderr.end(() => {
22
+ process.exit(code);
23
+ });
22
24
  }
23
- catch {
24
- // Ignore errors during cleanup
25
+ finally {
26
+ // Fallback: ensure we exit even if end() fails or callback never fires
27
+ // Use setImmediate to give the end() callback a chance to run first
28
+ setImmediate(() => {
29
+ process.exit(code);
30
+ });
25
31
  }
32
+ }
33
+ else {
26
34
  process.exit(code);
27
- });
35
+ }
28
36
  }
29
37
  setTimeout(() => {
30
38
  telemetryDebug('Worker timeout reached after %dms, force exiting', MAX_WORKER_LIFETIME_MS);
@@ -6,11 +6,11 @@ export const COLORS = [
6
6
  s => color.cyan(s),
7
7
  s => color.magenta(s),
8
8
  s => color.blue(s),
9
- s => color.teal(s),
10
- s => color.pink(s),
11
- s => color.gold(s),
12
- s => color.purple(s),
13
- s => color.orange(s),
9
+ s => color.info(s),
10
+ s => color.name(s),
11
+ s => color.addon(s),
12
+ s => color.pipeline(s),
13
+ s => color.warning(s),
14
14
  ];
15
15
  const assignedColors = {};
16
16
  let isInitialized = false;
@@ -1,3 +1,7 @@
1
+ export declare function huxTableNoWrapOptions(noWrap: boolean): {
2
+ maxWidth: 'none' | undefined;
3
+ overflow: 'truncate' | 'wrap';
4
+ };
1
5
  export declare const constructSortFilterTableOptions: (flags: Record<string, string>, tableColumns: Record<string, any>) => Record<string, any>;
2
6
  export declare const outputCSV: (tableData: Record<string, any>[], tableColumns: Record<string, any>) => void;
3
7
  export declare const constructTableColumns: (allTableColumns: Record<string, any>, baseColumnNames: string[], extended: boolean, columns?: string) => Record<string, any>;
@@ -1,5 +1,11 @@
1
1
  import { ux } from '@oclif/core/ux';
2
2
  import parseKeyValue from './keyValueParser.js';
3
+ export function huxTableNoWrapOptions(noWrap) {
4
+ return {
5
+ maxWidth: noWrap ? 'none' : undefined,
6
+ overflow: noWrap ? 'truncate' : 'wrap',
7
+ };
8
+ }
3
9
  export const constructSortFilterTableOptions = (flags, tableColumns) => {
4
10
  const { filter, sort } = flags;
5
11
  const columnNames = new Set(Object.keys(tableColumns).map(key => key.toLowerCase()));