@l4yercak3/cli 1.1.5 ā 1.1.7
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.
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"Bash(npm whoami:*)",
|
|
16
16
|
"Bash(npm pkg fix:*)",
|
|
17
17
|
"Bash(git push:*)",
|
|
18
|
-
"Bash(session\" errors when trying to use features.\n\nNow the login command validates the session with the backend first.\nIf validation fails, it clears the local session and prompts for\nfresh login, avoiding the confusing loop.\n\nš¤ Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
|
|
18
|
+
"Bash(session\" errors when trying to use features.\n\nNow the login command validates the session with the backend first.\nIf validation fails, it clears the local session and prompts for\nfresh login, avoiding the confusing loop.\n\nš¤ Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
19
|
+
"Bash(npm run build:*)",
|
|
20
|
+
"Bash(npm version:*)"
|
|
19
21
|
]
|
|
20
22
|
}
|
|
21
23
|
}
|
package/package.json
CHANGED
|
@@ -49,19 +49,43 @@ class BackendClient {
|
|
|
49
49
|
*/
|
|
50
50
|
async request(method, endpoint, data = null) {
|
|
51
51
|
const url = `${this.baseUrl}${endpoint}`;
|
|
52
|
+
const headers = this.getHeaders();
|
|
52
53
|
const options = {
|
|
53
54
|
method,
|
|
54
|
-
headers
|
|
55
|
+
headers,
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
58
59
|
options.body = JSON.stringify(data);
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// Debug logging - enable with L4YERCAK3_DEBUG=1
|
|
63
|
+
if (process.env.L4YERCAK3_DEBUG) {
|
|
64
|
+
console.log('\n[DEBUG] API Request:');
|
|
65
|
+
console.log(` URL: ${url}`);
|
|
66
|
+
console.log(` Method: ${method}`);
|
|
67
|
+
console.log(` Headers: ${JSON.stringify(headers, null, 2)}`);
|
|
68
|
+
if (options.body) {
|
|
69
|
+
console.log(` Body: ${options.body}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
try {
|
|
62
74
|
const response = await fetch(url, options);
|
|
75
|
+
|
|
76
|
+
// Debug: log raw response info
|
|
77
|
+
if (process.env.L4YERCAK3_DEBUG) {
|
|
78
|
+
console.log('[DEBUG] API Response:');
|
|
79
|
+
console.log(` Status: ${response.status} ${response.statusText}`);
|
|
80
|
+
console.log(` Headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
63
83
|
const responseData = await response.json();
|
|
64
84
|
|
|
85
|
+
if (process.env.L4YERCAK3_DEBUG) {
|
|
86
|
+
console.log(` Body: ${JSON.stringify(responseData, null, 2)}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
if (!response.ok) {
|
|
66
90
|
// Create error with additional details from backend
|
|
67
91
|
const error = new Error(responseData.message || responseData.error || `API request failed: ${response.status}`);
|
|
@@ -75,6 +99,9 @@ class BackendClient {
|
|
|
75
99
|
|
|
76
100
|
return responseData;
|
|
77
101
|
} catch (error) {
|
|
102
|
+
if (process.env.L4YERCAK3_DEBUG) {
|
|
103
|
+
console.log(`[DEBUG] Request error: ${error.message}`);
|
|
104
|
+
}
|
|
78
105
|
if (error.message.includes('fetch')) {
|
|
79
106
|
throw new Error(`Network error: Could not connect to backend at ${this.baseUrl}`);
|
|
80
107
|
}
|
|
@@ -121,6 +148,26 @@ class BackendClient {
|
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Revoke CLI session (logout on backend)
|
|
153
|
+
* This cleans up the session from the database
|
|
154
|
+
*/
|
|
155
|
+
async revokeSession() {
|
|
156
|
+
try {
|
|
157
|
+
const session = configManager.getSession();
|
|
158
|
+
if (!session || !session.token) {
|
|
159
|
+
return { success: true }; // Nothing to revoke
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return await this.request('POST', '/api/v1/auth/cli/revoke');
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Even if revoke fails, we'll clear locally
|
|
165
|
+
// Log but don't throw - user still wants to logout
|
|
166
|
+
console.error('Warning: Could not revoke session on backend:', error.message);
|
|
167
|
+
return { success: false, error: error.message };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
124
171
|
/**
|
|
125
172
|
* Get CLI login URL with state parameter for CSRF protection
|
|
126
173
|
* Browser login is served by Next.js at APP_URL (not the Convex API URL)
|
package/src/commands/login.js
CHANGED
|
@@ -156,9 +156,12 @@ async function handleLogin() {
|
|
|
156
156
|
await promptSetupWizard();
|
|
157
157
|
return;
|
|
158
158
|
} else {
|
|
159
|
-
// Session is invalid on backend -
|
|
159
|
+
// Session is invalid on backend - revoke it and proceed to login
|
|
160
160
|
console.log(chalk.yellow(' ā ļø Your session has expired or is invalid'));
|
|
161
|
-
console.log(chalk.gray('
|
|
161
|
+
console.log(chalk.gray(' Revoking old session and starting fresh login...\n'));
|
|
162
|
+
|
|
163
|
+
// Try to revoke the old session on backend (cleanup)
|
|
164
|
+
await backendClient.revokeSession();
|
|
162
165
|
configManager.clearSession();
|
|
163
166
|
}
|
|
164
167
|
}
|
package/src/commands/logout.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logout Command
|
|
3
|
-
* Clears CLI session
|
|
3
|
+
* Clears CLI session both locally and on the backend
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const configManager = require('../config/config-manager');
|
|
7
|
+
const backendClient = require('../api/backend-client');
|
|
7
8
|
const chalk = require('chalk');
|
|
8
9
|
|
|
9
10
|
async function handleLogout() {
|
|
@@ -12,6 +13,11 @@ async function handleLogout() {
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// Revoke session on backend first (cleans up database)
|
|
17
|
+
console.log(chalk.gray(' Revoking session...'));
|
|
18
|
+
await backendClient.revokeSession();
|
|
19
|
+
|
|
20
|
+
// Clear local session
|
|
15
21
|
configManager.clearSession();
|
|
16
22
|
console.log(chalk.green(' ā
Successfully logged out\n'));
|
|
17
23
|
}
|
|
@@ -248,6 +248,58 @@ describe('BackendClient', () => {
|
|
|
248
248
|
});
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
+
describe('revokeSession', () => {
|
|
252
|
+
it('calls revoke endpoint when session exists', async () => {
|
|
253
|
+
configManager.getSession.mockReturnValue({ token: 'test-token' });
|
|
254
|
+
const mockResponse = {
|
|
255
|
+
ok: true,
|
|
256
|
+
json: jest.fn().mockResolvedValue({ success: true }),
|
|
257
|
+
};
|
|
258
|
+
fetch.mockResolvedValue(mockResponse);
|
|
259
|
+
|
|
260
|
+
const result = await BackendClient.revokeSession();
|
|
261
|
+
|
|
262
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
263
|
+
expect.stringContaining('/api/v1/auth/cli/revoke'),
|
|
264
|
+
expect.objectContaining({ method: 'POST' })
|
|
265
|
+
);
|
|
266
|
+
expect(result.success).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('returns success when no session exists', async () => {
|
|
270
|
+
configManager.getSession.mockReturnValue(null);
|
|
271
|
+
|
|
272
|
+
const result = await BackendClient.revokeSession();
|
|
273
|
+
|
|
274
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
275
|
+
expect(result.success).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('returns success when session has no token', async () => {
|
|
279
|
+
configManager.getSession.mockReturnValue({ expiresAt: Date.now() });
|
|
280
|
+
|
|
281
|
+
const result = await BackendClient.revokeSession();
|
|
282
|
+
|
|
283
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
284
|
+
expect(result.success).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('returns failure but does not throw on error', async () => {
|
|
288
|
+
configManager.getSession.mockReturnValue({ token: 'test-token' });
|
|
289
|
+
fetch.mockRejectedValue(new Error('Revoke failed'));
|
|
290
|
+
|
|
291
|
+
// Suppress console.error for this test
|
|
292
|
+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
293
|
+
|
|
294
|
+
const result = await BackendClient.revokeSession();
|
|
295
|
+
|
|
296
|
+
expect(result.success).toBe(false);
|
|
297
|
+
expect(result.error).toBe('Revoke failed');
|
|
298
|
+
|
|
299
|
+
consoleSpy.mockRestore();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
251
303
|
describe('generateState', () => {
|
|
252
304
|
it('generates a 64-character hex string', () => {
|
|
253
305
|
const state = BackendClient.generateState();
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
jest.mock('../../src/config/config-manager');
|
|
6
|
+
jest.mock('../../src/api/backend-client');
|
|
6
7
|
jest.mock('chalk', () => ({
|
|
7
8
|
yellow: (str) => str,
|
|
8
9
|
green: (str) => str,
|
|
10
|
+
gray: (str) => str,
|
|
9
11
|
}));
|
|
10
12
|
|
|
11
13
|
const configManager = require('../../src/config/config-manager');
|
|
14
|
+
const backendClient = require('../../src/api/backend-client');
|
|
12
15
|
const logoutCommand = require('../../src/commands/logout');
|
|
13
16
|
|
|
14
17
|
describe('Logout Command', () => {
|
|
@@ -21,6 +24,8 @@ describe('Logout Command', () => {
|
|
|
21
24
|
console.log = jest.fn((...args) => {
|
|
22
25
|
consoleOutput.push(args.join(' '));
|
|
23
26
|
});
|
|
27
|
+
// Mock revokeSession to return success
|
|
28
|
+
backendClient.revokeSession.mockResolvedValue({ success: true });
|
|
24
29
|
});
|
|
25
30
|
|
|
26
31
|
afterEach(() => {
|
|
@@ -59,6 +64,15 @@ describe('Logout Command', () => {
|
|
|
59
64
|
expect(configManager.clearSession).toHaveBeenCalled();
|
|
60
65
|
});
|
|
61
66
|
|
|
67
|
+
it('revokes session on backend before clearing locally', async () => {
|
|
68
|
+
configManager.isLoggedIn.mockReturnValue(true);
|
|
69
|
+
|
|
70
|
+
await logoutCommand.handler();
|
|
71
|
+
|
|
72
|
+
expect(backendClient.revokeSession).toHaveBeenCalled();
|
|
73
|
+
expect(configManager.clearSession).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
62
76
|
it('shows success message after logout', async () => {
|
|
63
77
|
configManager.isLoggedIn.mockReturnValue(true);
|
|
64
78
|
|