@get-dx/cli 0.2.0 → 0.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 +28 -15
- package/dist/cli.js +4 -0
- package/dist/commands/auth.js +27 -22
- package/dist/commands/catalog/entities.js +91 -24
- package/dist/commands/catalog/entitiesRendering.js +13 -9
- package/dist/commands/catalog/entityTypes.js +4 -4
- package/dist/commands/catalog/entityTypesRendering.js +15 -21
- package/dist/commands/init.js +15 -60
- package/dist/commands/scorecards.js +4 -4
- package/dist/commands/scorecardsRendering.js +11 -16
- package/dist/commands/studio/query.js +2 -2
- package/dist/commands/workflows.js +82 -0
- package/dist/commands/workflowsRendering.js +136 -0
- package/dist/config.js +171 -29
- package/dist/http.js +1 -1
- package/dist/loginViaBrowser.js +5 -5
- package/dist/runtime.js +11 -7
- package/dist/ui.js +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,31 +18,44 @@ Interactively login and install the AI agent skill:
|
|
|
18
18
|
dx init
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Show help text:
|
|
22
|
+
|
|
23
|
+
```shell
|
|
24
|
+
dx --help
|
|
25
|
+
dx <subcommand> --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Customizing base URLs
|
|
22
29
|
|
|
23
|
-
|
|
30
|
+
The CLI requires two base URLs to be configured. The default values are used for DX **cloud** deployments. Users of **dedicated** and **managed** deployments will need to specify these values explicitly when logging in.
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
| Value | How it is used | Env var | Default value |
|
|
33
|
+
| ---------------- | -------------------------------------------- | ----------------- | ----------------------- |
|
|
34
|
+
| **Web base URL** | Browser-based login and displaying web links | `DX_WEB_BASE_URL` | `https://app.getdx.com` |
|
|
35
|
+
| **API base URL** | Making each API request to DX | `DX_API_BASE_URL` | `https://api.getdx.com` |
|
|
36
|
+
|
|
37
|
+
#### For dedicated deployments
|
|
38
|
+
|
|
39
|
+
Set the env vars once when initializing:
|
|
26
40
|
|
|
27
41
|
```shell
|
|
28
|
-
#
|
|
29
|
-
|
|
42
|
+
# Interactive login
|
|
43
|
+
DX_WEB_BASE_URL="https://mycompany.getdx.io" DX_API_BASE_URL="https://api.mycompany.getdx.io" dx init
|
|
30
44
|
|
|
31
|
-
#
|
|
32
|
-
|
|
45
|
+
# Non-interactive login for CI or remote agents
|
|
46
|
+
DX_WEB_BASE_URL="https://mycompany.getdx.io" DX_API_BASE_URL="https://api.mycompany.getdx.io" dx auth login --token $DX_TOKEN
|
|
33
47
|
```
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
#### For managed deployments
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
- `<account>.getdx.io` — DX dedicated (derives `https://api.<account>.getdx.io` automatically)
|
|
39
|
-
- Any custom domain — you will be prompted for the separate API base URL
|
|
40
|
-
|
|
41
|
-
Show help text:
|
|
51
|
+
Set the env vars once when initializing:
|
|
42
52
|
|
|
43
53
|
```shell
|
|
44
|
-
|
|
45
|
-
dx
|
|
54
|
+
# Interactive login
|
|
55
|
+
DX_WEB_BASE_URL="https://dx.some-example-subdomain.example.com" DX_API_BASE_URL="https://api.dx.some-example-subdomain.example.com" dx init
|
|
56
|
+
|
|
57
|
+
# Non-interactive login for CI or remote agents
|
|
58
|
+
DX_WEB_BASE_URL="https://dx.some-example-subdomain.example.com" DX_API_BASE_URL="https://api.dx.some-example-subdomain.example.com" dx auth login --token $DX_TOKEN
|
|
46
59
|
```
|
|
47
60
|
|
|
48
61
|
## Logging
|
package/dist/cli.js
CHANGED
|
@@ -5,10 +5,13 @@ import { initCommand } from "./commands/init.js";
|
|
|
5
5
|
import { scorecardsCommand } from "./commands/scorecards.js";
|
|
6
6
|
import { studioCommand } from "./commands/studio.js";
|
|
7
7
|
import { teamsCommand } from "./commands/teams.js";
|
|
8
|
+
import { workflowsCommand } from "./commands/workflows.js";
|
|
8
9
|
import { handleError } from "./commandHelpers.js";
|
|
9
10
|
import cliPackage from "../package.json" with { type: "json" };
|
|
11
|
+
import { handleTemporaryBaseUrlMigration } from "./config.js";
|
|
10
12
|
export async function run(argv = process.argv) {
|
|
11
13
|
try {
|
|
14
|
+
handleTemporaryBaseUrlMigration(argv);
|
|
12
15
|
const program = createProgram();
|
|
13
16
|
if (argv.length <= 2) {
|
|
14
17
|
program.outputHelp();
|
|
@@ -35,6 +38,7 @@ function createProgram() {
|
|
|
35
38
|
program.addCommand(scorecardsCommand());
|
|
36
39
|
program.addCommand(studioCommand());
|
|
37
40
|
program.addCommand(teamsCommand());
|
|
41
|
+
program.addCommand(workflowsCommand());
|
|
38
42
|
applyExitOverride(program);
|
|
39
43
|
return program;
|
|
40
44
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -4,12 +4,11 @@ import { deleteToken, setToken } from "../secrets.js";
|
|
|
4
4
|
import { renderJson } from "../renderers.js";
|
|
5
5
|
import { renderAuthInfo, renderLoggedOut } from "./authRendering.js";
|
|
6
6
|
import { getContext, wrapAction } from "../commandHelpers.js";
|
|
7
|
-
import {
|
|
7
|
+
import { persistBaseUrls, resolveApiBaseUrl, deriveBaseUrlsFromEnv, } from "../config.js";
|
|
8
8
|
import { CliError } from "../errors.js";
|
|
9
9
|
import { request } from "../http.js";
|
|
10
10
|
import { loginViaBrowser } from "../loginViaBrowser.js";
|
|
11
|
-
import {
|
|
12
|
-
import cliPackage from "../../package.json" with { type: "json" };
|
|
11
|
+
import { buildRuntime } from "../runtime.js";
|
|
13
12
|
import { maskToken } from "../ui.js";
|
|
14
13
|
export function authCommand() {
|
|
15
14
|
const auth = new Command()
|
|
@@ -20,7 +19,7 @@ export function authCommand() {
|
|
|
20
19
|
.option("--token <token>", "Account web API token or personal access token. Omit this option to login interactively by web browser or pasting.")
|
|
21
20
|
.action(wrapAction(async (commandOptions, command) => {
|
|
22
21
|
const context = getContext(command);
|
|
23
|
-
const
|
|
22
|
+
const { apiBaseUrl, webBaseUrl } = deriveBaseUrlsFromEnv();
|
|
24
23
|
let token = commandOptions.token;
|
|
25
24
|
if (!token) {
|
|
26
25
|
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
@@ -34,7 +33,7 @@ export function authCommand() {
|
|
|
34
33
|
],
|
|
35
34
|
});
|
|
36
35
|
if (method === "browser") {
|
|
37
|
-
token = await loginViaBrowser(
|
|
36
|
+
token = await loginViaBrowser(webBaseUrl);
|
|
38
37
|
}
|
|
39
38
|
else {
|
|
40
39
|
token = await password({
|
|
@@ -46,45 +45,51 @@ export function authCommand() {
|
|
|
46
45
|
throw new CliError("Account web API token or personal access token is required");
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
|
-
const runtime = {
|
|
50
|
-
|
|
48
|
+
const runtime = buildRuntime(context, {
|
|
49
|
+
apiBaseUrl,
|
|
51
50
|
token,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
logger: buildLogger(context),
|
|
55
|
-
};
|
|
51
|
+
webBaseUrl,
|
|
52
|
+
});
|
|
56
53
|
const response = await getAuthInfo(runtime);
|
|
57
|
-
|
|
58
|
-
setToken(
|
|
54
|
+
persistBaseUrls(apiBaseUrl, webBaseUrl);
|
|
55
|
+
setToken(apiBaseUrl, token);
|
|
59
56
|
if (context.json) {
|
|
60
|
-
renderJson({
|
|
57
|
+
renderJson({
|
|
58
|
+
...response,
|
|
59
|
+
api_base_url: apiBaseUrl,
|
|
60
|
+
web_base_url: webBaseUrl,
|
|
61
|
+
});
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
|
-
renderAuthInfo(response, token,
|
|
64
|
+
renderAuthInfo(response, token, apiBaseUrl);
|
|
64
65
|
}));
|
|
65
66
|
auth.command("logout").action(wrapAction(async (_options, command) => {
|
|
66
67
|
const context = getContext(command);
|
|
67
|
-
const
|
|
68
|
-
deleteToken(
|
|
68
|
+
const apiBaseUrl = resolveApiBaseUrl();
|
|
69
|
+
deleteToken(apiBaseUrl);
|
|
69
70
|
if (context.json) {
|
|
70
|
-
renderJson({ ok: true,
|
|
71
|
+
renderJson({ ok: true, api_base_url: apiBaseUrl, logged_out: true });
|
|
71
72
|
}
|
|
72
73
|
else {
|
|
73
|
-
renderLoggedOut(
|
|
74
|
+
renderLoggedOut(apiBaseUrl);
|
|
74
75
|
}
|
|
75
76
|
}));
|
|
76
|
-
auth
|
|
77
|
+
auth
|
|
78
|
+
.command("status")
|
|
79
|
+
.alias("info")
|
|
80
|
+
.action(wrapAction(async (_options, command) => {
|
|
77
81
|
const runtime = buildRuntime(getContext(command));
|
|
78
82
|
const response = await getAuthInfo(runtime);
|
|
79
83
|
if (runtime.context.json) {
|
|
80
84
|
renderJson({
|
|
81
85
|
...response,
|
|
82
86
|
token: maskToken(runtime.token),
|
|
83
|
-
|
|
87
|
+
api_base_url: runtime.apiBaseUrl,
|
|
88
|
+
web_base_url: runtime.webBaseUrl,
|
|
84
89
|
});
|
|
85
90
|
}
|
|
86
91
|
else {
|
|
87
|
-
renderAuthInfo(response, runtime.token, runtime.
|
|
92
|
+
renderAuthInfo(response, runtime.token, runtime.apiBaseUrl);
|
|
88
93
|
}
|
|
89
94
|
}));
|
|
90
95
|
return auth;
|
|
@@ -21,6 +21,7 @@ export function entitiesCommand() {
|
|
|
21
21
|
.option("--owner-team-ids <ids>", "Comma-separated owner team IDs")
|
|
22
22
|
.option("--owner-user-ids <ids>", "Comma-separated owner user IDs")
|
|
23
23
|
.option("--property <kv>", "Set a property value as key=value. Repeat for multiple properties.", (val, prev) => [...prev, val], [])
|
|
24
|
+
.option("--alias <kv>", "Set an alias value as key=value. Repeat for multiple aliases.", (val, prev) => [...prev, val], [])
|
|
24
25
|
.addHelpText("afterAll", createExampleText([
|
|
25
26
|
{
|
|
26
27
|
label: "Create a new service entity",
|
|
@@ -38,6 +39,10 @@ export function entitiesCommand() {
|
|
|
38
39
|
label: "Create with properties",
|
|
39
40
|
command: 'dx catalog entities create --type service --identifier my-service --property tier=Tier-1 --property "languages=Ruby,TypeScript"',
|
|
40
41
|
},
|
|
42
|
+
{
|
|
43
|
+
label: "Create with aliases",
|
|
44
|
+
command: "dx catalog entities create --type service --identifier my-service --alias github_repo=12345",
|
|
45
|
+
},
|
|
41
46
|
]))
|
|
42
47
|
.action(wrapAction(async (options, command) => {
|
|
43
48
|
if (!options.identifier) {
|
|
@@ -47,7 +52,7 @@ export function entitiesCommand() {
|
|
|
47
52
|
throw new CliError("--type is required", EXIT_CODES.ARGUMENT_ERROR);
|
|
48
53
|
}
|
|
49
54
|
const runtime = buildRuntime(getContext(command));
|
|
50
|
-
const
|
|
55
|
+
const resolvedValues = await resolveValuesForEntityType(runtime, options.type, options.property, options.alias);
|
|
51
56
|
const response = await createEntity(runtime, options.identifier, {
|
|
52
57
|
type: options.type,
|
|
53
58
|
name: options.name,
|
|
@@ -58,13 +63,13 @@ export function entitiesCommand() {
|
|
|
58
63
|
owner_user_ids: options.ownerUserIds
|
|
59
64
|
?.split(",")
|
|
60
65
|
.map((s) => s.trim()),
|
|
61
|
-
|
|
66
|
+
...resolvedValues,
|
|
62
67
|
});
|
|
63
68
|
if (runtime.context.json) {
|
|
64
69
|
renderJson(response);
|
|
65
70
|
}
|
|
66
71
|
else {
|
|
67
|
-
renderEntity(response.entity);
|
|
72
|
+
renderEntity(response.entity, runtime);
|
|
68
73
|
}
|
|
69
74
|
}));
|
|
70
75
|
entities
|
|
@@ -100,6 +105,7 @@ export function entitiesCommand() {
|
|
|
100
105
|
.option("--owner-team-ids <ids>", "Comma-separated owner team IDs")
|
|
101
106
|
.option("--owner-user-ids <ids>", "Comma-separated owner user IDs")
|
|
102
107
|
.option("--property <kv>", "Set a property value as key=value. Repeat for multiple properties. Use value null to remove a property.", (val, prev) => [...prev, val], [])
|
|
108
|
+
.option("--alias <kv>", "Set an alias value as key=value. Repeat for multiple aliases. Use value null to remove an alias.", (val, prev) => [...prev, val], [])
|
|
103
109
|
.addHelpText("afterAll", createExampleText([
|
|
104
110
|
{
|
|
105
111
|
label: "Rename an entity",
|
|
@@ -113,19 +119,23 @@ export function entitiesCommand() {
|
|
|
113
119
|
label: "Update properties",
|
|
114
120
|
command: 'dx catalog entities update my-service --property tier=Tier-1 --property "languages=Ruby,TypeScript"',
|
|
115
121
|
},
|
|
122
|
+
{
|
|
123
|
+
label: "Update aliases",
|
|
124
|
+
command: "dx catalog entities update my-service --alias github_repo=12345",
|
|
125
|
+
},
|
|
116
126
|
]))
|
|
117
127
|
.action(wrapAction(async (identifier, options, command) => {
|
|
118
128
|
const runtime = buildRuntime(getContext(command));
|
|
119
|
-
const
|
|
129
|
+
const resolvedValues = await resolveValuesForExistingEntity(runtime, identifier, options.property, options.alias);
|
|
120
130
|
const response = await updateEntity(runtime, identifier, {
|
|
121
131
|
...getEntityMutationOptionValues(options),
|
|
122
|
-
|
|
132
|
+
...resolvedValues,
|
|
123
133
|
});
|
|
124
134
|
if (runtime.context.json) {
|
|
125
135
|
renderJson(response);
|
|
126
136
|
}
|
|
127
137
|
else {
|
|
128
|
-
renderEntity(response.entity, `${ui.success(ui.GLYPHS.CHECK)} Entity updated`);
|
|
138
|
+
renderEntity(response.entity, runtime, `${ui.success(ui.GLYPHS.CHECK)} Entity updated`);
|
|
129
139
|
}
|
|
130
140
|
}));
|
|
131
141
|
entities
|
|
@@ -138,6 +148,7 @@ export function entitiesCommand() {
|
|
|
138
148
|
.option("--owner-team-ids <ids>", "Comma-separated owner team IDs")
|
|
139
149
|
.option("--owner-user-ids <ids>", "Comma-separated owner user IDs")
|
|
140
150
|
.option("--property <kv>", "Set a property value as key=value. Repeat for multiple properties. Use value null to remove a property.", (val, prev) => [...prev, val], [])
|
|
151
|
+
.option("--alias <kv>", "Set an alias value as key=value. Repeat for multiple aliases. Use value null to remove an alias.", (val, prev) => [...prev, val], [])
|
|
141
152
|
.addHelpText("afterAll", createExampleText([
|
|
142
153
|
{
|
|
143
154
|
label: "Create a new service entity if it does not exist",
|
|
@@ -151,6 +162,10 @@ export function entitiesCommand() {
|
|
|
151
162
|
label: "Set properties while preserving omitted fields",
|
|
152
163
|
command: 'dx catalog entities upsert --type service --identifier my-service --property tier=Tier-1 --property "languages=Ruby,TypeScript"',
|
|
153
164
|
},
|
|
165
|
+
{
|
|
166
|
+
label: "Set aliases while preserving omitted fields",
|
|
167
|
+
command: "dx catalog entities upsert --type service --identifier my-service --alias github_repo=12345",
|
|
168
|
+
},
|
|
154
169
|
]))
|
|
155
170
|
.action(wrapAction(async (options, command) => {
|
|
156
171
|
if (!options.type) {
|
|
@@ -160,11 +175,11 @@ export function entitiesCommand() {
|
|
|
160
175
|
throw new CliError("--identifier is required", EXIT_CODES.ARGUMENT_ERROR);
|
|
161
176
|
}
|
|
162
177
|
const runtime = buildRuntime(getContext(command));
|
|
163
|
-
const
|
|
178
|
+
const resolvedValues = await resolveValuesForEntityType(runtime, options.type, options.property, options.alias);
|
|
164
179
|
const response = await upsertEntity(runtime, options.identifier, {
|
|
165
180
|
type: options.type,
|
|
166
181
|
...getEntityMutationOptionValues(options),
|
|
167
|
-
|
|
182
|
+
...resolvedValues,
|
|
168
183
|
});
|
|
169
184
|
if (runtime.context.json) {
|
|
170
185
|
renderJson(response);
|
|
@@ -173,7 +188,7 @@ export function entitiesCommand() {
|
|
|
173
188
|
const title = response.result === "created_new_entity"
|
|
174
189
|
? `${ui.success(ui.GLYPHS.CHECK)} Created new entity`
|
|
175
190
|
: `${ui.success(ui.GLYPHS.CHECK)} Updated existing entity`;
|
|
176
|
-
renderEntity(response.entity, title);
|
|
191
|
+
renderEntity(response.entity, runtime, title);
|
|
177
192
|
}
|
|
178
193
|
}));
|
|
179
194
|
entities
|
|
@@ -202,7 +217,7 @@ export function entitiesCommand() {
|
|
|
202
217
|
renderJson(processedResponse);
|
|
203
218
|
}
|
|
204
219
|
else {
|
|
205
|
-
renderEntity(processedResponse.entity);
|
|
220
|
+
renderEntity(processedResponse.entity, runtime);
|
|
206
221
|
}
|
|
207
222
|
}));
|
|
208
223
|
entities
|
|
@@ -245,7 +260,7 @@ export function entitiesCommand() {
|
|
|
245
260
|
renderJson({ ...response, entities: processedEntities });
|
|
246
261
|
}
|
|
247
262
|
else {
|
|
248
|
-
renderEntityList(processedEntities, response.response_metadata?.next_cursor ?? null);
|
|
263
|
+
renderEntityList(processedEntities, response.response_metadata?.next_cursor ?? null, runtime);
|
|
249
264
|
}
|
|
250
265
|
}));
|
|
251
266
|
entities
|
|
@@ -311,7 +326,7 @@ export function entitiesCommand() {
|
|
|
311
326
|
renderJson(response);
|
|
312
327
|
}
|
|
313
328
|
else {
|
|
314
|
-
renderEntityScorecardList(response.scorecards, response.response_metadata?.next_cursor ?? null);
|
|
329
|
+
renderEntityScorecardList(response.scorecards, identifier, runtime, response.response_metadata?.next_cursor ?? null);
|
|
315
330
|
}
|
|
316
331
|
}));
|
|
317
332
|
return entities;
|
|
@@ -459,6 +474,33 @@ export function parsePropertyValue(prop, rawValue) {
|
|
|
459
474
|
return rawValue;
|
|
460
475
|
}
|
|
461
476
|
}
|
|
477
|
+
function parseEntityAliases(entityTypeIdentifier, rawAliases, knownAliases) {
|
|
478
|
+
const aliasKeys = Object.keys(knownAliases);
|
|
479
|
+
const aliasSet = new Set(aliasKeys);
|
|
480
|
+
const result = {};
|
|
481
|
+
for (const kv of rawAliases) {
|
|
482
|
+
const eqIndex = kv.indexOf("=");
|
|
483
|
+
if (eqIndex === -1) {
|
|
484
|
+
throw new CliError(`Invalid --alias "${kv}": expected format key=value`, EXIT_CODES.ARGUMENT_ERROR);
|
|
485
|
+
}
|
|
486
|
+
const key = kv.slice(0, eqIndex);
|
|
487
|
+
const rawValue = kv.slice(eqIndex + 1);
|
|
488
|
+
if (!aliasSet.has(key)) {
|
|
489
|
+
const availableAliases = aliasKeys
|
|
490
|
+
.sort()
|
|
491
|
+
.map((k) => `\`${k}\``)
|
|
492
|
+
.join(", ");
|
|
493
|
+
const availableAliasesText = availableAliases.length > 0 ? availableAliases : "(none)";
|
|
494
|
+
throw new CliError(`Unknown alias \`${key}\` for entity type \`${entityTypeIdentifier}\`. Available aliases: ${availableAliasesText}`, EXIT_CODES.ARGUMENT_ERROR);
|
|
495
|
+
}
|
|
496
|
+
if (rawValue === "null") {
|
|
497
|
+
result[key] = [];
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
result[key] = [...(result[key] ?? []), { identifier: rawValue }];
|
|
501
|
+
}
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
462
504
|
// --- Include helpers ---
|
|
463
505
|
const ENTITY_INCLUDE_SECTIONS = [
|
|
464
506
|
"core",
|
|
@@ -487,22 +529,31 @@ function parseIncludeSections(include) {
|
|
|
487
529
|
}
|
|
488
530
|
return results;
|
|
489
531
|
}
|
|
490
|
-
async function
|
|
491
|
-
if (rawProperties.length === 0) {
|
|
492
|
-
return
|
|
532
|
+
async function resolveValuesForEntityType(runtime, entityTypeIdentifier, rawProperties, rawAliases) {
|
|
533
|
+
if (rawProperties.length === 0 && rawAliases.length === 0) {
|
|
534
|
+
return {};
|
|
493
535
|
}
|
|
494
536
|
let entityTypeResponse;
|
|
495
537
|
try {
|
|
496
538
|
entityTypeResponse = await getEntityType(runtime, entityTypeIdentifier);
|
|
497
539
|
}
|
|
498
540
|
catch (err) {
|
|
499
|
-
throw wrapEntityTypeLookupError(entityTypeIdentifier, err
|
|
541
|
+
throw wrapEntityTypeLookupError(entityTypeIdentifier, err, rawAliases.length > 0
|
|
542
|
+
? "resolve property and alias values"
|
|
543
|
+
: "resolve property types");
|
|
544
|
+
}
|
|
545
|
+
const values = {};
|
|
546
|
+
if (rawProperties.length > 0) {
|
|
547
|
+
values.properties = parseEntityProperties(entityTypeIdentifier, rawProperties, entityTypeResponse.entity_type.properties);
|
|
500
548
|
}
|
|
501
|
-
|
|
549
|
+
if (rawAliases.length > 0) {
|
|
550
|
+
values.aliases = parseEntityAliases(entityTypeIdentifier, rawAliases, entityTypeResponse.entity_type.aliases);
|
|
551
|
+
}
|
|
552
|
+
return values;
|
|
502
553
|
}
|
|
503
|
-
async function
|
|
504
|
-
if (rawProperties.length === 0) {
|
|
505
|
-
return
|
|
554
|
+
async function resolveValuesForExistingEntity(runtime, identifier, rawProperties, rawAliases) {
|
|
555
|
+
if (rawProperties.length === 0 && rawAliases.length === 0) {
|
|
556
|
+
return {};
|
|
506
557
|
}
|
|
507
558
|
let entityResponse;
|
|
508
559
|
try {
|
|
@@ -512,9 +563,23 @@ async function resolvePropertiesForExistingEntity(runtime, identifier, rawProper
|
|
|
512
563
|
const exitCode = err instanceof HttpError && err.status !== undefined && err.status < 500
|
|
513
564
|
? EXIT_CODES.ARGUMENT_ERROR
|
|
514
565
|
: EXIT_CODES.RETRY_RECOMMENDED;
|
|
515
|
-
throw new CliError(`Failed to fetch entity "${identifier}" to resolve property
|
|
566
|
+
throw new CliError(`Failed to fetch entity "${identifier}" to resolve property and alias values: ${err instanceof Error ? err.message : String(err)}`, exitCode);
|
|
567
|
+
}
|
|
568
|
+
let entityTypeResponse;
|
|
569
|
+
try {
|
|
570
|
+
entityTypeResponse = await getEntityType(runtime, entityResponse.entity.type);
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
throw wrapEntityTypeLookupError(entityResponse.entity.type, err, "resolve property and alias values");
|
|
574
|
+
}
|
|
575
|
+
const values = {};
|
|
576
|
+
if (rawProperties.length > 0) {
|
|
577
|
+
values.properties = parseEntityProperties(entityResponse.entity.type, rawProperties, entityTypeResponse.entity_type.properties);
|
|
578
|
+
}
|
|
579
|
+
if (rawAliases.length > 0) {
|
|
580
|
+
values.aliases = parseEntityAliases(entityResponse.entity.type, rawAliases, entityTypeResponse.entity_type.aliases);
|
|
516
581
|
}
|
|
517
|
-
return
|
|
582
|
+
return values;
|
|
518
583
|
}
|
|
519
584
|
function getEntityMutationOptionValues(options) {
|
|
520
585
|
return {
|
|
@@ -549,13 +614,15 @@ function buildEntityMutationBody(identifier, params) {
|
|
|
549
614
|
body.owner_user_ids = params.owner_user_ids;
|
|
550
615
|
if (params.properties !== undefined)
|
|
551
616
|
body.properties = params.properties;
|
|
617
|
+
if (params.aliases !== undefined)
|
|
618
|
+
body.aliases = params.aliases;
|
|
552
619
|
return body;
|
|
553
620
|
}
|
|
554
|
-
function wrapEntityTypeLookupError(entityTypeIdentifier, err) {
|
|
621
|
+
function wrapEntityTypeLookupError(entityTypeIdentifier, err, purpose = "resolve property types") {
|
|
555
622
|
const exitCode = err instanceof HttpError && err.status !== undefined && err.status < 500
|
|
556
623
|
? EXIT_CODES.ARGUMENT_ERROR
|
|
557
624
|
: EXIT_CODES.RETRY_RECOMMENDED;
|
|
558
|
-
return new CliError(`Failed to fetch entity type "${entityTypeIdentifier}" to
|
|
625
|
+
return new CliError(`Failed to fetch entity type "${entityTypeIdentifier}" to ${purpose}: ${err instanceof Error ? err.message : String(err)}`, exitCode);
|
|
559
626
|
}
|
|
560
627
|
function uniqueSectionsInOrder(sections) {
|
|
561
628
|
const seen = new Set();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
import { renderRichText } from "../../renderers.js";
|
|
3
3
|
import * as ui from "../../ui.js";
|
|
4
|
-
export function renderEntityList(entities, nextCursor) {
|
|
4
|
+
export function renderEntityList(entities, nextCursor, runtime) {
|
|
5
5
|
const blocks = [ui.h1("Entities")];
|
|
6
6
|
blocks.push(ui.p(`Displaying ${ui.bold(entities.length.toString())} entities.`));
|
|
7
7
|
if (nextCursor) {
|
|
@@ -19,7 +19,7 @@ export function renderEntityList(entities, nextCursor) {
|
|
|
19
19
|
}
|
|
20
20
|
if (entity.identifier) {
|
|
21
21
|
blocks.push(ui.h3("Core attributes"));
|
|
22
|
-
blocks.push(...coreContent(entity));
|
|
22
|
+
blocks.push(...coreContent(entity, runtime));
|
|
23
23
|
}
|
|
24
24
|
if (entity.owner_teams || entity.owner_users) {
|
|
25
25
|
blocks.push(ui.h3("Owners"));
|
|
@@ -36,11 +36,11 @@ export function renderEntityList(entities, nextCursor) {
|
|
|
36
36
|
}
|
|
37
37
|
renderRichText(blocks);
|
|
38
38
|
}
|
|
39
|
-
export function renderEntity(entity, title = "Entity Information") {
|
|
39
|
+
export function renderEntity(entity, runtime, title = "Entity Information") {
|
|
40
40
|
renderRichText([
|
|
41
41
|
ui.h1(title),
|
|
42
42
|
entity.identifier
|
|
43
|
-
? [ui.h2("Core attributes"), ...coreContent(entity)]
|
|
43
|
+
? [ui.h2("Core attributes"), ...coreContent(entity, runtime)]
|
|
44
44
|
: null,
|
|
45
45
|
entity.owner_teams ? [ui.h2("Owners"), ...ownersContent(entity)] : null,
|
|
46
46
|
entity.properties
|
|
@@ -55,7 +55,7 @@ export function renderEntityDeleted(entity) {
|
|
|
55
55
|
ui.p(`The entity ${ui.code(entity.identifier)} has been deleted.`),
|
|
56
56
|
]);
|
|
57
57
|
}
|
|
58
|
-
export function renderEntityScorecardList(scorecards, nextCursor) {
|
|
58
|
+
export function renderEntityScorecardList(scorecards, entityIdentifier, runtime, nextCursor) {
|
|
59
59
|
const blocks = [ui.h1("Scorecards")];
|
|
60
60
|
blocks.push(ui.p(`Displaying ${ui.bold(scorecards.length.toString())} scorecards.`));
|
|
61
61
|
if (nextCursor) {
|
|
@@ -63,7 +63,7 @@ export function renderEntityScorecardList(scorecards, nextCursor) {
|
|
|
63
63
|
}
|
|
64
64
|
for (const scorecard of scorecards) {
|
|
65
65
|
blocks.push(ui.h2(scorecard.name));
|
|
66
|
-
blocks.push(...scorecardReportContent(scorecard));
|
|
66
|
+
blocks.push(...scorecardReportContent(scorecard, entityIdentifier, runtime));
|
|
67
67
|
}
|
|
68
68
|
renderRichText(blocks);
|
|
69
69
|
}
|
|
@@ -79,8 +79,9 @@ export function renderEntityTaskList(tasks, nextCursor) {
|
|
|
79
79
|
}
|
|
80
80
|
renderRichText(blocks);
|
|
81
81
|
}
|
|
82
|
-
function scorecardReportContent(scorecard) {
|
|
82
|
+
function scorecardReportContent(scorecard, entityIdentifier, runtime) {
|
|
83
83
|
const blocks = [];
|
|
84
|
+
blocks.push(ui.p(ui.link(ui.webLink(`/catalog/${entityIdentifier}/scorecards?expanded=${scorecard.id}`, runtime))));
|
|
84
85
|
if (scorecard.type === "POINTS") {
|
|
85
86
|
const total = scorecard.points_meta?.points_total ?? 0;
|
|
86
87
|
const achieved = scorecard.points_meta?.points_achieved ?? 0;
|
|
@@ -179,7 +180,7 @@ function dueDateText(completeBy) {
|
|
|
179
180
|
return formattedDate;
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
|
-
function coreContent(entity) {
|
|
183
|
+
function coreContent(entity, runtime) {
|
|
183
184
|
return [
|
|
184
185
|
ui.dl([
|
|
185
186
|
ui.dli("Name", [ui.p(entity.name ?? ui.dim("(None)"), false)]),
|
|
@@ -188,7 +189,10 @@ function coreContent(entity) {
|
|
|
188
189
|
ui.dli("Created", [
|
|
189
190
|
ui.p(ui.timestampSummary(entity.created_at), false),
|
|
190
191
|
]),
|
|
191
|
-
ui.dli("Last updated",
|
|
192
|
+
ui.dli("Last updated", ui.timestampSummary(entity.updated_at)),
|
|
193
|
+
ui.dli("Web link", [
|
|
194
|
+
ui.p(ui.link(ui.webLink(`/catalog/${entity.identifier}`, runtime))),
|
|
195
|
+
]),
|
|
192
196
|
ui.dli("Description", [
|
|
193
197
|
ui.p(entity.description ?? ui.dim("(None)"), false),
|
|
194
198
|
]),
|
|
@@ -53,7 +53,7 @@ export function entityTypesCommand() {
|
|
|
53
53
|
renderJson({ ok: true, entity_type: response.entity_type });
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
|
-
renderEntityType(response.entity_type, `${ui.success(ui.GLYPHS.CHECK)} Entity type created`);
|
|
56
|
+
renderEntityType(response.entity_type, runtime, `${ui.success(ui.GLYPHS.CHECK)} Entity type created`);
|
|
57
57
|
}
|
|
58
58
|
}));
|
|
59
59
|
entityTypes
|
|
@@ -106,7 +106,7 @@ export function entityTypesCommand() {
|
|
|
106
106
|
renderJson(processedEntityType);
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
renderEntityType(processedEntityType);
|
|
109
|
+
renderEntityType(processedEntityType, runtime);
|
|
110
110
|
}
|
|
111
111
|
}));
|
|
112
112
|
entityTypes
|
|
@@ -200,7 +200,7 @@ export function entityTypesCommand() {
|
|
|
200
200
|
renderJson({ ...response, entity_types: processedEntityTypes });
|
|
201
201
|
}
|
|
202
202
|
else {
|
|
203
|
-
renderEntityTypeList(processedEntityTypes, response.response_metadata?.next_cursor ?? null);
|
|
203
|
+
renderEntityTypeList(processedEntityTypes, response.response_metadata?.next_cursor ?? null, runtime);
|
|
204
204
|
}
|
|
205
205
|
}));
|
|
206
206
|
entityTypes
|
|
@@ -245,7 +245,7 @@ export function entityTypesCommand() {
|
|
|
245
245
|
renderJson({ ok: true, entity_type: response.entity_type });
|
|
246
246
|
}
|
|
247
247
|
else {
|
|
248
|
-
renderEntityType(response.entity_type, `${ui.success(ui.GLYPHS.CHECK)} Entity type updated`);
|
|
248
|
+
renderEntityType(response.entity_type, runtime, `${ui.success(ui.GLYPHS.CHECK)} Entity type updated`);
|
|
249
249
|
}
|
|
250
250
|
}));
|
|
251
251
|
return entityTypes;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { renderRichText } from "../../renderers.js";
|
|
2
2
|
import * as ui from "../../ui.js";
|
|
3
|
-
export function renderEntityTypeList(entityTypes, nextCursor) {
|
|
3
|
+
export function renderEntityTypeList(entityTypes, nextCursor, runtime) {
|
|
4
4
|
const blocks = [ui.h1("Entity Types")];
|
|
5
5
|
blocks.push(ui.p(`Displaying ${ui.bold(entityTypes.length.toString())} entity types.`));
|
|
6
6
|
if (nextCursor) {
|
|
@@ -18,7 +18,7 @@ export function renderEntityTypeList(entityTypes, nextCursor) {
|
|
|
18
18
|
}
|
|
19
19
|
if (entityType.identifier) {
|
|
20
20
|
blocks.push(ui.h3("Core attributes"));
|
|
21
|
-
blocks.push(...coreContent(entityType));
|
|
21
|
+
blocks.push(...coreContent(entityType, runtime));
|
|
22
22
|
}
|
|
23
23
|
if (entityType.properties) {
|
|
24
24
|
blocks.push(ui.h3("Properties"));
|
|
@@ -31,11 +31,11 @@ export function renderEntityTypeList(entityTypes, nextCursor) {
|
|
|
31
31
|
}
|
|
32
32
|
renderRichText(blocks);
|
|
33
33
|
}
|
|
34
|
-
export function renderEntityType(entityType, title = "Entity Type Information") {
|
|
34
|
+
export function renderEntityType(entityType, runtime, title = "Entity Type Information") {
|
|
35
35
|
renderRichText([
|
|
36
36
|
ui.h1(title),
|
|
37
37
|
entityType.identifier
|
|
38
|
-
? [ui.h2("Core attributes"), ...coreContent(entityType)]
|
|
38
|
+
? [ui.h2("Core attributes"), ...coreContent(entityType, runtime)]
|
|
39
39
|
: null,
|
|
40
40
|
entityType.properties
|
|
41
41
|
? [ui.h2("Properties"), ...propertiesContent(entityType.properties)]
|
|
@@ -51,25 +51,19 @@ export function renderEntityTypeDeleted(entityType) {
|
|
|
51
51
|
ui.p(`The entity type ${ui.code(entityType.identifier)} has been deleted.`),
|
|
52
52
|
]);
|
|
53
53
|
}
|
|
54
|
-
function coreContent(entityType) {
|
|
54
|
+
function coreContent(entityType, runtime) {
|
|
55
55
|
return [
|
|
56
56
|
ui.dl([
|
|
57
|
-
ui.dli("Name",
|
|
58
|
-
ui.dli("Identifier",
|
|
59
|
-
ui.dli("Description",
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
ui.dli("
|
|
63
|
-
ui.dli("
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
ui.p(ui.timestampSummary(entityType.created_at), false),
|
|
68
|
-
]),
|
|
69
|
-
ui.dli("Last updated", [
|
|
70
|
-
ui.p(ui.timestampSummary(entityType.updated_at)),
|
|
71
|
-
]),
|
|
72
|
-
], { termWidth: 14 }),
|
|
57
|
+
ui.dli("Name", entityType.name ?? ui.dim("(None)")),
|
|
58
|
+
ui.dli("Identifier", ui.code(entityType.identifier)),
|
|
59
|
+
ui.dli("Description", entityType.description || ui.dim("(None)")),
|
|
60
|
+
ui.dli("Icon", entityType.icon ?? ui.dim("(None)")),
|
|
61
|
+
ui.dli("Ordering", entityType.ordering?.toString() ?? ui.dim("(None)")),
|
|
62
|
+
ui.dli("Created", ui.timestampSummary(entityType.created_at)),
|
|
63
|
+
ui.dli("Last updated", ui.timestampSummary(entityType.updated_at)),
|
|
64
|
+
ui.dli("Entities link", ui.link(ui.webLink(`/catalog?type=${entityType.identifier}`, runtime))),
|
|
65
|
+
ui.dli("Settings link", ui.link(ui.webLink(`/catalog/manage/entitytypes/${entityType.identifier}`, runtime))),
|
|
66
|
+
], { termWidth: 15 }),
|
|
73
67
|
];
|
|
74
68
|
}
|
|
75
69
|
function propertiesContent(properties) {
|