@get-dx/cli 0.5.5 → 0.6.1
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 +2 -1
- package/dist/commandHelpers.js +39 -0
- package/dist/commands/studio/query.js +3 -0
- package/dist/commands/studio/report-blank-template.yaml +5 -0
- package/dist/commands/studio/reports.js +9 -3
- package/dist/config.js +20 -4
- package/dist/http.js +3 -0
- package/dist/loginViaBrowser.js +10 -1
- package/dist/versionCheck.js +12 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -42,9 +42,10 @@ function createProgram() {
|
|
|
42
42
|
program
|
|
43
43
|
.name("dx")
|
|
44
44
|
.description("DX CLI")
|
|
45
|
-
.version(cliPackage.version)
|
|
45
|
+
.version(cliPackage.version, "-v, --version")
|
|
46
46
|
.addOption(new Option("--json", "Print machine-readable JSON"))
|
|
47
47
|
.addOption(new Option("--agent <name>", "Agent name to send as an HTTP header"))
|
|
48
|
+
.addOption(new Option("--agent-model <model>", "Agent model name to send as an HTTP header"))
|
|
48
49
|
.addOption(new Option("--agent-session-id <id>", "Agent session ID to send as an HTTP header"));
|
|
49
50
|
program.addCommand(authCommand());
|
|
50
51
|
program.addCommand(catalogCommand());
|
package/dist/commandHelpers.js
CHANGED
|
@@ -6,6 +6,7 @@ export function getContext(command) {
|
|
|
6
6
|
return {
|
|
7
7
|
json: Boolean(root.json),
|
|
8
8
|
agent: root.agent || process.env.DX_AGENT_NAME,
|
|
9
|
+
agentModel: root.agentModel || process.env.DX_AGENT_MODEL,
|
|
9
10
|
agentSessionId: root.agentSessionId || process.env.DX_AGENT_SESSION_ID,
|
|
10
11
|
};
|
|
11
12
|
}
|
|
@@ -101,6 +102,44 @@ export function createExampleText(examples) {
|
|
|
101
102
|
}
|
|
102
103
|
return lines.join("\n");
|
|
103
104
|
}
|
|
105
|
+
export function createNoteText(paragraphs) {
|
|
106
|
+
const width = getHelpWidth();
|
|
107
|
+
const indent = " ";
|
|
108
|
+
const output = [];
|
|
109
|
+
output.push(""); // separate from the rest of the help text
|
|
110
|
+
output.push("Notes:");
|
|
111
|
+
paragraphs.forEach((paragraph, index) => {
|
|
112
|
+
if (index > 0) {
|
|
113
|
+
output.push(""); // blank line between paragraphs
|
|
114
|
+
}
|
|
115
|
+
output.push(...wrapParagraph(paragraph, width, indent));
|
|
116
|
+
});
|
|
117
|
+
return output.join("\n");
|
|
118
|
+
}
|
|
119
|
+
function getHelpWidth() {
|
|
120
|
+
return process.stdout.isTTY && process.stdout.columns
|
|
121
|
+
? process.stdout.columns
|
|
122
|
+
: 80;
|
|
123
|
+
}
|
|
124
|
+
function wrapParagraph(text, width, indent) {
|
|
125
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
126
|
+
const lines = [];
|
|
127
|
+
let current = "";
|
|
128
|
+
for (const word of words) {
|
|
129
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
130
|
+
if (current && indent.length + candidate.length > width) {
|
|
131
|
+
lines.push(`${indent}${current}`);
|
|
132
|
+
current = word;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
current = candidate;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (current) {
|
|
139
|
+
lines.push(`${indent}${current}`);
|
|
140
|
+
}
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
104
143
|
function inferContext(command, argv) {
|
|
105
144
|
if (command) {
|
|
106
145
|
return getContext(command);
|
|
@@ -170,6 +170,9 @@ function buildHeaders(runtime, accept) {
|
|
|
170
170
|
if (runtime.context.agent) {
|
|
171
171
|
headers.set("X-DX-Agent-Name", runtime.context.agent);
|
|
172
172
|
}
|
|
173
|
+
if (runtime.context.agentModel) {
|
|
174
|
+
headers.set("X-DX-Agent-Model", runtime.context.agentModel);
|
|
175
|
+
}
|
|
173
176
|
if (runtime.context.agentSessionId) {
|
|
174
177
|
headers.set("X-DX-Agent-Session-Id", runtime.context.agentSessionId);
|
|
175
178
|
}
|
|
@@ -22,6 +22,11 @@ edit_access_type: read_only
|
|
|
22
22
|
editor_emails: []
|
|
23
23
|
|
|
24
24
|
# Supported chart_type values: line, pie, stacked_bar, scatter, table
|
|
25
|
+
#
|
|
26
|
+
# Tile sql can reference report variables (e.g. $team_ids, $start_date, or a
|
|
27
|
+
# custom variable) as $variable_name. Variables themselves can't be managed
|
|
28
|
+
# from the CLI/API — enable built-in variables or add/edit custom ones for
|
|
29
|
+
# this report in the Data Studio UI, then reference them here.
|
|
25
30
|
tiles:
|
|
26
31
|
- title: ""
|
|
27
32
|
sql: |-
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
|
-
import { createExampleText, getContext, parsePositiveIntOption, wrapAction, } from "../../commandHelpers.js";
|
|
4
|
+
import { createExampleText, createNoteText, getContext, parsePositiveIntOption, wrapAction, } from "../../commandHelpers.js";
|
|
5
5
|
import { CliError, EXIT_CODES, HttpError } from "../../errors.js";
|
|
6
6
|
import { request } from "../../http.js";
|
|
7
7
|
import { renderJson, renderRichText } from "../../renderers.js";
|
|
8
8
|
import { buildRuntime } from "../../runtime.js";
|
|
9
9
|
import * as ui from "../../ui.js";
|
|
10
|
+
const VARIABLES_NOTE_TEXT = [
|
|
11
|
+
"Tile SQL can reference report variables using `$variable_name` syntax. Built-in variables — $service_ids, $team_ids, $tag_ids, $user_ids, $repo_ids, $start_date, $end_date — filter by service, team, attribute, user, repo, and date range, respectively. Custom variables are account-defined dropdown filters backed by a SQL query.",
|
|
12
|
+
"Variables cannot be created, updated, enabled, or disabled via this CLI or the API. Built-in variables must be toggled, and custom variables must be added, edited, or removed, from the report's settings in the Data Studio UI. Once a variable is enabled on a report, its `$variable_name` can be used in any tile's SQL via --from-file/--from-stdin.",
|
|
13
|
+
];
|
|
10
14
|
export function reportsCommand() {
|
|
11
15
|
const reports = new Command()
|
|
12
16
|
.name("reports")
|
|
@@ -16,6 +20,7 @@ export function reportsCommand() {
|
|
|
16
20
|
.description("Create a Data Studio report from a YAML file or stdin")
|
|
17
21
|
.option("--from-file <path>", "Read a YAML file and create a report from its contents")
|
|
18
22
|
.option("--from-stdin", "Read YAML from stdin and create a report from its contents")
|
|
23
|
+
.addHelpText("afterAll", createNoteText(VARIABLES_NOTE_TEXT))
|
|
19
24
|
.addHelpText("afterAll", createExampleText([
|
|
20
25
|
{
|
|
21
26
|
label: "Create a studio report from a YAML file",
|
|
@@ -174,6 +179,7 @@ export function reportsCommand() {
|
|
|
174
179
|
.argument("<id>", "Studio report ID")
|
|
175
180
|
.option("--from-file <path>", "Read a YAML file and update the report with its contents")
|
|
176
181
|
.option("--from-stdin", "Read YAML from stdin and update the report with its contents")
|
|
182
|
+
.addHelpText("afterAll", createNoteText(VARIABLES_NOTE_TEXT))
|
|
177
183
|
.addHelpText("afterAll", createExampleText([
|
|
178
184
|
{
|
|
179
185
|
label: "Initialize a file first",
|
|
@@ -361,9 +367,9 @@ function studioReportToYaml(report) {
|
|
|
361
367
|
description: report.description ?? "",
|
|
362
368
|
markdown_notes: report.markdown_notes ?? "",
|
|
363
369
|
view_access_type: report.view_access_type,
|
|
364
|
-
viewer_emails: [],
|
|
370
|
+
viewer_emails: report.viewer_emails ?? [],
|
|
365
371
|
edit_access_type: report.edit_access_type,
|
|
366
|
-
editor_emails: [],
|
|
372
|
+
editor_emails: report.editor_emails ?? [],
|
|
367
373
|
tiles: report.tiles.map((tile) => ({
|
|
368
374
|
id: tile.id,
|
|
369
375
|
title: tile.title,
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { CliError, EXIT_CODES } from "./errors.js";
|
|
5
|
-
const DEFAULT_API_BASE_URL = "https://api.getdx.com";
|
|
5
|
+
export const DEFAULT_API_BASE_URL = "https://api.getdx.com";
|
|
6
6
|
const DEFAULT_WEB_BASE_URL = "https://app.getdx.com";
|
|
7
7
|
// --- Config file I/O ---------------------------------------------------------
|
|
8
8
|
export function getConfigPath() {
|
|
@@ -14,7 +14,16 @@ export function readConfig() {
|
|
|
14
14
|
return {};
|
|
15
15
|
}
|
|
16
16
|
const content = fs.readFileSync(configPath, "utf8");
|
|
17
|
-
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = JSON.parse(content);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// The config file can be left empty or truncated if a previous `dx`
|
|
23
|
+
// process was killed mid-write, or two processes wrote concurrently.
|
|
24
|
+
// Treat it like a missing config rather than crashing every command.
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
18
27
|
const stored = {};
|
|
19
28
|
if (typeof raw.webBaseUrl === "string") {
|
|
20
29
|
stored.webBaseUrl = raw.webBaseUrl;
|
|
@@ -46,8 +55,15 @@ export function persistVersionPromptSelection(prompt) {
|
|
|
46
55
|
writeConfig(config);
|
|
47
56
|
}
|
|
48
57
|
export function writeConfig(config) {
|
|
49
|
-
|
|
50
|
-
fs.
|
|
58
|
+
const configDir = getConfigDir();
|
|
59
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
60
|
+
const configPath = getConfigPath();
|
|
61
|
+
// Write to a temp file and rename into place so a crash or a race between
|
|
62
|
+
// concurrent `dx` processes can't leave config.json empty or truncated —
|
|
63
|
+
// renames are atomic, so readers always see either the old or new content.
|
|
64
|
+
const tempPath = path.join(configDir, `.config.json.${process.pid}.${Date.now()}.tmp`);
|
|
65
|
+
fs.writeFileSync(tempPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
66
|
+
fs.renameSync(tempPath, configPath);
|
|
51
67
|
}
|
|
52
68
|
export function persistBaseUrls(apiBaseUrl, webBaseUrl) {
|
|
53
69
|
writeConfig({
|
package/dist/http.js
CHANGED
|
@@ -14,6 +14,9 @@ export async function request(runtime, route, options = {}) {
|
|
|
14
14
|
if (runtime.context.agent) {
|
|
15
15
|
headers.set("X-DX-Agent-Name", runtime.context.agent);
|
|
16
16
|
}
|
|
17
|
+
if (runtime.context.agentModel) {
|
|
18
|
+
headers.set("X-DX-Agent-Model", runtime.context.agentModel);
|
|
19
|
+
}
|
|
17
20
|
if (runtime.context.agentSessionId) {
|
|
18
21
|
headers.set("X-DX-Agent-Session-Id", runtime.context.agentSessionId);
|
|
19
22
|
}
|
package/dist/loginViaBrowser.js
CHANGED
|
@@ -49,7 +49,16 @@ export async function loginViaBrowser(webBaseUrl) {
|
|
|
49
49
|
error: new CliError(`Authentication failed: token exchange failed with status ${tokenExchangeResponse.status}`),
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
let body;
|
|
53
|
+
try {
|
|
54
|
+
body = (await tokenExchangeResponse.json());
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {
|
|
58
|
+
type: "ERROR",
|
|
59
|
+
error: new CliError("Authentication failed: token exchange returned an invalid response"),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
53
62
|
return {
|
|
54
63
|
type: "SUCCESS",
|
|
55
64
|
token: body.access_token,
|
package/dist/versionCheck.js
CHANGED
|
@@ -11,6 +11,7 @@ import cliPackage from "../package.json" with { type: "json" };
|
|
|
11
11
|
const VERSION_CHECK_INTERVAL_HOURS = 24;
|
|
12
12
|
const SNOOZE_DAYS = 7;
|
|
13
13
|
const SKIP_COMMANDS = new Set(["auth", "init"]);
|
|
14
|
+
const SKIP_OPTIONS = new Set(["-h", "--help", "-v", "--version"]);
|
|
14
15
|
/**
|
|
15
16
|
* Compares two semver strings (e.g. "1.2.3").
|
|
16
17
|
* Returns a negative number if a < b, 0 if equal, positive if a > b.
|
|
@@ -67,13 +68,21 @@ export async function checkForNewVersion(argv) {
|
|
|
67
68
|
if (process.env.DX_DISABLE_VERSION_CHECK) {
|
|
68
69
|
return { status: "disabled" };
|
|
69
70
|
}
|
|
70
|
-
const
|
|
71
|
+
const args = argv.slice(2);
|
|
72
|
+
if (args.some((arg) => SKIP_OPTIONS.has(arg))) {
|
|
73
|
+
return { status: "disabled" };
|
|
74
|
+
}
|
|
75
|
+
const topLevelCommand = args.find((arg) => !arg.startsWith("-"));
|
|
76
|
+
if (!topLevelCommand) {
|
|
77
|
+
return { status: "disabled" };
|
|
78
|
+
}
|
|
71
79
|
if (topLevelCommand && SKIP_COMMANDS.has(topLevelCommand)) {
|
|
72
80
|
return { status: "disabled" };
|
|
73
81
|
}
|
|
74
82
|
const context = {
|
|
75
83
|
json: argv.includes("--json"),
|
|
76
84
|
agent: undefined,
|
|
85
|
+
agentModel: undefined,
|
|
77
86
|
agentSessionId: undefined,
|
|
78
87
|
};
|
|
79
88
|
const runtime = await buildRuntimeSafe(context);
|
|
@@ -137,6 +146,8 @@ export async function promptVersionUpdate(latestVersion) {
|
|
|
137
146
|
{ name: `Remind me later (in ${SNOOZE_DAYS} days)`, value: "snooze" },
|
|
138
147
|
{ name: "Skip this version", value: "skip" },
|
|
139
148
|
],
|
|
149
|
+
}, {
|
|
150
|
+
output: process.stderr,
|
|
140
151
|
});
|
|
141
152
|
if (choice === "update") {
|
|
142
153
|
persistVersionPromptSelection(undefined);
|