@get-dx/cli 0.1.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 +44 -0
- package/dist/cli.js +50 -0
- package/dist/commandHelpers.js +110 -0
- package/dist/commands/auth.js +70 -0
- package/dist/commands/authRendering.js +40 -0
- package/dist/commands/catalog/entities.js +596 -0
- package/dist/commands/catalog/entitiesRendering.js +286 -0
- package/dist/commands/catalog/entity-type-blank-template.yaml +51 -0
- package/dist/commands/catalog/entityTypes.js +402 -0
- package/dist/commands/catalog/entityTypesRendering.js +159 -0
- package/dist/commands/catalog.js +11 -0
- package/dist/commands/scorecard-blank-template.yaml +82 -0
- package/dist/commands/scorecards.js +480 -0
- package/dist/commands/scorecardsRendering.js +219 -0
- package/dist/commands/studio/query.js +270 -0
- package/dist/commands/studio/queryRendering.js +99 -0
- package/dist/commands/studio.js +9 -0
- package/dist/config.js +38 -0
- package/dist/errors.js +24 -0
- package/dist/http.js +128 -0
- package/dist/index.js +3 -0
- package/dist/logger.js +70 -0
- package/dist/renderers.js +29 -0
- package/dist/runtime.js +22 -0
- package/dist/secrets.js +80 -0
- package/dist/types.js +1 -0
- package/dist/ui/blocks.js +94 -0
- package/dist/ui/glyphs.js +5 -0
- package/dist/ui.js +109 -0
- package/package.json +56 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import { renderRichText } from "../../renderers.js";
|
|
3
|
+
import * as ui from "../../ui.js";
|
|
4
|
+
export function renderEntityList(entities, nextCursor) {
|
|
5
|
+
const blocks = [ui.h1("Entities")];
|
|
6
|
+
blocks.push(ui.p(`Displaying ${ui.bold(entities.length.toString())} entities.`));
|
|
7
|
+
if (nextCursor) {
|
|
8
|
+
blocks.push(ui.p(`Next cursor: ${ui.code(nextCursor)}`));
|
|
9
|
+
}
|
|
10
|
+
for (const entity of entities) {
|
|
11
|
+
if (entity.name && entity.identifier) {
|
|
12
|
+
blocks.push(ui.h2(`${entity.name} (${ui.code(entity.identifier)})`));
|
|
13
|
+
}
|
|
14
|
+
else if (entity.identifier) {
|
|
15
|
+
blocks.push(ui.h2(ui.code(entity.identifier)));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
blocks.push(ui.h2("Entity"));
|
|
19
|
+
}
|
|
20
|
+
if (entity.identifier) {
|
|
21
|
+
blocks.push(ui.h3("Core attributes"));
|
|
22
|
+
blocks.push(...coreContent(entity));
|
|
23
|
+
}
|
|
24
|
+
if (entity.owner_teams || entity.owner_users) {
|
|
25
|
+
blocks.push(ui.h3("Owners"));
|
|
26
|
+
blocks.push(...ownersContent(entity));
|
|
27
|
+
}
|
|
28
|
+
if (entity.properties) {
|
|
29
|
+
blocks.push(ui.h3("Properties"));
|
|
30
|
+
blocks.push(...propertiesContent(entity));
|
|
31
|
+
}
|
|
32
|
+
if (entity.aliases) {
|
|
33
|
+
blocks.push(ui.h3("Aliases"));
|
|
34
|
+
blocks.push(...aliasesContent(entity));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
renderRichText(blocks);
|
|
38
|
+
}
|
|
39
|
+
export function renderEntity(entity, title = "Entity Information") {
|
|
40
|
+
renderRichText([
|
|
41
|
+
ui.h1(title),
|
|
42
|
+
entity.identifier
|
|
43
|
+
? [ui.h2("Core attributes"), ...coreContent(entity)]
|
|
44
|
+
: null,
|
|
45
|
+
entity.owner_teams ? [ui.h2("Owners"), ...ownersContent(entity)] : null,
|
|
46
|
+
entity.properties
|
|
47
|
+
? [ui.h2("Properties"), ...propertiesContent(entity)]
|
|
48
|
+
: null,
|
|
49
|
+
entity.aliases ? [ui.h2("Aliases"), ...aliasesContent(entity)] : null,
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
export function renderEntityDeleted(entity) {
|
|
53
|
+
renderRichText([
|
|
54
|
+
ui.h1(`${ui.success(ui.GLYPHS.CHECK)} Entity deleted`),
|
|
55
|
+
ui.p(`The entity ${ui.code(entity.identifier)} has been deleted.`),
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
export function renderEntityScorecardList(scorecards, nextCursor) {
|
|
59
|
+
const blocks = [ui.h1("Scorecards")];
|
|
60
|
+
blocks.push(ui.p(`Displaying ${ui.bold(scorecards.length.toString())} scorecards.`));
|
|
61
|
+
if (nextCursor) {
|
|
62
|
+
blocks.push(ui.p(`Next cursor: ${ui.code(nextCursor)}`));
|
|
63
|
+
}
|
|
64
|
+
for (const scorecard of scorecards) {
|
|
65
|
+
blocks.push(ui.h2(scorecard.name));
|
|
66
|
+
blocks.push(...scorecardReportContent(scorecard));
|
|
67
|
+
}
|
|
68
|
+
renderRichText(blocks);
|
|
69
|
+
}
|
|
70
|
+
export function renderEntityTaskList(tasks, nextCursor) {
|
|
71
|
+
const blocks = [ui.h1("Tasks")];
|
|
72
|
+
blocks.push(ui.p(`Displaying ${ui.bold(tasks.length.toString())} tasks.`));
|
|
73
|
+
if (nextCursor) {
|
|
74
|
+
blocks.push(ui.p(`Next cursor: ${ui.code(nextCursor)}`));
|
|
75
|
+
}
|
|
76
|
+
for (const task of tasks) {
|
|
77
|
+
blocks.push(ui.h2(task.check.name));
|
|
78
|
+
blocks.push(...taskContent(task));
|
|
79
|
+
}
|
|
80
|
+
renderRichText(blocks);
|
|
81
|
+
}
|
|
82
|
+
function scorecardReportContent(scorecard) {
|
|
83
|
+
const blocks = [];
|
|
84
|
+
if (scorecard.type === "POINTS") {
|
|
85
|
+
const total = scorecard.points_meta?.points_total ?? 0;
|
|
86
|
+
const achieved = scorecard.points_meta?.points_achieved ?? 0;
|
|
87
|
+
const pct = total === 0 ? 0 : Math.round((achieved / total) * 100);
|
|
88
|
+
blocks.push(ui.p(`Points: ${ui.bold(`${achieved}/${total}`)} ${ui.dim(`(${pct}%)`)}`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const levelName = scorecard.current_level?.name ??
|
|
92
|
+
scorecard.empty_level?.label ??
|
|
93
|
+
"No level";
|
|
94
|
+
blocks.push(ui.p(`Current level: ${ui.bold(levelName)}`));
|
|
95
|
+
}
|
|
96
|
+
if (scorecard.type === "LEVEL" && scorecard.levels) {
|
|
97
|
+
for (const level of scorecard.levels) {
|
|
98
|
+
const checksInLevel = scorecard.checks.filter((check) => check.level?.id === level.id);
|
|
99
|
+
if (checksInLevel.length === 0)
|
|
100
|
+
continue;
|
|
101
|
+
blocks.push(ui.h3(level.name));
|
|
102
|
+
blocks.push(ui.ul(checksInLevel.map(scorecardCheckListItem)));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (scorecard.type === "POINTS" && scorecard.check_groups) {
|
|
106
|
+
const sortedGroups = [...scorecard.check_groups].sort((a, b) => a.ordering - b.ordering);
|
|
107
|
+
for (const group of sortedGroups) {
|
|
108
|
+
const checksInGroup = scorecard.checks.filter((check) => check.check_group?.id === group.id);
|
|
109
|
+
if (checksInGroup.length === 0)
|
|
110
|
+
continue;
|
|
111
|
+
blocks.push(ui.h3(group.name));
|
|
112
|
+
blocks.push(ui.ul(checksInGroup.map(scorecardCheckListItem)));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (scorecard.checks.length > 0) {
|
|
116
|
+
blocks.push(ui.ul(scorecard.checks.map(scorecardCheckListItem)));
|
|
117
|
+
}
|
|
118
|
+
return blocks;
|
|
119
|
+
}
|
|
120
|
+
function scorecardCheckListItem(check) {
|
|
121
|
+
let statusBadge;
|
|
122
|
+
if (check.status === "PASS") {
|
|
123
|
+
statusBadge = ui.success(`${ui.GLYPHS.CHECK} Passed`);
|
|
124
|
+
}
|
|
125
|
+
else if (check.status === "WARN") {
|
|
126
|
+
statusBadge = ui.warning(`${ui.GLYPHS.WARNING} Warning`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
statusBadge = ui.error(`${ui.GLYPHS.ERROR} Not passed`);
|
|
130
|
+
}
|
|
131
|
+
const evalTime = check.executed_at
|
|
132
|
+
? ui.dim(`(${dayjs(check.executed_at).fromNow()})`)
|
|
133
|
+
: "";
|
|
134
|
+
const parts = [check.name, evalTime, statusBadge].filter(Boolean);
|
|
135
|
+
return ui.li([ui.p(parts.join(" "), false)]);
|
|
136
|
+
}
|
|
137
|
+
function taskContent(task) {
|
|
138
|
+
const { check, initiative, owner } = task;
|
|
139
|
+
return [
|
|
140
|
+
ui.h3("Check:"),
|
|
141
|
+
ui.dl([
|
|
142
|
+
ui.dli("Name", [ui.p(check.name, false)]),
|
|
143
|
+
ui.dli("ID", [ui.p(ui.code(check.id), false)]),
|
|
144
|
+
ui.dli("External URL", [
|
|
145
|
+
ui.p(check.external_url ? ui.link(check.external_url) : ui.dim("(none)"), false),
|
|
146
|
+
]),
|
|
147
|
+
ui.dli("Description", [
|
|
148
|
+
ui.p(check.description || ui.dim("(none)"), false),
|
|
149
|
+
]),
|
|
150
|
+
], { termWidth: 14 }),
|
|
151
|
+
ui.h3("Initiative:"),
|
|
152
|
+
ui.dl([
|
|
153
|
+
ui.dli("Name", [ui.p(initiative.name, false)]),
|
|
154
|
+
ui.dli("Priority", [ui.p(`P${initiative.priority}`, false)]),
|
|
155
|
+
ui.dli("Owner", [
|
|
156
|
+
ui.p(`${owner.name} (${ui.code(owner.email)})`, false),
|
|
157
|
+
]),
|
|
158
|
+
ui.dli("Due", [ui.p(dueDateText(initiative.complete_by), false)]),
|
|
159
|
+
], { termWidth: 10 }),
|
|
160
|
+
ui.p(ui.bold("Description:")),
|
|
161
|
+
ui.p(initiative.description || ui.dim("(none)")),
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
function dueDateText(completeBy) {
|
|
165
|
+
const now = dayjs();
|
|
166
|
+
const dueDate = dayjs(completeBy);
|
|
167
|
+
const formattedDate = new Date(completeBy).toLocaleDateString(undefined, {
|
|
168
|
+
month: "short",
|
|
169
|
+
day: "numeric",
|
|
170
|
+
timeZone: "UTC",
|
|
171
|
+
});
|
|
172
|
+
if (dueDate.isBefore(now)) {
|
|
173
|
+
return ui.error(formattedDate);
|
|
174
|
+
}
|
|
175
|
+
else if (dueDate.isBefore(now.add(2, "week"))) {
|
|
176
|
+
return ui.warning(formattedDate);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
return formattedDate;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function coreContent(entity) {
|
|
183
|
+
return [
|
|
184
|
+
ui.dl([
|
|
185
|
+
ui.dli("Name", [ui.p(entity.name ?? ui.dim("(None)"), false)]),
|
|
186
|
+
ui.dli("Identifier", [ui.p(ui.code(entity.identifier), false)]),
|
|
187
|
+
ui.dli("Type", [ui.p(ui.code(entity.type), false)]),
|
|
188
|
+
ui.dli("Created", [
|
|
189
|
+
ui.p(ui.timestampSummary(entity.created_at), false),
|
|
190
|
+
]),
|
|
191
|
+
ui.dli("Last updated", [ui.p(ui.timestampSummary(entity.updated_at))]),
|
|
192
|
+
ui.dli("Description", [
|
|
193
|
+
ui.p(entity.description ?? ui.dim("(None)"), false),
|
|
194
|
+
]),
|
|
195
|
+
], { termWidth: 14 }),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function ownersContent(entity) {
|
|
199
|
+
const results = [];
|
|
200
|
+
const teamCount = entity.owner_teams?.length ?? 0;
|
|
201
|
+
const userCount = entity.owner_users?.length ?? 0;
|
|
202
|
+
if (teamCount === 0 && userCount === 0) {
|
|
203
|
+
results.push(ui.p(ui.dim("(No owners assigned)")));
|
|
204
|
+
}
|
|
205
|
+
if (entity.owner_teams && teamCount > 0) {
|
|
206
|
+
results.push(ui.p(ui.bold("Teams:")));
|
|
207
|
+
results.push(ui.ul([
|
|
208
|
+
...entity.owner_teams.map((team) => ui.li([ui.p(`${team.name} (${team.id})`, false)])),
|
|
209
|
+
]));
|
|
210
|
+
}
|
|
211
|
+
if (entity.owner_users && userCount > 0) {
|
|
212
|
+
results.push(ui.p(ui.bold("Users:")));
|
|
213
|
+
results.push(ui.ul([
|
|
214
|
+
...entity.owner_users.map((user) => ui.li([ui.p(`${user.email} (${user.id})`, false)])),
|
|
215
|
+
]));
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
function propertiesContent(entity) {
|
|
220
|
+
const propertiesListItems = [];
|
|
221
|
+
for (const [identifier, value] of Object.entries(entity.properties)) {
|
|
222
|
+
const serialized = propertyValueContent(identifier, value);
|
|
223
|
+
if (typeof serialized === "string") {
|
|
224
|
+
propertiesListItems.push(ui.li([ui.p(`${ui.bold(identifier)}: ${serialized}`, false)]));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
propertiesListItems.push(ui.li([ui.p(`${ui.bold(identifier)}:`, false), serialized]));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return [ui.ul(propertiesListItems)];
|
|
231
|
+
}
|
|
232
|
+
function aliasesContent(entity) {
|
|
233
|
+
return [ui.json({ ...entity.aliases })];
|
|
234
|
+
}
|
|
235
|
+
function propertyValueContent(identifier, value) {
|
|
236
|
+
// Property-specific
|
|
237
|
+
if (isFileMatchingRule(value)) {
|
|
238
|
+
const matchText = value.match_count === 1 ? "match" : "matches";
|
|
239
|
+
return `File matching rule: ${ui.code(value.status)} (${value.match_count} ${matchText})`;
|
|
240
|
+
}
|
|
241
|
+
else if (isUrl(value)) {
|
|
242
|
+
return ui.link(value);
|
|
243
|
+
}
|
|
244
|
+
// Primitives
|
|
245
|
+
if (value === null) {
|
|
246
|
+
return ui.code("null");
|
|
247
|
+
}
|
|
248
|
+
else if (typeof value === "string") {
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
251
|
+
else if (typeof value === "number") {
|
|
252
|
+
return value.toString();
|
|
253
|
+
}
|
|
254
|
+
else if (typeof value === "boolean") {
|
|
255
|
+
return value.toString();
|
|
256
|
+
}
|
|
257
|
+
// General arrays
|
|
258
|
+
if (isStringArray(value)) {
|
|
259
|
+
return value.join(", ");
|
|
260
|
+
}
|
|
261
|
+
// Fallback to JSON
|
|
262
|
+
return ui.json(value);
|
|
263
|
+
}
|
|
264
|
+
function isStringArray(value) {
|
|
265
|
+
return (Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
266
|
+
}
|
|
267
|
+
function isUrl(value) {
|
|
268
|
+
if (typeof value !== "string" || !value.startsWith("http")) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
new URL(value);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function isFileMatchingRule(value) {
|
|
280
|
+
return (typeof value === "object" &&
|
|
281
|
+
value !== null &&
|
|
282
|
+
typeof value === "object" &&
|
|
283
|
+
"status" in value &&
|
|
284
|
+
typeof value.status === "string" &&
|
|
285
|
+
["FOUND_MATCHES", "NO_MATCHES", "FILE_NOT_FOUND"].includes(value.status));
|
|
286
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Entity type configuration
|
|
2
|
+
#
|
|
3
|
+
# Edit this file, then run:
|
|
4
|
+
#
|
|
5
|
+
# ```
|
|
6
|
+
# dx catalog entityTypes create --from-file <path>
|
|
7
|
+
# ```
|
|
8
|
+
#
|
|
9
|
+
# After the entity type is created, make updates by running:
|
|
10
|
+
#
|
|
11
|
+
# ```
|
|
12
|
+
# dx catalog entityTypes update <identifier> --from-file <path>
|
|
13
|
+
# ```
|
|
14
|
+
|
|
15
|
+
identifier: ""
|
|
16
|
+
name: ""
|
|
17
|
+
description: ""
|
|
18
|
+
icon: server
|
|
19
|
+
|
|
20
|
+
# Properties for this entity type (optional)
|
|
21
|
+
properties:
|
|
22
|
+
- identifier: tier
|
|
23
|
+
name: Tier
|
|
24
|
+
type: select
|
|
25
|
+
description: null
|
|
26
|
+
definition:
|
|
27
|
+
options:
|
|
28
|
+
- value: Tier-1
|
|
29
|
+
color: "#ef4444"
|
|
30
|
+
- value: Tier-2
|
|
31
|
+
color: "#fbbf24"
|
|
32
|
+
- value: Tier-3
|
|
33
|
+
color: "#38bdf8"
|
|
34
|
+
is_required: false
|
|
35
|
+
visibility: visible
|
|
36
|
+
ordering: 0
|
|
37
|
+
- identifier: incident-runbook
|
|
38
|
+
name: Incident runbook
|
|
39
|
+
type: url
|
|
40
|
+
description: null
|
|
41
|
+
definition:
|
|
42
|
+
call_to_action: globe
|
|
43
|
+
call_to_action_type: icon
|
|
44
|
+
is_required: false
|
|
45
|
+
visibility: visible
|
|
46
|
+
ordering: 1
|
|
47
|
+
|
|
48
|
+
# Aliases available for this entity type (optional)
|
|
49
|
+
# aliases:
|
|
50
|
+
# github_repo: true
|
|
51
|
+
aliases: {}
|