@heroku/heroku-cli-util 10.8.2 → 10.9.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/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/parse-key-value.d.ts +4 -0
- package/dist/utils/parse-key-value.js +6 -0
- package/dist/ux/table.d.ts +8 -1
- package/dist/ux/table.js +142 -21
- package/package.json +3 -2
package/dist/utils/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { AmbiguousError } from '../errors/ambiguous.js';
|
|
|
2
2
|
export { NotFound } from '../errors/not-found.js';
|
|
3
3
|
export { default as AddonResolver } from './addons/addon-resolver.js';
|
|
4
4
|
export { getAddonService, isAdvancedDatabase, isAdvancedPrivateDatabase, isEssentialDatabase, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, } from './addons/helpers.js';
|
|
5
|
+
export { default as parseKeyValue } from './parse-key-value.js';
|
|
5
6
|
export { getPsqlConfigs, sshTunnel } from './pg/bastion.js';
|
|
6
7
|
export { getConfigVarNameFromAttachment } from './pg/config-vars.js';
|
|
7
8
|
export { default as DatabaseResolver } from './pg/databases.js';
|
package/dist/utils/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { AmbiguousError } from '../errors/ambiguous.js';
|
|
|
2
2
|
export { NotFound } from '../errors/not-found.js';
|
|
3
3
|
export { default as AddonResolver } from './addons/addon-resolver.js';
|
|
4
4
|
export { getAddonService, isAdvancedDatabase, isAdvancedPrivateDatabase, isEssentialDatabase, isLegacyDatabase, isLegacyEssentialDatabase, isPostgresAddon, } from './addons/helpers.js';
|
|
5
|
+
export { default as parseKeyValue } from './parse-key-value.js';
|
|
5
6
|
export { getPsqlConfigs, sshTunnel } from './pg/bastion.js';
|
|
6
7
|
export { getConfigVarNameFromAttachment } from './pg/config-vars.js';
|
|
7
8
|
export { default as DatabaseResolver } from './pg/databases.js';
|
package/dist/ux/table.d.ts
CHANGED
|
@@ -8,7 +8,14 @@ type Column<T extends Record<string, unknown>> = {
|
|
|
8
8
|
type Columns<T extends Record<string, unknown>> = {
|
|
9
9
|
[key: string]: Partial<Column<T>>;
|
|
10
10
|
};
|
|
11
|
-
|
|
11
|
+
type TableFlags = {
|
|
12
|
+
columns?: string;
|
|
13
|
+
csv?: boolean;
|
|
14
|
+
extended?: boolean;
|
|
15
|
+
filter?: string;
|
|
16
|
+
sort?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function table<T extends Record<string, unknown>>(data: T[], columns: Columns<T>, options?: Omit<TableOptions<T>, 'columns' | 'data' | 'filter' | 'sort'> & TableFlags & {
|
|
12
19
|
printLine?(s: unknown): void;
|
|
13
20
|
}): void;
|
|
14
21
|
export {};
|
package/dist/ux/table.js
CHANGED
|
@@ -1,26 +1,147 @@
|
|
|
1
|
+
import { ux } from '@oclif/core/ux';
|
|
1
2
|
import { printTable } from '@oclif/table';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { orderBy } from 'natural-orderby';
|
|
4
|
+
import parseKeyValue from '../utils/parse-key-value.js';
|
|
5
|
+
/**
|
|
6
|
+
* Finds a column by id (header name or property key), case-insensitively.
|
|
7
|
+
* Returns undefined if no match found.
|
|
8
|
+
*/
|
|
9
|
+
function getColumnById(id, columns) {
|
|
10
|
+
const lowerCaseId = id.toLowerCase();
|
|
11
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
12
|
+
if (col.header?.toLowerCase() === lowerCaseId || key.toLowerCase() === lowerCaseId) {
|
|
13
|
+
return [key, col];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Selects columns based on --columns or --extended flags.
|
|
20
|
+
* --columns takes precedence over --extended.
|
|
21
|
+
*/
|
|
22
|
+
function selectColumns(columns, flags) {
|
|
23
|
+
if (flags.columns) {
|
|
24
|
+
const columnIds = flags.columns.split(',').filter(id => id.trim());
|
|
25
|
+
const uniqueColumnIds = [...new Set(columnIds)];
|
|
26
|
+
const selectedColumns = {};
|
|
27
|
+
for (const id of uniqueColumnIds) {
|
|
28
|
+
const column = getColumnById(id, columns);
|
|
29
|
+
if (!column) {
|
|
30
|
+
throw new Error('Columns flag has an invalid value.');
|
|
31
|
+
}
|
|
32
|
+
const [key, col] = column;
|
|
33
|
+
selectedColumns[key] = col;
|
|
34
|
+
}
|
|
35
|
+
return selectedColumns;
|
|
36
|
+
}
|
|
37
|
+
if (!flags.extended) {
|
|
38
|
+
const baseColumns = {};
|
|
39
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
40
|
+
if (!col.extended) {
|
|
41
|
+
baseColumns[key] = col;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return baseColumns;
|
|
45
|
+
}
|
|
46
|
+
return columns;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds a filter predicate function for substring matching.
|
|
50
|
+
*/
|
|
51
|
+
function getFilterPredicate(columns, filter) {
|
|
52
|
+
const { key: columnId, value: searchValue } = parseKeyValue(filter);
|
|
53
|
+
if (!searchValue) {
|
|
54
|
+
throw new Error('Filter flag has an invalid value.');
|
|
55
|
+
}
|
|
56
|
+
const column = getColumnById(columnId, columns);
|
|
57
|
+
if (!column) {
|
|
58
|
+
throw new Error('Filter flag has an invalid value.');
|
|
59
|
+
}
|
|
60
|
+
const [key] = column;
|
|
61
|
+
return (row) => {
|
|
62
|
+
const cellValue = String(row[key] ?? '');
|
|
63
|
+
return cellValue.includes(searchValue);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Gets the identifiers for the sort properties.
|
|
68
|
+
*/
|
|
69
|
+
function getSortIds(columns, sort) {
|
|
70
|
+
const sortIds = sort
|
|
71
|
+
.split(',')
|
|
72
|
+
.filter(id => id.trim())
|
|
73
|
+
.map(id => {
|
|
74
|
+
const column = getColumnById(id, columns);
|
|
75
|
+
if (!column) {
|
|
76
|
+
throw new Error('Sort flag has an invalid value.');
|
|
77
|
+
}
|
|
78
|
+
const [key] = column;
|
|
6
79
|
return key;
|
|
7
80
|
});
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
81
|
+
if (sortIds.length === 0) {
|
|
82
|
+
throw new Error('Sort flag has an invalid value.');
|
|
83
|
+
}
|
|
84
|
+
return sortIds;
|
|
85
|
+
}
|
|
86
|
+
function escapeCSV(value) {
|
|
87
|
+
const needsEscaping = /["\n\r,]/.test(value);
|
|
88
|
+
return needsEscaping ? `"${value.replaceAll('"', '""')}"` : value;
|
|
89
|
+
}
|
|
90
|
+
function outputCSV(data, columns, printLine) {
|
|
91
|
+
const columnEntries = Object.entries(columns);
|
|
92
|
+
const columnHeaders = columnEntries.map(([key, col]) => col.header || key);
|
|
93
|
+
printLine(columnHeaders.join(','));
|
|
94
|
+
for (const row of data) {
|
|
95
|
+
const values = columnEntries.map(([key]) => {
|
|
96
|
+
const value = row[key];
|
|
97
|
+
return escapeCSV(String(value ?? ''));
|
|
98
|
+
});
|
|
99
|
+
printLine(values.join(','));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function table(data, columns, options) {
|
|
103
|
+
const selectedColumns = selectColumns(columns, {
|
|
104
|
+
columns: options?.columns,
|
|
105
|
+
extended: options?.extended,
|
|
25
106
|
});
|
|
107
|
+
let processedData = data.map(row => Object.fromEntries(Object.entries(selectedColumns).map(([key, { get }]) => [
|
|
108
|
+
key,
|
|
109
|
+
get ? get(row) : row[key],
|
|
110
|
+
])));
|
|
111
|
+
if (options?.filter) {
|
|
112
|
+
const filterPredicate = getFilterPredicate(selectedColumns, options.filter);
|
|
113
|
+
processedData = processedData.filter(row => filterPredicate(row));
|
|
114
|
+
}
|
|
115
|
+
if (options?.sort) {
|
|
116
|
+
const sortIds = getSortIds(selectedColumns, options.sort);
|
|
117
|
+
processedData = orderBy(processedData, sortIds, ['asc']);
|
|
118
|
+
}
|
|
119
|
+
if (options?.csv) {
|
|
120
|
+
const printFn = options?.printLine || ux.stdout;
|
|
121
|
+
outputCSV(processedData, selectedColumns, printFn);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const cols = Object.entries(selectedColumns).map(([key, opts]) => {
|
|
125
|
+
if (opts.header)
|
|
126
|
+
return { key, name: opts.header };
|
|
127
|
+
return key;
|
|
128
|
+
});
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
130
|
+
const { columns, csv, extended, filter, printLine, sort, ...tableOptions } = options || {};
|
|
131
|
+
printTable({
|
|
132
|
+
...(tableOptions?.noStyle
|
|
133
|
+
? {}
|
|
134
|
+
: {
|
|
135
|
+
borderColor: 'whiteBright',
|
|
136
|
+
borderStyle: 'headers-only-with-underline',
|
|
137
|
+
headerOptions: {
|
|
138
|
+
bold: true,
|
|
139
|
+
color: 'white', // or 'reset' to use default terminal color
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
...tableOptions,
|
|
143
|
+
columns: cols,
|
|
144
|
+
data: processedData,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
26
147
|
}
|
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
|
+
"version": "10.9.0",
|
|
5
5
|
"description": "Set of helpful CLI utilities",
|
|
6
6
|
"author": "Heroku",
|
|
7
7
|
"license": "ISC",
|
|
@@ -70,13 +70,14 @@
|
|
|
70
70
|
"vitest": "^4.1.6"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@heroku-cli/command": "^12.4.
|
|
73
|
+
"@heroku-cli/command": "^12.4.2",
|
|
74
74
|
"@heroku/http-call": "^5.5.0",
|
|
75
75
|
"@oclif/core": "^4.3.0",
|
|
76
76
|
"@oclif/table": "0.5.9",
|
|
77
77
|
"ansis": "^4.1.0",
|
|
78
78
|
"debug": "^4.4.0",
|
|
79
79
|
"inquirer": "^12.6.1",
|
|
80
|
+
"natural-orderby": "^5.0.0",
|
|
80
81
|
"open": "^11",
|
|
81
82
|
"printf": "^0.6.1",
|
|
82
83
|
"tsheredoc": "^1.0.1",
|