@aetherpush/cli 0.4.2 → 0.5.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 +22 -6
- package/bin/script/auth/browser-launcher.d.ts +9 -0
- package/bin/script/auth/browser-launcher.js +58 -0
- package/bin/script/auth/browser-launcher.js.map +1 -0
- package/bin/script/auth/browser-login.d.ts +32 -0
- package/bin/script/auth/browser-login.js +215 -0
- package/bin/script/auth/browser-login.js.map +1 -0
- package/bin/script/auth/key-store.d.ts +24 -0
- package/bin/script/auth/key-store.js +193 -0
- package/bin/script/auth/key-store.js.map +1 -0
- package/bin/script/auth/loopback-listener.d.ts +19 -0
- package/bin/script/auth/loopback-listener.js +111 -0
- package/bin/script/auth/loopback-listener.js.map +1 -0
- package/bin/script/auth/pkce.d.ts +12 -0
- package/bin/script/auth/pkce.js +18 -0
- package/bin/script/auth/pkce.js.map +1 -0
- package/bin/script/command-executor.js +122 -38
- package/bin/script/command-executor.js.map +1 -1
- package/bin/script/command-parser.js +17 -2
- package/bin/script/command-parser.js.map +1 -1
- package/bin/script/types/cli.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startLoopbackListener = startLoopbackListener;
|
|
4
|
+
const http = require("http");
|
|
5
|
+
const CALLBACK_PATH = "/callback";
|
|
6
|
+
/**
|
|
7
|
+
* A one-shot HTTP listener on the loopback interface that receives the
|
|
8
|
+
* authorization code the browser is redirected to.
|
|
9
|
+
*
|
|
10
|
+
* Bound to 127.0.0.1 rather than every interface, and on an ephemeral port the
|
|
11
|
+
* server pins to this ceremony, so nothing off this machine can reach it and a
|
|
12
|
+
* second `aether login` cannot collide with the first.
|
|
13
|
+
*/
|
|
14
|
+
function startLoopbackListener() {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
let settle = null;
|
|
17
|
+
let fail = null;
|
|
18
|
+
let received = null;
|
|
19
|
+
const server = http.createServer((req, res) => {
|
|
20
|
+
const requestUrl = new URL(req.url || "/", "http://127.0.0.1");
|
|
21
|
+
if (requestUrl.pathname !== CALLBACK_PATH) {
|
|
22
|
+
res.statusCode = 404;
|
|
23
|
+
res.end("Not found");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const result = {
|
|
27
|
+
code: requestUrl.searchParams.get("code") || "",
|
|
28
|
+
state: requestUrl.searchParams.get("state"),
|
|
29
|
+
error: requestUrl.searchParams.get("error"),
|
|
30
|
+
};
|
|
31
|
+
res.statusCode = 200;
|
|
32
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
33
|
+
res.end(renderPage(result));
|
|
34
|
+
received = result;
|
|
35
|
+
if (settle) {
|
|
36
|
+
settle(result);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
server.on("error", (err) => {
|
|
40
|
+
if (fail) {
|
|
41
|
+
fail(err);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
reject(err);
|
|
45
|
+
});
|
|
46
|
+
server.listen(0, "127.0.0.1", () => {
|
|
47
|
+
const address = server.address();
|
|
48
|
+
if (!address || typeof address === "string") {
|
|
49
|
+
server.close();
|
|
50
|
+
reject(new Error("The local callback listener did not report a port."));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
resolve({
|
|
54
|
+
redirectUri: `http://127.0.0.1:${address.port}${CALLBACK_PATH}`,
|
|
55
|
+
waitForCallback(timeoutMs) {
|
|
56
|
+
if (received) {
|
|
57
|
+
return Promise.resolve(received);
|
|
58
|
+
}
|
|
59
|
+
return new Promise((resolveWait, rejectWait) => {
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
settle = null;
|
|
62
|
+
fail = null;
|
|
63
|
+
rejectWait(new Error("Timed out waiting for the browser to complete authorization. Run 'aether login' again."));
|
|
64
|
+
}, timeoutMs);
|
|
65
|
+
settle = (result) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
settle = null;
|
|
68
|
+
fail = null;
|
|
69
|
+
resolveWait(result);
|
|
70
|
+
};
|
|
71
|
+
fail = (error) => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
settle = null;
|
|
74
|
+
fail = null;
|
|
75
|
+
rejectWait(error);
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
close() {
|
|
80
|
+
server.close();
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function renderPage(result) {
|
|
87
|
+
const heading = result.error ? "Authorization failed" : "You're signed in";
|
|
88
|
+
const detail = result.error
|
|
89
|
+
? "Nothing was authorized. Return to your terminal for the details."
|
|
90
|
+
: "You can close this tab and return to your terminal.";
|
|
91
|
+
return `<!doctype html>
|
|
92
|
+
<html lang="en">
|
|
93
|
+
<head>
|
|
94
|
+
<meta charset="utf-8" />
|
|
95
|
+
<title>${heading}</title>
|
|
96
|
+
<style>
|
|
97
|
+
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; display: grid; place-items: center; min-height: 100vh; background: #0b0b10; color: #e7e7ee; }
|
|
98
|
+
main { text-align: center; padding: 2rem; }
|
|
99
|
+
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
|
|
100
|
+
p { margin: 0; color: #9a9aab; }
|
|
101
|
+
</style>
|
|
102
|
+
</head>
|
|
103
|
+
<body>
|
|
104
|
+
<main>
|
|
105
|
+
<h1>${heading}</h1>
|
|
106
|
+
<p>${detail}</p>
|
|
107
|
+
</main>
|
|
108
|
+
</body>
|
|
109
|
+
</html>`;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=loopback-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-listener.js","sourceRoot":"","sources":["../../../script/auth/loopback-listener.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 7636 helpers. The verifier never leaves this machine; only its SHA-256
|
|
3
|
+
* challenge is sent when the ceremony starts, so an authorization code
|
|
4
|
+
* intercepted on the way back is useless without this process.
|
|
5
|
+
*/
|
|
6
|
+
export interface PkcePair {
|
|
7
|
+
verifier: string;
|
|
8
|
+
challenge: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function generatePkcePair(): PkcePair;
|
|
11
|
+
export declare function generateState(): string;
|
|
12
|
+
export declare function generateDeviceId(): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generatePkcePair = generatePkcePair;
|
|
4
|
+
exports.generateState = generateState;
|
|
5
|
+
exports.generateDeviceId = generateDeviceId;
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
function generatePkcePair() {
|
|
8
|
+
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
9
|
+
const challenge = crypto.createHash("sha256").update(verifier, "ascii").digest("base64url");
|
|
10
|
+
return { verifier, challenge };
|
|
11
|
+
}
|
|
12
|
+
function generateState() {
|
|
13
|
+
return crypto.randomBytes(16).toString("base64url");
|
|
14
|
+
}
|
|
15
|
+
function generateDeviceId() {
|
|
16
|
+
return crypto.randomUUID();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../script/auth/pkce.ts"],"names":[],"mappings":";;AAaA,4CAIC;AAED,sCAEC;AAED,4CAEC;AAzBD,iCAAiC;AAajC,SAAgB,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC5F,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED,SAAgB,aAAa;IAC3B,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACtD,CAAC;AAED,SAAgB,gBAAgB;IAC9B,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -26,7 +26,9 @@ const react_native_utils_1 = require("./react-native-utils");
|
|
|
26
26
|
const file_utils_1 = require("./utils/file-utils");
|
|
27
27
|
const ci_metadata_1 = require("./utils/ci-metadata");
|
|
28
28
|
const release_json_1 = require("./utils/release-json");
|
|
29
|
-
const
|
|
29
|
+
const keyStore = require("./auth/key-store");
|
|
30
|
+
const pkce_1 = require("./auth/pkce");
|
|
31
|
+
const browser_login_1 = require("./auth/browser-login");
|
|
30
32
|
const DEFAULT_AETHER_SERVER_URL = "https://api.aetherpush.com";
|
|
31
33
|
const emailValidator = require("email-validator");
|
|
32
34
|
const packageJson = require("../../package.json");
|
|
@@ -291,12 +293,16 @@ function removeCollaborator(command) {
|
|
|
291
293
|
}
|
|
292
294
|
function deleteConnectionInfoCache(printMessage = true) {
|
|
293
295
|
try {
|
|
294
|
-
|
|
296
|
+
keyStore.clearCredential();
|
|
295
297
|
if (printMessage) {
|
|
296
|
-
(0, exports.log)(`Logged out. The
|
|
298
|
+
(0, exports.log)(`Logged out. The credentials at ${chalk.cyan(keyStore.getCredentialPath())} have been deleted.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (ex) {
|
|
302
|
+
if (printMessage) {
|
|
303
|
+
(0, exports.log)(chalk.yellow(ex?.message || `Could not delete ${keyStore.getCredentialPath()}.`));
|
|
297
304
|
}
|
|
298
305
|
}
|
|
299
|
-
catch (ex) { }
|
|
300
306
|
}
|
|
301
307
|
function deleteFolder(folderPath) {
|
|
302
308
|
return new Promise((resolve, reject) => {
|
|
@@ -414,23 +420,25 @@ function deploymentHistory(command) {
|
|
|
414
420
|
});
|
|
415
421
|
}
|
|
416
422
|
function deserializeConnectionInfo() {
|
|
423
|
+
let stored;
|
|
417
424
|
try {
|
|
418
|
-
|
|
419
|
-
encoding: "utf8",
|
|
420
|
-
});
|
|
421
|
-
let connectionInfo = JSON.parse(savedConnection);
|
|
422
|
-
// If the connection info is in the legacy format, convert it to the modern format
|
|
423
|
-
if (connectionInfo.accessKeyName) {
|
|
424
|
-
connectionInfo = {
|
|
425
|
-
accessKey: connectionInfo.accessKeyName,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
const connInfo = connectionInfo;
|
|
429
|
-
return connInfo;
|
|
425
|
+
stored = keyStore.readCredential();
|
|
430
426
|
}
|
|
431
|
-
catch
|
|
427
|
+
catch {
|
|
428
|
+
// A home directory we cannot read is "not logged in", not a crash on every
|
|
429
|
+
// command.
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (!stored) {
|
|
432
433
|
return;
|
|
433
434
|
}
|
|
435
|
+
return {
|
|
436
|
+
accessKey: stored.accessKey,
|
|
437
|
+
customServerUrl: stored.serverUrl,
|
|
438
|
+
preserveAccessKeyOnLogout: stored.preserveAccessKeyOnLogout,
|
|
439
|
+
credentialId: stored.credentialId,
|
|
440
|
+
accountEmail: stored.accountEmail,
|
|
441
|
+
};
|
|
434
442
|
}
|
|
435
443
|
function execute(command) {
|
|
436
444
|
connectionInfo = deserializeConnectionInfo();
|
|
@@ -445,9 +453,6 @@ function execute(command) {
|
|
|
445
453
|
switch (command.type) {
|
|
446
454
|
// Must not be logged in
|
|
447
455
|
case cli.CommandType.login:
|
|
448
|
-
if (connectionInfo && !command.nonInteractive) {
|
|
449
|
-
throw new Error("You are already logged in from this machine.");
|
|
450
|
-
}
|
|
451
456
|
break;
|
|
452
457
|
case cli.CommandType.register:
|
|
453
458
|
if (connectionInfo) {
|
|
@@ -461,7 +466,7 @@ function execute(command) {
|
|
|
461
466
|
if (!connectionInfo) {
|
|
462
467
|
throw new Error("You are not currently logged in. Run 'aether login' to authenticate with Aether.");
|
|
463
468
|
}
|
|
464
|
-
exports.sdk = getSdk(connectionInfo.accessKey, CLI_HEADERS, connectionInfo.customServerUrl);
|
|
469
|
+
exports.sdk = getSdk(connectionInfo.accessKey, CLI_HEADERS, resolveServerUrl(connectionInfo.customServerUrl));
|
|
465
470
|
break;
|
|
466
471
|
}
|
|
467
472
|
switch (command.type) {
|
|
@@ -547,7 +552,7 @@ function getTotalActiveFromDeploymentMetrics(metrics) {
|
|
|
547
552
|
return totalActive;
|
|
548
553
|
}
|
|
549
554
|
async function login(command) {
|
|
550
|
-
const serverUrl = command.serverUrl || DEFAULT_AETHER_SERVER_URL;
|
|
555
|
+
const serverUrl = command.serverUrl || connectionInfo?.customServerUrl || DEFAULT_AETHER_SERVER_URL;
|
|
551
556
|
if (command.accessKey) {
|
|
552
557
|
exports.sdk = getSdk(command.accessKey, CLI_HEADERS, serverUrl);
|
|
553
558
|
const authenticated = await exports.sdk.isAuthenticated();
|
|
@@ -557,9 +562,26 @@ async function login(command) {
|
|
|
557
562
|
serializeConnectionInfo(command.accessKey, /*preserveAccessKeyOnLogout*/ true, serverUrl);
|
|
558
563
|
return;
|
|
559
564
|
}
|
|
565
|
+
if (connectionInfo && !command.nonInteractive) {
|
|
566
|
+
// A credential the server no longer accepts must not block signing in
|
|
567
|
+
// again: every other command already clears it on a 401.
|
|
568
|
+
const stillValid = await isStoredCredentialUsable(serverUrl);
|
|
569
|
+
if (stillValid) {
|
|
570
|
+
throw new Error("You are already logged in from this machine.");
|
|
571
|
+
}
|
|
572
|
+
(0, exports.log)(chalk.yellow("The stored session is no longer valid. Signing in again."));
|
|
573
|
+
deleteConnectionInfoCache(/*printMessage*/ false);
|
|
574
|
+
connectionInfo = null;
|
|
575
|
+
exports.sdk = null;
|
|
576
|
+
}
|
|
560
577
|
if (command.nonInteractive) {
|
|
561
578
|
throw new Error("Interactive login is unavailable in non-interactive mode. Re-run with --accessKey <key>.");
|
|
562
579
|
}
|
|
580
|
+
if (!command.password) {
|
|
581
|
+
await browserLogin(command, serverUrl);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
(0, exports.log)(chalk.yellow("Password sign-in is deprecated and does not work on accounts with multi-factor authentication. Run 'aether login' to sign in through your browser."));
|
|
563
585
|
const { email, password } = await promptForLoginCredentials();
|
|
564
586
|
if (!email) {
|
|
565
587
|
throw new Error("Email is required.");
|
|
@@ -584,8 +606,8 @@ async function login(command) {
|
|
|
584
606
|
throw new Error(body.error || body.message || `Login failed (HTTP ${res.status}).`);
|
|
585
607
|
}
|
|
586
608
|
if (body.mfaRequired) {
|
|
587
|
-
throw new Error("This account has multi-factor authentication enabled, which
|
|
588
|
-
"
|
|
609
|
+
throw new Error("This account has multi-factor authentication enabled, which password sign-in cannot complete. " +
|
|
610
|
+
"Run 'aether login' without --password to authorize this machine through your browser.");
|
|
589
611
|
}
|
|
590
612
|
const accessKey = body.accessKey;
|
|
591
613
|
if (!accessKey) {
|
|
@@ -595,10 +617,66 @@ async function login(command) {
|
|
|
595
617
|
serializeConnectionInfo(accessKey, /*preserveAccessKeyOnLogout*/ false, serverUrl);
|
|
596
618
|
(0, exports.log)(chalk.green(`Successfully logged in as ${email}.`));
|
|
597
619
|
}
|
|
598
|
-
function
|
|
620
|
+
async function browserLogin(command, serverUrl) {
|
|
621
|
+
const deviceId = keyStore.readOrCreateDeviceId(pkce_1.generateDeviceId);
|
|
622
|
+
const result = await (0, browser_login_1.runBrowserLogin)({
|
|
623
|
+
serverUrl,
|
|
624
|
+
deviceId,
|
|
625
|
+
deviceName: os.hostname(),
|
|
626
|
+
clientVersion: packageJson.version,
|
|
627
|
+
clientPlatform: `${process.platform}-${process.arch}`,
|
|
628
|
+
headers: CLI_HEADERS,
|
|
629
|
+
forceDeviceFlow: !!command.device,
|
|
630
|
+
log: (message) => (0, exports.log)(message),
|
|
631
|
+
});
|
|
632
|
+
exports.sdk = getSdk(result.accessKey, CLI_HEADERS, serverUrl);
|
|
633
|
+
serializeConnectionInfo(result.accessKey,
|
|
634
|
+
/*preserveAccessKeyOnLogout*/ false, command.serverUrl || connectionInfo?.customServerUrl, {
|
|
635
|
+
credentialId: result.credentialId,
|
|
636
|
+
deviceId,
|
|
637
|
+
accountEmail: result.email,
|
|
638
|
+
});
|
|
639
|
+
(0, exports.log)(chalk.green(`Signed in as ${result.email || "your Aether account"}.`));
|
|
640
|
+
(0, exports.log)("This device is now authorized.");
|
|
641
|
+
}
|
|
642
|
+
async function isStoredCredentialUsable(serverUrl) {
|
|
643
|
+
try {
|
|
644
|
+
const probe = getSdk(connectionInfo.accessKey, CLI_HEADERS, resolveServerUrl(connectionInfo.customServerUrl) || serverUrl);
|
|
645
|
+
return await probe.isAuthenticated();
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// Offline or unreachable: assume the stored session is still good rather
|
|
649
|
+
// than discarding a working credential because the network is down.
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async function logout(command) {
|
|
654
|
+
const current = connectionInfo;
|
|
655
|
+
if (current && current.credentialId) {
|
|
656
|
+
try {
|
|
657
|
+
await revokeCurrentDevice(current);
|
|
658
|
+
(0, exports.log)("This device is no longer authorized on the server.");
|
|
659
|
+
}
|
|
660
|
+
catch (err) {
|
|
661
|
+
(0, exports.log)(chalk.yellow(`Could not revoke this device on the server (${err?.message || "unknown error"}). ` +
|
|
662
|
+
"It can still be revoked from the dashboard under Account > CLI & Devices."));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
599
665
|
exports.sdk = null;
|
|
600
666
|
deleteConnectionInfoCache();
|
|
601
|
-
|
|
667
|
+
}
|
|
668
|
+
async function revokeCurrentDevice(current) {
|
|
669
|
+
const serverUrl = resolveServerUrl(current.customServerUrl).replace(/\/$/, "");
|
|
670
|
+
const response = await fetch(`${serverUrl}/v1/cli/devices/current`, {
|
|
671
|
+
method: "DELETE",
|
|
672
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${current.accessKey}`, ...CLI_HEADERS },
|
|
673
|
+
});
|
|
674
|
+
// 401 means the credential is already dead; 404 means this server has no such
|
|
675
|
+
// endpoint. Neither is worth alarming the user about on the way out.
|
|
676
|
+
if (response.status === 204 || response.status === 404 || response.status === 401) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
throw new Error(`HTTP ${response.status}`);
|
|
602
680
|
}
|
|
603
681
|
function formatDate(unixOffset) {
|
|
604
682
|
const date = moment(unixOffset);
|
|
@@ -1379,18 +1457,16 @@ const runReactNativeBundleCommand = (bundleName, development, entryFile, outputF
|
|
|
1379
1457
|
});
|
|
1380
1458
|
};
|
|
1381
1459
|
exports.runReactNativeBundleCommand = runReactNativeBundleCommand;
|
|
1382
|
-
function serializeConnectionInfo(accessKey, preserveAccessKeyOnLogout, customServerUrl) {
|
|
1383
|
-
|
|
1384
|
-
accessKey
|
|
1385
|
-
preserveAccessKeyOnLogout
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
fs.writeFileSync(configFilePath, json, { encoding: "utf8" });
|
|
1393
|
-
(0, exports.log)(`Session file written to ${chalk.cyan(configFilePath)}. Run ${chalk.cyan("aether logout")} to terminate the session.`);
|
|
1460
|
+
function serializeConnectionInfo(accessKey, preserveAccessKeyOnLogout, customServerUrl, extra) {
|
|
1461
|
+
keyStore.writeCredential({
|
|
1462
|
+
accessKey,
|
|
1463
|
+
preserveAccessKeyOnLogout,
|
|
1464
|
+
serverUrl: customServerUrl,
|
|
1465
|
+
credentialId: extra?.credentialId,
|
|
1466
|
+
deviceId: extra?.deviceId,
|
|
1467
|
+
accountEmail: extra?.accountEmail,
|
|
1468
|
+
});
|
|
1469
|
+
(0, exports.log)(`Credentials written to ${chalk.cyan(keyStore.getCredentialPath())}. Run ${chalk.cyan("aether logout")} to terminate the session.`);
|
|
1394
1470
|
}
|
|
1395
1471
|
function sessionList(command) {
|
|
1396
1472
|
throwForInvalidOutputFormat(command.format);
|
|
@@ -1448,6 +1524,14 @@ function whoami(command) {
|
|
|
1448
1524
|
function isCommandOptionSpecified(option) {
|
|
1449
1525
|
return option !== undefined && option !== null;
|
|
1450
1526
|
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Only an explicit --serverUrl is persisted, so an absent one means the default
|
|
1529
|
+
* rather than nothing. Falling through to the SDK's own default would point the
|
|
1530
|
+
* CLI at localhost.
|
|
1531
|
+
*/
|
|
1532
|
+
function resolveServerUrl(storedServerUrl) {
|
|
1533
|
+
return storedServerUrl || DEFAULT_AETHER_SERVER_URL;
|
|
1534
|
+
}
|
|
1451
1535
|
function getSdk(accessKey, headers, customServerUrl) {
|
|
1452
1536
|
const sdk = new AccountManager(accessKey, CLI_HEADERS, customServerUrl);
|
|
1453
1537
|
/*
|