feat: live Gitea changelog, Gitea sidebar link, release title/body extraction
All checks were successful
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 43s
Release / Docker / caddy (push) Successful in 41s
Release / Docker / backend (push) Successful in 2m23s
Release / Docker / runner (push) Successful in 2m48s
Release / Docker / ui (push) Successful in 2m5s
Release / Gitea Release (push) Successful in 21s

- Admin changelog page now fetches releases live from the public Gitea
  API (https://gitea.kalekber.cc) instead of a baked releases.json file
- Remove /static/releases.json from ui/.gitignore (no longer generated)
- Add Gitea to admin sidebar external tools (admin_nav_gitea i18n key,
  all 5 locales, compiled paraglide JS force-added)
- release.yaml: remove 'Fetch releases from Gitea API' step; extract
  release title and body from the tagged commit message for Gitea releases
This commit is contained in:
Admin
2026-04-03 23:20:30 +05:00
parent e862135775
commit 9806f0d894
11 changed files with 86 additions and 27 deletions

View File

@@ -190,17 +190,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Fetch releases from Gitea API
run: |
set -euo pipefail
RESPONSE=$(curl -sfL \
-H "Accept: application/json" \
"http://gitea.kalekber.cc/api/v1/repos/kamil/libnovel/releases?limit=50&page=1")
# Validate JSON before writing — fails hard if response is not a JSON array
COUNT=$(echo "$RESPONSE" | jq 'if type == "array" then length else error("expected array, got \(type)") end')
echo "$RESPONSE" > ui/static/releases.json
echo "Fetched $COUNT releases"
- uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
@@ -278,8 +267,25 @@ jobs:
with:
fetch-depth: 0
- name: Extract release notes from tag commit
id: notes
run: |
set -euo pipefail
# Subject line (first line of commit message) → release title
SUBJECT=$(git log -1 --format="%s" "${{ gitea.sha }}")
# Body (everything after the blank line) → release body
BODY=$(git log -1 --format="%b" "${{ gitea.sha }}" | sed '/^Co-Authored-By:/d' | sed '/^[[:space:]]*$/{ N; /^\n$/d }' | sed 's/^[[:space:]]*$//' | awk 'NF || !p; {p = !NF}')
echo "title=${SUBJECT}" >> "$GITHUB_OUTPUT"
# Use a heredoc delimiter to safely handle multi-line body
{
echo "body<<RELEASE_BODY_EOF"
echo "${BODY}"
echo "RELEASE_BODY_EOF"
} >> "$GITHUB_OUTPUT"
- name: Create release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
token: ${{ secrets.GITEA_TOKEN }}
generate_release_notes: true
title: ${{ steps.notes.outputs.title }}
body: ${{ steps.notes.outputs.body }}

3
ui/.gitignore vendored
View File

@@ -22,5 +22,4 @@ Thumbs.db
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Generated by CI at build time — do not commit
/static/releases.json

View File

@@ -368,6 +368,7 @@
"admin_nav_logs": "Logs",
"admin_nav_uptime": "Uptime",
"admin_nav_push": "Push",
"admin_nav_gitea": "Gitea",
"admin_scrape_status_idle": "Idle",
"admin_scrape_status_running": "Running",

View File

@@ -430,5 +430,6 @@
"feed_reader_label": "lit",
"feed_chapters_label": "{n} chapitres",
"feed_browse_cta": "Parcourir le catalogue",
"feed_find_users_cta": "Trouver des lecteurs"
"feed_find_users_cta": "Trouver des lecteurs",
"admin_nav_gitea": "Gitea"
}

View File

@@ -430,5 +430,6 @@
"feed_reader_label": "membaca",
"feed_chapters_label": "{n} bab",
"feed_browse_cta": "Jelajahi katalog",
"feed_find_users_cta": "Temukan pembaca"
"feed_find_users_cta": "Temukan pembaca",
"admin_nav_gitea": "Gitea"
}

View File

@@ -430,5 +430,6 @@
"feed_reader_label": "lendo",
"feed_chapters_label": "{n} capítulos",
"feed_browse_cta": "Ver catálogo",
"feed_find_users_cta": "Encontrar leitores"
"feed_find_users_cta": "Encontrar leitores",
"admin_nav_gitea": "Gitea"
}

