@capawesome/cli 1.14.0 → 2.0.1

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 (76) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +7 -3
  3. package/dist/commands/apps/bundles/create.js +206 -239
  4. package/dist/commands/apps/bundles/create.test.js +276 -0
  5. package/dist/commands/apps/bundles/delete.js +35 -60
  6. package/dist/commands/apps/bundles/delete.test.js +139 -0
  7. package/dist/commands/apps/bundles/update.js +61 -89
  8. package/dist/commands/apps/bundles/update.test.js +141 -0
  9. package/dist/commands/apps/channels/create.js +45 -75
  10. package/dist/commands/apps/channels/create.test.js +119 -0
  11. package/dist/commands/apps/channels/delete.js +46 -69
  12. package/dist/commands/apps/channels/delete.test.js +141 -0
  13. package/dist/commands/apps/channels/get.js +52 -94
  14. package/dist/commands/apps/channels/get.test.js +135 -0
  15. package/dist/commands/apps/channels/list.js +37 -82
  16. package/dist/commands/apps/channels/list.test.js +121 -0
  17. package/dist/commands/apps/channels/update.js +39 -83
  18. package/dist/commands/apps/channels/update.test.js +138 -0
  19. package/dist/commands/apps/create.js +28 -53
  20. package/dist/commands/apps/create.test.js +117 -0
  21. package/dist/commands/apps/delete.js +29 -50
  22. package/dist/commands/apps/delete.test.js +120 -0
  23. package/dist/commands/apps/devices/delete.js +35 -60
  24. package/dist/commands/apps/devices/delete.test.js +139 -0
  25. package/dist/commands/doctor.js +12 -29
  26. package/dist/commands/doctor.test.js +52 -0
  27. package/dist/commands/login.js +50 -71
  28. package/dist/commands/login.test.js +116 -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/organizations/create.js +25 -0
  34. package/dist/commands/organizations/create.test.js +80 -0
  35. package/dist/commands/whoami.js +20 -31
  36. package/dist/commands/whoami.test.js +30 -0
  37. package/dist/config/consts.js +4 -5
  38. package/dist/config/index.js +1 -17
  39. package/dist/index.js +59 -80
  40. package/dist/services/app-bundle-files.js +117 -136
  41. package/dist/services/app-bundles.js +22 -41
  42. package/dist/services/app-channels.js +54 -77
  43. package/dist/services/app-devices.js +10 -25
  44. package/dist/services/apps.js +25 -43
  45. package/dist/services/authorization-service.js +4 -8
  46. package/dist/services/config.js +15 -28
  47. package/dist/services/organizations.js +19 -26
  48. package/dist/services/session-code.js +7 -22
  49. package/dist/services/sessions.js +13 -30
  50. package/dist/services/update.js +17 -55
  51. package/dist/services/users.js +11 -26
  52. package/dist/types/app-bundle-file.js +1 -2
  53. package/dist/types/app-bundle.js +1 -2
  54. package/dist/types/app-channel.js +1 -2
  55. package/dist/types/app-device.js +1 -2
  56. package/dist/types/app.js +1 -2
  57. package/dist/types/index.js +8 -24
  58. package/dist/types/npm-package.js +1 -2
  59. package/dist/types/organization.js +1 -2
  60. package/dist/types/session-code.js +1 -2
  61. package/dist/types/session.js +1 -2
  62. package/dist/types/user.js +1 -2
  63. package/dist/utils/buffer.js +12 -43
  64. package/dist/utils/error.js +24 -14
  65. package/dist/utils/file.js +22 -41
  66. package/dist/utils/hash.js +3 -39
  67. package/dist/utils/http-client.js +27 -53
  68. package/dist/utils/manifest.js +11 -24
  69. package/dist/utils/private-key.js +23 -0
  70. package/dist/utils/prompt.js +9 -26
  71. package/dist/utils/signature.js +3 -39
  72. package/dist/utils/user-config.js +12 -0
  73. package/dist/utils/zip.js +11 -27
  74. package/package.json +22 -9
  75. package/dist/utils/ci.js +0 -7
  76. package/dist/utils/userConfig.js +0 -16
