@l4yercak3/cli 1.1.5 → 1.1.6

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": "@l4yercak3/cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Icing on the L4yercak3 - The sweet finishing touch for your Layer Cake integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -121,6 +121,26 @@ class BackendClient {
121
121
  }
122
122
  }
123
123
 
124
+ /**
125
+ * Revoke CLI session (logout on backend)
126
+ * This cleans up the session from the database
127
+ */
128
+ async revokeSession() {
129
+ try {
130
+ const session = configManager.getSession();
131
+ if (!session || !session.token) {
132
+ return { success: true }; // Nothing to revoke
133
+ }
134
+
135
+ return await this.request('POST', '/api/v1/auth/cli/revoke');
136
+ } catch (error) {
137
+ // Even if revoke fails, we'll clear locally
138
+ // Log but don't throw - user still wants to logout
139
+ console.error('Warning: Could not revoke session on backend:', error.message);
140
+ return { success: false, error: error.message };
141
+ }
142
+ }
143
+
124
144
  /**
125
145
  * Get CLI login URL with state parameter for CSRF protection
126
146
  * Browser login is served by Next.js at APP_URL (not the Convex API URL)
@@ -156,9 +156,12 @@ async function handleLogin() {
156
156
  await promptSetupWizard();
157
157
  return;
158
158
  } else {
159
- // Session is invalid on backend - clear it and proceed to login
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(' Starting fresh login...\n'));
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
  }
@@ -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