fix(backend): Use regex to parse version

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-02-04 14:58:02 +01:00
parent 69a7a1ae69
commit 7b2d541cac
2 changed files with 112 additions and 40 deletions

View file

@ -5,23 +5,80 @@
*/ */
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { getServerVersionFromPackageJson } from './serverVersion'; import {
clearCachedVersion,
getServerVersionFromPackageJson,
} from './serverVersion';
it('getServerVersionFromPackageJson works', async () => { jest.mock('fs', () => ({
const major = 2; promises: {
const minor = 0; readFile: jest.fn(),
const patch = 0; },
const preRelease = 'dev'; }));
/* eslint-disable @typescript-eslint/require-await*/
jest.spyOn(fs, 'readFile').mockImplementationOnce(async (_) => { describe('getServerVersionFromPackageJson', () => {
return `{ afterEach(() => {
"version": "${major}.${minor}.${patch}" clearCachedVersion();
} });
`;
it('parses a complete version string', async () => {
const major = 2;
const minor = 0;
const patch = 0;
const preRelease = 'dev';
jest
.spyOn(fs, 'readFile')
.mockImplementationOnce(
async (_) =>
`{ "version": "${major}.${minor}.${patch}-${preRelease}" }`,
);
const serverVersion = await getServerVersionFromPackageJson();
expect(serverVersion.major).toEqual(major);
expect(serverVersion.minor).toEqual(minor);
expect(serverVersion.patch).toEqual(patch);
expect(serverVersion.preRelease).toEqual(preRelease);
});
it('parses a version string without pre release', async () => {
const major = 2;
const minor = 0;
const patch = 0;
jest
.spyOn(fs, 'readFile')
.mockImplementationOnce(
async (_) => `{ "version": "${major}.${minor}.${patch}" }`,
);
const serverVersion = await getServerVersionFromPackageJson();
expect(serverVersion.major).toEqual(major);
expect(serverVersion.minor).toEqual(minor);
expect(serverVersion.patch).toEqual(patch);
expect(serverVersion.preRelease).toEqual(undefined);
});
it("throws an error if package.json can't be found", async () => {
jest.spyOn(fs, 'readFile').mockImplementationOnce(async (_) => {
throw new Error('package.json not found');
});
await expect(getServerVersionFromPackageJson()).rejects.toThrow(
'package.json not found',
);
});
it("throws an error if version isn't present in package.json", async () => {
jest.spyOn(fs, 'readFile').mockImplementationOnce(async (_) => `{ }`);
await expect(getServerVersionFromPackageJson()).rejects.toThrow(
'No version found in root package.json',
);
});
it('throws an error if the version is malformed', async () => {
jest
.spyOn(fs, 'readFile')
.mockImplementationOnce(
async (_) => `{ "version": "TwoDotZeroDotZero" }`,
);
await expect(getServerVersionFromPackageJson()).rejects.toThrow(
'Version from package.json is malformed. Got TwoDotZeroDotZero',
);
}); });
const serverVersion = await getServerVersionFromPackageJson();
expect(serverVersion.major).toEqual(major);
expect(serverVersion.minor).toEqual(minor);
expect(serverVersion.patch).toEqual(patch);
expect(serverVersion.preRelease).toEqual(preRelease);
}); });

View file

@ -3,36 +3,51 @@
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { Optional } from '@mrdrogdrog/optional';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { join as joinPath } from 'path'; import { join as joinPath } from 'path';
import { ServerVersion } from '../monitoring/server-status.dto'; import { ServerVersion } from '../monitoring/server-status.dto';
let versionCache: ServerVersion; let versionCache: ServerVersion | undefined = undefined;
/**
* Reads the HedgeDoc version from the root package.json. This is done only once per run.
*
* @return {Promise<ServerVersion>} A Promise that contains the parsed server version.
* @throws {Error} if the package.json couldn't be found or doesn't contain a correct version.
*/
export async function getServerVersionFromPackageJson(): Promise<ServerVersion> { export async function getServerVersionFromPackageJson(): Promise<ServerVersion> {
if (versionCache === undefined) { if (!versionCache) {
const rawFileContent: string = await fs.readFile( versionCache = await parseVersionFromPackageJson();
joinPath(__dirname, '../../package.json'),
{ encoding: 'utf8' },
);
// TODO: Should this be validated in more detail?
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const packageInfo: { version: string } = JSON.parse(rawFileContent);
const versionParts: number[] = packageInfo.version
.split('.')
.map((x) => parseInt(x, 10));
const preRelease = 'dev'; // TODO: Replace this?
versionCache = {
major: versionParts[0],
minor: versionParts[1],
patch: versionParts[2],
preRelease: preRelease,
fullString: `${versionParts[0]}.${versionParts[1]}.${versionParts[2]}${
preRelease ? '-' + preRelease : ''
}`,
};
} }
return versionCache; return versionCache;
} }
async function parseVersionFromPackageJson(): Promise<ServerVersion> {
const rawFileContent: string = await fs.readFile(
joinPath(__dirname, '../../../package.json'),
{ encoding: 'utf8' },
);
const packageInfo = JSON.parse(rawFileContent) as { version: string };
const versionParts = Optional.ofNullable(packageInfo.version)
.orThrow(() => new Error('No version found in root package.json'))
.map((version) => /^(\d+).(\d+).(\d+)(?:-(\w+))?$/g.exec(version))
.orElseThrow(
() =>
new Error(
`Version from package.json is malformed. Got ${packageInfo.version}`,
),
);
return {
major: parseInt(versionParts[1]),
minor: parseInt(versionParts[2]),
patch: parseInt(versionParts[3]),
preRelease: versionParts[4],
fullString: versionParts[0],
};
}
export function clearCachedVersion(): void {
versionCache = undefined;
}