@capawesome/cli 3.11.0 → 4.0.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/commands/apps/builds/cancel.js +1 -1
  3. package/dist/commands/apps/builds/create.js +58 -50
  4. package/dist/commands/apps/builds/download.js +27 -3
  5. package/dist/commands/apps/bundles/create.js +5 -449
  6. package/dist/commands/apps/bundles/delete.js +3 -68
  7. package/dist/commands/apps/bundles/update.js +3 -66
  8. package/dist/commands/apps/channels/create.js +5 -8
  9. package/dist/commands/apps/channels/create.test.js +6 -9
  10. package/dist/commands/apps/channels/delete.js +3 -2
  11. package/dist/commands/apps/channels/get.js +2 -12
  12. package/dist/commands/apps/channels/get.test.js +1 -2
  13. package/dist/commands/apps/channels/list.js +2 -10
  14. package/dist/commands/apps/channels/list.test.js +2 -3
  15. package/dist/commands/apps/channels/pause.js +85 -0
  16. package/dist/commands/apps/channels/resume.js +85 -0
  17. package/dist/commands/apps/channels/update.js +4 -7
  18. package/dist/commands/apps/channels/update.test.js +2 -4
  19. package/dist/commands/apps/create.js +1 -1
  20. package/dist/commands/apps/delete.js +3 -2
  21. package/dist/commands/apps/deployments/cancel.js +1 -1
  22. package/dist/commands/apps/deployments/create.js +82 -31
  23. package/dist/commands/apps/devices/delete.js +3 -2
  24. package/dist/commands/apps/environments/create.js +1 -1
  25. package/dist/commands/apps/environments/delete.js +3 -2
  26. package/dist/commands/apps/liveupdates/bundle.js +117 -0
  27. package/dist/commands/apps/liveupdates/generate-manifest.js +39 -0
  28. package/dist/commands/{manifests/generate.test.js → apps/liveupdates/generate-manifest.test.js} +6 -6
  29. package/dist/commands/apps/liveupdates/register.js +291 -0
  30. package/dist/commands/apps/{bundles/create.test.js → liveupdates/register.test.js} +123 -111
  31. package/dist/commands/apps/liveupdates/rollback.js +171 -0
  32. package/dist/commands/apps/liveupdates/rollout.js +147 -0
  33. package/dist/commands/apps/liveupdates/upload.js +420 -0
  34. package/dist/commands/apps/liveupdates/upload.test.js +325 -0
  35. package/dist/commands/manifests/generate.js +2 -27
  36. package/dist/commands/organizations/create.js +1 -1
  37. package/dist/index.js +8 -0
  38. package/dist/services/app-builds.js +9 -2
  39. package/dist/services/app-channels.js +19 -0
  40. package/dist/services/app-deployments.js +24 -14
  41. package/dist/services/config.js +2 -0
  42. package/dist/utils/app-environments.js +2 -1
  43. package/dist/utils/time-format.js +26 -0
  44. package/package.json +3 -3
  45. package/dist/commands/apps/bundles/delete.test.js +0 -142
  46. package/dist/commands/apps/bundles/update.test.js +0 -144
  47. package/dist/utils/capacitor-config.js +0 -96
  48. package/dist/utils/package-json.js +0 -58
@@ -1,29 +1,6 @@
1
- import { MAX_CONCURRENT_UPLOADS } from '../../../config/index.js';
2
- import appBundleFilesService from '../../../services/app-bundle-files.js';
3
- import appBundlesService from '../../../services/app-bundles.js';
4
- import appsService from '../../../services/apps.js';
5
- import authorizationService from '../../../services/authorization-service.js';
6
- import organizationsService from '../../../services/organizations.js';
7
- import { createBufferFromPath, createBufferFromReadStream, createBufferFromString, isPrivateKeyContent, } from '../../../utils/buffer.js';
8
- import { findCapacitorConfigPath, getLiveUpdatePluginAppIdFromConfig, getLiveUpdatePluginPublicKeyFromConfig, getWebDirFromConfig, } from '../../../utils/capacitor-config.js';
9
- import { fileExistsAtPath, getFilesInDirectoryAndSubdirectories, isDirectory } from '../../../utils/file.js';
10
- import { createHash } from '../../../utils/hash.js';
11
- import { generateManifestJson } from '../../../utils/manifest.js';
12
- import { findPackageJsonPath, getBuildScript } from '../../../utils/package-json.js';
13
- import { formatPrivateKey } from '../../../utils/private-key.js';
14
- import { prompt } from '../../../utils/prompt.js';
15
- import { createSignature } from '../../../utils/signature.js';
16
- import zip from '../../../utils/zip.js';
17
1
  import { defineCommand, defineOptions } from '@robingenz/zli';
