@get-dx/cli 0.1.3 → 0.1.5

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
  }
@@ -45,7 +47,7 @@ function createProgram() {
45
47
  */
46
48
  function applyExitOverride(cmd) {
47
49
  cmd.exitOverride();
48
- cmd.configureOutput({ writeErr: () => { } });
50
+ cmd.configureOutput({ outputError: () => { } });
49
51
  for (const sub of cmd.commands) {
50
52
  applyExitOverride(sub);
51
53
  }
@@ -58,6 +58,10 @@ export function handleError(error, command, argv) {
58
58
  if (error instanceof HttpError) {
59
59
  process.stderr.write(`The API returned an error with status code ${error.status}:\n\n${JSON.stringify(error.body, null, 2)}\n`);
60
60
  }
61
+ else if (error instanceof CommanderError &&
62
+ (error.code === "commander.help" || error.message === "(outputHelp)")) {
63
+ // Commander already printed the relevant help text.
64
+ }
61
65
  else {
62
66
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
63
67
  }
@@ -160,7 +160,7 @@ async function optionallySetupSkill(runtime) {
160
160
  renderRichText([
161
161
  ui.p(`The DX skill is not installed. Would you like to install it?`),
162
162
  ui.p("This will run the following command:", false),
163
- ui.codeBlock("npx --yes -- skills@latest add get-dx/dx-cli --global"),
163
+ ui.codeBlock("npx --yes -- skills@latest add get-dx/cli --global"),
164
164
  ui.blankLine(),
165
165
  ]);
166
166
  const setupSkill = await confirm({
@@ -174,17 +174,18 @@ async function optionallySetupSkill(runtime) {
174
174
  return;
175
175
  }
176
176
  renderRichText([ui.p(`Setting up the DX skill...`)]);
177
+ // NOTE: if the user chooses "no" for the final confirmation prompt, `skills` still exits with a 0 status.
178
+ // We are also not able to pipe the output without also breaking interactivity.
177
179
  const result = await execa({
178
180
  stdout: "inherit",
179
181
  stderr: "inherit",
180
182
  stdin: "inherit",
181
- }) `npx --yes -- skills@latest add get-dx/dx-cli --global`;
183
+ }) `npx --yes -- skills@latest add get-dx/cli --global`;
182
184
  runtime.logger.debug("To update: npx skills update dx-cli --global");
183
185
  runtime.logger.debug("To uninstall: npx skills remove dx-cli --global");
184
186
  if (result.exitCode !== 0) {
185
187
  throw new CliError(`Failed to setup the DX skill: ${result.stderr}`);
186
188
  }
187
- renderRichText([ui.p(`DX skill setup successful.`)]);
188
189
  }
189
190
  function createEmptyContext() {
190
191
  return {
@@ -21,6 +21,7 @@ export function queryCommand() {
21
21
  .description("Execute a Data Studio SQL query and wait for the results")
22
22
  .argument("<sql>", "SQL query to execute")
23
23
  .option("--output <filename>", "Save the full result set as CSV to a file")
24
+ .option("--variable <key=value>", "Set a template variable referenced in the SQL (can be repeated; use comma-separated values for arrays)", (val, prev) => [...prev, val], [])
24
25
  .addHelpText("afterAll", createExampleText([
25
26
  {
26
27
  label: "Run a query and print the results as a table",
@@ -28,21 +29,30 @@ export function queryCommand() {
28
29
  },
29
30
  {
30
31
  label: "Run a query and print the JSON results payload",
31
- command: "dx studio query 'SELECT id, name FROM github_repos LIMIT 5' --json",
32
+ command: "dx studio query 'SELECT id, name FROM github_repositories LIMIT 5' --json",
32
33
  },
33
34
  {
34
35
  label: "Run a query and save the full result set as CSV",
35
36
  command: "dx studio query 'SELECT * FROM github_pulls' --output pulls.csv",
36
37
  },
38
+ {
39
+ label: "Run a parameterized query with a scalar variable",
40
+ command: "dx studio query 'SELECT * FROM github_repositories WHERE name = $name' --variable name=my-repo",
41
+ },
42
+ {
43
+ label: "Run a parameterized query with an array variable",
44
+ command: "dx studio query 'SELECT * FROM github_repositories WHERE id IN ($repo_ids)' --variable repo_ids=1,2,3",
45
+ },
37
46
  ]))
38
47
  .action(wrapAction(async (sql, options, command) => {
39
48
  const context = getContext(command);
40
49
  if (context.json && options.output) {
41
50
  throw new CliError("--output cannot be used with --json", EXIT_CODES.ARGUMENT_ERROR);
42
51
  }
52
+ const variables = parseVariables(options.variable);
43
53
  const runtime = buildRuntime(context);
44
54
  const progress = new QueryProgressReporter();
45
- const queryRun = await executeQuery(runtime, progress, sql);
55
+ const queryRun = await executeQuery(runtime, progress, sql, variables);
46
56
  await waitForQuery(runtime, progress, queryRun.id);
47
57
  if (options.output) {
48
58
  await downloadStudioQueryResultsCsv(runtime, queryRun.id, options.output);
@@ -58,11 +68,11 @@ export function queryCommand() {
58
68
  }
59
69
  }));
60
70
  }
61
- async function executeQuery(runtime, progress, sql) {
71
+ async function executeQuery(runtime, progress, sql, variables) {
62
72
  try {
63
73
  renderRunningQuery(sql);
64
74
  progress.start(ui.bold("Submitting query"));
65
- const executeResponse = await executeStudioQuery(runtime, sql);
75
+ const executeResponse = await executeStudioQuery(runtime, sql, variables);
66
76
  const queryRun = executeResponse.body.query_run;
67
77
  await waitForRetryAfter(executeResponse.retryAfterMs);
68
78
  return queryRun;
@@ -115,10 +125,10 @@ function renderRunningQuery(sql) {
115
125
  function renderPendingQueryStatus(queryRunId) {
116
126
  return `${ui.bold("Query running")} ${ui.dim(`(${queryRunId})`)}`;
117
127
  }
118
- async function executeStudioQuery(runtime, sql) {
128
+ async function executeStudioQuery(runtime, sql, variables) {
119
129
  return request(runtime, "/studio.queryRuns.execute", {
120
130
  method: "POST",
121
- body: { sql },
131
+ body: variables ? { sql, variables } : { sql },
122
132
  });
123
133
  }
124
134
  async function getStudioQueryRun(runtime, id) {
@@ -262,6 +272,24 @@ async function writeResponseBodyToFile(response, filename) {
262
272
  reader.releaseLock();
263
273
  }
264
274
  }
275
+ function parseVariables(vars) {
276
+ if (vars.length === 0)
277
+ return undefined;
278
+ const result = {};
279
+ for (const v of vars) {
280
+ const eqIdx = v.indexOf("=");
281
+ if (eqIdx === -1) {
282
+ throw new CliError(`Invalid variable format: "${v}". Expected key=value.`, EXIT_CODES.ARGUMENT_ERROR);
283
+ }
284
+ const key = v.slice(0, eqIdx);
285
+ const raw = v.slice(eqIdx + 1);
286
+ const parts = raw.includes(",") ? raw.split(",") : [raw];
287
+ const coerced = parts.map((p) => p !== "" && !isNaN(Number(p)) ? Number(p) : p);
288
+ result[key] =
289
+ coerced.length === 1 ? coerced[0] : coerced;
290
+ }
291
+ return result;
292
+ }
265
293
  function buildFailedQueryError(error) {
266
294
  if (!error) {
267
295
  return new CliError("Query failed.", 1);
@@ -0,0 +1,69 @@
1
+ import { Command } from "commander";
2
+ import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
3
+ import { request } from "../http.js";
4
+ import { renderJson, renderRichText } from "../renderers.js";
5
+ import { buildRuntime } from "../runtime.js";
6
+ import * as ui from "../ui.js";
7
+ export function teamsCommand() {
8
+ const teams = new Command().name("teams").description("Manage DX teams");
9
+ teams
10
+ .command("list")
11
+ .description("List all teams in DX")
12
+ .addHelpText("afterAll", createExampleText([
13
+ {
14
+ label: "List all teams",
15
+ command: "dx teams list",
16
+ },
17
+ {
18
+ label: "List all teams as JSON",
19
+ command: "dx --json teams list",
20
+ },
21
+ ]))
22
+ .action(wrapAction(async (_options, command) => {
23
+ const runtime = buildRuntime(getContext(command));
24
+ const response = await listTeams(runtime);
25
+ if (runtime.context.json) {
26
+ renderJson(response);
27
+ }
28
+ else {
29
+ renderTeams(response.teams);
30
+ }
31
+ }));
32
+ return teams;
33
+ }
34
+ async function listTeams(runtime) {
35
+ const response = await request(runtime, "/teams.list", {
36
+ method: "GET",
37
+ });
38
+ return response.body;
39
+ }
40
+ function renderTeams(teams) {
41
+ const blocks = [ui.h1("Teams")];
42
+ blocks.push(ui.p(`Displaying ${ui.bold(teams.length.toString())} teams.`));
43
+ for (const team of teams) {
44
+ blocks.push(ui.h2(`${team.name} (${ui.code(team.id)})`));
45
+ blocks.push(ui.dl([
46
+ ui.dli("Reference ID", formatText(team.reference_id)),
47
+ ui.dli("Parent ID", formatText(team.parent_id)),
48
+ ui.dli("Manager ID", formatText(team.manager_id)),
49
+ ui.dli("Ancestors", team.ancestors && team.ancestors.length > 0
50
+ ? team.ancestors.map((ancestor) => ui.code(ancestor)).join(", ")
51
+ : ui.dim("(None)")),
52
+ ui.dli("Contributors", typeof team.contributors === "number"
53
+ ? team.contributors.toString()
54
+ : ui.dim("(Unknown)")),
55
+ ui.dli("Parent team", typeof team.parent === "boolean"
56
+ ? team.parent
57
+ ? "Yes"
58
+ : "No"
59
+ : ui.dim("(Unknown)")),
60
+ ui.dli("Last changed", team.last_changed_at
61
+ ? ui.timestampSummary(team.last_changed_at)
62
+ : ui.dim("(None)")),
63
+ ], { termWidth: 14 }));
64
+ }
65
+ renderRichText(blocks);
66
+ }
67
+ function formatText(value) {
68
+ return value ? ui.code(value) : ui.dim("(None)");
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-dx/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "DX CLI",
5
5
  "author": "DX <developers@getdx.com>",
6
6
  "bugs": {
@@ -16,7 +16,7 @@
16
16
  "homepage": "https://github.com/get-dx/cli#readme",
17
17
  "license": "Apache-2.0",
18
18
  "engines": {
19
- "node": "20.x"
19
+ "node": "^20.0.0 || ^22.0.0 || ^24.0.0"
20
20
  },
21
21
  "private": false,
22
22
  "publishConfig": {