@capawesome/cli 1.13.1 → 1.14.0-dev.1f912c9.1755635879

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -3
  3. package/dist/commands/apps/bundles/create.js +215 -233
  4. package/dist/commands/apps/bundles/create.test.js +274 -0
  5. package/dist/commands/apps/bundles/delete.js +42 -47
  6. package/dist/commands/apps/bundles/delete.test.js +140 -0
  7. package/dist/commands/apps/bundles/update.js +69 -72
  8. package/dist/commands/apps/bundles/update.test.js +142 -0
  9. package/dist/commands/apps/channels/create.js +47 -70
  10. package/dist/commands/apps/channels/create.test.js +115 -0
  11. package/dist/commands/apps/channels/delete.js +53 -56
  12. package/dist/commands/apps/channels/delete.test.js +140 -0
  13. package/dist/commands/apps/channels/get.js +27 -61
  14. package/dist/commands/apps/channels/get.test.js +136 -0
  15. package/dist/commands/apps/channels/list.js +26 -63
  16. package/dist/commands/apps/channels/list.test.js +123 -0
  17. package/dist/commands/apps/channels/update.js +48 -67
  18. package/dist/commands/apps/channels/update.test.js +139 -0
  19. package/dist/commands/apps/create.js +38 -38
  20. package/dist/commands/apps/create.test.js +117 -0
  21. package/dist/commands/apps/delete.js +39 -40
  22. package/dist/commands/apps/delete.test.js +119 -0
  23. package/dist/commands/apps/devices/delete.js +42 -47
  24. package/dist/commands/apps/devices/delete.test.js +140 -0
  25. package/dist/commands/doctor.js +12 -29
  26. package/dist/commands/doctor.test.js +52 -0
  27. package/dist/commands/login.js +48 -69
  28. package/dist/commands/login.test.js +122 -0
  29. package/dist/commands/logout.js +13 -31
  30. package/dist/commands/logout.test.js +47 -0
  31. package/dist/commands/manifests/generate.js +20 -38
  32. package/dist/commands/manifests/generate.test.js +60 -0
  33. package/dist/commands/whoami.js +13 -30
  34. package/dist/commands/whoami.test.js +30 -0
  35. package/dist/config/consts.js +4 -5
  36. package/dist/config/index.js +1 -17
  37. package/dist/index.js +50 -80
  38. package/dist/services/app-bundle-files.js +117 -136
  39. package/dist/services/app-bundles.js +22 -41
  40. package/dist/services/app-channels.js +54 -77
  41. package/dist/services/app-devices.js +10 -25
  42. package/dist/services/apps.js +25 -41
  43. package/dist/services/authorization-service.js +4 -8
  44. package/dist/services/config.js +15 -28
  45. package/dist/services/organizations.js +18 -0
  46. package/dist/services/session-code.js +7 -22
  47. package/dist/services/sessions.js +13 -30
  48. package/dist/services/update.js +17 -55
  49. package/dist/services/users.js +11 -26
  50. package/dist/types/app-bundle-file.js +1 -2
  51. package/dist/types/app-bundle.js +1 -2
  52. package/dist/types/app-channel.js +1 -2
  53. package/dist/types/app-device.js +1 -2
  54. package/dist/types/app.js +1 -2
  55. package/dist/types/index.js +8 -23
  56. package/dist/types/npm-package.js +1 -2
  57. package/dist/types/organization.js +1 -0
  58. package/dist/types/session-code.js +1 -2
  59. package/dist/types/session.js +1 -2
  60. package/dist/types/user.js +1 -2
  61. package/dist/utils/buffer.js +12 -43
  62. package/dist/utils/error.js +24 -14
  63. package/dist/utils/file.js +22 -41
  64. package/dist/utils/hash.js +3 -39
  65. package/dist/utils/http-client.js +27 -53
  66. package/dist/utils/manifest.js +11 -24
  67. package/dist/utils/prompt.js +9 -26
  68. package/dist/utils/signature.js +3 -39
  69. package/dist/utils/userConfig.js +5 -9
  70. package/dist/utils/zip.js +11 -27
  71. package/package.json +23 -10
  72. package/dist/utils/ci.js +0 -7
