@mintlify/cli 4.0.1075 → 4.0.1077
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/__test__/keyring.test.ts +52 -0
- package/bin/keyring.js +13 -1
- package/bin/login.js +21 -6
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -3
- package/src/keyring.ts +13 -2
- package/src/login.tsx +40 -5
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const mockKeytar = {
|
|
4
|
+
setPassword: vi.fn().mockResolvedValue(undefined),
|
|
5
|
+
getPassword: vi.fn().mockResolvedValue('test-token'),
|
|
6
|
+
deletePassword: vi.fn().mockResolvedValue(true),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
vi.mock('keytar', () => ({
|
|
10
|
+
default: mockKeytar,
|
|
11
|
+
setPassword: mockKeytar.setPassword,
|
|
12
|
+
getPassword: mockKeytar.getPassword,
|
|
13
|
+
deletePassword: mockKeytar.deletePassword,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('keyring', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
vi.resetModules();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('stores access and refresh tokens via keytar', async () => {
|
|
23
|
+
const { storeCredentials } = await import('../src/keyring.js');
|
|
24
|
+
await storeCredentials('access', 'refresh');
|
|
25
|
+
expect(mockKeytar.setPassword).toHaveBeenCalledWith('mintlify', 'access_token', 'access');
|
|
26
|
+
expect(mockKeytar.setPassword).toHaveBeenCalledWith('mintlify', 'refresh_token', 'refresh');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('retrieves the access token via keytar', async () => {
|
|
30
|
+
const { getAccessToken } = await import('../src/keyring.js');
|
|
31
|
+
const token = await getAccessToken();
|
|
32
|
+
expect(token).toBe('test-token');
|
|
33
|
+
expect(mockKeytar.getPassword).toHaveBeenCalledWith('mintlify', 'access_token');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('deletes both tokens via keytar', async () => {
|
|
37
|
+
const { clearCredentials } = await import('../src/keyring.js');
|
|
38
|
+
await clearCredentials();
|
|
39
|
+
expect(mockKeytar.deletePassword).toHaveBeenCalledWith('mintlify', 'access_token');
|
|
40
|
+
expect(mockKeytar.deletePassword).toHaveBeenCalledWith('mintlify', 'refresh_token');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('throws a helpful error when keytar is not installed', async () => {
|
|
44
|
+
vi.doMock('keytar', () => {
|
|
45
|
+
throw new Error('Cannot find module');
|
|
46
|
+
});
|
|
47
|
+
const { storeCredentials } = await import('../src/keyring.js');
|
|
48
|
+
await expect(storeCredentials('a', 'b')).rejects.toThrow(
|
|
49
|
+
'keytar is required for credential storage but is not installed'
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/bin/keyring.js
CHANGED
|
@@ -7,12 +7,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import keytar from 'keytar';
|
|
11
10
|
const SERVICE = 'mintlify';
|
|
12
11
|
const ACCESS_TOKEN_ACCOUNT = 'access_token';
|
|
13
12
|
const REFRESH_TOKEN_ACCOUNT = 'refresh_token';
|
|
13
|
+
function getKeytar() {
|
|
14
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
+
try {
|
|
16
|
+
return yield import('keytar');
|
|
17
|
+
}
|
|
18
|
+
catch (_a) {
|
|
19
|
+
throw new Error('keytar is required for credential storage but is not installed. Install it with: npm install keytar');
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
14
23
|
export function storeCredentials(accessToken, refreshToken) {
|
|
15
24
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
const keytar = yield getKeytar();
|
|
16
26
|
yield Promise.all([
|
|
17
27
|
keytar.setPassword(SERVICE, ACCESS_TOKEN_ACCOUNT, accessToken),
|
|
18
28
|
keytar.setPassword(SERVICE, REFRESH_TOKEN_ACCOUNT, refreshToken),
|
|
@@ -21,11 +31,13 @@ export function storeCredentials(accessToken, refreshToken) {
|
|
|
21
31
|
}
|
|
22
32
|
export function getAccessToken() {
|
|
23
33
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
const keytar = yield getKeytar();
|
|
24
35
|
return keytar.getPassword(SERVICE, ACCESS_TOKEN_ACCOUNT);
|
|
25
36
|
});
|
|
26
37
|
}
|
|
27
38
|
export function clearCredentials() {
|
|
28
39
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const keytar = yield getKeytar();
|
|
29
41
|
yield Promise.all([
|
|
30
42
|
keytar.deletePassword(SERVICE, ACCESS_TOKEN_ACCOUNT),
|
|
31
43
|
keytar.deletePassword(SERVICE, REFRESH_TOKEN_ACCOUNT),
|
package/bin/login.js
CHANGED
|
@@ -7,10 +7,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { input } from '@inquirer/prompts';
|
|
12
12
|
import { addLog } from '@mintlify/previewing';
|
|
13
|
-
import
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { Box, Text } from 'ink';
|
|
15
|
+
import open from 'open';
|
|
14
16
|
import { calculatePKCECodeChallenge, randomNonce, randomPKCECodeVerifier, randomState, } from 'openid-client';
|
|
15
17
|
import { DASHBOARD_URL, STYTCH_CLIENT_ID, TOKEN_ENDPOINT } from './constants.js';
|
|
16
18
|
import { storeCredentials } from './keyring.js';
|
|
@@ -25,8 +27,21 @@ export function login() {
|
|
|
25
27
|
const authorizeUrl = new URL('/api/cli/oauth/authorize', DASHBOARD_URL);
|
|
26
28
|
authorizeUrl.searchParams.set('state', state);
|
|
27
29
|
authorizeUrl.searchParams.set('code_challenge', codeChallenge);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
const url = authorizeUrl.toString();
|
|
31
|
+
addLog(_jsxs(Box, { flexDirection: "column", gap: 1, paddingY: 1, children: [_jsxs(Text, { bold: true, children: [_jsx(Text, { color: "green", children: "\u25C6 " }), "A browser window will open for Mintlify authentication"] }), _jsxs(Box, { flexDirection: "column", paddingLeft: 3, gap: 1, children: [_jsx(Text, { dimColor: true, children: "If your browser doesn't open automatically, copy this URL:" }), _jsx(Text, { color: "cyan", children: url })] })] }));
|
|
32
|
+
open(url).catch(() => { });
|
|
33
|
+
addLog(_jsxs(Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "\u256D\u2500 Paste the authorization code from your browser" }), _jsx(Text, { dimColor: true, children: "\u2502" })] }));
|
|
34
|
+
// Let ink finish rendering before inquirer takes over stdout
|
|
35
|
+
yield new Promise((resolve) => setTimeout(resolve, 50));
|
|
36
|
+
const code = yield input({
|
|
37
|
+
message: '█',
|
|
38
|
+
theme: {
|
|
39
|
+
prefix: chalk.dim(' │'),
|
|
40
|
+
style: {
|
|
41
|
+
answer: (text) => chalk.cyan(text),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
30
45
|
const res = yield fetch(TOKEN_ENDPOINT, {
|
|
31
46
|
method: 'POST',
|
|
32
47
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -40,11 +55,11 @@ export function login() {
|
|
|
40
55
|
});
|
|
41
56
|
const body = yield res.json().catch(() => ({}));
|
|
42
57
|
if (!res.ok) {
|
|
43
|
-
addLog(_jsxs(Text, { color: "red", children: ["Login failed: ", (_b = (_a = body.error_message) !== null && _a !== void 0 ? _a : body.error) !== null && _b !== void 0 ? _b : 'unknown error'] }));
|
|
58
|
+
addLog(_jsxs(Text, { color: "red", children: ["\u2716 Login failed: ", (_b = (_a = body.error_message) !== null && _a !== void 0 ? _a : body.error) !== null && _b !== void 0 ? _b : 'unknown error'] }));
|
|
44
59
|
return;
|
|
45
60
|
}
|
|
46
61
|
const token = body;
|
|
47
62
|
yield storeCredentials(token.access_token, token.refresh_token);
|
|
48
|
-
addLog(_jsx(Text, { color: "green", children: "Logged in successfully." }));
|
|
63
|
+
addLog(_jsx(Text, { color: "green", children: "\u2714 Logged in successfully." }));
|
|
49
64
|
});
|
|
50
65
|
}
|