Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 520f917723 | |||
| d1dd77bc1e | |||
| 0be45b6513 |
@@ -38,33 +38,29 @@ jobs:
|
|||||||
FULL_SHA=$(git rev-parse HEAD)
|
FULL_SHA=$(git rev-parse HEAD)
|
||||||
IMAGE="git.kubebase.de/${{ gitea.repository }}"
|
IMAGE="git.kubebase.de/${{ gitea.repository }}"
|
||||||
DATE_STAMP=$(date -u +"%Y%m%d")
|
DATE_STAMP=$(date -u +"%Y%m%d")
|
||||||
|
VERSION="dev-$(date -u +"%Y%m%d-%H%M")"
|
||||||
|
TAGS="-t ${IMAGE}:latest"
|
||||||
if [ "${{ gitea.ref_type }}" = "tag" ]; then
|
if [ "${{ gitea.ref_type }}" = "tag" ]; then
|
||||||
VERSION="${{ gitea.ref_name }}"
|
VERSION="${{ gitea.ref_name }}"
|
||||||
VERSION_TAG="${{ gitea.ref_name }}"
|
TAGS="${TAGS} -t ${IMAGE}:${VERSION}"
|
||||||
else
|
|
||||||
VERSION="dev-$(date -u +"%Y%m%d-%H%M")"
|
|
||||||
VERSION_TAG="nightly-${DATE_STAMP}"
|
|
||||||
fi
|
fi
|
||||||
TAGS="-t ${IMAGE}:sha-${SHA} -t ${IMAGE}:latest -t ${IMAGE}:${VERSION_TAG}"
|
|
||||||
docker build --no-cache \
|
docker build --no-cache \
|
||||||
--build-arg COMMIT_SHA="$FULL_SHA" \
|
--build-arg COMMIT_SHA="$FULL_SHA" \
|
||||||
--build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
--build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
||||||
--build-arg VERSION="$VERSION" \
|
--build-arg VERSION="$VERSION" \
|
||||||
$TAGS .
|
$TAGS .
|
||||||
docker push "${IMAGE}:sha-${SHA}"
|
|
||||||
docker push "${IMAGE}:latest"
|
docker push "${IMAGE}:latest"
|
||||||
docker push "${IMAGE}:${VERSION_TAG}"
|
if [ "${{ gitea.ref_type }}" = "tag" ]; then
|
||||||
|
docker push "${IMAGE}:${VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Update k8s manifest in admin/apps
|
- name: Update k8s manifest in admin/apps
|
||||||
|
if: gitea.ref_type == 'tag'
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
SHA=$(git rev-parse --short HEAD)
|
SHA=$(git rev-parse --short HEAD)
|
||||||
if [ "${{ gitea.ref_type }}" = "tag" ]; then
|
|
||||||
NEWTAG="${{ gitea.ref_name }}"
|
NEWTAG="${{ gitea.ref_name }}"
|
||||||
else
|
|
||||||
NEWTAG="sha-${SHA}"
|
|
||||||
fi
|
|
||||||
git clone "https://admin:${GITEA_TOKEN}@git.kubebase.de/admin/apps.git" /tmp/apps
|
git clone "https://admin:${GITEA_TOKEN}@git.kubebase.de/admin/apps.git" /tmp/apps
|
||||||
cd /tmp/apps
|
cd /tmp/apps
|
||||||
cd apps/system/toolrate/overlays/k3s
|
cd apps/system/toolrate/overlays/k3s
|
||||||
|
|||||||
@@ -47,3 +47,6 @@ Thumbs.db
|
|||||||
# Replit
|
# Replit
|
||||||
.cache/
|
.cache/
|
||||||
.local/
|
.local/
|
||||||
|
|
||||||
|
# Generated release docs (produced by scripts/src/sync-release-docs.mjs during build)
|
||||||
|
/artifacts/toolrate/public/docs/
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --config vite.config.ts --host 0.0.0.0",
|
"dev": "node ../../scripts/src/sync-release-docs.mjs && vite --config vite.config.ts --host 0.0.0.0",
|
||||||
"build": "vite build --config vite.config.ts",
|
"build": "node ../../scripts/src/sync-release-docs.mjs && vite build --config vite.config.ts",
|
||||||
"serve": "vite preview --config vite.config.ts --host 0.0.0.0",
|
"serve": "vite preview --config vite.config.ts --host 0.0.0.0",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
@@ -54,11 +54,13 @@
|
|||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
"cmdk": "1.1.1",
|
"cmdk": "1.1.1",
|
||||||
"date-fns": "4.4.0",
|
"date-fns": "4.4.0",
|
||||||
|
"dompurify": "catalog:",
|
||||||
"embla-carousel-react": "8.6.0",
|
"embla-carousel-react": "8.6.0",
|
||||||
"framer-motion": "catalog:",
|
"framer-motion": "catalog:",
|
||||||
"i18next": "26.3.6",
|
"i18next": "26.3.6",
|
||||||
"input-otp": "1.4.2",
|
"input-otp": "1.4.2",
|
||||||
"lucide-react": "catalog:",
|
"lucide-react": "catalog:",
|
||||||
|
"marked": "catalog:",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-day-picker": "10.0.1",
|
"react-day-picker": "10.0.1",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import Trash from "@/pages/trash";
|
|||||||
import Compare from "@/pages/compare";
|
import Compare from "@/pages/compare";
|
||||||
import Watchlist from "@/pages/watchlist";
|
import Watchlist from "@/pages/watchlist";
|
||||||
import Login from "@/pages/login";
|
import Login from "@/pages/login";
|
||||||
|
import Docs from "@/pages/docs";
|
||||||
import NotFound from "@/pages/not-found";
|
import NotFound from "@/pages/not-found";
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
@@ -46,6 +47,8 @@ function Router() {
|
|||||||
<Route path="/admin" component={Admin} />
|
<Route path="/admin" component={Admin} />
|
||||||
<Route path="/admin/redundancy" component={Redundancy} />
|
<Route path="/admin/redundancy" component={Redundancy} />
|
||||||
<Route path="/trash" component={Trash} />
|
<Route path="/trash" component={Trash} />
|
||||||
|
<Route path="/docs" component={Docs} />
|
||||||
|
<Route path="/docs/:version" component={Docs} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ function buildCrumbs(location: string, t: TFunction): Crumb[] {
|
|||||||
crumbs.push({ label: t("compare.title") });
|
crumbs.push({ label: t("compare.title") });
|
||||||
} else if (location.startsWith("/analytics")) {
|
} else if (location.startsWith("/analytics")) {
|
||||||
crumbs.push({ label: t("nav.analytics") });
|
crumbs.push({ label: t("nav.analytics") });
|
||||||
|
} else if (location.startsWith("/docs")) {
|
||||||
|
crumbs.push({ href: "/docs", label: t("nav.docs") });
|
||||||
|
const match = location.match(/^\/docs\/(.+)$/);
|
||||||
|
if (match) crumbs.push({ label: match[1] });
|
||||||
} else if (location.startsWith("/login")) {
|
} else if (location.startsWith("/login")) {
|
||||||
crumbs.push({ label: t("auth.signIn") });
|
crumbs.push({ label: t("auth.signIn") });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link, useLocation } from "wouter";
|
import { Link, useLocation } from "wouter";
|
||||||
import { LayoutDashboard, Wrench, BarChart3, LogIn, LogOut, ShieldCheck, AlertTriangle, Search } from "lucide-react";
|
import { LayoutDashboard, Wrench, BarChart3, FileText, LogIn, LogOut, ShieldCheck, AlertTriangle, Search } from "lucide-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAuth } from "@/hooks/use-auth";
|
import { useAuth } from "@/hooks/use-auth";
|
||||||
import { useGetVersion, getGetVersionQueryKey } from "@workspace/api-client-react";
|
import { useGetVersion, getGetVersionQueryKey } from "@workspace/api-client-react";
|
||||||
@@ -39,6 +39,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
{ href: "/", label: t("nav.home"), icon: LayoutDashboard },
|
{ href: "/", label: t("nav.home"), icon: LayoutDashboard },
|
||||||
{ href: "/tools", label: t("nav.browseTools"), icon: Wrench },
|
{ href: "/tools", label: t("nav.browseTools"), icon: Wrench },
|
||||||
{ href: "/analytics", label: t("nav.analytics"), icon: BarChart3 },
|
{ href: "/analytics", label: t("nav.analytics"), icon: BarChart3 },
|
||||||
|
{ href: "/docs", label: t("nav.docs"), icon: FileText },
|
||||||
];
|
];
|
||||||
|
|
||||||
const adminLinks = [
|
const adminLinks = [
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"trash": "Papierkorb",
|
"trash": "Papierkorb",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"redundancy": "Redundanz",
|
"redundancy": "Redundanz",
|
||||||
|
"docs": "Doku",
|
||||||
"search": "Tools suchen…"
|
"search": "Tools suchen…"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
@@ -181,6 +182,12 @@
|
|||||||
"text": "Diese Seite existiert nicht.",
|
"text": "Diese Seite existiert nicht.",
|
||||||
"backHome": "Zurück zur Startseite"
|
"backHome": "Zurück zur Startseite"
|
||||||
},
|
},
|
||||||
|
"docs": {
|
||||||
|
"title": "Versionsdokumentation",
|
||||||
|
"subtitle": "Version-gebundene Dokumentation je Release — was ist neu, was hat sich geändert und was beim Upgrade zu beachten ist.",
|
||||||
|
"backToIndex": "Alle Releases",
|
||||||
|
"noDocs": "Noch keine Release-Dokumentation verfügbar."
|
||||||
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"navigate": "Navigation",
|
"navigate": "Navigation",
|
||||||
"recent": "Zuletzt besucht",
|
"recent": "Zuletzt besucht",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"trash": "Trash",
|
"trash": "Trash",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"redundancy": "Redundancy",
|
"redundancy": "Redundancy",
|
||||||
|
"docs": "Docs",
|
||||||
"search": "Search tools…"
|
"search": "Search tools…"
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
@@ -181,6 +182,12 @@
|
|||||||
"text": "This page doesn't exist.",
|
"text": "This page doesn't exist.",
|
||||||
"backHome": "Back to Home"
|
"backHome": "Back to Home"
|
||||||
},
|
},
|
||||||
|
"docs": {
|
||||||
|
"title": "Release Documentation",
|
||||||
|
"subtitle": "Version-bound documentation for each release — what's new, what changed, and what to know when upgrading.",
|
||||||
|
"backToIndex": "All releases",
|
||||||
|
"noDocs": "No release documentation available yet."
|
||||||
|
},
|
||||||
"command": {
|
"command": {
|
||||||
"navigate": "Navigate",
|
"navigate": "Navigate",
|
||||||
"recent": "Recent",
|
"recent": "Recent",
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link, useLocation, useParams } from "wouter";
|
||||||
|
import { marked } from "marked";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Layout } from "@/components/layout";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { FileText, CalendarDays, Tag, ArrowLeft, ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
|
type ReleaseDoc = {
|
||||||
|
version: string;
|
||||||
|
file: string;
|
||||||
|
title: string;
|
||||||
|
date: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DOCS_BASE = `${import.meta.env.BASE_URL.replace(/\/$/, "")}/docs`;
|
||||||
|
|
||||||
|
function fetchJson<T>(url: string): Promise<T> {
|
||||||
|
return fetch(url).then((res) => {
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
return res.json() as Promise<T>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function useReleases() {
|
||||||
|
const [releases, setReleases] = useState<ReleaseDoc[] | null>(null);
|
||||||
|
const [error, setError] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
fetchJson<ReleaseDoc[]>(`${DOCS_BASE}/index.json`)
|
||||||
|
.then((data) => {
|
||||||
|
if (!cancelled) setReleases(data);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setError(true);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { releases, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
function useReleaseMarkdown(file: string) {
|
||||||
|
const [html, setHtml] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
setHtml(null);
|
||||||
|
setError(false);
|
||||||
|
fetch(`${DOCS_BASE}/${encodeURIComponent(file)}`)
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then(async (md) => {
|
||||||
|
const rendered = await marked.parse(md, { async: false, gfm: true });
|
||||||
|
if (!cancelled) setHtml(DOMPurify.sanitize(rendered));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setError(true);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [file]);
|
||||||
|
|
||||||
|
return { html, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocsIndex() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { releases, error } = useReleases();
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="space-y-6 pb-10">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold tracking-tight mb-2">{t("docs.title")}</h1>
|
||||||
|
<p className="text-muted-foreground">{t("docs.subtitle")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-muted-foreground">{t("docs.noDocs")}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!releases && !error && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
|
<Skeleton key={i} className="h-20 w-full" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{releases && releases.length === 0 && (
|
||||||
|
<p className="text-sm text-muted-foreground">{t("docs.noDocs")}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{releases && releases.length > 0 && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{releases.map((release) => (
|
||||||
|
<Card key={release.version} className="hover:bg-accent/50 transition-colors">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setLocation(`/docs/${release.version}`)}
|
||||||
|
className="flex w-full items-center gap-4 p-4 text-left"
|
||||||
|
>
|
||||||
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
|
||||||
|
<FileText className="h-5 w-5 text-primary" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-semibold">{release.title}</span>
|
||||||
|
<Badge variant="secondary">{release.version}</Badge>
|
||||||
|
</div>
|
||||||
|
{release.date && (
|
||||||
|
<p className="mt-0.5 flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||||
|
<CalendarDays className="h-3.5 w-3.5" />
|
||||||
|
{new Date(`${release.date}T00:00:00`).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ExternalLink className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||||
|
</button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DocsDetail() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const params = useParams<{ version: string }>();
|
||||||
|
const version = params.version;
|
||||||
|
const { releases, error: indexError } = useReleases();
|
||||||
|
const release = releases?.find((r) => r.version === version);
|
||||||
|
const { html, error: mdError } = useReleaseMarkdown(release?.file ?? `${version}.md`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<div className="space-y-6 pb-10">
|
||||||
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
<Button variant="outline" size="sm" asChild>
|
||||||
|
<Link href="/docs">
|
||||||
|
<ArrowLeft className="h-4 w-4 mr-1" />
|
||||||
|
{t("docs.backToIndex")}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
{release && (
|
||||||
|
<a
|
||||||
|
href={`https://git.kubebase.de/admin/tool-evaluator/tags/${release.version}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
|
||||||
|
>
|
||||||
|
<Tag className="h-4 w-4" /> {release.version}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mdError || (release && !html) ? (
|
||||||
|
<p className="text-sm text-muted-foreground">{t("docs.noDocs")}</p>
|
||||||
|
) : !html ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Skeleton className="h-8 w-64" />
|
||||||
|
<Skeleton className="h-4 w-full" />
|
||||||
|
<Skeleton className="h-4 w-3/4" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="docs-prose prose dark:prose-invert max-w-none"
|
||||||
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Docs() {
|
||||||
|
const [location] = useLocation();
|
||||||
|
const match = location.match(/^\/docs\/(.+)$/);
|
||||||
|
return match ? <DocsDetail /> : <DocsIndex />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Release-Dokumentation
|
||||||
|
|
||||||
|
Jeder Release hat eine version-gebundene Dokumentation unter
|
||||||
|
`docs/releases/`. Die Dokumentation wird öffentlich in der App unter
|
||||||
|
`/docs` (Index) und `/docs/<version>` (Detail) angezeigt.
|
||||||
|
|
||||||
|
## Struktur
|
||||||
|
|
||||||
|
- `docs/releases/TEMPLATE.md` — Vorlage für neue Releases
|
||||||
|
- `docs/releases/vX.Y.Z.md` — Dokumentation pro Release (eine Datei je Version)
|
||||||
|
|
||||||
|
## Inhalt
|
||||||
|
|
||||||
|
Pro Release wird abgedeckt (kombiniert):
|
||||||
|
|
||||||
|
- **Changelog:** Neue Features, Fixes & Verbesserungen
|
||||||
|
- **API-Änderungen:** Neue/geänderte/entfernte Endpunkte (Delta zur Vorversion)
|
||||||
|
- **Betrieb / Upgrade:** Env-Vars, DB-Migrationen, Breaking Changes
|
||||||
|
|
||||||
|
## Workflow beim Release
|
||||||
|
|
||||||
|
1. **Version taggen** wie bisher (`git tag vX.Y.Z`, CI baut und deployed).
|
||||||
|
2. **`docs/releases/vX.Y.Z.md` anlegen** — Vorlage aus
|
||||||
|
`TEMPLATE.md` kopieren. Entwurf aus der Git-Historie seit dem letzten Tag
|
||||||
|
ableiten:
|
||||||
|
```sh
|
||||||
|
git log --oneline vX.Y.Z-1..vX.Y.Z
|
||||||
|
```
|
||||||
|
(Funktions-/Fix-Commits in die passenden Abschnitte übernehmen, API-Delta
|
||||||
|
anhand `lib/api-spec/openapi.yaml` prüfen.)
|
||||||
|
3. **Committen & pushen.** Der Sync-Schritt (`scripts/sync-release-docs.mjs`)
|
||||||
|
kopiert die Markdown-Dateien beim Frontend-Build automatisch nach
|
||||||
|
`artifacts/toolrate/public/docs/` und generiert `index.json`. Dadurch sind
|
||||||
|
die Releases im Deployment als `/docs/...` verfügbar.
|
||||||
|
|
||||||
|
> Hinweis: `index.json` und die kopierten Dateien unter
|
||||||
|
> `artifacts/toolrate/public/docs/` sind Build-Artefakte und werden bei jedem
|
||||||
|
> Build neu generiert — nicht von Hand bearbeiten.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# vX.Y.Z — Release Notes
|
||||||
|
|
||||||
|
> Template für neue Release-Dokumentationen. Eine Kopie pro Release unter
|
||||||
|
> `docs/releases/vX.Y.Z.md` anlegen, Platzhalter ersetzen, Abschnitte die
|
||||||
|
> nicht zutreffen entfernen. Die Seite wird unter `/docs/vX.Y.Z` in der App
|
||||||
|
> angezeigt.
|
||||||
|
|
||||||
|
**Datum:** YYYY-MM-DD · **Tag:** [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/tags/vX.Y.Z)
|
||||||
|
|
||||||
|
## Neue Features
|
||||||
|
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Fixes & Verbesserungen
|
||||||
|
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## API-Änderungen
|
||||||
|
|
||||||
|
- ... (neue/geänderte/entfernte Endpunkte — siehe `lib/api-spec/openapi.yaml`)
|
||||||
|
|
||||||
|
## Betrieb / Upgrade
|
||||||
|
|
||||||
|
- **Env-Vars:** ... (neu/geändert/entfernt)
|
||||||
|
- **Migration:** ... (Datenbank-/Schema-Änderungen, Schritte für den Betreiber)
|
||||||
|
- **Breaking Changes:** ... (nur wenn vorhanden)
|
||||||
|
|
||||||
|
## Bekannte Einschränkungen
|
||||||
|
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Commit: [`<short-sha>`](https://git.kubebase.de/admin/tool-evaluator/commit/<short-sha>)
|
||||||
|
- Tag: [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/tags/vX.Y.Z)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# v0.6.0 — Release Notes
|
||||||
|
|
||||||
|
**Datum:** 2026-08-03 · **Tag:** [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.6.0)
|
||||||
|
|
||||||
|
## Neue Features
|
||||||
|
|
||||||
|
- Vollständige Modernisierung aller Abhängigkeiten auf die aktuellen Hauptversionen
|
||||||
|
(TypeScript 7, Vite 8, React 19.2, openid-client 6, zod 4, recharts 3, react-day-picker 10).
|
||||||
|
|
||||||
|
## Fixes & Verbesserungen
|
||||||
|
|
||||||
|
- CI-Build durch `allowBuilds`-Konfiguration für pnpm 11 repariert
|
||||||
|
(Build-Scripts für esbuild & Co. werden nicht mehr blockiert).
|
||||||
|
- Image-Tagging vereinfacht: nur noch `latest` und `v*`-Tags, keine `nightly-*`/`sha-*`-Tags.
|
||||||
|
- Alle Dependencies exakt gepinnt; automatische Updates via Renovate vorbereitet
|
||||||
|
(`renovate.json`, `docs/dependency-policy.md`).
|
||||||
|
|
||||||
|
## API-Änderungen
|
||||||
|
|
||||||
|
- Keine Breaking Changes an der API. openid-client intern auf v6 migriert
|
||||||
|
(auth-Fluss verhält sich identisch).
|
||||||
|
|
||||||
|
## Betrieb / Upgrade
|
||||||
|
|
||||||
|
- **Env-Vars:** unverändert. Node-Image auf `node:24.18.1-alpine` gepinnt.
|
||||||
|
- **Migration:** keine Datenbank-Migration erforderlich.
|
||||||
|
- **Breaking Changes:** keine.
|
||||||
|
|
||||||
|
## Bekannte Einschränkungen
|
||||||
|
|
||||||
|
- `typedoc` (indirekte orval-Abhängigkeit) zeigt eine Peer-Dependency-Warnung
|
||||||
|
(erwartet TypeScript 5.x/6.x, installiert ist 7.x) — harmlos für Build & Laufzeit.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Commit: [`2f66fff`](https://git.kubebase.de/admin/tool-evaluator/commit/2f66fff)
|
||||||
|
- Tag: [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.6.0)
|
||||||
Generated
+58
-15
@@ -42,6 +42,9 @@ catalogs:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: 2.1.1
|
specifier: 2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
dompurify:
|
||||||
|
specifier: 3.4.12
|
||||||
|
version: 3.4.12
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: 0.45.2
|
specifier: 0.45.2
|
||||||
version: 0.45.2
|
version: 0.45.2
|
||||||
@@ -51,6 +54,9 @@ catalogs:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: 1.28.0
|
specifier: 1.28.0
|
||||||
version: 1.28.0
|
version: 1.28.0
|
||||||
|
marked:
|
||||||
|
specifier: 18.0.7
|
||||||
|
version: 18.0.7
|
||||||
react:
|
react:
|
||||||
specifier: 19.2.8
|
specifier: 19.2.8
|
||||||
version: 19.2.8
|
version: 19.2.8
|
||||||
@@ -64,8 +70,8 @@ catalogs:
|
|||||||
specifier: 4.3.3
|
specifier: 4.3.3
|
||||||
version: 4.3.3
|
version: 4.3.3
|
||||||
tsx:
|
tsx:
|
||||||
specifier: 4.23.1
|
specifier: 4.23.4
|
||||||
version: 4.23.1
|
version: 4.23.4
|
||||||
vite:
|
vite:
|
||||||
specifier: 8.2.0
|
specifier: 8.2.0
|
||||||
version: 8.2.0
|
version: 8.2.0
|
||||||
@@ -359,7 +365,7 @@ importers:
|
|||||||
version: 0.0.6
|
version: 0.0.6
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))
|
version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 26.1.2
|
version: 26.1.2
|
||||||
@@ -371,7 +377,7 @@ importers:
|
|||||||
version: 19.2.4(@types/react@19.2.18)
|
version: 19.2.4(@types/react@19.2.18)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))
|
version: 6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))
|
||||||
chokidar:
|
chokidar:
|
||||||
specifier: 5.0.0
|
specifier: 5.0.0
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
@@ -446,7 +452,7 @@ importers:
|
|||||||
version: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
vite:
|
vite:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4)
|
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4)
|
||||||
zod:
|
zod:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
@@ -551,7 +557,7 @@ importers:
|
|||||||
version: 0.5.20(tailwindcss@4.3.3)
|
version: 0.5.20(tailwindcss@4.3.3)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))
|
version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.101.4(react@19.2.8)
|
version: 5.101.4(react@19.2.8)
|
||||||
@@ -569,7 +575,7 @@ importers:
|
|||||||
version: 19.2.4(@types/react@19.2.18)
|
version: 19.2.4(@types/react@19.2.18)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))
|
version: 6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))
|
||||||
'@workspace/api-client-react':
|
'@workspace/api-client-react':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../lib/api-client-react
|
version: link:../../lib/api-client-react
|
||||||
@@ -585,6 +591,9 @@ importers:
|
|||||||
date-fns:
|
date-fns:
|
||||||
specifier: 4.4.0
|
specifier: 4.4.0
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
|
dompurify:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 3.4.12
|
||||||
embla-carousel-react:
|
embla-carousel-react:
|
||||||
specifier: 8.6.0
|
specifier: 8.6.0
|
||||||
version: 8.6.0(react@19.2.8)
|
version: 8.6.0(react@19.2.8)
|
||||||
@@ -600,6 +609,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.28.0(react@19.2.8)
|
version: 1.28.0(react@19.2.8)
|
||||||
|
marked:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 18.0.7
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: 0.4.6
|
specifier: 0.4.6
|
||||||
version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
@@ -647,7 +659,7 @@ importers:
|
|||||||
version: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
vite:
|
vite:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4)
|
version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4)
|
||||||
wouter:
|
wouter:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.10.0(react@19.2.8)
|
version: 3.10.0(react@19.2.8)
|
||||||
@@ -705,7 +717,7 @@ importers:
|
|||||||
version: 26.1.2
|
version: 26.1.2
|
||||||
tsx:
|
tsx:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.23.1
|
version: 4.23.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -2101,6 +2113,9 @@ packages:
|
|||||||
'@types/serve-static@2.2.0':
|
'@types/serve-static@2.2.0':
|
||||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||||
|
|
||||||
|
'@types/trusted-types@2.0.7':
|
||||||
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
|
||||||
'@types/unist@3.0.3':
|
'@types/unist@3.0.3':
|
||||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
@@ -2470,6 +2485,9 @@ packages:
|
|||||||
detect-node-es@1.1.0:
|
detect-node-es@1.1.0:
|
||||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
|
||||||
|
dompurify@3.4.12:
|
||||||
|
resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==}
|
||||||
|
|
||||||
drizzle-kit@0.31.10:
|
drizzle-kit@0.31.10:
|
||||||
resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
|
resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -2959,6 +2977,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Lf8ajvVNdRpzSNB4VegxNy7gjs8gU35l4b4+ET49LrQC5PKYwLZ72u60LeJ9gv3qiaesuYjJWCyVeQmv/QWKQw==}
|
resolution: {integrity: sha512-Lf8ajvVNdRpzSNB4VegxNy7gjs8gU35l4b4+ET49LrQC5PKYwLZ72u60LeJ9gv3qiaesuYjJWCyVeQmv/QWKQw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
marked@18.0.7:
|
||||||
|
resolution: {integrity: sha512-iDVQ5ldaiKXn6b2JroX5kgRfmwgqolW7NpaEzTl1k/2Zh1njIEN9yniyLV/mOvWwtsE8OGgkjsCYvijuPk1dtA==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3557,6 +3580,11 @@ packages:
|
|||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
tsx@4.23.4:
|
||||||
|
resolution: {integrity: sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tw-animate-css@1.4.0:
|
tw-animate-css@1.4.0:
|
||||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
@@ -5002,12 +5030,12 @@ snapshots:
|
|||||||
postcss-selector-parser: 6.0.10
|
postcss-selector-parser: 6.0.10
|
||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
|
|
||||||
'@tailwindcss/vite@4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))':
|
'@tailwindcss/vite@4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.3.3
|
'@tailwindcss/node': 4.3.3
|
||||||
'@tailwindcss/oxide': 4.3.3
|
'@tailwindcss/oxide': 4.3.3
|
||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4)
|
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4)
|
||||||
|
|
||||||
'@tanstack/query-core@5.101.4': {}
|
'@tanstack/query-core@5.101.4': {}
|
||||||
|
|
||||||
@@ -5140,6 +5168,9 @@ snapshots:
|
|||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/node': 25.6.2
|
'@types/node': 25.6.2
|
||||||
|
|
||||||
|
'@types/trusted-types@2.0.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
'@types/use-sync-external-store@0.0.6': {}
|
'@types/use-sync-external-store@0.0.6': {}
|
||||||
@@ -5204,10 +5235,10 @@ snapshots:
|
|||||||
'@typescript/typescript-win32-x64@7.0.2':
|
'@typescript/typescript-win32-x64@7.0.2':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4))':
|
'@vitejs/plugin-react@6.0.5(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.1
|
'@rolldown/pluginutils': 1.0.1
|
||||||
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4)
|
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4)
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5407,6 +5438,10 @@ snapshots:
|
|||||||
|
|
||||||
detect-node-es@1.1.0: {}
|
detect-node-es@1.1.0: {}
|
||||||
|
|
||||||
|
dompurify@3.4.12:
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/trusted-types': 2.0.7
|
||||||
|
|
||||||
drizzle-kit@0.31.10:
|
drizzle-kit@0.31.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@drizzle-team/brocli': 0.10.2
|
'@drizzle-team/brocli': 0.10.2
|
||||||
@@ -5802,6 +5837,8 @@ snapshots:
|
|||||||
punycode.js: 2.3.1
|
punycode.js: 2.3.1
|
||||||
uc.micro: 3.0.0
|
uc.micro: 3.0.0
|
||||||
|
|
||||||
|
marked@18.0.7: {}
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
mdurl@2.1.0: {}
|
mdurl@2.1.0: {}
|
||||||
@@ -6402,6 +6439,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
tsx@4.23.4:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.28.1
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
tw-animate-css@1.4.0: {}
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
type-is@2.0.1:
|
type-is@2.0.1:
|
||||||
@@ -6521,7 +6564,7 @@ snapshots:
|
|||||||
d3-time: 3.1.0
|
d3-time: 3.1.0
|
||||||
d3-timer: 3.0.1
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)(yaml@2.8.4):
|
vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)(yaml@2.8.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
lightningcss: 1.33.0
|
lightningcss: 1.33.0
|
||||||
picomatch: 4.0.5
|
picomatch: 4.0.5
|
||||||
@@ -6533,7 +6576,7 @@ snapshots:
|
|||||||
esbuild: 0.28.1
|
esbuild: 0.28.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.7.0
|
jiti: 2.7.0
|
||||||
tsx: 4.23.1
|
tsx: 4.23.4
|
||||||
yaml: 2.8.4
|
yaml: 2.8.4
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
|
|||||||
+3
-1
@@ -61,14 +61,16 @@ catalog:
|
|||||||
'@vitejs/plugin-react': 6.0.5
|
'@vitejs/plugin-react': 6.0.5
|
||||||
class-variance-authority: 0.7.1
|
class-variance-authority: 0.7.1
|
||||||
clsx: 2.1.1
|
clsx: 2.1.1
|
||||||
|
dompurify: 3.4.12
|
||||||
drizzle-orm: 0.45.2
|
drizzle-orm: 0.45.2
|
||||||
framer-motion: 12.43.0
|
framer-motion: 12.43.0
|
||||||
lucide-react: 1.28.0
|
lucide-react: 1.28.0
|
||||||
|
marked: 18.0.7
|
||||||
react: 19.2.8
|
react: 19.2.8
|
||||||
react-dom: 19.2.8
|
react-dom: 19.2.8
|
||||||
tailwind-merge: 3.6.0
|
tailwind-merge: 3.6.0
|
||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
tsx: 4.23.1
|
tsx: 4.23.4
|
||||||
vite: 8.2.0
|
vite: 8.2.0
|
||||||
wouter: 3.10.0
|
wouter: 3.10.0
|
||||||
zod: 4.4.3
|
zod: 4.4.3
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copies docs/releases/*.md into the toolrate frontend's static public dir
|
||||||
|
// and generates index.json (version list) so the app can render /docs.
|
||||||
|
//
|
||||||
|
// Run before `vite build` (Vite copies public/ -> dist/public verbatim), and
|
||||||
|
// before `vite dev` so the docs are available locally too.
|
||||||
|
//
|
||||||
|
// Usage: node scripts/src/sync-release-docs.mjs
|
||||||
|
|
||||||
|
import { readdir, readFile, copyFile, mkdir, rm, writeFile } from "node:fs/promises";
|
||||||
|
import { resolve, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
||||||
|
const sourceDir = resolve(root, "docs/releases");
|
||||||
|
const targetDir = resolve(root, "artifacts/toolrate/public/docs");
|
||||||
|
|
||||||
|
const isReleaseFile = (name) => /^v\d+\.\d+\.\d+\.md$/.test(name);
|
||||||
|
|
||||||
|
function parseVersion(name) {
|
||||||
|
return name.replace(/\.md$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic-ish comparison: v0.6.0 > v0.5.0 > v0.4.2
|
||||||
|
function cmp(a, b) {
|
||||||
|
const pa = parseVersion(a).slice(1).split(".").map(Number);
|
||||||
|
const pb = parseVersion(b).slice(1).split(".").map(Number);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTitle(content) {
|
||||||
|
const m = content.match(/^#\s+(.+)$/m);
|
||||||
|
return m ? m[1].trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractDate(content) {
|
||||||
|
// e.g. "**Datum:** 2026-08-03" or "**Date:** 2026-08-03" in the header block.
|
||||||
|
// Match the first ISO date that follows a Datum/Date label, tolerating
|
||||||
|
// bold markers/colons around it.
|
||||||
|
const m = content.match(/(?:Datum|Date)[^\d\n]{0,20}(\d{4}-\d{2}-\d{2})/);
|
||||||
|
return m ? m[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let names;
|
||||||
|
try {
|
||||||
|
names = (await readdir(sourceDir)).filter(isReleaseFile);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code === "ENOENT") {
|
||||||
|
console.warn(`[sync-release-docs] ${sourceDir} not found; nothing to sync`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (names.length === 0) {
|
||||||
|
console.warn("[sync-release-docs] no release docs found; clearing target dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
names.sort(cmp).reverse();
|
||||||
|
|
||||||
|
await rm(targetDir, { recursive: true, force: true });
|
||||||
|
await mkdir(targetDir, { recursive: true });
|
||||||
|
|
||||||
|
const index = [];
|
||||||
|
for (const name of names) {
|
||||||
|
const content = await readFile(join(sourceDir, name), "utf8");
|
||||||
|
await copyFile(join(sourceDir, name), join(targetDir, name));
|
||||||
|
index.push({
|
||||||
|
version: parseVersion(name),
|
||||||
|
file: name,
|
||||||
|
title: extractTitle(content) ?? parseVersion(name),
|
||||||
|
date: extractDate(content) ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(join(targetDir, "index.json"), JSON.stringify(index, null, 2));
|
||||||
|
|
||||||
|
console.log(`[sync-release-docs] synced ${names.length} release doc(s) -> ${targetDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("[sync-release-docs] failed:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user