@capawesome/cli 4.7.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
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
+
5
16
  ## [4.7.0](https://github.com/capawesome-team/cli/compare/v4.6.0...v4.7.0) (2026-04-04)
6
17
 
7
18
 
@@ -5,7 +5,9 @@ import appCertificatesService from '../../../services/app-certificates.js';
5
5
  import appEnvironmentsService from '../../../services/app-environments.js';
6
6
  import { parseKeyValuePairs } from '../../../utils/app-environments.js';
7
7
  import { withAuth } from '../../../utils/auth.js';
8
+ import { createBufferFromPath } from '../../../utils/buffer.js';
8
9
  import { isInteractive } from '../../../utils/environment.js';
10
+ import { fileExistsAtPath, isDirectory } from '../../../utils/file.js';
9
11
  import { waitForJobCompletion } from '../../../utils/job.js';
10
12
  import { prompt, promptAppSelection, promptOrganizationSelection } from '../../../utils/prompt.js';
11
13
  import zip from '../../../utils/zip.js';
@@ -47,7 +49,7 @@ export default defineCommand({
47
49
  .optional()
48
50
  .describe('Download the generated IPA file (iOS only). Optionally provide a file path.'),
49
51
  json: z.boolean().optional().describe('Output in JSON format.'),
50
- path: z.string().optional().describe('Path to local source files to upload.'),
52
+ path: z.string().optional().describe('Path to local source files to upload. Must be a folder or a zip file.'),
51
53
  platform: z
52
54
  .enum(['ios', 'android', 'web'], {
53
55
  message: 'Platform must be either `ios`, `android`, or `web`.',
@@ -117,15 +119,22 @@ export default defineCommand({
117
119
  if (sourcePath) {
118
120
  consola.warn('The --path option is experimental and may change in the future.');
119
121
  const resolvedPath = path.resolve(sourcePath);
120
- const stat = await fs.stat(resolvedPath).catch(() => null);
121
- if (!stat || !stat.isDirectory()) {
122
- consola.error('The --path must point to an existing directory.');
122
+ const exists = await fileExistsAtPath(resolvedPath);
123
+ if (!exists) {
124
+ consola.error('The --path does not exist.');
123
125
  process.exit(1);
124
126
  }
125
- const packageJsonPath = path.join(resolvedPath, 'package.json');
126
- const packageJsonStat = await fs.stat(packageJsonPath).catch(() => null);
127
- if (!packageJsonStat || !packageJsonStat.isFile()) {
128
- consola.error('The directory specified by --path must contain a package.json file.');
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.');
129
138
  process.exit(1);
130
139
  }
131
140
  }
@@ -265,8 +274,14 @@ export default defineCommand({
265
274
  // Upload source files if path is provided
266
275
  if (sourcePath) {
267
276
  const resolvedPath = path.resolve(sourcePath);
268
- consola.start('Zipping source files...');
269
- const buffer = await zip.zipFolderWithGitignore(resolvedPath);
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
+ }
270
285
  consola.start('Uploading source files...');
271
286
  const appBuildSource = await appBuildSourcesService.createFromFile({
272
287
  appId,
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_CONSOLE_BASE_URL } from '../../config/consts.js';
1
2
  import appsService from '../../services/apps.js';
2
3
  import { withAuth } from '../../utils/auth.js';
3
4
  import { isInteractive } from '../../utils/environment.js';
@@ -8,11 +9,14 @@ import { z } from 'zod';
8
9
  export default defineCommand({
9
10
  description: 'Create a new app.',
10
11
  options: defineOptions(z.object({
12
+ json: z.boolean().optional().describe('Output in JSON format.'),
13
+ link: z.boolean().optional().describe('Connect the created app to the local git repository.'),
11
14
  name: z.string().optional().describe('Name of the app.'),
12
15
  organizationId: z.string().optional().describe('ID of the organization to create the app in.'),
13
- })),
16
+ yes: z.boolean().optional().describe('Skip all confirmation prompts.'),
17
+ }), { y: 'yes' }),
14
18
  action: withAuth(async (options, args) => {
15
- let { name, organizationId } = options;
19
+ let { json, name, organizationId } = options;
16
20
  if (!organizationId) {
17
21
  if (!isInteractive()) {
18
22
  consola.error('You must provide the organization ID when running in non-interactive environment.');
@@ -28,7 +32,22 @@ export default defineCommand({
28
32
  name = await prompt('Enter the name of the app:', { type: 'text' });
29
33
  }
30
34
  const response = await appsService.create({ name, organizationId });
31
- consola.info(`App ID: ${response.id}`);
32
- consola.success('App created successfully.');
35
+ if (!json) {
36
+ consola.info(`App ID: ${response.id}`);
37
+ consola.info(`App URL: ${DEFAULT_CONSOLE_BASE_URL}/apps/${response.id}`);
38
+ consola.success('App created successfully.');
39
+ }
40
+ let shouldLink = options.link ?? false;
41
+ if (!shouldLink && !options.yes && !json && isInteractive()) {
42
+ shouldLink = await prompt('Do you want to connect a git repository?', {
43
+ type: 'confirm',
44
+ });
45
+ }
46
+ if (shouldLink) {
47
+ await (await import('../../commands/apps/link.js').then((mod) => mod.default)).action({ appId: response.id }, undefined);
48
+ }
49
+ if (json) {
50
+ console.log(JSON.stringify({ id: response.id }, null, 2));
51
+ }
33
52
  }),
34
53
  });
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import configService from './services/config.js';
3
3
  import updateService from './services/update.js';
4
- import { getMessageFromUnknownError } from './utils/error.js';
4
+ import { getMessageFromUnknownError, UserError } from './utils/error.js';
5
5
  import { defineConfig, processConfig, ZliError } from '@robingenz/zli';
6
6
  import * as Sentry from '@sentry/node';
7
7
  import { AxiosError } from 'axios';
@@ -76,6 +76,14 @@ const config = defineConfig({
76
76
  },
77
77
  });
78
78
  const captureException = async (error) => {
79
+ // Ignore failed HTTP requests
80
+ if (error instanceof AxiosError) {
81
+ return;
82
+ }
83
+ // Ignore expected user errors
84
+ if (error instanceof UserError) {
85
+ return;
86
+ }
79
87
  // Ignore errors from the CLI itself (e.g. "No command found.")
80
88
  if (error instanceof ZliError) {
81
89
  return;
@@ -84,10 +92,6 @@ const captureException = async (error) => {
84
92
  if (error instanceof ZodError) {
85
93
  return;
86
94
  }
87
- // Ignore failed HTTP requests
88
- if (error instanceof AxiosError) {
89
- return;
90
- }
91
95
  const environment = await configService.getValueForKey('ENVIRONMENT');
92
96
  if (environment !== 'production') {
93
97
  return;
@@ -1,5 +1,11 @@
1
1
  import { AxiosError } from 'axios';
2
2
  import { ZodError } from 'zod';
3
+ export class UserError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'UserError';
7
+ }
8
+ }
3
9
  export const getMessageFromUnknownError = (error) => {
4
10
  let message = 'An unknown error has occurred.';
5
11
  if (error instanceof AxiosError) {
package/dist/utils/git.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { execSync } from 'child_process';
2
+ import { UserError } from '../utils/error.js';
2
3
  const HOSTNAME_TO_PROVIDER = {
3
4
  'github.com': 'github',
4
5
  'gitlab.com': 'gitlab',
@@ -15,7 +16,7 @@ const getGitRemoteUrl = () => {
15
16
  return execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
16
17
  }
17
18
  catch {
18
- throw new Error('Could not read the git remote URL. Make sure you are inside a git repository with an origin remote.');
19
+ throw new UserError('Could not read the git remote URL. Make sure you are inside a git repository with an origin remote.');
19
20
  }
20
21
  };
21
22
  export const parseGitRemoteUrl = (remoteUrl) => {
@@ -55,7 +56,7 @@ export const parseGitRemoteUrl = (remoteUrl) => {
55
56
  const hostname = sshMatch[1];
56
57
  const provider = HOSTNAME_TO_PROVIDER[hostname];
57
58
  if (!provider) {
58
- throw new Error(`Unsupported git provider for hostname "${hostname}".`);
59
+ throw new UserError(`Unsupported git provider for hostname "${hostname}".`);
59
60
  }
60
61
  return {
61
62
  ownerSlug: sshMatch[2],
@@ -71,7 +72,7 @@ export const parseGitRemoteUrl = (remoteUrl) => {
71
72
  const hostname = url.hostname;
72
73
  const provider = HOSTNAME_TO_PROVIDER[hostname];
73
74
  if (!provider) {
74
- throw new Error(`Unsupported git provider for hostname "${hostname}".`);
75
+ throw new UserError(`Unsupported git provider for hostname "${hostname}".`);
75
76
  }
76
77
  const pathSegments = url.pathname.split('/').filter(Boolean);
77
78
  const repositorySlug = pathSegments.pop()?.replace(/\.git$/, '');
@@ -83,9 +84,9 @@ export const parseGitRemoteUrl = (remoteUrl) => {
83
84
  }
84
85
  }
85
86
  catch (error) {
86
- if (error instanceof Error && error.message.startsWith('Unsupported git provider')) {
87
+ if (error instanceof UserError) {
87
88
  throw error;
88
89
  }
89
90
  }
90
- throw new Error('Could not parse git remote URL.');
91
+ throw new UserError('Could not parse git remote URL.');
91
92
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "4.7.0",
3
+ "version": "4.8.0-dev.efa0850.1775645973",
4
4
  "description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
5
5
  "type": "module",
6
6
  "scripts": {