@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.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -3
- package/dist/commands/apps/bundles/create.js +215 -233
- package/dist/commands/apps/bundles/create.test.js +274 -0
- package/dist/commands/apps/bundles/delete.js +42 -47
- package/dist/commands/apps/bundles/delete.test.js +140 -0
- package/dist/commands/apps/bundles/update.js +69 -72
- package/dist/commands/apps/bundles/update.test.js +142 -0
- package/dist/commands/apps/channels/create.js +47 -70
- package/dist/commands/apps/channels/create.test.js +115 -0
- package/dist/commands/apps/channels/delete.js +53 -56
- package/dist/commands/apps/channels/delete.test.js +140 -0
- package/dist/commands/apps/channels/get.js +27 -61
- package/dist/commands/apps/channels/get.test.js +136 -0
- package/dist/commands/apps/channels/list.js +26 -63
- package/dist/commands/apps/channels/list.test.js +123 -0
- package/dist/commands/apps/channels/update.js +48 -67
- package/dist/commands/apps/channels/update.test.js +139 -0
- package/dist/commands/apps/create.js +38 -38
- package/dist/commands/apps/create.test.js +117 -0
- package/dist/commands/apps/delete.js +39 -40
- package/dist/commands/apps/delete.test.js +119 -0
- package/dist/commands/apps/devices/delete.js +42 -47
- package/dist/commands/apps/devices/delete.test.js +140 -0
- package/dist/commands/doctor.js +12 -29
- package/dist/commands/doctor.test.js +52 -0
- package/dist/commands/login.js +48 -69
- package/dist/commands/login.test.js +122 -0
- package/dist/commands/logout.js +13 -31
- package/dist/commands/logout.test.js +47 -0
- package/dist/commands/manifests/generate.js +20 -38
- package/dist/commands/manifests/generate.test.js +60 -0
- package/dist/commands/whoami.js +13 -30
- package/dist/commands/whoami.test.js +30 -0
- package/dist/config/consts.js +4 -5
- package/dist/config/index.js +1 -17
- package/dist/index.js +50 -80
- package/dist/services/app-bundle-files.js +117 -136
- package/dist/services/app-bundles.js +22 -41
- package/dist/services/app-channels.js +54 -77
- package/dist/services/app-devices.js +10 -25
- package/dist/services/apps.js +25 -41
- package/dist/services/authorization-service.js +4 -8
- package/dist/services/config.js +15 -28
- package/dist/services/organizations.js +18 -0
- package/dist/services/session-code.js +7 -22
- package/dist/services/sessions.js +13 -30
- package/dist/services/update.js +17 -55
- package/dist/services/users.js +11 -26
- package/dist/types/app-bundle-file.js +1 -2
- package/dist/types/app-bundle.js +1 -2
- package/dist/types/app-channel.js +1 -2
- package/dist/types/app-device.js +1 -2
- package/dist/types/app.js +1 -2
- package/dist/types/index.js +8 -23
- package/dist/types/npm-package.js +1 -2
- package/dist/types/organization.js +1 -0
- package/dist/types/session-code.js +1 -2
- package/dist/types/session.js +1 -2
- package/dist/types/user.js +1 -2
- package/dist/utils/buffer.js +12 -43
- package/dist/utils/error.js +24 -14
- package/dist/utils/file.js +22 -41
- package/dist/utils/hash.js +3 -39
- package/dist/utils/http-client.js +27 -53
- package/dist/utils/manifest.js +11 -24
- package/dist/utils/prompt.js +9 -26
- package/dist/utils/signature.js +3 -39
- package/dist/utils/userConfig.js +5 -9
- package/dist/utils/zip.js +11 -27
- package/package.json +23 -10
- package/dist/utils/ci.js +0 -7
|
@@ -1,65 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
args: {
|
|
27
|
-
appId: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
description: 'ID of the app.',
|
|
30
|
-
},
|
|
31
|
-
channelId: {
|
|
32
|
-
type: 'string',
|
|
33
|
-
description: 'ID of the channel. Either the ID or name of the channel must be provided.',
|
|
34
|
-
},
|
|
35
|
-
name: {
|
|
36
|
-
type: 'string',
|
|
37
|
-
description: 'Name of the channel. Either the ID or name of the channel must be provided.',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
run: (ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
-
let appId = ctx.args.appId;
|
|
42
|
-
let channelId = ctx.args.channelId;
|
|
43
|
-
let channelName = ctx.args.name;
|
|
1
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { isCI } from 'std-env';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import appChannelsService from '../../../services/app-channels.js';
|
|
6
|
+
import appsService from '../../../services/apps.js';
|
|
7
|
+
import organizationsService from '../../../services/organizations.js';
|
|
8
|
+
import { getMessageFromUnknownError } from '../../../utils/error.js';
|
|
9
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
10
|
+
export default defineCommand({
|
|
11
|
+
description: 'Delete an app channel.',
|
|
12
|
+
options: defineOptions(z.object({
|
|
13
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
14
|
+
channelId: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('ID of the channel. Either the ID or name of the channel must be provided.'),
|
|
18
|
+
name: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Name of the channel. Either the ID or name of the channel must be provided.'),
|
|
22
|
+
})),
|
|
23
|
+
action: async (options, args) => {
|
|
24
|
+
let { appId, channelId, name } = options;
|
|
44
25
|
if (!appId) {
|
|
45
|
-
const
|
|
26
|
+
const organizations = await organizationsService.findAll();
|
|
27
|
+
if (organizations.length === 0) {
|
|
28
|
+
consola.error('You must create an organization before deleting a channel.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
32
|
+
const organizationId = await prompt('Select the organization of the app from which you want to delete a channel.', {
|
|
33
|
+
type: 'select',
|
|
34
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
35
|
+
});
|
|
36
|
+
if (!organizationId) {
|
|
37
|
+
consola.error('You must select the organization of an app from which you want to delete a channel.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const apps = await appsService.findAll({
|
|
41
|
+
organizationId,
|
|
42
|
+
});
|
|
46
43
|
if (!apps.length) {
|
|
47
|
-
|
|
44
|
+
consola.error('You must create an app before deleting a channel.');
|
|
48
45
|
process.exit(1);
|
|
49
46
|
}
|
|
50
47
|
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
51
|
-
appId =
|
|
48
|
+
appId = await prompt('Which app do you want to delete the channel from?', {
|
|
52
49
|
type: 'select',
|
|
53
50
|
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
54
51
|
});
|
|
55
52
|
}
|
|
56
|
-
if (!channelId && !
|
|
57
|
-
|
|
53
|
+
if (!channelId && !name) {
|
|
54
|
+
name = await prompt('Enter the channel name:', {
|
|
58
55
|
type: 'text',
|
|
59
56
|
});
|
|
60
57
|
}
|
|
61
|
-
if (!
|
|
62
|
-
const confirmed =
|
|
58
|
+
if (!isCI) {
|
|
59
|
+
const confirmed = await prompt('Are you sure you want to delete this channel?', {
|
|
63
60
|
type: 'confirm',
|
|
64
61
|
});
|
|
65
62
|
if (!confirmed) {
|
|
@@ -67,17 +64,17 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
67
64
|
}
|
|
68
65
|
}
|
|
69
66
|
try {
|
|
70
|
-
|
|
67
|
+
await appChannelsService.delete({
|
|
71
68
|
appId,
|
|
72
69
|
id: channelId,
|
|
73
|
-
name
|
|
70
|
+
name,
|
|
74
71
|
});
|
|
75
|
-
|
|
72
|
+
consola.success('Channel deleted successfully.');
|
|
76
73
|
}
|
|
77
74
|
catch (error) {
|
|
78
|
-
const message =
|
|
79
|
-
|
|
75
|
+
const message = getMessageFromUnknownError(error);
|
|
76
|
+
consola.error(message);
|
|
80
77
|
process.exit(1);
|
|
81
78
|
}
|
|
82
|
-
}
|
|
79
|
+
},
|
|
83
80
|
});
|
|
@@ -0,0 +1,140 @@
|
|
|
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/userConfig.js';
|
|
5
|
+
import consola from 'consola';
|
|
6
|
+
import nock from 'nock';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
|
+
import deleteChannelCommand from './delete.js';
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
vi.mock('@/utils/userConfig.js');
|
|
11
|
+
vi.mock('@/utils/prompt.js');
|
|
12
|
+
vi.mock('@/services/authorization-service.js');
|
|
13
|
+
vi.mock('consola');
|
|
14
|
+
vi.mock('std-env', () => ({
|
|
15
|
+
isCI: false,
|
|
16
|
+
}));
|
|
17
|
+
describe('apps-channels-delete', () => {
|
|
18
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
19
|
+
const mockPrompt = vi.mocked(prompt);
|
|
20
|
+
const mockConsola = vi.mocked(consola);
|
|
21
|
+
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
mockUserConfig.read.mockReturnValue({ token: 'test-token' });
|
|
25
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
|
|
26
|
+
vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
nock.cleanAll();
|
|
30
|
+
vi.restoreAllMocks();
|
|
31
|
+
});
|
|
32
|
+
it('should delete channel with provided appId and channelId after confirmation', async () => {
|
|
33
|
+
const appId = 'app-123';
|
|
34
|
+
const channelId = 'channel-456';
|
|
35
|
+
const testToken = 'test-token';
|
|
36
|
+
const options = { appId, channelId };
|
|
37
|
+
mockPrompt.mockResolvedValueOnce(true); // confirmation
|
|
38
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
39
|
+
.delete(`/v1/apps/${appId}/channels/${channelId}`)
|
|
40
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
41
|
+
.reply(200);
|
|
42
|
+
await deleteChannelCommand.action(options, undefined);
|
|
43
|
+
expect(scope.isDone()).toBe(true);
|
|
44
|
+
expect(mockPrompt).toHaveBeenCalledWith('Are you sure you want to delete this channel?', {
|
|
45
|
+
type: 'confirm',
|
|
46
|
+
});
|
|
47
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel deleted successfully.');
|
|
48
|
+
});
|
|
49
|
+
it('should delete channel with provided appId and name after confirmation', async () => {
|
|
50
|
+
const appId = 'app-123';
|
|
51
|
+
const channelName = 'production';
|
|
52
|
+
const testToken = 'test-token';
|
|
53
|
+
const options = { appId, name: channelName };
|
|
54
|
+
mockPrompt.mockResolvedValueOnce(true); // confirmation
|
|
55
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
56
|
+
.delete(`/v1/apps/${appId}/channels`)
|
|
57
|
+
.query({ name: channelName })
|
|
58
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
59
|
+
.reply(200);
|
|
60
|
+
await deleteChannelCommand.action(options, undefined);
|
|
61
|
+
expect(scope.isDone()).toBe(true);
|
|
62
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel deleted successfully.');
|
|
63
|
+
});
|
|
64
|
+
it('should not delete channel when confirmation is declined', async () => {
|
|
65
|
+
const appId = 'app-123';
|
|
66
|
+
const channelId = 'channel-456';
|
|
67
|
+
const options = { appId, channelId };
|
|
68
|
+
mockPrompt.mockResolvedValueOnce(false); // declined confirmation
|
|
69
|
+
await deleteChannelCommand.action(options, undefined);
|
|
70
|
+
expect(mockPrompt).toHaveBeenCalledWith('Are you sure you want to delete this channel?', {
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
});
|
|
73
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('should prompt for app selection when appId not provided', async () => {
|
|
76
|
+
const orgId = 'org-1';
|
|
77
|
+
const appId = 'app-1';
|
|
78
|
+
const channelName = 'staging';
|
|
79
|
+
const testToken = 'test-token';
|
|
80
|
+
const organization = { id: orgId, name: 'Org 1' };
|
|
81
|
+
const app = { id: appId, name: 'App 1' };
|
|
82
|
+
const options = { name: channelName };
|
|
83
|
+
const orgsScope = nock(DEFAULT_API_BASE_URL)
|
|
84
|
+
.get('/v1/organizations')
|
|
85
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
86
|
+
.reply(200, [organization]);
|
|
87
|
+
const appsScope = nock(DEFAULT_API_BASE_URL)
|
|
88
|
+
.get('/v1/apps')
|
|
89
|
+
.query({ organizationId: orgId })
|
|
90
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
91
|
+
.reply(200, [app]);
|
|
92
|
+
const deleteScope = nock(DEFAULT_API_BASE_URL)
|
|
93
|
+
.delete(`/v1/apps/${appId}/channels`)
|
|
94
|
+
.query({ name: channelName })
|
|
95
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
96
|
+
.reply(200);
|
|
97
|
+
mockPrompt
|
|
98
|
+
.mockResolvedValueOnce(orgId) // organization selection
|
|
99
|
+
.mockResolvedValueOnce(appId) // app selection
|
|
100
|
+
.mockResolvedValueOnce(true); // confirmation
|
|
101
|
+
await deleteChannelCommand.action(options, undefined);
|
|
102
|
+
expect(orgsScope.isDone()).toBe(true);
|
|
103
|
+
expect(appsScope.isDone()).toBe(true);
|
|
104
|
+
expect(deleteScope.isDone()).toBe(true);
|
|
105
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel deleted successfully.');
|
|
106
|
+
});
|
|
107
|
+
it('should prompt for channel name when neither channelId nor name provided', async () => {
|
|
108
|
+
const appId = 'app-123';
|
|
109
|
+
const channelName = 'development';
|
|
110
|
+
const testToken = 'test-token';
|
|
111
|
+
const options = { appId };
|
|
112
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
113
|
+
.delete(`/v1/apps/${appId}/channels`)
|
|
114
|
+
.query({ name: channelName })
|
|
115
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
116
|
+
.reply(200);
|
|
117
|
+
mockPrompt
|
|
118
|
+
.mockResolvedValueOnce(channelName) // channel name input
|
|
119
|
+
.mockResolvedValueOnce(true); // confirmation
|
|
120
|
+
await deleteChannelCommand.action(options, undefined);
|
|
121
|
+
expect(scope.isDone()).toBe(true);
|
|
122
|
+
expect(mockPrompt).toHaveBeenCalledWith('Enter the channel name:', { type: 'text' });
|
|
123
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel deleted successfully.');
|
|
124
|
+
});
|
|
125
|
+
it('should handle API error during deletion', async () => {
|
|
126
|
+
const appId = 'app-123';
|
|
127
|
+
const channelId = 'channel-456';
|
|
128
|
+
const testToken = 'test-token';
|
|
129
|
+
const options = { appId, channelId };
|
|
130
|
+
mockPrompt.mockResolvedValueOnce(true);
|
|
131
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
132
|
+
.delete(`/v1/apps/${appId}/channels/${channelId}`)
|
|
133
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
134
|
+
.reply(404, { message: 'Channel not found' });
|
|
135
|
+
await deleteChannelCommand.action(options, undefined);
|
|
136
|
+
expect(scope.isDone()).toBe(true);
|
|
137
|
+
expect(mockConsola.error).toHaveBeenCalled();
|
|
138
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -1,82 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const error_1 = require("../../../utils/error");
|
|
20
|
-
exports.default = (0, citty_1.defineCommand)({
|
|
21
|
-
meta: {
|
|
22
|
-
description: 'Get an existing app channel.',
|
|
23
|
-
},
|
|
24
|
-
args: {
|
|
25
|
-
appId: {
|
|
26
|
-
type: 'string',
|
|
27
|
-
description: 'ID of the app.',
|
|
28
|
-
},
|
|
29
|
-
channelId: {
|
|
30
|
-
type: 'string',
|
|
31
|
-
description: 'ID of the channel.',
|
|
32
|
-
},
|
|
33
|
-
json: {
|
|
34
|
-
type: 'boolean',
|
|
35
|
-
description: 'Output in JSON format.',
|
|
36
|
-
},
|
|
37
|
-
name: {
|
|
38
|
-
type: 'string',
|
|
39
|
-
description: 'Name of the channel.',
|
|
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 { defineCommand, defineOptions } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import appChannelsService from '../../../services/app-channels.js';
|
|
5
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
6
|
+
import { getMessageFromUnknownError } from '../../../utils/error.js';
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: 'Get an existing app channel.',
|
|
9
|
+
options: defineOptions(z.object({
|
|
10
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
11
|
+
channelId: z.string().optional().describe('ID of the channel.'),
|
|
12
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
13
|
+
name: z.string().optional().describe('Name of the channel.'),
|
|
14
|
+
})),
|
|
15
|
+
action: async (options, args) => {
|
|
16
|
+
let { appId, channelId, json, name } = options;
|
|
17
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
18
|
+
consola.error('You must be logged in to run this command.');
|
|
45
19
|
process.exit(1);
|
|
46
20
|
}
|
|
47
|
-
let appId = ctx.args.appId;
|
|
48
|
-
let channelId = ctx.args.channelId;
|
|
49
|
-
let json = ctx.args.json;
|
|
50
|
-
// Convert json to boolean
|
|
51
|
-
if (typeof json === 'string') {
|
|
52
|
-
json = json.toLowerCase() === 'true';
|
|
53
|
-
}
|
|
54
|
-
let name = ctx.args.name;
|
|
55
21
|
if (!appId) {
|
|
56
|
-
|
|
22
|
+
consola.error('You must provide an app ID.');
|
|
57
23
|
process.exit(1);
|
|
58
24
|
}
|
|
59
25
|
if (!channelId && !name) {
|
|
60
|
-
|
|
26
|
+
consola.error('You must provide a channel ID or name.');
|
|
61
27
|
process.exit(1);
|
|
62
28
|
}
|
|
63
29
|
try {
|
|
64
30
|
let channel;
|
|
65
31
|
if (channelId) {
|
|
66
|
-
channel =
|
|
32
|
+
channel = await appChannelsService.findOneById({
|
|
67
33
|
appId,
|
|
68
34
|
id: channelId,
|
|
69
35
|
});
|
|
70
36
|
}
|
|
71
37
|
else if (name) {
|
|
72
|
-
const foundChannels =
|
|
38
|
+
const foundChannels = await appChannelsService.findAll({
|
|
73
39
|
appId,
|
|
74
40
|
name,
|
|
75
41
|
});
|
|
76
42
|
channel = foundChannels[0];
|
|
77
43
|
}
|
|
78
44
|
if (!channel) {
|
|
79
|
-
|
|
45
|
+
consola.error('Channel not found.');
|
|
80
46
|
process.exit(1);
|
|
81
47
|
}
|
|
82
48
|
if (json) {
|
|
@@ -94,13 +60,13 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
94
60
|
totalAppBundleLimit: channel.totalAppBundleLimit,
|
|
95
61
|
appId: channel.appId,
|
|
96
62
|
});
|
|
97
|
-
|
|
63
|
+
consola.success('Channel retrieved successfully.');
|
|
98
64
|
}
|
|
99
65
|
}
|
|
100
66
|
catch (error) {
|
|
101
|
-
const message =
|
|
102
|
-
|
|
67
|
+
const message = getMessageFromUnknownError(error);
|
|
68
|
+
consola.error(message);
|
|
103
69
|
process.exit(1);
|
|
104
70
|
}
|
|
105
|
-
}
|
|
71
|
+
},
|
|
106
72
|
});
|
|
@@ -0,0 +1,136 @@
|
|
|
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 getChannelCommand from './get.js';
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('@/utils/userConfig.js');
|
|
10
|
+
vi.mock('@/services/authorization-service.js');
|
|
11
|
+
vi.mock('consola');
|
|
12
|
+
describe('apps-channels-get', () => {
|
|
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(() => undefined);
|
|
22
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
23
|
+
vi.spyOn(console, 'table').mockImplementation(() => { });
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
nock.cleanAll();
|
|
27
|
+
vi.restoreAllMocks();
|
|
28
|
+
});
|
|
29
|
+
it('should require authentication', async () => {
|
|
30
|
+
const appId = 'app-123';
|
|
31
|
+
const channelId = 'channel-456';
|
|
32
|
+
const options = { appId, channelId };
|
|
33
|
+
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(false);
|
|
34
|
+
await getChannelCommand.action(options, undefined);
|
|
35
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must be logged in to run this command.');
|
|
36
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
37
|
+
});
|
|
38
|
+
it('should require appId', async () => {
|
|
39
|
+
const channelId = 'channel-456';
|
|
40
|
+
const options = { channelId };
|
|
41
|
+
await getChannelCommand.action(options, undefined);
|
|
42
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must provide an app ID.');
|
|
43
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
44
|
+
});
|
|
45
|
+
it('should require channelId or name', async () => {
|
|
46
|
+
const appId = 'app-123';
|
|
47
|
+
const options = { appId };
|
|
48
|
+
await getChannelCommand.action(options, undefined);
|
|
49
|
+
expect(mockConsola.error).toHaveBeenCalledWith('You must provide a channel ID or name.');
|
|
50
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
51
|
+
});
|
|
52
|
+
it('should get channel by channelId and display table format', async () => {
|
|
53
|
+
const appId = 'app-123';
|
|
54
|
+
const channelId = 'channel-456';
|
|
55
|
+
const testToken = 'test-token';
|
|
56
|
+
const channel = {
|
|
57
|
+
id: channelId,
|
|
58
|
+
name: 'production',
|
|
59
|
+
totalAppBundleLimit: 10,
|
|
60
|
+
appId,
|
|
61
|
+
};
|
|
62
|
+
const options = { appId, channelId };
|
|
63
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
64
|
+
.get(`/v1/apps/${appId}/channels/${channelId}`)
|
|
65
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
66
|
+
.reply(200, channel);
|
|
67
|
+
await getChannelCommand.action(options, undefined);
|
|
68
|
+
expect(scope.isDone()).toBe(true);
|
|
69
|
+
expect(console.table).toHaveBeenCalledWith(channel);
|
|
70
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Channel retrieved successfully.');
|
|
71
|
+
});
|
|
72
|
+
it('should get channel by name and display JSON format', async () => {
|
|
73
|
+
const appId = 'app-123';
|
|
74
|
+
const channelName = 'staging';
|
|
75
|
+
const testToken = 'test-token';
|
|
76
|
+
const channel = {
|
|
77
|
+
id: 'channel-789',
|
|
78
|
+
name: channelName,
|
|
79
|
+
totalAppBundleLimit: 5,
|
|
80
|
+
appId,
|
|
81
|
+
};
|
|
82
|
+
const options = { appId, name: channelName, json: true };
|
|
83
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
84
|
+
.get(`/v1/apps/${appId}/channels`)
|
|
85
|
+
.query({ name: channelName })
|
|
86
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
87
|
+
.reply(200, [channel]);
|
|
88
|
+
await getChannelCommand.action(options, undefined);
|
|
89
|
+
expect(scope.isDone()).toBe(true);
|
|
90
|
+
expect(console.log).toHaveBeenCalledWith(JSON.stringify(channel, null, 2));
|
|
91
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
it('should handle channel not found by channelId', async () => {
|
|
94
|
+
const appId = 'app-123';
|
|
95
|
+
const channelId = 'channel-456';
|
|
96
|
+
const testToken = 'test-token';
|
|
97
|
+
const options = { appId, channelId };
|
|
98
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
99
|
+
.get(`/v1/apps/${appId}/channels/${channelId}`)
|
|
100
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
101
|
+
.reply(404, { message: 'Channel not found' });
|
|
102
|
+
await getChannelCommand.action(options, undefined);
|
|
103
|
+
expect(scope.isDone()).toBe(true);
|
|
104
|
+
expect(mockConsola.error).toHaveBeenCalled();
|
|
105
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
106
|
+
});
|
|
107
|
+
it('should handle channel not found by name', async () => {
|
|
108
|
+
const appId = 'app-123';
|
|
109
|
+
const channelName = 'nonexistent';
|
|
110
|
+
const testToken = 'test-token';
|
|
111
|
+
const options = { appId, name: channelName };
|
|
112
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
113
|
+
.get(`/v1/apps/${appId}/channels`)
|
|
114
|
+
.query({ name: channelName })
|
|
115
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
116
|
+
.reply(200, []);
|
|
117
|
+
await getChannelCommand.action(options, undefined);
|
|
118
|
+
expect(scope.isDone()).toBe(true);
|
|
119
|
+
expect(mockConsola.error).toHaveBeenCalledWith('Channel not found.');
|
|
120
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
121
|
+
});
|
|
122
|
+
it('should handle API error', async () => {
|
|
123
|
+
const appId = 'app-123';
|
|
124
|
+
const channelId = 'channel-456';
|
|
125
|
+
const testToken = 'test-token';
|
|
126
|
+
const options = { appId, channelId };
|
|
127
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
128
|
+
.get(`/v1/apps/${appId}/channels/${channelId}`)
|
|
129
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
130
|
+
.reply(500, { message: 'Internal server error' });
|
|
131
|
+
await getChannelCommand.action(options, undefined);
|
|
132
|
+
expect(scope.isDone()).toBe(true);
|
|
133
|
+
expect(mockConsola.error).toHaveBeenCalled();
|
|
134
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -1,69 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 { defineCommand, defineOptions } from '@robingenz/zli';
|
|
2
|
+
import consola from 'consola';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import appChannelsService from '../../../services/app-channels.js';
|
|
5
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
6
|
+
import { getMessageFromUnknownError } from '../../../utils/error.js';
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: 'Retrieve a list of existing app channels.',
|
|
9
|
+
options: defineOptions(z.object({
|
|
10
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
11
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
12
|
+
limit: z.coerce.number().optional().describe('Limit for pagination.'),
|
|
13
|
+
offset: z.coerce.number().optional().describe('Offset for pagination.'),
|
|
14
|
+
})),
|
|
15
|
+
action: async (options, args) => {
|
|
16
|
+
let { appId, json, limit, offset } = options;
|
|
17
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
18
|
+
consola.error('You must be logged in to run this command.');
|
|
45
19
|
process.exit(1);
|
|
46
20
|
}
|
|
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
21
|
if (!appId) {
|
|
59
|
-
|
|
22
|
+
consola.error('You must provide an app ID.');
|
|
60
23
|
process.exit(1);
|
|
61
24
|
}
|
|
62
25
|
try {
|
|
63
|
-
const foundChannels =
|
|
26
|
+
const foundChannels = await appChannelsService.findAll({
|
|
64
27
|
appId,
|
|
65
|
-
limit
|
|
66
|
-
offset
|
|
28
|
+
limit,
|
|
29
|
+
offset,
|
|
67
30
|
});
|
|
68
31
|
const logData = foundChannels.map((channel) => ({
|
|
69
32
|
id: channel.id,
|
|
@@ -78,13 +41,13 @@ exports.default = (0, citty_1.defineCommand)({
|
|
|
78
41
|
}
|
|
79
42
|
else {
|
|
80
43
|
console.table(logData);
|
|
81
|
-
|
|
44
|
+
consola.success('Channels retrieved successfully.');
|
|
82
45
|
}
|
|
83
46
|
}
|
|
84
47
|
catch (error) {
|
|
85
|
-
const message =
|
|
86
|
-
|
|
48
|
+
const message = getMessageFromUnknownError(error);
|
|
49
|
+
consola.error(message);
|
|
87
50
|
process.exit(1);
|
|
88
51
|
}
|
|
89
|
-
}
|
|
52
|
+
},
|
|
90
53
|
});
|