@mintlify/cli 4.0.1097 → 4.0.1099

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mintlify/cli",
3
- "version": "4.0.1097",
3
+ "version": "4.0.1099",
4
4
  "description": "The Mintlify CLI",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -45,11 +45,11 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@inquirer/prompts": "7.9.0",
48
- "@mintlify/common": "1.0.840",
49
- "@mintlify/link-rot": "3.0.1015",
50
- "@mintlify/prebuild": "1.0.982",
51
- "@mintlify/previewing": "4.0.1043",
52
- "@mintlify/validation": "0.1.656",
48
+ "@mintlify/common": "1.0.841",
49
+ "@mintlify/link-rot": "3.0.1016",
50
+ "@mintlify/prebuild": "1.0.983",
51
+ "@mintlify/previewing": "4.0.1044",
52
+ "@mintlify/validation": "0.1.657",
53
53
  "adm-zip": "0.5.16",
54
54
  "chalk": "5.2.0",
55
55
  "color": "4.2.3",
@@ -93,5 +93,5 @@
93
93
  "vitest": "2.1.9",
94
94
  "vitest-mock-process": "1.0.4"
95
95
  },
96
- "gitHead": "7d99240d2ad695afdd1cc96ec1c042e12b18fde8"
96
+ "gitHead": "9580be43b49210b3a2a21e11e8b6930f67801556"
97
97
  }
@@ -1,3 +1,4 @@
1
+ import { authenticatedFetch } from '../authenticatedFetch.js';
1
2
  import { API_URL } from '../constants.js';
