@capawesome/cli 4.24.0 → 4.26.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 +19 -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/builds/create.js +5 -4
- package/dist/commands/apps/create.js +3 -2
- package/dist/commands/apps/deployments/create.js +3 -2
- package/dist/commands/apps/deployments/create.test.js +17 -0
- package/dist/commands/apps/import.js +10 -9
- package/dist/commands/apps/import.test.js +19 -2
- package/dist/commands/apps/link.js +3 -2
- package/dist/commands/apps/liveupdates/create.js +4 -3
- package/dist/commands/apps/liveupdates/register.js +19 -3
- package/dist/commands/apps/liveupdates/register.test.js +39 -0
- package/dist/commands/apps/liveupdates/rollback.js +3 -2
- package/dist/commands/apps/liveupdates/rollout.js +3 -2
- package/dist/commands/apps/liveupdates/upload.js +20 -3
- package/dist/commands/apps/liveupdates/upload.test.js +60 -0
- 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,25 @@
|
|
|
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.26.0](https://github.com/capawesome-team/cli/compare/v4.25.0...v4.26.0) (2026-10-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add Electron app version constraint flags for live update bundles ([#185](https://github.com/capawesome-team/cli/issues/185)) ([b1109d6](https://github.com/capawesome-team/cli/commit/b1109d6b40c01325e35cbd9f0a2aa0d6c8123e7a))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* use the configured console URL in printed links ([#241](https://github.com/capawesome-team/cli/issues/241)) ([511d220](https://github.com/capawesome-team/cli/commit/511d2201643aed8733f36a1ca9edef67b24cd850))
|
|
16
|
+
|
|
17
|
+
## [4.25.0](https://github.com/capawesome-team/cli/compare/v4.24.0...v4.25.0) (2026-10-04)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **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))
|
|
23
|
+
|
|
5
24
|
## [4.24.0](https://github.com/capawesome-team/cli/compare/v4.23.1...v4.24.0) (2026-10-01)
|
|
6
25
|
|
|
7
26
|
|
|
@@ -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';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appBuildSourcesService from '../../../services/app-build-sources.js';
|
|
3
2
|
import appBuildsService from '../../../services/app-builds.js';
|
|
4
3
|
import appCertificatesService from '../../../services/app-certificates.js';
|
|
5
4
|
import appConfigurationsService from '../../../services/app-configurations.js';
|
|
6
5
|
import appEnvironmentsService from '../../../services/app-environments.js';
|
|
7
6
|
import appsService from '../../../services/apps.js';
|
|
7
|
+
import configService from '../../../services/config.js';
|
|
8
8
|
import { APP_BUILD_ARTIFACT_FORM_FACTORS, APP_BUILD_ARTIFACT_TYPES_BY_PLATFORM, handleAppBuildArtifactDownload, } from '../../../utils/app-build-artifacts.js';
|
|
9
9
|
import { getAppBuildShareUrls } from '../../../utils/app-build-shares.js';
|
|
10
10
|
import { parseKeyValuePairs } from '../../../utils/app-environments.js';
|
|
@@ -423,7 +423,8 @@ export default defineCommand({
|
|
|
423
423
|
});
|
|
424
424
|
consola.info(`Build ID: ${response.id}`);
|
|
425
425
|
consola.info(`Build Number: ${response.numberAsString}`);
|
|
426
|
-
|
|
426
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
427
|
+
consola.info(`Build URL: ${consoleBaseUrl}/apps/${appId}/builds/${response.id}`);
|
|
427
428
|
consola.success('Build created successfully.');
|
|
428
429
|
if (channels?.length) {
|
|
429
430
|
consola.info(`The build will be deployed to the following ${channels.length === 1 ? 'channel' : 'channels'} once it succeeds: ${channels.join(', ')}.`);
|
|
@@ -446,7 +447,7 @@ export default defineCommand({
|
|
|
446
447
|
});
|
|
447
448
|
consola.info(`Build ID: ${response.id}`);
|
|
448
449
|
consola.info(`Build Number: ${response.numberAsString}`);
|
|
449
|
-
consola.info(`Build URL: ${
|
|
450
|
+
consola.info(`Build URL: ${consoleBaseUrl}/apps/${appId}/builds/${response.id}`);
|
|
450
451
|
consola.success('Build completed successfully.');
|
|
451
452
|
console.log();
|
|
452
453
|
// Download artifacts if flags are set
|
|
@@ -517,7 +518,7 @@ export default defineCommand({
|
|
|
517
518
|
else {
|
|
518
519
|
consola.info(`Build ID: ${response.id}`);
|
|
519
520
|
consola.info(`Build Number: ${response.numberAsString}`);
|
|
520
|
-
consola.info(`Build URL: ${
|
|
521
|
+
consola.info(`Build URL: ${consoleBaseUrl}/apps/${appId}/builds/${response.id}`);
|
|
521
522
|
consola.success('Build started successfully.');
|
|
522
523
|
}
|
|
523
524
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../config/consts.js';
|
|
2
1
|
import appsService from '../../services/apps.js';
|
|
2
|
+
import configService from '../../services/config.js';
|
|
3
3
|
import { withAuth } from '../../utils/auth.js';
|
|
4
4
|
import { isInteractive } from '../../utils/environment.js';
|
|
5
5
|
import { prompt, promptOrganizationSelection } from '../../utils/prompt.js';
|
|
@@ -39,7 +39,8 @@ export default defineCommand({
|
|
|
39
39
|
const response = await appsService.create({ name, organizationId, type });
|
|
40
40
|
if (!json) {
|
|
41
41
|
consola.info(`App ID: ${response.id}`);
|
|
42
|
-
|
|
42
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
43
|
+
consola.info(`App URL: ${consoleBaseUrl}/apps/${response.id}`);
|
|
43
44
|
consola.success('App created successfully.');
|
|
44
45
|
}
|
|
45
46
|
let shouldLink = options.link ?? false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appBuildsService from '../../../services/app-builds.js';
|
|
3
2
|
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
4
3
|
import appDestinationsService from '../../../services/app-destinations.js';
|
|
4
|
+
import configService from '../../../services/config.js';
|
|
5
5
|
import { withAuth } from '../../../utils/auth.js';
|
|
6
6
|
import { isInteractive } from '../../../utils/environment.js';
|
|
7
7
|
import { offerJobFailureSummary } from '../../../utils/job-failure-summary.js';
|
|
@@ -171,7 +171,8 @@ export default defineCommand({
|
|
|
171
171
|
releaseNotes,
|
|
172
172
|
});
|
|
173
173
|
consola.info(`Deployment ID: ${response.id}`);
|
|
174
|
-
|
|
174
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
175
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${response.id}`);
|
|
175
176
|
consola.success('Deployment created successfully.');
|
|
176
177
|
// Wait for deployment job to complete by default, unless --detached flag is set
|
|
177
178
|
const shouldWait = !options.detached && build.platform !== 'web';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_API_BASE_URL } from '../../../config/consts.js';
|
|
2
2
|
import authorizationService from '../../../services/authorization-service.js';
|
|
3
|
+
import configService from '../../../services/config.js';
|
|
3
4
|
import userConfig from '../../../utils/user-config.js';
|
|
4
5
|
import consola from 'consola';
|
|
5
6
|
import nock from 'nock';
|
|
@@ -62,6 +63,22 @@ describe('apps-deployments-create', () => {
|
|
|
62
63
|
expect(deploymentScope.isDone()).toBe(true);
|
|
63
64
|
expect(mockConsola.success).toHaveBeenCalledWith('Deployment created successfully.');
|
|
64
65
|
});
|
|
66
|
+
it('should print the deployment url using the configured console base url', async () => {
|
|
67
|
+
const consoleBaseUrl = 'https://console.example.com';
|
|
68
|
+
const getValueForKey = configService.getValueForKey.bind(configService);
|
|
69
|
+
vi.spyOn(configService, 'getValueForKey').mockImplementation((key) => key === 'CONSOLE_BASE_URL' ? Promise.resolve(consoleBaseUrl) : getValueForKey(key));
|
|
70
|
+
const options = { appId, buildId, destination: 'Google Play', detached: true };
|
|
71
|
+
nock(DEFAULT_API_BASE_URL)
|
|
72
|
+
.get(`/v1/apps/${appId}/builds/${buildId}`)
|
|
73
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
74
|
+
.reply(200, { id: buildId, platform: 'android' });
|
|
75
|
+
nock(DEFAULT_API_BASE_URL)
|
|
76
|
+
.post(`/v1/apps/${appId}/deployments`)
|
|
77
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
78
|
+
.reply(201, { id: deploymentId, jobId: 'job-1' });
|
|
79
|
+
await createCommand.action(options, undefined);
|
|
80
|
+
expect(mockConsola.info).toHaveBeenCalledWith(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${deploymentId}`);
|
|
81
|
+
});
|
|
65
82
|
it('should reject translated release notes without a default text', async () => {
|
|
66
83
|
const options = { appId, buildId, destination: 'Google Play', releaseNotesLocale: ['de-DE=Fehlerbehebungen'] };
|
|
67
84
|
await expect(createCommand.action(options, undefined)).rejects.toThrow('Process exited with code 1');
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../config/consts.js';
|
|
2
1
|
import appAutomationsService from '../../services/app-automations.js';
|
|
3
2
|
import appCertificatesService from '../../services/app-certificates.js';
|
|
4
3
|
import appChannelsService from '../../services/app-channels.js';
|
|
@@ -8,6 +7,7 @@ import appEnvironmentsService from '../../services/app-environments.js';
|
|
|
8
7
|
import appGoogleServiceAccountKeysService from '../../services/app-google-service-account-keys.js';
|
|
9
8
|
import appProvisioningProfilesService from '../../services/app-provisioning-profiles.js';
|
|
10
9
|
import appsService from '../../services/apps.js';
|
|
10
|
+
import configService from '../../services/config.js';
|
|
11
11
|
import gitConnectionsService from '../../services/git-connections.js';
|
|
12
12
|
import { generateUniqueName, isNameTaken } from '../../utils/app-import.js';
|
|
13
13
|
import { parseAppflowExport } from '../../utils/appflow-export.js';
|
|
@@ -103,12 +103,13 @@ export default defineCommand({
|
|
|
103
103
|
selectedSkippedApps.push(...unresolvedApps);
|
|
104
104
|
await assignUniqueAppNames(resolvedApps, organizationId);
|
|
105
105
|
const gitConnectionIds = await findGitConnectionIds(resolvedApps, organizationId);
|
|
106
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
106
107
|
for (const provider of getGitProviders(resolvedApps)) {
|
|
107
108
|
if (gitConnectionIds.has(provider)) {
|
|
108
109
|
continue;
|
|
109
110
|
}
|
|
110
111
|
const appCount = resolvedApps.filter((app) => app.repository?.provider === provider).length;
|
|
111
|
-
consola.warn(`The organization has no \`${provider}\` git connection, so the repositories of ${appCount} app(s) will not be linked. Connect the git provider at ${
|
|
112
|
+
consola.warn(`The organization has no \`${provider}\` git connection, so the repositories of ${appCount} app(s) will not be linked. Connect the git provider at ${consoleBaseUrl}/organizations/${organizationId}/git before running the import to link them automatically, or link the repositories manually in the Capawesome Cloud Console afterwards.`);
|
|
112
113
|
}
|
|
113
114
|
const outcomes = [];
|
|
114
115
|
for (const app of resolvedApps) {
|
|
@@ -132,10 +133,10 @@ export default defineCommand({
|
|
|
132
133
|
}
|
|
133
134
|
const errorCount = outcomes.reduce((count, outcome) => count + outcome.errors.length, 0);
|
|
134
135
|
if (json) {
|
|
135
|
-
printJsonSummary(outcomes, selectedSkippedApps, warnings, dryRun === true);
|
|
136
|
+
printJsonSummary(outcomes, selectedSkippedApps, warnings, dryRun === true, consoleBaseUrl);
|
|
136
137
|
}
|
|
137
138
|
else {
|
|
138
|
-
printSummary(outcomes, selectedSkippedApps, dryRun === true, file);
|
|
139
|
+
printSummary(outcomes, selectedSkippedApps, dryRun === true, file, consoleBaseUrl);
|
|
139
140
|
}
|
|
140
141
|
if (errorCount > 0) {
|
|
141
142
|
process.exitCode = 1;
|
|
@@ -415,7 +416,7 @@ const importApp = async (organizationId, outcome, gitConnectionIds) => {
|
|
|
415
416
|
buildType: automation.buildType,
|
|
416
417
|
enabled: automation.enabled,
|
|
417
418
|
appCertificateName: automation.appCertificateName,
|
|
418
|
-
|
|
419
|
+
appChannelNames: automation.appChannelNames,
|
|
419
420
|
appConfigurationName: automation.appConfigurationName,
|
|
420
421
|
appDestinationName: automation.appDestinationName,
|
|
421
422
|
appEnvironmentName: automation.appEnvironmentName,
|
|
@@ -444,7 +445,7 @@ const importApp = async (organizationId, outcome, gitConnectionIds) => {
|
|
|
444
445
|
}
|
|
445
446
|
}
|
|
446
447
|
};
|
|
447
|
-
const printSummary = (outcomes, skippedApps, dryRun, file) => {
|
|
448
|
+
const printSummary = (outcomes, skippedApps, dryRun, file, consoleBaseUrl) => {
|
|
448
449
|
consola.log('');
|
|
449
450
|
if (dryRun) {
|
|
450
451
|
consola.info('Dry run. No resources were created.');
|
|
@@ -477,7 +478,7 @@ const printSummary = (outcomes, skippedApps, dryRun, file) => {
|
|
|
477
478
|
}
|
|
478
479
|
for (const outcome of outcomes) {
|
|
479
480
|
if (outcome.id) {
|
|
480
|
-
consola.info(`App URL: ${
|
|
481
|
+
consola.info(`App URL: ${consoleBaseUrl}/apps/${outcome.id}`);
|
|
481
482
|
}
|
|
482
483
|
}
|
|
483
484
|
const retryLaterApps = skippedApps.filter((app) => app.retryLater);
|
|
@@ -498,7 +499,7 @@ const printSummary = (outcomes, skippedApps, dryRun, file) => {
|
|
|
498
499
|
const formatCount = (created, total, dryRun) => {
|
|
499
500
|
return dryRun ? `${total}` : `${created}/${total}`;
|
|
500
501
|
};
|
|
501
|
-
const printJsonSummary = (outcomes, skippedApps, warnings, dryRun) => {
|
|
502
|
+
const printJsonSummary = (outcomes, skippedApps, warnings, dryRun, consoleBaseUrl) => {
|
|
502
503
|
console.log(JSON.stringify({
|
|
503
504
|
dryRun,
|
|
504
505
|
warnings,
|
|
@@ -507,7 +508,7 @@ const printJsonSummary = (outcomes, skippedApps, warnings, dryRun) => {
|
|
|
507
508
|
name: outcome.app.name,
|
|
508
509
|
sourceId: outcome.app.sourceId,
|
|
509
510
|
sourceName: outcome.app.sourceName,
|
|
510
|
-
webUrl: outcome.id ? `${
|
|
511
|
+
webUrl: outcome.id ? `${consoleBaseUrl}/apps/${outcome.id}` : null,
|
|
511
512
|
created: outcome.created,
|
|
512
513
|
notes: outcome.app.notes,
|
|
513
514
|
renames: outcome.app.renames,
|
|
@@ -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);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../config/consts.js';
|
|
2
1
|
import appsService from '../../services/apps.js';
|
|
2
|
+
import configService from '../../services/config.js';
|
|
3
3
|
import gitConnectionsService from '../../services/git-connections.js';
|
|
4
4
|
import { withAuth } from '../../utils/auth.js';
|
|
5
5
|
import { isInteractive } from '../../utils/environment.js';
|
|
@@ -37,7 +37,8 @@ export default defineCommand({
|
|
|
37
37
|
limit: 1,
|
|
38
38
|
});
|
|
39
39
|
if (!gitConnection) {
|
|
40
|
-
|
|
40
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
41
|
+
consola.error(`No \`${gitRemoteInfo.provider}\` git connection found in the organization. Connect the git provider first at ${consoleBaseUrl}/organizations/${app.organizationId}/git.`);
|
|
41
42
|
process.exit(1);
|
|
42
43
|
}
|
|
43
44
|
gitConnectionId = gitConnection.id;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appBuildSourcesService from '../../../services/app-build-sources.js';
|
|
3
2
|
import appBuildsService from '../../../services/app-builds.js';
|
|
4
3
|
import appCertificatesService from '../../../services/app-certificates.js';
|
|
5
4
|
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
6
5
|
import appEnvironmentsService from '../../../services/app-environments.js';
|
|
6
|
+
import configService from '../../../services/config.js';
|
|
7
7
|
import { parseKeyValuePairs } from '../../../utils/app-environments.js';
|
|
8
8
|
import { withAuth } from '../../../utils/auth.js';
|
|
9
9
|
import { parseCustomProperties } from '../../../utils/custom-properties.js';
|
|
@@ -241,7 +241,8 @@ export default defineCommand({
|
|
|
241
241
|
});
|
|
242
242
|
consola.info(`Build ID: ${response.id}`);
|
|
243
243
|
consola.info(`Build Number: ${response.numberAsString}`);
|
|
244
|
-
|
|
244
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
245
|
+
consola.info(`Build URL: ${consoleBaseUrl}/apps/${appId}/builds/${response.id}`);
|
|
245
246
|
consola.success('Build created successfully.');
|
|
246
247
|
// Wait for build to complete
|
|
247
248
|
await waitForJobCompletion({ jobId: response.jobId });
|
|
@@ -284,7 +285,7 @@ export default defineCommand({
|
|
|
284
285
|
});
|
|
285
286
|
appDeploymentIds.push(deployment.id);
|
|
286
287
|
consola.info(`Deployment ID: ${deployment.id}`);
|
|
287
|
-
consola.info(`Deployment URL: ${
|
|
288
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${deployment.id}`);
|
|
288
289
|
consola.success('Deployment created successfully.');
|
|
289
290
|
}
|
|
290
291
|
// Output JSON if json flag is set
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appBundlesService from '../../../services/app-bundles.js';
|
|
3
2
|
import appsService from '../../../services/apps.js';
|
|
3
|
+
import configService from '../../../services/config.js';
|
|
4
4
|
import { withAuth } from '../../../utils/auth.js';
|
|
5
5
|
import { parseCustomProperties } from '../../../utils/custom-properties.js';
|
|
6
6
|
import { createBufferFromPath, createBufferFromString, isPrivateKeyContent } from '../../../utils/buffer.js';
|
|
@@ -56,6 +56,18 @@ export default defineCommand({
|
|
|
56
56
|
.max(10)
|
|
57
57
|
.optional()
|
|
58
58
|
.describe('A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times.'),
|
|
59
|
+
electronMax: z
|
|
60
|
+
.string()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('The maximum Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle supports. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
63
|
+
electronMin: z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe('The minimum Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle supports. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
67
|
+
electronEq: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('The exact Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle does not support. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
59
71
|
expiresInDays: z.coerce
|
|
60
72
|
.number({
|
|
61
73
|
message: 'Expiration days must be an integer.',
|
|
@@ -104,7 +116,7 @@ export default defineCommand({
|
|
|
104
116
|
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
|
|
105
117
|
}), { y: 'yes' }),
|
|
106
118
|
action: withAuth(async (options, args) => {
|
|
107
|
-
let { androidEq, androidMax, androidMin, appId, channel, commitMessage, commitRef, commitSha, customProperty, expiresInDays, gitRef, iosEq, iosMax, iosMin, json, path, privateKey, rolloutPercentage, url, } = options;
|
|
119
|
+
let { androidEq, androidMax, androidMin, appId, channel, commitMessage, commitRef, commitSha, customProperty, electronEq, electronMax, electronMin, expiresInDays, gitRef, iosEq, iosMax, iosMin, json, path, privateKey, rolloutPercentage, url, } = options;
|
|
108
120
|
if (expiresInDays) {
|
|
109
121
|
consola.warn('The `--expires-in-days` option is deprecated and will be removed in a future version. Bundle expiration is now managed by the data retention policy of your organization billing plan.');
|
|
110
122
|
}
|
|
@@ -225,6 +237,7 @@ export default defineCommand({
|
|
|
225
237
|
channelName: channel,
|
|
226
238
|
checksum,
|
|
227
239
|
eqAndroidAppVersionCode: androidEq,
|
|
240
|
+
eqElectronAppVersionCode: electronEq,
|
|
228
241
|
eqIosAppVersionCode: iosEq,
|
|
229
242
|
gitCommitMessage: commitMessage,
|
|
230
243
|
gitCommitRef: commitRef,
|
|
@@ -233,15 +246,18 @@ export default defineCommand({
|
|
|
233
246
|
customProperties: parseCustomProperties(customProperty),
|
|
234
247
|
url,
|
|
235
248
|
maxAndroidAppVersionCode: androidMax,
|
|
249
|
+
maxElectronAppVersionCode: electronMax,
|
|
236
250
|
maxIosAppVersionCode: iosMax,
|
|
237
251
|
minAndroidAppVersionCode: androidMin,
|
|
252
|
+
minElectronAppVersionCode: electronMin,
|
|
238
253
|
minIosAppVersionCode: iosMin,
|
|
239
254
|
rolloutPercentage: (rolloutPercentage ?? 100) / 100,
|
|
240
255
|
signature,
|
|
241
256
|
});
|
|
242
257
|
consola.info(`Bundle Artifact ID: ${response.id}`);
|
|
243
258
|
if (response.appDeploymentId) {
|
|
244
|
-
|
|
259
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
260
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${response.appDeploymentId}`);
|
|
245
261
|
}
|
|
246
262
|
consola.success('Live Update successfully registered.');
|
|
247
263
|
if (json) {
|
|
@@ -134,6 +134,45 @@ describe('apps-liveupdates-register', () => {
|
|
|
134
134
|
expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`);
|
|
135
135
|
expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.');
|
|
136
136
|
});
|
|
137
|
+
it('should pass Electron version constraints to API when provided', async () => {
|
|
138
|
+
const appId = 'app-123';
|
|
139
|
+
const bundleUrl = 'https://example.com/bundle.zip';
|
|
140
|
+
const bundleId = 'bundle-456';
|
|
141
|
+
const testToken = 'test-token';
|
|
142
|
+
const electronEq = '1.0.0';
|
|
143
|
+
const electronMin = '2.0.0';
|
|
144
|
+
const electronMax = '3.0.0';
|
|
145
|
+
const options = {
|
|
146
|
+
appId,
|
|
147
|
+
url: bundleUrl,
|
|
148
|
+
rolloutPercentage: 1,
|
|
149
|
+
electronEq,
|
|
150
|
+
electronMin,
|
|
151
|
+
electronMax,
|
|
152
|
+
yes: true,
|
|
153
|
+
};
|
|
154
|
+
const appScope = nock(DEFAULT_API_BASE_URL)
|
|
155
|
+
.get(`/v1/apps/${appId}`)
|
|
156
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
157
|
+
.reply(200, { id: appId, name: 'Test App' });
|
|
158
|
+
const bundleScope = nock(DEFAULT_API_BASE_URL)
|
|
159
|
+
.post(`/v1/apps/${appId}/bundles`, {
|
|
160
|
+
appId,
|
|
161
|
+
url: bundleUrl,
|
|
162
|
+
artifactType: 'zip',
|
|
163
|
+
eqElectronAppVersionCode: electronEq,
|
|
164
|
+
minElectronAppVersionCode: electronMin,
|
|
165
|
+
maxElectronAppVersionCode: electronMax,
|
|
166
|
+
rolloutPercentage: 0.01,
|
|
167
|
+
})
|
|
168
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
169
|
+
.reply(201, { id: bundleId, appBuildId: 'build-789' });
|
|
170
|
+
await registerCommand.action(options, undefined);
|
|
171
|
+
expect(appScope.isDone()).toBe(true);
|
|
172
|
+
expect(bundleScope.isDone()).toBe(true);
|
|
173
|
+
expect(mockConsola.info).toHaveBeenCalledWith(`Bundle Artifact ID: ${bundleId}`);
|
|
174
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully registered.');
|
|
175
|
+
});
|
|
137
176
|
it('should register bundle with checksum when path is provided', async () => {
|
|
138
177
|
const appId = 'app-123';
|
|
139
178
|
const bundleUrl = 'https://example.com/bundle.zip';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appChannelsService from '../../../services/app-channels.js';
|
|
3
2
|
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
3
|
+
import configService from '../../../services/config.js';
|
|
4
4
|
import { withAuth } from '../../../utils/auth.js';
|
|
5
5
|
import { isInteractive } from '../../../utils/environment.js';
|
|
6
6
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
@@ -130,7 +130,8 @@ export default defineCommand({
|
|
|
130
130
|
appChannelName: channel,
|
|
131
131
|
});
|
|
132
132
|
consola.info(`Deployment ID: ${response.id}`);
|
|
133
|
-
|
|
133
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
134
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${response.id}`);
|
|
134
135
|
consola.success(`Rolled back to Build #${selectedAppDeployment.appBuild?.numberAsString} (${selectedIndex} step${selectedIndex === 1 ? '' : 's'} back).`);
|
|
135
136
|
}),
|
|
136
137
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
1
|
import appChannelsService from '../../../services/app-channels.js';
|
|
3
2
|
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
3
|
+
import configService from '../../../services/config.js';
|
|
4
4
|
import { withAuth } from '../../../utils/auth.js';
|
|
5
5
|
import { isInteractive } from '../../../utils/environment.js';
|
|
6
6
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
@@ -106,7 +106,8 @@ export default defineCommand({
|
|
|
106
106
|
rolloutPercentage: percentage / 100,
|
|
107
107
|
});
|
|
108
108
|
consola.info(`Deployment ID: ${response.id}`);
|
|
109
|
-
|
|
109
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
110
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${response.id}`);
|
|
110
111
|
consola.success(`Rolled out to ${percentage}%.`);
|
|
111
112
|
}),
|
|
112
113
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MAX_CONCURRENT_FILE_UPLOADS } from '../../../config/index.js';
|
|
2
2
|
import appBundleFilesService from '../../../services/app-bundle-files.js';
|
|
3
3
|
import appBundlesService from '../../../services/app-bundles.js';
|
|
4
4
|
import appsService from '../../../services/apps.js';
|
|
5
|
+
import configService from '../../../services/config.js';
|
|
5
6
|
import { withAuth } from '../../../utils/auth.js';
|
|
6
7
|
import { parseCustomProperties } from '../../../utils/custom-properties.js';
|
|
7
8
|
import { defineCommand, defineOptions } from 'zodline';
|
|
@@ -67,6 +68,18 @@ export default defineCommand({
|
|
|
67
68
|
.max(10)
|
|
68
69
|
.optional()
|
|
69
70
|
.describe('A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times.'),
|
|
71
|
+
electronMax: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('The maximum Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle supports. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
75
|
+
electronMin: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe('The minimum Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle supports. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
79
|
+
electronEq: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('The exact Electron app version (`app.getVersion()`, from `electron/package.json`) that the bundle does not support. Must be in the format `major[.minor[.patch]]`. Prerelease suffixes are not supported.'),
|
|
70
83
|
expiresInDays: z.coerce
|
|
71
84
|
.number({
|
|
72
85
|
message: 'Expiration days must be an integer.',
|
|
@@ -117,7 +130,7 @@ export default defineCommand({
|
|
|
117
130
|
yes: z.boolean().optional().describe('Skip confirmation prompt.'),
|
|
118
131
|
}), { y: 'yes' }),
|
|
119
132
|
action: withAuth(async (options, args) => {
|
|
120
|
-
let { androidEq, androidMax, androidMin, appId, artifactType, channel, commitMessage, commitRef, commitSha, customProperty, expiresInDays, gitRef, iosEq, iosMax, iosMin, json, path, privateKey, rolloutPercentage, } = options;
|
|
133
|
+
let { androidEq, androidMax, androidMin, appId, artifactType, channel, commitMessage, commitRef, commitSha, customProperty, electronEq, electronMax, electronMin, expiresInDays, gitRef, iosEq, iosMax, iosMin, json, path, privateKey, rolloutPercentage, } = options;
|
|
121
134
|
if (expiresInDays) {
|
|
122
135
|
consola.warn('The `--expires-in-days` option is deprecated and will be removed in a future version. Bundle expiration is now managed by the data retention policy of your organization billing plan.');
|
|
123
136
|
}
|
|
@@ -255,6 +268,7 @@ export default defineCommand({
|
|
|
255
268
|
artifactType,
|
|
256
269
|
channelName: channel,
|
|
257
270
|
eqAndroidAppVersionCode: androidEq,
|
|
271
|
+
eqElectronAppVersionCode: electronEq,
|
|
258
272
|
eqIosAppVersionCode: iosEq,
|
|
259
273
|
gitCommitMessage: commitMessage,
|
|
260
274
|
gitCommitRef: commitRef,
|
|
@@ -262,8 +276,10 @@ export default defineCommand({
|
|
|
262
276
|
gitRef,
|
|
263
277
|
customProperties: parseCustomProperties(customProperty),
|
|
264
278
|
maxAndroidAppVersionCode: androidMax,
|
|
279
|
+
maxElectronAppVersionCode: electronMax,
|
|
265
280
|
maxIosAppVersionCode: iosMax,
|
|
266
281
|
minAndroidAppVersionCode: androidMin,
|
|
282
|
+
minElectronAppVersionCode: electronMin,
|
|
267
283
|
minIosAppVersionCode: iosMin,
|
|
268
284
|
// Convert percentage from 0-100 to 0-1 for API
|
|
269
285
|
rolloutPercentage: (rolloutPercentage ?? 100) / 100,
|
|
@@ -287,7 +303,8 @@ export default defineCommand({
|
|
|
287
303
|
});
|
|
288
304
|
consola.info(`Build Artifact ID: ${createBundleResponse.id}`);
|
|
289
305
|
if (updateBundleResponse.appDeploymentId) {
|
|
290
|
-
|
|
306
|
+
const consoleBaseUrl = await configService.getValueForKey('CONSOLE_BASE_URL');
|
|
307
|
+
consola.info(`Deployment URL: ${consoleBaseUrl}/apps/${appId}/deployments/${updateBundleResponse.appDeploymentId}`);
|
|
291
308
|
}
|
|
292
309
|
consola.success('Live Update successfully uploaded.');
|
|
293
310
|
if (json) {
|
|
@@ -211,6 +211,66 @@ describe('apps-liveupdates-upload', () => {
|
|
|
211
211
|
expect(mockConsola.info).toHaveBeenCalledWith(`Build Artifact ID: ${bundleId}`);
|
|
212
212
|
expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully uploaded.');
|
|
213
213
|
});
|
|
214
|
+
it('should pass Electron version constraints to API when provided', async () => {
|
|
215
|
+
const appId = 'app-123';
|
|
216
|
+
const bundlePath = './dist';
|
|
217
|
+
const bundleId = 'bundle-456';
|
|
218
|
+
const testToken = 'test-token';
|
|
219
|
+
const testBuffer = Buffer.from('test');
|
|
220
|
+
const electronEq = '1.0.0';
|
|
221
|
+
const electronMin = '2.0.0';
|
|
222
|
+
const electronMax = '3.0.0';
|
|
223
|
+
const options = {
|
|
224
|
+
appId,
|
|
225
|
+
path: bundlePath,
|
|
226
|
+
artifactType: 'zip',
|
|
227
|
+
rollout: 1,
|
|
228
|
+
electronEq,
|
|
229
|
+
electronMin,
|
|
230
|
+
electronMax,
|
|
231
|
+
};
|
|
232
|
+
mockIsReadable.mockResolvedValue(true);
|
|
233
|
+
mockIsDirectory.mockResolvedValue(true);
|
|
234
|
+
mockGetFilesInDirectoryAndSubdirectories.mockResolvedValue([
|
|
235
|
+
{ href: 'index.html', mimeType: 'text/html', name: 'index.html', path: 'index.html' },
|
|
236
|
+
]);
|
|
237
|
+
// Mock utility functions
|
|
238
|
+
const mockZip = await import('../../../utils/zip.js');
|
|
239
|
+
const mockHash = await import('../../../utils/hash.js');
|
|
240
|
+
vi.mocked(mockZip.default.isZipped).mockReturnValue(false);
|
|
241
|
+
vi.mocked(mockZip.default.zipFolder).mockResolvedValue(testBuffer);
|
|
242
|
+
vi.mocked(mockHash.createHash).mockResolvedValue('test-hash');
|
|
243
|
+
const appScope = nock(DEFAULT_API_BASE_URL)
|
|
244
|
+
.get(`/v1/apps/${appId}`)
|
|
245
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
246
|
+
.reply(200, { id: appId, name: 'Test App' });
|
|
247
|
+
const bundleScope = nock(DEFAULT_API_BASE_URL)
|
|
248
|
+
.post(`/v1/apps/${appId}/bundles`, {
|
|
249
|
+
appId,
|
|
250
|
+
artifactType: 'zip',
|
|
251
|
+
eqElectronAppVersionCode: electronEq,
|
|
252
|
+
minElectronAppVersionCode: electronMin,
|
|
253
|
+
maxElectronAppVersionCode: electronMax,
|
|
254
|
+
rolloutPercentage: 1,
|
|
255
|
+
})
|
|
256
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
257
|
+
.reply(201, { id: bundleId, appBuildId: 'build-789' });
|
|
258
|
+
const uploadScope = nock(DEFAULT_API_BASE_URL)
|
|
259
|
+
.post(`/v1/apps/${appId}/bundles/${bundleId}/files`)
|
|
260
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
261
|
+
.reply(201, { id: 'file-123' });
|
|
262
|
+
const updateScope = nock(DEFAULT_API_BASE_URL)
|
|
263
|
+
.patch(`/v1/apps/${appId}/bundles/${bundleId}`)
|
|
264
|
+
.matchHeader('Authorization', `Bearer ${testToken}`)
|
|
265
|
+
.reply(200, { id: bundleId });
|
|
266
|
+
await uploadCommand.action(options, undefined);
|
|
267
|
+
expect(appScope.isDone()).toBe(true);
|
|
268
|
+
expect(bundleScope.isDone()).toBe(true);
|
|
269
|
+
expect(uploadScope.isDone()).toBe(true);
|
|
270
|
+
expect(updateScope.isDone()).toBe(true);
|
|
271
|
+
expect(mockConsola.info).toHaveBeenCalledWith(`Build Artifact ID: ${bundleId}`);
|
|
272
|
+
expect(mockConsola.success).toHaveBeenCalledWith('Live Update successfully uploaded.');
|
|
273
|
+
});
|
|
214
274
|
it('should output JSON when json flag is set', async () => {
|
|
215
275
|
const appId = 'app-123';
|
|
216
276
|
const bundlePath = './dist';
|
|
@@ -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
|
]);
|