@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,90 +1,45 @@
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 app_channels_1 = __importDefault(require("../../../services/app-channels"));
18
- const authorization_service_1 = __importDefault(require("../../../services/authorization-service"));
19
- const error_1 = require("../../../utils/error");
20
- exports.default = (0, citty_1.defineCommand)({
21
- meta: {
22
- description: 'Retrieve a list of existing app channels.',
23
- },
24
- args: {
25
- appId: {
26
- type: 'string',
27
- description: 'ID of the app.',
28
- },
29
- json: {
30
- type: 'boolean',
31
- description: 'Output in JSON format.',
32
- },
33
- limit: {
34
- type: 'string',
35
- description: 'Limit for pagination.',
36
- },
37
- offset: {
38
- type: 'string',
39
- description: 'Offset for pagination.',
40
- },
41
- },
42
- run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
43
- if (!authorization_service_1.default.hasAuthorizationToken()) {
44
- consola_1.default.error('You must be logged in to run this command.');
1
+ import appChannelsService from '../../../services/app-channels.js';
2
+ import authorizationService from '../../../services/authorization-service.js';
3
+ import { defineCommand, defineOptions } from '@robingenz/zli';
4
+ import consola from 'consola';
5
+ import { z } from 'zod';
6
+ export default defineCommand({
7
+ description: 'Retrieve a list of existing app channels.',
8
+ options: defineOptions(z.object({
9
+ appId: z.string().optional().describe('ID of the app.'),
10
+ json: z.boolean().optional().describe('Output in JSON format.'),
11
+ limit: z.coerce.number().optional().describe('Limit for pagination.'),
12
+ offset: z.coerce.number().optional().describe('Offset for pagination.'),
13
+ })),
14
+ action: async (options, args) => {
15
+ let { appId, json, limit, offset } = options;
16
+ if (!authorizationService.hasAuthorizationToken()) {
17
+ consola.error('You must be logged in to run this command.');
45
18
  process.exit(1);
46
19
  }
47
- let appId = ctx.args.appId;
48
- let json = ctx.args.json;
49
- const limit = ctx.args.limit;
50
- const offset = ctx.args.offset;
51
- // Convert limit and offset to numbers
52
- const limitAsNumber = limit ? parseInt(limit, 10) : undefined;
53
- const offsetAsNumber = offset ? parseInt(offset, 10) : undefined;
54
- // Convert json to boolean
55
- if (typeof json === 'string') {
56
- json = json.toLowerCase() === 'true';
57
- }
58
20
  if (!appId) {
59
- consola_1.default.error('You must provide an app ID.');
21
+ consola.error('You must provide an app ID.');
60
22
  process.exit(1);
61
23
  }
62
- try {
63
- const foundChannels = yield app_channels_1.default.findAll({
64
- appId,
65
- limit: limitAsNumber,
66
- offset: offsetAsNumber,
67
- });
68
- const logData = foundChannels.map((channel) => ({
69
- id: channel.id,
70
- name: channel.name,
71
- totalAppBundleLimit: channel.totalAppBundleLimit,
72
- appId: channel.appId,
73
- createdAt: channel.createdAt,
74
- updatedAt: channel.updatedAt,
75
- }));
76
- if (json) {
77
- console.log(JSON.stringify(logData, null, 2));
78
- }
79
- else {
80
- console.table(logData);
81
- consola_1.default.success('Channels retrieved successfully.');
82
- }
24
+ const foundChannels = await appChannelsService.findAll({
25
+ appId,
26
+ limit,
27
+ offset,
28
+ });
29
+ const logData = foundChannels.map((channel) => ({
30
+ id: channel.id,
31
+ name: channel.name,
32
+ totalAppBundleLimit: channel.totalAppBundleLimit,
33
+ appId: channel.appId,
34
+ createdAt: channel.createdAt,
35
+ updatedAt: channel.updatedAt,
36
+ }));
37
+ if (json) {
38
+ console.log(JSON.stringify(logData, null, 2));
83
39
  }
84
- catch (error) {
85
- const message = (0, error_1.getMessageFromUnknownError)(error);
86
- consola_1.default.error(message);
87
- process.exit(1);
40
+ else {
41
+ console.table(logData);
42
+ consola.success('Channels retrieved successfully.');
88
43
  }
89
- }),
44
+ },
90
45
  });
@@ -0,0 +1,121 @@
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 listChannelsCommand from './list.js';
8
+ // Mock dependencies
9
+ vi.mock('@/utils/user-config.js');
10
+ vi.mock('@/services/authorization-service.js');
11
+ vi.mock('consola');
12
+ describe('apps-channels-list', () => {
13
+ const mockUserConfig = vi.mocked(userConfig);
14
+ const mockConsola = vi.mocked(consola);
15
+ const mockAuthorizationService = vi.mocked(authorizationService);
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ mockUserConfig.read.mockReturnValue({ token: 'test-token' });
19
+ mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
20
+ mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
21
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
22
+ throw new Error(`Process exited with code ${code}`);
23
+ });
24
+ vi.spyOn(console, 'log').mockImplementation(() => { });
25
+ vi.spyOn(console, 'table').mockImplementation(() => { });
26
+ });
27
+ afterEach(() => {
28
+ nock.cleanAll();
29
+ vi.restoreAllMocks();
30
+ });
31
+ it('should require authentication', async () => {
32
+ const appId = 'app-123';
33
+ const options = { appId };
34
+ mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
35
+ await expect(listChannelsCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
36
+ expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
37
+ });
38
+ it('should require appId', async () => {
39
+ const options = { appId: undefined };
40
+ await expect(listChannelsCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
41
+ expect(mockConsola.error).toHaveBeenCalledWith('You must provide an app ID.');
42
+ });
43
+ it('should list channels and display table format', async () => {
44
+ const appId = 'app-123';
45
+ const testToken = 'test-token';
46
+ const channels = [
47
+ {
48
+ id: 'channel-1',
49
+ name: 'production',
50
+ totalAppBundleLimit: 10,
51
+ appId,
52
+ createdAt: '2023-01-01T00:00:00Z',
53
+ updatedAt: '2023-01-01T00:00:00Z',
54
+ },
55
+ {
56
+ id: 'channel-2',
57
+ name: 'staging',
58
+ totalAppBundleLimit: 5,
59
+ appId,
60
+ createdAt: '2023-01-02T00:00:00Z',
61
+ updatedAt: '2023-01-02T00:00:00Z',
62
+ },
63
+ ];
64
+ const options = { appId };
65
+ const scope = nock(DEFAULT_API_BASE_URL)
66
+ .get(`/v1/apps/${appId}/channels`)
67
+ .matchHeader('Authorization', `Bearer ${testToken}`)
68
+ .reply(200, channels);
69
+ await listChannelsCommand.action(options, undefined);
70
+ expect(scope.isDone()).toBe(true);
71
+ expect(console.table).toHaveBeenCalledWith(channels);
72
+ expect(mockConsola.success).toHaveBeenCalledWith('Channels retrieved successfully.');
73
+ });
74
+ it('should list channels with JSON format', async () => {
75
+ const appId = 'app-123';
76
+ const testToken = 'test-token';
77
+ const channels = [
78
+ {
79
+ id: 'channel-1',
80
+ name: 'production',
81
+ totalAppBundleLimit: 10,
82
+ appId,
83
+ createdAt: '2023-01-01T00:00:00Z',
84
+ updatedAt: '2023-01-01T00:00:00Z',
85
+ },
86
+ ];
87
+ const options = { appId, json: true };
88
+ const scope = nock(DEFAULT_API_BASE_URL)
89
+ .get(`/v1/apps/${appId}/channels`)
90
+ .matchHeader('Authorization', `Bearer ${testToken}`)
91
+ .reply(200, channels);
92
+ await listChannelsCommand.action(options, undefined);
93
+ expect(scope.isDone()).toBe(true);
94
+ expect(console.log).toHaveBeenCalledWith(JSON.stringify(channels, null, 2));
95
+ expect(mockConsola.success).not.toHaveBeenCalled();
96
+ });
97
+ it('should handle empty channels list', async () => {
98
+ const appId = 'app-123';
99
+ const testToken = 'test-token';
100
+ const options = { appId };
101
+ const scope = nock(DEFAULT_API_BASE_URL)
102
+ .get(`/v1/apps/${appId}/channels`)
103
+ .matchHeader('Authorization', `Bearer ${testToken}`)
104
+ .reply(200, []);
105
+ await listChannelsCommand.action(options, undefined);
106
+ expect(scope.isDone()).toBe(true);
107
+ expect(console.table).toHaveBeenCalledWith([]);
108
+ expect(mockConsola.success).toHaveBeenCalledWith('Channels retrieved successfully.');
109
+ });
110
+ it('should handle API error', async () => {
111
+ const appId = 'app-123';
112
+ const testToken = 'test-token';
113
+ const options = { appId };
114
+ const scope = nock(DEFAULT_API_BASE_URL)
115
+ .get(`/v1/apps/${appId}/channels`)
116
+ .matchHeader('Authorization', `Bearer ${testToken}`)
117
+ .reply(500, { message: 'Internal server error' });
118
+ await expect(listChannelsCommand.action(options, undefined)).rejects.toThrow();
119
+ expect(scope.isDone()).toBe(true);
120
+ });
121
+ });
@@ -1,112 +1,68 @@
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 app_channels_1 = __importDefault(require("../../../services/app-channels"));
18
- const apps_1 = __importDefault(require("../../../services/apps"));
19
- const authorization_service_1 = __importDefault(require("../../../services/authorization-service"));
20
- const organizations_1 = __importDefault(require("../../../services/organizations"));
21
- const error_1 = require("../../../utils/error");
22
- const prompt_1 = require("../../../utils/prompt");
23
- exports.default = (0, citty_1.defineCommand)({
24
- meta: {
25
- description: 'Update an existing app channel.',
26
- },
27
- args: {
28
- appId: {
29
- type: 'string',
30
- description: 'ID of the app.',
31
- },
32
- channelId: {
33
- type: 'string',
34
- description: 'ID of the channel.',
35
- },
36
- bundleLimit: {
37
- type: 'string',
38
- description: 'Maximum number of bundles that can be assigned to the channel. If more bundles are assigned, the oldest bundles will be automatically deleted.',
39
- },
40
- name: {
41
- type: 'string',
42
- description: 'Name of the channel.',
43
- },
44
- },
45
- run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
46
- if (!authorization_service_1.default.hasAuthorizationToken()) {
47
- consola_1.default.error('You must be logged in to run this command.');
1
+ import appChannelsService from '../../../services/app-channels.js';
2
+ import appsService from '../../../services/apps.js';
3
+ import authorizationService from '../../../services/authorization-service.js';
4
+ import organizationsService from '../../../services/organizations.js';
5
+ import { prompt } from '../../../utils/prompt.js';
6
+ import { defineCommand, defineOptions } from '@robingenz/zli';
7
+ import consola from 'consola';
8
+ import { z } from 'zod';
9
+ export default defineCommand({
10
+ description: 'Update an existing app channel.',
11
+ options: defineOptions(z.object({
12
+ appId: z.string().optional().describe('ID of the app.'),
13
+ channelId: z.string().optional().describe('ID of the channel.'),
14
+ bundleLimit: z.coerce
15
+ .number()
16
+ .optional()
17
+ .describe('Maximum number of bundles that can be assigned to the channel. If more bundles are assigned, the oldest bundles will be automatically deleted.'),
18
+ name: z.string().optional().describe('Name of the channel.'),
19
+ })),
20
+ action: async (options, args) => {
21
+ let { appId, channelId, bundleLimit, name } = options;
22
+ if (!authorizationService.hasAuthorizationToken()) {
23
+ consola.error('You must be logged in to run this command.');
48
24
  process.exit(1);
49
25
  }
50
- let appId = ctx.args.appId;
51
- let bundleLimitAsString = ctx.args.bundleLimit;
52
- let channelId = ctx.args.channelId;
53
- let name = ctx.args.name;
54
- // Validate the bundle limit
55
- let bundleLimit;
56
- if (bundleLimitAsString) {
57
- bundleLimit = parseInt(bundleLimitAsString, 10);
58
- if (isNaN(bundleLimit)) {
59
- consola_1.default.error('The bundle limit must be a number.');
60
- process.exit(1);
61
- }
62
- }
63
26
  if (!appId) {
64
- const organizations = yield organizations_1.default.findAll();
27
+ const organizations = await organizationsService.findAll();
65
28
  if (organizations.length === 0) {
66
- consola_1.default.error('You must create an organization before updating a channel.');
29
+ consola.error('You must create an organization before updating a channel.');
67
30
  process.exit(1);
68
31
  }
69
32
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
70
- const organizationId = yield (0, prompt_1.prompt)('Select the organization of the app for which you want to update a channel.', {
33
+ const organizationId = await prompt('Select the organization of the app for which you want to update a channel.', {
71
34
  type: 'select',
72
35
  options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
73
36
  });
74
37
  if (!organizationId) {
75
- consola_1.default.error('You must select the organization of an app for which you want to update a channel.');
38
+ consola.error('You must select the organization of an app for which you want to update a channel.');
76
39
  process.exit(1);
77
40
  }
78
- const apps = yield apps_1.default.findAll({
41
+ const apps = await appsService.findAll({
79
42
  organizationId,
80
43
  });
81
44
  if (!apps.length) {
82
- consola_1.default.error('You must create an app before updating a channel.');
45
+ consola.error('You must create an app before updating a channel.');
83
46
  process.exit(1);
84
47
  }
85
48
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
86
- appId = yield (0, prompt_1.prompt)('Which app do you want to update the channel for?', {
49
+ appId = await prompt('Which app do you want to update the channel for?', {
87
50
  type: 'select',
88
51
  options: apps.map((app) => ({ label: app.name, value: app.id })),
89
52
  });
90
53
  }
91
54
  if (!channelId) {
92
- channelId = yield (0, prompt_1.prompt)('Enter the channel ID:', {
55
+ channelId = await prompt('Enter the channel ID:', {
93
56
  type: 'text',
94
57
  });
95
58
  }
96
59
  // Update channel
97
- try {
98
- yield app_channels_1.default.update({
99
- appId,
100
- appChannelId: channelId,
101
- name,
102
- totalAppBundleLimit: bundleLimit,
103
- });
104
- consola_1.default.success('Channel updated successfully.');
105
- }
106
- catch (error) {
107
- const message = (0, error_1.getMessageFromUnknownError)(error);
108
- consola_1.default.error(message);
109
- process.exit(1);
110
- }
111
- }),
60
+ await appChannelsService.update({
61
+ appId,
62
+ appChannelId: channelId,
63
+ name,
64
+ totalAppBundleLimit: bundleLimit,
65
+ });
66
+ consola.success('Channel updated successfully.');
67
+ },
112
68
  });
@@ -0,0 +1,138 @@
1
+ import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
2
+ import authorizationService from '../../../services/authorization-service.js';
3
+ import { prompt } from '../../../utils/prompt.js';
4
+ import userConfig from '../../../utils/user-config.js';
5
+ import consola from 'consola';
6
+ import nock from 'nock';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import updateChannelCommand from './update.js';
9
+ // Mock dependencies
10
+ vi.mock('@/utils/user-config.js');
11
+ vi.mock('@/utils/prompt.js');
12
+ vi.mock('@/services/authorization-service.js');
13
+ vi.mock('consola');
14
+ describe('apps-channels-update', () => {
15
+ const mockUserConfig = vi.mocked(userConfig);
16
+ const mockPrompt = vi.mocked(prompt);
17
+ const mockConsola = vi.mocked(consola);
18
+ const mockAuthorizationService = vi.mocked(authorizationService);
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ mockUserConfig.read.mockReturnValue({ token: 'test-token' });
22
+ mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
23
+ mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
24
+ vi.spyOn(process, 'exit').mockImplementation((code) => {
25
+ throw new Error(`Process exited with code ${code}`);
26
+ });
27
+ });
28
+ afterEach(() => {
29
+ nock.cleanAll();
30
+ vi.restoreAllMocks();
31
+ });
32
+ it('should require authentication', async () => {
33
+ const appId = 'app-123';
34
+ const channelId = 'channel-456';
35
+ const options = { appId, channelId };
36
+ mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
37
+ await expect(updateChannelCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
38
+ expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
39
+ });
40
+ it('should update channel with provided options', async () => {
41
+ const appId = 'app-123';
42
+ const channelId = 'channel-456';
43
+ const channelName = 'updated-production';
44
+ const bundleLimit = 15;
45
+ const testToken = 'test-token';
46
+ const options = { appId, channelId, name: channelName, bundleLimit };
47
+ const scope = nock(DEFAULT_API_BASE_URL)
48
+ .patch(`/v1/apps/${appId}/channels/${channelId}`, {
49
+ appId,
50
+ appChannelId: channelId,
51
+ name: channelName,
52
+ totalAppBundleLimit: bundleLimit,
53
+ })
54
+ .matchHeader('Authorization', `Bearer ${testToken}`)
55
+ .reply(200, { id: channelId, name: channelName });
56
+ await updateChannelCommand.action(options, undefined);
57
+ expect(scope.isDone()).toBe(true);
58
+ expect(mockConsola.success).toHaveBeenCalledWith('Channel updated successfully.');
59
+ });
60
+ it('should prompt for app selection when appId not provided', async () => {
61
+ const orgId = 'org-1';
62
+ const appId = 'app-1';
63
+ const channelId = 'channel-456';
64
+ const testToken = 'test-token';
65
+ const organization = { id: orgId, name: 'Org 1' };
66
+ const app = { id: appId, name: 'App 1' };
67
+ const options = { channelId, bundleLimit: 10 };
68
+ const orgsScope = nock(DEFAULT_API_BASE_URL)
69
+ .get('/v1/organizations')
70
+ .matchHeader('Authorization', `Bearer ${testToken}`)
71
+ .reply(200, [organization]);
72
+ const appsScope = nock(DEFAULT_API_BASE_URL)
73
+ .get('/v1/apps')
74
+ .query({ organizationId: orgId })
75
+ .matchHeader('Authorization', `Bearer ${testToken}`)
76
+ .reply(200, [app]);
77
+ const updateScope = nock(DEFAULT_API_BASE_URL)
78
+ .patch(`/v1/apps/${appId}/channels/${channelId}`)
79
+ .matchHeader('Authorization', `Bearer ${testToken}`)
80
+ .reply(200, { id: channelId });
81
+ mockPrompt
82
+ .mockResolvedValueOnce(orgId) // organization selection
83
+ .mockResolvedValueOnce(appId) // app selection
84
+ .mockResolvedValueOnce(channelId); // channel ID input
85
+ await updateChannelCommand.action(options, undefined);
86
+ expect(orgsScope.isDone()).toBe(true);
87
+ expect(appsScope.isDone()).toBe(true);
88
+ expect(updateScope.isDone()).toBe(true);
89
+ expect(mockConsola.success).toHaveBeenCalledWith('Channel updated successfully.');
90
+ });
91
+ it('should prompt for channelId when not provided', async () => {
92
+ const appId = 'app-123';
93
+ const channelId = 'channel-456';
94
+ const testToken = 'test-token';
95
+ const options = { appId, name: 'staging' };
96
+ const scope = nock(DEFAULT_API_BASE_URL)
97
+ .patch(`/v1/apps/${appId}/channels/${channelId}`)
98
+ .matchHeader('Authorization', `Bearer ${testToken}`)
99
+ .reply(200, { id: channelId });
100
+ mockPrompt.mockResolvedValueOnce(channelId); // channel ID input
101
+ await updateChannelCommand.action(options, undefined);
102
+ expect(scope.isDone()).toBe(true);
103
+ expect(mockPrompt).toHaveBeenCalledWith('Enter the channel ID:', { type: 'text' });
104
+ expect(mockConsola.success).toHaveBeenCalledWith('Channel updated successfully.');
105
+ });
106
+ it('should handle API error during update', async () => {
107
+ const appId = 'app-123';
108
+ const channelId = 'channel-456';
109
+ const testToken = 'test-token';
110
+ const options = { appId, channelId, name: 'updated-name' };
111
+ const scope = nock(DEFAULT_API_BASE_URL)
112
+ .patch(`/v1/apps/${appId}/channels/${channelId}`)
113
+ .matchHeader('Authorization', `Bearer ${testToken}`)
114
+ .reply(404, { message: 'Channel not found' });
115
+ await expect(updateChannelCommand.action(options, undefined)).rejects.toThrow();
116
+ expect(scope.isDone()).toBe(true);
117
+ });
118
+ it('should handle error when no organizations exist', async () => {
119
+ const testToken = 'test-token';
120
+ const options = { name: 'new-name' };
121
+ const scope = nock(DEFAULT_API_BASE_URL)
122
+ .get('/v1/organizations')
123
+ .matchHeader('Authorization', `Bearer ${testToken}`)
124
+ .reply(200, []);
125
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => {
126
+ throw new Error(`process.exit called with code ${code}`);
127
+ });
128
+ try {
129
+ await updateChannelCommand.action(options, undefined);
130
+ }
131
+ catch (error) {
132
+ expect(error.message).toBe('process.exit called with code 1');
133
+ }
134
+ expect(scope.isDone()).toBe(true);
135
+ expect(mockConsola.error).toHaveBeenCalledWith('You must create an organization before updating a channel.');
136
+ expect(exitSpy).toHaveBeenCalledWith(1);
137
+ });
138
+ });
@@ -1,68 +1,43 @@
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 apps_1 = __importDefault(require("../../services/apps"));
18
- const organizations_1 = __importDefault(require("../../services/organizations"));
19
- const error_1 = require("../../utils/error");
20
- const prompt_1 = require("../../utils/prompt");
21
- exports.default = (0, citty_1.defineCommand)({
22
- meta: {
23
- description: 'Create a new app.',
24
- },
25
- args: {
26
- name: {
27
- type: 'string',
28
- description: 'Name of the app.',
29
- },
30
- organizationId: {
31
- type: 'string',
32
- description: 'ID of the organization to create the app in.',
33
- },
34
- },
35
- run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
36
- let organizationId = ctx.args.organizationId;
1
+ import appsService from '../../services/apps.js';
2
+ import authorizationService from '../../services/authorization-service.js';
3
+ import organizationsService from '../../services/organizations.js';
4
+ import { prompt } from '../../utils/prompt.js';
5
+ import { defineCommand, defineOptions } from '@robingenz/zli';
6
+ import consola from 'consola';
7
+ import { z } from 'zod';
8
+ export default defineCommand({
9
+ description: 'Create a new app.',
10
+ options: defineOptions(z.object({
11
+ name: z.string().optional().describe('Name of the app.'),
12
+ organizationId: z.string().optional().describe('ID of the organization to create the app in.'),
13
+ })),
14
+ action: async (options, args) => {
15
+ let { name, organizationId } = options;
16
+ if (!authorizationService.hasAuthorizationToken()) {
17
+ consola.error('You must be logged in to run this command.');
18
+ process.exit(1);
19
+ }
37
20
  if (!organizationId) {
38
- const organizations = yield organizations_1.default.findAll();
21
+ const organizations = await organizationsService.findAll();
39
22
  if (organizations.length === 0) {
40
- consola_1.default.error('You must create an organization before creating an app.');
23
+ consola.error('You must create an organization before creating an app.');
41
24
  process.exit(1);
42
25
  }
43
26
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
44
- organizationId = yield (0, prompt_1.prompt)('Which organization do you want to create the app in?', {
27
+ organizationId = await prompt('Which organization do you want to create the app in?', {
45
28
  type: 'select',
46
29
  options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
47
30
  });
48
31
  if (!organizationId) {
49
- consola_1.default.error('You must select an organization to create the app in.');
32
+ consola.error('You must select an organization to create the app in.');
50
33
  process.exit(1);
51
34
  }
52
35
  }
53
- let name = ctx.args.name;
54
36
  if (!name) {
55
- name = yield (0, prompt_1.prompt)('Enter the name of the app:', { type: 'text' });
37
+ name = await prompt('Enter the name of the app:', { type: 'text' });
56
38
  }
57
- try {
58
- const response = yield apps_1.default.create({ name, organizationId });
59
- consola_1.default.success('App created successfully.');
60
- consola_1.default.info(`App ID: ${response.id}`);
61
- }
62
- catch (error) {
63
- const message = (0, error_1.getMessageFromUnknownError)(error);
64
- consola_1.default.error(message);
65
- process.exit(1);
66
- }
67
- }),
39
+ const response = await appsService.create({ name, organizationId });
40
+ consola.success('App created successfully.');
41
+ consola.info(`App ID: ${response.id}`);
42
+ },
68
43
  });