18
- import { exec } from 'child_process';
19
2
  import consola from 'consola';
20
- import { createReadStream } from 'fs';
21
- import pathModule from 'path';
22
- import { promisify } from 'util';
23
3
  import { z } from 'zod';
24
- import { isInteractive } from '../../../utils/environment.js';
25
- // Promisified exec for running build scripts
26
- const execAsync = promisify(exec);
27
4
  export default defineCommand({
28
5
  description: 'Create a new app bundle.',
29
6
  options: defineOptions(z.object({
@@ -104,431 +81,10 @@ export default defineCommand({
104
81
  url: z.string().optional().describe('The url to the self-hosted bundle file.'),
105
82
  })),
106
83
  action: async (options, args) => {
107
- let { androidEq, androidMax, androidMin, appId, artifactType, channel, commitMessage, commitRef, commitSha, customProperty, expiresInDays, iosEq, iosMax, iosMin, path, privateKey, rollout, url, } = options;
108
- // Check if the user is logged in
109
- if (!authorizationService.hasAuthorizationToken()) {
110
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
111
- process.exit(1);
112
- }
113
- // Calculate the expiration date
114
- let expiresAt;
115
- if (expiresInDays) {
116
- const expiresAtDate = new Date();
117
- expiresAtDate.setDate(expiresAtDate.getDate() + expiresInDays);
118
- expiresAt = expiresAtDate.toISOString();
119
- }
120
- // Try to auto-detect webDir from Capacitor configuration
121
- const capacitorConfigPath = await findCapacitorConfigPath();
122
- if (!capacitorConfigPath) {
123
- consola.warn('No Capacitor configuration found to auto-detect web asset directory or app ID.');
124
- }
125
- // Check that either a path or a url is provided
126
- if (!path && !url) {
127
- // Try to auto-detect webDir from Capacitor configuration
128
- if (capacitorConfigPath) {
129
- const webDirPath = await getWebDirFromConfig(capacitorConfigPath);
130
- if (webDirPath) {
131
- const relativeWebDirPath = pathModule.relative(process.cwd(), webDirPath);
132
- consola.success(`Auto-detected web asset directory "${relativeWebDirPath}" from Capacitor configuration.`);
133
- path = webDirPath;
134
- }
135
- else {
136
- consola.warn('No web asset directory found in Capacitor configuration (`webDir`).');
137
- }
138
- }
139
- // If still no path, prompt the user
140
- if (!path) {
141
- if (!isInteractive()) {
142
- consola.error('You must provide either a path or a url when running in non-interactive environment.');
143
- process.exit(1);
144
- }
145
- else {
146
- path = await prompt('Enter the path to the app bundle:', {
147
- type: 'text',
148
- });
149
- if (!path) {
150
- consola.error('You must provide a path to the app bundle.');
151
- process.exit(1);
152
- }
153
- }
154
- }
155
- }
156
- // Check for build scripts if a path is provided or detected
157
- if (path && !url) {
158
- const packageJsonPath = await findPackageJsonPath();
159
- if (!packageJsonPath) {
160
- consola.warn('No package.json file found.');
161
- }
162
- else {
163
- const buildScript = await getBuildScript(packageJsonPath);
164
- if (!buildScript) {
165
- consola.warn('No build script (`capawesome:build` or `build`) found in package.json.');
166
- }
167
- else if (isInteractive()) {
168
- const shouldBuild = await prompt('Do you want to run the build script before creating the bundle to ensure the latest assets are included?', {
169
- type: 'confirm',
170
- initial: true,
171
- });
172
- if (shouldBuild) {
173
- try {
174
- consola.start(`Running \`${buildScript.name}\` script...`);
175
- const { stdout, stderr } = await execAsync(`npm run ${buildScript.name}`);
176
- if (stdout) {
177
- console.log(stdout);
178
- }
179
- if (stderr) {
180
- console.error(stderr);
181
- }
182
- consola.success('Build completed successfully.');
183
- }
184
- catch (error) {
185
- consola.error('Build failed.');
186
- if (error.stdout) {
187
- console.log(error.stdout);
188
- }
189
- if (error.stderr) {
190
- console.error(error.stderr);
191
- }
192
- process.exit(1);
193
- }
194
- }
195
- }
196
- }
197
- }
198
- // Validate the provided path
199
- if (path) {
200
- // Check if the path exists when a path is provided
201
- const pathExists = await fileExistsAtPath(path);
202
- if (!pathExists) {
203
- consola.error(`The path does not exist.`);
204
- process.exit(1);
205
- }
206
- // Check if the directory contains an index.html file
207
- const pathIsDirectory = await isDirectory(path);
208
- if (pathIsDirectory) {
209
- const files = await getFilesInDirectoryAndSubdirectories(path);
210
- const indexHtml = files.find((file) => file.href === 'index.html');
211
- if (!indexHtml) {
212
- consola.error('The directory must contain an `index.html` file.');
213
- process.exit(1);
214
- }
215
- }
216
- else if (zip.isZipped(path)) {
217
- // No-op
218
- }
219
- else {
220
- consola.error('The path must be either a folder or a zip file.');
221
- process.exit(1);
222
- }
223
- }
224
- // Check that the path is a directory when creating a bundle with an artifact type
225
- if (artifactType === 'manifest' && path) {
226
- const pathIsDirectory = await isDirectory(path);
227
- if (!pathIsDirectory) {
228
- consola.error('The path must be a folder when creating a bundle with an artifact type of `manifest`.');
229
- process.exit(1);
230
- }
231
- }
232
- // Check that a URL is not provided when creating a bundle with an artifact type of manifest
233
- if (artifactType === 'manifest' && url) {
234
- consola.error('It is not yet possible to provide a URL when creating a bundle with an artifact type of `manifest`.');
235
- process.exit(1);
236
- }
237
- // Track if we found a Capacitor configuration but no app ID (for showing setup hint later)
238
- if (!appId) {
239
- // Try to auto-detect appId from Capacitor configuration
240
- if (capacitorConfigPath) {
241
- const configAppId = await getLiveUpdatePluginAppIdFromConfig(capacitorConfigPath);
242
- if (configAppId) {
243
- consola.success(`Auto-detected Capawesome Cloud app ID "${configAppId}" from Capacitor configuration.`);
244
- appId = configAppId;
245
- }
246
- else {
247
- consola.warn('No Capawesome Cloud app ID found in Capacitor configuration (`plugins.LiveUpdate.appId`).');
248
- }
249
- }
250
- // If still no appId, prompt the user
251
- if (!appId) {
252
- if (!isInteractive()) {
253
- consola.error('You must provide an app ID when running in non-interactive environment.');
254
- process.exit(1);
255
- }
256
- const organizations = await organizationsService.findAll();
257
- if (organizations.length === 0) {
258
- consola.error('You must create an organization before creating a bundle.');
259
- process.exit(1);
260
- }
261
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
262
- const organizationId = await prompt('Select the organization of the app for which you want to create a bundle.', {
263
- type: 'select',
264
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
265
- });
266
- if (!organizationId) {
267
- consola.error('You must select the organization of an app for which you want to create a bundle.');
268
- process.exit(1);
269
- }
270
- const apps = await appsService.findAll({
271
- organizationId,
272
- });
273
- if (apps.length === 0) {
274
- consola.error('You must create an app before creating a bundle.');
275
- process.exit(1);
276
- }
277
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
278
- appId = await prompt('Which app do you want to deploy to:', {
279
- type: 'select',
280
- options: apps.map((app) => ({ label: app.name, value: app.id })),
281
- });
282
- if (!appId) {
283
- consola.error('You must select an app to deploy to.');
284
- process.exit(1);
285
- }
286
- }
287
- }
288
- if (!channel && isInteractive()) {
289
- const shouldDeployToChannel = await prompt('Do you want to deploy to a specific channel?', {
290
- type: 'confirm',
291
- initial: false,
292
- });
293
- if (shouldDeployToChannel) {
294
- channel = await prompt('Enter the channel name:', {
295
- type: 'text',
296
- });
297
- if (!channel) {
298
- consola.error('The channel name must be at least one character long.');
299
- process.exit(1);
300
- }
301
- }
302
- }
303
- // Check if public key is configured but no private key was provided
304
- if (!privateKey && capacitorConfigPath) {
305
- const publicKey = await getLiveUpdatePluginPublicKeyFromConfig(capacitorConfigPath);
306
- if (publicKey) {
307
- consola.warn('A public key for verifying the integrity of the bundles is configured in your Capacitor configuration, but no private key has been provided for signing this bundle.');
308
- }
309
- }
310
- // Create the private key buffer
311
- let privateKeyBuffer;
312
- if (privateKey) {
313
- if (isPrivateKeyContent(privateKey)) {
314
- // Handle plain text private key content
315
- const formattedPrivateKey = formatPrivateKey(privateKey);
316
- privateKeyBuffer = createBufferFromString(formattedPrivateKey);
317
- }
318
- else if (privateKey.endsWith('.pem')) {
319
- // Handle file path
320
- const fileExists = await fileExistsAtPath(privateKey);
321
- if (fileExists) {
322
- const keyBuffer = await createBufferFromPath(privateKey);
323
- const keyContent = keyBuffer.toString('utf8');
324
- const formattedPrivateKey = formatPrivateKey(keyContent);
325
- privateKeyBuffer = createBufferFromString(formattedPrivateKey);
326
- }
327
- else {
328
- consola.error('Private key file not found.');
329
- process.exit(1);
330
- }
331
- }
332
- else {
333
- consola.error('Private key must be either a path to a .pem file or the private key content as plain text.');
334
- process.exit(1);
335
- }
336
- }
337
- // Get app details for confirmation
338
- const app = await appsService.findOne({ appId });
339
- const appName = app.name;
340
- // Final confirmation before creating bundle
341
- if (path && isInteractive()) {
342
- const relativePath = pathModule.relative(process.cwd(), path);
343
- const confirmed = await prompt(`Are you sure you want to create a bundle from path "${relativePath}" for app "${appName}" (${appId})?`, {
344
- type: 'confirm',
345
- });
346
- if (!confirmed) {
347
- consola.info('Bundle creation cancelled.');
348
- process.exit(0);
349
- }
350
- }
351
- // Create the app bundle
352
- let appBundleId;
353
- try {
354
- consola.start('Creating bundle...');
355
- let checksum;
356
- let signature;
357
- if (path && url) {
358
- // Create the file buffer
359
- if (!zip.isZipped(path)) {
360
- consola.error('The path must be a zip file when providing a URL.');
361
- process.exit(1);
362
- }
363
- const fileBuffer = await createBufferFromPath(path);
364
- // Generate checksum
365
- checksum = await createHash(fileBuffer);
366
- // Sign the bundle
367
- if (privateKeyBuffer) {
368
- signature = await createSignature(privateKeyBuffer, fileBuffer);
369
- }
370
- }
371
- const response = await appBundlesService.create({
372
- appId,
373
- artifactType,
374
- channelName: channel,
375
- checksum,
376
- eqAndroidAppVersionCode: androidEq,
377
- eqIosAppVersionCode: iosEq,
378
- gitCommitMessage: commitMessage,
379
- gitCommitRef: commitRef,
380
- gitCommitSha: commitSha,
381
- customProperties: parseCustomProperties(customProperty),
382
- expiresAt,
383
- url,
384
- maxAndroidAppVersionCode: androidMax,
385
- maxIosAppVersionCode: iosMax,
386
- minAndroidAppVersionCode: androidMin,
387
- minIosAppVersionCode: iosMin,
388
- rolloutPercentage: rollout,
389
- signature,
390
- });
391
- appBundleId = response.id;
392
- if (path) {
393
- if (url) {
394
- // Important: Do NOT upload files if the URL is provided.
395
- // The user wants to self-host the bundle. The path is only needed for code signing.
396
- }
397
- else {
398
- let appBundleFileId;
399
- // Upload the app bundle files
400
- if (artifactType === 'manifest') {
401
- await uploadFiles({ appId, appBundleId: response.id, path, privateKeyBuffer });
402
- }
403
- else {
404
- const result = await uploadZip({ appId, appBundleId: response.id, path, privateKeyBuffer });
405
- appBundleFileId = result.appBundleFileId;
406
- }
407
- // Update the app bundle
408
- consola.start('Updating bundle...');
409
- await appBundlesService.update({
410
- appBundleFileId,
411
- appId,
412
- artifactStatus: 'ready',
413
- appBundleId: response.id,
414
- });
415
- }
416
- }
417
- consola.success('Bundle successfully created.');
418
- consola.info(`Bundle ID: ${response.id}`);
419
- }
420
- catch (error) {
421
- if (appBundleId) {
422
- await appBundlesService.delete({ appId, appBundleId }).catch(() => {
423
- // No-op
424
- });
425
- }
426
- throw error;
427
- }
84
+ consola.warn('The `apps:bundles:create` command has been deprecated.');
85
+ consola.info('Please use one of the following commands instead:');
86
+ consola.info(' - `apps:liveupdates:upload` to upload a bundle to Capawesome Cloud');
87
+ consola.info(' - `apps:liveupdates:register` to register a self-hosted bundle URL');
88
+ process.exit(0);
428
89
  },
429
90
  });
430
- const uploadFile = async (options) => {
431
- let { appId, appBundleId, buffer, href, mimeType, name, privateKeyBuffer, retryOnFailure } = options;
432
- try {
433
- // Generate checksum
434
- const hash = await createHash(buffer);
435
- // Sign the bundle
436
- let signature;
437
- if (privateKeyBuffer) {
438
- signature = await createSignature(privateKeyBuffer, buffer);
439
- }
440
- // Create the multipart upload
441
- return await appBundleFilesService.create({
442
- appId,
443
- appBundleId,
444
- buffer,
445
- checksum: hash,
446
- href,
447
- mimeType,
448
- name,
449
- signature,
450
- });
451
- }
452
- catch (error) {
453
- if (retryOnFailure) {
454
- return uploadFile({
455
- ...options,
456
- retryOnFailure: false,
457
- });
458
- }
459
- throw error;
460
- }
461
- };
462
- const uploadFiles = async (options) => {
463
- let { appId, appBundleId, path, privateKeyBuffer } = options;
464
- // Generate the manifest file
465
- await generateManifestJson(path);
466
- // Get all files in the directory
467
- const files = await getFilesInDirectoryAndSubdirectories(path);
468
- // Iterate over each file
469
- let fileIndex = 0;
470
- const uploadNextFile = async () => {
471
- if (fileIndex >= files.length) {
472
- return;
473
- }
474
- const file = files[fileIndex];
475
- fileIndex++;
476
- consola.start(`Uploading file (${fileIndex}/${files.length})...`);
477
- const buffer = await createBufferFromPath(file.path);
478
- await uploadFile({
479
- appId,
480
- appBundleId: appBundleId,
481
- buffer,
482
- href: file.href,
483
- mimeType: file.mimeType,
484
- name: file.name,
485
- privateKeyBuffer: privateKeyBuffer,
486
- retryOnFailure: true,
487
- });
488
- await uploadNextFile();
489
- };
490
- const uploadPromises = Array.from({ length: MAX_CONCURRENT_UPLOADS });
491
- for (let i = 0; i < MAX_CONCURRENT_UPLOADS; i++) {
492
- uploadPromises[i] = uploadNextFile();
493
- }
494
- await Promise.all(uploadPromises);
495
- };
496
- const uploadZip = async (options) => {
497
- let { appId, appBundleId, path, privateKeyBuffer } = options;
498
- // Read the zip file
499
- let fileBuffer;
500
- if (zip.isZipped(path)) {
501
- const readStream = createReadStream(path);
502
- fileBuffer = await createBufferFromReadStream(readStream);
503
- }
504
- else {
505
- consola.start('Zipping folder...');
506
- fileBuffer = await zip.zipFolder(path);
507
- }
508
- // Upload the zip file
509
- consola.start('Uploading file...');
510
- const result = await uploadFile({
511
- appId,
512
- appBundleId: appBundleId,
513
- buffer: fileBuffer,
514
- mimeType: 'application/zip',
515
- name: 'bundle.zip',
516
- privateKeyBuffer: privateKeyBuffer,
517
- });
518
- return {
519
- appBundleFileId: result.id,
520
- };
521
- };
522
- const parseCustomProperties = (customProperty) => {
523
- let customProperties;
524
- if (customProperty) {
525
- customProperties = {};
526
- for (const property of customProperty) {
527
- const [key, value] = property.split('=');
528
- if (key && value) {
529
- customProperties[key] = value;
530
- }
531
- }
532
- }
533
- return customProperties;
534
- };
@@ -1,11 +1,5 @@
1
- import appBundlesService from '../../../services/app-bundles.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
5
- import { prompt } from '../../../utils/prompt.js';
6
1
  import { defineCommand, defineOptions } from '@robingenz/zli';
7
2
  import consola from 'consola';
8
- import { isInteractive } from '../../../utils/environment.js';
9
3
  import { z } from 'zod';
10
4
  export default defineCommand({
11
5
  description: 'Delete an app bundle.',
@@ -14,67 +8,8 @@ export default defineCommand({
14
8
  bundleId: z.string().optional().describe('ID of the bundle.'),
15
9
  })),
16
10
  action: async (options, args) => {
17
- let { appId, bundleId } = options;
18
- if (!authorizationService.hasAuthorizationToken()) {
19
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
20
- process.exit(1);
21
- }
22
- // Prompt for missing arguments
23
- if (!appId) {
24
- if (!isInteractive()) {
25
- consola.error('You must provide an app ID when running in non-interactive environment.');
26
- process.exit(1);
27
- }
28
- const organizations = await organizationsService.findAll();
29
- if (organizations.length === 0) {
30
- consola.error('You must create an organization before deleting a bundle.');
31
- process.exit(1);
32
- }
33
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
34
- const organizationId = await prompt('Select the organization of the app from which you want to delete a bundle.', {
35
- type: 'select',
36
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
37
- });
38
- if (!organizationId) {
39
- consola.error('You must select the organization of an app from which you want to delete a bundle.');
40
- process.exit(1);
41
- }
42
- const apps = await appsService.findAll({
43
- organizationId,
44
- });
45
- if (!apps.length) {
46
- consola.error('You must create an app before deleting a bundle.');
47
- process.exit(1);
48
- }
49
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
50
- appId = await prompt('Which app do you want to delete the bundle from?', {
51
- type: 'select',
52
- options: apps.map((app) => ({ label: app.name, value: app.id })),
53
- });
54
- }
55
- if (!bundleId) {
56
- if (!isInteractive()) {
57
- consola.error('You must provide the bundle ID when running in non-interactive environment.');
58
- process.exit(1);
59
- }
60
- bundleId = await prompt('Enter the bundle ID:', {
61
- type: 'text',
62
- });
63
- }
64
- // Confirm deletion
65
- if (isInteractive()) {
66
- const confirmed = await prompt('Are you sure you want to delete this bundle?', {
67
- type: 'confirm',
68
- });
69
- if (!confirmed) {
70
- return;
71
- }
72
- }
73
- // Delete bundle
74
- await appBundlesService.delete({
75
- appId,
76
- appBundleId: bundleId,
77
- });
78
- consola.success('Bundle deleted successfully.');
11
+ consola.warn('The `apps:bundles:delete` command has been deprecated and will be removed in future versions.');
12
+ consola.info('Please refer to the official documentation for alternative approaches.');
13
+ process.exit(0);
79
14
  },
80
15
  });
@@ -1,11 +1,5 @@
1
- import appBundlesService from '../../../services/app-bundles.js';
2
- import appsService from '../../../services/apps.js';
3
- import authorizationService from '../../../services/authorization-service.js';
4
- import organizationsService from '../../../services/organizations.js';
5
- import { prompt } from '../../../utils/prompt.js';
6
1
  import { defineCommand, defineOptions } from '@robingenz/zli';
7
2
  import consola from 'consola';
8
- import { isInteractive } from '../../../utils/environment.js';
9
3
  import { z } from 'zod';
10
4
  export default defineCommand({
11
5
  description: 'Update an app bundle.',
@@ -46,65 +40,8 @@ export default defineCommand({
46
40
  .describe('The exact iOS bundle version (`CFBundleVersion`) that the bundle should not support.'),
47
41
  })),
48
42
  action: async (options, args) => {
49
- let { androidMax, androidMin, androidEq, appId, bundleId, rollout, iosMax, iosMin, iosEq } = options;
50
- if (!authorizationService.hasAuthorizationToken()) {
51
- consola.error('You must be logged in to run this command. Please run the `login` command first.');
52
- process.exit(1);
53
- }
54
- // Prompt for missing arguments
55
- if (!appId) {
56
- if (!isInteractive()) {
57
- consola.error('You must provide an app ID when running in non-interactive environment.');
58
- process.exit(1);
59
- }
60
- const organizations = await organizationsService.findAll();
61
- if (organizations.length === 0) {
62
- consola.error('You must create an organization before updating a bundle.');
63
- process.exit(1);
64
- }
65
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
66
- const organizationId = await prompt('Select the organization of the app for which you want to update a bundle.', {
67
- type: 'select',
68
- options: organizations.map((organization) => ({ label: organization.name, value: organization.id })),
69
- });
70
- if (!organizationId) {
71
- consola.error('You must select the organization of an app for which you want to update a bundle.');
72
- process.exit(1);
73
- }
74
- const apps = await appsService.findAll({
75
- organizationId,
76
- });
77
- if (!apps.length) {
78
- consola.error('You must create an app before updating a bundle.');
79
- process.exit(1);
80
- }
81
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
82
- appId = await prompt('Which app do you want to update the bundle for?', {
83
- type: 'select',
84
- options: apps.map((app) => ({ label: app.name, value: app.id })),
85
- });
86
- }
87
- if (!bundleId) {
88
- if (!isInteractive()) {
89
- consola.error('You must provide the bundle ID when running in non-interactive environment.');
90
- process.exit(1);
91
- }
92
- bundleId = await prompt('Enter the bundle ID:', {
93
- type: 'text',
94
- });
95
- }
96
- // Update bundle
97
- await appBundlesService.update({
98
- appId,
99
- appBundleId: bundleId,
100
- maxAndroidAppVersionCode: androidMax,
101
- maxIosAppVersionCode: iosMax,
102
- minAndroidAppVersionCode: androidMin,
103
- minIosAppVersionCode: iosMin,
104
- eqAndroidAppVersionCode: androidEq,
105
- eqIosAppVersionCode: iosEq,
106
- rolloutPercentage: rollout,
107
- });
108
- consola.success('Bundle updated successfully.');
43
+ consola.warn('The `apps:bundles:update` command has been deprecated and will be removed in future versions.');
44
+ consola.info('Please refer to the official documentation for alternative approaches.');
45
+ process.exit(0);
109
46
  },
110
47
  });
