Public Access
feat(settings): show the build version of each service
This commit is contained in:
1 parent
9d1c7bb40d
commit
cfceca5e5e
11 files changed
+213
-3
No files matched your search
@@ -0,0 +1,38 @@
|
||||
const REPO_URL = 'https://git.andreas-dahm.eu/Lobo_Corp';
|
||||
|
||||
// Set by vite.config.js from the Docker build args; "dev" and "" outside the CI build.
|
||||
export const FRONTEND_VERSION = {commit: __BUILD_COMMIT__, builtAt: __BUILD_TIME__ || null};
|
||||
|
||||
export const VERSION_SOURCES = [
|
||||
{name: 'Frontend', repo: 'ToBePlayed-Frontend'},
|
||||
{name: 'Auth', repo: 'ToBePlayed-Auth', url: () => `${import.meta.env.VITE_API_URL}/api/version`},
|
||||
{name: 'Core', repo: 'ToBePlayed-Core', url: () => `${import.meta.env.VITE_CORE_API_URL}/api/version`},
|
||||
];
|
||||
|
||||
/**
|
||||
* GET /api/version of one backend (public). Resolves to null when the service does not answer or runs a build
|
||||
* without this endpoint.
|
||||
*/
|
||||
export async function fetchVersion(url) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) return null;
|
||||
const version = await response.json();
|
||||
return typeof version?.commit === 'string' ? version : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The build time without seconds, e.g. "27.09.2026, 10:05"; null when missing or not a date.
|
||||
*/
|
||||
export const formatBuildTime = (builtAt, locale) => {
|
||||
const date = new Date(builtAt ?? '');
|
||||
return Number.isNaN(date.getTime()) ? null
|
||||
: date.toLocaleString(locale, {dateStyle: 'medium', timeStyle: 'short'});
|
||||
};
|
||||
|
||||
const GIT_HASH = /^[0-9a-f]{7,40}$/;
|
||||
|
||||
export const commitUrl = (repo, commit) => (GIT_HASH.test(commit ?? '') ? `${REPO_URL}/${repo}/commit/${commit}` : null);
|
||||
@@ -0,0 +1,41 @@
|
||||
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||
import {commitUrl, fetchVersion, formatBuildTime} from './version.js';
|
||||
|
||||
describe('fetchVersion', () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it.each([
|
||||
['an error status', {ok: false, status: 401}],
|
||||
['a body without a commit', {ok: true, json: async () => ({status: 'ok'})}],
|
||||
])('is null for %s', async (_, response) => {
|
||||
vi.spyOn(globalThis, 'fetch').mockResolvedValue(response);
|
||||
|
||||
expect(await fetchVersion('http://core/api/version')).toBeNull();
|
||||
});
|
||||
|
||||
it('is null when the service does not answer', async () => {
|
||||
vi.spyOn(globalThis, 'fetch').mockRejectedValue(new TypeError('Failed to fetch'));
|
||||
|
||||
expect(await fetchVersion('http://core/api/version')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatBuildTime', () => {
|
||||
it('shows minutes, not seconds', () => {
|
||||
const text = formatBuildTime('2026-09-27T08:05:42Z', 'en');
|
||||
|
||||
expect(text).toMatch(/2026/);
|
||||
expect(text).not.toMatch(/42/);
|
||||
});
|
||||
|
||||
it.each([null, '', 'yesterday'])('is null for %s', (value) => {
|
||||
expect(formatBuildTime(value, 'en')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('commitUrl', () => {
|
||||
it('links only a git hash', () => {
|
||||
expect(commitUrl('ToBePlayed-Core', 'abc1234')).toBe('https://git.andreas-dahm.eu/Lobo_Corp/ToBePlayed-Core/commit/abc1234');
|
||||
expect(commitUrl('ToBePlayed-Core', 'dev')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user