@get-dx/cli 0.1.4 → 0.1.6

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 CHANGED
@@ -4,6 +4,7 @@ import { catalogCommand } from "./commands/catalog.js";
4
4
  import { initCommand } from "./commands/init.js";
5
5
  import { scorecardsCommand } from "./commands/scorecards.js";
6
6
  import { studioCommand } from "./commands/studio.js";
7
+ import { teamsCommand } from "./commands/teams.js";
7
8
  import { handleError } from "./commandHelpers.js";
8
9
  import cliPackage from "../package.json" with { type: "json" };
9
10
  export async function run(argv = process.argv) {
@@ -33,6 +34,7 @@ function createProgram() {
33
34
  program.addCommand(initCommand());
34
35
  program.addCommand(scorecardsCommand());
35
36
  program.addCommand(studioCommand());
37
+ program.addCommand(teamsCommand());
36
38
  applyExitOverride(program);
37
39
  return program;
38
40
  }
@@ -9,6 +9,13 @@ export function renderAuthInfo(authInfo, token, baseUrl) {
9
9
  ui.dli("Token type", tokenTypeName(authInfo.auth.token_type)),
10
10
  ui.dli("Token name", authInfo.auth.token_name),
11
11
  ui.dli("Token created at", ui.timestampSummary(authInfo.auth.created_at)),
12
+ ...(authInfo.auth.token_type === "personal_access_token"
13
+ ? [
14
+ ui.dli("Token expires at", authInfo.auth.expires_at
15
+ ? ui.timestampSummary(authInfo.auth.expires_at)
16
+ : ui.dim("(no expiration)")),
17
+ ]
18
+ : []),
12
19
  ], { termWidth: 18 }),
13
20
  ui.p(ui.bold("Token scopes:"), false),
14
21
  ...scopesContent(authInfo.auth.scopes),