@@ -1,34 +1,17 @@
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 package_json_1 = __importDefault(require("../../package.json"));
18
- const systeminformation_1 = __importDefault(require("systeminformation"));
19
- exports.default = (0, citty_1.defineCommand)({
20
- meta: {
21
- name: 'doctor',
22
- description: 'Prints out neccessary information for debugging',
23
- },
24
- run: () => __awaiter(void 0, void 0, void 0, function* () {
25
- const osInfo = yield systeminformation_1.default.osInfo();
26
- const versions = yield systeminformation_1.default.versions('npm, node');
27
- consola_1.default.box([
1
+ import { defineCommand } from '@robingenz/zli';
2
+ import consola from 'consola';
3
+ import systeminformation from 'systeminformation';
4
+ import pkg from '../../package.json' with { type: 'json' };
5
+ export default defineCommand({
6
+ description: 'Prints out neccessary information for debugging',
7
+ action: async (options, args) => {
8
+ const osInfo = await systeminformation.osInfo();
9
+ const versions = await systeminformation.versions('npm, node');
10
+ consola.box([
28
11
  `NodeJS version: ${versions.node}`,
29
12
  `NPM version: ${versions.npm}`,
30
- `CLI version: ${package_json_1.default.version}`,
13
+ `CLI version: ${pkg.version}`,
31
14
  `OS: ${osInfo.distro} ${osInfo.release} ${osInfo.codename ? `(${osInfo.codename})` : ''}`,
32
15
  ].join('\n'));
33
- }),
16
+ },
34
17
  });
@@ -0,0 +1,52 @@
1
+ import consola from 'consola';
2
+ import systeminformation from 'systeminformation';
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+ import doctorCommand from './doctor.js';
5
+ // Mock dependencies
6
+ vi.mock('consola');
7
+ vi.mock('systeminformation');
8
+ describe('doctor', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockSystemInformation = vi.mocked(systeminformation);
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+ afterEach(() => {
15
+ vi.restoreAllMocks();
16
+ });
17
+ it('should display system information', async () => {
18
+ const mockOsInfo = {
19
+ distro: 'macOS',
20
+ release: '14.0',
21
+ codename: 'Sonoma',
22
+ };
23
+ const mockVersions = {
24
+ node: '18.17.0',
25
+ npm: '9.6.7',
26
+ };
27
+ mockSystemInformation.osInfo.mockResolvedValue(mockOsInfo);
28
+ mockSystemInformation.versions.mockResolvedValue(mockVersions);
29
+ await doctorCommand.action({}, undefined);
30
+ expect(mockSystemInformation.osInfo).toHaveBeenCalled();
31
+ expect(mockSystemInformation.versions).toHaveBeenCalledWith('npm, node');
32
+ expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('NodeJS version: 18.17.0'));
33
+ expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('NPM version: 9.6.7'));
34
+ expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('OS: macOS 14.0 (Sonoma)'));
35
+ });
36
+ it('should handle OS info without codename', async () => {
37
+ const mockOsInfo = {
38
+ distro: 'Ubuntu',
39
+ release: '22.04',
40
+ codename: '',
41
+ };
42
+ const mockVersions = {
43
+ node: '20.0.0',
44
+ npm: '10.0.0',
45
+ };
46
+ mockSystemInformation.osInfo.mockResolvedValue(mockOsInfo);
47
+ mockSystemInformation.versions.mockResolvedValue(mockVersions);
48
+ await doctorCommand.action({}, undefined);
49
+ expect(mockConsola.box).toHaveBeenCalledWith(expect.stringContaining('OS: Ubuntu 22.04'));
50
+ expect(mockConsola.box).toHaveBeenCalledWith(expect.not.stringContaining('()'));
51
+ });
52
+ });
@@ -1,46 +1,25 @@
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 configService from '../services/config.js';
2
+ import sessionCodesService from '../services/session-code.js';
3
+ import sessionsService from '../services/sessions.js';
4
+ import usersService from '../services/users.js';
5
+ import { prompt } from '../utils/prompt.js';
6
+ import userConfig from '../utils/user-config.js';
7
+ import { defineCommand, defineOptions } from '@robingenz/zli';
8
+ import { AxiosError } from 'axios';
9
+ import consola from 'consola';
10
+ import open from 'open';
11
+ import { z } from 'zod';
12
+ export default defineCommand({
13
+ description: 'Sign in to the Capawesome Cloud Console.',
14
+ options: defineOptions(z.object({
15
+ token: z.string().optional().describe('Token to use for authentication.'),
16
+ })),
17
+ action: async (options, args) => {
18
+ const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
19
+ let { token: sessionIdOrToken } = options;
41
20
  if (sessionIdOrToken === undefined) {
42
21
  // @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?', {
22
+ const authenticationMethod = await prompt('How would you like to authenticate Capawesome CLI?', {
44
23
  type: 'select',
45
24
  options: [
46
25
  { label: 'Login with a web browser', value: 'browser' },
@@ -49,89 +28,89 @@ exports.default = (0, citty_1.defineCommand)({
49
28
  });
50
29
  if (authenticationMethod === 'browser') {
51
30
  // 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)}`);
31
+ const { id: deviceCode, code: userCode } = await sessionCodesService.create();
32
+ consola.box(`Copy your one-time code: ${userCode.slice(0, 4)}-${userCode.slice(4)}`);
54
33
  // 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.`, {
34
+ const shouldProceed = await prompt(`Select Yes to continue in your browser or No to cancel the authentication.`, {
56
35
  type: 'confirm',
57
36
  initial: true,
58
37
  });
59
38
  if (!shouldProceed) {
60
- consola_1.default.error('Authentication cancelled.');
39
+ consola.error('Authentication cancelled.');
61
40
  process.exit(1);
62
41
  }
63
42
  // Open the authorization URL in the user's default browser
64
- consola_1.default.start('Opening browser...');
43
+ consola.start('Opening browser...');
65
44
  const authorizationUrl = `${consoleBaseUrl}/login/device`;
66
45
  try {
67
- (0, open_1.default)(authorizationUrl);
46
+ open(authorizationUrl);
68
47
  }
69
48
  catch (error) {
70
- consola_1.default.warn(`Could not open browser automatically. Please open the following URL manually: ${authorizationUrl}`);
49
+ consola.warn(`Could not open browser automatically. Please open the following URL manually: ${authorizationUrl}`);
71
50
  }
72
51
  // Wait for the user to authenticate
73
- consola_1.default.start('Waiting for authentication...');
74
- const sessionId = yield createSession(deviceCode);
52
+ consola.start('Waiting for authentication...');
53
+ const sessionId = await createSession(deviceCode);
75
54
  if (!sessionId) {
76
- consola_1.default.error('Authentication timed out. Please try again.');
55
+ consola.error('Authentication timed out. Please try again.');
77
56
  process.exit(1);
78
57
  }
79
58
  sessionIdOrToken = sessionId;
80
59
  }
81
60
  else {
82
- sessionIdOrToken = yield (0, prompt_1.prompt)('Please provide your authentication token:', {
61
+ sessionIdOrToken = await prompt('Please provide your authentication token:', {
83
62
  type: 'text',
84
63
  });
85
64
  if (!sessionIdOrToken) {
86
- consola_1.default.error('Token must be provided.');
65
+ consola.error('Token must be provided.');
87
66
  process.exit(1);
88
67
  }
89
68
  }
90
69
  }
91
70
  else if (sessionIdOrToken.length === 0) {
92
71
  // No token provided
93
- consola_1.default.error(`Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
72
+ consola.error(`Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
94
73
  process.exit(1);
95
74
  }
96
75
  // Sign in with the provided token
97
- consola_1.default.start('Signing in...');
98
- userConfig_1.default.write({
76
+ consola.start('Signing in...');
77
+ userConfig.write({
99
78
  token: sessionIdOrToken,
100
79
  });
101
80
  try {
102
- yield users_1.default.me();
103
- consola_1.default.success(`Successfully signed in.`);
81
+ await usersService.me();
82
+ consola.success(`Successfully signed in.`);
104
83
  }
105
84
  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) {
109
- message = `Invalid token. Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`;
85
+ userConfig.write({});
86
+ if (error instanceof AxiosError && error.response?.status === 401) {
87
+ consola.error(`Invalid token. Please provide a valid token. You can create a token at ${consoleBaseUrl}/settings/tokens.`);
88
+ process.exit(1);
89
+ }
90
+ else {
91
+ throw error;
110
92
  }
111
- consola_1.default.error(message);
112
- 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,116 @@
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/user-config.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/user-config.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((code) => {
41
+ throw new Error(`Process exited with code ${code}`);
42
+ });
43
+ });
44
+ afterEach(() => {
45
+ nock.cleanAll();
46
+ vi.restoreAllMocks();
47
+ });
48
+ it('should use the provided token for authentication', async () => {
49
+ const testToken = 'valid-token-123';
50
+ const options = { token: testToken };
51
+ // Mock userConfig.read to return our test token after it's written
52
+ mockUserConfig.read.mockReturnValue({ token: testToken });
53
+ // Set up nock to intercept the /v1/users/me request
54
+ const scope = nock(DEFAULT_API_BASE_URL)
55
+ .get('/v1/users/me')
56
+ .matchHeader('Authorization', `Bearer ${testToken}`)
57
+ .reply(200, { id: 'user-123', email: 'test@example.com' });
58
+ await loginCommand.action(options, undefined);
59
+ expect(mockUserConfig.write).toHaveBeenCalledWith({ token: testToken });
60
+ expect(scope.isDone()).toBe(true);
61
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
62
+ });
63
+ it('should open the browser', async () => {
64
+ const options = {};
65
+ mockPrompt
66
+ .mockResolvedValueOnce('browser') // authentication method
67
+ .mockResolvedValueOnce(true); // should proceed
68
+ mockSessionCodesService.create.mockResolvedValue({
69
+ id: 'device-code-123',
70
+ code: 'ABCD1234',
71
+ });
72
+ mockSessionsService.create.mockResolvedValue({ id: 'session-123' });
73
+ // Mock userConfig.read to return the session token
74
+ mockUserConfig.read.mockReturnValue({ token: 'session-123' });
75
+ // Set up nock to intercept the /v1/users/me request
76
+ const scope = nock(DEFAULT_API_BASE_URL)
77
+ .get('/v1/users/me')
78
+ .matchHeader('Authorization', `Bearer session-123`)
79
+ .reply(200, { id: 'user-123', email: 'test@example.com' });
80
+ await loginCommand.action(options, undefined);
81
+ expect(mockPrompt).toHaveBeenCalledWith('How would you like to authenticate Capawesome CLI?', {
82
+ type: 'select',
83
+ options: [
84
+ { label: 'Login with a web browser', value: 'browser' },
85
+ { label: 'Paste an authentication token', value: 'token' },
86
+ ],
87
+ });
88
+ expect(mockSessionCodesService.create).toHaveBeenCalled();
89
+ expect(mockConsola.box).toHaveBeenCalledWith('Copy your one-time code: ABCD-1234');
90
+ expect(mockOpen).toHaveBeenCalledWith(`${DEFAULT_CONSOLE_BASE_URL}/login/device`);
91
+ expect(scope.isDone()).toBe(true);
92
+ expect(mockConsola.success).toHaveBeenCalledWith('Successfully signed in.');
93
+ });
94
+ it('should throw an error because the provided token is empty', async () => {
95
+ const options = { token: '' };
96
+ // This test should exit early without making API calls since empty token is caught in login logic
97
+ await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
98
+ expect(mockConsola.error).toHaveBeenCalledWith(`Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
99
+ });
100
+ it('should throw an error because the provided token is invalid', async () => {
101
+ const invalidToken = 'invalid-token';
102
+ const options = { token: invalidToken };
103
+ // Mock userConfig.read to return our invalid token after it's written
104
+ mockUserConfig.read.mockReturnValue({ token: invalidToken });
105
+ // Set up nock to intercept the /v1/users/me request and return 401
106
+ const scope = nock(DEFAULT_API_BASE_URL)
107
+ .get('/v1/users/me')
108
+ .matchHeader('Authorization', `Bearer ${invalidToken}`)
109
+ .reply(401, { message: 'Unauthorized' });
110
+ await expect(loginCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
111
+ expect(mockUserConfig.write).toHaveBeenCalledWith({ token: invalidToken });
112
+ expect(mockUserConfig.write).toHaveBeenCalledWith({}); // Clears token on error
113
+ expect(scope.isDone()).toBe(true);
114
+ expect(mockConsola.error).toHaveBeenCalledWith(`Invalid token. Please provide a valid token. You can create a token at ${DEFAULT_CONSOLE_BASE_URL}/settings/tokens.`);
115
+ });
116
+ });
@@ -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/user-config.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/user-config.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/user-config.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((code) => {
20
+ throw new Error(`Process exited with code ${code}`);
21
+ });
22
+ });
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+ it('should generate manifest with provided path', async () => {
27
+ const options = { path: './dist' };
28
+ mockFileExistsAtPath.mockResolvedValue(true);
29
+ mockGenerateManifestJson.mockResolvedValue(undefined);
30
+ await generateManifestCommand.action(options, undefined);
31
+ expect(mockFileExistsAtPath).toHaveBeenCalledWith('./dist');
32
+ expect(mockGenerateManifestJson).toHaveBeenCalledWith('./dist');
33
+ expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
34
+ });
35
+ it('should prompt for path when not provided', async () => {
36
+ const options = {};
37
+ mockPrompt.mockResolvedValueOnce('./www');
38
+ mockFileExistsAtPath.mockResolvedValue(true);
39
+ mockGenerateManifestJson.mockResolvedValue(undefined);
40
+ await generateManifestCommand.action(options, undefined);
41
+ expect(mockPrompt).toHaveBeenCalledWith('Enter the path to the web assets folder:', {
42
+ type: 'text',
43
+ });
44
+ expect(mockFileExistsAtPath).toHaveBeenCalledWith('./www');
45
+ expect(mockGenerateManifestJson).toHaveBeenCalledWith('./www');
46
+ expect(mockConsola.success).toHaveBeenCalledWith('Manifest file generated.');
47
+ });
48
+ it('should handle missing path in prompt', async () => {
49
+ const options = {};
50
+ mockPrompt.mockResolvedValueOnce('');
51
+ await expect(generateManifestCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
52
+ expect(mockConsola.error).toHaveBeenCalledWith('You must provide a path to the web assets folder.');
53
+ });
54
+ it('should handle nonexistent path', async () => {
55
+ const options = { path: './nonexistent' };
56
+ mockFileExistsAtPath.mockResolvedValue(false);
57
+ await expect(generateManifestCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
58
+ expect(mockConsola.error).toHaveBeenCalledWith('The path does not exist.');
59
+ });
60
+ });