@heroku/heroku-cli-util 10.4.0 → 10.6.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.
@@ -20,6 +20,13 @@ declare const COLORS: {
20
20
  readonly TEAL: 43;
21
21
  readonly YELLOW: 185;
22
22
  };
23
+ /** Theme name: 'heroku' (default) or 'simple' (ANSI 8 only). Set via HEROKU_THEME env. */
24
+ export type ThemeName = 'heroku' | 'simple';
25
+ /**
26
+ * Resolves active theme from HEROKU_THEME; defaults to 'heroku'.
27
+ * @returns The active theme name.
28
+ */
29
+ export declare function getTheme(): ThemeName;
23
30
  export declare const app: (text: string) => string;
24
31
  export declare const pipeline: (text: string) => string;
25
32
  export declare const space: (text: string) => string;
package/dist/ux/colors.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable import/no-named-as-default-member */
2
- import ansi from 'ansis';
2
+ import ansis from 'ansis';
3
3
  /**
4
4
  * Color constants for Heroku CLI output
5
5
  * Most colors use ANSI256 codes for better compatibility with terminals that don't support TrueColor.
@@ -42,37 +42,88 @@ const COLORS = {
42
42
  * Colors use ANSI256 codes where possible, with hex values for magenta, red, and cyan
43
43
  */
44
44
  // Helper function to apply color based on type (number = ANSI256, string = hex)
45
- const colorize = (color) => typeof color === 'number' ? ansi.fg(color) : ansi.hex(color);
46
- const bgColorize = (color) => typeof color === 'number' ? ansi.bg(color) : ansi.bgHex(color);
45
+ const colorize = (color) => typeof color === 'number' ? ansis.fg(color) : ansis.hex(color);
46
+ const bgColorize = (color) => typeof color === 'number' ? ansis.bg(color) : ansis.bgHex(color);
47
47
  // Check if terminal supports at least ANSI256 (level >= 2)
48
48
  // Level values: 0=no color, 1=ANSI16, 2=ANSI256, 3=TrueColor
49
- const supportsAnsi256 = ansi.level >= 2;
49
+ const supportsAnsi256 = ansis.level >= 2;
50
50
  // Helper function for purple color that falls back to ANSI16 magenta when only ANSI16 is supported
51
- const purpleColorize = () => supportsAnsi256 ? ansi.fg(COLORS.PURPLE) : ansi.magenta;
51
+ const purpleColorize = () => supportsAnsi256 ? ansis.fg(COLORS.PURPLE) : ansis.magenta;
52
+ const THEME_ENV = 'HEROKU_THEME';
53
+ /**
54
+ * Resolves active theme from HEROKU_THEME; defaults to 'heroku'.
55
+ * @returns The active theme name.
56
+ */
57
+ export function getTheme() {
58
+ const v = process.env[THEME_ENV]?.toLowerCase().trim();
59
+ if (v === 'simple')
60
+ return 'simple';
61
+ return 'heroku';
62
+ }
63
+ const herokuTheme = {
64
+ addon: (text) => colorize(COLORS.YELLOW)(text),
65
+ app: (text) => purpleColorize()(`${supportsAnsi256 ? '⬢ ' : ''}${text}`),
66
+ attachment: (text) => colorize(COLORS.GOLD)(text),
67
+ code: (text) => bgColorize(COLORS.CODE_BG).fg(COLORS.CODE_FG).bold(`${text}`),
68
+ command: (text) => bgColorize(COLORS.CODE_BG).fg(COLORS.CODE_FG).bold(` $ ${text} `),
69
+ datastore: (text) => colorize(COLORS.YELLOW)(`${supportsAnsi256 ? '⛁ ' : ''}${text}`),
70
+ failure: (text) => colorize(COLORS.RED)(text),
71
+ inactive: (text) => colorize(COLORS.GRAY)(text),
72
+ info: (text) => colorize(COLORS.TEAL)(text),
73
+ label: (text) => ansis.bold(text),
74
+ name: (text) => colorize(COLORS.PINK)(text),
75
+ pipeline: (text) => colorize(COLORS.MAGENTA)(text),
76
+ space: (text) => colorize(COLORS.BLUE)(`${supportsAnsi256 ? '⬡ ' : ''}${text}`),
77
+ success: (text) => colorize(COLORS.GREEN)(text),
78
+ team: (text) => colorize(COLORS.CYAN_LIGHT)(text),
79
+ user: (text) => colorize(COLORS.CYAN)(text),
80
+ warning: (text) => colorize(COLORS.ORANGE)(text),
81
+ };
82
+ /** Simple theme: ANSI 8 colors only, no symbols. */
83
+ const simpleTheme = {
84
+ addon: (text) => ansis.yellow(text),
85
+ app: (text) => ansis.magenta(text),
86
+ attachment: (text) => ansis.yellow(text),
87
+ code: (text) => ansis.bold(text),
88
+ command: (text) => ansis.bold(` $ ${text} `),
89
+ datastore: (text) => ansis.yellow(text),
90
+ failure: (text) => ansis.red(text),
91
+ inactive: (text) => ansis.gray(text),
92
+ info: (text) => ansis.cyan(text),
93
+ label: (text) => ansis.bold(text),
94
+ name: (text) => ansis.magenta(text),
95
+ pipeline: (text) => ansis.magenta(text),
96
+ space: (text) => ansis.cyan(text),
97
+ success: (text) => ansis.green(text),
98
+ team: (text) => ansis.cyan(text),
99
+ user: (text) => ansis.cyan(text),
100
+ warning: (text) => ansis.yellow(text),
101
+ };
102
+ const activeTheme = () => (getTheme() === 'simple' ? simpleTheme : herokuTheme);
52
103
  // Colors for entities on the Heroku platform
