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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 { CliError, getMessageFromUnknownError } from './utils/error.js';
4
+ import { 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';
@@ -33,24 +33,14 @@ const config = defineConfig({
33
33
  'apps:channels:update': await import('./commands/apps/channels/update.js').then((mod) => mod.default),
34
34
  'apps:devices:delete': await import('./commands/apps/devices/delete.js').then((mod) => mod.default),
35
35
  'manifests:generate': await import('./commands/manifests/generate.js').then((mod) => mod.default),
36
- 'mutate:version:get': await import('./commands/mutate/version/get.js').then((mod) => mod.default),
37
- 'mutate:version:set': await import('./commands/mutate/version/set.js').then((mod) => mod.default),
38
- 'mutate:version:major': await import('./commands/mutate/version/major.js').then((mod) => mod.default),
39
- 'mutate:version:minor': await import('./commands/mutate/version/minor.js').then((mod) => mod.default),
40
- 'mutate:version:patch': await import('./commands/mutate/version/patch.js').then((mod) => mod.default),
41
- 'mutate:version:hotfix': await import('./commands/mutate/version/hotfix.js').then((mod) => mod.default),
42
- 'mutate:version:sync': await import('./commands/mutate/version/sync.js').then((mod) => mod.default),
43
36
  'organizations:create': await import('./commands/organizations/create.js').then((mod) => mod.default),
44
37
  },
45
38
  });