@@ -2,20 +2,16 @@ import appChannelsService from '../../../services/app-channels.js';
2
2
  import appsService from '../../../services/apps.js';
3
3
  import authorizationService from '../../../services/authorization-service.js';
4
4
  import organizationsService from '../../../services/organizations.js';
5
+ import { isInteractive } from '../../../utils/environment.js';
5
6
  import { getMessageFromUnknownError } from '../../../utils/error.js';
6
7
  import { prompt } from '../../../utils/prompt.js';
7
8
  import { defineCommand, defineOptions } from '@robingenz/zli';
8
9
  import consola from 'consola';
9
- import { isInteractive } from '../../../utils/environment.js';
10
10
  import { z } from 'zod';
11
11
  export default defineCommand({
12
12
  description: 'Create a new app channel.',
13
13
  options: defineOptions(z.object({
14
14
  appId: z.string().optional().describe('ID of the app.'),
15
- bundleLimit: z.coerce
16
- .number()
17
- .optional()
18
- .describe('Maximum number of bundles that can be assigned to the channel. If more bundles are assigned, the oldest bundles will be automatically deleted.'),
19
15
  expiresInDays: z.coerce
20
16
  .number({
21
17
  message: 'Expiration days must be an integer.',
@@ -27,9 +23,10 @@ export default defineCommand({
27
23
  .describe('The number of days until the channel is automatically deleted.'),
28
24
  ignoreErrors: z.boolean().optional().describe('Whether to ignore errors or not.'),
29
25
  name: z.string().optional().describe('Name of the channel.'),
26
+ protected: z.boolean().optional().describe('Whether to protect the channel or not. Default is `false`.'),
30
27
  })),
31
28
  action: async (options, args) => {
32
- let { appId, bundleLimit, expiresInDays, ignoreErrors, name } = options;
29
+ let { appId, expiresInDays, ignoreErrors, name, protected: _protected } = options;
33
30
  if (!authorizationService.hasAuthorizationToken()) {
34
31
  consola.error('You must be logged in to run this command. Please run the `login` command first.');
35
32
  process.exit(1);
@@ -85,12 +82,12 @@ export default defineCommand({
85
82
  try {
86
83
  const response = await appChannelsService.create({
87
84
  appId,
85
+ protected: _protected,
88
86
  name,
89
- totalAppBundleLimit: bundleLimit,
90
87
  expiresAt,
91
88
  });
92
- consola.success('Channel created successfully.');
93
89
  consola.info(`Channel ID: ${response.id}`);
90
+ consola.success('Channel created successfully.');
94
91
  }
95
92
  catch (error) {
96
93
  if (ignoreErrors) {