@capawesome/cli 4.17.2 → 4.18.0
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 +16 -0
- package/dist/commands/apps/automations/create.js +141 -0
- package/dist/commands/apps/automations/create.test.js +162 -0
- package/dist/commands/apps/automations/delete.js +66 -0
- package/dist/commands/apps/automations/delete.test.js +127 -0
- package/dist/commands/apps/automations/get.js +62 -0
- package/dist/commands/apps/automations/get.test.js +118 -0
- package/dist/commands/apps/automations/list.js +46 -0
- package/dist/commands/apps/automations/list.test.js +92 -0
- package/dist/commands/apps/automations/update.js +118 -0
- package/dist/commands/apps/automations/update.test.js +124 -0
- package/dist/commands/apps/builds/create.js +31 -1
- package/dist/commands/apps/builds/create.test.js +15 -0
- package/dist/commands/apps/builds/run.js +265 -0
- package/dist/commands/apps/configurations/create.js +51 -0
- package/dist/commands/apps/configurations/create.test.js +120 -0
- package/dist/commands/apps/configurations/delete.js +61 -0
- package/dist/commands/apps/configurations/delete.test.js +112 -0
- package/dist/commands/apps/configurations/get.js +65 -0
- package/dist/commands/apps/configurations/get.test.js +119 -0
- package/dist/commands/apps/configurations/list.js +39 -0
- package/dist/commands/apps/configurations/list.test.js +94 -0
- package/dist/commands/apps/configurations/update.js +61 -0
- package/dist/commands/apps/configurations/update.test.js +122 -0
- package/dist/index.js +11 -0
- package/dist/services/app-automations.js +64 -0
- package/dist/services/app-configurations.js +77 -0
- package/dist/types/app-automation.js +1 -0
- package/dist/types/app-configuration.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/android-emulator.js +170 -0
- package/dist/utils/ios-simulator.js +57 -0
- package/dist/utils/zip.js +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import appConfigurationsService from '../../../services/app-configurations.js';
|
|
2
|
+
import { withAuth } from '../../../utils/auth.js';
|
|
3
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
4
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } 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: 'Get an existing native configuration.',
|
|
10
|
+
options: defineOptions(z.object({
|
|
11
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
12
|
+
configurationId: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('ID of the native configuration. Either the ID or name must be provided.'),
|
|
16
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
17
|
+
name: z.string().optional().describe('Name of the native configuration. Either the ID or name must be provided.'),
|
|
18
|
+
})),
|
|
19
|
+
action: withAuth(async (options, args) => {
|
|
20
|
+
let { appId, configurationId, json, name } = options;
|
|
21
|
+
if (!appId) {
|
|
22
|
+
if (!isInteractive()) {
|
|
23
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const organizationId = await promptOrganizationSelection();
|
|
27
|
+
appId = await promptAppSelection(organizationId);
|
|
28
|
+
}
|
|
29
|
+
if (!configurationId && !name) {
|
|
30
|
+
if (!isInteractive()) {
|
|
31
|
+
consola.error('You must provide either the native configuration ID or name when running in non-interactive environment.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const configurations = await appConfigurationsService.findAll({ appId });
|
|
35
|
+
if (!configurations.length) {
|
|
36
|
+
consola.error('No native configurations found for this app. Create one first.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
40
|
+
configurationId = await prompt('Select the native configuration:', {
|
|
41
|
+
type: 'select',
|
|
42
|
+
options: configurations.map((configuration) => ({ label: configuration.name, value: configuration.id })),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
let configuration;
|
|
46
|
+
if (configurationId) {
|
|
47
|
+
configuration = await appConfigurationsService.findOneById({ appId, id: configurationId });
|
|
48
|
+
}
|
|
49
|
+
else if (name) {
|
|
50
|
+
const configurations = await appConfigurationsService.findAll({ appId, name });
|
|
51
|
+
configuration = configurations[0];
|
|
52
|
+
}
|
|
53
|
+
if (!configuration) {
|
|
54
|
+
consola.error('Native configuration not found.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
if (json) {
|
|
58
|
+
console.log(JSON.stringify(configuration, null, 2));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.table(configuration);
|
|
62
|
+
consola.success('Native configuration retrieved successfully.');
|
|
63
|
+
}
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } 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 getConfigurationCommand from './get.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
|
+
vi.mock('@/utils/environment.js', () => ({
|
|
15
|
+
isInteractive: () => true,
|
|
16
|
+
}));
|
|
17
|
+
describe('apps-configurations-get', () => {
|
|
18
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
19
|
+
const mockPrompt = vi.mocked(prompt);
|
|
20
|
+
const mockPromptOrganizationSelection = vi.mocked(promptOrganizationSelection);
|
|
21
|
+
const mockPromptAppSelection = vi.mocked(promptAppSelection);
|
|
22
|
+
const mockConsola = vi.mocked(consola);
|
|
23
|
+
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
mockUserConfig.read.mockReturnValue({ token: 'test-token' });
|
|
27
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
|
|
28
|
+
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
|
|
29
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
30
|
+
throw new Error(`Process exited with code ${code}`);
|
|
31
|
+
});
|
|
32
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
33
|
+
vi.spyOn(console, 'table').mockImplementation(() => { });
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
nock.cleanAll();
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
it('should get configuration by ID and display table format', async () => {
|
|
40
|
+
const appId = 'app-123';
|
|
41
|
+
const configurationId = 'configuration-456';
|
|
42
|
+
const configuration = { id: configurationId, appId, name: 'production' };
|
|
43
|
+
const options = { appId, configurationId };
|
|
44
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
45
|
+
.get(`/v1/apps/${appId}/configurations/${configurationId}`)
|
|
46
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
47
|
+
.reply(200, configuration);
|
|
48
|
+
await getConfigurationCommand.action(options, undefined);
|
|
49
|
+
expect(scope.isDone()).toBe(true);
|
|
50
|
+
expect(console.table).toHaveBeenCalledWith(configuration);
|
|
51
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configuration retrieved successfully.');
|
|
52
|
+
});
|
|
53
|
+
it('should get configuration by name and display JSON format', async () => {
|
|
54
|
+
const appId = 'app-123';
|
|
55
|
+
const configurationName = 'staging';
|
|
56
|
+
const configuration = { id: 'configuration-789', appId, name: configurationName };
|
|
57
|
+
const options = { appId, json: true, name: configurationName };
|
|
58
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
59
|
+
.get(`/v1/apps/${appId}/configurations`)
|
|
60
|
+
.query({ name: configurationName })
|
|
61
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
62
|
+
.reply(200, [configuration]);
|
|
63
|
+
await getConfigurationCommand.action(options, undefined);
|
|
64
|
+
expect(scope.isDone()).toBe(true);
|
|
65
|
+
expect(console.log).toHaveBeenCalledWith(JSON.stringify(configuration, null, 2));
|
|
66
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('should prompt for app and configuration when not provided', async () => {
|
|
69
|
+
const orgId = 'org-1';
|
|
70
|
+
const appId = 'app-1';
|
|
71
|
+
const configurationId = 'configuration-456';
|
|
72
|
+
const configurationName = 'development';
|
|
73
|
+
const configuration = { id: configurationId, appId, name: configurationName };
|
|
74
|
+
const options = {};
|
|
75
|
+
const listScope = nock(DEFAULT_API_BASE_URL)
|
|
76
|
+
.get(`/v1/apps/${appId}/configurations`)
|
|
77
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
78
|
+
.reply(200, [configuration]);
|
|
79
|
+
const getScope = nock(DEFAULT_API_BASE_URL)
|
|
80
|
+
.get(`/v1/apps/${appId}/configurations/${configurationId}`)
|
|
81
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
82
|
+
.reply(200, configuration);
|
|
83
|
+
mockPromptOrganizationSelection.mockResolvedValueOnce(orgId);
|
|
84
|
+
mockPromptAppSelection.mockResolvedValueOnce(appId);
|
|
85
|
+
mockPrompt.mockResolvedValueOnce(configurationId);
|
|
86
|
+
await getConfigurationCommand.action(options, undefined);
|
|
87
|
+
expect(listScope.isDone()).toBe(true);
|
|
88
|
+
expect(getScope.isDone()).toBe(true);
|
|
89
|
+
expect(mockPrompt).toHaveBeenCalledWith('Select the native configuration:', {
|
|
90
|
+
type: 'select',
|
|
91
|
+
options: [{ label: configurationName, value: configurationId }],
|
|
92
|
+
});
|
|
93
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configuration retrieved successfully.');
|
|
94
|
+
});
|
|
95
|
+
it('should handle configuration not found by name', async () => {
|
|
96
|
+
const appId = 'app-123';
|
|
97
|
+
const configurationName = 'nonexistent';
|
|
98
|
+
const options = { appId, name: configurationName };
|
|
99
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
100
|
+
.get(`/v1/apps/${appId}/configurations`)
|
|
101
|
+
.query({ name: configurationName })
|
|
102
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
103
|
+
.reply(200, []);
|
|
104
|
+
await expect(getConfigurationCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
105
|
+
expect(scope.isDone()).toBe(true);
|
|
106
|
+
expect(mockConsola.error).toHaveBeenCalledWith('Native configuration not found.');
|
|
107
|
+
});
|
|
108
|
+
it('should handle API error', async () => {
|
|
109
|
+
const appId = 'app-123';
|
|
110
|
+
const configurationId = 'configuration-456';
|
|
111
|
+
const options = { appId, configurationId };
|
|
112
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
113
|
+
.get(`/v1/apps/${appId}/configurations/${configurationId}`)
|
|
114
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
115
|
+
.reply(500, { message: 'Internal server error' });
|
|
116
|
+
await expect(getConfigurationCommand.action(options, undefined)).rejects.toThrow();
|
|
117
|
+
expect(scope.isDone()).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import appConfigurationsService from '../../../services/app-configurations.js';
|
|
2
|
+
import { withAuth } from '../../../utils/auth.js';
|
|
3
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
4
|
+
import { promptAppSelection, promptOrganizationSelection } 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: 'List all native configurations for an app.',
|
|
10
|
+
options: defineOptions(z.object({
|
|
11
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
12
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
13
|
+
limit: z.coerce.number().optional().describe('Limit for pagination.'),
|
|
14
|
+
offset: z.coerce.number().optional().describe('Offset for pagination.'),
|
|
15
|
+
})),
|
|
16
|
+
action: withAuth(async (options, args) => {
|
|
17
|
+
let { appId, json, limit, offset } = options;
|
|
18
|
+
if (!appId) {
|
|
19
|
+
if (!isInteractive()) {
|
|
20
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const organizationId = await promptOrganizationSelection();
|
|
24
|
+
appId = await promptAppSelection(organizationId);
|
|
25
|
+
}
|
|
26
|
+
const configurations = await appConfigurationsService.findAll({
|
|
27
|
+
appId,
|
|
28
|
+
limit,
|
|
29
|
+
offset,
|
|
30
|
+
});
|
|
31
|
+
if (json) {
|
|
32
|
+
console.log(JSON.stringify(configurations, null, 2));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.table(configurations);
|
|
36
|
+
consola.success('Native configurations retrieved successfully.');
|
|
37
|
+
}
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
+
import { promptAppSelection, promptOrganizationSelection } 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 listConfigurationsCommand from './list.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
|
+
vi.mock('@/utils/environment.js', () => ({
|
|
15
|
+
isInteractive: () => true,
|
|
16
|
+
}));
|
|
17
|
+
describe('apps-configurations-list', () => {
|
|
18
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
19
|
+
const mockPromptOrganizationSelection = vi.mocked(promptOrganizationSelection);
|
|
20
|
+
const mockPromptAppSelection = vi.mocked(promptAppSelection);
|
|
21
|
+
const mockConsola = vi.mocked(consola);
|
|
22
|
+
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
23
|
+
const configurations = [{ id: 'configuration-456', appId: 'app-123', name: 'production' }];
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
mockUserConfig.read.mockReturnValue({ token: 'test-token' });
|
|
27
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
|
|
28
|
+
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
|
|
29
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
30
|
+
throw new Error(`Process exited with code ${code}`);
|
|
31
|
+
});
|
|
32
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
33
|
+
vi.spyOn(console, 'table').mockImplementation(() => { });
|
|
34
|
+
});
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
nock.cleanAll();
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
it('should list configurations and display table format', async () => {
|
|
40
|
+
const options = { appId: 'app-123' };
|
|
41
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
42
|
+
.get('/v1/apps/app-123/configurations')
|
|
43
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
44
|
+
.reply(200, configurations);
|
|
45
|
+
await listConfigurationsCommand.action(options, undefined);
|
|
46
|
+
expect(scope.isDone()).toBe(true);
|
|
47
|
+
expect(console.table).toHaveBeenCalledWith(configurations);
|
|
48
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configurations retrieved successfully.');
|
|
49
|
+
});
|
|
50
|
+
it('should output JSON when json flag is set', async () => {
|
|
51
|
+
const options = { appId: 'app-123', json: true };
|
|
52
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
53
|
+
.get('/v1/apps/app-123/configurations')
|
|
54
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
55
|
+
.reply(200, configurations);
|
|
56
|
+
await listConfigurationsCommand.action(options, undefined);
|
|
57
|
+
expect(scope.isDone()).toBe(true);
|
|
58
|
+
expect(console.log).toHaveBeenCalledWith(JSON.stringify(configurations, null, 2));
|
|
59
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
it('should forward pagination options', async () => {
|
|
62
|
+
const options = { appId: 'app-123', limit: 10, offset: 20 };
|
|
63
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
64
|
+
.get('/v1/apps/app-123/configurations')
|
|
65
|
+
.query({ limit: '10', offset: '20' })
|
|
66
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
67
|
+
.reply(200, configurations);
|
|
68
|
+
await listConfigurationsCommand.action(options, undefined);
|
|
69
|
+
expect(scope.isDone()).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('should prompt for app when not provided', async () => {
|
|
72
|
+
const orgId = 'org-1';
|
|
73
|
+
const appId = 'app-1';
|
|
74
|
+
const options = {};
|
|
75
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
76
|
+
.get(`/v1/apps/${appId}/configurations`)
|
|
77
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
78
|
+
.reply(200, configurations);
|
|
79
|
+
mockPromptOrganizationSelection.mockResolvedValueOnce(orgId);
|
|
80
|
+
mockPromptAppSelection.mockResolvedValueOnce(appId);
|
|
81
|
+
await listConfigurationsCommand.action(options, undefined);
|
|
82
|
+
expect(scope.isDone()).toBe(true);
|
|
83
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configurations retrieved successfully.');
|
|
84
|
+
});
|
|
85
|
+
it('should handle API error', async () => {
|
|
86
|
+
const options = { appId: 'app-123' };
|
|
87
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
88
|
+
.get('/v1/apps/app-123/configurations')
|
|
89
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
90
|
+
.reply(500, { message: 'Internal server error' });
|
|
91
|
+
await expect(listConfigurationsCommand.action(options, undefined)).rejects.toThrow();
|
|
92
|
+
expect(scope.isDone()).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import appConfigurationsService from '../../../services/app-configurations.js';
|
|
2
|
+
import { withAuth } from '../../../utils/auth.js';
|
|
3
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
4
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } 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: 'Update an existing native configuration.',
|
|
10
|
+
options: defineOptions(z.object({
|
|
11
|
+
appId: z.string().optional().describe('ID of the app.'),
|
|
12
|
+
configurationId: z.string().optional().describe('ID of the native configuration.'),
|
|
13
|
+
displayName: z.string().optional().describe('Display name of the app. Pass an empty string to clear it.'),
|
|
14
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
15
|
+
name: z.string().optional().describe('Name of the native configuration.'),
|
|
16
|
+
packageName: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Package name of the app (application ID on Android, bundle ID on iOS). Pass an empty string to clear it.'),
|
|
20
|
+
})),
|
|
21
|
+
action: withAuth(async (options, args) => {
|
|
22
|
+
let { appId, configurationId, displayName, json, name, packageName } = options;
|
|
23
|
+
if (!appId) {
|
|
24
|
+
if (!isInteractive()) {
|
|
25
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const organizationId = await promptOrganizationSelection();
|
|
29
|
+
appId = await promptAppSelection(organizationId);
|
|
30
|
+
}
|
|
31
|
+
if (!configurationId) {
|
|
32
|
+
if (!isInteractive()) {
|
|
33
|
+
consola.error('You must provide the native configuration ID when running in non-interactive environment.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const configurations = await appConfigurationsService.findAll({ appId });
|
|
37
|
+
if (!configurations.length) {
|
|
38
|
+
consola.error('No native configurations found for this app. Create one first.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
42
|
+
configurationId = await prompt('Select the native configuration to update:', {
|
|
43
|
+
type: 'select',
|
|
44
|
+
options: configurations.map((configuration) => ({ label: configuration.name, value: configuration.id })),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const configuration = await appConfigurationsService.update({
|
|
48
|
+
appId,
|
|
49
|
+
configurationId,
|
|
50
|
+
displayName: displayName === '' ? null : displayName,
|
|
51
|
+
name,
|
|
52
|
+
packageName: packageName === '' ? null : packageName,
|
|
53
|
+
});
|
|
54
|
+
if (json) {
|
|
55
|
+
console.log(JSON.stringify(configuration, null, 2));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
consola.success('Native configuration updated successfully.');
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } 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 updateConfigurationCommand 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
|
+
vi.mock('@/utils/environment.js', () => ({
|
|
15
|
+
isInteractive: () => true,
|
|
16
|
+
}));
|
|
17
|
+
describe('apps-configurations-update', () => {
|
|
18
|
+
const mockUserConfig = vi.mocked(userConfig);
|
|
19
|
+
const mockPrompt = vi.mocked(prompt);
|
|
20
|
+
const mockPromptOrganizationSelection = vi.mocked(promptOrganizationSelection);
|
|
21
|
+
const mockPromptAppSelection = vi.mocked(promptAppSelection);
|
|
22
|
+
const mockConsola = vi.mocked(consola);
|
|
23
|
+
const mockAuthorizationService = vi.mocked(authorizationService);
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
mockUserConfig.read.mockReturnValue({ token: 'test-token' });
|
|
27
|
+
mockAuthorizationService.getCurrentAuthorizationToken.mockReturnValue('test-token');
|
|
28
|
+
mockAuthorizationService.hasAuthorizationToken.mockReturnValue(true);
|
|
29
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
30
|
+
throw new Error(`Process exited with code ${code}`);
|
|
31
|
+
});
|
|
32
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
nock.cleanAll();
|
|
36
|
+
vi.restoreAllMocks();
|
|
37
|
+
});
|
|
38
|
+
it('should update configuration with provided options', async () => {
|
|
39
|
+
const appId = 'app-123';
|
|
40
|
+
const configurationId = 'configuration-456';
|
|
41
|
+
const displayName = 'My App';
|
|
42
|
+
const packageName = 'io.capawesome.app';
|
|
43
|
+
const options = { appId, configurationId, displayName, packageName };
|
|
44
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
45
|
+
.patch(`/v1/apps/${appId}/configurations/${configurationId}`, {
|
|
46
|
+
displayName,
|
|
47
|
+
packageName,
|
|
48
|
+
})
|
|
49
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
50
|
+
.reply(200, { id: configurationId, appId, displayName, packageName });
|
|
51
|
+
await updateConfigurationCommand.action(options, undefined);
|
|
52
|
+
expect(scope.isDone()).toBe(true);
|
|
53
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configuration updated successfully.');
|
|
54
|
+
});
|
|
55
|
+
it('should clear display name and package name when empty strings are provided', async () => {
|
|
56
|
+
const appId = 'app-123';
|
|
57
|
+
const configurationId = 'configuration-456';
|
|
58
|
+
const options = { appId, configurationId, displayName: '', packageName: '' };
|
|
59
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
60
|
+
.patch(`/v1/apps/${appId}/configurations/${configurationId}`, {
|
|
61
|
+
displayName: null,
|
|
62
|
+
packageName: null,
|
|
63
|
+
})
|
|
64
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
65
|
+
.reply(200, { id: configurationId, appId, displayName: null, packageName: null });
|
|
66
|
+
await updateConfigurationCommand.action(options, undefined);
|
|
67
|
+
expect(scope.isDone()).toBe(true);
|
|
68
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configuration updated successfully.');
|
|
69
|
+
});
|
|
70
|
+
it('should output JSON when json flag is set', async () => {
|
|
71
|
+
const appId = 'app-123';
|
|
72
|
+
const configurationId = 'configuration-456';
|
|
73
|
+
const configuration = { id: configurationId, appId, name: 'production' };
|
|
74
|
+
const options = { appId, configurationId, json: true, name: 'production' };
|
|
75
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
76
|
+
.patch(`/v1/apps/${appId}/configurations/${configurationId}`, { name: 'production' })
|
|
77
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
78
|
+
.reply(200, configuration);
|
|
79
|
+
await updateConfigurationCommand.action(options, undefined);
|
|
80
|
+
expect(scope.isDone()).toBe(true);
|
|
81
|
+
expect(console.log).toHaveBeenCalledWith(JSON.stringify(configuration, null, 2));
|
|
82
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
it('should prompt for app and configuration when not provided', async () => {
|
|
85
|
+
const orgId = 'org-1';
|
|
86
|
+
const appId = 'app-1';
|
|
87
|
+
const configurationId = 'configuration-456';
|
|
88
|
+
const configurationName = 'development';
|
|
89
|
+
const options = { displayName: 'My App' };
|
|
90
|
+
const listScope = nock(DEFAULT_API_BASE_URL)
|
|
91
|
+
.get(`/v1/apps/${appId}/configurations`)
|
|
92
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
93
|
+
.reply(200, [{ id: configurationId, appId, name: configurationName }]);
|
|
94
|
+
const updateScope = nock(DEFAULT_API_BASE_URL)
|
|
95
|
+
.patch(`/v1/apps/${appId}/configurations/${configurationId}`, { displayName: 'My App' })
|
|
96
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
97
|
+
.reply(200, { id: configurationId, appId, name: configurationName });
|
|
98
|
+
mockPromptOrganizationSelection.mockResolvedValueOnce(orgId);
|
|
99
|
+
mockPromptAppSelection.mockResolvedValueOnce(appId);
|
|
100
|
+
mockPrompt.mockResolvedValueOnce(configurationId);
|
|
101
|
+
await updateConfigurationCommand.action(options, undefined);
|
|
102
|
+
expect(listScope.isDone()).toBe(true);
|
|
103
|
+
expect(updateScope.isDone()).toBe(true);
|
|
104
|
+
expect(mockPrompt).toHaveBeenCalledWith('Select the native configuration to update:', {
|
|
105
|
+
type: 'select',
|
|
106
|
+
options: [{ label: configurationName, value: configurationId }],
|
|
107
|
+
});
|
|
108
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Native configuration updated successfully.');
|
|
109
|
+
});
|
|
110
|
+
it('should handle API error', async () => {
|
|
111
|
+
const appId = 'app-123';
|
|
112
|
+
const configurationId = 'configuration-456';
|
|
113
|
+
const options = { appId, configurationId, name: 'production' };
|
|
114
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
115
|
+
.patch(`/v1/apps/${appId}/configurations/${configurationId}`)
|
|
116
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
117
|
+
.reply(404, { message: 'Configuration not found' });
|
|
118
|
+
await expect(updateConfigurationCommand.action(options, undefined)).rejects.toThrow();
|
|
119
|
+
expect(scope.isDone()).toBe(true);
|
|
120
|
+
expect(mockConsola.success).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,18 @@ const config = defineConfig({
|
|
|
30
30
|
'apps:list': await import('./commands/apps/list.js').then((mod) => mod.default),
|
|
31
31
|
'apps:transfer': await import('./commands/apps/transfer.js').then((mod) => mod.default),
|
|
32
32
|
'apps:unlink': await import('./commands/apps/unlink.js').then((mod) => mod.default),
|
|
33
|
+
'apps:automations:create': await import('./commands/apps/automations/create.js').then((mod) => mod.default),
|
|
34
|
+
'apps:automations:delete': await import('./commands/apps/automations/delete.js').then((mod) => mod.default),
|
|
35
|
+
'apps:automations:get': await import('./commands/apps/automations/get.js').then((mod) => mod.default),
|
|
36
|
+
'apps:automations:list': await import('./commands/apps/automations/list.js').then((mod) => mod.default),
|
|
37
|
+
'apps:automations:update': await import('./commands/apps/automations/update.js').then((mod) => mod.default),
|
|
33
38
|
'apps:builds:cancel': await import('./commands/apps/builds/cancel.js').then((mod) => mod.default),
|
|
34
39
|
'apps:builds:create': await import('./commands/apps/builds/create.js').then((mod) => mod.default),
|
|
35
40
|
'apps:builds:failure-summary': await import('./commands/apps/builds/failure-summary.js').then((mod) => mod.default),
|
|
36
41
|
'apps:builds:get': await import('./commands/apps/builds/get.js').then((mod) => mod.default),
|
|
37
42
|
'apps:builds:list': await import('./commands/apps/builds/list.js').then((mod) => mod.default),
|
|
38
43
|
'apps:builds:logs': await import('./commands/apps/builds/logs.js').then((mod) => mod.default),
|
|
44
|
+
'apps:builds:run': await import('./commands/apps/builds/run.js').then((mod) => mod.default),
|
|
39
45
|
'apps:builds:share': await import('./commands/apps/builds/share.js').then((mod) => mod.default),
|
|
40
46
|
'apps:builds:unshare': await import('./commands/apps/builds/unshare.js').then((mod) => mod.default),
|
|
41
47
|
'apps:builds:download': await import('./commands/apps/builds/download.js').then((mod) => mod.default),
|
|
@@ -54,6 +60,11 @@ const config = defineConfig({
|
|
|
54
60
|
'apps:channels:pause': await import('./commands/apps/channels/pause.js').then((mod) => mod.default),
|
|
55
61
|
'apps:channels:resume': await import('./commands/apps/channels/resume.js').then((mod) => mod.default),
|
|
56
62
|
'apps:channels:update': await import('./commands/apps/channels/update.js').then((mod) => mod.default),
|
|
63
|
+
'apps:configurations:create': await import('./commands/apps/configurations/create.js').then((mod) => mod.default),
|
|
64
|
+
'apps:configurations:delete': await import('./commands/apps/configurations/delete.js').then((mod) => mod.default),
|
|
65
|
+
'apps:configurations:get': await import('./commands/apps/configurations/get.js').then((mod) => mod.default),
|
|
66
|
+
'apps:configurations:list': await import('./commands/apps/configurations/list.js').then((mod) => mod.default),
|
|
67
|
+
'apps:configurations:update': await import('./commands/apps/configurations/update.js').then((mod) => mod.default),
|
|
57
68
|
'apps:deployments:create': await import('./commands/apps/deployments/create.js').then((mod) => mod.default),
|
|
58
69
|
'apps:deployments:cancel': await import('./commands/apps/deployments/cancel.js').then((mod) => mod.default),
|
|
59
70
|
'apps:deployments:failure-summary': await import('./commands/apps/deployments/failure-summary.js').then((mod) => mod.default),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppAutomationsServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async create(dto) {
|
|
9
|
+
const { appId, ...bodyData } = dto;
|
|
10
|
+
const response = await this.httpClient.post(`/v1/apps/${appId}/automations`, bodyData, {
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
async delete(dto) {
|
|
18
|
+
await this.httpClient.delete(`/v1/apps/${dto.appId}/automations/${dto.automationId}`, {
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async findAll(dto) {
|
|
25
|
+
const params = {};
|
|
26
|
+
if (dto.limit !== undefined) {
|
|
27
|
+
params.limit = dto.limit.toString();
|
|
28
|
+
}
|
|
29
|
+
if (dto.name) {
|
|
30
|
+
params.name = dto.name;
|
|
31
|
+
}
|
|
32
|
+
if (dto.offset !== undefined) {
|
|
33
|
+
params.offset = dto.offset.toString();
|
|
34
|
+
}
|
|
35
|
+
if (dto.platform) {
|
|
36
|
+
params.platform = dto.platform;
|
|
37
|
+
}
|
|
38
|
+
const response = await this.httpClient.get(`/v1/apps/${dto.appId}/automations`, {
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
41
|
+
},
|
|
42
|
+
params,
|
|
43
|
+
});
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
async findOneById(dto) {
|
|
47
|
+
const response = await this.httpClient.get(`/v1/apps/${dto.appId}/automations/${dto.automationId}`, {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return response.data;
|
|
53
|
+
}
|
|
54
|
+
async update(dto) {
|
|
55
|
+
const { appId, automationId, ...bodyData } = dto;
|
|
56
|
+
await this.httpClient.patch(`/v1/apps/${appId}/automations/${automationId}`, bodyData, {
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const appAutomationsService = new AppAutomationsServiceImpl(httpClient);
|
|
64
|
+
export default appAutomationsService;
|