Upgrade infer commands from APP_VERSION (#11881)

# Introduction
This PR refactors the way we previously manually handled the upgrade
command `versionTo` and `versionFrom` values to be replaced by a
programmatic inferring using the `APP_VERSION` env variable. It raises
new invariant edge cases that are covered by new tests and so on

Please keep in mind that an upgrade will run agnostically of any `patch`
semver value as it should be done only when releasing a `major/minor`
version update
[Related discord
thread](https://discord.com/channels/1130383047699738754/1368953221921505280)

## Testing in local
In order to test in local we have to define an `APP_VERSION` value in
`packages/twenty-server/.env` following semver ( or not 🙃 )

## Logs example
```ts
Computing new Datasource for cacheKey: 20202020-1c25-4d02-bf25-6aeccf7ea419-8 out of 0
query: SELECT * FROM current_schema()
query: SELECT version();
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Initialized upgrade context with:
   - currentVersion (migrating to): 0.53.0
   - fromWorkspaceVersion: 0.52.0
   - 2 commands
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Upgrading workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 from=0.52.0 to=0.53.0 1/2
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Upgrade for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419 ignored as is already at a higher version.
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Running command on workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db 2/2
Computing new Datasource for cacheKey: 3b8e6458-5fc1-4e63-8563-008ccddaa6db-8 out of 0
query: SELECT * FROM current_schema()
query: SELECT version();
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Upgrading workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db from=0.52.0 to=0.53.0 2/2
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Upgrade for workspace 3b8e6458-5fc1-4e63-8563-008ccddaa6db ignored as is already at a higher version.
[Nest] 37872  - 05/06/2025, 4:07:21 PM     LOG [UpgradeCommand] Command completed!
```

## Misc
Related to https://github.com/twentyhq/twenty/issues/11780
This commit is contained in:
Paul Rastoin
2025-05-07 15:48:19 +02:00
committed by GitHub
parent e96afe444f
commit f129bc0ac4
9 changed files with 354 additions and 105 deletions

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`is-same-major-and-minor-version incomplete version1 1`] = `"Received invalid version: 1.0 1.1.0"`;
exports[`It should compare two versions with incomplete version1 1`] = `"Received invalid version: 1.0 1.1.0"`;
exports[`is-same-major-and-minor-version invalid version1 1`] = `"Received invalid version: invalid 1.1.0"`;
exports[`It should compare two versions with invalid version1 1`] = `"Received invalid version: invalid 1.1.0"`;
exports[`is-same-major-and-minor-version invalid version2 1`] = `"Received invalid version: 1.0.0 invalid"`;
exports[`It should compare two versions with invalid version2 1`] = `"Received invalid version: 1.0.0 invalid"`;

View File

@ -11,7 +11,7 @@ type IsSameVersionTestCase = EachTestingContext<{
expected?: CompareVersionMajorAndMinorReturnType;
expectToThrow?: boolean;
}>;
describe('is-same-major-and-minor-version', () => {
describe('It should compare two versions with', () => {
const beneathVersionTestCases: IsSameVersionTestCase[] = [
{
context: {
@ -181,7 +181,7 @@ describe('is-same-major-and-minor-version', () => {
...beneathVersionTestCases,
...aboveVersionTestCases,
])(
'$title',
' $title',
({ context: { version1, version2, expected, expectToThrow = false } }) => {
if (expectToThrow) {
expect(() =>

View File

@ -6,7 +6,7 @@ type IsSameVersionTestCase = EachTestingContext<{
version: string | undefined;
expected: string | null;
}>;
describe('extract-version-major-minor-patch', () => {
describe('It should extract major.minor.patch values from a', () => {
const testCase: IsSameVersionTestCase[] = [
{
context: {
@ -101,7 +101,7 @@ describe('extract-version-major-minor-patch', () => {
},
];
test.each(testCase)('$title', ({ context: { version, expected } }) => {
test.each(testCase)(' $title', ({ context: { version, expected } }) => {
expect(extractVersionMajorMinorPatch(version)).toBe(expected);
});
});

View File

@ -0,0 +1,103 @@
import { EachTestingContext } from 'twenty-shared/testing';
import { getPreviousVersion } from 'src/utils/version/get-previous-version';
type GetPreviousVersionTestCase = EachTestingContext<{
versions: string[];
currentVersion: string;
expected: string | undefined;
}>;
describe('It should return the previous version from a list of', () => {
const testCase: GetPreviousVersionTestCase[] = [
{
context: {
versions: ['0.1.0', '0.2.0', '0.3.0'],
currentVersion: '0.3.0',
expected: '0.2.0',
},
title: 'Basic version sequence',
},
{
context: {
versions: ['1.0.0', '1.1.0', '2.0.0'],
currentVersion: '2.0.0',
expected: '1.1.0',
},
title: 'Major version jump',
},
{
context: {
versions: ['0.1.0', '0.1.1', '0.1.2'],
currentVersion: '0.1.2',
expected: '0.1.1',
},
title: 'Patch version sequence',
},
{
context: {
versions: ['1.0.0'],
currentVersion: '1.0.0',
expected: undefined,
},
title: 'Single version',
},
{
context: {
versions: [],
currentVersion: '1.0.0',
expected: undefined,
},
title: 'Empty version array',
},
{
context: {
versions: ['0.1.0', '0.2.0', '0.3.0'],
currentVersion: '0.1.0',
expected: undefined,
},
title: 'No previous version available',
},
{
context: {
versions: ['1.0.0', '2.0.0', '1.5.0'],
currentVersion: '2.0.0',
expected: '1.5.0',
},
title: 'Unordered versions',
},
{
context: {
versions: ['1.0.0', '1.0.0-alpha', '1.0.0-beta'],
currentVersion: '1.0.0',
expected: '1.0.0-beta',
},
title: 'Pre-release versions',
},
{
context: {
versions: ['invalid', '1.0.0', '2.0.0'],
currentVersion: '2.0.0',
expected: undefined,
},
title: 'Invalid version in array',
},
{
context: {
versions: ['1.0.0', '2.0.0'],
currentVersion: 'invalid',
expected: undefined,
},
title: 'Invalid current version',
},
];
test.each(testCase)(
' $title',
({ context: { versions, currentVersion, expected } }) => {
const result = getPreviousVersion({ versions, currentVersion });
expect(result?.format()).toBe(expected);
},
);
});

View File

@ -0,0 +1,26 @@
import { SemVer } from 'semver';
type GetPreviousVersionFromArrayArgs = {
versions: string[];
currentVersion: string;
};
export const getPreviousVersion = ({
versions,
currentVersion,
}: GetPreviousVersionFromArrayArgs): SemVer | undefined => {
try {
const semverVersions = versions
.map((version) => new SemVer(version))
.sort((a, b) => b.compare(a));
const currentSemver = new SemVer(currentVersion);
const previousVersion = semverVersions.find(
(version) => version.compare(currentSemver) < 0,
);
return previousVersion;
} catch {
return undefined;
}
};