@capawesome/cli 4.6.0-dev.0108a83.1774286472 → 4.6.0-dev.bda9e9d.1774345600
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/dist/commands/apps/builds/create.js +58 -169
- package/dist/commands/apps/liveupdates/create.js +189 -0
- package/dist/index.js +1 -0
- package/dist/types/index.js +0 -1
- package/dist/utils/build.js +74 -0
- package/dist/utils/zip.js +2 -19
- package/package.json +1 -2
- package/dist/services/app-build-sources.js +0 -112
- package/dist/types/app-build-source.js +0 -1
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
|
|
2
|
-
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 appEnvironmentsService from '../../../services/app-environments.js';
|
|
6
|
-
import {
|
|
5
|
+
import { waitForBuildCompletion } from '../../../utils/build.js';
|
|
7
6
|
import { withAuth } from '../../../utils/auth.js';
|
|
8
7
|
import { isInteractive } from '../../../utils/environment.js';
|
|
9
8
|
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
10
|
-
import { wait } from '../../../utils/wait.js';
|
|
11
|
-
import zip from '../../../utils/zip.js';
|
|
12
9
|
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
13
10
|
import consola from 'consola';
|
|
14
11
|
import fs from 'fs/promises';
|
|
@@ -47,7 +44,6 @@ export default defineCommand({
|
|
|
47
44
|
.optional()
|
|
48
45
|
.describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
|
|
49
46
|
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
50
|
-
path: z.string().optional().describe('Path to local source files to upload.'),
|
|
51
47
|
platform: z
|
|
52
48
|
.enum(['ios', 'android', 'web'], {
|
|
53
49
|
message: 'Platform must be either `ios`, `android`, or `web`.',
|
|
@@ -71,7 +67,7 @@ export default defineCommand({
|
|
|
71
67
|
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
|
|
72
68
|
}), { y: 'yes' }),
|
|
73
69
|
action: withAuth(async (options) => {
|
|
74
|
-
let { appId, platform, type, gitRef, environment, certificate, json, stack
|
|
70
|
+
let { appId, platform, type, gitRef, environment, certificate, json, stack } = options;
|
|
75
71
|
// Validate that detached flag cannot be used with artifact flags
|
|
76
72
|
if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
|
|
77
73
|
consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
|
|
@@ -87,26 +83,6 @@ export default defineCommand({
|
|
|
87
83
|
consola.error('The --channel and --destination flags cannot be used together.');
|
|
88
84
|
process.exit(1);
|
|
89
85
|
}
|
|
90
|
-
// Validate that path and gitRef cannot be used together
|
|
91
|
-
if (sourcePath && gitRef) {
|
|
92
|
-
consola.error('The --path and --git-ref flags cannot be used together.');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
// Validate path if provided
|
|
96
|
-
if (sourcePath) {
|
|
97
|
-
const resolvedPath = path.resolve(sourcePath);
|
|
98
|
-
const stat = await fs.stat(resolvedPath).catch(() => null);
|
|
99
|
-
if (!stat || !stat.isDirectory()) {
|
|
100
|
-
consola.error('The --path must point to an existing directory.');
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
const packageJsonPath = path.join(resolvedPath, 'package.json');
|
|
104
|
-
const packageJsonStat = await fs.stat(packageJsonPath).catch(() => null);
|
|
105
|
-
if (!packageJsonStat || !packageJsonStat.isFile()) {
|
|
106
|
-
consola.error('The directory specified by --path must contain a package.json file.');
|
|
107
|
-
process.exit(1);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
86
|
// Prompt for app ID if not provided
|
|
111
87
|
if (!appId) {
|
|
112
88
|
if (!isInteractive()) {
|
|
@@ -136,10 +112,10 @@ export default defineCommand({
|
|
|
136
112
|
process.exit(1);
|
|
137
113
|
}
|
|
138
114
|
}
|
|
139
|
-
// Prompt for git ref if not provided
|
|
140
|
-
if (!
|
|
115
|
+
// Prompt for git ref if not provided
|
|
116
|
+
if (!gitRef) {
|
|
141
117
|
if (!isInteractive()) {
|
|
142
|
-
consola.error('You must provide a git ref
|
|
118
|
+
consola.error('You must provide a git ref when running in non-interactive environment.');
|
|
143
119
|
process.exit(1);
|
|
144
120
|
}
|
|
145
121
|
gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
|
|
@@ -220,28 +196,9 @@ export default defineCommand({
|
|
|
220
196
|
}
|
|
221
197
|
}
|
|
222
198
|
}
|
|
223
|
-
// Upload source files if path is provided
|
|
224
|
-
let appBuildSourceId;
|
|
225
|
-
if (sourcePath) {
|
|
226
|
-
const resolvedPath = path.resolve(sourcePath);
|
|
227
|
-
consola.start('Zipping source files...');
|
|
228
|
-
const buffer = await zip.zipFolderWithGitignore(resolvedPath);
|
|
229
|
-
consola.start('Uploading source files...');
|
|
230
|
-
const appBuildSource = await appBuildSourcesService.create({
|
|
231
|
-
appId,
|
|
232
|
-
fileSizeInBytes: buffer.byteLength,
|
|
233
|
-
buffer,
|
|
234
|
-
name: 'source.zip',
|
|
235
|
-
}, (currentPart, totalParts) => {
|
|
236
|
-
consola.start(`Uploading source files (${currentPart}/${totalParts})...`);
|
|
237
|
-
});
|
|
238
|
-
appBuildSourceId = appBuildSource.id;
|
|
239
|
-
consola.success('Source files uploaded successfully.');
|
|
240
|
-
}
|
|
241
199
|
// Create the app build
|
|
242
200
|
consola.start('Creating build...');
|
|
243
201
|
const response = await appBuildsService.create({
|
|
244
|
-
appBuildSourceId,
|
|
245
202
|
appCertificateName: certificate,
|
|
246
203
|
appEnvironmentName: environment,
|
|
247
204
|
appId,
|
|
@@ -257,127 +214,59 @@ export default defineCommand({
|
|
|
257
214
|
// Wait for build job to complete by default, unless --detached flag is set
|
|
258
215
|
const shouldWait = !options.detached;
|
|
259
216
|
if (shouldWait) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
await handleArtifactDownload({
|
|
314
|
-
appId,
|
|
315
|
-
buildId: response.id,
|
|
316
|
-
buildArtifacts: build.appBuildArtifacts,
|
|
317
|
-
artifactType: 'apk',
|
|
318
|
-
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
if (options.aab && platform === 'android') {
|
|
322
|
-
await handleArtifactDownload({
|
|
323
|
-
appId,
|
|
324
|
-
buildId: response.id,
|
|
325
|
-
buildArtifacts: build.appBuildArtifacts,
|
|
326
|
-
artifactType: 'aab',
|
|
327
|
-
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
if (options.ipa && platform === 'ios') {
|
|
331
|
-
await handleArtifactDownload({
|
|
332
|
-
appId,
|
|
333
|
-
buildId: response.id,
|
|
334
|
-
buildArtifacts: build.appBuildArtifacts,
|
|
335
|
-
artifactType: 'ipa',
|
|
336
|
-
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
if (options.zip && platform === 'web') {
|
|
340
|
-
await handleArtifactDownload({
|
|
341
|
-
appId,
|
|
342
|
-
buildId: response.id,
|
|
343
|
-
buildArtifacts: build.appBuildArtifacts,
|
|
344
|
-
artifactType: 'zip',
|
|
345
|
-
filePath: typeof options.zip === 'string' ? options.zip : undefined,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
// Output JSON if json flag is set
|
|
349
|
-
if (json) {
|
|
350
|
-
console.log(JSON.stringify({
|
|
351
|
-
id: response.id,
|
|
352
|
-
numberAsString: response.numberAsString,
|
|
353
|
-
}, null, 2));
|
|
354
|
-
}
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
else if (jobStatus === 'failed') {
|
|
358
|
-
consola.error('Build failed.');
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
else if (jobStatus === 'canceled') {
|
|
362
|
-
consola.warn('Build was canceled.');
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
365
|
-
else if (jobStatus === 'rejected') {
|
|
366
|
-
consola.error('Build was rejected.');
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
else if (jobStatus === 'timed_out') {
|
|
370
|
-
consola.error('Build timed out.');
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// Wait before next poll (3 seconds)
|
|
375
|
-
await wait(3000);
|
|
376
|
-
}
|
|
377
|
-
catch (error) {
|
|
378
|
-
consola.error('Error polling build status:', error);
|
|
379
|
-
process.exit(1);
|
|
380
|
-
}
|
|
217
|
+
const build = await waitForBuildCompletion({
|
|
218
|
+
appId,
|
|
219
|
+
appBuildId: response.id,
|
|
220
|
+
relations: 'appBuildArtifacts,job,job.jobLogs',
|
|
221
|
+
});
|
|
222
|
+
consola.info(`Build ID: ${response.id}`);
|
|
223
|
+
consola.info(`Build Number: ${response.numberAsString}`);
|
|
224
|
+
consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
|
|
225
|
+
consola.success('Build completed successfully.');
|
|
226
|
+
console.log();
|
|
227
|
+
// Download artifacts if flags are set
|
|
228
|
+
if (options.apk && platform === 'android') {
|
|
229
|
+
await handleArtifactDownload({
|
|
230
|
+
appId,
|
|
231
|
+
buildId: response.id,
|
|
232
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
233
|
+
artifactType: 'apk',
|
|
234
|
+
filePath: typeof options.apk === 'string' ? options.apk : undefined,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (options.aab && platform === 'android') {
|
|
238
|
+
await handleArtifactDownload({
|
|
239
|
+
appId,
|
|
240
|
+
buildId: response.id,
|
|
241
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
242
|
+
artifactType: 'aab',
|
|
243
|
+
filePath: typeof options.aab === 'string' ? options.aab : undefined,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (options.ipa && platform === 'ios') {
|
|
247
|
+
await handleArtifactDownload({
|
|
248
|
+
appId,
|
|
249
|
+
buildId: response.id,
|
|
250
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
251
|
+
artifactType: 'ipa',
|
|
252
|
+
filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (options.zip && platform === 'web') {
|
|
256
|
+
await handleArtifactDownload({
|
|
257
|
+
appId,
|
|
258
|
+
buildId: response.id,
|
|
259
|
+
buildArtifacts: build.appBuildArtifacts,
|
|
260
|
+
artifactType: 'zip',
|
|
261
|
+
filePath: typeof options.zip === 'string' ? options.zip : undefined,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Output JSON if json flag is set
|
|
265
|
+
if (json) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
id: response.id,
|
|
268
|
+
numberAsString: response.numberAsString,
|
|
269
|
+
}, null, 2));
|
|
381
270
|
}
|
|
382
271
|
}
|
|
383
272
|
else {
|
|
@@ -0,0 +1,189 @@
|
|
|
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 appCertificatesService from '../../../services/app-certificates.js';
|
|
5
|
+
import appEnvironmentsService from '../../../services/app-environments.js';
|
|
6
|
+
import { withAuth } from '../../../utils/auth.js';
|
|
7
|
+
import { waitForBuildCompletion } from '../../../utils/build.js';
|
|
8
|
+
import { isInteractive } from '../../../utils/environment.js';
|
|
9
|
+
import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
|
|
10
|
+
import { defineCommand, defineOptions } from '@robingenz/zli';
|
|
11
|
+
import consola from 'consola';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
export default defineCommand({
|
|
14
|
+
description: 'Create a new live update.',
|
|
15
|
+
options: defineOptions(z.object({
|
|
16
|
+
appId: z
|
|
17
|
+
.uuid({
|
|
18
|
+
message: 'App ID must be a UUID.',
|
|
19
|
+
})
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('App ID to create the live update for.'),
|
|
22
|
+
certificate: z.string().optional().describe('The name of the certificate to use for the build.'),
|
|
23
|
+
channel: z.string().optional().describe('The name of the channel to deploy to.'),
|
|
24
|
+
gitRef: z.string().optional().describe('The Git reference (branch, tag, or commit SHA) to build.'),
|
|
25
|
+
environment: z.string().optional().describe('The name of the environment to use for the build.'),
|
|
26
|
+
stack: z
|
|
27
|
+
.enum(['macos-sequoia', 'macos-tahoe'], {
|
|
28
|
+
message: 'Build stack must be either `macos-sequoia` or `macos-tahoe`.',
|
|
29
|
+
})
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('The build stack to use for the build process.'),
|
|
32
|
+
androidMin: z.string().optional().describe('The minimum Android versionCode for the live update.'),
|
|
33
|
+
androidMax: z.string().optional().describe('The maximum Android versionCode for the live update.'),
|
|
34
|
+
androidEq: z.string().optional().describe('The exact Android versionCode for the live update.'),
|
|
35
|
+
iosMin: z.string().optional().describe('The minimum iOS CFBundleVersion for the live update.'),
|
|
36
|
+
iosMax: z.string().optional().describe('The maximum iOS CFBundleVersion for the live update.'),
|
|
37
|
+
iosEq: z.string().optional().describe('The exact iOS CFBundleVersion for the live update.'),
|
|
38
|
+
rolloutPercentage: z
|
|
39
|
+
.number()
|
|
40
|
+
.int()
|
|
41
|
+
.min(0)
|
|
42
|
+
.max(100)
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('The rollout percentage for the deployment (0-100). Default: 100.'),
|
|
45
|
+
json: z.boolean().optional().describe('Output in JSON format.'),
|
|
46
|
+
yes: z.boolean().optional().describe('Skip confirmation prompts.'),
|
|
47
|
+
}), { y: 'yes' }),
|
|
48
|
+
action: withAuth(async (options) => {
|
|
49
|
+
let { appId, certificate, channel, gitRef, environment, json, stack } = options;
|
|
50
|
+
// Prompt for app ID if not provided
|
|
51
|
+
if (!appId) {
|
|
52
|
+
if (!isInteractive()) {
|
|
53
|
+
consola.error('You must provide an app ID when running in non-interactive environment.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const organizationId = await promptOrganizationSelection({ allowCreate: true });
|
|
57
|
+
appId = await promptAppSelection(organizationId, { allowCreate: true });
|
|
58
|
+
}
|
|
59
|
+
// Prompt for git ref if not provided
|
|
60
|
+
if (!gitRef) {
|
|
61
|
+
if (!isInteractive()) {
|
|
62
|
+
consola.error('You must provide a git ref when running in non-interactive environment.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
|
|
66
|
+
type: 'text',
|
|
67
|
+
});
|
|
68
|
+
if (!gitRef) {
|
|
69
|
+
consola.error('You must provide a git ref.');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Prompt for channel if not provided
|
|
74
|
+
if (!channel) {
|
|
75
|
+
if (!isInteractive()) {
|
|
76
|
+
consola.error('You must provide a channel when running in non-interactive environment.');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
channel = await prompt('Enter the channel name to deploy to:', {
|
|
80
|
+
type: 'text',
|
|
81
|
+
});
|
|
82
|
+
if (!channel) {
|
|
83
|
+
consola.error('You must provide a channel.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Prompt for environment if not provided
|
|
88
|
+
if (!environment && !options.yes && isInteractive()) {
|
|
89
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
90
|
+
const selectEnvironment = await prompt('Do you want to select an environment?', {
|
|
91
|
+
type: 'confirm',
|
|
92
|
+
initial: false,
|
|
93
|
+
});
|
|
94
|
+
if (selectEnvironment) {
|
|
95
|
+
const environments = await appEnvironmentsService.findAll({ appId });
|
|
96
|
+
if (environments.length === 0) {
|
|
97
|
+
consola.warn('No environments found for this app.');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
101
|
+
environment = await prompt('Select the environment for the build:', {
|
|
102
|
+
type: 'select',
|
|
103
|
+
options: environments.map((env) => ({ label: env.name, value: env.name })),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Prompt for certificate if not provided
|
|
109
|
+
if (!certificate && !options.yes && isInteractive()) {
|
|
110
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
111
|
+
const selectCertificate = await prompt('Do you want to select a certificate?', {
|
|
112
|
+
type: 'confirm',
|
|
113
|
+
initial: false,
|
|
114
|
+
});
|
|
115
|
+
if (selectCertificate) {
|
|
116
|
+
const certificates = await appCertificatesService.findAll({ appId, platform: 'web' });
|
|
117
|
+
if (certificates.length === 0) {
|
|
118
|
+
consola.warn('No certificates found for this app.');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
|
|
122
|
+
certificate = await prompt('Select the certificate for the build:', {
|
|
123
|
+
type: 'select',
|
|
124
|
+
options: certificates.map((cert) => ({ label: cert.name, value: cert.name })),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Create the web build
|
|
130
|
+
consola.start('Creating build...');
|
|
131
|
+
const response = await appBuildsService.create({
|
|
132
|
+
appCertificateName: certificate,
|
|
133
|
+
appEnvironmentName: environment,
|
|
134
|
+
appId,
|
|
135
|
+
stack,
|
|
136
|
+
gitRef,
|
|
137
|
+
platform: 'web',
|
|
138
|
+
});
|
|
139
|
+
consola.info(`Build ID: ${response.id}`);
|
|
140
|
+
consola.info(`Build Number: ${response.numberAsString}`);
|
|
141
|
+
consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
|
|
142
|
+
consola.success('Build created successfully.');
|
|
143
|
+
// Wait for build to complete
|
|
144
|
+
await waitForBuildCompletion({ appId, appBuildId: response.id });
|
|
145
|
+
consola.success('Build completed successfully.');
|
|
146
|
+
console.log();
|
|
147
|
+
// Update build with version constraints if any are provided
|
|
148
|
+
const hasVersionConstraints = options.androidMin ||
|
|
149
|
+
options.androidMax ||
|
|
150
|
+
options.androidEq ||
|
|
151
|
+
options.iosMin ||
|
|
152
|
+
options.iosMax ||
|
|
153
|
+
options.iosEq;
|
|
154
|
+
if (hasVersionConstraints) {
|
|
155
|
+
consola.start('Updating version constraints...');
|
|
156
|
+
await appBuildsService.update({
|
|
157
|
+
appId,
|
|
158
|
+
appBuildId: response.id,
|
|
159
|
+
minAndroidAppVersionCode: options.androidMin,
|
|
160
|
+
maxAndroidAppVersionCode: options.androidMax,
|
|
161
|
+
eqAndroidAppVersionCode: options.androidEq,
|
|
162
|
+
minIosAppVersionCode: options.iosMin,
|
|
163
|
+
maxIosAppVersionCode: options.iosMax,
|
|
164
|
+
eqIosAppVersionCode: options.iosEq,
|
|
165
|
+
});
|
|
166
|
+
consola.success('Version constraints updated successfully.');
|
|
167
|
+
}
|
|
168
|
+
// Deploy to channel
|
|
169
|
+
consola.start('Creating deployment...');
|
|
170
|
+
const rolloutPercentage = (options.rolloutPercentage ?? 100) / 100;
|
|
171
|
+
const deployment = await appDeploymentsService.create({
|
|
172
|
+
appId,
|
|
173
|
+
appBuildId: response.id,
|
|
174
|
+
appChannelName: channel,
|
|
175
|
+
rolloutPercentage,
|
|
176
|
+
});
|
|
177
|
+
consola.info(`Deployment ID: ${deployment.id}`);
|
|
178
|
+
consola.info(`Deployment URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/deployments/${deployment.id}`);
|
|
179
|
+
consola.success('Deployment created successfully.');
|
|
180
|
+
// Output JSON if json flag is set
|
|
181
|
+
if (json) {
|
|
182
|
+
console.log(JSON.stringify({
|
|
183
|
+
buildId: response.id,
|
|
184
|
+
buildNumberAsString: response.numberAsString,
|
|
185
|
+
deploymentId: deployment.id,
|
|
186
|
+
}, null, 2));
|
|
187
|
+
}
|
|
188
|
+
}),
|
|
189
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const config = defineConfig({
|
|
|
60
60
|
'apps:environments:set': await import('./commands/apps/environments/set.js').then((mod) => mod.default),
|
|
61
61
|
'apps:environments:unset': await import('./commands/apps/environments/unset.js').then((mod) => mod.default),
|
|
62
62
|
'apps:liveupdates:bundle': await import('./commands/apps/liveupdates/bundle.js').then((mod) => mod.default),
|
|
63
|
+
'apps:liveupdates:create': await import('./commands/apps/liveupdates/create.js').then((mod) => mod.default),
|
|
63
64
|
'apps:liveupdates:generatesigningkey': await import('./commands/apps/liveupdates/generate-signing-key.js').then((mod) => mod.default),
|
|
64
65
|
'apps:liveupdates:rollback': await import('./commands/apps/liveupdates/rollback.js').then((mod) => mod.default),
|
|
65
66
|
'apps:liveupdates:rollout': await import('./commands/apps/liveupdates/rollout.js').then((mod) => mod.default),
|
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import appBuildsService from '../services/app-builds.js';
|
|
2
|
+
import { unescapeAnsi } from '../utils/ansi.js';
|
|
3
|
+
import { wait } from '../utils/wait.js';
|
|
4
|
+
import consola from 'consola';
|
|
5
|
+
export const waitForBuildCompletion = async (options) => {
|
|
6
|
+
const { appId, appBuildId, relations = 'job,job.jobLogs' } = options;
|
|
7
|
+
let lastPrintedLogNumber = 0;
|
|
8
|
+
let isWaitingForStart = true;
|
|
9
|
+
while (true) {
|
|
10
|
+
try {
|
|
11
|
+
const build = await appBuildsService.findOne({
|
|
12
|
+
appId,
|
|
13
|
+
appBuildId,
|
|
14
|
+
relations,
|
|
15
|
+
});
|
|
16
|
+
if (!build.job) {
|
|
17
|
+
await wait(3000);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const jobStatus = build.job.status;
|
|
21
|
+
if (jobStatus === 'queued' || jobStatus === 'pending') {
|
|
22
|
+
if (isWaitingForStart) {
|
|
23
|
+
consola.start(`Waiting for build to start (status: ${jobStatus})...`);
|
|
24
|
+
}
|
|
25
|
+
await wait(3000);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (isWaitingForStart && jobStatus === 'in_progress') {
|
|
29
|
+
isWaitingForStart = false;
|
|
30
|
+
consola.success('Build started...');
|
|
31
|
+
}
|
|
32
|
+
if (build.job.jobLogs && build.job.jobLogs.length > 0) {
|
|
33
|
+
const newLogs = build.job.jobLogs
|
|
34
|
+
.filter((log) => log.number > lastPrintedLogNumber)
|
|
35
|
+
.sort((a, b) => a.number - b.number);
|
|
36
|
+
for (const log of newLogs) {
|
|
37
|
+
console.log(unescapeAnsi(log.payload));
|
|
38
|
+
lastPrintedLogNumber = log.number;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (jobStatus === 'succeeded' ||
|
|
42
|
+
jobStatus === 'failed' ||
|
|
43
|
+
jobStatus === 'canceled' ||
|
|
44
|
+
jobStatus === 'rejected' ||
|
|
45
|
+
jobStatus === 'timed_out') {
|
|
46
|
+
console.log();
|
|
47
|
+
if (jobStatus === 'succeeded') {
|
|
48
|
+
return build;
|
|
49
|
+
}
|
|
50
|
+
else if (jobStatus === 'failed') {
|
|
51
|
+
consola.error('Build failed.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
else if (jobStatus === 'canceled') {
|
|
55
|
+
consola.warn('Build was canceled.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
else if (jobStatus === 'rejected') {
|
|
59
|
+
consola.error('Build was rejected.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
else if (jobStatus === 'timed_out') {
|
|
63
|
+
consola.error('Build timed out.');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await wait(3000);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
consola.error('Error polling build status:', error);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
package/dist/utils/zip.js
CHANGED
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import AdmZip from 'adm-zip';
|
|
2
|
-
import { globby } from 'globby';
|
|
3
|
-
import path from 'path';
|
|
4
2
|
class ZipImpl {
|
|
5
3
|
async zipFolder(sourceFolder) {
|
|
6
4
|
const zip = new AdmZip();
|
|
7
5
|
zip.addLocalFolder(sourceFolder);
|
|
8
6
|
return zip.toBuffer();
|
|
9
7
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
cwd: sourceFolder,
|
|
13
|
-
gitignore: true,
|
|
14
|
-
ignore: ['.git/**'],
|
|
15
|
-
dot: true,
|
|
16
|
-
});
|
|
17
|
-
const zip = new AdmZip();
|
|
18
|
-
for (const file of files) {
|
|
19
|
-
const filePath = path.join(sourceFolder, file);
|
|
20
|
-
const dirName = path.dirname(file);
|
|
21
|
-
zip.addLocalFile(filePath, dirName === '.' ? '' : dirName);
|
|
22
|
-
}
|
|
23
|
-
return zip.toBuffer();
|
|
24
|
-
}
|
|
25
|
-
isZipped(filePath) {
|
|
26
|
-
return filePath.endsWith('.zip');
|
|
8
|
+
isZipped(path) {
|
|
9
|
+
return path.endsWith('.zip');
|
|
27
10
|
}
|
|
28
11
|
}
|
|
29
12
|
const zip = new ZipImpl();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/cli",
|
|
3
|
-
"version": "4.6.0-dev.
|
|
3
|
+
"version": "4.6.0-dev.bda9e9d.1774345600",
|
|
4
4
|
"description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"c12": "3.3.3",
|
|
62
62
|
"consola": "3.3.0",
|
|
63
63
|
"form-data": "4.0.4",
|
|
64
|
-
"globby": "16.1.1",
|
|
65
64
|
"http-proxy-agent": "7.0.2",
|
|
66
65
|
"https-proxy-agent": "7.0.6",
|
|
67
66
|
"mime": "4.0.7",
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { MAX_CONCURRENT_PART_UPLOADS } from '../config/index.js';
|
|
2
|
-
import authorizationService from '../services/authorization-service.js';
|
|
3
|
-
import httpClient from '../utils/http-client.js';
|
|
4
|
-
import FormData from 'form-data';
|
|
5
|
-
class AppBuildSourcesServiceImpl {
|
|
6
|
-
httpClient;
|
|
7
|
-
constructor(httpClient) {
|
|
8
|
-
this.httpClient = httpClient;
|
|
9
|
-
}
|
|
10
|
-
async create(dto, onProgress) {
|
|
11
|
-
const response = await this.httpClient.post(`/v1/apps/${dto.appId}/build-sources`, { fileSizeInBytes: dto.fileSizeInBytes }, {
|
|
12
|
-
headers: {
|
|
13
|
-
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
await this.upload({
|
|
17
|
-
appBuildSourceId: response.data.id,
|
|
18
|
-
appId: dto.appId,
|
|
19
|
-
buffer: dto.buffer,
|
|
20
|
-
name: dto.name,
|
|
21
|
-
}, onProgress);
|
|
22
|
-
return response.data;
|
|
23
|
-
}
|
|
24
|
-
async completeUpload(dto) {
|
|
25
|
-
return this.httpClient
|
|
26
|
-
.post(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-complete&uploadId=${dto.uploadId}`, {
|
|
27
|
-
parts: dto.parts,
|
|
28
|
-
}, {
|
|
29
|
-
headers: {
|
|
30
|
-
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
.then((response) => response.data);
|
|
34
|
-
}
|
|
35
|
-
async createUpload(dto) {
|
|
36
|
-
const response = await this.httpClient.post(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-create`, {}, {
|
|
37
|
-
headers: {
|
|
38
|
-
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
return response.data;
|
|
42
|
-
}
|
|
43
|
-
async createUploadPart(dto) {
|
|
44
|
-
const formData = new FormData();
|
|
45
|
-
formData.append('blob', dto.buffer, { filename: dto.name });
|
|
46
|
-
formData.append('partNumber', dto.partNumber.toString());
|
|
47
|
-
return this.httpClient
|
|
48
|
-
.put(`/v1/apps/${dto.appId}/build-sources/${dto.appBuildSourceId}/upload?action=mpu-uploadpart&uploadId=${dto.uploadId}`, formData, {
|
|
49
|
-
headers: {
|
|
50
|
-
Authorization: `Bearer ${authorizationService.getCurrentAuthorizationToken()}`,
|
|
51
|
-
...formData.getHeaders(),
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
.then((response) => response.data);
|
|
55
|
-
}
|
|
56
|
-
async createUploadParts(dto, onProgress) {
|
|
57
|
-
const uploadedParts = [];
|
|
58
|
-
const partSize = 10 * 1024 * 1024; // 10 MB
|
|
59
|
-
const totalParts = Math.ceil(dto.buffer.byteLength / partSize);
|
|
60
|
-
let partNumber = 0;
|
|
61
|
-
const uploadNextPart = async () => {
|
|
62
|
-
if (partNumber >= totalParts) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
partNumber++;
|
|
66
|
-
onProgress?.(partNumber, totalParts);
|
|
67
|
-
const start = (partNumber - 1) * partSize;
|
|
68
|
-
const end = Math.min(start + partSize, dto.buffer.byteLength);
|
|
69
|
-
const partBuffer = dto.buffer.subarray(start, end);
|
|
70
|
-
const uploadedPart = await this.createUploadPart({
|
|
71
|
-
appBuildSourceId: dto.appBuildSourceId,
|
|
72
|
-
appId: dto.appId,
|
|
73
|
-
buffer: partBuffer,
|
|
74
|
-
name: dto.name,
|
|
75
|
-
partNumber,
|
|
76
|
-
uploadId: dto.uploadId,
|
|
77
|
-
});
|
|
78
|
-
uploadedParts.push(uploadedPart);
|
|
79
|
-
await uploadNextPart();
|
|
80
|
-
};
|
|
81
|
-
const uploadPartPromises = Array.from({ length: MAX_CONCURRENT_PART_UPLOADS });
|
|
82
|
-
for (let i = 0; i < MAX_CONCURRENT_PART_UPLOADS; i++) {
|
|
83
|
-
uploadPartPromises[i] = uploadNextPart();
|
|
84
|
-
}
|
|
85
|
-
await Promise.all(uploadPartPromises);
|
|
86
|
-
return uploadedParts.sort((a, b) => a.partNumber - b.partNumber);
|
|
87
|
-
}
|
|
88
|
-
async upload(dto, onProgress) {
|
|
89
|
-
// 1. Create a multipart upload
|
|
90
|
-
const { uploadId } = await this.createUpload({
|
|
91
|
-
appBuildSourceId: dto.appBuildSourceId,
|
|
92
|
-
appId: dto.appId,
|
|
93
|
-
});
|
|
94
|
-
// 2. Upload the file in parts
|
|
95
|
-
const parts = await this.createUploadParts({
|
|
96
|
-
appBuildSourceId: dto.appBuildSourceId,
|
|
97
|
-
appId: dto.appId,
|
|
98
|
-
buffer: dto.buffer,
|
|
99
|
-
name: dto.name,
|
|
100
|
-
uploadId,
|
|
101
|
-
}, onProgress);
|
|
102
|
-
// 3. Complete the upload
|
|
103
|
-
await this.completeUpload({
|
|
104
|
-
appBuildSourceId: dto.appBuildSourceId,
|
|
105
|
-
appId: dto.appId,
|
|
106
|
-
parts,
|
|
107
|
-
uploadId,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const appBuildSourcesService = new AppBuildSourcesServiceImpl(httpClient);
|
|
112
|
-
export default appBuildSourcesService;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|