@@ -0,0 +1,161 @@
1
+ import { Command } from "commander";
2
+ import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
3
+ import { CliError, EXIT_CODES, HttpError } from "../errors.js";
4
+ import { request } from "../http.js";
5
+ import { renderJson, renderRichText } from "../renderers.js";
6
+ import { buildRuntime } from "../runtime.js";
7
+ import * as ui from "../ui.js";
8
+ export function teamsCommand() {
9
+ const teams = new Command().name("teams").description("Manage DX teams");
10
+ teams
11
+ .command("findByMembers")
12
+ .description("Find a team by member email addresses")
13
+ .option("--team-emails <emails>", "Comma-separated list of team member email addresses")
14
+ .addHelpText("afterAll", createExampleText([
15
+ {
16
+ label: "Find the team for a group of members",
17
+ command: "dx teams findByMembers --team-emails person@example.com,another@example.com",
18
+ },
19
+ {
20
+ label: "Find a team and return JSON",
21
+ command: "dx --json teams findByMembers --team-emails person@example.com,another@example.com",
22
+ },
23
+ ]))
24
+ .action(wrapAction(async (options, command) => {
25
+ const teamEmails = parseTeamEmails(options.teamEmails);
26
+ const runtime = buildRuntime(getContext(command));
27
+ const response = await findTeamByMembers(runtime, teamEmails);
28
+ if (runtime.context.json) {
29
+ renderJson(response);
30
+ }
31
+ else {
32
+ renderTeamByMembers(response.team);
33
+ }
34
+ }));
35
+ teams
36
+ .command("list")
37
+ .description("List all teams in DX")
38
+ .addHelpText("afterAll", createExampleText([
39
+ {
40
+ label: "List all teams",
41
+ command: "dx teams list",
42
+ },
43
+ {
44
+ label: "List all teams as JSON",
45
+ command: "dx --json teams list",
46
+ },
47
+ ]))
48
+ .action(wrapAction(async (_options, command) => {
49
+ const runtime = buildRuntime(getContext(command));
50
+ const response = await listTeams(runtime);
51
+ if (runtime.context.json) {
52
+ renderJson(response);
53
+ }
54
+ else {
55
+ renderTeams(response.teams);
56
+ }
57
+ }));
58
+ return teams;
59
+ }
60
+ async function findTeamByMembers(runtime, teamEmails) {
61
+ try {
62
+ const response = await request(runtime, "/teams.findByMembers", {
63
+ method: "GET",
64
+ query: { team_emails: teamEmails },
65
+ });
66
+ return response.body;
67
+ }
68
+ catch (error) {
69
+ if (runtime.context.json) {
70
+ throw error;
71
+ }
72
+ throw buildFindByMembersError(error, teamEmails);
73
+ }
74
+ }
75
+ async function listTeams(runtime) {
76
+ const response = await request(runtime, "/teams.list", {
77
+ method: "GET",
78
+ });
79
+ return response.body;
80
+ }
81
+ function renderTeamByMembers(team) {
82
+ const blocks = [
83
+ ui.h1("Team"),
84
+ ui.h2(`${team.name} (${ui.code(team.id)})`),
85
+ ui.dl([
86
+ ui.dli("Manager", formatTeamMember(team.manager)),
87
+ ui.dli("Members", team.members.length > 0
88
+ ? [
89
+ ui.ul(team.members.map((member) => ui.li(formatTeamMember(member)))),
90
+ ]
91
+ : ui.dim("(None)")),
92
+ ], { termWidth: 8 }),
93
+ ];
94
+ renderRichText(blocks);
95
+ }
96
+ function renderTeams(teams) {
97
+ const blocks = [ui.h1("Teams")];
98
+ blocks.push(ui.p(`Displaying ${ui.bold(teams.length.toString())} teams.`));
99
+ for (const team of teams) {
100
+ blocks.push(ui.h2(`${team.name} (${ui.code(team.id)})`));
101
+ blocks.push(ui.dl([
102
+ ui.dli("Reference ID", formatText(team.reference_id)),
103
+ ui.dli("Parent ID", formatText(team.parent_id)),
104
+ ui.dli("Manager ID", formatText(team.manager_id)),
105
+ ui.dli("Ancestors", team.ancestors && team.ancestors.length > 0
106
+ ? team.ancestors.map((ancestor) => ui.code(ancestor)).join(", ")
107
+ : ui.dim("(None)")),
108
+ ui.dli("Contributors", typeof team.contributors === "number"
109
+ ? team.contributors.toString()
110
+ : ui.dim("(Unknown)")),
111
+ ui.dli("Parent team", typeof team.parent === "boolean"
112
+ ? team.parent
113
+ ? "Yes"
114
+ : "No"
115
+ : ui.dim("(Unknown)")),
116
+ ui.dli("Last changed", team.last_changed_at
117
+ ? ui.timestampSummary(team.last_changed_at)
118
+ : ui.dim("(None)")),
119
+ ], { termWidth: 14 }));
120
+ }
121
+ renderRichText(blocks);
122
+ }
123
+ function formatText(value) {
124
+ return value ? ui.code(value) : ui.dim("(None)");
125
+ }
126
+ function formatTeamMember(member) {
127
+ if (!member) {
128
+ return ui.dim("(None)");
129
+ }
130
+ return `${member.name} <${member.email}> (${ui.code(member.id)})`;
131
+ }
132
+ function parseTeamEmails(value) {
133
+ if (typeof value !== "string") {
134
+ throw new CliError("--team-emails is required", EXIT_CODES.ARGUMENT_ERROR);
135
+ }
136
+ const emails = value
137
+ .split(",")
138
+ .map((email) => email.trim())
139
+ .filter((email) => email.length > 0);
140
+ if (emails.length === 0) {
141
+ throw new CliError("--team-emails must include at least one email address", EXIT_CODES.ARGUMENT_ERROR);
142
+ }
143
+ return emails.join(",");
144
+ }
145
+ function buildFindByMembersError(error, teamEmails) {
146
+ if (!(error instanceof HttpError)) {
147
+ return error instanceof Error ? error : new Error(String(error));
148
+ }
149
+ const apiError = getApiErrorCode(error.body);
150
+ if (apiError === "no_users_found") {
151
+ return new CliError(`No active DX users were found for the provided email addresses: ${teamEmails}.\n\nCheck the spelling and make sure these users exist and are active in the DX account for your API token.`, EXIT_CODES.ARGUMENT_ERROR);
152
+ }
153
+ return error;
154
+ }
155
+ function getApiErrorCode(body) {
156
+ if (!body || typeof body !== "object") {
157
+ return undefined;
158
+ }
159
+ const error = body.error;
160
+ return typeof error === "string" ? error : undefined;
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-dx/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "DX CLI",
5
5
  "author": "DX <developers@getdx.com>",
6
6
  "bugs": {