@capawesome/cli 3.3.0 → 3.4.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/builds/cancel.js +106 -0
- package/dist/commands/apps/builds/create.js +359 -0
- package/dist/commands/apps/deployments/cancel.js +111 -0
- package/dist/commands/apps/deployments/create.js +222 -0
- package/dist/index.js +4 -0
- package/dist/services/app-builds.js +52 -0
- package/dist/services/app-certificates.js +24 -0
- package/dist/services/app-deployments.js +48 -0
- package/dist/services/app-destinations.js +24 -0
- package/dist/services/app-environments.js +19 -0
- package/dist/services/jobs.js +19 -0
- package/dist/types/app-build.js +1 -0
- package/dist/types/app-certificate.js +1 -0
- package/dist/types/app-deployment.js +1 -0
- package/dist/types/app-destination.js +1 -0
- package/dist/types/app-environment.js +1 -0
- package/dist/types/job.js +1 -0
- package/dist/utils/ansi.js +15 -0
- package/dist/utils/wait.js +9 -0
- 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
|
+
## [3.4.0](https://github.com/capawesome-team/cli/compare/v3.3.0...v3.4.0) (2025-11-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* support builds and deployments ([#89](https://github.com/capawesome-team/cli/issues/89)) ([71be7b0](https://github.com/capawesome-team/cli/commit/71be7b0b69c59bd5d7e123fe37b30044a47f7afc))
|
|
11
|
+
|
|
5
12
|
## [3.3.0](https://github.com/capawesome-team/cli/compare/v3.2.2...v3.3.0) (2025-10-03)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import appBuildsService from '../../../services/app-builds.js';
|
|
2
|
+
import appsService from '../../../services/apps.js';
|
|
3
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
+
import jobsService from '../../../services/jobs.js';
|
|
5
|
+
import organizationsService from '../../../services/organizations.js';
|
|
6
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
7
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
8
|
+
import consola from 'consola';
|
|
9
|
+
import { hasTTY } from 'std-env';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
export default defineCommand({
|
|
12
|
+
description: 'Cancel an app build.',
|
|
13
|
+
options: defineOptions(z.object({
|
|
14
|
+
appId: z
|
|
15
|
+
.uuid({
|
|
16
|
+
message: 'App ID must be a UUID.',
|
|
17
|
+
})
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('App ID the build belongs to.'),
|
|
20
|
+
buildId: z
|
|
21
|
+
.uuid({
|
|
22
|
+
message: 'Build ID must be a UUID.',
|
|
23
|
+
})
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Build ID to cancel.'),
|
|
26
|
+
})),
|
|
27
|
+
action: async (options) => {
|
|
28
|
+
let { appId, buildId } = options;
|
|
29
|
+
// Check if the user is logged in
|
|
30
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
31
|
+
consola.error('You must be logged in to run this command.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// Prompt for app ID if not provided
|
|
35
|
+
if (!appId) {
|
|
36
|
+
if (!hasTTY) {
|
|
37
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const organizations = await organizationsService.findAll();
|
|
41
|
+
if (organizations.length === 0) {
|
|
42
|
+
consola.error('You must create an organization before canceling a build.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
46
|
+
const organizationId = await prompt('Select the organization of the app for which you want to cancel a build.', {
|
|
47
|
+
type: 'select',
|
|
48
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
49
|
+
});
|
|
50
|
+
if (!organizationId) {
|
|
51
|
+
consola.error('You must select the organization of an app for which you want to cancel a build.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const apps = await appsService.findAll({
|
|
55
|
+
organizationId,
|
|
56
|
+
});
|
|
57
|
+
if (apps.length === 0) {
|
|
58
|
+
consola.error('You must create an app before canceling a build.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
62
|
+
appId = await prompt('Which app do you want to cancel a build for:', {
|
|
63
|
+
type: 'select',
|
|
64
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
65
|
+
});
|
|
66
|
+
if (!appId) {
|
|
67
|
+
consola.error('You must select an app to cancel a build for.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Prompt for build ID if not provided
|
|
72
|
+
if (!buildId) {
|
|
73
|
+
if (!hasTTY) {
|
|
74
|
+
consola.error('You must provide a build ID when running in non-interactive environment.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const builds = await appBuildsService.findAll({ appId });
|
|
78
|
+
if (builds.length === 0) {
|
|
79
|
+
consola.error('No builds found for this app.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
83
|
+
buildId = await prompt('Which build do you want to cancel:', {
|
|
84
|
+
type: 'select',
|
|
85
|
+
options: builds.map((build) => ({
|
|
86
|
+
label: `Build #${build.numberAsString || build.id} (${build.platform} - ${build.type})`,
|
|
87
|
+
value: build.id,
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
if (!buildId) {
|
|
91
|
+
consola.error('You must select a build to cancel.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Fetch the build details to get the job ID
|
|
96
|
+
consola.start('Fetching build details...');
|
|
97
|
+
const build = await appBuildsService.findOne({ appId, appBuildId: buildId });
|
|
98
|
+
// Cancel the job
|
|
99
|
+
consola.start('Canceling build...');
|
|
100
|
+
await jobsService.update({
|
|
101
|
+
jobId: build.jobId,
|
|
102
|
+
dto: { status: 'canceled' },
|
|
103
|
+
});
|
|
104
|
+
consola.success('Build successfully canceled.');
|
|
105
|
+
},
|
|
106
|
+
});
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import appBuildsService from '../../../services/app-builds.js';
|
|
3
|
+
import appCertificatesService from '../../../services/app-certificates.js';
|
|
4
|
+
import appEnvironmentsService from '../../../services/app-environments.js';
|
|
5
|
+
import appsService from '../../../services/apps.js';
|
|
6
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
7
|
+
import organizationsService from '../../../services/organizations.js';
|
|
8
|
+
import { unescapeAnsi } from '../../../utils/ansi.js';
|
|
9
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
10
|
+
import { wait } from '../../../utils/wait.js';
|
|
11
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
12
|
+
import consola from 'consola';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { hasTTY } from 'std-env';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
const IOS_BUILD_TYPES = ['simulator', 'development', 'ad-hoc', 'app-store', 'enterprise'];
|
|
18
|
+
const ANDROID_BUILD_TYPES = ['debug', 'release'];
|
|
19
|
+
export default defineCommand({
|
|
20
|
+
description: 'Create a new app build.',
|
|
21
|
+
options: defineOptions(z.object({
|
|
22
|
+
aab: z
|
|
23
|
+
.union([z.boolean(), z.string()])
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Download the generated AAB file (Android only). Optionally provide a file path.'),
|
|
26
|
+
apk: z
|
|
27
|
+
.union([z.boolean(), z.string()])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Download the generated APK file (Android only). Optionally provide a file path.'),
|
|
30
|
+
appId: z
|
|
31
|
+
.uuid({
|
|
32
|
+
message: 'App ID must be a UUID.',
|
|
33
|
+
})
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('App ID to create the build for.'),
|
|
36
|
+
certificate: z.string().optional().describe('The name of the certificate to use for the build.'),
|
|
37
|
+
detached: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Exit immediately after creating the build without waiting for completion.'),
|
|
41
|
+
environment: z.string().optional().describe('The name of the environment to use for the build.'),
|
|
42
|
+
gitRef: z.string().optional().describe('The Git reference (branch, tag, or commit SHA) to build.'),
|
|
43
|
+
ipa: z
|
|
44
|
+
.union([z.boolean(), z.string()])
|
|
45
|
+
.optional()
|
|
46
|
+
.describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
|
|
47
|
+
platform: z
|
|
48
|
+
.enum(['ios', 'android'], {
|
|
49
|
+
message: 'Platform must be either `ios` or `android`.',
|
|
50
|
+
})
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('The platform for the build. Supported values are `ios` and `android`.'),
|
|
53
|
+
type: z
|
|
54
|
+
.string()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe('The type of build. For iOS, supported values are `simulator`, `development`, `ad-hoc`, `app-store`, and `enterprise`. For Android, supported values are `debug` and `release`.'),
|
|
57
|
+
})),
|
|
58
|
+
action: async (options) => {
|
|
59
|
+
let { appId, platform, type, gitRef, environment, certificate } = options;
|
|
60
|
+
// Check if the user is logged in
|
|
61
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
62
|
+
consola.error('You must be logged in to run this command.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
// Validate that detached flag cannot be used with artifact flags
|
|
66
|
+
if (options.detached && (options.apk || options.aab || options.ipa)) {
|
|
67
|
+
consola.error('The --detached flag cannot be used with --apk, --aab, or --ipa flags.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Prompt for app ID if not provided
|
|
71
|
+
if (!appId) {
|
|
72
|
+
if (!hasTTY) {
|
|
73
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const organizations = await organizationsService.findAll();
|
|
77
|
+
if (organizations.length === 0) {
|
|
78
|
+
consola.error('You must create an organization before creating a build.');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
82
|
+
const organizationId = await prompt('Select the organization of the app for which you want to create a build.', {
|
|
83
|
+
type: 'select',
|
|
84
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
85
|
+
});
|
|
86
|
+
if (!organizationId) {
|
|
87
|
+
consola.error('You must select the organization of an app for which you want to create a build.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const apps = await appsService.findAll({
|
|
91
|
+
organizationId,
|
|
92
|
+
});
|
|
93
|
+
if (apps.length === 0) {
|
|
94
|
+
consola.error('You must create an app before creating a build.');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
98
|
+
appId = await prompt('Which app do you want to create a build for:', {
|
|
99
|
+
type: 'select',
|
|
100
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
101
|
+
});
|
|
102
|
+
if (!appId) {
|
|
103
|
+
consola.error('You must select an app to create a build for.');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Prompt for platform if not provided
|
|
108
|
+
if (!platform) {
|
|
109
|
+
if (!hasTTY) {
|
|
110
|
+
consola.error('You must provide a platform when running in non-interactive environment.');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
114
|
+
platform = await prompt('Select the platform for the build:', {
|
|
115
|
+
type: 'select',
|
|
116
|
+
options: [
|
|
117
|
+
{ label: 'Android', value: 'android' },
|
|
118
|
+
{ label: 'iOS', value: 'ios' },
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
if (!platform) {
|
|
122
|
+
consola.error('You must select a platform.');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Prompt for git ref if not provided
|
|
127
|
+
if (!gitRef) {
|
|
128
|
+
if (!hasTTY) {
|
|
129
|
+
consola.error('You must provide a git ref when running in non-interactive environment.');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
|
|
133
|
+
type: 'text',
|
|
134
|
+
});
|
|
135
|
+
if (!gitRef) {
|
|
136
|
+
consola.error('You must provide a git ref.');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Set default type based on platform if not provided
|
|
141
|
+
if (!type) {
|
|
142
|
+
type = platform === 'android' ? 'debug' : 'simulator';
|
|
143
|
+
}
|
|
144
|
+
// Validate type based on platform
|
|
145
|
+
if (platform === 'ios' && !IOS_BUILD_TYPES.includes(type)) {
|
|
146
|
+
consola.error(`Invalid build type for iOS. Supported values are: ${IOS_BUILD_TYPES.map((t) => `\`${t}\``).join(', ')}.`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
if (platform === 'android' && !ANDROID_BUILD_TYPES.includes(type)) {
|
|
150
|
+
consola.error(`Invalid build type for Android. Supported values are: ${ANDROID_BUILD_TYPES.map((t) => `\`${t}\``).join(', ')}.`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
// Prompt for environment if not provided
|
|
154
|
+
if (!environment && hasTTY) {
|
|
155
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
156
|
+
const selectEnvironment = await prompt('Do you want to select an environment?', {
|
|
157
|
+
type: 'confirm',
|
|
158
|
+
initial: false,
|
|
159
|
+
});
|
|
160
|
+
if (selectEnvironment) {
|
|
161
|
+
const environments = await appEnvironmentsService.findAll({ appId });
|
|
162
|
+
if (environments.length === 0) {
|
|
163
|
+
consola.warn('No environments found for this app.');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
167
|
+
environment = await prompt('Select the environment for the build:', {
|
|
168
|
+
type: 'select',
|
|
169
|
+
options: environments.map((env) => ({ label: env.name, value: env.name })),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Prompt for certificate if not provided
|
|
175
|
+
if (!certificate && hasTTY) {
|
|
176
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
177
|
+
const selectCertificate = await prompt('Do you want to select a certificate?', {
|
|
178
|
+
type: 'confirm',
|
|
179
|
+
initial: false,
|
|
180
|
+
});
|
|
181
|
+
if (selectCertificate) {
|
|
182
|
+
const certificates = await appCertificatesService.findAll({ appId, platform });
|
|
183
|
+
if (certificates.length === 0) {
|
|
184
|
+
consola.warn('No certificates found for this app.');
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
188
|
+
certificate = await prompt('Select the certificate for the build:', {
|
|
189
|
+
type: 'select',
|
|
190
|
+
options: certificates.map((cert) => ({ label: cert.name, value: cert.name })),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Create the app build
|
|
196
|
+
consola.start('Creating build...');
|
|
197
|
+
const response = await appBuildsService.create({
|
|
198
|
+
appCertificateName: certificate,
|
|
199
|
+
appEnvironmentName: environment,
|
|
200
|
+
appId,
|
|
201
|
+
gitRef,
|
|
202
|
+
platform,
|
|
203
|
+
type,
|
|
204
|
+
});
|
|
205
|
+
consola.success(`Build created successfully.`);
|
|
206
|
+
consola.info(`Build Number: ${response.numberAsString}`);
|
|
207
|
+
consola.info(`Build ID: ${response.id}`);
|
|
208
|
+
consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
|
|
209
|
+
// Wait for build job to complete by default, unless --detached flag is set
|
|
210
|
+
const shouldWait = !options.detached;
|
|
211
|
+
if (shouldWait) {
|
|
212
|
+
let lastPrintedLogNumber = 0;
|
|
213
|
+
let isWaitingForStart = true;
|
|
214
|
+
// Poll build status until completion
|
|
215
|
+
while (true) {
|
|
216
|
+
try {
|
|
217
|
+
const build = await appBuildsService.findOne({
|
|
218
|
+
appId,
|
|
219
|
+
appBuildId: response.id,
|
|
220
|
+
relations: 'appBuildArtifacts,job,job.jobLogs',
|
|
221
|
+
});
|
|
222
|
+
if (!build.job) {
|
|
223
|
+
await wait(3000);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const jobStatus = build.job.status;
|
|
227
|
+
// Show spinner while queued or pending
|
|
228
|
+
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
229
|
+
if (isWaitingForStart) {
|
|
230
|
+
consola.start(`Waiting for build to start (status: ${jobStatus})...`);
|
|
231
|
+
}
|
|
232
|
+
await wait(3000);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Stop spinner when job moves to in_progress
|
|
236
|
+
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
237
|
+
isWaitingForStart = false;
|
|
238
|
+
consola.success('Build started...');
|
|
239
|
+
}
|
|
240
|
+
// Print new logs
|
|
241
|
+
if (build.job.jobLogs && build.job.jobLogs.length > 0) {
|
|
242
|
+
const newLogs = build.job.jobLogs
|
|
243
|
+
.filter((log) => log.number > lastPrintedLogNumber)
|
|
244
|
+
.sort((a, b) => a.number - b.number);
|
|
245
|
+
for (const log of newLogs) {
|
|
246
|
+
console.log(unescapeAnsi(log.payload));
|
|
247
|
+
lastPrintedLogNumber = log.number;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Handle terminal states
|
|
251
|
+
if (jobStatus === 'succeeded' ||
|
|
252
|
+
jobStatus === 'failed' ||
|
|
253
|
+
jobStatus === 'canceled' ||
|
|
254
|
+
jobStatus === 'rejected' ||
|
|
255
|
+
jobStatus === 'timed_out') {
|
|
256
|
+
console.log(); // New line for better readability
|
|
257
|
+
if (jobStatus === 'succeeded') {
|
|
258
|
+
consola.success('Build completed successfully.');
|
|
259
|
+
console.log(); // New line for better readability
|
|
260
|
+
// Download artifacts if flags are set
|
|
261
|
+
if (options.apk && platform === 'android') {
|
|
262
|
+
await handleArtifactDownload({
|
|
263
|
+
appId,
|
|
264
|
+
buildId: response.id,
|
|
265
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
266
|
+
artifactType: 'apk',
|
|
267
|
+
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (options.aab && platform === 'android') {
|
|
271
|
+
await handleArtifactDownload({
|
|
272
|
+
appId,
|
|
273
|
+
buildId: response.id,
|
|
274
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
275
|
+
artifactType: 'aab',
|
|
276
|
+
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (options.ipa && platform === 'ios') {
|
|
280
|
+
await handleArtifactDownload({
|
|
281
|
+
appId,
|
|
282
|
+
buildId: response.id,
|
|
283
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
284
|
+
artifactType: 'ipa',
|
|
285
|
+
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
else if (jobStatus === 'failed') {
|
|
291
|
+
consola.error('Build failed.');
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
else if (jobStatus === 'canceled') {
|
|
295
|
+
consola.warn('Build was canceled.');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
else if (jobStatus === 'rejected') {
|
|
299
|
+
consola.error('Build was rejected.');
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
else if (jobStatus === 'timed_out') {
|
|
303
|
+
consola.error('Build timed out.');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Wait before next poll (3 seconds)
|
|
308
|
+
await wait(3000);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
consola.error('Error polling build status:', error);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
/**
|
|
319
|
+
* Download a build artifact (APK, AAB, or IPA).
|
|
320
|
+
*/
|
|
321
|
+
const handleArtifactDownload = async (options) => {
|
|
322
|
+
const { appId, buildId, buildArtifacts, artifactType, filePath } = options;
|
|
323
|
+
try {
|
|
324
|
+
const artifactTypeUpper = artifactType.toUpperCase();
|
|
325
|
+
consola.start(`Downloading ${artifactTypeUpper}...`);
|
|
326
|
+
// Find the artifact
|
|
327
|
+
const artifact = buildArtifacts?.find((artifact) => artifact.type === artifactType);
|
|
328
|
+
if (!artifact) {
|
|
329
|
+
consola.warn(`No ${artifactTypeUpper} artifact found for this build.`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (artifact.status !== 'ready') {
|
|
333
|
+
consola.warn(`${artifactTypeUpper} artifact is not ready (status: ${artifact.status}).`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
// Download the artifact
|
|
337
|
+
const artifactData = await appBuildsService.downloadArtifact({
|
|
338
|
+
appId,
|
|
339
|
+
appBuildId: buildId,
|
|
340
|
+
artifactId: artifact.id,
|
|
341
|
+
});
|
|
342
|
+
// Determine the file path
|
|
343
|
+
let outputPath;
|
|
344
|
+
if (filePath) {
|
|
345
|
+
// Use provided path (can be relative or absolute)
|
|
346
|
+
outputPath = path.resolve(filePath);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
// Default to current working directory with build ID as filename
|
|
350
|
+
outputPath = path.resolve(`${buildId}.${artifactType}`);
|
|
351
|
+
}
|
|
352
|
+
// Save the file
|
|
353
|
+
await fs.writeFile(outputPath, Buffer.from(artifactData));
|
|
354
|
+
consola.success(`${artifactTypeUpper} downloaded successfully: ${outputPath}`);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
consola.error(`Failed to download ${artifactType.toUpperCase()}:`, error);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
2
|
+
import appsService from '../../../services/apps.js';
|
|
3
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
4
|
+
import jobsService from '../../../services/jobs.js';
|
|
5
|
+
import organizationsService from '../../../services/organizations.js';
|
|
6
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
7
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
8
|
+
import consola from 'consola';
|
|
9
|
+
import { hasTTY } from 'std-env';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
export default defineCommand({
|
|
12
|
+
description: 'Cancel an ongoing app deployment.',
|
|
13
|
+
options: defineOptions(z.object({
|
|
14
|
+
appId: z
|
|
15
|
+
.uuid({
|
|
16
|
+
message: 'App ID must be a UUID.',
|
|
17
|
+
})
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('App ID the deployment belongs to.'),
|
|
20
|
+
deploymentId: z
|
|
21
|
+
.uuid({
|
|
22
|
+
message: 'Deployment ID must be a UUID.',
|
|
23
|
+
})
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Deployment ID to cancel.'),
|
|
26
|
+
})),
|
|
27
|
+
action: async (options) => {
|
|
28
|
+
let { appId, deploymentId } = options;
|
|
29
|
+
// Check if the user is logged in
|
|
30
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
31
|
+
consola.error('You must be logged in to run this command.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// Prompt for app ID if not provided
|
|
35
|
+
if (!appId) {
|
|
36
|
+
if (!hasTTY) {
|
|
37
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const organizations = await organizationsService.findAll();
|
|
41
|
+
if (organizations.length === 0) {
|
|
42
|
+
consola.error('You must create an organization before canceling a deployment.');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
46
|
+
const organizationId = await prompt('Select the organization of the app for which you want to cancel a deployment.', {
|
|
47
|
+
type: 'select',
|
|
48
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
49
|
+
});
|
|
50
|
+
if (!organizationId) {
|
|
51
|
+
consola.error('You must select the organization of an app for which you want to cancel a deployment.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const apps = await appsService.findAll({
|
|
55
|
+
organizationId,
|
|
56
|
+
});
|
|
57
|
+
if (apps.length === 0) {
|
|
58
|
+
consola.error('You must create an app before canceling a deployment.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
62
|
+
appId = await prompt('Which app do you want to cancel a deployment for:', {
|
|
63
|
+
type: 'select',
|
|
64
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
65
|
+
});
|
|
66
|
+
if (!appId) {
|
|
67
|
+
consola.error('You must select an app to cancel a deployment for.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Prompt for deployment ID if not provided
|
|
72
|
+
if (!deploymentId) {
|
|
73
|
+
if (!hasTTY) {
|
|
74
|
+
consola.error('You must provide a deployment ID when running in non-interactive environment.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const deployments = await appDeploymentsService.findAll({ appId });
|
|
78
|
+
if (deployments.length === 0) {
|
|
79
|
+
consola.error('No deployments found for this app.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
83
|
+
deploymentId = await prompt('Which deployment do you want to cancel:', {
|
|
84
|
+
type: 'select',
|
|
85
|
+
options: deployments.map((deployment) => ({
|
|
86
|
+
label: `Deployment ${deployment.id}`,
|
|
87
|
+
value: deployment.id,
|
|
88
|
+
})),
|
|
89
|
+
});
|
|
90
|
+
if (!deploymentId) {
|
|
91
|
+
consola.error('You must select a deployment to cancel.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Get deployment details to retrieve the job ID
|
|
96
|
+
consola.start('Fetching deployment details...');
|
|
97
|
+
const deployment = await appDeploymentsService.findOne({
|
|
98
|
+
appId,
|
|
99
|
+
appDeploymentId: deploymentId,
|
|
100
|
+
});
|
|
101
|
+
// Cancel the job
|
|
102
|
+
consola.start('Canceling deployment...');
|
|
103
|
+
await jobsService.update({
|
|
104
|
+
jobId: deployment.jobId,
|
|
105
|
+
dto: {
|
|
106
|
+
status: 'canceled',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
consola.success('Deployment successfully canceled.');
|
|
110
|
+
},
|
|
111
|
+
});
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
|
+
import appBuildsService from '../../../services/app-builds.js';
|
|
3
|
+
import appDeploymentsService from '../../../services/app-deployments.js';
|
|
4
|
+
import appDestinationsService from '../../../services/app-destinations.js';
|
|
5
|
+
import appsService from '../../../services/apps.js';
|
|
6
|
+
import authorizationService from '../../../services/authorization-service.js';
|
|
7
|
+
import organizationsService from '../../../services/organizations.js';
|
|
8
|
+
import { unescapeAnsi } from '../../../utils/ansi.js';
|
|
9
|
+
import { prompt } from '../../../utils/prompt.js';
|
|
10
|
+
import { wait } from '../../../utils/wait.js';
|
|
11
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
12
|
+
import consola from 'consola';
|
|
13
|
+
import { hasTTY } from 'std-env';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
export default defineCommand({
|
|
16
|
+
description: 'Create a new app deployment.',
|
|
17
|
+
options: defineOptions(z.object({
|
|
18
|
+
appId: z
|
|
19
|
+
.uuid({
|
|
20
|
+
message: 'App ID must be a UUID.',
|
|
21
|
+
})
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('App ID to create the deployment for.'),
|
|
24
|
+
buildId: z
|
|
25
|
+
.uuid({
|
|
26
|
+
message: 'Build ID must be a UUID.',
|
|
27
|
+
})
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Build ID to deploy.'),
|
|
30
|
+
destination: z.string().optional().describe('The name of the destination to deploy to.'),
|
|
31
|
+
detached: z
|
|
32
|
+
.boolean()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Exit immediately after creating the deployment without waiting for completion.'),
|
|
35
|
+
})),
|
|
36
|
+
action: async (options) => {
|
|
37
|
+
let { appId, buildId, destination } = options;
|
|
38
|
+
// Check if the user is logged in
|
|
39
|
+
if (!authorizationService.hasAuthorizationToken()) {
|
|
40
|
+
consola.error('You must be logged in to run this command.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
// Prompt for app ID if not provided
|
|
44
|
+
if (!appId) {
|
|
45
|
+
if (!hasTTY) {
|
|
46
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const organizations = await organizationsService.findAll();
|
|
50
|
+
if (organizations.length === 0) {
|
|
51
|
+
consola.error('You must create an organization before creating a deployment.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
55
|
+
const organizationId = await prompt('Select the organization of the app for which you want to create a deployment.', {
|
|
56
|
+
type: 'select',
|
|
57
|
+
options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
|
|
58
|
+
});
|
|
59
|
+
if (!organizationId) {
|
|
60
|
+
consola.error('You must select the organization of an app for which you want to create a deployment.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const apps = await appsService.findAll({
|
|
64
|
+
organizationId,
|
|
65
|
+
});
|
|
66
|
+
if (apps.length === 0) {
|
|
67
|
+
consola.error('You must create an app before creating a deployment.');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
71
|
+
appId = await prompt('Which app do you want to create a deployment for:', {
|
|
72
|
+
type: 'select',
|
|
73
|
+
options: apps.map((app) => ({ label: app.name, value: app.id })),
|
|
74
|
+
});
|
|
75
|
+
if (!appId) {
|
|
76
|
+
consola.error('You must select an app to create a deployment for.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Prompt for build ID if not provided
|
|
81
|
+
if (!buildId) {
|
|
82
|
+
if (!hasTTY) {
|
|
83
|
+
consola.error('You must provide a build ID when running in non-interactive environment.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const builds = await appBuildsService.findAll({ appId });
|
|
87
|
+
if (builds.length === 0) {
|
|
88
|
+
consola.error('You must create a build before creating a deployment.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
92
|
+
buildId = await prompt('Which build do you want to deploy:', {
|
|
93
|
+
type: 'select',
|
|
94
|
+
options: builds.map((build) => ({
|
|
95
|
+
label: `Build #${build.numberAsString} (${build.platform} - ${build.type})`,
|
|
96
|
+
value: build.id,
|
|
97
|
+
})),
|
|
98
|
+
});
|
|
99
|
+
if (!buildId) {
|
|
100
|
+
consola.error('You must select a build to deploy.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Get build information to determine platform
|
|
105
|
+
const build = await appBuildsService.findOne({ appId, appBuildId: buildId });
|
|
106
|
+
// Prompt for destination if not provided
|
|
107
|
+
if (!destination) {
|
|
108
|
+
if (!hasTTY) {
|
|
109
|
+
consola.error('You must provide a destination when running in non-interactive environment.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const destinations = await appDestinationsService.findAll({
|
|
113
|
+
appId,
|
|
114
|
+
platform: build.platform,
|
|
115
|
+
});
|
|
116
|
+
if (destinations.length === 0) {
|
|
117
|
+
consola.error(`You must create a destination for the ${build.platform} platform before creating a deployment.`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
121
|
+
destination = await prompt('Which destination do you want to deploy to:', {
|
|
122
|
+
type: 'select',
|
|
123
|
+
options: destinations.map((dest) => ({
|
|
124
|
+
label: dest.name,
|
|
125
|
+
value: dest.name,
|
|
126
|
+
})),
|
|
127
|
+
});
|
|
128
|
+
if (!destination) {
|
|
129
|
+
consola.error('You must select a destination to deploy to.');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Create the deployment
|
|
134
|
+
consola.start('Creating deployment...');
|
|
135
|
+
const response = await appDeploymentsService.create({
|
|
136
|
+
appId,
|
|
137
|
+
appBuildId: buildId,
|
|
138
|
+
appDestinationName: destination,
|
|
139
|
+
});
|
|
140
|
+
consola.success('Deployment created successfully.');
|
|
141
|
+
consola.info(`Deployment ID: ${response.id}`);
|
|
142
|
+
consola.info(`Deployment URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/deployments/${response.id}`);
|
|
143
|
+
// Wait for deployment job to complete by default, unless --detached flag is set
|
|
144
|
+
const shouldWait = !options.detached;
|
|
145
|
+
if (shouldWait) {
|
|
146
|
+
let lastPrintedLogNumber = 0;
|
|
147
|
+
let isWaitingForStart = true;
|
|
148
|
+
// Poll deployment status until completion
|
|
149
|
+
while (true) {
|
|
150
|
+
try {
|
|
151
|
+
const deployment = await appDeploymentsService.findOne({
|
|
152
|
+
appId,
|
|
153
|
+
appDeploymentId: response.id,
|
|
154
|
+
relations: 'job,job.jobLogs',
|
|
155
|
+
});
|
|
156
|
+
if (!deployment.job) {
|
|
157
|
+
await wait(3000);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const jobStatus = deployment.job.status;
|
|
161
|
+
// Show spinner while queued or pending
|
|
162
|
+
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
163
|
+
if (isWaitingForStart) {
|
|
164
|
+
consola.start(`Waiting for deployment to start (status: ${jobStatus})...`);
|
|
165
|
+
}
|
|
166
|
+
await wait(3000);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Stop spinner when job moves to in_progress
|
|
170
|
+
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
171
|
+
isWaitingForStart = false;
|
|
172
|
+
consola.success('Deployment started...');
|
|
173
|
+
}
|
|
174
|
+
// Print new logs
|
|
175
|
+
if (deployment.job.jobLogs && deployment.job.jobLogs.length > 0) {
|
|
176
|
+
const newLogs = deployment.job.jobLogs
|
|
177
|
+
.filter((log) => log.number > lastPrintedLogNumber)
|
|
178
|
+
.sort((a, b) => a.number - b.number);
|
|
179
|
+
for (const log of newLogs) {
|
|
180
|
+
console.log(unescapeAnsi(log.payload));
|
|
181
|
+
lastPrintedLogNumber = log.number;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Handle terminal states
|
|
185
|
+
if (jobStatus === 'succeeded' ||
|
|
186
|
+
jobStatus === 'failed' ||
|
|
187
|
+
jobStatus === 'canceled' ||
|
|
188
|
+
jobStatus === 'rejected' ||
|
|
189
|
+
jobStatus === 'timed_out') {
|
|
190
|
+
console.log(); // New line for better readability
|
|
191
|
+
if (jobStatus === 'succeeded') {
|
|
192
|
+
consola.success('Deployment completed successfully.');
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
else if (jobStatus === 'failed') {
|
|
196
|
+
consola.error('Deployment failed.');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
else if (jobStatus === 'canceled') {
|
|
200
|
+
consola.warn('Deployment was canceled.');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
else if (jobStatus === 'rejected') {
|
|
204
|
+
consola.error('Deployment was rejected.');
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
else if (jobStatus === 'timed_out') {
|
|
208
|
+
consola.error('Deployment timed out.');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Wait before next poll (3 seconds)
|
|
213
|
+
await wait(3000);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
consola.error('Error polling deployment status:', error);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,8 @@ const config = defineConfig({
|
|
|
23
23
|
doctor: await import('./commands/doctor.js').then((mod) => mod.default),
|
|
24
24
|
'apps:create': await import('./commands/apps/create.js').then((mod) => mod.default),
|
|
25
25
|
'apps:delete': await import('./commands/apps/delete.js').then((mod) => mod.default),
|
|
26
|
+
'apps:builds:cancel': await import('./commands/apps/builds/cancel.js').then((mod) => mod.default),
|
|
27
|
+
'apps:builds:create': await import('./commands/apps/builds/create.js').then((mod) => mod.default),
|
|
26
28
|
'apps:bundles:create': await import('./commands/apps/bundles/create.js').then((mod) => mod.default),
|
|
27
29
|
'apps:bundles:delete': await import('./commands/apps/bundles/delete.js').then((mod) => mod.default),
|
|
28
30
|
'apps:bundles:update': await import('./commands/apps/bundles/update.js').then((mod) => mod.default),
|
|
@@ -31,6 +33,8 @@ const config = defineConfig({
|
|
|
31
33
|
'apps:channels:get': await import('./commands/apps/channels/get.js').then((mod) => mod.default),
|
|
32
34
|
'apps:channels:list': await import('./commands/apps/channels/list.js').then((mod) => mod.default),
|
|
33
35
|
'apps:channels:update': await import('./commands/apps/channels/update.js').then((mod) => mod.default),
|
|
36
|
+
'apps:deployments:create': await import('./commands/apps/deployments/create.js').then((mod) => mod.default),
|
|
37
|
+
'apps:deployments:cancel': await import('./commands/apps/deployments/cancel.js').then((mod) => mod.default),
|
|
34
38
|
'apps:devices:delete': await import('./commands/apps/devices/delete.js').then((mod) => mod.default),
|
|
35
39
|
'manifests:generate': await import('./commands/manifests/generate.js').then((mod) => mod.default),
|
|
36
40
|
'organizations:create': await import('./commands/organizations/create.js').then((mod) => mod.default),
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppBuildsServiceImpl {
|
|
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}/builds`, bodyData, {
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
async findAll(dto) {
|
|
18
|
+
const { appId } = dto;
|
|
19
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/builds`, {
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
return response.data;
|
|
25
|
+
}
|
|
26
|
+
async findOne(dto) {
|
|
27
|
+
const { appId, appBuildId, relations } = dto;
|
|
28
|
+
const params = {};
|
|
29
|
+
if (relations) {
|
|
30
|
+
params.relations = relations;
|
|
31
|
+
}
|
|
32
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/builds/${appBuildId}`, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
35
|
+
},
|
|
36
|
+
params,
|
|
37
|
+
});
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
async downloadArtifact(dto) {
|
|
41
|
+
const { appId, appBuildId, artifactId } = dto;
|
|
42
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/builds/${appBuildId}/artifacts/${artifactId}/download`, {
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
45
|
+
},
|
|
46
|
+
responseType: 'arraybuffer',
|
|
47
|
+
});
|
|
48
|
+
return response.data;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const appBuildsService = new AppBuildsServiceImpl(httpClient);
|
|
52
|
+
export default appBuildsService;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppCertificatesServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async findAll(dto) {
|
|
9
|
+
const { appId, platform } = dto;
|
|
10
|
+
const params = {};
|
|
11
|
+
if (platform) {
|
|
12
|
+
params.platform = platform;
|
|
13
|
+
}
|
|
14
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/certificates`, {
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
17
|
+
},
|
|
18
|
+
params,
|
|
19
|
+
});
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const appCertificatesService = new AppCertificatesServiceImpl(httpClient);
|
|
24
|
+
export default appCertificatesService;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppDeploymentsServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async create(dto) {
|
|
9
|
+
const { appId, appBuildId, appDestinationName } = dto;
|
|
10
|
+
const bodyData = {
|
|
11
|
+
appBuildId,
|
|
12
|
+
};
|
|
13
|
+
if (appDestinationName) {
|
|
14
|
+
bodyData.appDestinationName = appDestinationName;
|
|
15
|
+
}
|
|
16
|
+
const response = await this.httpClient.post(`/v1/apps/${appId}/deployments`, bodyData, {
|
|
17
|
+
headers: {
|
|
18
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
return response.data;
|
|
22
|
+
}
|
|
23
|
+
async findAll(dto) {
|
|
24
|
+
const { appId } = dto;
|
|
25
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/deployments`, {
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return response.data;
|
|
31
|
+
}
|
|
32
|
+
async findOne(dto) {
|
|
33
|
+
const { appId, appDeploymentId, relations } = dto;
|
|
34
|
+
const params = {};
|
|
35
|
+
if (relations) {
|
|
36
|
+
params.relations = relations;
|
|
37
|
+
}
|
|
38
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/deployments/${appDeploymentId}`, {
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
41
|
+
},
|
|
42
|
+
params,
|
|
43
|
+
});
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const appDeploymentsService = new AppDeploymentsServiceImpl(httpClient);
|
|
48
|
+
export default appDeploymentsService;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppDestinationsServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async findAll(dto) {
|
|
9
|
+
const { appId, platform } = dto;
|
|
10
|
+
const params = {};
|
|
11
|
+
if (platform) {
|
|
12
|
+
params.platform = platform;
|
|
13
|
+
}
|
|
14
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/destinations`, {
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
17
|
+
},
|
|
18
|
+
params,
|
|
19
|
+
});
|
|
20
|
+
return response.data;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const appDestinationsService = new AppDestinationsServiceImpl(httpClient);
|
|
24
|
+
export default appDestinationsService;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class AppEnvironmentsServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async findAll(dto) {
|
|
9
|
+
const { appId } = dto;
|
|
10
|
+
const response = await this.httpClient.get(`/v1/apps/${appId}/environments`, {
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const appEnvironmentsService = new AppEnvironmentsServiceImpl(httpClient);
|
|
19
|
+
export default appEnvironmentsService;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import authorizationService from '../services/authorization-service.js';
|
|
2
|
+
import httpClient from '../utils/http-client.js';
|
|
3
|
+
class JobsServiceImpl {
|
|
4
|
+
httpClient;
|
|
5
|
+
constructor(httpClient) {
|
|
6
|
+
this.httpClient = httpClient;
|
|
7
|
+
}
|
|
8
|
+
async update(options) {
|
|
9
|
+
const { jobId, dto } = options;
|
|
10
|
+
const response = await this.httpClient.patch(`/v1/jobs/${jobId}`, dto, {
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return response.data;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const jobsService = new JobsServiceImpl(httpClient);
|
|
19
|
+
export default jobsService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unescape ANSI color codes in a string.
|
|
3
|
+
* Converts escaped sequences like \033 or \x1b to actual escape characters.
|
|
4
|
+
*
|
|
5
|
+
* @param str - The string containing escaped ANSI codes.
|
|
6
|
+
* @returns The string with unescaped ANSI codes.
|
|
7
|
+
*/
|
|
8
|
+
export const unescapeAnsi = (str) => {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/\\033/g, '\x1b')
|
|
11
|
+
.replace(/\\x1b/g, '\x1b')
|
|
12
|
+
.replace(/\\n/g, '\n')
|
|
13
|
+
.replace(/\\r/g, '\r')
|
|
14
|
+
.replace(/\\t/g, '\t');
|
|
15
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wait for a specified number of milliseconds.
|
|
3
|
+
*
|
|
4
|
+
* @param ms - The number of milliseconds to wait.
|
|
5
|
+
* @returns A promise that resolves after the specified time.
|
|
6
|
+
*/
|
|
7
|
+
export const wait = (ms) => {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
};
|