Add release notes and build version metadata
CI - Build & Verify / Build, Typecheck & Hygiene (push) Has been cancelled
CI - Build & Verify / Deploy to award.noveria.net (push) Has been cancelled

This commit is contained in:
AzuTear
2026-06-26 22:00:39 +02:00
parent 9393d433fa
commit f225addfe8
6 changed files with 289 additions and 4 deletions
+38 -1
View File
@@ -82,6 +82,15 @@ jobs:
- name: Frontend build - name: Frontend build
run: | run: |
set -euo pipefail set -euo pipefail
base_version="$(node -p "require('./package.json').version")"
run_number="${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}}"
if [ "$run_number" != "0" ]; then
export VITE_BUILD_VERSION="${base_version}+build.${run_number}"
else
export VITE_BUILD_VERSION="${base_version}+local"
fi
export VITE_BUILD_DATE="$(date -u +%Y-%m-%d)"
echo "Building frontend version ${VITE_BUILD_VERSION}"
npm ci npm ci
npm run build npm run build
working-directory: frontend working-directory: frontend
@@ -109,8 +118,18 @@ jobs:
id: meta id: meta
run: | run: |
set -euo pipefail set -euo pipefail
version="$(node -p "require('./frontend/package.json').version")"
run_number="${GITHUB_RUN_NUMBER:-${GITEA_RUN_NUMBER:-0}}"
if [ "$run_number" != "0" ]; then
build_version="${version}+build.${run_number}"
else
build_version="${version}+local"
fi
echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT" echo "sha=$(git rev-parse HEAD)" >> "$GITEA_OUTPUT"
echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT" echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITEA_OUTPUT"
echo "version=$version" >> "$GITEA_OUTPUT"
echo "build_version=$build_version" >> "$GITEA_OUTPUT"
echo "Deploying version ${build_version}"
git log -1 --oneline git log -1 --oneline
- name: Verify production host layout - name: Verify production host layout
@@ -140,6 +159,22 @@ jobs:
chown -R "$dest_owner" /dest chown -R "$dest_owner" /dest
' '
- name: Write frontend build metadata
run: |
set -euo pipefail
build_date="$(date -u +%Y-%m-%d)"
docker run --rm \
-e BUILD_VERSION="${{ steps.meta.outputs.build_version }}" \
-e BUILD_DATE="${build_date}" \
-v "${DEPLOY_APP_PATH}:/app" \
alpine:latest \
sh -lc '
test -d /app/frontend
printf "VITE_BUILD_VERSION=%s\nVITE_BUILD_DATE=%s\n" "$BUILD_VERSION" "$BUILD_DATE" > /app/frontend/.env.production
echo "Frontend build metadata:"
cat /app/frontend/.env.production
'
- name: Build Docker images - name: Build Docker images
run: | run: |
set -euo pipefail set -euo pipefail
@@ -244,9 +279,11 @@ jobs:
css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)" css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)"
test -n "$js_asset" test -n "$js_asset"
test -n "$css_asset" test -n "$css_asset"
curl -fsS --max-time 20 "${LIVE_URL}${js_asset}" >/dev/null js_body="$(curl -fsS --max-time 20 "${LIVE_URL}${js_asset}")"
printf "%s" "$js_body" | grep -Fq "${{ steps.meta.outputs.build_version }}"
curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null curl -fsS --max-time 20 "${LIVE_URL}${css_asset}" >/dev/null
echo "Frontend assets served: ${js_asset}, ${css_asset}" echo "Frontend assets served: ${js_asset}, ${css_asset}"
echo "Frontend version verified: ${{ steps.meta.outputs.build_version }}"
- name: Show production containers - name: Show production containers
if: always() if: always()
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@lucide/vue": "^1.20.0", "@lucide/vue": "^1.20.0",
"@primeuix/themes": "^2.0.3", "@primeuix/themes": "^2.0.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "frontend", "name": "frontend",
"private": true, "private": true,
"version": "0.0.0", "version": "0.1.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -0,0 +1,157 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { BookOpenText, CheckCircle2, History, Info, Rocket, ShieldCheck, Sparkles, Tag } from '@lucide/vue'
import { buildDateLabel, buildVersion, releaseNotes, semanticVersion, versionRules, type ReleaseChangeType } from '../../lib/releaseNotes'
import Button from '../ui/Button.vue'
import Card from '../ui/Card.vue'
import Modal from '../ui/Modal.vue'
const changelogOpen = ref(false)
const featureCount = computed(() => releaseNotes.filter((note) => note.type === 'feature').length)
const fixCount = computed(() => releaseNotes.filter((note) => note.type === 'fix').length)
function changeTypeLabel(type: ReleaseChangeType) {
if (type === 'major') return 'Major'
if (type === 'feature') return 'Feature'
return 'Fix'
}
function changeTypeClasses(type: ReleaseChangeType) {
if (type === 'major') {
return {
badge: 'bg-rose-50 text-rose-700',
icon: 'bg-rose-100 text-rose-700',
panel: 'border-rose-100 bg-rose-50/45',
}
}
if (type === 'feature') {
return {
badge: 'bg-violet-50 text-violet-700',
icon: 'bg-violet-100 text-violet-700',
panel: 'border-violet-100 bg-violet-50/50',
}
}
return {
badge: 'bg-emerald-50 text-emerald-700',
icon: 'bg-emerald-100 text-emerald-700',
panel: 'border-emerald-100 bg-emerald-50/45',
}
}
</script>
<template>
<Card class="overflow-hidden">
<div class="border-b border-violet-100 bg-gradient-to-br from-white via-violet-50/70 to-emerald-50/55 p-6">
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div class="flex items-start gap-3">
<div class="grid h-12 w-12 shrink-0 place-items-center rounded-2xl bg-violet-100 text-violet-700">
<Rocket class="h-5 w-5" />
</div>
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Build Version</p>
<h2 class="mt-1 text-xl font-bold text-slate-900">Version {{ buildVersion }}</h2>
<p class="mt-2 max-w-3xl text-sm leading-6 text-slate-500">
Feature-Release vom {{ buildDateLabel }} mit Twitch Login, Team-Verknuepfung, Admin-UX-Polish und Backend-Haertung.
</p>
</div>
</div>
<Button type="button" variant="ghost" class="gap-2 rounded-2xl px-5" @click="changelogOpen = true">
<BookOpenText class="h-4 w-4" />
Changelog ansehen
</Button>
</div>
</div>
<div class="grid gap-3 p-5 md:grid-cols-3">
<div class="rounded-2xl border border-violet-100 bg-violet-50/55 px-4 py-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">Release-Typ</p>
<strong class="mt-1 block text-lg text-violet-700">Feature</strong>
<p class="mt-1 text-xs leading-5 text-slate-600">Nach deiner Regel: 0.x.0.</p>
</div>
<div class="rounded-2xl border border-emerald-100 bg-emerald-50/55 px-4 py-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">Neue Features</p>
<strong class="mt-1 block text-lg text-emerald-700">{{ featureCount }}</strong>
<p class="mt-1 text-xs leading-5 text-slate-600">Sichtbare neue Funktionen und bessere Workflows.</p>
</div>
<div class="rounded-2xl border border-amber-100 bg-amber-50/65 px-4 py-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-500">Fixes & Polish</p>
<strong class="mt-1 block text-lg text-amber-700">{{ fixCount }}</strong>
<p class="mt-1 text-xs leading-5 text-slate-600">Stabilitaet, UI-Feedback und Backend-Hygiene.</p>
</div>
</div>
</Card>
<Modal
:open="changelogOpen"
title="Changelog"
:subtitle="`Build ${buildVersion} · ${buildDateLabel}`"
size="lg"
@close="changelogOpen = false"
>
<div class="space-y-6">
<section>
<div class="flex items-center gap-2">
<Tag class="h-4 w-4 text-violet-600" />
<h3 class="text-sm font-bold text-slate-900">Versionsschema</h3>
</div>
<div class="mt-3 grid gap-3 md:grid-cols-3">
<div
v-for="rule in versionRules"
:key="rule.pattern"
class="rounded-2xl border border-violet-100 bg-white px-4 py-3"
>
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-violet-500">{{ rule.pattern }}</p>
<strong class="mt-1 block text-sm text-slate-900">{{ rule.label }}</strong>
<p class="mt-1 text-xs leading-5 text-slate-500">{{ rule.description }}</p>
</div>
</div>
</section>
<section>
<div class="flex items-center gap-2">
<History class="h-4 w-4 text-violet-600" />
<h3 class="text-sm font-bold text-slate-900">Was neu ist</h3>
</div>
<div class="mt-3 space-y-3">
<article
v-for="note in releaseNotes"
:key="note.title"
class="rounded-2xl border p-4"
:class="changeTypeClasses(note.type).panel"
>
<div class="flex items-start gap-3">
<div class="grid h-9 w-9 shrink-0 place-items-center rounded-xl" :class="changeTypeClasses(note.type).icon">
<Sparkles v-if="note.type === 'feature'" class="h-4 w-4" />
<ShieldCheck v-else-if="note.type === 'major'" class="h-4 w-4" />
<CheckCircle2 v-else class="h-4 w-4" />
</div>
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<h4 class="text-sm font-bold text-slate-900">{{ note.title }}</h4>
<span class="rounded-full px-2.5 py-1 text-[11px] font-semibold" :class="changeTypeClasses(note.type).badge">
{{ changeTypeLabel(note.type) }}
</span>
</div>
<p class="mt-1 text-sm leading-6 text-slate-600">{{ note.description }}</p>
</div>
</div>
</article>
</div>
</section>
<div class="flex items-start gap-3 rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm leading-6 text-violet-900">
<Info class="mt-0.5 h-4 w-4 shrink-0" />
<p>Diese Version ist als <strong>{{ semanticVersion }}</strong> gesetzt, weil der Schwerpunkt auf neuen Features und groesseren UX-Verbesserungen liegt. Die Pipeline darf Build-Metadaten ergaenzen, aber nicht selbst Major, Feature oder Fix entscheiden.</p>
</div>
</div>
<template #footer>
<Button type="button" variant="ghost" @click="changelogOpen = false">Schließen</Button>
</template>
</Modal>
</template>
+88
View File
@@ -0,0 +1,88 @@
export type ReleaseChangeType = 'major' | 'feature' | 'fix'
export interface ReleaseNoteItem {
type: ReleaseChangeType
title: string
description: string
}
export interface VersionRule {
pattern: string
label: string
description: string
}
export const semanticVersion = '0.1.0'
export const buildVersion = import.meta.env.VITE_BUILD_VERSION?.trim() || semanticVersion
export const buildDateLabel = import.meta.env.VITE_BUILD_DATE?.trim() || '26.06.2026'
export const versionRules: VersionRule[] = [
{
pattern: 'x.0.0',
label: 'Major Changes',
description: 'Grosse Umbauten, Breaking Changes oder Releases, die sich wie ein neues Kapitel anfuehlen.',
},
{
pattern: '0.x.0',
label: 'Neue Features',
description: 'Neue Funktionen, groessere UI-Flows oder sichtbare Produktverbesserungen.',
},
{
pattern: '0.0.x',
label: 'Minor & Bugfixes',
description: 'Kleine Verbesserungen, Bugfixes, Copy-Polish und unkritische Feinschliffe.',
},
]
export const releaseNotes: ReleaseNoteItem[] = [
{
type: 'feature',
title: 'Twitch Login ist live',
description: 'Login und Account-Verknuepfung laufen jetzt ueber den offiziellen Twitch OAuth Flow.',
},
{
type: 'feature',
title: 'Team-Accounts koennen Twitch verbinden',
description: 'Teammitglieder koennen ihren Account mit Twitch verknuepfen, wieder loesen und danach sauber ueber Twitch einsteigen.',
},
{
type: 'feature',
title: 'Team-Status aktualisiert sich automatisch',
description: 'In der Teamverwaltung siehst du regelmaessig, wer online ist. Die Ansicht aktualisiert sich alle 30 Sekunden.',
},
{
type: 'feature',
title: 'Moderation ist schneller bedienbar',
description: 'Reviewed Nominierungen und Risiko-Queues lassen sich angenehmer mit Pfeiltasten und Tastatur bedienen.',
},
{
type: 'feature',
title: 'Admin-Bereiche wurden aufgeraeumt',
description: 'Jahre, Landingpage, Risiko, Team und Analytics sind kompakter, klarer gruppiert und weniger erschlagend.',
},
{
type: 'feature',
title: 'User-Profil wurde repariert und ueberarbeitet',
description: 'Das Profil ist wieder ein echter Teil des Flows, inklusive Twitch-Verknuepfung und sauberem Account-Status.',
},
{
type: 'feature',
title: 'Besseres Feedback beim Bearbeiten',
description: 'Buttons haben Animationen, Speichern/Bearbeiten gibt sichtbarere Rueckmeldung und Modals fuehlen sich stabiler an.',
},
{
type: 'fix',
title: 'Dropdowns sehen wieder korrekt aus',
description: 'Das Dropdown-Design wurde stabilisiert und passt nun besser zum restlichen Admin-UI.',
},
{
type: 'fix',
title: 'Backend, Auth und Sicherheit wurden gehaertet',
description: 'Sessions, Team-Rechte, Audit-Logs, Public Writes, Vote-Eindeutigkeit und Runtime-Settings wurden robuster gemacht.',
},
{
type: 'fix',
title: 'Datenbank- und Deploy-Sicherheit verbessert',
description: 'Healthchecks, Migrationen, Seed-Verhalten und produktionsnahe Konfiguration sind klarer sichtbar und weniger fehleranfaellig.',
},
]
@@ -5,6 +5,7 @@ import { onBeforeRouteLeave } from 'vue-router'
import AdminOperationalSettingsCard from '../../components/admin/AdminOperationalSettingsCard.vue' import AdminOperationalSettingsCard from '../../components/admin/AdminOperationalSettingsCard.vue'
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue' import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
import AdminReleaseNotesCard from '../../components/admin/AdminReleaseNotesCard.vue'
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue' import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
import AdminSettingsDatabaseCard from '../../components/admin/AdminSettingsDatabaseCard.vue' import AdminSettingsDatabaseCard from '../../components/admin/AdminSettingsDatabaseCard.vue'
import AdminSettingsOverviewBoard from '../../components/admin/AdminSettingsOverviewBoard.vue' import AdminSettingsOverviewBoard from '../../components/admin/AdminSettingsOverviewBoard.vue'
@@ -90,6 +91,8 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
:content-completion="contentCompletion" :content-completion="contentCompletion"
/> />
<AdminReleaseNotesCard />
<AdminOperationalSettingsCard <AdminOperationalSettingsCard
v-model:demo-password="demoPasswordInput" v-model:demo-password="demoPasswordInput"
v-model:twitch-client-secret="twitchClientSecretInput" v-model:twitch-client-secret="twitchClientSecretInput"