@@ -1,46 +1,26 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const axios_1 = require("axios");
16
- const citty_1 = require("citty");
17
- const consola_1 = __importDefault(require("consola"));
18
- const open_1 = __importDefault(require("open"));
19
- const config_1 = __importDefault(require("../services/config"));
20
- const session_code_1 = __importDefault(require("../services/session-code"));
21
- const sessions_1 = __importDefault(require("../services/sessions"));
22
- const users_1 = __importDefault(require("../services/users"));
23
- const error_1 = require("../utils/error");
24
- const prompt_1 = require("../utils/prompt");
25
- const userConfig_1 = __importDefault(require("../utils/userConfig"));
26
- exports.default = (0, citty_1.defineCommand)({
27
- meta: {
28
- name: 'login',
29
- description: 'Sign in to the Capawesome Cloud Console.',
30
- },
31
- args: {
32
- token: {
33
- type: 'string',
34
- description: 'Token to use for authentication.',
35
- },
36
- },
37
- run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
38
- var _a;
39
- const consoleBaseUrl = yield config_1.default.getValueForKey('CONSOLE_BASE_URL');
40
- let sessionIdOrToken = ctx.args.token;
1
+ import { defineCommand, defineOptions } from '@robingenz/zli';
2
+ import { AxiosError } from 'axios';
3
+ import consola from 'consola';
4
+ import open from 'open';
5
+ import { z } from 'zod';
6
+ import configService from '../services/config.js';
7
+ import sessionCodesService from '../services/session-code.js';
8
+ import sessionsService from '../services/sessions.js';
9
+ import usersService from '../services/users.js';
10
+ import { getMessageFromUnknownError } from '../utils/error.js';
11
+ import { prompt } from '../utils/prompt.js';
12
+ import userConfig from '../utils/userConfig.js';
13
+ export default defineCommand({
14
+ description: 'Sign in to the Capawesome Cloud Console.',
15
+ options: defineOptions(z.object({
16
+ token: z.string().optional().describe('Token to use for authentication.'),
17
+ })),
18
+ action: async (options, args) => {
19
+ const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
20
+ let { token: sessionIdOrToken } = options;
41
21
  if (sessionIdOrToken === undefined) {
42
22
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
43
- const authenticationMethod = yield (0, prompt_1.prompt)('How would you like to authenticate Capawesome CLI?', {
23
+ const authenticationMethod = await prompt('How would you like to authenticate Capawesome CLI?', {
44
24
  type: 'select',
45
25
  options: [
46
26
  { label: 'Login with a web browser', value: 'browser' },
@@ -49,89 +29,88 @@ exports.default = (0, citty_1.defineCommand)({
49
29
  });
50
30
  if (authenticationMethod === 'browser') {
51
31
  // Create a session code
52
- const { id: deviceCode, code: userCode } = yield session_code_1.default.create();
53
- consola_1.default.box(`Copy your one-time code: ${userCode.slice(0, 4)}-${userCode.slice(4)}`);
32
+ const { id: deviceCode, code: userCode } = await sessionCodesService.create();
33
+ consola.box(`Copy your one-time code: ${userCode.slice(0, 4)}-${userCode.slice(4)}`);
54
34
  // Prompt the user to open the authorization URL in their browser
55
- const shouldProceed = yield (0, prompt_1.prompt)(`Select Yes to continue in your browser or No to cancel the authentication.`, {
35
+ const shouldProceed = await prompt(`Select Yes to continue in your browser or No to cancel the authentication.`, {
56
36
  type: 'confirm',
57
37
  initial: true,
58
38
  });
59
39
  if (!shouldProceed) {
60
- consola_1.default.error('Authentication cancelled.');
40
+ consola.error('Authentication cancelled.');
61
41
  process.exit(1);
62
42
  }
63
43
  // Open the authorization URL in the user's default browser
64
- consola_1.default.start('Opening browser...');
44
+ consola.start('Opening browser...');
65
45
  const authorizationUrl = `${consoleBaseUrl}/login/device`;
66
46
  try {
67
- (0, open_1.default)(authorizationUrl);
47
+ open(authorizationUrl);
68
48
  }
69
49
  catch (error) {
70
- consola_1.default.warn(`Could not open browser automatically. Please open the following URL manually: ${authorizationUrl}`);
50
+ consola.warn(`Could not open browser automatically. Please open the following URL manually: ${authorizationUrl}`);
71
51
  }
72
52
  // Wait for the user to authenticate
73
- consola_1.default.start('Waiting for authentication...');
74
- const sessionId = yield createSession(deviceCode);
53
+ consola.start('Waiting for authentication...');
54
+ const sessionId = await createSession(deviceCode);
75
55
  if (!sessionId) {
76
- consola_1.default.error('Authentication timed out. Please try again.');
56
+ consola.error('Authentication timed out. Please try again.');
77
57
  process.exit(1);
78
58
  }
79
59
  sessionIdOrToken = sessionId;
80
60
  }
81
61
  else {
82
- sessionIdOrToken = yield (0, prompt_1.prompt)('Please provide your authentication token:', {
62
+ sessionIdOrToken = await prompt('Please provide your authentication token:', {
83
63
  type: 'text',
84
64
  });
85
65
  if (!sessionIdOrToken) {
86
- consola_1.default.error('Token must be provided.');
66
+ consola.error('Token must be provided.');
87
67
  process.exit(1);
88
68
  }
89
69
  }
90
70
  }
91
71
  else if (sessionIdOrToken.length === 0) {
92
72
  // No token provided
93
- consola_1.default.error(`Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
73
+ consola.error(`Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
94
74
  process.exit(1);
95
75
  }
96
76
  // Sign in with the provided token
97
- consola_1.default.start('Signing in...');
98
- userConfig_1.default.write({
77
+ consola.start('Signing in...');
78
+ userConfig.write({
99
79
  token: sessionIdOrToken,
100
80
  });
101
81
  try {
102
- yield users_1.default.me();
103
- consola_1.default.success(`Successfully signed in.`);
82
+ await usersService.me();
83
+ consola.success(`Successfully signed in.`);
104
84
  }
105
85
  catch (error) {
106
- userConfig_1.default.write({});
107
- let message = (0, error_1.getMessageFromUnknownError)(error);
108
- if (error instanceof axios_1.AxiosError && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
86
+ userConfig.write({});
87
+ let message = getMessageFromUnknownError(error);
88
+ if (error instanceof AxiosError && error.response?.status === 401) {
109
89
  message = `Invalid token. Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`;
110
90
  }
111
- consola_1.default.error(message);
91
+ consola.error(message);
112
92
  process.exit(1);
113
93
  }
114
- }),
94
+ },
115
95
  });
116
- const createSession = (deviceCode) => __awaiter(void 0, void 0, void 0, function* () {
117
- var _a;
96
+ const createSession = async (deviceCode) => {
118
97
  const maxAttempts = 20;
119
98
  const interval = 3 * 1000; // 3 seconds
120
99
  let attempts = 0;
121
100
  let sessionId = null;
122
101
  while (attempts < maxAttempts && sessionId === null) {
123
102
  try {
124
- const response = yield sessions_1.default.create({
103
+ const response = await sessionsService.create({
125
104
  code: deviceCode,
126
105
  provider: 'code',
127
106
  });
128
107
  sessionId = response.id;
129
108
  }
130
109
  catch (error) {
131
- if (error instanceof axios_1.AxiosError && ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 400) {
110
+ if (error instanceof AxiosError && error.response?.status === 400) {
132
111
  // Session not ready yet, wait and try again
133
112
  attempts++;
134
- yield new Promise((resolve) => setTimeout(resolve, interval));
113
+ await new Promise((resolve) => setTimeout(resolve, interval));
135
114
  }
136
115
  else {
137
116
  throw error;
@@ -139,4 +118,4 @@ const createSession = (deviceCode) => __awaiter(void 0, void 0, void 0, function
139
118
  }
140
119
  }
141
120
  return sessionId;
142
- });
121
+ };
@@ -0,0 +1,122 @@
1
+ import { DEFAULT_API_BASE_URL, DEFAULT_CONSOLE_BASE_URL } from '../config/consts.js';
2
+ import configService from '../services/config.js';
3
+ import sessionCodesService from '../services/session-code.js';
4
+ import sessionsService from '../services/sessions.js';
5
+ import { prompt } from '../utils/prompt.js';
6
+ import userConfig from '../utils/userConfig.js';
7
+ import consola from 'consola';
8
+ import nock from 'nock';
9
+ import open from 'open';
10
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
11
+ import loginCommand from './login.js';
12
+ // Mock dependencies
13
+ vi.mock('@/utils/userConfig.js');
14
+ vi.mock('@/services/session-code.js');
15
+ vi.mock('@/services/sessions.js');
16
+ vi.mock('@/services/config.js');
17
+ vi.mock('open');
18
+ vi.mock('consola');
19
+ vi.mock('@/utils/prompt.js');
20
+ describe('login', () => {
21
+ const mockUserConfig = vi.mocked(userConfig);
22
+ const mockSessionCodesService = vi.mocked(sessionCodesService);
23
+ const mockSessionsService = vi.mocked(sessionsService);
24
+ const mockConfigService = vi.mocked(configService);
25
+ const mockOpen = vi.mocked(open);
26
+ const mockConsola = vi.mocked(consola);
27
+ const mockPrompt = vi.mocked(prompt);
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ mockUserConfig.write.mockImplementation(() => { });
31
+ mockUserConfig.read.mockReturnValue({});
32
+ // Mock config service to return consistent URLs
33
+ mockConfigService.getValueForKey.mockImplementation((key) => {
34
+ if (key === 'CONSOLE_BASE_URL')
35
+ return Promise.resolve(DEFAULT_CONSOLE_BASE_URL);
36
+ if (key === 'API_BASE_URL')
37
+ return Promise.resolve(DEFAULT_API_BASE_URL);
38
+ return Promise.resolve('');
39
+ });
40
+ vi.spyOn(process, 'exit').mockImplementation(() => undefined);
41
+ });
42
+ afterEach(() => {
43
+ nock.cleanAll();
44
+ vi.restoreAllMocks();
45
+ });
46
+ it('should use the provided token for authentication', async () => {
47
+ const testToken = 'valid-token-123';
48
+ const options = { token: testToken };
49
+ // Mock userConfig.read to return our test token after it's written
50
+ mockUserConfig.read.mockReturnValue({ token: testToken });
51
+ // Set up nock to intercept the /v1/users/me request
52
+ const scope = nock(DEFAULT_API_BASE_URL)
53
+ .get('/v1/users/me')
54
+ .matchHeader('Authorization', `Bearer ${testToken}`)
55
+ .reply(200, { id: 'user-123', email: 'test@example.com' });
56
+ await loginCommand.action(options, undefined);
57
+ expect(mockUserConfig.write).toHaveBeenCalledWith({ token: testToken });
58
+ expect(scope.isDone()).toBe(true);
59
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
60
+ });
61
+ it('should open the browser', async () => {
62
+ const options = {};
63
+ mockPrompt
64
+ .mockResolvedValueOnce('browser') // authentication method
65
+ .mockResolvedValueOnce(true); // should proceed
66
+ mockSessionCodesService.create.mockResolvedValue({
67
+ id: 'device-code-123',
68
+ code: 'ABCD1234',
69
+ });
70
+ mockSessionsService.create.mockResolvedValue({ id: 'session-123' });
71
+ // Mock userConfig.read to return the session token
72
+ mockUserConfig.read.mockReturnValue({ token: 'session-123' });
73
+ // Set up nock to intercept the /v1/users/me request
74
+ const scope = nock(DEFAULT_API_BASE_URL)
75
+ .get('/v1/users/me')
76
+ .matchHeader('Authorization', `Bearer session-123`)
77
+ .reply(200, { id: 'user-123', email: 'test@example.com' });
78
+ await loginCommand.action(options, undefined);
79
+ expect(mockPrompt).toHaveBeenCalledWith('How would you like to authenticate Capawesome CLI?', {
80
+ type: 'select',
81
+ options: [
82
+ { label: 'Login with a web browser', value: 'browser' },
83
+ { label: 'Paste an authentication token', value: 'token' },
84
+ ],
85
+ });
86
+ expect(mockSessionCodesService.create).toHaveBeenCalled();
87
+ expect(mockConsola.box).toHaveBeenCalledWith('Copy your one-time code: ABCD-1234');
88
+ expect(mockOpen).toHaveBeenCalledWith(`${DEFAULT_CONSOLE_BASE_URL}/login/device`);
89
+ expect(scope.isDone()).toBe(true);
90
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
91
+ });
92
+ it('should throw an error because the provided token is empty', async () => {
93
+ const options = { token: '' };
94
+ // Mock userConfig.read to return empty token
95
+ mockUserConfig.read.mockReturnValue({ token: '' });
96
+ // Set up nock to intercept potential API call (should get 401)
97
+ const scope = nock(DEFAULT_API_BASE_URL)
98
+ .get('/v1/users/me')
99
+ .matchHeader('Authorization', 'Bearer ')
100
+ .reply(401, { message: 'Unauthorized' });
101
+ await loginCommand.action(options, undefined);
102
+ expect(mockConsola.error).toHaveBeenCalledWith(`Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
103
+ expect(process.exit).toHaveBeenCalledWith(1);
104
+ });
105
+ it('should throw an error because the provided token is invalid', async () => {
106
+ const invalidToken = 'invalid-token';
107
+ const options = { token: invalidToken };
108
+ // Mock userConfig.read to return our invalid token after it's written
109
+ mockUserConfig.read.mockReturnValue({ token: invalidToken });
110
+ // Set up nock to intercept the /v1/users/me request and return 401
111
+ const scope = nock(DEFAULT_API_BASE_URL)
112
+ .get('/v1/users/me')
113
+ .matchHeader('Authorization', `Bearer ${invalidToken}`)
114
+ .reply(401, { message: 'Unauthorized' });
115
+ await loginCommand.action(options, undefined);
116
+ expect(mockUserConfig.write).toHaveBeenCalledWith({ token: invalidToken });
117
+ expect(mockUserConfig.write).toHaveBeenCalledWith({}); // Clears token on error
118
+ expect(scope.isDone()).toBe(true);
119
+ expect(mockConsola.error).toHaveBeenCalledWith(`Invalid token. Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
120
+ expect(process.exit).toHaveBeenCalledWith(1);
121
+ });
122
+ });
@@ -1,34 +1,16 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const citty_1 = require("citty");
16
- const consola_1 = __importDefault(require("consola"));
17
- const authorization_service_1 = __importDefault(require("../services/authorization-service"));
18
- const sessions_1 = __importDefault(require("../services/sessions"));
19
- const userConfig_1 = __importDefault(require("../utils/userConfig"));
20
- exports.default = (0, citty_1.defineCommand)({
21
- meta: {
22
- name: 'logout',
23
- description: 'Sign out from the Capawesome Cloud Console.',
24
- },
25
- args: {},
26
- run: () => __awaiter(void 0, void 0, void 0, function* () {
27
- const token = authorization_service_1.default.getCurrentAuthorizationToken();
1
+ import { defineCommand } from '@robingenz/zli';
2
+ import consola from 'consola';
3
+ import authorizationService from '../services/authorization-service.js';
4
+ import sessionsService from '../services/sessions.js';
5
+ import userConfig from '../utils/userConfig.js';
6
+ export default defineCommand({
7
+ description: 'Sign out from the Capawesome Cloud Console.',
8
+ action: async (options, args) => {
9
+ const token = authorizationService.getCurrentAuthorizationToken();
28
10
  if (token && !token.startsWith('ca_')) {
29
- yield sessions_1.default.delete({ id: token }).catch(() => { });
11
+ await sessionsService.delete({ id: token }).catch(() => { });
30
12
  }
31
- userConfig_1.default.write({});
32
- consola_1.default.success('Successfully signed out.');
33
- }),
13
+ userConfig.write({});
14
+ consola.success('Successfully signed out.');
15
+ },
34
16
  });
@@ -0,0 +1,47 @@
1
+ import { DEFAULT_API_BASE_URL } from '../config/consts.js';
2
+ import authorizationService from '../services/authorization-service.js';
3
+ import userConfig from '../utils/userConfig.js';
4
+ import consola from 'consola';
5
+ import nock from 'nock';
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import logoutCommand from './logout.js';
8
+ // Mock dependencies
9
+ vi.mock('@/services/authorization-service.js');
10
+ vi.mock('@/utils/userConfig.js');
11
+ vi.mock('consola');
12
+ describe('logout', () => {
13
+ const mockAuthorizationService = vi.mocked(authorizationService);
14
+ const mockUserConfig = vi.mocked(userConfig);
15
+ const mockConsola = vi.mocked(consola);
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ mockUserConfig.write.mockImplementation(() => { });
19
+ });
20
+ afterEach(() => {
21
+ nock.cleanAll();
22
+ vi.restoreAllMocks();
23
+ });
24
+ it('should delete session and clear user config with session token', async () => {
25
+ const sessionToken = 'session-123';
26
+ mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(sessionToken);
27
+ // Set up nock to intercept the DELETE request
28
+ const scope = nock(DEFAULT_API_BASE_URL).delete('/v1/sessions/session-123').reply(200);
29
+ await logoutCommand.action({}, undefined);
30
+ expect(scope.isDone()).toBe(true);
31
+ expect(mockUserConfig.write).toHaveBeenCalledWith({});
32
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
33
+ });
34
+ it('should only clear user config with API token', async () => {
35
+ const apiToken = 'ca_abc123';
36
+ mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(apiToken);
37
+ await logoutCommand.action({}, undefined);
38
+ expect(mockUserConfig.write).toHaveBeenCalledWith({});
39
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
40
+ });
41
+ it('should handle no token gracefully', async () => {
42
+ mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue(null);
43
+ await logoutCommand.action({}, undefined);
44
+ expect(mockUserConfig.write).toHaveBeenCalledWith({});
45
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed out.');
46
+ });
47
+ });
@@ -1,51 +1,33 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const citty_1 = require("citty");
16
- const consola_1 = __importDefault(require("consola"));
17
- const file_1 = require("../../utils/file");
18
- const manifest_1 = require("../../utils/manifest");
19
- const prompt_1 = require("../../utils/prompt");
20
- exports.default = (0, citty_1.defineCommand)({
21
- meta: {
22
- description: 'Generate a manifest file.',
23
- },
24
- args: {
25
- path: {
26
- type: 'string',
27
- description: 'Path to the web assets folder (e.g. `www` or `dist`).',
28
- },
29
- },
30
- run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
31
- let path = ctx.args.path;
1
+ import { defineCommand, defineOptions } from '@robingenz/zli';
2
+ import consola from 'consola';
3
+ import { z } from 'zod';
4
+ import { fileExistsAtPath } from '../../utils/file.js';
5
+ import { generateManifestJson } from '../../utils/manifest.js';
6
+ import { prompt } from '../../utils/prompt.js';
7
+ export default defineCommand({
8
+ description: 'Generate a manifest file.',
9
+ options: defineOptions(z.object({
10
+ path: z.string().optional().describe('Path to the web assets folder (e.g. `www` or `dist`).'),
11
+ })),
12
+ action: async (options, args) => {
13
+ let path = options.path;
32
14
  if (!path) {
33
- path = yield (0, prompt_1.prompt)('Enter the path to the web assets folder:', {
15
+ path = await prompt('Enter the path to the web assets folder:', {
34
16
  type: 'text',
35
17
  });
36
18
  if (!path) {
37
- consola_1.default.error('You must provide a path to the web assets folder.');
19
+ consola.error('You must provide a path to the web assets folder.');
38
20
  process.exit(1);
39
21
  }
40
22
  }
41
23
  // Check if the path exists
42
- const pathExists = yield (0, file_1.fileExistsAtPath)(path);
24
+ const pathExists = await fileExistsAtPath(path);
43
25
  if (!pathExists) {
44
- consola_1.default.error(`The path does not exist.`);
26
+ consola.error(`The path does not exist.`);
45
27
  process.exit(1);
46
28
  }
47
29
  // Generate the manifest file
48
- yield (0, manifest_1.generateManifestJson)(path);
49
- consola_1.default.success('Manifest file generated.');
50
- }),
30
+ await generateManifestJson(path);
31
+ consola.success('Manifest file generated.');
32
+ },
51
33
  });
@@ -0,0 +1,60 @@
1
+ import { fileExistsAtPath } from '../../utils/file.js';
2
+ import { generateManifestJson } from '../../utils/manifest.js';
3
+ import { prompt } from '../../utils/prompt.js';
4
+ import consola from 'consola';
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import generateManifestCommand from './generate.js';
7
+ // Mock dependencies
8
+ vi.mock('@/utils/file.js');
9
+ vi.mock('@/utils/manifest.js');
10
+ vi.mock('@/utils/prompt.js');
11
+ vi.mock('consola');
12
+ describe('manifests-generate', () => {
13
+ const mockFileExistsAtPath = vi.mocked(fileExistsAtPath);
14
+ const mockGenerateManifestJson = vi.mocked(generateManifestJson);
15
+ const mockPrompt = vi.mocked(prompt);
16
+ const mockConsola = vi.mocked(consola);
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ vi.spyOn(process, 'exit').mockImplementation(() => undefined);
20
+ });
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+ it('should generate manifest with provided path', async () => {
25
+ const options = { path: './dist' };
26
+ mockFileExistsAtPath.mockResolvedValue(true);
27
+ mockGenerateManifestJson.mockResolvedValue(undefined);
28
+ await generateManifestCommand.action(options, undefined);
29
+ expect(mockFileExistsAtPath).toHaveBeenCalledWith('./dist');
30
+ expect(mockGenerateManifestJson).toHaveBeenCalledWith('./dist');
31
+ expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
32
+ });
33
+ it('should prompt for path when not provided', async () => {
34
+ const options = {};
35
+ mockPrompt.mockResolvedValueOnce('./www');
36
+ mockFileExistsAtPath.mockResolvedValue(true);
37
+ mockGenerateManifestJson.mockResolvedValue(undefined);
38
+ await generateManifestCommand.action(options, undefined);
39
+ expect(mockPrompt).toHaveBeenCalledWith('Enter the path to the web assets folder:', {
40
+ type: 'text',
41
+ });
42
+ expect(mockFileExistsAtPath).toHaveBeenCalledWith('./www');
43
+ expect(mockGenerateManifestJson).toHaveBeenCalledWith('./www');
44
+ expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
45
+ });
46
+ it('should handle missing path in prompt', async () => {
47
+ const options = {};
48
+ mockPrompt.mockResolvedValueOnce('');
49
+ await generateManifestCommand.action(options, undefined);
50
+ expect(mockConsola.error).toHaveBeenCalledWith('You must provide a path to the web assets folder.');
51
+ expect(process.exit).toHaveBeenCalledWith(1);
52
+ });
53
+ it('should handle nonexistent path', async () => {
54
+ const options = { path: './nonexistent' };
55
+ mockFileExistsAtPath.mockResolvedValue(false);
56
+ await generateManifestCommand.action(options, undefined);
57
+ expect(mockConsola.error).toHaveBeenCalledWith('The path does not exist.');
58
+ expect(process.exit).toHaveBeenCalledWith(1);
59
+ });
60
+ });
@@ -1,41 +1,24 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- const citty_1 = require("citty");
16
- const consola_1 = __importDefault(require("consola"));
17
- const users_1 = __importDefault(require("../services/users"));
18
- const userConfig_1 = __importDefault(require("../utils/userConfig"));
19
- exports.default = (0, citty_1.defineCommand)({
20
- meta: {
21
- name: 'whoami',
22
- description: 'Show current user',
23
- },
24
- run: () => __awaiter(void 0, void 0, void 0, function* () {
25
- const { token } = userConfig_1.default.read();
1
+ import { defineCommand } from '@robingenz/zli';
2
+ import consola from 'consola';
3
+ import usersService from '../services/users.js';
4
+ import userConfig from '../utils/userConfig.js';
5
+ export default defineCommand({
6
+ description: 'Show current user',
7
+ action: async (options, args) => {
8
+ const { token } = userConfig.read();
26
9
  if (token) {
27
10
  try {
28
- const user = yield users_1.default.me();
29
- consola_1.default.info(`Logged in as ${user.email}.`);
11
+ const user = await usersService.me();
12
+ consola.info(`Logged in as ${user.email}.`);
30
13
  }
31
14
  catch (error) {
32
- consola_1.default.error('Token is invalid. Please sign in again.');
15
+ consola.error('Token is invalid. Please sign in again.');
33
16
  process.exit(1);
34
17
  }
35
18
  }
36
19
  else {
37
- consola_1.default.error('Not logged in.');
20
+ consola.error('Not logged in.');
38
21
  process.exit(1);
39
22
  }
40
- }),
23
+ },
41
24
  });
@@ -0,0 +1,30 @@
1
+ import { DEFAULT_API_BASE_URL } from '../config/consts.js';
2
+ import userConfig from '../utils/userConfig.js';
3
+ import nock from 'nock';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import whoamiCommand from './whoami.js';
6
+ // Mock only the dependencies we need to control
7
+ vi.mock('@/utils/userConfig.js');
8
+ describe('whoami', () => {
9
+ const mockUserConfig = vi.mocked(userConfig);
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ vi.spyOn(process, 'exit').mockImplementation(() => undefined);
13
+ });
14
+ afterEach(() => {
15
+ nock.cleanAll();
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should send Bearer token in Authorization header when checking current user', async () => {
19
+ const testToken = 'user-token-456';
20
+ // Mock userConfig.read to return our test token
21
+ mockUserConfig.read.mockReturnValue({ token: testToken });
22
+ // Set up nock to intercept the /v1/users/me request
23
+ const scope = nock(DEFAULT_API_BASE_URL)
24
+ .get('/v1/users/me')
25
+ .matchHeader('Authorization', `Bearer ${testToken}`)
26
+ .reply(200, { id: 'user-456', email: 'user@example.com' });
27
+ await whoamiCommand.action({}, undefined);
28
+ expect(scope.isDone()).toBe(true);
29
+ });
30
+ });
@@ -1,5 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_CONCURRENT_UPLOADS = exports.MANIFEST_JSON_FILE_NAME = void 0;
4
- exports.MANIFEST_JSON_FILE_NAME = 'capawesome-live-update-manifest.json'; // Do NOT change this!
5
- exports.MAX_CONCURRENT_UPLOADS = 20;
1
+ export const DEFAULT_API_BASE_URL = 'https://api.cloud.capawesome.io';
2
+ export const DEFAULT_CONSOLE_BASE_URL = 'https://console.cloud.capawesome.io';
3
+ export const MANIFEST_JSON_FILE_NAME = 'capawesome-live-update-manifest.json'; // Do NOT change this!
4
+ export const MAX_CONCURRENT_UPLOADS = 20;