53
- export const app = (text) => purpleColorize()(`${supportsAnsi256 ? '⬢ ' : ''}${text}`);
54
- export const pipeline = (text) => colorize(COLORS.MAGENTA)(text);
55
- export const space = (text) => colorize(COLORS.BLUE)(`${supportsAnsi256 ? '⬡ ' : ''}${text}`);
56
- export const datastore = (text) => colorize(COLORS.YELLOW)(`${supportsAnsi256 ? '⛁ ' : ''}${text}`);
57
- export const addon = (text) => colorize(COLORS.YELLOW)(text);
58
- export const attachment = (text) => colorize(COLORS.GOLD)(text);
59
- export const name = (text) => colorize(COLORS.PINK)(text);
104
+ export const app = (text) => activeTheme().app(text);
105
+ export const pipeline = (text) => activeTheme().pipeline(text);
106
+ export const space = (text) => activeTheme().space(text);
107
+ export const datastore = (text) => activeTheme().datastore(text);
108
+ export const addon = (text) => activeTheme().addon(text);
109
+ export const attachment = (text) => activeTheme().attachment(text);
110
+ export const name = (text) => activeTheme().name(text);
60
111
  // Status colors
61
- export const success = (text) => colorize(COLORS.GREEN)(text);
112
+ export const success = (text) => activeTheme().success(text);
62
113
  export const enabled = success;
63
- export const failure = (text) => colorize(COLORS.RED)(text);
114
+ export const failure = (text) => activeTheme().failure(text);
64
115
  export const error = failure;
65
- export const warning = (text) => colorize(COLORS.ORANGE)(text);
116
+ export const warning = (text) => activeTheme().warning(text);
66
117
  // User/Team colors
67
- export const team = (text) => colorize(COLORS.CYAN_LIGHT)(text);
68
- export const user = (text) => colorize(COLORS.CYAN)(text);
118
+ export const team = (text) => activeTheme().team(text);
119
+ export const user = (text) => activeTheme().user(text);
69
120
  // General purpose colors
70
- export const label = (text) => ansi.bold(text);
71
- export const info = (text) => colorize(COLORS.TEAL)(text);
72
- export const inactive = (text) => colorize(COLORS.GRAY)(text);
121
+ export const label = (text) => activeTheme().label(text);
122
+ export const info = (text) => activeTheme().info(text);
123
+ export const inactive = (text) => activeTheme().inactive(text);
73
124
  export const disabled = inactive;
74
- export const command = (text) => bgColorize(COLORS.CODE_BG).fg(COLORS.CODE_FG).bold(` $ ${text} `);
75
- export const code = (text) => bgColorize(COLORS.CODE_BG).fg(COLORS.CODE_FG).bold(`${text}`);
125
+ export const command = (text) => activeTheme().command(text);
126
+ export const code = (text) => activeTheme().code(text);
76
127
  export const snippet = code;
77
128
  /**
78
129
  * Color palette for reference
@@ -27,11 +27,18 @@ export function styledObject(obj, keys) {
27
27
  if (typeof obj === 'boolean')
28
28
  return obj.toString();
29
29
  const output = [];
30
- const keyLengths = Object.keys(obj).map(key => key.toString().length);
31
- const maxKeyLength = Math.max(...keyLengths) + 2;
30
+ const keysToIterate = keys || Object.keys(obj);
31
+ // Only calculate max key length for keys that will actually be displayed
32
+ const displayableKeys = keysToIterate.filter(key => {
33
+ const value = obj[key];
34
+ return value !== null && value !== undefined && (!Array.isArray(value) || value.length > 0);
35
+ });
36
+ const keyLengths = displayableKeys.map(key => key.toString().length);
37
+ const maxKeyLength = keyLengths.length > 0 ? Math.max(...keyLengths) + 2 : 0;
32
38
  const logKeyValue = (key, value) => `${color.label(key)}:` + ' '.repeat(maxKeyLength - key.length - 1) + prettyPrint(value);
33
- for (const [key, value] of Object.entries(obj)) {
34
- if (keys && !keys.includes(key))
39
+ for (const key of keysToIterate) {
40
+ const value = obj[key];
41
+ if (value === null || value === undefined)
35
42
  continue;
36
43
  if (Array.isArray(value)) {
37
44
  if (value.length > 0) {
@@ -41,7 +48,7 @@ export function styledObject(obj, keys) {
41
48
  }
42
49
  }
43
50
  }
44
- else if (value !== null && value !== undefined) {
51
+ else {
45
52
  output.push(logKeyValue(key, value));
46
53
  }
47
54
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@heroku/heroku-cli-util",
4
- "version": "10.4.0",
4
+ "version": "10.6.0",
5
5
  "description": "Set of helpful CLI utilities",
6
6
  "author": "Heroku",
7
7
  "license": "ISC",
@@ -49,7 +49,7 @@
49
49
  "@heroku-cli/command": "^12.0.0",
50
50
  "@heroku/http-call": "^5.5.0",
51
51
  "@oclif/core": "^4.3.0",
52
- "@oclif/table": "0.4.14",
52
+ "@oclif/table": "0.5.2",
53
53
  "ansis": "^4.1.0",
54
54
  "debug": "^4.4.0",
55
55
  "inquirer": "^12.6.1",