@get-dx/cli 0.4.0 → 0.5.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 +14 -0
- package/dist/commands/auth.js +32 -3
- package/dist/commands/authRendering.js +42 -1
- package/dist/commands/init.js +4 -9
- package/dist/config.js +25 -6
- package/dist/skill.js +9 -0
- package/dist/versionCheck.js +203 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { workflowsCommand } from "./commands/workflows.js";
|
|
|
11
11
|
import { handleError } from "./commandHelpers.js";
|
|
12
12
|
import cliPackage from "../package.json" with { type: "json" };
|
|
13
13
|
import { handleTemporaryBaseUrlMigration } from "./config.js";
|
|
14
|
+
import { checkForNewVersion, performUpdate, promptVersionUpdate, } from "./versionCheck.js";
|
|
14
15
|
export async function run(argv = process.argv) {
|
|
15
16
|
try {
|
|
16
17
|
handleTemporaryBaseUrlMigration(argv);
|
|
@@ -19,7 +20,20 @@ export async function run(argv = process.argv) {
|
|
|
19
20
|
program.outputHelp();
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
23
|
+
let versionCheckResult = { shouldUpdate: false };
|
|
24
|
+
try {
|
|
25
|
+
const versionStatus = await checkForNewVersion(argv);
|
|
26
|
+
if (versionStatus.status === "available") {
|
|
27
|
+
versionCheckResult = await promptVersionUpdate(versionStatus.latestVersion);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Never let a failure with version check or upgrade prompt block a command
|
|
32
|
+
}
|
|
22
33
|
await program.parseAsync(argv);
|
|
34
|
+
if (versionCheckResult.shouldUpdate && versionCheckResult.latestVersion) {
|
|
35
|
+
await performUpdate(versionCheckResult.latestVersion);
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
38
|
catch (error) {
|
|
25
39
|
handleError(error, undefined, argv);
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,7 +2,7 @@ import { password, select } from "@inquirer/prompts";
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { deleteToken, setToken } from "../secrets.js";
|
|
4
4
|
import { renderJson } from "../renderers.js";
|
|
5
|
-
import { renderAuthInfo, renderLoggedOut } from "./authRendering.js";
|
|
5
|
+
import { renderAuthInfo, renderAuthWhoami, renderLoggedOut, } from "./authRendering.js";
|
|
6
6
|
import { createExampleText, getContext, wrapAction, } from "../commandHelpers.js";
|
|
7
7
|
import { persistBaseUrls, resolveApiBaseUrl, deriveBaseUrlsFromEnv, } from "../config.js";
|
|
8
8
|
import { CliError } from "../errors.js";
|
|
@@ -51,12 +51,12 @@ export function authCommand() {
|
|
|
51
51
|
}
|
|
52
52
|
else {
|
|
53
53
|
token = await password({
|
|
54
|
-
message: "Paste your
|
|
54
|
+
message: "Paste your personal access token or organization token here:",
|
|
55
55
|
mask: true,
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
if (!token) {
|
|
59
|
-
throw new CliError("
|
|
59
|
+
throw new CliError("Personal access token or organization token is required");
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
const runtime = await buildRuntime(context, {
|
|
@@ -128,6 +128,29 @@ export function authCommand() {
|
|
|
128
128
|
renderAuthInfo(response, runtime.token, runtime.apiBaseUrl);
|
|
129
129
|
}
|
|
130
130
|
}));
|
|
131
|
+
auth
|
|
132
|
+
.command("whoami")
|
|
133
|
+
.description("Show user and team details for the authenticated token")
|
|
134
|
+
.addHelpText("afterAll", createExampleText([
|
|
135
|
+
{
|
|
136
|
+
label: "Fetch user and team details",
|
|
137
|
+
command: "dx auth whoami",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: "Fetch user and team details as JSON",
|
|
141
|
+
command: "dx auth whoami --json",
|
|
142
|
+
},
|
|
143
|
+
]))
|
|
144
|
+
.action(wrapAction(async (_options, command) => {
|
|
145
|
+
const runtime = await buildRuntime(getContext(command));
|
|
146
|
+
const response = await getAuthWhoami(runtime);
|
|
147
|
+
if (runtime.context.json) {
|
|
148
|
+
renderJson(response);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
renderAuthWhoami(response);
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
131
154
|
return auth;
|
|
132
155
|
}
|
|
133
156
|
// TODO: should we move this somewhere more central, since `init` needs it?
|
|
@@ -137,3 +160,9 @@ export async function getAuthInfo(runtime) {
|
|
|
137
160
|
});
|
|
138
161
|
return response.body;
|
|
139
162
|
}
|
|
163
|
+
export async function getAuthWhoami(runtime) {
|
|
164
|
+
const response = await request(runtime, "/auth.whoami", {
|
|
165
|
+
method: "GET",
|
|
166
|
+
});
|
|
167
|
+
return response.body;
|
|
168
|
+
}
|
|
@@ -37,10 +37,51 @@ function scopesContent(scopes) {
|
|
|
37
37
|
];
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
export function renderAuthWhoami(response) {
|
|
41
|
+
const blocks = [];
|
|
42
|
+
// Auth section
|
|
43
|
+
blocks.push(ui.h3("Auth"));
|
|
44
|
+
blocks.push(ui.dl([
|
|
45
|
+
ui.dli("Account", response.account.name),
|
|
46
|
+
ui.dli("Token type", tokenTypeName(response.auth_token_type)),
|
|
47
|
+
], { termWidth: 12 }));
|
|
48
|
+
// User section
|
|
49
|
+
blocks.push(ui.h3("Current user"));
|
|
50
|
+
if (response.user) {
|
|
51
|
+
blocks.push(ui.dl([
|
|
52
|
+
ui.dli("ID", ui.code(response.user.id)),
|
|
53
|
+
ui.dli("Name", response.user.name),
|
|
54
|
+
ui.dli("Email", response.user.email),
|
|
55
|
+
], { termWidth: 7 }));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
blocks.push(ui.p(ui.dim("Not available for organization tokens")));
|
|
59
|
+
}
|
|
60
|
+
// Team section
|
|
61
|
+
blocks.push(ui.h3("Team"));
|
|
62
|
+
if (response.team) {
|
|
63
|
+
blocks.push(ui.dl([
|
|
64
|
+
ui.dli("ID", ui.code(response.team.id)),
|
|
65
|
+
ui.dli("Name", response.team.name),
|
|
66
|
+
ui.dli("Lead", formatUser(response.team.lead)),
|
|
67
|
+
], { termWidth: 6 }));
|
|
68
|
+
if (response.team.contributors.length > 0) {
|
|
69
|
+
blocks.push(ui.p(ui.bold("Contributors:"), false));
|
|
70
|
+
blocks.push(ui.ul(response.team.contributors.map((c) => ui.li(formatUser(c)))));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
blocks.push(ui.p(ui.dim("Not available for organization tokens")));
|
|
75
|
+
}
|
|
76
|
+
renderRichText(blocks);
|
|
77
|
+
}
|
|
78
|
+
function formatUser(user) {
|
|
79
|
+
return `${user.name} ${ui.dim(`(${user.id}, ${user.email})`)}`;
|
|
80
|
+
}
|
|
40
81
|
function tokenTypeName(tokenType) {
|
|
41
82
|
switch (tokenType) {
|
|
42
83
|
case "account_web_api_token":
|
|
43
|
-
return "
|
|
84
|
+
return "Organization token";
|
|
44
85
|
case "personal_access_token":
|
|
45
86
|
return "Personal access token";
|
|
46
87
|
default:
|
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
1
|
import { password, confirm, select } from "@inquirer/prompts";
|
|
5
2
|
import { Command } from "commander";
|
|
6
3
|
import { execa } from "execa";
|
|
@@ -13,6 +10,7 @@ import { renderRichText } from "../renderers.js";
|
|
|
13
10
|
import * as ui from "../ui.js";
|
|
14
11
|
import { CliError } from "../errors.js";
|
|
15
12
|
import { deriveBaseUrlsFromEnv, persistBaseUrls } from "../config.js";
|
|
13
|
+
import { isSkillInstalled } from "../skill.js";
|
|
16
14
|
import { setToken } from "../secrets.js";
|
|
17
15
|
import { renderWelcomeAnimation } from "../welcomeAnimation.js";
|
|
18
16
|
export function initCommand() {
|
|
@@ -85,12 +83,12 @@ async function attemptLogin(apiBaseUrl, webBaseUrl, context) {
|
|
|
85
83
|
}
|
|
86
84
|
else {
|
|
87
85
|
token = await password({
|
|
88
|
-
message: "Paste your
|
|
86
|
+
message: "Paste your personal access token or organization token here:",
|
|
89
87
|
mask: true,
|
|
90
88
|
});
|
|
91
89
|
}
|
|
92
90
|
if (!token) {
|
|
93
|
-
throw new CliError("
|
|
91
|
+
throw new CliError("Personal access token or organization token is required");
|
|
94
92
|
}
|
|
95
93
|
const runtime = await buildRuntime(context, {
|
|
96
94
|
apiBaseUrl,
|
|
@@ -113,10 +111,7 @@ async function optionallySetupSkill(runtime) {
|
|
|
113
111
|
ui.h1(`Checking for the DX skill...`),
|
|
114
112
|
ui.blankLine(),
|
|
115
113
|
]);
|
|
116
|
-
const
|
|
117
|
-
const skillInstalled = await access(skillPath)
|
|
118
|
-
.then(() => true)
|
|
119
|
-
.catch(() => false);
|
|
114
|
+
const skillInstalled = await isSkillInstalled();
|
|
120
115
|
if (skillInstalled) {
|
|
121
116
|
renderRichText([ui.p(`The DX skill is already installed.`)]);
|
|
122
117
|
runtime.logger.debug("To update: npx skills update dx-cli --global");
|
package/dist/config.js
CHANGED
|
@@ -15,17 +15,36 @@ export function readConfig() {
|
|
|
15
15
|
}
|
|
16
16
|
const content = fs.readFileSync(configPath, "utf8");
|
|
17
17
|
const raw = JSON.parse(content);
|
|
18
|
-
const api = raw.apiBaseUrl;
|
|
19
|
-
const web = raw.webBaseUrl;
|
|
20
18
|
const stored = {};
|
|
21
|
-
if (
|
|
22
|
-
stored.webBaseUrl =
|
|
19
|
+
if (typeof raw.webBaseUrl === "string") {
|
|
20
|
+
stored.webBaseUrl = raw.webBaseUrl;
|
|
21
|
+
}
|
|
22
|
+
if (typeof raw.apiBaseUrl === "string") {
|
|
23
|
+
stored.apiBaseUrl = raw.apiBaseUrl;
|
|
24
|
+
}
|
|
25
|
+
if (raw.currentVersionCache && typeof raw.currentVersionCache === "object") {
|
|
26
|
+
stored.currentVersionCache = raw.currentVersionCache;
|
|
23
27
|
}
|
|
24
|
-
if (
|
|
25
|
-
|
|
28
|
+
if (raw.versionPromptSelection &&
|
|
29
|
+
typeof raw.versionPromptSelection === "object") {
|
|
30
|
+
stored.versionPromptSelection =
|
|
31
|
+
raw.versionPromptSelection;
|
|
26
32
|
}
|
|
27
33
|
return stored;
|
|
28
34
|
}
|
|
35
|
+
export function persistCurrentVersionCache(cache) {
|
|
36
|
+
writeConfig({ ...readConfig(), currentVersionCache: cache });
|
|
37
|
+
}
|
|
38
|
+
export function persistVersionPromptSelection(prompt) {
|
|
39
|
+
const config = readConfig();
|
|
40
|
+
if (prompt === undefined) {
|
|
41
|
+
delete config.versionPromptSelection;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
config.versionPromptSelection = prompt;
|
|
45
|
+
}
|
|
46
|
+
writeConfig(config);
|
|
47
|
+
}
|
|
29
48
|
/**
|
|
30
49
|
* Responsible for handling the migration from `baseUrl` to `apiBaseUrl` and `webBaseUrl`,
|
|
31
50
|
* so the upgrade to 0.3.x will be non-breaking for `cloud` and `dedicated` deployments.
|
package/dist/skill.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export async function isSkillInstalled() {
|
|
5
|
+
const skillPath = join(homedir(), ".agents", "skills", "dx-cli");
|
|
6
|
+
return access(skillPath)
|
|
7
|
+
.then(() => true)
|
|
8
|
+
.catch(() => false);
|
|
9
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { select } from "@inquirer/prompts";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import { persistCurrentVersionCache, persistVersionPromptSelection, readConfig, } from "./config.js";
|
|
5
|
+
import { request } from "./http.js";
|
|
6
|
+
import { renderRichText } from "./renderers.js";
|
|
7
|
+
import { buildRuntimeSafe } from "./runtime.js";
|
|
8
|
+
import { isSkillInstalled } from "./skill.js";
|
|
9
|
+
import * as ui from "./ui.js";
|
|
10
|
+
import cliPackage from "../package.json" with { type: "json" };
|
|
11
|
+
const VERSION_CHECK_INTERVAL_HOURS = 24;
|
|
12
|
+
const SNOOZE_DAYS = 7;
|
|
13
|
+
const SKIP_COMMANDS = new Set(["auth", "init"]);
|
|
14
|
+
/**
|
|
15
|
+
* Compares two semver strings (e.g. "1.2.3").
|
|
16
|
+
* Returns a negative number if a < b, 0 if equal, positive if a > b.
|
|
17
|
+
*/
|
|
18
|
+
export function compareVersions(a, b) {
|
|
19
|
+
const parse = (v) => v.split(".").map((n) => parseInt(n, 10));
|
|
20
|
+
const [aMajor = 0, aMinor = 0, aPatch = 0] = parse(a);
|
|
21
|
+
const [bMajor = 0, bMinor = 0, bPatch = 0] = parse(b);
|
|
22
|
+
return aMajor - bMajor || aMinor - bMinor || aPatch - bPatch;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns true if a version fetch should be attempted — i.e. no cached result
|
|
26
|
+
* exists or the cached result is older than VERSION_CHECK_INTERVAL_HOURS.
|
|
27
|
+
*/
|
|
28
|
+
export function shouldPerformVersionCheck(config) {
|
|
29
|
+
const cache = config.currentVersionCache;
|
|
30
|
+
if (!cache?.updatedAt) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const age = dayjs().diff(dayjs(cache.updatedAt), "hour");
|
|
34
|
+
return age >= VERSION_CHECK_INTERVAL_HOURS;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns true if the upgrade prompt should be shown to the user, taking into
|
|
38
|
+
* account any previously persisted SKIP or SNOOZE state.
|
|
39
|
+
*/
|
|
40
|
+
export function shouldShowVersionPrompt(latestVersion, versionPromptSelection) {
|
|
41
|
+
if (!versionPromptSelection) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (versionPromptSelection.type === "SKIP" &&
|
|
45
|
+
versionPromptSelection.skipVersion === latestVersion) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (versionPromptSelection.type === "SNOOZE" &&
|
|
49
|
+
dayjs().isBefore(dayjs(versionPromptSelection.snoozeUntil))) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
async function fetchLatestVersion(runtime) {
|
|
55
|
+
const response = await request(runtime, "/cli.currentVersion", { method: "GET" });
|
|
56
|
+
return response.body.versions.cli;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether a newer CLI version is available.
|
|
60
|
+
*
|
|
61
|
+
* Returns:
|
|
62
|
+
* "available" — a newer version exists and the user should be prompted
|
|
63
|
+
* "up-to-date" — the installed version is current (or the user has snoozed/skipped)
|
|
64
|
+
* "disabled" — the check was skipped or could not run (env var, skip command, no token, network error)
|
|
65
|
+
*/
|
|
66
|
+
export async function checkForNewVersion(argv) {
|
|
67
|
+
if (process.env.DX_DISABLE_VERSION_CHECK) {
|
|
68
|
+
return { status: "disabled" };
|
|
69
|
+
}
|
|
70
|
+
const topLevelCommand = argv.slice(2).find((arg) => !arg.startsWith("-"));
|
|
71
|
+
if (topLevelCommand && SKIP_COMMANDS.has(topLevelCommand)) {
|
|
72
|
+
return { status: "disabled" };
|
|
73
|
+
}
|
|
74
|
+
const context = {
|
|
75
|
+
json: argv.includes("--json"),
|
|
76
|
+
agent: undefined,
|
|
77
|
+
agentSessionId: undefined,
|
|
78
|
+
};
|
|
79
|
+
const runtime = await buildRuntimeSafe(context);
|
|
80
|
+
if (!runtime) {
|
|
81
|
+
return { status: "disabled" };
|
|
82
|
+
}
|
|
83
|
+
const config = readConfig();
|
|
84
|
+
let latestVersion;
|
|
85
|
+
if (!shouldPerformVersionCheck(config)) {
|
|
86
|
+
latestVersion = config.currentVersionCache.contents.cli;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
try {
|
|
90
|
+
latestVersion = await fetchLatestVersion(runtime);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { status: "disabled" };
|
|
94
|
+
}
|
|
95
|
+
persistCurrentVersionCache({
|
|
96
|
+
updatedAt: new Date().toISOString(),
|
|
97
|
+
contents: { cli: latestVersion },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (compareVersions(latestVersion, cliPackage.version) <= 0) {
|
|
101
|
+
return { status: "up-to-date" };
|
|
102
|
+
}
|
|
103
|
+
if (!shouldShowVersionPrompt(latestVersion, readConfig().versionPromptSelection)) {
|
|
104
|
+
return { status: "up-to-date" };
|
|
105
|
+
}
|
|
106
|
+
return { status: "available", latestVersion };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Notifies the user that a new version is available and, in interactive
|
|
110
|
+
* sessions, prompts them to update, snooze, or skip.
|
|
111
|
+
*
|
|
112
|
+
* Returns a result indicating whether the user chose to update immediately,
|
|
113
|
+
* so the caller can invoke performUpdate() after the command finishes.
|
|
114
|
+
*/
|
|
115
|
+
export async function promptVersionUpdate(latestVersion) {
|
|
116
|
+
const noUpdate = { shouldUpdate: false };
|
|
117
|
+
const currentVersion = cliPackage.version;
|
|
118
|
+
const isInteractive = process.stdin.isTTY && process.stderr.isTTY;
|
|
119
|
+
if (!isInteractive) {
|
|
120
|
+
renderRichText([
|
|
121
|
+
ui.blankLine(),
|
|
122
|
+
ui.p(ui.warning(`A new version of the DX CLI is available: ${currentVersion} → ${latestVersion}`)),
|
|
123
|
+
ui.p(`Run: ${ui.code(`npm install -g @get-dx/cli@${latestVersion}`)}`),
|
|
124
|
+
ui.blankLine(),
|
|
125
|
+
], { useStderr: true });
|
|
126
|
+
return noUpdate;
|
|
127
|
+
}
|
|
128
|
+
renderRichText([
|
|
129
|
+
ui.blankLine(),
|
|
130
|
+
ui.p(ui.warning(`A new version of the DX CLI is available: ${currentVersion} → ${latestVersion}`)),
|
|
131
|
+
ui.blankLine(),
|
|
132
|
+
], { useStderr: true });
|
|
133
|
+
const choice = await select({
|
|
134
|
+
message: "What would you like to do?",
|
|
135
|
+
choices: [
|
|
136
|
+
{ name: "Update now", value: "update" },
|
|
137
|
+
{ name: `Remind me later (in ${SNOOZE_DAYS} days)`, value: "snooze" },
|
|
138
|
+
{ name: "Skip this version", value: "skip" },
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
if (choice === "update") {
|
|
142
|
+
persistVersionPromptSelection(undefined);
|
|
143
|
+
return { shouldUpdate: true, latestVersion };
|
|
144
|
+
}
|
|
145
|
+
if (choice === "snooze") {
|
|
146
|
+
persistVersionPromptSelection({
|
|
147
|
+
type: "SNOOZE",
|
|
148
|
+
snoozeUntil: dayjs().add(SNOOZE_DAYS, "day").toISOString(),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
persistVersionPromptSelection({ type: "SKIP", skipVersion: latestVersion });
|
|
153
|
+
}
|
|
154
|
+
return noUpdate;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Performs the actual CLI + skill upgrade after the user's command has finished.
|
|
158
|
+
*/
|
|
159
|
+
export async function performUpdate(latestVersion) {
|
|
160
|
+
if (await isSkillInstalled()) {
|
|
161
|
+
renderRichText([
|
|
162
|
+
ui.blankLine(),
|
|
163
|
+
ui.p(ui.bold("Updating the DX skill.")),
|
|
164
|
+
ui.p("This will run the following command:", false),
|
|
165
|
+
ui.codeBlock("npx --yes -- skills@latest update dx-cli --global"),
|
|
166
|
+
ui.blankLine(),
|
|
167
|
+
], {
|
|
168
|
+
useStderr: true,
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
await execa({
|
|
172
|
+
stdout: "inherit",
|
|
173
|
+
stderr: "inherit",
|
|
174
|
+
stdin: "inherit",
|
|
175
|
+
}) `npx --yes -- skills@latest update dx-cli --global`;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
renderRichText([
|
|
179
|
+
ui.p(ui.warning("Warning: failed to update the DX skill. You can update it manually with: " +
|
|
180
|
+
ui.code("npx skills update dx-cli --global"))),
|
|
181
|
+
], { useStderr: true });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
renderRichText([
|
|
185
|
+
ui.blankLine(),
|
|
186
|
+
ui.p(ui.bold(`Updating the DX CLI to ${latestVersion}.`)),
|
|
187
|
+
ui.p("This will run the following command:", false),
|
|
188
|
+
ui.codeBlock(`npm install -g @get-dx/cli@${latestVersion}`),
|
|
189
|
+
ui.blankLine(),
|
|
190
|
+
], { useStderr: true });
|
|
191
|
+
try {
|
|
192
|
+
await execa({
|
|
193
|
+
stdout: "inherit",
|
|
194
|
+
stderr: "inherit",
|
|
195
|
+
}) `npm install -g @get-dx/cli@${latestVersion}`;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
renderRichText([
|
|
199
|
+
ui.p(ui.warning(`Warning: failed to update the DX CLI. You can update it manually with: ` +
|
|
200
|
+
ui.code(`npm install -g @get-dx/cli@${latestVersion}`))),
|
|
201
|
+
], { useStderr: true });
|
|
202
|
+
}
|
|
203
|
+
}
|