@capawesome/cli 4.6.0 → 4.8.0-dev.efa0850.1775645973

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 (45) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/commands/apps/builds/create.js +160 -128
  3. package/dist/commands/apps/bundles/create.js +4 -2
  4. package/dist/commands/apps/bundles/delete.js +2 -3
  5. package/dist/commands/apps/bundles/update.js +2 -3
  6. package/dist/commands/apps/certificates/create.js +3 -19
  7. package/dist/commands/apps/certificates/delete.js +28 -5
  8. package/dist/commands/apps/certificates/get.js +28 -5
  9. package/dist/commands/apps/certificates/update.js +3 -1
  10. package/dist/commands/apps/create.js +23 -4
  11. package/dist/commands/apps/deployments/create.js +5 -77
  12. package/dist/commands/apps/devices/forcechannel.js +9 -7
  13. package/dist/commands/apps/devices/unforcechannel.js +9 -7
  14. package/dist/commands/apps/link.js +34 -0
  15. package/dist/commands/apps/link.test.js +94 -0
  16. package/dist/commands/apps/liveupdates/bundle.js +12 -2
  17. package/dist/commands/apps/liveupdates/create.js +293 -0
  18. package/dist/commands/apps/liveupdates/create.test.js +300 -0
  19. package/dist/commands/apps/liveupdates/generate-manifest.js +17 -1
  20. package/dist/commands/apps/liveupdates/generate-manifest.test.js +21 -1
  21. package/dist/commands/apps/liveupdates/register.js +10 -15
  22. package/dist/commands/apps/liveupdates/upload.js +25 -16
  23. package/dist/commands/apps/transfer.js +47 -0
  24. package/dist/commands/apps/transfer.test.js +123 -0
  25. package/dist/commands/apps/unlink.js +35 -0
  26. package/dist/commands/apps/unlink.test.js +99 -0
  27. package/dist/commands/manifests/generate.js +1 -1
  28. package/dist/index.js +13 -5
  29. package/dist/services/app-build-sources.js +120 -0
  30. package/dist/services/app-certificates.js +0 -1
  31. package/dist/services/app-devices.js +8 -0
  32. package/dist/services/apps.js +25 -0
  33. package/dist/services/authorization-service.js +5 -1
  34. package/dist/services/jobs.js +13 -0
  35. package/dist/types/app-build-source.js +1 -0
  36. package/dist/types/index.js +1 -0
  37. package/dist/utils/custom-properties.js +22 -0
  38. package/dist/utils/error.js +6 -0
  39. package/dist/utils/file.js +12 -1
  40. package/dist/utils/git.js +92 -0
  41. package/dist/utils/git.test.js +130 -0
  42. package/dist/utils/job.js +77 -0
  43. package/dist/utils/prompt.js +1 -1
  44. package/dist/utils/zip.js +19 -2
  45. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,41 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [4.8.0](https://github.com/capawesome-team/cli/compare/v4.7.0...v4.8.0) (2026-04-08)
6
+
7
+
8
+ ### Features
9
+
10
+ * **apps:** add `--json` option to `apps:create` ([#147](https://github.com/capawesome-team/cli/issues/147)) ([3573d60](https://github.com/capawesome-team/cli/commit/3573d6021574111e12a6758d212b41002a357ae8))
11
+ * **apps:** add `--link` option to `apps:create` ([#145](https://github.com/capawesome-team/cli/issues/145)) ([c9e38b8](https://github.com/capawesome-team/cli/commit/c9e38b8e3e360bc856cbff4bf66b68fadedd3b04))
12
+ * **apps:** add prompt to connect git repository after app creation ([#143](https://github.com/capawesome-team/cli/issues/143)) ([3427e6d](https://github.com/capawesome-team/cli/commit/3427e6da277fa61902dac5b9045c433535188142))
13
+ * **apps:** print app URL after `apps:create` ([#146](https://github.com/capawesome-team/cli/issues/146)) ([ed7e04a](https://github.com/capawesome-team/cli/commit/ed7e04a5b379187d8c69856d180256f246931571))
14
+ * **error:** add `UserError` class to skip user errors in Sentry ([#144](https://github.com/capawesome-team/cli/issues/144)) ([f7728d0](https://github.com/capawesome-team/cli/commit/f7728d0849cf4883c761de5bfd673eb5c3c37409))
15
+
16
+ ## [4.7.0](https://github.com/capawesome-team/cli/compare/v4.6.0...v4.7.0) (2026-04-04)
17
+
18
+
19
+ ### Features
20
+
21
+ * **apps:builds:create:** support ad hoc environment variables ([#134](https://github.com/capawesome-team/cli/issues/134)) ([80c962a](https://github.com/capawesome-team/cli/commit/80c962abbc7548d32a7c0782860632117817d62b))
22
+ * **apps:certificates:** add `--type` option to `get` and `delete` commands ([#135](https://github.com/capawesome-team/cli/issues/135)) ([b9bdc51](https://github.com/capawesome-team/cli/commit/b9bdc5190a7a19b82589876cc798a30a83a4c5f2))
23
+ * **apps:certificates:** deprecate `--type` option ([#130](https://github.com/capawesome-team/cli/issues/130)) ([c53b766](https://github.com/capawesome-team/cli/commit/c53b76697189e40c9b96f8ad679e058775c23cd9))
24
+ * **apps:devices:** support multiple `--device-id` flags for `forcechannel` and `unforcechannel` ([#132](https://github.com/capawesome-team/cli/issues/132)) ([f61b812](https://github.com/capawesome-team/cli/commit/f61b8126d862eb44c1c1989692e823fdb95b0635))
25
+ * **apps:liveupdates:** add `apps:liveupdates:create` command ([#133](https://github.com/capawesome-team/cli/issues/133)) ([244c0b0](https://github.com/capawesome-team/cli/commit/244c0b0f785548083c555453c63941584135bc17))
26
+ * **apps:liveupdates:** add source map detection and warning ([f90427c](https://github.com/capawesome-team/cli/commit/f90427c0f1d9a691f609f481c4b098c51f6adb1f))
27
+ * **apps:liveupdates:** support multiple `--channel` options in `create` command ([#140](https://github.com/capawesome-team/cli/issues/140)) ([12df3ca](https://github.com/capawesome-team/cli/commit/12df3ca100cb53196064ca18c3f5970b7326b1a2))
28
+ * **apps:** add `--path` and `--url` options ([#131](https://github.com/capawesome-team/cli/issues/131)) ([8ae803e](https://github.com/capawesome-team/cli/commit/8ae803e38b74634be3eb32333aca072f722c1bf4))
29
+ * **apps:** add `apps:link` and `apps:unlink` commands ([#141](https://github.com/capawesome-team/cli/issues/141)) ([e37e2fe](https://github.com/capawesome-team/cli/commit/e37e2fe8c8d80845e92379a7f859564f78725052))
30
+ * **apps:** add `apps:transfer` command ([#142](https://github.com/capawesome-team/cli/issues/142)) ([b6f6b57](https://github.com/capawesome-team/cli/commit/b6f6b57943075ac3ab04d52a9a2d62deda85482e))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * **apps:liveupdates:** improve error message for invalid private keys ([#138](https://github.com/capawesome-team/cli/issues/138)) ([7752df3](https://github.com/capawesome-team/cli/commit/7752df3fc94f61555d07f7e2e9488d51fca0ae9b))
36
+ * **apps:liveupdates:** skip symlinks and validate directory path in live update commands ([#139](https://github.com/capawesome-team/cli/issues/139)) ([e1ad835](https://github.com/capawesome-team/cli/commit/e1ad83592ea0e6fdf21ea888f39b5dce66a63e29))
37
+ * **login:** trim authorization token to prevent invalid header characters ([#137](https://github.com/capawesome-team/cli/issues/137)) ([d49d410](https://github.com/capawesome-team/cli/commit/d49d4104c4392d32bfc0fb351e5837a74e02b6b3))
38
+ * **utils:** use error instead of warn for canceled build status ([ef3ab54](https://github.com/capawesome-team/cli/commit/ef3ab540416553f71047a0400a6af4e28da32568))
39
+
5
40
  ## [4.6.0](https://github.com/capawesome-team/cli/compare/v4.5.0...v4.6.0) (2026-03-18)
6
41
 
7
42
 
@@ -1,12 +1,16 @@
1
1
  import { DEFAULT_CONSOLE_BASE_URL } from '../../../config/consts.js';
2
+ import appBuildSourcesService from '../../../services/app-build-sources.js';
2
3
  import appBuildsService from '../../../services/app-builds.js';
3
4
  import appCertificatesService from '../../../services/app-certificates.js';
4
5
  import appEnvironmentsService from '../../../services/app-environments.js';
5
- import { unescapeAnsi } from '../../../utils/ansi.js';
6
+ import { parseKeyValuePairs } from '../../../utils/app-environments.js';
6
7
  import { withAuth } from '../../../utils/auth.js';
8
+ import { createBufferFromPath } from '../../../utils/buffer.js';
7
9
  import { isInteractive } from '../../../utils/environment.js';
10
+ import { fileExistsAtPath, isDirectory } from '../../../utils/file.js';
11
+ import { waitForJobCompletion } from '../../../utils/job.js';
8
12
  import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
9
- import { wait } from '../../../utils/wait.js';
13
+ import zip from '../../../utils/zip.js';
10
14
  import { defineCommand, defineOptions } from '@robingenz/zli';
11
15
  import consola from 'consola';
12
16
  import fs from 'fs/promises';
@@ -15,7 +19,7 @@ import { z } from 'zod';
15
19
  const IOS_BUILD_TYPES = ['simulator', 'development', 'ad-hoc', 'app-store', 'enterprise'];
16
20
  const ANDROID_BUILD_TYPES = ['debug', 'release'];
17
21
  export default defineCommand({
18
- description: 'Create a new app build.',
22
+ description: 'Create a new app build on Capawesome Cloud Runners.',
19
23
  options: defineOptions(z.object({
20
24
  aab: z
21
25
  .union([z.boolean(), z.string()])
@@ -45,6 +49,7 @@ export default defineCommand({
45
49
  .optional()
46
50
  .describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
47
51
  json: z.boolean().optional().describe('Output in JSON format.'),
52
+ path: z.string().optional().describe('Path to local source files to upload. Must be a folder or a zip file.'),
48
53
  platform: z
49
54
  .enum(['ios', 'android', 'web'], {
50
55
  message: 'Platform must be either `ios`, `android`, or `web`.',
@@ -57,10 +62,19 @@ export default defineCommand({
57
62
  })
58
63
  .optional()
59
64
  .describe('The build stack to use for the build process.'),
65
+ url: z.string().optional().describe('URL to a zip file to use as build source.'),
60
66
  type: z
61
67
  .string()
62
68
  .optional()
63
69
  .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`. For Web, no type is required.'),
70
+ variable: z
71
+ .array(z.string())
72
+ .optional()
73
+ .describe('Ad hoc environment variable in key=value format. Can be specified multiple times.'),
74
+ variableFile: z
75
+ .string()
76
+ .optional()
77
+ .describe('Path to a file containing ad hoc environment variables in .env format.'),
64
78
  zip: z
65
79
  .union([z.boolean(), z.string()])
66
80
  .optional()
@@ -68,7 +82,7 @@ export default defineCommand({
68
82
  yes: z.boolean().optional().describe('Skip confirmation prompts.'),
69
83
  }), { y: 'yes' }),
70
84
  action: withAuth(async (options) => {
71
- let { appId, platform, type, gitRef, environment, certificate, json, stack } = options;
85
+ let { appId, platform, type, gitRef, environment, certificate, json, stack, path: sourcePath, url } = options;
72
86
  // Validate that detached flag cannot be used with artifact flags
73
87
  if (options.detached && (options.apk || options.aab || options.ipa || options.zip)) {
74
88
  consola.error('The --detached flag cannot be used with --apk, --aab, --ipa, or --zip flags.');
@@ -84,6 +98,46 @@ export default defineCommand({
84
98
  consola.error('The --channel and --destination flags cannot be used together.');
85
99
  process.exit(1);
86
100
  }
101
+ // Validate that path, url, and gitRef cannot be used together
102
+ if (sourcePath && gitRef) {
103
+ consola.error('The --path and --git-ref flags cannot be used together.');
104
+ process.exit(1);
105
+ }
106
+ if (url && gitRef) {
107
+ consola.error('The --url and --git-ref flags cannot be used together.');
108
+ process.exit(1);
109
+ }
110
+ if (url && sourcePath) {
111
+ consola.error('The --url and --path flags cannot be used together.');
112
+ process.exit(1);
113
+ }
114
+ // Validate url if provided
115
+ if (url) {
116
+ consola.warn('The --url option is experimental and may change in the future.');
117
+ }
118
+ // Validate path if provided
119
+ if (sourcePath) {
120
+ consola.warn('The --path option is experimental and may change in the future.');
121
+ const resolvedPath = path.resolve(sourcePath);
122
+ const exists = await fileExistsAtPath(resolvedPath);
123
+ if (!exists) {
124
+ consola.error('The --path does not exist.');
125
+ process.exit(1);
126
+ }
127
+ const pathIsDirectory = await isDirectory(resolvedPath);
128
+ if (pathIsDirectory) {
129
+ const packageJsonPath = path.join(resolvedPath, 'package.json');
130
+ const packageJsonExists = await fileExistsAtPath(packageJsonPath);
131
+ if (!packageJsonExists) {
132
+ consola.error('The directory specified by --path must contain a package.json file.');
133
+ process.exit(1);
134
+ }
135
+ }
136
+ else if (!zip.isZipped(resolvedPath)) {
137
+ consola.error('The --path must be a folder or a zip file.');
138
+ process.exit(1);
139
+ }
140
+ }
87
141
  // Prompt for app ID if not provided
88
142
  if (!appId) {
89
143
  if (!isInteractive()) {
@@ -113,10 +167,10 @@ export default defineCommand({
113
167
  process.exit(1);
114
168
  }
115
169
  }
116
- // Prompt for git ref if not provided
117
- if (!gitRef) {
170
+ // Prompt for git ref if not provided and no path or url specified
171
+ if (!sourcePath && !url && !gitRef) {
118
172
  if (!isInteractive()) {
119
- consola.error('You must provide a git ref when running in non-interactive environment.');
173
+ consola.error('You must provide a git ref, path, or url when running in non-interactive environment.');
120
174
  process.exit(1);
121
175
  }
122
176
  gitRef = await prompt('Enter the Git reference (branch, tag, or commit SHA):', {
@@ -197,9 +251,54 @@ export default defineCommand({
197
251
  }
198
252
  }
199
253
  }
254
+ // Parse ad hoc environment variables from inline and file
255
+ const variablesMap = new Map();
256
+ if (options.variableFile) {
257
+ const fileContent = await fs.readFile(options.variableFile, 'utf-8');
258
+ const fileVariables = parseKeyValuePairs(fileContent);
259
+ fileVariables.forEach((v) => variablesMap.set(v.key, v.value));
260
+ }
261
+ if (options.variable) {
262
+ const inlineVariables = parseKeyValuePairs(options.variable.join('\n'));
263
+ inlineVariables.forEach((v) => variablesMap.set(v.key, v.value));
264
+ }
265
+ const adHocEnvironmentVariables = variablesMap.size > 0 ? Object.fromEntries(variablesMap) : undefined;
266
+ // Create build source from URL if provided
267
+ let appBuildSourceId;
268
+ if (url) {
269
+ consola.start('Creating build source from URL...');
270
+ const appBuildSource = await appBuildSourcesService.createFromUrl({ appId, fileUrl: url });
271
+ appBuildSourceId = appBuildSource.id;
272
+ consola.success('Build source created successfully.');
273
+ }
274
+ // Upload source files if path is provided
275
+ if (sourcePath) {
276
+ const resolvedPath = path.resolve(sourcePath);
277
+ let buffer;
278
+ if (zip.isZipped(resolvedPath)) {
279
+ buffer = await createBufferFromPath(resolvedPath);
280
+ }
281
+ else {
282
+ consola.start('Zipping source files...');
283
+ buffer = await zip.zipFolderWithGitignore(resolvedPath);
284
+ }
285
+ consola.start('Uploading source files...');
286
+ const appBuildSource = await appBuildSourcesService.createFromFile({
287
+ appId,
288
+ fileSizeInBytes: buffer.byteLength,
289
+ buffer,
290
+ name: 'source.zip',
291
+ }, (currentPart, totalParts) => {
292
+ consola.start(`Uploading source files (${currentPart}/${totalParts})...`);
293
+ });
294
+ appBuildSourceId = appBuildSource.id;
295
+ consola.success('Source files uploaded successfully.');
296
+ }
200
297
  // Create the app build
201
298
  consola.start('Creating build...');
202
299
  const response = await appBuildsService.create({
300
+ adHocEnvironmentVariables,
301
+ appBuildSourceId,
203
302
  appCertificateName: certificate,
204
303
  appEnvironmentName: environment,
205
304
  appId,
@@ -215,127 +314,60 @@ export default defineCommand({
215
314
  // Wait for build job to complete by default, unless --detached flag is set
216
315
  const shouldWait = !options.detached;
217
316
  if (shouldWait) {
218
- let lastPrintedLogNumber = 0;
219
- let isWaitingForStart = true;
220
- // Poll build status until completion
221
- while (true) {
222
- try {
223
- const build = await appBuildsService.findOne({
224
- appId,
225
- appBuildId: response.id,
226
- relations: 'appBuildArtifacts,job,job.jobLogs',
227
- });
228
- if (!build.job) {
229
- await wait(3000);
230
- continue;
231
- }
232
- const jobStatus = build.job.status;
233
- // Show spinner while queued or pending
234
- if (jobStatus === 'queued' || jobStatus === 'pending') {
235
- if (isWaitingForStart) {
236
- consola.start(`Waiting for build to start (status: ${jobStatus})...`);
237
- }
238
- await wait(3000);
239
- continue;
240
- }
241
- // Stop spinner when job moves to in_progress
242
- if (isWaitingForStart && jobStatus === 'in_progress') {
243
- isWaitingForStart = false;
244
- consola.success('Build started...');
245
- }
246
- // Print new logs
247
- if (build.job.jobLogs && build.job.jobLogs.length > 0) {
248
- const newLogs = build.job.jobLogs
249
- .filter((log) => log.number > lastPrintedLogNumber)
250
- .sort((a, b) => a.number - b.number);
251
- for (const log of newLogs) {
252
- console.log(unescapeAnsi(log.payload));
253
- lastPrintedLogNumber = log.number;
254
- }
255
- }
256
- // Handle terminal states
257
- if (jobStatus === 'succeeded' ||
258
- jobStatus === 'failed' ||
259
- jobStatus === 'canceled' ||
260
- jobStatus === 'rejected' ||
261
- jobStatus === 'timed_out') {
262
- console.log(); // New line for better readability
263
- if (jobStatus === 'succeeded') {
264
- consola.info(`Build ID: ${response.id}`);
265
- consola.info(`Build Number: ${response.numberAsString}`);
266
- consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
267
- consola.success('Build completed successfully.');
268
- console.log(); // New line for better readability
269
- // Download artifacts if flags are set
270
- if (options.apk && platform === 'android') {
271
- await handleArtifactDownload({
272
- appId,
273
- buildId: response.id,
274
- buildArtifacts: build.appBuildArtifacts,
275
- artifactType: 'apk',
276
- filePath: typeof options.apk === 'string' ? options.apk : undefined,
277
- });
278
- }
279
- if (options.aab && platform === 'android') {
280
- await handleArtifactDownload({
281
- appId,
282
- buildId: response.id,
283
- buildArtifacts: build.appBuildArtifacts,
284
- artifactType: 'aab',
285
- filePath: typeof options.aab === 'string' ? options.aab : undefined,
286
- });
287
- }
288
- if (options.ipa && platform === 'ios') {
289
- await handleArtifactDownload({
290
- appId,
291
- buildId: response.id,
292
- buildArtifacts: build.appBuildArtifacts,
293
- artifactType: 'ipa',
294
- filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
295
- });
296
- }
297
- if (options.zip && platform === 'web') {
298
- await handleArtifactDownload({
299
- appId,
300
- buildId: response.id,
301
- buildArtifacts: build.appBuildArtifacts,
302
- artifactType: 'zip',
303
- filePath: typeof options.zip === 'string' ? options.zip : undefined,
304
- });
305
- }
306
- // Output JSON if json flag is set
307
- if (json) {
308
- console.log(JSON.stringify({
309
- id: response.id,
310
- numberAsString: response.numberAsString,
311
- }, null, 2));
312
- }
313
- break;
314
- }
315
- else if (jobStatus === 'failed') {
316
- consola.error('Build failed.');
317
- process.exit(1);
318
- }
319
- else if (jobStatus === 'canceled') {
320
- consola.warn('Build was canceled.');
321
- process.exit(1);
322
- }
323
- else if (jobStatus === 'rejected') {
324
- consola.error('Build was rejected.');
325
- process.exit(1);
326
- }
327
- else if (jobStatus === 'timed_out') {
328
- consola.error('Build timed out.');
329
- process.exit(1);
330
- }
331
- }
332
- // Wait before next poll (3 seconds)
333
- await wait(3000);
334
- }
335
- catch (error) {
336
- consola.error('Error polling build status:', error);
337
- process.exit(1);
338
- }
317
+ await waitForJobCompletion({ jobId: response.jobId });
318
+ const appBuild = await appBuildsService.findOne({
319
+ appId,
320
+ appBuildId: response.id,
321
+ relations: 'appBuildArtifacts',
322
+ });
323
+ consola.info(`Build ID: ${response.id}`);
324
+ consola.info(`Build Number: ${response.numberAsString}`);
325
+ consola.info(`Build URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${appId}/builds/${response.id}`);
326
+ consola.success('Build completed successfully.');
327
+ console.log();
328
+ // Download artifacts if flags are set
329
+ if (options.apk && platform === 'android') {
330
+ await handleArtifactDownload({
331
+ appId,
332
+ buildId: response.id,
333
+ buildArtifacts: appBuild.appBuildArtifacts,
334
+ artifactType: 'apk',
335
+ filePath: typeof options.apk === 'string' ? options.apk : undefined,
336
+ });
337
+ }
338
+ if (options.aab && platform === 'android') {
339
+ await handleArtifactDownload({
340
+ appId,
341
+ buildId: response.id,
342
+ buildArtifacts: appBuild.appBuildArtifacts,
343
+ artifactType: 'aab',
344
+ filePath: typeof options.aab === 'string' ? options.aab : undefined,
345
+ });
346
+ }
347
+ if (options.ipa && platform === 'ios') {
348
+ await handleArtifactDownload({
349
+ appId,
350
+ buildId: response.id,
351
+ buildArtifacts: appBuild.appBuildArtifacts,
352
+ artifactType: 'ipa',
353
+ filePath: typeof options.ipa === 'string' ? options.ipa : undefined,
354
+ });
355
+ }
356
+ if (options.zip && platform === 'web') {
357
+ await handleArtifactDownload({
358
+ appId,
359
+ buildId: response.id,
360
+ buildArtifacts: appBuild.appBuildArtifacts,
361
+ artifactType: 'zip',
362
+ filePath: typeof options.zip === 'string' ? options.zip : undefined,
363
+ });
364
+ }
365
+ // Output JSON if json flag is set
366
+ if (json) {
367
+ console.log(JSON.stringify({
368
+ id: response.id,
369
+ numberAsString: response.numberAsString,
370
+ }, null, 2));
339
371
  }
340
372
  }
341
373
  else {
@@ -2,7 +2,7 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
2
2
  import consola from 'consola';
3
3
  import { z } from 'zod';
4
4
  export default defineCommand({
5
- description: 'Create a new app bundle.',
5
+ description: 'Create a new app bundle. Deprecated, use the `apps:liveupdates` commands instead.',
6
6
  options: defineOptions(z.object({
7
7
  androidMax: z.coerce
8
8
  .string()
@@ -38,6 +38,7 @@ export default defineCommand({
38
38
  commitSha: z.string().optional().describe('The commit sha related to the bundle.'),
39
39
  customProperty: z
40
40
  .array(z.string().min(1).max(100))
41
+ .max(10)
41
42
  .optional()
42
43
  .describe('A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times.'),
43
44
  expiresInDays: z.coerce
@@ -83,7 +84,8 @@ export default defineCommand({
83
84
  action: async (options, args) => {
84
85
  consola.warn('The `apps:bundles:create` command has been deprecated.');
85
86
  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:create` to build and deploy using Capawesome Cloud Runners');
88
+ consola.info(' - `apps:liveupdates:upload` to upload a locally built bundle to Capawesome Cloud');
87
89
  consola.info(' - `apps:liveupdates:register` to register a self-hosted bundle URL');
88
90
  process.exit(1);
89
91
  },
@@ -2,14 +2,13 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
2
2
  import consola from 'consola';
3
3
  import { z } from 'zod';
4
4
  export default defineCommand({
5
- description: 'Delete an app bundle.',
5
+ description: 'Delete an app bundle. Deprecated.',
6
6
  options: defineOptions(z.object({
7
7
  appId: z.string().optional().describe('ID of the app.'),
8
8
  bundleId: z.string().optional().describe('ID of the bundle.'),
9
9
  })),
10
10
  action: async (options, args) => {
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.');
11
+ consola.warn('The `apps:bundles:delete` command has been deprecated. Please use the `apps:liveupdates` commands instead.');
13
12
  process.exit(1);
14
13
  },
15
14
  });
@@ -2,7 +2,7 @@ import { defineCommand, defineOptions } from '@robingenz/zli';
2
2
  import consola from 'consola';
3
3
  import { z } from 'zod';
4
4
  export default defineCommand({
5
- description: 'Update an app bundle.',
5
+ description: 'Update an app bundle. Deprecated.',
6
6
  options: defineOptions(z.object({
7
7
  androidMax: z
8
8
  .string()
@@ -40,8 +40,7 @@ export default defineCommand({
40
40
  .describe('The exact iOS bundle version (`CFBundleVersion`) that the bundle should not support.'),
41
41
  })),
42
42
  action: async (options, args) => {
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.');
43
+ consola.warn('The `apps:bundles:update` command has been deprecated. Please use the `apps:liveupdates` commands instead.');
45
44
  process.exit(1);
46
45
  },
47
46
  });
@@ -74,24 +74,9 @@ export default defineCommand({
74
74
  process.exit(1);
75
75
  }
76
76
  }
77
- // 4. Select certificate type
78
- if (!type) {
79
- if (!isInteractive()) {
80
- consola.error('You must provide the certificate type when running in non-interactive environment.');
81
- process.exit(1);
82
- }
83
- // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
84
- type = await prompt('Select the certificate type:', {
85
- type: 'select',
86
- options: [
87
- { label: 'Development', value: 'development' },
88
- { label: 'Production', value: 'production' },
89
- ],
90
- });
91
- if (!type) {
92
- consola.error('You must select a certificate type.');
93
- process.exit(1);
94
- }
77
+ // 4. Warn if deprecated --type option is used
78
+ if (type) {
79
+ consola.warn('The --type option is deprecated and will be removed in a future version. The certificate type is now detected automatically.');
95
80
  }
96
81
  // 5. Enter certificate file path
97
82
  if (!file) {
@@ -167,7 +152,6 @@ export default defineCommand({
167
152
  fileName,
168
153
  name,
169
154
  platform: platform,
170
- type: type,
171
155
  password,
172
156
  keyAlias,
173
157
  keyPassword,
@@ -15,10 +15,14 @@ export default defineCommand({
15
15
  .enum(['android', 'ios', 'web'])
16
16
  .optional()
17
17
  .describe('Platform of the certificate (android, ios, web).'),
18
+ type: z
19
+ .enum(['development', 'production'])
20
+ .optional()
21
+ .describe('Type of the certificate (development, production).'),
18
22
  yes: z.boolean().optional().describe('Skip confirmation prompt.'),
19
23
  }), { y: 'yes' }),
20
24
  action: withAuth(async (options, args) => {
21
- let { appId, certificateId, name, platform } = options;
25
+ let { appId, certificateId, name, platform, type } = options;
22
26
  if (!appId) {
23
27
  if (!isInteractive()) {
24
28
  consola.error('You must provide an app ID when running in non-interactive environment.');
@@ -29,10 +33,19 @@ export default defineCommand({
29
33
  }
30
34
  if (!certificateId) {
31
35
  if (name && platform) {
32
- const certificates = await appCertificatesService.findAll({ appId, name, platform });
36
+ const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
33
37
  const firstCertificate = certificates[0];
34
38
  if (!firstCertificate) {
35
- consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
39
+ if (type) {
40
+ consola.error(`No certificate found with name '${name}', platform '${platform}', and type '${type}'.`);
41
+ }
42
+ else {
43
+ consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
44
+ }
45
+ process.exit(1);
46
+ }
47
+ if (certificates.length > 1 && !type) {
48
+ consola.error(`Multiple certificates found with name '${name}' and platform '${platform}'. Please specify --type or --certificate-id to disambiguate.`);
36
49
  process.exit(1);
37
50
  }
38
51
  certificateId = firstCertificate.id;
@@ -49,9 +62,19 @@ export default defineCommand({
49
62
  ],
50
63
  });
51
64
  }
52
- const certificates = await appCertificatesService.findAll({ appId, platform });
65
+ if (!type) {
66
+ // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
67
+ type = await prompt('Select the type:', {
68
+ type: 'select',
69
+ options: [
70
+ { label: 'Development', value: 'development' },
71
+ { label: 'Production', value: 'production' },
72
+ ],
73
+ });
74
+ }
75
+ const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
53
76
  if (!certificates.length) {
54
- consola.error('No certificates found for this app. Create one first.');
77
+ consola.error(`No certificates found with platform '${platform}' and type '${type}'. Create one first.`);
55
78
  process.exit(1);
56
79
  }
57
80
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
@@ -16,9 +16,13 @@ export default defineCommand({
16
16
  .enum(['android', 'ios', 'web'])
17
17
  .optional()
18
18
  .describe('Platform of the certificate (android, ios, web).'),
19
+ type: z
20
+ .enum(['development', 'production'])
21
+ .optional()
22
+ .describe('Type of the certificate (development, production).'),
19
23
  })),
20
24
  action: withAuth(async (options, args) => {
21
- let { appId, certificateId, name, platform } = options;
25
+ let { appId, certificateId, name, platform, type } = options;
22
26
  if (!appId) {
23
27
  if (!isInteractive()) {
24
28
  consola.error('You must provide an app ID when running in non-interactive environment.');
@@ -29,10 +33,19 @@ export default defineCommand({
29
33
  }
30
34
  if (!certificateId) {
31
35
  if (name && platform) {
32
- const certificates = await appCertificatesService.findAll({ appId, name, platform });
36
+ const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
33
37
  const firstCertificate = certificates[0];
34
38
  if (!firstCertificate) {
35
- consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
39
+ if (type) {
40
+ consola.error(`No certificate found with name '${name}', platform '${platform}', and type '${type}'.`);
41
+ }
42
+ else {
43
+ consola.error(`No certificate found with name '${name}' and platform '${platform}'.`);
44
+ }
45
+ process.exit(1);
46
+ }
47
+ if (certificates.length > 1 && !type) {
48
+ consola.error(`Multiple certificates found with name '${name}' and platform '${platform}'. Please specify --type or --certificate-id to disambiguate.`);
36
49
  process.exit(1);
37
50
  }
38
51
  certificateId = firstCertificate.id;
@@ -49,9 +62,19 @@ export default defineCommand({
49
62
  ],
50
63
  });
51
64
  }
52
- const certificates = await appCertificatesService.findAll({ appId, platform });
65
+ if (!type) {
66
+ // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
67
+ type = await prompt('Select the type:', {
68
+ type: 'select',
69
+ options: [
70
+ { label: 'Development', value: 'development' },
71
+ { label: 'Production', value: 'production' },
72
+ ],
73
+ });
74
+ }
75
+ const certificates = await appCertificatesService.findAll({ appId, name, platform, type });
53
76
  if (!certificates.length) {
54
- consola.error('No certificates found for this app. Create one first.');
77
+ consola.error(`No certificates found with platform '${platform}' and type '${type}'. Create one first.`);
55
78
  process.exit(1);
56
79
  }
57
80
  // @ts-ignore wait till https://github.com/unjs/consola/pull/280 is merged
@@ -36,11 +36,13 @@ export default defineCommand({
36
36
  }
37
37
  certificateId = await prompt('Enter the certificate ID:', { type: 'text' });
38
38
  }
39
+ if (type) {
40
+ consola.warn('The --type option is deprecated and will be removed in a future version. The certificate type is now detected automatically.');
41
+ }
39
42
  await appCertificatesService.update({
40
43
  appId,
41
44
  certificateId,
42
45
  name,
43
- type,
44
46
  password,
45
47
  keyAlias,
46
48
  keyPassword,