@heroku/heroku-cli-util 10.2.0 → 10.3.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/README.md +27 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/addons/addon-resolver.js +0 -2
- package/dist/utils/pg/config-vars.js +3 -3
- package/dist/utils/pg/databases.js +1 -1
- package/dist/ux/colors.d.ts +134 -0
- package/dist/ux/colors.js +87 -0
- package/dist/ux/styled-header.js +2 -2
- package/dist/ux/styled-object.js +2 -2
- package/dist/ux/table.js +2 -1
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A set of helpful CLI utilities for Heroku and oclif-based Node.js CLIs. This
|
|
4
4
|
package provides convenient wrappers and helpers for user interaction, output
|
|
5
|
-
formatting, and
|
|
5
|
+
formatting, and color styling.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **User prompts and confirmations** (yes/no, input)
|
|
10
10
|
- **Styled output** (headers, JSON, objects, tables)
|
|
11
11
|
- **Wait indicators** for async operations
|
|
12
|
-
- **
|
|
12
|
+
- **Colors** for consistent, semantic CLI styling
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -59,25 +59,35 @@ const name = await hux.prompt('What is your name?');
|
|
|
59
59
|
const proceed = await hux.confirm('Continue?');
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
###
|
|
62
|
+
### Colors
|
|
63
63
|
|
|
64
64
|
```js
|
|
65
|
-
import {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
import { color } from '@heroku/heroku-cli-util';
|
|
66
|
+
|
|
67
|
+
// App-related colors
|
|
68
|
+
console.log(color.app('my-awesome-app')); // Purple, bold with app icon
|
|
69
|
+
console.log(color.pipeline('staging')); // Purple
|
|
70
|
+
console.log(color.space('production')); // Blue, bold with space icon
|
|
71
|
+
console.log(color.addon('heroku-postgresql')); // Yellow, bold
|
|
72
|
+
console.log(color.datastore('postgresql-123')); // Yellow, bold with datastore icon
|
|
73
|
+
|
|
74
|
+
// Status colors
|
|
75
|
+
console.log(color.success('Deploy complete')); // Green
|
|
76
|
+
console.log(color.failure('Build failed')); // Red
|
|
77
|
+
console.log(color.warning('Deprecated feature')); // Orange
|
|
78
|
+
|
|
79
|
+
// User/Team colors
|
|
80
|
+
console.log(color.team('my-team')); // Cyan, bold
|
|
81
|
+
console.log(color.user('user@example.com')); // Cyan
|
|
82
|
+
|
|
83
|
+
// General purpose
|
|
84
|
+
console.log(color.label('Name')); // Bold
|
|
85
|
+
console.log(color.info('Help text')); // Teal
|
|
86
|
+
console.log(color.command('heroku apps:list')); // Command with prompt styling
|
|
79
87
|
```
|
|
80
88
|
|
|
89
|
+
See the [COLORS.md](docs/COLORS.md) documentation for the complete color palette and usage guide.
|
|
90
|
+
|
|
81
91
|
### Types
|
|
82
92
|
|
|
83
93
|
#### Error Classes
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -10,8 +10,6 @@ export default class AddonResolver {
|
|
|
10
10
|
}
|
|
11
11
|
async resolve(addon, app, addonService) {
|
|
12
12
|
const [appPart, addonPart] = addon.match(/^(.+)::(.+)$/)?.slice(1) ?? [app, addon];
|
|
13
|
-
console.log('appPart', appPart);
|
|
14
|
-
console.log('addonPart', addonPart);
|
|
15
13
|
const { body: addons } = await this.heroku.post('/actions/addons/resolve', {
|
|
16
14
|
body: {
|
|
17
15
|
addon: addonPart,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as color from '../../ux/colors.js';
|
|
2
2
|
/**
|
|
3
3
|
* Cache of app config vars.
|
|
4
4
|
*/
|
|
@@ -49,8 +49,8 @@ export function getConfigVarNameFromAttachment(attachment, config = {}) {
|
|
|
49
49
|
.filter(cvn => config[cvn]?.startsWith('postgres://'));
|
|
50
50
|
if (connStringConfigVarNames.length === 0) {
|
|
51
51
|
throw new Error(`No config vars found for ${attachment.name}; perhaps they were removed as a side effect of`
|
|
52
|
-
+ ` ${color.
|
|
53
|
-
+ `then ${color.
|
|
52
|
+
+ ` ${color.command('heroku rollback')}? Use ${color.command('heroku addons:attach')} to create a new attachment and `
|
|
53
|
+
+ `then ${color.command('heroku addons:detach')} to remove the current attachment.`);
|
|
54
54
|
}
|
|
55
55
|
// Generate the default config var name and return it if it contains a database URL connection string.
|
|
56
56
|
const configVarName = `${attachment.name}_URL`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { color } from '@heroku-cli/color';
|
|
2
1
|
import { HerokuAPIError } from '@heroku-cli/command/lib/api-client.js';
|
|
3
2
|
import debug from 'debug';
|
|
4
3
|
import { AmbiguousError } from '../../errors/ambiguous.js';
|
|
4
|
+
import * as color from '../../ux/colors.js';
|
|
5
5
|
import AddonAttachmentResolver from '../addons/attachment-resolver.js';
|
|
6
6
|
import { getAddonService, isLegacyDatabase } from '../addons/helpers.js';
|
|
7
7
|
import { bastionKeyPlan, fetchBastionConfig, getBastionConfig } from './bastion.js';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color constants for Heroku CLI output
|
|
3
|
+
* Each hex value corresponds to a specific color in the design system
|
|
4
|
+
*/
|
|
5
|
+
declare const COLORS: {
|
|
6
|
+
readonly BLUE: "#62CBF4";
|
|
7
|
+
readonly CODE_BG: "#3A3A3A";
|
|
8
|
+
readonly CODE_FG: "#FFFFFF";
|
|
9
|
+
readonly CYAN: "#50D3D5";
|
|
10
|
+
readonly GRAY: "#B6B6B6";
|
|
11
|
+
readonly GREEN: "#00D300";
|
|
12
|
+
readonly MAGENTA: "#FF8DD3";
|
|
13
|
+
readonly ORANGE: "#F29D00";
|
|
14
|
+
readonly PURPLE: "#ACADFF";
|
|
15
|
+
readonly RED: "#FF8787";
|
|
16
|
+
readonly TEAL: "#00D4AA";
|
|
17
|
+
readonly YELLOW: "#BFBD25";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Color definitions for Heroku CLI output
|
|
21
|
+
* Each color has a specific purpose and hex value as defined in the design system
|
|
22
|
+
*/
|
|
23
|
+
export declare const app: (text: string) => string;
|
|
24
|
+
export declare const pipeline: (text: string) => string;
|
|
25
|
+
export declare const space: (text: string) => string;
|
|
26
|
+
export declare const datastore: (text: string) => string;
|
|
27
|
+
export declare const addon: (text: string) => string;
|
|
28
|
+
export declare const attachment: (text: string) => string;
|
|
29
|
+
export declare const name: (text: string) => string;
|
|
30
|
+
export declare const success: (text: string) => string;
|
|
31
|
+
export declare const failure: (text: string) => string;
|
|
32
|
+
export declare const warning: (text: string) => string;
|
|
33
|
+
export declare const team: (text: string) => string;
|
|
34
|
+
export declare const user: (text: string) => string;
|
|
35
|
+
export declare const label: (text: string) => string;
|
|
36
|
+
export declare const info: (text: string) => string;
|
|
37
|
+
export declare const inactive: (text: string) => string;
|
|
38
|
+
export declare const command: (text: string) => string;
|
|
39
|
+
export declare const code: (text: string) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Color palette for reference
|
|
42
|
+
*/
|
|
43
|
+
export declare const colorPalette: {
|
|
44
|
+
readonly addon: {
|
|
45
|
+
readonly hex: "#BFBD25";
|
|
46
|
+
readonly name: "yellow";
|
|
47
|
+
readonly style: "bold";
|
|
48
|
+
};
|
|
49
|
+
readonly app: {
|
|
50
|
+
readonly hex: "#ACADFF";
|
|
51
|
+
readonly name: "purple";
|
|
52
|
+
readonly style: "bold";
|
|
53
|
+
};
|
|
54
|
+
readonly attachment: {
|
|
55
|
+
readonly hex: "#BFBD25";
|
|
56
|
+
readonly name: "yellow";
|
|
57
|
+
readonly style: "normal";
|
|
58
|
+
};
|
|
59
|
+
readonly command: {
|
|
60
|
+
readonly hex: "#FFFFFF";
|
|
61
|
+
readonly name: "white on dark gray";
|
|
62
|
+
readonly style: "bold";
|
|
63
|
+
};
|
|
64
|
+
readonly datastore: {
|
|
65
|
+
readonly hex: "#BFBD25";
|
|
66
|
+
readonly name: "yellow";
|
|
67
|
+
readonly style: "bold";
|
|
68
|
+
};
|
|
69
|
+
readonly failure: {
|
|
70
|
+
readonly hex: "#FF8787";
|
|
71
|
+
readonly name: "red";
|
|
72
|
+
readonly style: "normal";
|
|
73
|
+
};
|
|
74
|
+
readonly inactive: {
|
|
75
|
+
readonly hex: "#B6B6B6";
|
|
76
|
+
readonly name: "gray";
|
|
77
|
+
readonly style: "normal";
|
|
78
|
+
};
|
|
79
|
+
readonly info: {
|
|
80
|
+
readonly hex: "#00D4AA";
|
|
81
|
+
readonly name: "teal";
|
|
82
|
+
readonly style: "normal";
|
|
83
|
+
};
|
|
84
|
+
readonly label: {
|
|
85
|
+
readonly hex: "default";
|
|
86
|
+
readonly name: "bright white/black";
|
|
87
|
+
readonly style: "bold";
|
|
88
|
+
};
|
|
89
|
+
readonly name: {
|
|
90
|
+
readonly hex: "#FF8DD3";
|
|
91
|
+
readonly name: "magenta";
|
|
92
|
+
readonly style: "normal";
|
|
93
|
+
};
|
|
94
|
+
readonly pipeline: {
|
|
95
|
+
readonly hex: "#ACADFF";
|
|
96
|
+
readonly name: "purple";
|
|
97
|
+
readonly style: "normal";
|
|
98
|
+
};
|
|
99
|
+
readonly space: {
|
|
100
|
+
readonly hex: "#62CBF4";
|
|
101
|
+
readonly name: "blue";
|
|
102
|
+
readonly style: "bold";
|
|
103
|
+
};
|
|
104
|
+
readonly success: {
|
|
105
|
+
readonly hex: "#00D300";
|
|
106
|
+
readonly name: "green";
|
|
107
|
+
readonly style: "normal";
|
|
108
|
+
};
|
|
109
|
+
readonly team: {
|
|
110
|
+
readonly hex: "#50D3D5";
|
|
111
|
+
readonly name: "cyan";
|
|
112
|
+
readonly style: "bold";
|
|
113
|
+
};
|
|
114
|
+
readonly user: {
|
|
115
|
+
readonly hex: "#50D3D5";
|
|
116
|
+
readonly name: "cyan";
|
|
117
|
+
readonly style: "normal";
|
|
118
|
+
};
|
|
119
|
+
readonly warning: {
|
|
120
|
+
readonly hex: "#F29D00";
|
|
121
|
+
readonly name: "orange";
|
|
122
|
+
readonly style: "normal";
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Export color constants for external use
|
|
127
|
+
*/
|
|
128
|
+
export { COLORS };
|
|
129
|
+
/**
|
|
130
|
+
* Re-export everything from ansis as a passthrough
|
|
131
|
+
* This gives access to all ansis functionality while adding our custom colors
|
|
132
|
+
*/
|
|
133
|
+
export * from 'ansis';
|
|
134
|
+
export { default as ansi } from 'ansis';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable import/no-named-as-default-member */
|
|
2
|
+
import ansi from 'ansis';
|
|
3
|
+
/**
|
|
4
|
+
* Color constants for Heroku CLI output
|
|
5
|
+
* Each hex value corresponds to a specific color in the design system
|
|
6
|
+
*/
|
|
7
|
+
const COLORS = {
|
|
8
|
+
// Blue tones
|
|
9
|
+
BLUE: '#62CBF4',
|
|
10
|
+
// Command tones (dark gray background, white foreground)
|
|
11
|
+
CODE_BG: '#3A3A3A',
|
|
12
|
+
CODE_FG: '#FFFFFF',
|
|
13
|
+
// Cyan tones
|
|
14
|
+
CYAN: '#50D3D5',
|
|
15
|
+
// Gray tones
|
|
16
|
+
GRAY: '#B6B6B6',
|
|
17
|
+
// Green tones
|
|
18
|
+
GREEN: '#00D300',
|
|
19
|
+
// Magenta tones
|
|
20
|
+
MAGENTA: '#FF8DD3',
|
|
21
|
+
// Orange tones
|
|
22
|
+
ORANGE: '#F29D00',
|
|
23
|
+
// Purple tones
|
|
24
|
+
PURPLE: '#ACADFF',
|
|
25
|
+
// Red tones
|
|
26
|
+
RED: '#FF8787',
|
|
27
|
+
// Teal tones
|
|
28
|
+
TEAL: '#00D4AA',
|
|
29
|
+
// Yellow tones
|
|
30
|
+
YELLOW: '#BFBD25',
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Color definitions for Heroku CLI output
|
|
34
|
+
* Each color has a specific purpose and hex value as defined in the design system
|
|
35
|
+
*/
|
|
36
|
+
// Colors for entities on the Heroku platform
|
|
37
|
+
export const app = (text) => ansi.hex(COLORS.PURPLE).bold(`⬢ ${text}`);
|
|
38
|
+
export const pipeline = (text) => ansi.hex(COLORS.PURPLE)(text);
|
|
39
|
+
export const space = (text) => ansi.hex(COLORS.BLUE).bold(`⬡ ${text}`);
|
|
40
|
+
export const datastore = (text) => ansi.hex(COLORS.YELLOW).bold(`☷ ${text}`);
|
|
41
|
+
export const addon = (text) => ansi.hex(COLORS.YELLOW).bold(text);
|
|
42
|
+
export const attachment = (text) => ansi.hex(COLORS.YELLOW)(text);
|
|
43
|
+
export const name = (text) => ansi.hex(COLORS.MAGENTA)(text);
|
|
44
|
+
// Status colors
|
|
45
|
+
export const success = (text) => ansi.hex(COLORS.GREEN)(text);
|
|
46
|
+
export const failure = (text) => ansi.hex(COLORS.RED)(text);
|
|
47
|
+
export const warning = (text) => ansi.hex(COLORS.ORANGE)(text);
|
|
48
|
+
// User/Team colors
|
|
49
|
+
export const team = (text) => ansi.hex(COLORS.CYAN).bold(text);
|
|
50
|
+
export const user = (text) => ansi.hex(COLORS.CYAN)(text);
|
|
51
|
+
// General purpose colors
|
|
52
|
+
export const label = (text) => ansi.bold(text);
|
|
53
|
+
export const info = (text) => ansi.hex(COLORS.TEAL)(text);
|
|
54
|
+
export const inactive = (text) => ansi.hex(COLORS.GRAY)(text);
|
|
55
|
+
export const command = (text) => ansi.bgHex(COLORS.CODE_BG).hex(COLORS.CODE_FG).bold(` $ ${text} `);
|
|
56
|
+
export const code = (text) => ansi.bgHex(COLORS.CODE_BG).hex(COLORS.CODE_FG).bold(`${text} `);
|
|
57
|
+
/**
|
|
58
|
+
* Color palette for reference
|
|
59
|
+
*/
|
|
60
|
+
export const colorPalette = {
|
|
61
|
+
addon: { hex: COLORS.YELLOW, name: 'yellow', style: 'bold' },
|
|
62
|
+
app: { hex: COLORS.PURPLE, name: 'purple', style: 'bold' },
|
|
63
|
+
attachment: { hex: COLORS.YELLOW, name: 'yellow', style: 'normal' },
|
|
64
|
+
command: { hex: COLORS.CODE_FG, name: 'white on dark gray', style: 'bold' },
|
|
65
|
+
datastore: { hex: COLORS.YELLOW, name: 'yellow', style: 'bold' },
|
|
66
|
+
failure: { hex: COLORS.RED, name: 'red', style: 'normal' },
|
|
67
|
+
inactive: { hex: COLORS.GRAY, name: 'gray', style: 'normal' },
|
|
68
|
+
info: { hex: COLORS.TEAL, name: 'teal', style: 'normal' },
|
|
69
|
+
label: { hex: 'default', name: 'bright white/black', style: 'bold' },
|
|
70
|
+
name: { hex: COLORS.MAGENTA, name: 'magenta', style: 'normal' },
|
|
71
|
+
pipeline: { hex: COLORS.PURPLE, name: 'purple', style: 'normal' },
|
|
72
|
+
space: { hex: COLORS.BLUE, name: 'blue', style: 'bold' },
|
|
73
|
+
success: { hex: COLORS.GREEN, name: 'green', style: 'normal' },
|
|
74
|
+
team: { hex: COLORS.CYAN, name: 'cyan', style: 'bold' },
|
|
75
|
+
user: { hex: COLORS.CYAN, name: 'cyan', style: 'normal' },
|
|
76
|
+
warning: { hex: COLORS.ORANGE, name: 'orange', style: 'normal' },
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Export color constants for external use
|
|
80
|
+
*/
|
|
81
|
+
export { COLORS };
|
|
82
|
+
/**
|
|
83
|
+
* Re-export everything from ansis as a passthrough
|
|
84
|
+
* This gives access to all ansis functionality while adding our custom colors
|
|
85
|
+
*/
|
|
86
|
+
export * from 'ansis';
|
|
87
|
+
export { default as ansi } from 'ansis';
|
package/dist/ux/styled-header.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { color } from '@heroku-cli/color';
|
|
2
1
|
import { ux } from '@oclif/core';
|
|
2
|
+
import * as color from './colors.js';
|
|
3
3
|
export function styledHeader(header) {
|
|
4
|
-
return ux.stdout(color.
|
|
4
|
+
return ux.stdout(color.label('=== ') + color.label(header) + '\n');
|
|
5
5
|
}
|
package/dist/ux/styled-object.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { color } from '@heroku-cli/color';
|
|
2
1
|
import { ux } from '@oclif/core';
|
|
3
2
|
import { inspect } from 'node:util';
|
|
3
|
+
import * as color from './colors.js';
|
|
4
4
|
function prettyPrint(obj) {
|
|
5
5
|
if (!obj)
|
|
6
6
|
return inspect(obj);
|
|
@@ -29,7 +29,7 @@ export function styledObject(obj, keys) {
|
|
|
29
29
|
const output = [];
|
|
30
30
|
const keyLengths = Object.keys(obj).map(key => key.toString().length);
|
|
31
31
|
const maxKeyLength = Math.max(...keyLengths) + 2;
|
|
32
|
-
const logKeyValue = (key, value) => `${color.
|
|
32
|
+
const logKeyValue = (key, value) => `${color.label(key)}:` + ' '.repeat(maxKeyLength - key.length - 1) + prettyPrint(value);
|
|
33
33
|
for (const [key, value] of Object.entries(obj)) {
|
|
34
34
|
if (keys && !keys.includes(key))
|
|
35
35
|
continue;
|
package/dist/ux/table.js
CHANGED
|
@@ -13,7 +13,8 @@ export function table(data, columns, options) {
|
|
|
13
13
|
borderColor: 'whiteBright',
|
|
14
14
|
borderStyle: 'headers-only-with-underline',
|
|
15
15
|
headerOptions: {
|
|
16
|
-
|
|
16
|
+
bold: true,
|
|
17
|
+
color: 'white', // or 'reset' to use default terminal color
|
|
17
18
|
},
|
|
18
19
|
}),
|
|
19
20
|
...tableOptions,
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@heroku/heroku-cli-util",
|
|
4
|
-
"version": "10.
|
|
4
|
+
"version": "10.3.0",
|
|
5
5
|
"description": "Set of helpful CLI utilities",
|
|
6
6
|
"author": "Heroku",
|
|
7
7
|
"license": "ISC",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/heroku/heroku-cli-util.git"
|
|
11
|
+
},
|
|
8
12
|
"main": "dist/index.js",
|
|
9
13
|
"types": "dist/index.d.ts",
|
|
10
14
|
"files": [
|
|
@@ -36,7 +40,6 @@
|
|
|
36
40
|
"nock": "^13.2.9",
|
|
37
41
|
"sinon": "^18.0.1",
|
|
38
42
|
"sinon-chai": "^3.7.0",
|
|
39
|
-
"strip-ansi": "^6",
|
|
40
43
|
"tmp": "^0.2.5",
|
|
41
44
|
"ts-node": "^10.9.2",
|
|
42
45
|
"tsconfig-paths": "^4.2.0",
|
|
@@ -44,11 +47,11 @@
|
|
|
44
47
|
"typescript": "^5.4.0"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"@heroku-cli/color": "^2.0.4",
|
|
48
50
|
"@heroku-cli/command": "^12.0.0",
|
|
49
51
|
"@heroku/http-call": "^5.5.0",
|
|
50
52
|
"@oclif/core": "^4.3.0",
|
|
51
53
|
"@oclif/table": "0.4.14",
|
|
54
|
+
"ansis": "^4.1.0",
|
|
52
55
|
"debug": "^4.4.0",
|
|
53
56
|
"inquirer": "^12.6.1",
|
|
54
57
|
"tunnel-ssh": "5.2.0"
|
|
@@ -61,8 +64,7 @@
|
|
|
61
64
|
"clean": "rm -rf dist",
|
|
62
65
|
"example": "sh examples/run.sh",
|
|
63
66
|
"lint": "eslint . --ext .ts --config .eslintrc.cjs",
|
|
64
|
-
"prepare": "npm run build",
|
|
65
67
|
"test": "c8 mocha --forbid-only \"test/**/*.test.ts\"",
|
|
66
68
|
"test:local": "c8 mocha \"${npm_config_file:-test/**/*.test.+(ts|tsx)}\""
|
|
67
69
|
}
|
|
68
|
-
}
|
|
70
|
+
}
|