46
39
  const captureException = async (error) => {
47
- // Ignore expected CLI errors (e.g. "No command found.")
40
+ // Ignore errors from the CLI itself (e.g. "No command found.")
48
41
  if (error instanceof ZliError) {
49
42
  return;
50
43
  }
51
- if (error instanceof CliError) {
52
- return;
53
- }
54
44
  // Ignore validation errors
55
45
  if (error instanceof ZodError) {
56
46
  return;
@@ -65,7 +55,7 @@ const captureException = async (error) => {
65
55
  }
66
56
  Sentry.init({
67
57
  dsn: 'https://19f30f2ec4b91899abc33818568ceb42@o4507446340747264.ingest.de.sentry.io/4508506426966096',
68
- release: pkg.version,
58
+ release: `capawesome-team-cli@${pkg.version}`,
69
59
  });
70
60
  if (process.argv.slice(2).length > 0) {
71
61
  Sentry.setTag('cli_command', process.argv.slice(2)[0]);
@@ -1,17 +1,8 @@
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
- }
9
3
  export const getMessageFromUnknownError = (error) => {
10
4
  let message = 'An unknown error has occurred.';
11
- if (error instanceof CliError) {
12
- message = error.message;
13
- }
14
- else if (error instanceof AxiosError) {
5
+ if (error instanceof AxiosError) {
15
6
  message = getErrorMessageFromAxiosError(error);
16
7
  }
17
8
  else if (error instanceof ZodError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/cli",
3
- "version": "2.1.4-dev.6aa4113.1756747595",
3
+ "version": "2.1.4-dev.6aa4113.1756907810",
4
4
  "description": "The Capawesome Cloud Command Line Interface (CLI) to manage Live Updates and more.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -12,9 +12,9 @@
12
12
  "lint": "npm run prettier -- --check",
13
13
  "fmt": "npm run prettier -- --write",
14
14
  "prettier": "prettier \"**/*.{css,html,ts,js}\"",
15
- "sentry:releases:new": "sentry-cli releases new $npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
16
- "sentry:releases:set-commits": "sentry-cli releases set-commits $npm_package_version --auto --org genz-it-solutions-gmbh --project capawesome-team-cli",
17
- "sentry:releases:finalize": "sentry-cli releases finalize $npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
15
+ "sentry:releases:new": "sentry-cli releases new capawesome-team-cli@$npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
16
+ "sentry:releases:set-commits": "sentry-cli releases set-commits capawesome-team-cli@$npm_package_version --auto --org genz-it-solutions-gmbh --project capawesome-team-cli --ignore-missing",
17
+ "sentry:releases:finalize": "sentry-cli releases finalize capawesome-team-cli@$npm_package_version --org genz-it-solutions-gmbh --project capawesome-team-cli",
18
18
  "release": "commit-and-tag-version",
19
19
  "prepublishOnly": "npm run build && npm run sentry:releases:new && npm run sentry:releases:set-commits",
20
20
  "postpublish": "npm run sentry:releases:finalize"
@@ -55,7 +55,6 @@
55
55
  "@clack/prompts": "0.7.0",
56
56
  "@robingenz/zli": "0.1.5",
57
57
  "@sentry/node": "8.55.0",
58
- "@trapezedev/project": "7.1.3",
59
58
  "archiver": "7.0.1",
60
59
  "axios": "1.8.4",
61
60
  "axios-retry": "4.5.0",
@@ -1,47 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- export default defineCommand({
7
- description: 'Get the version of the app from all relevant files',
8
- action: async () => {
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:');
31
- versions.forEach((pv) => {
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})`);
35
- });
36
- throw new CliError('Versions are not synchronized across platforms');
37
- }
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
- });
46
- },
47
- });
@@ -1,85 +0,0 @@
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,22 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { incrementHotfix, versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- export default defineCommand({
7
- description: 'Increment the hotfix version of the app in all relevant files',
8
- action: async () => {
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');
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})`);
21
- },
22
- });
@@ -1,49 +0,0 @@
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
- });
@@ -1,14 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { incrementMajor, versionToString } from '../../../utils/version.js';
3
- import { defineCommand } from '@robingenz/zli';
4
- import consola from 'consola';
5
- export default defineCommand({
6
- description: 'Increment the major version of the app in all relevant files',
7
- action: async () => {
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)}`);
13
- },
14
- });
@@ -1,49 +0,0 @@
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,19 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { incrementMinor, versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- export default defineCommand({
7
- description: 'Increment the minor version of the app in all relevant files',
8
- action: async () => {
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');
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)}`);
18
- },
19
- });
@@ -1,48 +0,0 @@
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,19 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { incrementPatch, versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- export default defineCommand({
7
- description: 'Increment the patch version of the app in all relevant files',
8
- action: async () => {
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');
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)}`);
18
- },
19
- });
@@ -1,48 +0,0 @@
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,22 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { parseVersion, versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- import { z } from 'zod';
7
- export default defineCommand({
8
- description: 'Set the version of the app in all relevant files',
9
- args: z.tuple([z.string().describe('Version')]),
10
- action: async (_options, args) => {
11
- let version;
12
- try {
13
- version = parseVersion(args[0]);
14
- }
15
- catch (error) {
16
- throw new CliError("Invalid version format. Please use the format 'major.minor.patch' (e.g. '1.2.3').");
17
- }
18
- consola.info(`Setting version to ${versionToString(version)}...`);
19
- await versionService.setVersion(version);
20
- consola.success(`Version set to ${versionToString(version)}`);
21
- },
22
- });
@@ -1,41 +0,0 @@
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,26 +0,0 @@
1
- import versionService from '../../../services/mutate/version.js';
2
- import { CliError } from '../../../utils/error.js';
3
- import { versionToString } from '../../../utils/version.js';
4
- import { defineCommand } from '@robingenz/zli';
5
- import consola from 'consola';
6
- export default defineCommand({
7
- description: 'Set the highest version number among all platforms in all relevant files',
8
- action: async () => {
9
- const versions = await versionService.getAllVersions();
10
- if (versions.length === 0) {
11
- throw new CliError('No platform versions found');
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}`);
25
- },
26
- });
@@ -1,90 +0,0 @@
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
- });
@@ -1,213 +0,0 @@
1
- import { CliError } from '../../utils/error.js';
2
- import { compareVersions, parseBuildNumber, parseVersion, versionToBuildNumber, versionToString, } from '../../utils/version.js';
3
- import { MobileProject, Logger } from '@trapezedev/project';
4
- import { existsSync, readFileSync } from 'fs';
5
- import { join } from 'path';
6
- // Disable Trapeze logging
7
- Logger.log = () => { };
8
- Logger.v = () => { };
9
- Logger.debug = () => { };
10
- Logger.warn = () => { };
11
- Logger.error = () => { };
12
- export class VersionService {
13
- projectPath;
14
- constructor(projectPath = process.cwd()) {
15
- this.projectPath = projectPath;
16
- }
17
- async getAllVersions() {
18
- const versions = [];
19
- const iosVersion = await this.getIosVersion();
20
- if (iosVersion) {
21
- versions.push(iosVersion);
22
- }
23
- const androidVersion = await this.getAndroidVersion();
24
- if (androidVersion) {
25
- versions.push(androidVersion);
26
- }
27
- const webVersion = await this.getWebVersion();
28
- if (webVersion) {
29
- versions.push(webVersion);
30
- }
31
- return versions;
32
- }
33
- async getIosVersion() {
34
- const iosPath = join(this.projectPath, 'ios');
35
- if (!existsSync(iosPath)) {
36
- return null;
37
- }
38
- try {
39
- const project = new MobileProject(this.projectPath, {
40
- ios: {
41
- path: 'ios/App',
42
- },
43
- });
44
- await project.load();
45
- if (!project.ios) {
46
- return null;
47
- }
48
- const iosProject = project.ios.getPbxProject();
49
- if (!iosProject) {
50
- return null;
51
- }
52
- const buildNumber = await project.ios.getBuild(null, null);
53
- if (!buildNumber) {
54
- return null;
55
- }
56
- const version = parseBuildNumber(buildNumber);
57
- return {
58
- platform: 'ios',
59
- version,
60
- source: 'ios/App/App.xcodeproj/project.pbxproj',
61
- };
62
- }
63
- catch (error) {
64
- return null;
65
- }
66
- }
67
- async getAndroidVersion() {
68
- const androidPath = join(this.projectPath, 'android');
69
- if (!existsSync(androidPath)) {
70
- return null;
71
- }
72
- try {
73
- const project = new MobileProject(this.projectPath, {
74
- android: {
75
- path: 'android',
76
- },
77
- });
78
- await project.load();
79
- if (!project.android) {
80
- return null;
81
- }
82
- const versionCode = await project.android.getVersionCode();
83
- if (!versionCode) {
84
- return null;
85
- }
86
- const version = parseBuildNumber(versionCode);
87
- return {
88
- platform: 'android',
89
- version,
90
- source: 'android/app/build.gradle',
91
- };
92
- }
93
- catch (error) {
94
- return null;
95
- }
96
- }
97
- async getWebVersion() {
98
- const packageJsonPath = join(this.projectPath, 'package.json');
99
- if (!existsSync(packageJsonPath)) {
100
- return null;
101
- }
102
- try {
103
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
104
- if (!packageJson.version) {
105
- return null;
106
- }
107
- // Web only has version string, no build number (hotfix will always be 0)
108
- const version = parseVersion(packageJson.version);
109
- return {
110
- platform: 'web',
111
- version,
112
- source: 'package.json',
113
- };
114
- }
115
- catch (error) {
116
- return null;
117
- }
118
- }
119
- async setVersion(version) {
120
- const iosPath = join(this.projectPath, 'ios');
121
- const androidPath = join(this.projectPath, 'android');
122
- const packageJsonPath = join(this.projectPath, 'package.json');
123
- const project = new MobileProject(this.projectPath, {
124
- ios: existsSync(iosPath)
125
- ? {
126
- path: 'ios/App',
127
- }
128
- : undefined,
129
- android: existsSync(androidPath)
130
- ? {
131
- path: 'android',
132
- }
133
- : undefined,
134
- });
135
- await project.load();
136
- const versionString = versionToString(version);
137
- const buildNumber = versionToBuildNumber(version);
138
- if (project.ios) {
139
- await project.ios.setVersion(null, null, versionString);
140
- await project.ios.setBuild(null, null, buildNumber.toString());
141
- const infoPlistPath = await project.ios.getInfoPlist(null, null);
142
- if (infoPlistPath) {
143
- const infoPlist = await project.ios.getPlistFile(infoPlistPath);
144
- if (infoPlist) {
145
- await infoPlist.set({
146
- CFBundleShortVersionString: versionString,
147
- CFBundleVersion: buildNumber.toString(),
148
- });
149
- }
150
- }
151
- }
152
- if (project.android) {
153
- await project.android.setVersionName(versionString);
154
- await project.android.setVersionCode(buildNumber);
155
- }
156
- if (existsSync(packageJsonPath)) {
157
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
158
- packageJson.version = versionString;
159
- // Web only stores version string, not build number
160
- const fs = await import('fs/promises');
161
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
162
- }
163
- await project.commit();
164
- }
165
- async ensureVersionsInSync() {
166
- const versions = await this.getAllVersions();
167
- const firstVersion = versions && versions[0] ? versions[0].version : null;
168
- if (!firstVersion) {
169
- throw new CliError('No platform versions found');
170
- }
171
- // Check major.minor.patch synchronization for all platforms
172
- const allVersionsInSync = versions.every((pv) => pv.version.major === firstVersion.major &&
173
- pv.version.minor === firstVersion.minor &&
174
- pv.version.patch === firstVersion.patch);
175
- if (!allVersionsInSync) {
176
- const versionStrings = versions.map((pv) => {
177
- const versionStr = versionToString(pv.version);
178
- const hotfixStr = pv.platform !== 'web' && pv.version.hotfix ? ` (hotfix: ${pv.version.hotfix})` : '';
179
- return `${pv.platform}: ${versionStr}${hotfixStr} (${pv.source})`;
180
- });
181
- throw new CliError(`Versions are not synchronized across platforms:\n${versionStrings.join('\n')}`);
182
- }
183
- // Check hotfix synchronization between iOS and Android only
184
- const iosVersion = versions.find((pv) => pv.platform === 'ios');
185
- const androidVersion = versions.find((pv) => pv.platform === 'android');
186
- if (iosVersion && androidVersion) {
187
- const iosHotfix = iosVersion.version.hotfix || 0;
188
- const androidHotfix = androidVersion.version.hotfix || 0;
189
- if (iosHotfix !== androidHotfix) {
190
- throw new CliError(`Hotfix versions are not synchronized between iOS and Android:\n` +
191
- `iOS: ${versionToString(iosVersion.version)} (hotfix: ${iosHotfix})\n` +
192
- `Android: ${versionToString(androidVersion.version)} (hotfix: ${androidHotfix})`);
193
- }
194
- }
195
- // Return version with hotfix from iOS or Android if available
196
- const versionWithHotfix = versions.find((pv) => pv.platform !== 'web' && pv.version.hotfix && pv.version.hotfix > 0);
197
- return versionWithHotfix ? versionWithHotfix.version : firstVersion;
198
- }
199
- async getHighestVersion() {
200
- const versions = await this.getAllVersions();
201
- if (versions.length === 0) {
202
- throw new CliError('No platform versions found');
203
- }
204
- let highest = versions[0];
205
- for (const current of versions) {
206
- if (compareVersions(current.version, highest.version) > 0) {
207
- highest = current;
208
- }
209
- }
210
- return highest.version;
211
- }
212
- }
213
- export default new VersionService();
@@ -1,36 +0,0 @@
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
- });
@@ -1,96 +0,0 @@
1
- export const parseVersion = (versionString) => {
2
- const parts = versionString.split('.');
3
- if (parts.length !== 3) {
4
- throw new Error(`Invalid version format: ${versionString}. Expected format: major.minor.patch`);
5
- }
6
- const major = parseInt(parts[0] || '0', 10);
7
- const minor = parseInt(parts[1] || '0', 10);
8
- const patch = parseInt(parts[2] || '0', 10);
9
- if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
10
- throw new Error(`Invalid version format: ${versionString}. Version parts must be numbers.`);
11
- }
12
- if (major < 0 || minor < 0 || patch < 0) {
13
- throw new Error(`Invalid version format: ${versionString}. Version parts must be non-negative.`);
14
- }
15
- return { major, minor, patch };
16
- };
17
- export const parseBuildNumber = (buildNumber) => {
18
- const buildStr = buildNumber.toString();
19
- // Build number format: [major][minor:3][patch:2][hotfix:2]
20
- // The last 7 digits are always minor(3) + patch(2) + hotfix(2)
21
- if (buildStr.length < 8) {
22
- throw new Error(`Invalid build number: ${buildNumber}. Build number must be at least 8 digits.`);
23
- }
24
- const fixedPartStart = buildStr.length - 7;
25
- const major = parseInt(buildStr.substring(0, fixedPartStart), 10);
26
- const minor = parseInt(buildStr.substring(fixedPartStart, fixedPartStart + 3), 10);
27
- const patch = parseInt(buildStr.substring(fixedPartStart + 3, fixedPartStart + 5), 10);
28
- const hotfix = parseInt(buildStr.substring(fixedPartStart + 5, fixedPartStart + 7), 10);
29
- return { major, minor, patch, hotfix };
30
- };
31
- export const versionToString = (version) => {
32
- return `${version.major}.${version.minor}.${version.patch}`;
33
- };
34
- export const versionToBuildNumber = (version) => {
35
- // Build number format: [major][minor:3][patch:2][hotfix:2]
36
- // Major version has no limit and uses as many digits as needed
37
- const majorStr = version.major.toString();
38
- const minor = version.minor.toString().padStart(3, '0');
39
- const patch = version.patch.toString().padStart(2, '0');
40
- const hotfix = (version.hotfix || 0).toString().padStart(2, '0');
41
- if (version.minor > 999) {
42
- throw new Error(`Minor version ${version.minor} exceeds maximum value of 999`);
43
- }
44
- if (version.patch > 99) {
45
- throw new Error(`Patch version ${version.patch} exceeds maximum value of 99`);
46
- }
47
- if (version.hotfix && version.hotfix > 99) {
48
- throw new Error(`Hotfix version ${version.hotfix} exceeds maximum value of 99`);
49
- }
50
- return parseInt(`${majorStr}${minor}${patch}${hotfix}`, 10);
51
- };
52
- export const incrementMajor = (version) => {
53
- const newMajor = version.major + 1;
54
- return { major: newMajor, minor: 0, patch: 0 };
55
- };
56
- export const incrementMinor = (version) => {
57
- const newMinor = version.minor + 1;
58
- if (newMinor > 999) {
59
- throw new Error(`Cannot increment minor version: would exceed maximum value of 999`);
60
- }
61
- return { major: version.major, minor: newMinor, patch: 0 };
62
- };
63
- export const incrementPatch = (version) => {
64
- const newPatch = version.patch + 1;
65
- if (newPatch > 99) {
66
- throw new Error(`Cannot increment patch version: would exceed maximum value of 99`);
67
- }
68
- return { major: version.major, minor: version.minor, patch: newPatch };
69
- };
70
- export const incrementHotfix = (version) => {
71
- const currentHotfix = version.hotfix || 0;
72
- const newHotfix = currentHotfix + 1;
73
- if (newHotfix > 99) {
74
- throw new Error(`Cannot increment hotfix version: would exceed maximum value of 99`);
75
- }
76
- return { ...version, hotfix: newHotfix };
77
- };
78
- export const compareVersions = (v1, v2) => {
79
- if (v1.major !== v2.major)
80
- return v1.major - v2.major;
81
- if (v1.minor !== v2.minor)
82
- return v1.minor - v2.minor;
83
- if (v1.patch !== v2.patch)
84
- return v1.patch - v2.patch;
85
- const h1 = v1.hotfix || 0;
86
- const h2 = v2.hotfix || 0;
87
- if (h1 !== h2)
88
- return h1 - h2;
89
- return 0;
90
- };
91
- export const versionsEqual = (v1, v2, ignoreHotfix = false) => {
92
- if (ignoreHotfix) {
93
- return v1.major === v2.major && v1.minor === v2.minor && v1.patch === v2.patch;
94
- }
95
- return compareVersions(v1, v2) === 0;
96
- };