2
3
  import type {
3
4
  BucketThreadsResponse,
@@ -13,32 +14,14 @@ import type {
13
14
 
14
15
  type Params = Record<string, string | number | undefined>;
15
16
 
16
- async function getAuthHeaders(): Promise<Record<string, string>> {
17
- try {
18
- const { getAccessToken } = await import('../keyring.js');
19
- const token = await getAccessToken();
20
- if (token) {
21
- return { Authorization: `Bearer ${token}` };
22
- }
23
- } catch {}
24
-
25
- const envToken = process.env.MINTLIFY_SESSION_TOKEN;
26
- if (envToken) {
27
- return { Authorization: `Bearer ${envToken}` };
28
- }
29
-
30
- throw new Error('Not authenticated. Run `mint login` to authenticate.');
31
- }
32
-
33
17
  async function request<T>(path: string, params: Params = {}): Promise<T> {
34
18
  const url = new URL(`${API_URL}/api/cli/analytics${path}`);
35
19
  for (const [key, value] of Object.entries(params)) {
36
20
  if (value !== undefined) url.searchParams.set(key, String(value));
37
21
  }
38
22
 
39
- const authHeaders = await getAuthHeaders();
40
- const res = await fetch(url, {
41
- headers: { ...authHeaders, Accept: 'application/json' },
23
+ const res = await authenticatedFetch(url.toString(), {
24
+ headers: { Accept: 'application/json' },
42
25
  });
43
26
 
44
27
  if (!res.ok) {
@@ -0,0 +1,42 @@
1
+ import { getAccessToken } from './keyring.js';
2
+ import { refreshAccessToken } from './tokenRefresh.js';
3
+
4
+ async function getKeyringToken(): Promise<string | null> {
5
+ try {
6
+ return await getAccessToken();
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+
12
+ export async function authenticatedFetch(url: string | URL, init?: RequestInit): Promise<Response> {
13
+ const keyringToken = await getKeyringToken();
14
+ const envToken = process.env.MINTLIFY_SESSION_TOKEN;
15
+ const token = keyringToken ?? envToken;
16
+
17
+ if (!token) {
18
+ throw new Error('Not authenticated. Run `mint login` to authenticate.');
19
+ }
20
+
21
+ const makeRequest = (t: string) =>
22
+ fetch(url, {
23
+ ...init,
24
+ headers: { ...init?.headers, Authorization: `Bearer ${t}` },
25
+ });
26
+
27
+ const res = await makeRequest(token);
28
+ if (res.status !== 401) return res;
29
+
30
+ // If the keyring token expired, try refreshing it
31
+ if (keyringToken) {
32
+ const refreshed = await refreshAccessToken();
33
+ if (refreshed) return makeRequest(refreshed);
34
+ }
35
+
36
+ // Fall back to the env session token if it wasn't already used
37
+ if (envToken && token !== envToken) {
38
+ return makeRequest(envToken);
39
+ }
40
+
41
+ return res;
42
+ }
package/src/keyring.ts CHANGED
@@ -27,6 +27,11 @@ export async function getAccessToken(): Promise<string | null> {
27
27
  return keytar.getPassword(SERVICE, ACCESS_TOKEN_ACCOUNT);
28
28
  }
29
29
 
30
+ export async function getRefreshToken(): Promise<string | null> {
31
+ const keytar = await getKeytar();
32
+ return keytar.getPassword(SERVICE, REFRESH_TOKEN_ACCOUNT);
33
+ }
34
+
30
35
  export async function clearCredentials(): Promise<void> {
31
36
  const keytar = await getKeytar();
32
37
  await Promise.all([
package/src/login.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { input } from '@inquirer/prompts';
2
- import { addLog } from '@mintlify/previewing';
2
+ import { addLog, ErrorLog, SuccessLog } from '@mintlify/previewing';
3
3
  import chalk from 'chalk';
4
4
  import { Box, Text } from 'ink';
5
5
  import open from 'open';
@@ -49,7 +49,7 @@ export async function login(): Promise<void> {
49
49
  </Text>
50
50
  <Box flexDirection="column" paddingLeft={3} gap={1}>
51
51
  <Text dimColor>If your browser doesn't open automatically, copy this URL:</Text>
52
- <Text color="cyan">{url}</Text>
52
+ <Text dimColor>{url}</Text>
53
53
  </Box>
54
54
  </Box>
55
55
  );
@@ -82,7 +82,7 @@ export async function login(): Promise<void> {
82
82
  } catch {
83
83
  closeServer();
84
84
  inputPromise.cancel();
85
- addLog(<Text color="red">✖ Login cancelled.</Text>);
85
+ addLog(<ErrorLog message="login cancelled" />);
86
86
  return;
87
87
  }
88
88
 
@@ -106,12 +106,12 @@ export async function login(): Promise<void> {
106
106
  if (!res.ok) {
107
107
  const reason = body.error_message ?? body.error ?? 'unknown error';
108
108
  void trackLoginFailed(reason);
109
- addLog(<Text color="red">✖ Login failed: {reason}</Text>);
109
+ addLog(<ErrorLog message={`login failed: ${reason}`} />);
110
110
  return;
111
111
  }
112
112
 
113
113
  const token = body as TokenResponse;
114
114
  await storeCredentials(token.access_token, token.refresh_token);
115
115
  void trackLoginSuccess();
116
- addLog(<Text color="green">✔ Logged in successfully.</Text>);
116
+ addLog(<SuccessLog message="logged in successfully" />);
117
117
  }
package/src/logout.tsx CHANGED
@@ -1,9 +1,8 @@
1
- import { addLog } from '@mintlify/previewing';
2
- import { Text } from 'ink';
1
+ import { addLog, SuccessLog } from '@mintlify/previewing';
3
2
 
4
3
  import { clearCredentials } from './keyring.js';
5
4
 
6
5
  export async function logout(): Promise<void> {
7
6
  await clearCredentials();
8
- addLog(<Text color="green">Logged out successfully.</Text>);
7
+ addLog(<SuccessLog message="logged out successfully" />);
9
8
  }
package/src/status.tsx CHANGED
@@ -1,8 +1,11 @@
1
- import { addLog } from '@mintlify/previewing';
2
- import { Text } from 'ink';
1
+ import { addLog, ErrorLog } from '@mintlify/previewing';
2
+ import { Box, Text } from 'ink';
3
3
  import { z } from 'zod';
4
4
 
5
+ import { authenticatedFetch } from './authenticatedFetch.js';
6
+ import { getConfigValue } from './config.js';
5
7
  import { API_URL } from './constants.js';
8
+ import { getCliVersion } from './helpers.js';
6
9
  import { getAccessToken } from './keyring.js';
7
10
 
8
11
  const StatusResponseSchema = z.object({
@@ -29,17 +32,15 @@ export async function status(): Promise<void> {
29
32
  const accessToken = await getAccessToken();
30
33
 
31
34
  if (!accessToken) {
32
- addLog(<Text color="red">Not logged in. Run `mint login` to authenticate.</Text>);
35
+ addLog(<ErrorLog message="not logged in. Run `mint login` to authenticate." />);
33
36
  return;
34
37
  }
35
38
 
36
39
  try {
37
- const res = await fetch(`${API_URL}/api/cli/status`, {
38
- headers: { Authorization: `Bearer ${accessToken}` },
39
- });
40
+ const res = await authenticatedFetch(`${API_URL}/api/cli/status`);
40
41
 
41
42
  if (!res.ok) {
42
- addLog(<Text color="red">Not logged in. Run `mint login` to authenticate.</Text>);
43
+ addLog(<ErrorLog message="not logged in. Run `mint login` to authenticate." />);
43
44
  return;
44
45
  }
45
46
 
@@ -47,18 +48,46 @@ export async function status(): Promise<void> {
47
48
  const parsed = StatusResponseSchema.safeParse(json);
48
49
 
49
50
  if (!parsed.success) {
50
- addLog(<Text color="red">Unexpected response from server. Please try again.</Text>);
51
+ addLog(<ErrorLog message="unexpected response from server. please try again." />);
51
52
  return;
52
53
  }
53
54
 
54
- const { user, org } = parsed.data;
55
+ const { user, org, subdomain: apiSubdomain } = parsed.data;
56
+ const version = getCliVersion();
57
+ const subdomain = getConfigValue('subdomain') ?? apiSubdomain ?? null;
55
58
  addLog(
56
- <Text>
57
- Logged in as <Text color="green">{user.email}</Text> in org{' '}
58
- <Text color="green">{org.name}</Text>
59
- </Text>
59
+ <Box flexDirection="column" paddingY={1}>
60
+ {version && (
61
+ <Box>
62
+ <Box minWidth={16}>
63
+ <Text dimColor>Version</Text>
64
+ </Box>
65
+ <Text>{version}</Text>
66
+ </Box>
67
+ )}
68
+ <Box>
69
+ <Box minWidth={16}>
70
+ <Text dimColor>Email</Text>
71
+ </Box>
72
+ <Text>{user.email}</Text>
73
+ </Box>
74
+ <Box>
75
+ <Box minWidth={16}>
76
+ <Text dimColor>Organization</Text>
77
+ </Box>
78
+ <Text>{org.name}</Text>
79
+ </Box>
80
+ {subdomain && (
81
+ <Box>
82
+ <Box minWidth={16}>
83
+ <Text dimColor>Subdomain</Text>
84
+ </Box>
85
+ <Text>{subdomain}</Text>
86
+ </Box>
87
+ )}
88
+ </Box>
60
89
  );
61
90
  } catch (e) {
62
- addLog(<Text color="red">Unexpected response from server. Please try again.</Text>);
91
+ addLog(<ErrorLog message="unexpected response from server. please try again." />);
63
92
  }
64
93
  }
@@ -0,0 +1,48 @@
1
+ import { STYTCH_CLIENT_ID, TOKEN_ENDPOINT } from './constants.js';
2
+ import { getRefreshToken, storeCredentials } from './keyring.js';
3
+
4
+ interface TokenResponse {
5
+ access_token: string;
6
+ refresh_token: string;
7
+ token_type: 'bearer';
8
+ expires_in: number;
9
+ }
10
+
11
+ let inflightRefresh: Promise<string | null> | null = null;
12
+
13
+ export async function refreshAccessToken(): Promise<string | null> {
14
+ if (inflightRefresh) return inflightRefresh;
15
+
16
+ inflightRefresh = doRefresh().finally(() => {
17
+ inflightRefresh = null;
18
+ });
19
+
20
+ return inflightRefresh;
21
+ }
22
+
23
+ async function doRefresh(): Promise<string | null> {
24
+ try {
25
+ const refreshToken = await getRefreshToken();
26
+ if (!refreshToken) return null;
27
+
28
+ const res = await fetch(TOKEN_ENDPOINT, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify({
32
+ client_id: STYTCH_CLIENT_ID,
33
+ grant_type: 'refresh_token',
34
+ refresh_token: refreshToken,
35
+ }),
36
+ });
37
+
38
+ if (!res.ok) return null;
39
+
40
+ const body = (await res.json().catch(() => null)) as TokenResponse | null;
41
+ if (!body?.access_token || !body.refresh_token) return null;
42
+
43
+ await storeCredentials(body.access_token, body.refresh_token);
44
+ return body.access_token;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }