@sentio/cli 3.4.1-rc.1 → 3.4.1-rc.2

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/lib/index.js CHANGED
@@ -145841,32 +145841,46 @@ app.get("/callback", async (req, res) => {
145841
145841
  fail("Failed to get authorization code");
145842
145842
  return;
145843
145843
  }
145844
- const tokenResRaw = await getToken(host, code);
145845
- if (!tokenResRaw.ok) {
145846
- fail(`Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`);
145844
+ try {
145845
+ const username = await exchangeCodeAndSave(host, code, authParams.codeVerifier);
145846
+ res.end("Login success, please go back to CLI to continue");
145847
+ console.log(source_default.green(`Login success with ${username}`));
145848
+ } catch (e10) {
145849
+ fail(e10.message);
145847
145850
  return;
145848
145851
  }
145852
+ server.close();
145853
+ server.closeAllConnections();
145854
+ if (authParams.onSuccess) {
145855
+ authParams.onSuccess();
145856
+ } else {
145857
+ setTimeout(() => process.exit(), 1e3);
145858
+ }
145859
+ });
145860
+ async function exchangeCodeAndSave(host, code, codeVerifier) {
145861
+ const tokenResRaw = await getToken(host, code, codeVerifier);
145862
+ if (!tokenResRaw.ok) {
145863
+ throw new Error(
145864
+ `Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`
145865
+ );
145866
+ }
145849
145867
  const tokenRes = await tokenResRaw.json();
145850
145868
  const accessToken = tokenRes.access_token;
145851
145869
  const userResRaw = await getUser(host, accessToken);
145852
145870
  if (!userResRaw.ok) {
145853
145871
  if (userResRaw.status == 401) {
145854
- fail("The account does not exist, please sign up on sentio first");
145855
- } else {
145856
- fail(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`);
145872
+ throw new Error("The account does not exist, please sign up on sentio first");
145857
145873
  }
145858
- return;
145874
+ throw new Error(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`);
145859
145875
  }
145860
145876
  const userRes = await userResRaw.json();
145861
145877
  if (!userRes.emailVerified) {
145862
- fail("Your account is not verified, please verify your email first");
145863
- return;
145878
+ throw new Error("Your account is not verified, please verify your email first");
145864
145879
  }
145865
145880
  const apiKeyName = `${os3.hostname()}-${crypto.randomBytes(4).toString("hex")}`;
145866
145881
  const createApiKeyResRaw = await createApiKey(host, apiKeyName, "sdk_generated", accessToken);
145867
145882
  if (!createApiKeyResRaw.ok) {
145868
- fail(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`);
145869
- return;
145883
+ throw new Error(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`);
145870
145884
  }
145871
145885
  const { key, username } = await createApiKeyResRaw.json();
145872
145886
  WriteKey(host, key);
@@ -145874,22 +145888,14 @@ app.get("/callback", async (req, res) => {
145874
145888
  if (expiresAt !== void 0) {
145875
145889
  WriteAccessToken(host, accessToken, expiresAt);
145876
145890
  }
145877
- res.end("Login success, please go back to CLI to continue");
145878
- console.log(source_default.green(`Login success with ${username}, new API key: ${key}`));
145879
- server.close();
145880
- server.closeAllConnections();
145881
- if (authParams.onSuccess) {
145882
- authParams.onSuccess();
145883
- } else {
145884
- setTimeout(() => process.exit(), 1e3);
145885
- }
145886
- });
145887
- async function getToken(host, code) {
145891
+ return username;
145892
+ }
145893
+ async function getToken(host, code, codeVerifier) {
145888
145894
  const authConf = getAuthConfig(host);
145889
145895
  const params = new url3.URLSearchParams({
145890
145896
  grant_type: "authorization_code",
145891
145897
  client_id: authConf.clientId,
145892
- code_verifier: authParams.codeVerifier,
145898
+ code_verifier: codeVerifier,
145893
145899
  code,
145894
145900
  redirect_uri: authConf.redirectUri
145895
145901
  });
@@ -145937,6 +145943,7 @@ function decodeJwtExpiry(token) {
145937
145943
  }
145938
145944
 
145939
145945
  // src/commands/login.ts
145946
+ import readline from "readline";
145940
145947
  import url4, { URL as URL2 } from "url";
145941
145948
  import * as crypto2 from "crypto";
145942
145949
 
@@ -146564,10 +146571,43 @@ var open_default = open;
146564
146571
  // src/commands/login.ts
146565
146572
  var port = 2e4;
146566
146573
  function createLoginCommand() {
146567
- return new Command("login").description("Login to Sentio").option("--host <host>", "(Optional) Override Sentio Host name").option("--api-key <key>", "(Optional) Your API key").action((options) => {
146568
- login(options);
146574
+ return new Command("login").description("Login to Sentio").option("--host <host>", "(Optional) Override Sentio Host name").option("--api-key <key>", "(Optional) Your API key").option("--status", "Show current login status").option("--no-browser", "Print the auth URL and accept the authorization code manually (for headless environments)").action((options) => {
146575
+ if (options.status) {
146576
+ loginStatus(options);
146577
+ } else {
146578
+ login(options);
146579
+ }
146569
146580
  });
146570
146581
  }
146582
+ async function loginStatus(options) {
146583
+ const host = getFinalizedHost(options.host);
146584
+ console.log(source_default.blue("Host: ") + host);
146585
+ const apiKey = ReadKey(host);
146586
+ if (!apiKey) {
146587
+ console.log(source_default.red("Not logged in") + " (no API key stored for this host)");
146588
+ return;
146589
+ }
146590
+ console.log(source_default.green("API key: ") + apiKey.slice(0, 8) + "...");
146591
+ const tokenInfo = ReadAccessToken(host);
146592
+ if (tokenInfo) {
146593
+ const expired = isAccessTokenExpired(tokenInfo.expiresAt);
146594
+ const expiresDate = new Date(tokenInfo.expiresAt * 1e3).toLocaleString();
146595
+ if (expired) {
146596
+ console.log(source_default.yellow("Access token: ") + `expired (${expiresDate})`);
146597
+ } else {
146598
+ console.log(source_default.green("Access token: ") + `valid until ${expiresDate}`);
146599
+ }
146600
+ } else {
146601
+ console.log(source_default.yellow("Access token: ") + "none stored");
146602
+ }
146603
+ const res = await checkKey(host, apiKey);
146604
+ if (res.status === 200) {
146605
+ const { username } = await res.json();
146606
+ console.log(source_default.green("Logged in as: ") + username);
146607
+ } else {
146608
+ console.log(source_default.red("API key validation failed: ") + `${res.status} ${res.statusText}`);
146609
+ }
146610
+ }
146571
146611
  function login(options) {
146572
146612
  const host = getFinalizedHost(options.host);
146573
146613
  if (options.apiKey) {
@@ -146591,10 +146631,15 @@ function login(options) {
146591
146631
  return;
146592
146632
  }
146593
146633
  const authURL = buildAuthURL(conf, challenge);
146634
+ if (options.browser === false) {
146635
+ loginNoBrowser(host, authURL.toString(), verifier);
146636
+ return;
146637
+ }
146594
146638
  console.log("Continue your authorization in the browser");
146595
146639
  open_default(authURL.toString()).catch((reason) => {
146596
- console.error(source_default.red("Unable to open browser: " + reason));
146597
- console.error(source_default.red("Open this url in your browser: " + authURL.toString()));
146640
+ console.error(source_default.yellow("Unable to open browser: " + reason));
146641
+ console.error(source_default.yellow("Falling back to manual login..."));
146642
+ loginNoBrowser(host, authURL.toString(), verifier);
146598
146643
  });
146599
146644
  startServer({
146600
146645
  serverPort: port,
@@ -146627,6 +146672,28 @@ function loginInteractiveAndWait(host) {
146627
146672
  });
146628
146673
  });
146629
146674
  }
146675
+ function loginNoBrowser(host, authURL, codeVerifier) {
146676
+ console.log(source_default.blue("\nOpen the following URL in your browser to complete login:"));
146677
+ console.log(source_default.cyan(authURL));
146678
+ console.log(source_default.blue("\nAfter completing login, copy the authorization code from the redirect URL"));
146679
+ console.log(source_default.blue("(it is the value of the `code` query parameter) and paste it below.\n"));
146680
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
146681
+ rl.question("Authorization code: ", async (input) => {
146682
+ rl.close();
146683
+ const code = input.trim();
146684
+ if (!code) {
146685
+ console.error(source_default.red("No code provided, login aborted."));
146686
+ process.exit(1);
146687
+ }
146688
+ try {
146689
+ const username = await exchangeCodeAndSave(host, code, codeVerifier);
146690
+ console.log(source_default.green(`Login success with ${username}`));
146691
+ } catch (e10) {
146692
+ console.error(source_default.red("Login failed: " + e10.message));
146693
+ process.exit(1);
146694
+ }
146695
+ });
146696
+ }
146630
146697
  function buildAuthURL(conf, challenge) {
146631
146698
  const authURL = new URL2(conf.domain + `/authorize?`);
146632
146699
  const params = new url4.URLSearchParams({
@@ -148996,7 +149063,7 @@ import path12 from "path";
148996
149063
  import { createHash as createHash3 } from "crypto";
148997
149064
  import { execFileSync as execFileSync2 } from "child_process";
148998
149065
  var import_jszip = __toESM(require_lib9(), 1);
148999
- import readline from "readline";
149066
+ import readline2 from "readline";
149000
149067
 
149001
149068
  // src/uploader.ts
149002
149069
  init_cjs_shim();
@@ -149435,7 +149502,7 @@ async function createProjectPrompt(options, auth, type) {
149435
149502
  if (options.silentOverwrite) {
149436
149503
  return await create2();
149437
149504
  }
149438
- const rl = readline.createInterface({
149505
+ const rl = readline2.createInterface({
149439
149506
  input: process.stdin,
149440
149507
  output: process.stdout
149441
149508
  });
@@ -149457,7 +149524,7 @@ async function createProjectPrompt(options, auth, type) {
149457
149524
  }
149458
149525
  }
149459
149526
  async function confirm(question) {
149460
- const rl = readline.createInterface({
149527
+ const rl = readline2.createInterface({
149461
149528
  input: process.stdin,
149462
149529
  output: process.stdout
149463
149530
  });
@@ -151477,7 +151544,7 @@ function asRecordArray(value) {
151477
151544
  init_cjs_shim();
151478
151545
  var import_yaml6 = __toESM(require_dist(), 1);
151479
151546
  import process18 from "process";
151480
- import readline2 from "readline";
151547
+ import readline3 from "readline";
151481
151548
  function createProjectCommand() {
151482
151549
  const projectCommand = new Command("project").description("Manage Sentio projects");
151483
151550
  projectCommand.addCommand(createProjectListCommand());
@@ -151626,7 +151693,7 @@ async function requireAccessToken(host, { strict }) {
151626
151693
  return refreshed.token;
151627
151694
  }
151628
151695
  async function confirmDelete(projectSlug) {
151629
- const rl = readline2.createInterface({ input: process18.stdin, output: process18.stdout });
151696
+ const rl = readline3.createInterface({ input: process18.stdin, output: process18.stdout });
151630
151697
  return new Promise((resolve) => {
151631
151698
  rl.question(
151632
151699
  source_default.yellow(`Are you sure you want to delete project "${projectSlug}"? This cannot be undone. [y/N] `),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/cli",
3
- "version": "3.4.1-rc.1",
3
+ "version": "3.4.1-rc.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "files": [
13
13
  "{lib,src,templates}",
14
+ "!lib/**/*.map",
14
15
  "!{lib,src}/**/*.test.{js,ts}"
15
16
  ],
16
17
  "types": "module",
@@ -49,11 +49,36 @@ app.get('/callback', async (req, res) => {
49
49
  return
50
50
  }
51
51
 
52
+ try {
53
+ const username = await exchangeCodeAndSave(host, code as string, authParams.codeVerifier)
54
+ res.end('Login success, please go back to CLI to continue')
55
+ console.log(chalk.green(`Login success with ${username}`))
56
+ } catch (e) {
57
+ fail((e as Error).message)
58
+ return
59
+ }
60
+
61
+ server.close()
62
+ server.closeAllConnections()
63
+ if (authParams.onSuccess) {
64
+ authParams.onSuccess()
65
+ } else {
66
+ setTimeout(() => process.exit(), 1000)
67
+ }
68
+ })
69
+
70
+ /**
71
+ * Exchanges an OAuth authorization code for an access token, verifies the account,
72
+ * creates an API key, and saves everything to local config.
73
+ * Returns the username on success, throws on failure.
74
+ */
75
+ export async function exchangeCodeAndSave(host: string, code: string, codeVerifier: string): Promise<string> {
52
76
  // exchange token
53
- const tokenResRaw = await getToken(host, code as string)
77
+ const tokenResRaw = await getToken(host, code, codeVerifier)
54
78
  if (!tokenResRaw.ok) {
55
- fail(`Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`)
56
- return
79
+ throw new Error(
80
+ `Failed to get access token: ${tokenResRaw.status} ${tokenResRaw.statusText}, ${await tokenResRaw.text()}`
81
+ )
57
82
  }
58
83
  const tokenRes = (await tokenResRaw.json()) as { access_token: string }
59
84
  const accessToken = tokenRes.access_token
@@ -62,24 +87,20 @@ app.get('/callback', async (req, res) => {
62
87
  const userResRaw = await getUser(host, accessToken)
63
88
  if (!userResRaw.ok) {
64
89
  if (userResRaw.status == 401) {
65
- fail('The account does not exist, please sign up on sentio first')
66
- } else {
67
- fail(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`)
90
+ throw new Error('The account does not exist, please sign up on sentio first')
68
91
  }
69
- return
92
+ throw new Error(`Failed to get user info: ${userResRaw.status} ${userResRaw.statusText}`)
70
93
  }
71
94
  const userRes = (await userResRaw.json()) as { emailVerified: boolean }
72
95
  if (!userRes.emailVerified) {
73
- fail('Your account is not verified, please verify your email first')
74
- return
96
+ throw new Error('Your account is not verified, please verify your email first')
75
97
  }
76
98
 
77
99
  // create API key
78
100
  const apiKeyName = `${os.hostname()}-${crypto.randomBytes(4).toString('hex')}`
79
101
  const createApiKeyResRaw = await createApiKey(host, apiKeyName, 'sdk_generated', accessToken)
80
102
  if (!createApiKeyResRaw.ok) {
81
- fail(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`)
82
- return
103
+ throw new Error(`Failed to create API key: ${createApiKeyResRaw.status} ${createApiKeyResRaw.statusText}`)
83
104
  }
84
105
  const { key, username } = (await createApiKeyResRaw.json()) as { key: string; username: string }
85
106
  WriteKey(host, key)
@@ -90,24 +111,15 @@ app.get('/callback', async (req, res) => {
90
111
  WriteAccessToken(host, accessToken, expiresAt)
91
112
  }
92
113
 
93
- res.end('Login success, please go back to CLI to continue')
94
- console.log(chalk.green(`Login success with ${username}, new API key: ${key}`))
95
-
96
- server.close()
97
- server.closeAllConnections()
98
- if (authParams.onSuccess) {
99
- authParams.onSuccess()
100
- } else {
101
- setTimeout(() => process.exit(), 1000)
102
- }
103
- })
114
+ return username
115
+ }
104
116
 
105
- async function getToken(host: string, code: string) {
117
+ async function getToken(host: string, code: string, codeVerifier: string) {
106
118
  const authConf = getAuthConfig(host)
107
119
  const params = new url.URLSearchParams({
108
120
  grant_type: 'authorization_code',
109
121
  client_id: authConf.clientId,
110
- code_verifier: authParams.codeVerifier,
122
+ code_verifier: codeVerifier,
111
123
  code: code,
112
124
  redirect_uri: authConf.redirectUri
113
125
  })
@@ -1,10 +1,11 @@
1
1
  import { Command } from '@commander-js/extra-typings'
2
2
  import { getAuthConfig, getFinalizedHost } from '../config.js'
3
- import { startServer } from './login-server.js'
3
+ import { startServer, exchangeCodeAndSave } from './login-server.js'
4
+ import readline from 'readline'
4
5
  import url, { URL } from 'url'
5
6
  import * as crypto from 'crypto'
6
7
  import chalk from 'chalk'
7
- import { WriteKey } from '../key.js'
8
+ import { WriteKey, ReadKey, ReadAccessToken, isAccessTokenExpired } from '../key.js'
8
9
  import fetch from 'node-fetch'
9
10
  import open from 'open'
10
11
  import { CommandOptionsType } from './types.js'
@@ -17,11 +18,51 @@ export function createLoginCommand() {
17
18
  .description('Login to Sentio')
18
19
  .option('--host <host>', '(Optional) Override Sentio Host name')
19
20
  .option('--api-key <key>', '(Optional) Your API key')
21
+ .option('--status', 'Show current login status')
22
+ .option('--no-browser', 'Print the auth URL and accept the authorization code manually (for headless environments)')
20
23
  .action((options) => {
21
- login(options)
24
+ if (options.status) {
25
+ loginStatus(options)
26
+ } else {
27
+ login(options)
28
+ }
22
29
  })
23
30
  }
24
31
 
32
+ async function loginStatus(options: CommandOptionsType<typeof createLoginCommand>) {
33
+ const host = getFinalizedHost(options.host)
34
+ console.log(chalk.blue('Host: ') + host)
35
+
36
+ const apiKey = ReadKey(host)
37
+ if (!apiKey) {
38
+ console.log(chalk.red('Not logged in') + ' (no API key stored for this host)')
39
+ return
40
+ }
41
+
42
+ console.log(chalk.green('API key: ') + apiKey.slice(0, 8) + '...')
43
+
44
+ const tokenInfo = ReadAccessToken(host)
45
+ if (tokenInfo) {
46
+ const expired = isAccessTokenExpired(tokenInfo.expiresAt)
47
+ const expiresDate = new Date(tokenInfo.expiresAt * 1000).toLocaleString()
48
+ if (expired) {
49
+ console.log(chalk.yellow('Access token: ') + `expired (${expiresDate})`)
50
+ } else {
51
+ console.log(chalk.green('Access token: ') + `valid until ${expiresDate}`)
52
+ }
53
+ } else {
54
+ console.log(chalk.yellow('Access token: ') + 'none stored')
55
+ }
56
+
57
+ const res = await checkKey(host, apiKey)
58
+ if (res.status === 200) {
59
+ const { username } = (await res.json()) as { username: string }
60
+ console.log(chalk.green('Logged in as: ') + username)
61
+ } else {
62
+ console.log(chalk.red('API key validation failed: ') + `${res.status} ${res.statusText}`)
63
+ }
64
+ }
65
+
25
66
  function login(options: CommandOptionsType<typeof createLoginCommand>) {
26
67
  const host = getFinalizedHost(options.host)
27
68
 
@@ -48,10 +89,16 @@ function login(options: CommandOptionsType<typeof createLoginCommand>) {
48
89
  }
49
90
  const authURL = buildAuthURL(conf, challenge)
50
91
 
92
+ if (options.browser === false) {
93
+ loginNoBrowser(host, authURL.toString(), verifier)
94
+ return
95
+ }
96
+
51
97
  console.log('Continue your authorization in the browser')
52
98
  open(authURL.toString()).catch((reason) => {
53
- console.error(chalk.red('Unable to open browser: ' + reason))
54
- console.error(chalk.red('Open this url in your browser: ' + authURL.toString()))
99
+ console.error(chalk.yellow('Unable to open browser: ' + reason))
100
+ console.error(chalk.yellow('Falling back to manual login...'))
101
+ loginNoBrowser(host, authURL.toString(), verifier)
55
102
  })
56
103
 
57
104
  startServer({
@@ -95,6 +142,30 @@ export function loginInteractiveAndWait(host: string): Promise<void> {
95
142
  })
96
143
  }
97
144
 
145
+ function loginNoBrowser(host: string, authURL: string, codeVerifier: string) {
146
+ console.log(chalk.blue('\nOpen the following URL in your browser to complete login:'))
147
+ console.log(chalk.cyan(authURL))
148
+ console.log(chalk.blue('\nAfter completing login, copy the authorization code from the redirect URL'))
149
+ console.log(chalk.blue('(it is the value of the `code` query parameter) and paste it below.\n'))
150
+
151
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
152
+ rl.question('Authorization code: ', async (input) => {
153
+ rl.close()
154
+ const code = input.trim()
155
+ if (!code) {
156
+ console.error(chalk.red('No code provided, login aborted.'))
157
+ process.exit(1)
158
+ }
159
+ try {
160
+ const username = await exchangeCodeAndSave(host, code, codeVerifier)
161
+ console.log(chalk.green(`Login success with ${username}`))
162
+ } catch (e) {
163
+ console.error(chalk.red('Login failed: ' + (e as Error).message))
164
+ process.exit(1)
165
+ }
166
+ })
167
+ }
168
+
98
169
  function buildAuthURL(
99
170
  conf: { domain: string; clientId: string; audience: string; redirectUri: string },
100
171
  challenge: string