@capawesome/cli 4.24.0 → 4.25.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 +7 -0
- package/dist/commands/apps/automations/create.js +6 -2
- package/dist/commands/apps/automations/create.test.js +46 -0
- package/dist/commands/apps/automations/update.js +4 -4
- package/dist/commands/apps/automations/update.test.js +24 -0
- package/dist/commands/apps/import.js +1 -1
- package/dist/commands/apps/import.test.js +19 -2
- package/dist/utils/appflow-export.js +8 -20
- package/dist/utils/appflow-export.test.js +2 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [4.25.0](https://github.com/capawesome-team/cli/compare/v4.24.0...v4.25.0) (2026-10-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **apps:** deploy automation builds to multiple channels ([#239](https://github.com/capawesome-team/cli/issues/239)) ([4904d60](https://github.com/capawesome-team/cli/commit/4904d607ec40f976bc002d9c4a44ed8374baec71))
|
|
11
|
+
|
|
5
12
|
## [4.24.0](https://github.com/capawesome-team/cli/compare/v4.23.1...v4.24.0) (2026-10-01)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import appAutomationsService from '../../../services/app-automations.js';
|
|
2
2
|
import appsService from '../../../services/apps.js';
|
|
3
3
|
import { withAuth } from '../../../utils/auth.js';
|
|
4
|
+
import { parseListOption } from '../../../utils/cli-options.js';
|
|
4
5
|
import { isInteractive } from '../../../utils/environment.js';
|
|
5
6
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
6
7
|
import consola from 'consola';
|
|
@@ -11,7 +12,10 @@ export default defineCommand({
|
|
|
11
12
|
options: defineOptions(z.object({
|
|
12
13
|
appId: z.string().optional().describe('ID of the app.'),
|
|
13
14
|
certificate: z.string().optional().describe('The name of the certificate to use for the build.'),
|
|
14
|
-
channel: z
|
|
15
|
+
channel: z
|
|
16
|
+
.array(z.string())
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('The name of a channel to deploy to (Web only). Can be specified multiple times or comma-separated.'),
|
|
15
19
|
commitMessagePattern: z
|
|
16
20
|
.string()
|
|
17
21
|
.optional()
|
|
@@ -117,7 +121,7 @@ export default defineCommand({
|
|
|
117
121
|
}
|
|
118
122
|
const automation = await appAutomationsService.create({
|
|
119
123
|
appCertificateName: options.certificate,
|
|
120
|
-
|
|
124
|
+
appChannelNames: parseListOption(options.channel),
|
|
121
125
|
appConfigurationName: options.configuration,
|
|
122
126
|
appDestinationName: options.destination,
|
|
123
127
|
appEnvironmentName: options.environment,
|
|
@@ -68,6 +68,52 @@ describe('apps-automations-create', () => {
|
|
|
68
68
|
expect(mockConsola.info).toHaveBeenCalledWith(`Automation ID: ${automationId}`);
|
|
69
69
|
expect(mockConsola.success).toHaveBeenCalledWith('Automation created successfully.');
|
|
70
70
|
});
|
|
71
|
+
it('should send the channels as appChannelNames', async () => {
|
|
72
|
+
const appId = 'app-123';
|
|
73
|
+
const automationId = 'automation-456';
|
|
74
|
+
const options = {
|
|
75
|
+
appId,
|
|
76
|
+
channel: ['beta', 'alpha'],
|
|
77
|
+
name: 'nightly',
|
|
78
|
+
platform: 'web',
|
|
79
|
+
triggerType: 'branch',
|
|
80
|
+
};
|
|
81
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
82
|
+
.post(`/v1/apps/${appId}/automations`, {
|
|
83
|
+
appChannelNames: ['beta', 'alpha'],
|
|
84
|
+
name: 'nightly',
|
|
85
|
+
platform: 'web',
|
|
86
|
+
triggerType: 'branch',
|
|
87
|
+
})
|
|
88
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
89
|
+
.reply(201, { id: automationId, name: 'nightly' });
|
|
90
|
+
await createAutomationCommand.action(options, undefined);
|
|
91
|
+
expect(scope.isDone()).toBe(true);
|
|
92
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Automation created successfully.');
|
|
93
|
+
});
|
|
94
|
+
it('should split comma-separated channels into separate appChannelNames', async () => {
|
|
95
|
+
const appId = 'app-123';
|
|
96
|
+
const automationId = 'automation-456';
|
|
97
|
+
const options = {
|
|
98
|
+
appId,
|
|
99
|
+
channel: ['beta, alpha'],
|
|
100
|
+
name: 'nightly',
|
|
101
|
+
platform: 'web',
|
|
102
|
+
triggerType: 'branch',
|
|
103
|
+
};
|
|
104
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
105
|
+
.post(`/v1/apps/${appId}/automations`, {
|
|
106
|
+
appChannelNames: ['beta', 'alpha'],
|
|
107
|
+
name: 'nightly',
|
|
108
|
+
platform: 'web',
|
|
109
|
+
triggerType: 'branch',
|
|
110
|
+
})
|
|
111
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
112
|
+
.reply(201, { id: automationId, name: 'nightly' });
|
|
113
|
+
await createAutomationCommand.action(options, undefined);
|
|
114
|
+
expect(scope.isDone()).toBe(true);
|
|
115
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Automation created successfully.');
|
|
116
|
+
});
|
|
71
117
|
it('should output JSON when json flag is set', async () => {
|
|
72
118
|
const appId = 'app-123';
|
|
73
119
|
const automationId = 'automation-456';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import appAutomationsService from '../../../services/app-automations.js';
|
|
2
2
|
import { withAuth } from '../../../utils/auth.js';
|
|
3
|
-
import { clearableValue } from '../../../utils/cli-options.js';
|
|
3
|
+
import { clearableValue, parseListOption } from '../../../utils/cli-options.js';
|
|
4
4
|
import { isInteractive } from '../../../utils/environment.js';
|
|
5
5
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
6
6
|
import consola from 'consola';
|
|
@@ -17,9 +17,9 @@ export default defineCommand({
|
|
|
17
17
|
.optional()
|
|
18
18
|
.describe('The name of the certificate to use for the build. Pass an empty string to clear it.'),
|
|
19
19
|
channel: z
|
|
20
|
-
.string()
|
|
20
|
+
.array(z.string())
|
|
21
21
|
.optional()
|
|
22
|
-
.describe('The name of
|
|
22
|
+
.describe('The name of a channel to deploy to (Web only). Can be specified multiple times or comma-separated. Pass an empty string to clear them.'),
|
|
23
23
|
commitMessagePattern: z
|
|
24
24
|
.string()
|
|
25
25
|
.optional()
|
|
@@ -95,7 +95,7 @@ export default defineCommand({
|
|
|
95
95
|
}
|
|
96
96
|
await appAutomationsService.update({
|
|
97
97
|
appCertificateName: clearableValue(options.certificate),
|
|
98
|
-
|
|
98
|
+
appChannelNames: parseListOption(options.channel),
|
|
99
99
|
appConfigurationName: clearableValue(options.configuration),
|
|
100
100
|
appDestinationName: clearableValue(options.destination),
|
|
101
101
|
appEnvironmentName: clearableValue(options.environment),
|
|
@@ -71,6 +71,30 @@ describe('apps-automations-update', () => {
|
|
|
71
71
|
expect(scope.isDone()).toBe(true);
|
|
72
72
|
expect(mockConsola.success).toHaveBeenCalledWith('Automation updated successfully.');
|
|
73
73
|
});
|
|
74
|
+
it('should send the channels as appChannelNames', async () => {
|
|
75
|
+
const appId = 'app-123';
|
|
76
|
+
const automationId = 'automation-456';
|
|
77
|
+
const options = { appId, automationId, channel: ['beta, alpha', 'gamma'] };
|
|
78
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
79
|
+
.patch(`/v1/apps/${appId}/automations/${automationId}`, { appChannelNames: ['beta', 'alpha', 'gamma'] })
|
|
80
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
81
|
+
.reply(204);
|
|
82
|
+
await updateAutomationCommand.action(options, undefined);
|
|
83
|
+
expect(scope.isDone()).toBe(true);
|
|
84
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Automation updated successfully.');
|
|
85
|
+
});
|
|
86
|
+
it('should clear the channels when an empty string is passed', async () => {
|
|
87
|
+
const appId = 'app-123';
|
|
88
|
+
const automationId = 'automation-456';
|
|
89
|
+
const options = { appId, automationId, channel: [''] };
|
|
90
|
+
const scope = nock(DEFAULT_API_BASE_URL)
|
|
91
|
+
.patch(`/v1/apps/${appId}/automations/${automationId}`, { appChannelNames: [] })
|
|
92
|
+
.matchHeader('Authorization', 'Bearer test-token')
|
|
93
|
+
.reply(204);
|
|
94
|
+
await updateAutomationCommand.action(options, undefined);
|
|
95
|
+
expect(scope.isDone()).toBe(true);
|
|
96
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Automation updated successfully.');
|
|
97
|
+
});
|
|
74
98
|
it('should clear the build stack when an empty string is passed', async () => {
|
|
75
99
|
const appId = 'app-123';
|
|
76
100
|
const automationId = 'automation-456';
|
|
@@ -415,7 +415,7 @@ const importApp = async (organizationId, outcome, gitConnectionIds) => {
|
|
|
415
415
|
buildType: automation.buildType,
|
|
416
416
|
enabled: automation.enabled,
|
|
417
417
|
appCertificateName: automation.appCertificateName,
|
|
418
|
-
|
|
418
|
+
appChannelNames: automation.appChannelNames,
|
|
419
419
|
appConfigurationName: automation.appConfigurationName,
|
|
420
420
|
appDestinationName: automation.appDestinationName,
|
|
421
421
|
appEnvironmentName: automation.appEnvironmentName,
|
|
@@ -101,7 +101,18 @@ describe('apps-import', () => {
|
|
|
101
101
|
destinationId: null,
|
|
102
102
|
},
|
|
103
103
|
],
|
|
104
|
-
'web-build-automations.json': [
|
|
104
|
+
'web-build-automations.json': [
|
|
105
|
+
{
|
|
106
|
+
name: 'Web Prod',
|
|
107
|
+
gitBranch: 'main',
|
|
108
|
+
platform: 'web-deploy',
|
|
109
|
+
environmentId: null,
|
|
110
|
+
webhook: null,
|
|
111
|
+
automationEnabled: true,
|
|
112
|
+
channelIds: ['channel-uuid'],
|
|
113
|
+
webPreviewEnabled: false,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
105
116
|
'signing-certificates/android/Debug-3/android-signing-certificate.json': {
|
|
106
117
|
id: 3,
|
|
107
118
|
name: 'Debug',
|
|
@@ -152,6 +163,12 @@ describe('apps-import', () => {
|
|
|
152
163
|
body.appEnvironmentName === 'Production');
|
|
153
164
|
})
|
|
154
165
|
.reply(201, { id: 'automation-1', name: 'Android Release' })
|
|
166
|
+
.post(`/v1/apps/${appId}/automations`, (body) => {
|
|
167
|
+
return (body.name === 'Web Prod' &&
|
|
168
|
+
body.platform === 'web' &&
|
|
169
|
+
JSON.stringify(body.appChannelNames) === JSON.stringify(['Production']));
|
|
170
|
+
})
|
|
171
|
+
.reply(201, { id: 'automation-2', name: 'Web Prod' })
|
|
155
172
|
.get(`/v1/organizations/${organizationId}/git-connections`)
|
|
156
173
|
.query({ provider: 'github', restricted: 'false', limit: 1 })
|
|
157
174
|
.reply(200, [{ id: 'git-connection-1', provider: 'github' }])
|
|
@@ -170,7 +187,7 @@ describe('apps-import', () => {
|
|
|
170
187
|
name: 'My App',
|
|
171
188
|
sourceId: '6668c18c',
|
|
172
189
|
sourceName: 'My App',
|
|
173
|
-
created: { automations:
|
|
190
|
+
created: { automations: 2, certificates: 1, channels: 1, configurations: 1, destinations: 0, environments: 1 },
|
|
174
191
|
errors: [],
|
|
175
192
|
});
|
|
176
193
|
expect(output.apps[0].webUrl).toContain(appId);
|
|
@@ -372,26 +372,14 @@ const parseAutomations = (appFolder, references, notes) => {
|
|
|
372
372
|
.map((channelId) => resolveName(references.channelNamesById, channelId, automation.name, 'channel', notes))
|
|
373
373
|
.filter((name) => !!name);
|
|
374
374
|
const appEnvironmentName = resolveName(references.environmentNamesById, automation.environmentId, automation.name, 'environment', notes);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
continue;
|
|
384
|
-
}
|
|
385
|
-
for (const channelName of channelNames) {
|
|
386
|
-
automations.push({
|
|
387
|
-
name: channelNames.length > 1 ? `${automation.name} (${channelName})` : automation.name,
|
|
388
|
-
platform: 'web',
|
|
389
|
-
triggerPattern: automation.gitBranch,
|
|
390
|
-
enabled: automation.automationEnabled,
|
|
391
|
-
appChannelName: channelName,
|
|
392
|
-
appEnvironmentName,
|
|
393
|
-
});
|
|
394
|
-
}
|
|
375
|
+
automations.push({
|
|
376
|
+
name: automation.name,
|
|
377
|
+
platform: 'web',
|
|
378
|
+
triggerPattern: automation.gitBranch,
|
|
379
|
+
enabled: automation.automationEnabled,
|
|
380
|
+
appChannelNames: channelNames,
|
|
381
|
+
appEnvironmentName,
|
|
382
|
+
});
|
|
395
383
|
}
|
|
396
384
|
return automations;
|
|
397
385
|
};
|
|
@@ -190,19 +190,11 @@ describe('appflow-export', () => {
|
|
|
190
190
|
appEnvironmentName: undefined,
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
|
-
name: 'Web Prod
|
|
193
|
+
name: 'Web Prod',
|
|
194
194
|
platform: 'web',
|
|
195
195
|
triggerPattern: 'main',
|
|
196
196
|
enabled: true,
|
|
197
|
-
|
|
198
|
-
appEnvironmentName: 'Production',
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: 'Web Prod (Production)',
|
|
202
|
-
platform: 'web',
|
|
203
|
-
triggerPattern: 'main',
|
|
204
|
-
enabled: true,
|
|
205
|
-
appChannelName: 'Production',
|
|
197
|
+
appChannelNames: ['Staging', 'Production'],
|
|
206
198
|
appEnvironmentName: 'Production',
|
|
207
199
|
},
|
|
208
200
|
]);
|