@get-dx/cli 0.8.0 → 0.10.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/cli.js +4 -0
- package/dist/commandHelpers.js +10 -0
- package/dist/commands/attributeGroups.js +100 -0
- package/dist/commands/auth.js +2 -1
- package/dist/commands/catalog/entities.js +1 -11
- package/dist/commands/catalog/relation-edges-blank-template.yaml +19 -0
- package/dist/commands/catalog/relationEdges.js +285 -0
- package/dist/commands/catalog/relationEdgesRendering.js +45 -0
- package/dist/commands/catalog/relations.js +231 -0
- package/dist/commands/catalog/relationsRendering.js +37 -0
- package/dist/commands/catalog.js +4 -0
- package/dist/commands/doctor.js +85 -0
- package/dist/commands/snapshots.js +318 -8
- package/dist/commands/studio/reports.js +8 -0
- package/dist/commands/teams.js +1 -1
- package/dist/commands/workflow-blank-template.yaml +6 -0
- package/dist/commands/workflowsRendering.js +3 -0
- package/dist/config.js +70 -9
- package/dist/doctor/auth.js +133 -0
- package/dist/doctor/cli.js +154 -0
- package/dist/doctor/config.js +119 -0
- package/dist/doctor/environment.js +60 -0
- package/dist/doctor/format.js +68 -0
- package/dist/doctor/index.js +81 -0
- package/dist/doctor/network.js +224 -0
- package/dist/doctor/probes.js +130 -0
- package/dist/doctor/skill.js +16 -0
- package/dist/doctor/types.js +11 -0
- package/dist/http.js +2 -0
- package/dist/secrets.js +87 -9
- package/dist/ui/blocks.js +46 -0
- package/dist/ui.js +8 -1
- package/dist/versionCheck.js +25 -3
- package/package.json +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createExampleText, getContext, parsePositiveIntOption, wrapAction, } from "../../commandHelpers.js";
|
|
3
|
+
import { CliError, EXIT_CODES } from "../../errors.js";
|
|
4
|
+
import { request } from "../../http.js";
|
|
5
|
+
import { renderJson } from "../../renderers.js";
|
|
6
|
+
import { buildRuntime } from "../../runtime.js";
|
|
7
|
+
import * as ui from "../../ui.js";
|
|
8
|
+
import { renderRelation, renderRelationDeleted, renderRelationList, } from "./relationsRendering.js";
|
|
9
|
+
const RELATION_TYPES = [
|
|
10
|
+
"consumes",
|
|
11
|
+
"depends on",
|
|
12
|
+
"parent of",
|
|
13
|
+
"part of",
|
|
14
|
+
"provides",
|
|
15
|
+
"manages",
|
|
16
|
+
];
|
|
17
|
+
const RELATION_CARDINALITIES = [
|
|
18
|
+
"one_to_one",
|
|
19
|
+
"one_to_many",
|
|
20
|
+
"many_to_one",
|
|
21
|
+
"many_to_many",
|
|
22
|
+
];
|
|
23
|
+
const RELATION_TYPE_HELP = `Relation type. One of: ${RELATION_TYPES.map((t) => `"${t}"`).join(", ")}`;
|
|
24
|
+
export function relationsCommand() {
|
|
25
|
+
const relations = new Command()
|
|
26
|
+
.name("relations")
|
|
27
|
+
.description("Manage relation definitions between entity types");
|
|
28
|
+
relations
|
|
29
|
+
.command("create")
|
|
30
|
+
.description("Create a new relation definition between two entity types in your software catalog")
|
|
31
|
+
.requiredOption("--identifier <identifier>", "Unique identifier for the new relation (required)")
|
|
32
|
+
.requiredOption("--source-type <type>", "Entity type identifier for the source side of the relation (required)")
|
|
33
|
+
.requiredOption("--target-type <type>", "Entity type identifier for the target side of the relation (required)")
|
|
34
|
+
.requiredOption("--type <type>", `${RELATION_TYPE_HELP} (required)`)
|
|
35
|
+
.requiredOption("--cardinality <cardinality>", `Relation cardinality. One of: ${RELATION_CARDINALITIES.join(", ")} (required)`)
|
|
36
|
+
.option("--description <desc>", "Description of the relation")
|
|
37
|
+
.addHelpText("afterAll", createExampleText([
|
|
38
|
+
{
|
|
39
|
+
label: "Create a relation between services",
|
|
40
|
+
command: 'dx catalog relations create --identifier service-depends-on-service --source-type service --target-type service --type "depends on" --cardinality many_to_many',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: "Create with a description and return JSON",
|
|
44
|
+
command: 'dx catalog relations create --identifier service-provides-package --source-type service --target-type package --type provides --cardinality one_to_many --description "Service provides a client SDK" --json',
|
|
45
|
+
},
|
|
46
|
+
]))
|
|
47
|
+
.action(wrapAction(async (options, command) => {
|
|
48
|
+
const runtime = await buildRuntime(getContext(command));
|
|
49
|
+
const response = await createRelation(runtime, {
|
|
50
|
+
identifier: options.identifier,
|
|
51
|
+
source_entity_type_identifier: options.sourceType,
|
|
52
|
+
target_entity_type_identifier: options.targetType,
|
|
53
|
+
type: options.type,
|
|
54
|
+
cardinality: options.cardinality,
|
|
55
|
+
description: options.description,
|
|
56
|
+
});
|
|
57
|
+
if (runtime.context.json) {
|
|
58
|
+
renderJson(response);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
renderRelation(response.relation, `${ui.success(ui.GLYPHS.CHECK)} Relation created`);
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
relations
|
|
65
|
+
.command("delete")
|
|
66
|
+
.description("Delete a relation definition from your software catalog")
|
|
67
|
+
.argument("<identifier>", "Relation identifier")
|
|
68
|
+
.addHelpText("afterAll", createExampleText([
|
|
69
|
+
{
|
|
70
|
+
label: "Delete the `service-depends-on-service` relation",
|
|
71
|
+
command: "dx catalog relations delete service-depends-on-service",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: "Delete a relation and return JSON",
|
|
75
|
+
command: "dx catalog relations delete service-depends-on-service --json",
|
|
76
|
+
},
|
|
77
|
+
]))
|
|
78
|
+
.action(wrapAction(async (identifier, _options, command) => {
|
|
79
|
+
const runtime = await buildRuntime(getContext(command));
|
|
80
|
+
const response = await deleteRelation(runtime, identifier);
|
|
81
|
+
if (runtime.context.json) {
|
|
82
|
+
renderJson(response);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
renderRelationDeleted(response.relation);
|
|
86
|
+
}
|
|
87
|
+
}));
|
|
88
|
+
relations
|
|
89
|
+
.command("info")
|
|
90
|
+
.description("Get information about a relation definition")
|
|
91
|
+
.argument("<identifier>", "Relation identifier")
|
|
92
|
+
.addHelpText("afterAll", createExampleText([
|
|
93
|
+
{
|
|
94
|
+
label: "Fetch info for the `service-depends-on-service` relation",
|
|
95
|
+
command: "dx catalog relations info service-depends-on-service",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
label: "Fetch info and return as JSON",
|
|
99
|
+
command: "dx catalog relations info service-depends-on-service --json",
|
|
100
|
+
},
|
|
101
|
+
]))
|
|
102
|
+
.action(wrapAction(async (identifier, _options, command) => {
|
|
103
|
+
const runtime = await buildRuntime(getContext(command));
|
|
104
|
+
const response = await getRelation(runtime, identifier);
|
|
105
|
+
if (runtime.context.json) {
|
|
106
|
+
renderJson(response);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
renderRelation(response.relation);
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
112
|
+
relations
|
|
113
|
+
.command("list")
|
|
114
|
+
.description("List relation definitions in your software catalog")
|
|
115
|
+
.option("--cursor <cursor>", "Cursor for the next page of results")
|
|
116
|
+
.option("--limit <n>", "Max relations per page (default is 25)", (value) => parsePositiveIntOption(value, "--limit"))
|
|
117
|
+
.addHelpText("afterAll", createExampleText([
|
|
118
|
+
{
|
|
119
|
+
label: "List the first page of relations",
|
|
120
|
+
command: "dx catalog relations list",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: "List with a limit and return JSON",
|
|
124
|
+
command: "dx catalog relations list --limit 10 --json",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
label: "Fetch the next page using a cursor from the prior response",
|
|
128
|
+
command: "dx catalog relations list --cursor avsgf30ccan3",
|
|
129
|
+
},
|
|
130
|
+
]))
|
|
131
|
+
.action(wrapAction(async (options, command) => {
|
|
132
|
+
const runtime = await buildRuntime(getContext(command));
|
|
133
|
+
const response = await listRelations(runtime, {
|
|
134
|
+
cursor: options.cursor,
|
|
135
|
+
limit: options.limit,
|
|
136
|
+
});
|
|
137
|
+
if (runtime.context.json) {
|
|
138
|
+
renderJson(response);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
renderRelationList(response.relations, response.response_metadata?.next_cursor ?? null);
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
144
|
+
relations
|
|
145
|
+
.command("update")
|
|
146
|
+
.description("Update a relation definition. The identifier, source and target entity types, and cardinality cannot be changed.")
|
|
147
|
+
.argument("<identifier>", "Relation identifier")
|
|
148
|
+
.option("--type <type>", RELATION_TYPE_HELP)
|
|
149
|
+
.option("--description <desc>", "Description of the relation")
|
|
150
|
+
.addHelpText("afterAll", createExampleText([
|
|
151
|
+
{
|
|
152
|
+
label: "Update a relation's description",
|
|
153
|
+
command: 'dx catalog relations update service-depends-on-service --description "Runtime dependencies between services"',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: "Change the relation type and return JSON",
|
|
157
|
+
command: "dx catalog relations update service-provides-package --type provides --json",
|
|
158
|
+
},
|
|
159
|
+
]))
|
|
160
|
+
.action(wrapAction(async (identifier, options, command) => {
|
|
161
|
+
if (options.type === undefined && options.description === undefined) {
|
|
162
|
+
throw new CliError("Provide at least one of --type or --description to update", EXIT_CODES.ARGUMENT_ERROR);
|
|
163
|
+
}
|
|
164
|
+
const runtime = await buildRuntime(getContext(command));
|
|
165
|
+
const response = await updateRelation(runtime, identifier, {
|
|
166
|
+
type: options.type,
|
|
167
|
+
description: options.description,
|
|
168
|
+
});
|
|
169
|
+
if (runtime.context.json) {
|
|
170
|
+
renderJson(response);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
renderRelation(response.relation, `${ui.success(ui.GLYPHS.CHECK)} Relation updated`);
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
return relations;
|
|
177
|
+
}
|
|
178
|
+
async function createRelation(runtime, params) {
|
|
179
|
+
const body = {
|
|
180
|
+
identifier: params.identifier,
|
|
181
|
+
source_entity_type_identifier: params.source_entity_type_identifier,
|
|
182
|
+
target_entity_type_identifier: params.target_entity_type_identifier,
|
|
183
|
+
type: params.type,
|
|
184
|
+
cardinality: params.cardinality,
|
|
185
|
+
};
|
|
186
|
+
if (params.description !== undefined)
|
|
187
|
+
body.description = params.description;
|
|
188
|
+
const response = await request(runtime, "/catalog.relations.create", {
|
|
189
|
+
method: "POST",
|
|
190
|
+
body,
|
|
191
|
+
});
|
|
192
|
+
return response.body;
|
|
193
|
+
}
|
|
194
|
+
async function deleteRelation(runtime, identifier) {
|
|
195
|
+
const response = await request(runtime, "/catalog.relations.delete", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
query: { identifier },
|
|
198
|
+
});
|
|
199
|
+
return response.body;
|
|
200
|
+
}
|
|
201
|
+
async function getRelation(runtime, identifier) {
|
|
202
|
+
const response = await request(runtime, "/catalog.relations.info", {
|
|
203
|
+
method: "GET",
|
|
204
|
+
query: { identifier },
|
|
205
|
+
});
|
|
206
|
+
return response.body;
|
|
207
|
+
}
|
|
208
|
+
async function listRelations(runtime, params) {
|
|
209
|
+
const query = {};
|
|
210
|
+
if (params.cursor !== undefined)
|
|
211
|
+
query.cursor = params.cursor;
|
|
212
|
+
if (params.limit !== undefined)
|
|
213
|
+
query.limit = params.limit;
|
|
214
|
+
const response = await request(runtime, "/catalog.relations.list", {
|
|
215
|
+
method: "GET",
|
|
216
|
+
query,
|
|
217
|
+
});
|
|
218
|
+
return response.body;
|
|
219
|
+
}
|
|
220
|
+
async function updateRelation(runtime, identifier, params) {
|
|
221
|
+
const body = { identifier };
|
|
222
|
+
if (params.type !== undefined)
|
|
223
|
+
body.type = params.type;
|
|
224
|
+
if (params.description !== undefined)
|
|
225
|
+
body.description = params.description;
|
|
226
|
+
const response = await request(runtime, "/catalog.relations.update", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body,
|
|
229
|
+
});
|
|
230
|
+
return response.body;
|
|
231
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { renderRichText } from "../../renderers.js";
|
|
2
|
+
import * as ui from "../../ui.js";
|
|
3
|
+
export function renderRelationList(relations, nextCursor) {
|
|
4
|
+
const blocks = [ui.h1("Relations")];
|
|
5
|
+
blocks.push(ui.p(`Displaying ${ui.bold(relations.length.toString())} relations.`));
|
|
6
|
+
if (nextCursor) {
|
|
7
|
+
blocks.push(ui.p(`Next cursor: ${ui.code(nextCursor)}`));
|
|
8
|
+
}
|
|
9
|
+
for (const relation of relations) {
|
|
10
|
+
blocks.push(ui.h2(ui.code(relation.identifier)));
|
|
11
|
+
blocks.push(...relationContent(relation));
|
|
12
|
+
}
|
|
13
|
+
renderRichText(blocks);
|
|
14
|
+
}
|
|
15
|
+
export function renderRelation(relation, title = "Relation Information") {
|
|
16
|
+
renderRichText([ui.h1(title), ...relationContent(relation)]);
|
|
17
|
+
}
|
|
18
|
+
export function renderRelationDeleted(relation) {
|
|
19
|
+
renderRichText([
|
|
20
|
+
ui.h1(`${ui.success(ui.GLYPHS.CHECK)} Relation deleted`),
|
|
21
|
+
ui.p(`The relation ${ui.code(relation.identifier)} has been deleted.`),
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
function relationContent(relation) {
|
|
25
|
+
return [
|
|
26
|
+
ui.dl([
|
|
27
|
+
ui.dli("Identifier", ui.code(relation.identifier)),
|
|
28
|
+
ui.dli("Relation", `${ui.code(relation.source_entity_type_identifier)} ${relation.type} ${ui.code(relation.target_entity_type_identifier)}`),
|
|
29
|
+
ui.dli("Type", relation.type),
|
|
30
|
+
ui.dli("Inverse type", relation.inverse_type),
|
|
31
|
+
ui.dli("Cardinality", ui.code(relation.cardinality)),
|
|
32
|
+
ui.dli("Description", relation.description || ui.dim("(None)")),
|
|
33
|
+
ui.dli("Created", ui.timestampSummary(relation.created_at)),
|
|
34
|
+
ui.dli("Last updated", ui.timestampSummary(relation.updated_at)),
|
|
35
|
+
], { termWidth: 15 }),
|
|
36
|
+
];
|
|
37
|
+
}
|
package/dist/commands/catalog.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { entitiesCommand } from "./catalog/entities.js";
|
|
3
3
|
import { entityTypesCommand } from "./catalog/entityTypes.js";
|
|
4
|
+
import { relationEdgesCommand } from "./catalog/relationEdges.js";
|
|
5
|
+
import { relationsCommand } from "./catalog/relations.js";
|
|
4
6
|
export function catalogCommand() {
|
|
5
7
|
const catalog = new Command()
|
|
6
8
|
.name("catalog")
|
|
7
9
|
.description("Manage the Software Catalog");
|
|
8
10
|
catalog.addCommand(entitiesCommand());
|
|
9
11
|
catalog.addCommand(entityTypesCommand());
|
|
12
|
+
catalog.addCommand(relationEdgesCommand());
|
|
13
|
+
catalog.addCommand(relationsCommand());
|
|
10
14
|
return catalog;
|
|
11
15
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
|
|
3
|
+
import { runDoctor } from "../doctor/index.js";
|
|
4
|
+
import { AsyncProgressReporter, renderJson, renderRichText, } from "../renderers.js";
|
|
5
|
+
import * as ui from "../ui.js";
|
|
6
|
+
const MIN_LABEL_WIDTH = 26;
|
|
7
|
+
const PHASE_MESSAGES = {
|
|
8
|
+
config: "Checking configuration…",
|
|
9
|
+
network: "Checking network…",
|
|
10
|
+
auth: "Checking authentication…",
|
|
11
|
+
version: "Checking for updates…",
|
|
12
|
+
};
|
|
13
|
+
export function doctorCommand() {
|
|
14
|
+
return new Command("doctor")
|
|
15
|
+
.description("Diagnose problems with your CLI instance's installation, configuration, networking, and authentication")
|
|
16
|
+
.addHelpText("afterAll", createExampleText([
|
|
17
|
+
{ label: "Check your setup", command: "dx doctor" },
|
|
18
|
+
{ label: "Print the report as JSON", command: "dx doctor --json" },
|
|
19
|
+
]))
|
|
20
|
+
.action(wrapAction(async (_options, command) => {
|
|
21
|
+
const context = getContext(command);
|
|
22
|
+
const progress = new AsyncProgressReporter();
|
|
23
|
+
progress.start(PHASE_MESSAGES.config);
|
|
24
|
+
let report;
|
|
25
|
+
try {
|
|
26
|
+
report = await runDoctor(context, {
|
|
27
|
+
onPhase: (phase) => progress.update(PHASE_MESSAGES[phase]),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
progress.stop();
|
|
32
|
+
}
|
|
33
|
+
if (context.json) {
|
|
34
|
+
renderJson(report);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
renderDoctorReport(report);
|
|
38
|
+
}
|
|
39
|
+
if (!report.ok) {
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
export function renderDoctorReport(report) {
|
|
45
|
+
const labelWidth = Math.max(MIN_LABEL_WIDTH, ...report.sections.flatMap((section) => section.checks.map((check) => check.label.length + 2)));
|
|
46
|
+
renderRichText([
|
|
47
|
+
...report.sections.map((section) => ui.statusList(section.title, section.checks.map((check) => ({
|
|
48
|
+
status: check.status,
|
|
49
|
+
label: check.label,
|
|
50
|
+
detail: formatDetail(check),
|
|
51
|
+
hint: check.hint,
|
|
52
|
+
})), { labelWidth })),
|
|
53
|
+
ui.p(summaryLine(report)),
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
function formatDetail(check) {
|
|
57
|
+
if (!check.source) {
|
|
58
|
+
return check.message;
|
|
59
|
+
}
|
|
60
|
+
return `${check.message} ${ui.dim(`[from ${sourceName(check.source)}]`)}`;
|
|
61
|
+
}
|
|
62
|
+
function sourceName(source) {
|
|
63
|
+
switch (source) {
|
|
64
|
+
case "env":
|
|
65
|
+
return "env";
|
|
66
|
+
case "config":
|
|
67
|
+
return "config file";
|
|
68
|
+
case "default":
|
|
69
|
+
return "default";
|
|
70
|
+
case "keychain":
|
|
71
|
+
return "keychain";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function summaryLine(report) {
|
|
75
|
+
const { fail, warn } = report.summary;
|
|
76
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
77
|
+
if (fail > 0) {
|
|
78
|
+
const failed = ui.error(`${plural(fail, "check")} failed`);
|
|
79
|
+
return warn > 0 ? `${failed}, ${plural(warn, "warning")}.` : `${failed}.`;
|
|
80
|
+
}
|
|
81
|
+
if (warn > 0) {
|
|
82
|
+
return ui.warning(`All required checks passed, ${plural(warn, "warning")}.`);
|
|
83
|
+
}
|
|
84
|
+
return ui.success("All checks passed.");
|
|
85
|
+
}
|