@capawesome/cli 2.1.4-dev.6aa4113.1756747593 → 2.1.4-dev.6aa4113.1756747595

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.
@@ -1,53 +1,47 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
5
6
  export default defineCommand({
6
7
  description: 'Get the version of the app from all relevant files',
7
8
  action: async () => {
8
- try {
9
- const versions = await versionService.getAllVersions();
10
- if (versions.length === 0) {
11
- consola.error('No platform versions found');
12
- process.exit(1);
13
- }
14
- const firstVersion = versions[0].version;
15
- // Check major.minor.patch synchronization for all platforms
16
- const allVersionsInSync = versions.every((pv) => {
17
- return (pv.version.major === firstVersion.major &&
18
- pv.version.minor === firstVersion.minor &&
19
- pv.version.patch === firstVersion.patch);
20
- });
21
- // Check hotfix synchronization between iOS and Android
22
- const iosVersion = versions.find((pv) => pv.platform === 'ios');
23
- const androidVersion = versions.find((pv) => pv.platform === 'android');
24
- let hotfixInSync = true;
25
- if (iosVersion && androidVersion) {
26
- const iosHotfix = iosVersion.version.hotfix || 0;
27
- const androidHotfix = androidVersion.version.hotfix || 0;
28
- hotfixInSync = iosHotfix === androidHotfix;
29
- }
30
- if (!allVersionsInSync || !hotfixInSync) {
31
- consola.error('Versions are not synchronized across platforms:');
32
- versions.forEach((pv) => {
33
- const versionStr = versionToString(pv.version);
34
- const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
35
- consola.log(` ${pv.platform}: ${versionStr}${hotfixStr} (${pv.source})`);
36
- });
37
- process.exit(1);
38
- }
39
- const versionStr = versionToString(firstVersion);
40
- // Show hotfix if iOS or Android has one
41
- const platformWithHotfix = versions.find((pv) => pv.platform !== 'web' && pv.version.hotfix && pv.version.hotfix > 0);
42
- const hotfixStr = platformWithHotfix ? ` (hotfix: ${platformWithHotfix.version.hotfix})` : '';
43
- consola.success(`Version: ${versionStr}${hotfixStr}`);
9
+ const versions = await versionService.getAllVersions();
10
+ if (versions.length === 0) {
11
+ throw new CliError('No platform versions found');
12
+ }
13
+ const firstVersion = versions[0].version;
14
+ // Check major.minor.patch synchronization for all platforms
15
+ const allVersionsInSync = versions.every((pv) => {
16
+ return (pv.version.major === firstVersion.major &&
17
+ pv.version.minor === firstVersion.minor &&
18
+ pv.version.patch === firstVersion.patch);
19
+ });
20
+ // Check hotfix synchronization between iOS and Android
21
+ const iosVersion = versions.find((pv) => pv.platform === 'ios');
22
+ const androidVersion = versions.find((pv) => pv.platform === 'android');
23
+ let hotfixInSync = true;
24
+ if (iosVersion && androidVersion) {
25
+ const iosHotfix = iosVersion.version.hotfix || 0;
26
+ const androidHotfix = androidVersion.version.hotfix || 0;
27
+ hotfixInSync = iosHotfix === androidHotfix;
28
+ }
29
+ if (!allVersionsInSync || !hotfixInSync) {
30
+ consola.error('Versions are not synchronized across platforms:');
44
31
  versions.forEach((pv) => {
45
- consola.log(` ${pv.platform}: ${pv.source}`);
32
+ const versionStr = versionToString(pv.version);
33
+ const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
34
+ consola.log(` ${pv.platform}: ${versionStr}${hotfixStr} (${pv.source})`);
46
35
  });
36
+ throw new CliError('Versions are not synchronized across platforms');
47
37
  }
48
- catch (error) {
49
- consola.error(error instanceof Error ? error.message : String(error));
50
- process.exit(1);
51
- }
38
+ const versionStr = versionToString(firstVersion);
39
+ // Show hotfix if iOS or Android has one
40
+ const platformWithHotfix = versions.find((pv) => pv.platform !== 'web' && pv.version.hotfix && pv.version.hotfix > 0);
41
+ const hotfixStr = platformWithHotfix ? ` (hotfix: ${platformWithHotfix.version.hotfix})` : '';
42
+ consola.success(`Version: ${versionStr}${hotfixStr}`);
43
+ versions.forEach((pv) => {
44
+ consola.log(` ${pv.platform}: ${pv.source}`);
45
+ });
52
46
  },
53
47
  });
@@ -0,0 +1,85 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import getCommand from './get.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:get', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should display synchronized versions across all platforms', async () => {
19
+ const mockVersions = [
20
+ {
21
+ platform: 'ios',
22
+ version: { major: 1, minor: 2, patch: 3, hotfix: 0 },
23
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
24
+ },
25
+ {
26
+ platform: 'android',
27
+ version: { major: 1, minor: 2, patch: 3, hotfix: 0 },
28
+ source: 'android/app/build.gradle',
29
+ },
30
+ { platform: 'web', version: { major: 1, minor: 2, patch: 3 }, source: 'package.json' },
31
+ ];
32
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
33
+ await getCommand.action({}, undefined);
34
+ expect(mockConsola.success).toHaveBeenCalledWith('Version: 1.2.3');
35
+ expect(mockConsola.log).toHaveBeenCalledWith(' ios: ios/App/App.xcodeproj/project.pbxproj');
36
+ expect(mockConsola.log).toHaveBeenCalledWith(' android: android/app/build.gradle');
37
+ expect(mockConsola.log).toHaveBeenCalledWith(' web: package.json');
38
+ expect(mockExit).not.toHaveBeenCalled();
39
+ });
40
+ it('should display hotfix when iOS/Android have one', async () => {
41
+ const mockVersions = [
42
+ {
43
+ platform: 'ios',
44
+ version: { major: 1, minor: 2, patch: 3, hotfix: 5 },
45
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
46
+ },
47
+ {
48
+ platform: 'android',
49
+ version: { major: 1, minor: 2, patch: 3, hotfix: 5 },
50
+ source: 'android/app/build.gradle',
51
+ },
52
+ { platform: 'web', version: { major: 1, minor: 2, patch: 3 }, source: 'package.json' },
53
+ ];
54
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
55
+ await getCommand.action({}, undefined);
56
+ expect(mockConsola.success).toHaveBeenCalledWith('Version: 1.2.3 (hotfix: 5)');
57
+ });
58
+ it('should error when versions are not synchronized', async () => {
59
+ const mockVersions = [
60
+ {
61
+ platform: 'ios',
62
+ version: { major: 1, minor: 2, patch: 3, hotfix: 0 },
63
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
64
+ },
65
+ {
66
+ platform: 'android',
67
+ version: { major: 1, minor: 2, patch: 4, hotfix: 0 },
68
+ source: 'android/app/build.gradle',
69
+ },
70
+ { platform: 'web', version: { major: 1, minor: 2, patch: 3 }, source: 'package.json' },
71
+ ];
72
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
73
+ await expect(getCommand.action({}, undefined)).rejects.toThrow(CliError);
74
+ await expect(getCommand.action({}, undefined)).rejects.toThrow('Versions are not synchronized across platforms');
75
+ expect(mockConsola.error).toHaveBeenCalledWith('Versions are not synchronized across platforms:');
76
+ expect(mockConsola.log).toHaveBeenCalledWith(' ios: 1.2.3 (ios/App/App.xcodeproj/project.pbxproj)');
77
+ expect(mockConsola.log).toHaveBeenCalledWith(' android: 1.2.4 (android/app/build.gradle)');
78
+ expect(mockConsola.log).toHaveBeenCalledWith(' web: 1.2.3 (package.json)');
79
+ });
80
+ it('should error when no platform versions found', async () => {
81
+ mockVersionService.getAllVersions.mockResolvedValue([]);
82
+ await expect(getCommand.action({}, undefined)).rejects.toThrow(CliError);
83
+ await expect(getCommand.action({}, undefined)).rejects.toThrow('No platform versions found');
84
+ });
85
+ });
@@ -1,23 +1,22 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { incrementHotfix, versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
5
6
  export default defineCommand({
6
7
  description: 'Increment the hotfix version of the app in all relevant files',
7
8
  action: async () => {
8
- try {
9
- const currentVersion = await versionService.ensureVersionsInSync();
10
- const newVersion = incrementHotfix(currentVersion);
11
- const versionStr = versionToString(currentVersion);
12
- const currentHotfix = currentVersion.hotfix || 0;
13
- const newHotfix = newVersion.hotfix || 0;
14
- consola.info(`Incrementing hotfix for version ${versionStr} (${currentHotfix} -> ${newHotfix})...`);
15
- await versionService.setVersion(newVersion);
16
- consola.success(`Hotfix incremented for version ${versionStr} (now ${newHotfix})`);
17
- }
18
- catch (error) {
19
- consola.error(error instanceof Error ? error.message : String(error));
20
- process.exit(1);
9
+ const currentVersion = await versionService.ensureVersionsInSync();
10
+ // Check for hotfix version limit
11
+ const currentHotfix = currentVersion.hotfix || 0;
12
+ if (currentHotfix >= 99) {
13
+ throw new CliError('Cannot increment hotfix version: would exceed maximum value of 99');
21
14
  }
15
+ const newVersion = incrementHotfix(currentVersion);
16
+ const versionStr = versionToString(currentVersion);
17
+ const newHotfix = newVersion.hotfix || 0;
18
+ consola.info(`Incrementing hotfix for version ${versionStr} (${currentHotfix} -> ${newHotfix})...`);
19
+ await versionService.setVersion(newVersion);
20
+ consola.success(`Hotfix incremented for version ${versionStr} (now ${newHotfix})`);
22
21
  },
23
22
  });
@@ -0,0 +1,49 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import hotfixCommand from './hotfix.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:hotfix', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should increment hotfix version from 0', async () => {
19
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 0 };
20
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
21
+ mockVersionService.setVersion.mockResolvedValue(undefined);
22
+ await hotfixCommand.action({}, undefined);
23
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing hotfix for version 1.2.3 (0 -> 1)...');
24
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 3, hotfix: 1 });
25
+ expect(mockConsola.success).toHaveBeenCalledWith('Hotfix incremented for version 1.2.3 (now 1)');
26
+ expect(mockExit).not.toHaveBeenCalled();
27
+ });
28
+ it('should increment existing hotfix version', async () => {
29
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 5 };
30
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
31
+ mockVersionService.setVersion.mockResolvedValue(undefined);
32
+ await hotfixCommand.action({}, undefined);
33
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing hotfix for version 1.2.3 (5 -> 6)...');
34
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 3, hotfix: 6 });
35
+ expect(mockConsola.success).toHaveBeenCalledWith('Hotfix incremented for version 1.2.3 (now 6)');
36
+ });
37
+ it('should handle maximum hotfix version limit', async () => {
38
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 99 };
39
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
40
+ await expect(hotfixCommand.action({}, undefined)).rejects.toThrow(CliError);
41
+ await expect(hotfixCommand.action({}, undefined)).rejects.toThrow('Cannot increment hotfix version: would exceed maximum value of 99');
42
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
43
+ });
44
+ it('should handle hotfix sync errors between iOS and Android', async () => {
45
+ mockVersionService.ensureVersionsInSync.mockRejectedValue(new CliError('Hotfix versions are not synchronized between iOS and Android'));
46
+ await expect(hotfixCommand.action({}, undefined)).rejects.toThrow('Hotfix versions are not synchronized between iOS and Android');
47
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
48
+ });
49
+ });
@@ -5,16 +5,10 @@ import consola from 'consola';
5
5
  export default defineCommand({
6
6
  description: 'Increment the major version of the app in all relevant files',
7
7
  action: async () => {
8
- try {
9
- const currentVersion = await versionService.ensureVersionsInSync();
10
- const newVersion = incrementMajor(currentVersion);
11
- consola.info(`Incrementing major version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
12
- await versionService.setVersion(newVersion);
13
- consola.success(`Major version incremented to ${versionToString(newVersion)}`);
14
- }
15
- catch (error) {
16
- consola.error(error instanceof Error ? error.message : String(error));
17
- process.exit(1);
18
- }
8
+ const currentVersion = await versionService.ensureVersionsInSync();
9
+ const newVersion = incrementMajor(currentVersion);
10
+ consola.info(`Incrementing major version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
11
+ await versionService.setVersion(newVersion);
12
+ consola.success(`Major version incremented to ${versionToString(newVersion)}`);
19
13
  },
20
14
  });
@@ -0,0 +1,49 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import majorCommand from './major.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:major', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should increment major version successfully', async () => {
19
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 0 };
20
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
21
+ mockVersionService.setVersion.mockResolvedValue(undefined);
22
+ await majorCommand.action({}, undefined);
23
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing major version from 1.2.3 to 2.0.0...');
24
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 2, minor: 0, patch: 0 });
25
+ expect(mockConsola.success).toHaveBeenCalledWith('Major version incremented to 2.0.0');
26
+ expect(mockExit).not.toHaveBeenCalled();
27
+ });
28
+ it('should handle large major versions', async () => {
29
+ const currentVersion = { major: 999, minor: 2, patch: 3, hotfix: 0 };
30
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
31
+ mockVersionService.setVersion.mockResolvedValue(undefined);
32
+ await majorCommand.action({}, undefined);
33
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing major version from 999.2.3 to 1000.0.0...');
34
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1000, minor: 0, patch: 0 });
35
+ expect(mockConsola.success).toHaveBeenCalledWith('Major version incremented to 1000.0.0');
36
+ });
37
+ it('should handle version sync errors', async () => {
38
+ mockVersionService.ensureVersionsInSync.mockRejectedValue(new CliError('Versions are not synchronized across platforms'));
39
+ await expect(majorCommand.action({}, undefined)).rejects.toThrow('Versions are not synchronized across platforms');
40
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
41
+ });
42
+ it('should handle service errors during set', async () => {
43
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 0 };
44
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
45
+ mockVersionService.setVersion.mockRejectedValue(new Error('Failed to update version'));
46
+ await expect(majorCommand.action({}, undefined)).rejects.toThrow('Failed to update version');
47
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 2, minor: 0, patch: 0 });
48
+ });
49
+ });
@@ -1,20 +1,19 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { incrementMinor, versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
5
6
  export default defineCommand({
6
7
  description: 'Increment the minor version of the app in all relevant files',
7
8
  action: async () => {
8
- try {
9
- const currentVersion = await versionService.ensureVersionsInSync();
10
- const newVersion = incrementMinor(currentVersion);
11
- consola.info(`Incrementing minor version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
12
- await versionService.setVersion(newVersion);
13
- consola.success(`Minor version incremented to ${versionToString(newVersion)}`);
14
- }
15
- catch (error) {
16
- consola.error(error instanceof Error ? error.message : String(error));
17
- process.exit(1);
9
+ const currentVersion = await versionService.ensureVersionsInSync();
10
+ // Check for minor version limit
11
+ if (currentVersion.minor >= 999) {
12
+ throw new CliError('Cannot increment minor version: would exceed maximum value of 999');
18
13
  }
14
+ const newVersion = incrementMinor(currentVersion);
15
+ consola.info(`Incrementing minor version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
16
+ await versionService.setVersion(newVersion);
17
+ consola.success(`Minor version incremented to ${versionToString(newVersion)}`);
19
18
  },
20
19
  });
@@ -0,0 +1,48 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import minorCommand from './minor.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:minor', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should increment minor version successfully', async () => {
19
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 0 };
20
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
21
+ mockVersionService.setVersion.mockResolvedValue(undefined);
22
+ await minorCommand.action({}, undefined);
23
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing minor version from 1.2.3 to 1.3.0...');
24
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 3, patch: 0 });
25
+ expect(mockConsola.success).toHaveBeenCalledWith('Minor version incremented to 1.3.0');
26
+ expect(mockExit).not.toHaveBeenCalled();
27
+ });
28
+ it('should handle maximum minor version limit', async () => {
29
+ const currentVersion = { major: 1, minor: 999, patch: 3, hotfix: 0 };
30
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
31
+ await expect(minorCommand.action({}, undefined)).rejects.toThrow(CliError);
32
+ await expect(minorCommand.action({}, undefined)).rejects.toThrow('Cannot increment minor version: would exceed maximum value of 999');
33
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
34
+ });
35
+ it('should reset patch and hotfix when incrementing minor', async () => {
36
+ const currentVersion = { major: 1, minor: 2, patch: 5, hotfix: 3 };
37
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
38
+ mockVersionService.setVersion.mockResolvedValue(undefined);
39
+ await minorCommand.action({}, undefined);
40
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 3, patch: 0 });
41
+ expect(mockConsola.success).toHaveBeenCalledWith('Minor version incremented to 1.3.0');
42
+ });
43
+ it('should handle version sync errors', async () => {
44
+ mockVersionService.ensureVersionsInSync.mockRejectedValue(new CliError('Versions are not synchronized across platforms'));
45
+ await expect(minorCommand.action({}, undefined)).rejects.toThrow('Versions are not synchronized across platforms');
46
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
47
+ });
48
+ });
@@ -1,20 +1,19 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { incrementPatch, versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
5
6
  export default defineCommand({
6
7
  description: 'Increment the patch version of the app in all relevant files',
7
8
  action: async () => {
8
- try {
9
- const currentVersion = await versionService.ensureVersionsInSync();
10
- const newVersion = incrementPatch(currentVersion);
11
- consola.info(`Incrementing patch version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
12
- await versionService.setVersion(newVersion);
13
- consola.success(`Patch version incremented to ${versionToString(newVersion)}`);
14
- }
15
- catch (error) {
16
- consola.error(error instanceof Error ? error.message : String(error));
17
- process.exit(1);
9
+ const currentVersion = await versionService.ensureVersionsInSync();
10
+ // Check for patch version limit
11
+ if (currentVersion.patch >= 99) {
12
+ throw new CliError('Cannot increment patch version: would exceed maximum value of 99');
18
13
  }
14
+ const newVersion = incrementPatch(currentVersion);
15
+ consola.info(`Incrementing patch version from ${versionToString(currentVersion)} to ${versionToString(newVersion)}...`);
16
+ await versionService.setVersion(newVersion);
17
+ consola.success(`Patch version incremented to ${versionToString(newVersion)}`);
19
18
  },
20
19
  });
@@ -0,0 +1,48 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import patchCommand from './patch.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:patch', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should increment patch version successfully', async () => {
19
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 0 };
20
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
21
+ mockVersionService.setVersion.mockResolvedValue(undefined);
22
+ await patchCommand.action({}, undefined);
23
+ expect(mockConsola.info).toHaveBeenCalledWith('Incrementing patch version from 1.2.3 to 1.2.4...');
24
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 4 });
25
+ expect(mockConsola.success).toHaveBeenCalledWith('Patch version incremented to 1.2.4');
26
+ expect(mockExit).not.toHaveBeenCalled();
27
+ });
28
+ it('should handle maximum patch version limit', async () => {
29
+ const currentVersion = { major: 1, minor: 2, patch: 99, hotfix: 0 };
30
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
31
+ await expect(patchCommand.action({}, undefined)).rejects.toThrow(CliError);
32
+ await expect(patchCommand.action({}, undefined)).rejects.toThrow('Cannot increment patch version: would exceed maximum value of 99');
33
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
34
+ });
35
+ it('should reset hotfix when incrementing patch', async () => {
36
+ const currentVersion = { major: 1, minor: 2, patch: 3, hotfix: 5 };
37
+ mockVersionService.ensureVersionsInSync.mockResolvedValue(currentVersion);
38
+ mockVersionService.setVersion.mockResolvedValue(undefined);
39
+ await patchCommand.action({}, undefined);
40
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 4 });
41
+ expect(mockConsola.success).toHaveBeenCalledWith('Patch version incremented to 1.2.4');
42
+ });
43
+ it('should handle version sync errors', async () => {
44
+ mockVersionService.ensureVersionsInSync.mockRejectedValue(new CliError('Versions are not synchronized across platforms'));
45
+ await expect(patchCommand.action({}, undefined)).rejects.toThrow('Versions are not synchronized across platforms');
46
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
47
+ });
48
+ });
@@ -1,4 +1,5 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { parseVersion, versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
@@ -7,15 +8,15 @@ export default defineCommand({
7
8
  description: 'Set the version of the app in all relevant files',
8
9
  args: z.tuple([z.string().describe('Version')]),
9
10
  action: async (_options, args) => {
11
+ let version;
10
12
  try {
11
- const version = parseVersion(args[0]);
12
- consola.info(`Setting version to ${versionToString(version)}...`);
13
- await versionService.setVersion(version);
14
- consola.success(`Version set to ${versionToString(version)}`);
13
+ version = parseVersion(args[0]);
15
14
  }
16
15
  catch (error) {
17
- consola.error(error instanceof Error ? error.message : String(error));
18
- process.exit(1);
16
+ throw new CliError("Invalid version format. Please use the format 'major.minor.patch' (e.g. '1.2.3').");
19
17
  }
18
+ consola.info(`Setting version to ${versionToString(version)}...`);
19
+ await versionService.setVersion(version);
20
+ consola.success(`Version set to ${versionToString(version)}`);
20
21
  },
21
22
  });
@@ -0,0 +1,41 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import setCommand from './set.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:set', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should set a valid version', async () => {
19
+ mockVersionService.setVersion.mockResolvedValue(undefined);
20
+ await setCommand.action({}, ['1.2.3']);
21
+ expect(mockConsola.info).toHaveBeenCalledWith('Setting version to 1.2.3...');
22
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 3 });
23
+ expect(mockConsola.success).toHaveBeenCalledWith('Version set to 1.2.3');
24
+ expect(mockExit).not.toHaveBeenCalled();
25
+ });
26
+ it('should handle invalid version format', async () => {
27
+ await expect(setCommand.action({}, ['1.2'])).rejects.toThrow(CliError);
28
+ await expect(setCommand.action({}, ['1.2'])).rejects.toThrow("Invalid version format. Please use the format 'major.minor.patch' (e.g. '1.2.3').");
29
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
30
+ });
31
+ it('should handle non-numeric version parts', async () => {
32
+ await expect(setCommand.action({}, ['1.2.abc'])).rejects.toThrow(CliError);
33
+ await expect(setCommand.action({}, ['1.2.abc'])).rejects.toThrow("Invalid version format. Please use the format 'major.minor.patch' (e.g. '1.2.3').");
34
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
35
+ });
36
+ it('should handle service errors', async () => {
37
+ mockVersionService.setVersion.mockRejectedValue(new Error('Failed to set version'));
38
+ await expect(setCommand.action({}, ['1.2.3'])).rejects.toThrow('Failed to set version');
39
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith({ major: 1, minor: 2, patch: 3 });
40
+ });
41
+ });
@@ -1,32 +1,26 @@
1
1
  import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
2
3
  import { versionToString } from '../../../utils/version.js';
3
4
  import { defineCommand } from '@robingenz/zli';
4
5
  import consola from 'consola';
5
6
  export default defineCommand({
6
7
  description: 'Set the highest version number among all platforms in all relevant files',
7
8
  action: async () => {
8
- try {
9
- const versions = await versionService.getAllVersions();
10
- if (versions.length === 0) {
11
- consola.error('No platform versions found');
12
- process.exit(1);
13
- }
14
- const highestVersion = await versionService.getHighestVersion();
15
- consola.info('Current versions:');
16
- versions.forEach((pv) => {
17
- const versionStr = versionToString(pv.version);
18
- const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
19
- consola.log(` ${pv.platform}: ${versionStr}${hotfixStr}`);
20
- });
21
- const highestVersionStr = versionToString(highestVersion);
22
- const hotfixStr = highestVersion.hotfix ? ` (hotfix: ${highestVersion.hotfix})` : '';
23
- consola.info(`Syncing all platforms to highest version: ${highestVersionStr}${hotfixStr}...`);
24
- await versionService.setVersion(highestVersion);
25
- consola.success(`All platforms synced to version ${highestVersionStr}${hotfixStr}`);
26
- }
27
- catch (error) {
28
- consola.error(error instanceof Error ? error.message : String(error));
29
- process.exit(1);
9
+ const versions = await versionService.getAllVersions();
10
+ if (versions.length === 0) {
11
+ throw new CliError('No platform versions found');
30
12
  }
13
+ const highestVersion = await versionService.getHighestVersion();
14
+ consola.info('Current versions:');
15
+ versions.forEach((pv) => {
16
+ const versionStr = versionToString(pv.version);
17
+ const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
18
+ consola.log(` ${pv.platform}: ${versionStr}${hotfixStr}`);
19
+ });
20
+ const highestVersionStr = versionToString(highestVersion);
21
+ const hotfixStr = highestVersion.hotfix ? ` (hotfix: ${highestVersion.hotfix})` : '';
22
+ consola.info(`Syncing all platforms to highest version: ${highestVersionStr}${hotfixStr}...`);
23
+ await versionService.setVersion(highestVersion);
24
+ consola.success(`All platforms synced to version ${highestVersionStr}${hotfixStr}`);
31
25
  },
32
26
  });
@@ -0,0 +1,90 @@
1
+ import versionService from '../../../services/mutate/version.js';
2
+ import { CliError } from '../../../utils/error.js';
3
+ import consola from 'consola';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import syncCommand from './sync.js';
6
+ vi.mock('consola');
7
+ vi.mock('@/services/mutate/version.js');
8
+ describe('mutate:version:sync', () => {
9
+ const mockConsola = vi.mocked(consola);
10
+ const mockVersionService = vi.mocked(versionService);
11
+ const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ vi.restoreAllMocks();
17
+ });
18
+ it('should sync to highest version across platforms', async () => {
19
+ const mockVersions = [
20
+ {
21
+ platform: 'ios',
22
+ version: { major: 1, minor: 2, patch: 3, hotfix: 0 },
23
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
24
+ },
25
+ {
26
+ platform: 'android',
27
+ version: { major: 1, minor: 3, patch: 0, hotfix: 0 },
28
+ source: 'android/app/build.gradle',
29
+ },
30
+ { platform: 'web', version: { major: 1, minor: 2, patch: 4 }, source: 'package.json' },
31
+ ];
32
+ const highestVersion = { major: 1, minor: 3, patch: 0, hotfix: 0 };
33
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
34
+ mockVersionService.getHighestVersion.mockResolvedValue(highestVersion);
35
+ mockVersionService.setVersion.mockResolvedValue(undefined);
36
+ await syncCommand.action({}, undefined);
37
+ expect(mockConsola.info).toHaveBeenCalledWith('Current versions:');
38
+ expect(mockConsola.log).toHaveBeenCalledWith(' ios: 1.2.3');
39
+ expect(mockConsola.log).toHaveBeenCalledWith(' android: 1.3.0');
40
+ expect(mockConsola.log).toHaveBeenCalledWith(' web: 1.2.4');
41
+ expect(mockConsola.info).toHaveBeenCalledWith('Syncing all platforms to highest version: 1.3.0...');
42
+ expect(mockVersionService.setVersion).toHaveBeenCalledWith(highestVersion);
43
+ expect(mockConsola.success).toHaveBeenCalledWith('All platforms synced to version 1.3.0');
44
+ expect(mockExit).not.toHaveBeenCalled();
45
+ });
46
+ it('should sync including hotfix for iOS/Android', async () => {
47
+ const mockVersions = [
48
+ {
49
+ platform: 'ios',
50
+ version: { major: 1, minor: 2, patch: 3, hotfix: 5 },
51
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
52
+ },
53
+ {
54
+ platform: 'android',
55
+ version: { major: 1, minor: 2, patch: 3, hotfix: 5 },
56
+ source: 'android/app/build.gradle',
57
+ },
58
+ { platform: 'web', version: { major: 1, minor: 2, patch: 2 }, source: 'package.json' },
59
+ ];
60
+ const highestVersion = { major: 1, minor: 2, patch: 3, hotfix: 5 };
61
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
62
+ mockVersionService.getHighestVersion.mockResolvedValue(highestVersion);
63
+ mockVersionService.setVersion.mockResolvedValue(undefined);
64
+ await syncCommand.action({}, undefined);
65
+ expect(mockConsola.log).toHaveBeenCalledWith(' ios: 1.2.3 (hotfix: 5)');
66
+ expect(mockConsola.log).toHaveBeenCalledWith(' android: 1.2.3 (hotfix: 5)');
67
+ expect(mockConsola.log).toHaveBeenCalledWith(' web: 1.2.2');
68
+ expect(mockConsola.info).toHaveBeenCalledWith('Syncing all platforms to highest version: 1.2.3 (hotfix: 5)...');
69
+ expect(mockConsola.success).toHaveBeenCalledWith('All platforms synced to version 1.2.3 (hotfix: 5)');
70
+ });
71
+ it('should handle no platform versions found', async () => {
72
+ mockVersionService.getAllVersions.mockResolvedValue([]);
73
+ await expect(syncCommand.action({}, undefined)).rejects.toThrow(CliError);
74
+ await expect(syncCommand.action({}, undefined)).rejects.toThrow('No platform versions found');
75
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
76
+ });
77
+ it('should handle service errors', async () => {
78
+ const mockVersions = [
79
+ {
80
+ platform: 'ios',
81
+ version: { major: 1, minor: 2, patch: 3, hotfix: 0 },
82
+ source: 'ios/App/App.xcodeproj/project.pbxproj',
83
+ },
84
+ ];
85
+ mockVersionService.getAllVersions.mockResolvedValue(mockVersions);
86
+ mockVersionService.getHighestVersion.mockRejectedValue(new Error('Failed to determine highest version'));
87
+ await expect(syncCommand.action({}, undefined)).rejects.toThrow('Failed to determine highest version');
88
+ expect(mockVersionService.setVersion).not.toHaveBeenCalled();
89
+ });
90
+ });
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
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 { CliError, getMessageFromUnknownError } 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';
8
8
  import consola from 'consola';
9
- import { ZodError } from 'zod';
10
9
  import { createRequire } from 'module';
10
+ import { ZodError } from 'zod';
11
11
  const require = createRequire(import.meta.url);
12
12
  const pkg = require('../package.json');
13
13
  const config = defineConfig({
@@ -44,10 +44,13 @@ const config = defineConfig({
44
44
  },
45
45
  });
46
46
  const captureException = async (error) => {
47
- // Ignore errors from the CLI itself (e.g. "No command found.")
47
+ // Ignore expected CLI errors (e.g. "No command found.")
48
48
  if (error instanceof ZliError) {
49
49
  return;
50
50
  }
51
+ if (error instanceof CliError) {
52
+ return;
53
+ }
51
54
  // Ignore validation errors
52
55
  if (error instanceof ZodError) {
53
56
  return;
@@ -1,7 +1,14 @@
1
+ import { CliError } from '../../utils/error.js';
1
2
  import { compareVersions, parseBuildNumber, parseVersion, versionToBuildNumber, versionToString, } from '../../utils/version.js';
2
- import { MobileProject } from '@trapezedev/project';
3
+ import { MobileProject, Logger } from '@trapezedev/project';
3
4
  import { existsSync, readFileSync } from 'fs';
4
5
  import { join } from 'path';
6
+ // Disable Trapeze logging
7
+ Logger.log = () => { };
8
+ Logger.v = () => { };
9
+ Logger.debug = () => { };
10
+ Logger.warn = () => { };
11
+ Logger.error = () => { };
5
12
  export class VersionService {
6
13
  projectPath;
7
14
  constructor(projectPath = process.cwd()) {
@@ -159,7 +166,7 @@ export class VersionService {
159
166
  const versions = await this.getAllVersions();
160
167
  const firstVersion = versions && versions[0] ? versions[0].version : null;
161
168
  if (!firstVersion) {
162
- throw new Error('No platform versions found');
169
+ throw new CliError('No platform versions found');
163
170
  }
164
171
  // Check major.minor.patch synchronization for all platforms
165
172
  const allVersionsInSync = versions.every((pv) => pv.version.major === firstVersion.major &&
@@ -171,7 +178,7 @@ export class VersionService {
171
178
  const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
172
179
  return `${pv.platform}: ${versionStr}${hotfixStr} (${pv.source})`;
173
180
  });
174
- throw new Error(`Versions are not synchronized across platforms:\n${versionStrings.join('\n')}`);
181
+ throw new CliError(`Versions are not synchronized across platforms:\n${versionStrings.join('\n')}`);
175
182
  }
176
183
  // Check hotfix synchronization between iOS and Android only
177
184
  const iosVersion = versions.find((pv) => pv.platform === 'ios');
@@ -180,7 +187,7 @@ export class VersionService {
180
187
  const iosHotfix = iosVersion.version.hotfix || 0;
181
188
  const androidHotfix = androidVersion.version.hotfix || 0;
182
189
  if (iosHotfix !== androidHotfix) {
183
- throw new Error(`Hotfix versions are not synchronized between iOS and Android:\n` +
190
+ throw new CliError(`Hotfix versions are not synchronized between iOS and Android:\n` +
184
191
  `iOS: ${versionToString(iosVersion.version)} (hotfix: ${iosHotfix})\n` +
185
192
  `Android: ${versionToString(androidVersion.version)} (hotfix: ${androidHotfix})`);
186
193
  }
@@ -192,7 +199,7 @@ export class VersionService {
192
199
  async getHighestVersion() {
193
200
  const versions = await this.getAllVersions();
194
201
  if (versions.length === 0) {
195
- throw new Error('No platform versions found');
202
+ throw new CliError('No platform versions found');
196
203
  }
197
204
  let highest = versions[0];
198
205
  for (const current of versions) {
@@ -1,8 +1,17 @@
1
1
  import { AxiosError } from 'axios';
2
2
  import { ZodError } from 'zod';
3
+ export class CliError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'CliError';
7
+ }
8
+ }
3
9
  export const getMessageFromUnknownError = (error) => {
4
10
  let message = 'An unknown error has occurred.';
5
- if (error instanceof AxiosError) {
11
+ if (error instanceof CliError) {
12
+ message = error.message;
13
+ }
14
+ else if (error instanceof AxiosError) {
6
15
  message = getErrorMessageFromAxiosError(error);
7
16
  }
8
17
  else if (error instanceof ZodError) {
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { CliError, getMessageFromUnknownError } from './error.js';
3
+ describe('CliError', () => {
4
+ it('should be an instance of Error', () => {
5
+ const error = new CliError('Test error');
6
+ expect(error).toBeInstanceOf(Error);
7
+ expect(error).toBeInstanceOf(CliError);
8
+ });
9
+ it('should have the correct name', () => {
10
+ const error = new CliError('Test error');
11
+ expect(error.name).toBe('CliError');
12
+ });
13
+ it('should have the correct message', () => {
14
+ const message = 'This is a test error message';
15
+ const error = new CliError(message);
16
+ expect(error.message).toBe(message);
17
+ });
18
+ });
19
+ describe('getMessageFromUnknownError', () => {
20
+ it('should handle CliError correctly', () => {
21
+ const message = 'This is a CLI error';
22
+ const error = new CliError(message);
23
+ expect(getMessageFromUnknownError(error)).toBe(message);
24
+ });
25
+ it('should handle regular Error', () => {
26
+ const message = 'This is a regular error';
27
+ const error = new Error(message);
28
+ expect(getMessageFromUnknownError(error)).toBe(message);
29
+ });
30
+ it('should handle unknown error types', () => {
31
+ expect(getMessageFromUnknownError('string error')).toBe('An unknown error has occurred.');
32
+ expect(getMessageFromUnknownError(123)).toBe('An unknown error has occurred.');
33
+ expect(getMessageFromUnknownError(null)).toBe('An unknown error has occurred.');
34
+ expect(getMessageFromUnknownError(undefined)).toBe('An unknown error has occurred.');
35
+ });
36
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "2.1.4-dev.6aa4113.1756747593",
3
+ "version": "2.1.4-dev.6aa4113.1756747595",
4
4
  "description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
5
5
  "type": "module",
6
6
  "scripts": {