View File

@@ -430,5 +430,6 @@
"feed_reader_label": "читает",
"feed_chapters_label": "{n} глав",
"feed_browse_cta": "Каталог",
"feed_find_users_cta": "Найти читателей"
"feed_find_users_cta": "Найти читателей",
"admin_nav_gitea": "Gitea"
}

View File

@@ -339,6 +339,7 @@ export * from './admin_nav_analytics.js'
export * from './admin_nav_logs.js'
export * from './admin_nav_uptime.js'
export * from './admin_nav_push.js'
export * from './admin_nav_gitea.js'
export * from './admin_scrape_status_idle.js'
export * from './admin_scrape_full_catalogue.js'
export * from './admin_scrape_single_book.js'

View File

@@ -0,0 +1,44 @@
/* eslint-disable */
import { getLocale, experimentalStaticLocale } from '../runtime.js';
/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
/** @typedef {{}} Admin_Nav_GiteaInputs */
const en_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Gitea`)
};
const ru_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Gitea`)
};
const id_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Gitea`)
};
const pt_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Gitea`)
};
const fr_admin_nav_gitea = /** @type {(inputs: Admin_Nav_GiteaInputs) => LocalizedString} */ () => {
return /** @type {LocalizedString} */ (`Gitea`)
};
/**
* | output |
* | --- |
* | "Gitea" |
*
* @param {Admin_Nav_GiteaInputs} inputs
* @param {{ locale?: "en" | "ru" | "id" | "pt" | "fr" }} options
* @returns {LocalizedString}
*/
export const admin_nav_gitea = /** @type {((inputs?: Admin_Nav_GiteaInputs, options?: { locale?: "en" | "ru" | "id" | "pt" | "fr" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Admin_Nav_GiteaInputs, { locale?: "en" | "ru" | "id" | "pt" | "fr" }, {}>} */ ((inputs = {}, options = {}) => {
const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
if (locale === "en") return en_admin_nav_gitea(inputs)
if (locale === "ru") return ru_admin_nav_gitea(inputs)
if (locale === "id") return id_admin_nav_gitea(inputs)
if (locale === "pt") return pt_admin_nav_gitea(inputs)
return fr_admin_nav_gitea(inputs)
});

View File

@@ -15,7 +15,8 @@
{ href: 'https://analytics.libnovel.cc', label: () => m.admin_nav_analytics() },
{ href: 'https://logs.libnovel.cc', label: () => m.admin_nav_logs() },
{ href: 'https://uptime.libnovel.cc', label: () => m.admin_nav_uptime() },
{ href: 'https://push.libnovel.cc', label: () => m.admin_nav_push() }
{ href: 'https://push.libnovel.cc', label: () => m.admin_nav_push() },
{ href: 'https://gitea.kalekber.cc/kamil/libnovel', label: () => m.admin_nav_gitea() }
];
interface Props {

View File

@@ -1,5 +1,3 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import type { PageServerLoad } from './$types';
export interface Release {
@@ -12,13 +10,18 @@ export interface Release {
draft: boolean;
}
export const load: PageServerLoad = async () => {
const GITEA_RELEASES_URL =
'https://gitea.kalekber.cc/api/v1/repos/kamil/libnovel/releases?limit=50&page=1';
export const load: PageServerLoad = async ({ fetch }) => {
try {
// releases.json is baked into the image at build time by CI.
// SvelteKit Node adapter copies static/ → build/client/, so the file
// lives at <cwd>/build/client/releases.json in production.
const raw = readFileSync(join(process.cwd(), 'build', 'client', 'releases.json'), 'utf-8');
const releases: Release[] = JSON.parse(raw);
const res = await fetch(GITEA_RELEASES_URL, {
headers: { Accept: 'application/json' }
});
if (!res.ok) {
return { releases: [], error: `Gitea API returned ${res.status}` };
}
const releases: Release[] = await res.json();
return { releases: releases.filter((r) => !r.draft) };
} catch (e) {
return { releases: [], error: String(e) };