Add release notes and build version metadata
This commit is contained in:
@@ -82,6 +82,15 @@ jobs:
|
||||
- name: Frontend build
|
||||
run: |
|
||||
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 run build
|
||||
working-directory: frontend
|
||||
@@ -109,8 +118,18 @@ jobs:
|
||||
id: meta
|
||||
run: |
|
||||
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 "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
|
||||
|
||||
- name: Verify production host layout
|
||||
@@ -140,6 +159,22 @@ jobs:
|
||||
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
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -244,9 +279,11 @@ jobs:
|
||||
css_asset="$(printf "%s" "$index" | grep -Eo '/assets/index-[^"]+\.css' | head -n 1)"
|
||||
test -n "$js_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
|
||||
echo "Frontend assets served: ${js_asset}, ${css_asset}"
|
||||
echo "Frontend version verified: ${{ steps.meta.outputs.build_version }}"
|
||||
|
||||
- name: Show production containers
|
||||
if: always()
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@lucide/vue": "^1.20.0",
|
||||
"@primeuix/themes": "^2.0.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"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>
|
||||
@@ -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 AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
||||
import AdminReleaseNotesCard from '../../components/admin/AdminReleaseNotesCard.vue'
|
||||
import AdminSeasonToolbar from '../../components/admin/AdminSeasonToolbar.vue'
|
||||
import AdminSettingsDatabaseCard from '../../components/admin/AdminSettingsDatabaseCard.vue'
|
||||
import AdminSettingsOverviewBoard from '../../components/admin/AdminSettingsOverviewBoard.vue'
|
||||
@@ -90,6 +91,8 @@ onBeforeUnmount(() => window.removeEventListener('beforeunload', handleBeforeUnl
|
||||
:content-completion="contentCompletion"
|
||||
/>
|
||||
|
||||
<AdminReleaseNotesCard />
|
||||
|
||||
<AdminOperationalSettingsCard
|
||||
v-model:demo-password="demoPasswordInput"
|
||||
v-model:twitch-client-secret="twitchClientSecretInput"
|
||||
|
||||
Reference in New Issue
Block a user