Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e55e415a50 | |||
| 5253d14af7 | |||
| 4f75d78a2b | |||
| 8f9b65147b | |||
| 571ab00918 | |||
| 27f5478dad | |||
| 5cd519be50 | |||
| e0c5eb4bf3 | |||
| aa92bcd96d | |||
| 1798a608bc |
@@ -139,12 +139,14 @@ This script handles: branch creation → version bump → PR → CI wait → mer
|
|||||||
|
|
||||||
### Version Files (MANDATORY)
|
### Version Files (MANDATORY)
|
||||||
|
|
||||||
The version number is displayed in the **About modal** (Settings → About) showing both Frontend and Backend versions. These are read from:
|
The version number is displayed in the **About modal** (Settings → About) as a single unified app version. This version is a **clickable link** pointing to the corresponding GitHub release (`https://github.com/DanielVolz/medassist-ng/releases/tag/vX.Y.Z`). The version is read from:
|
||||||
|
|
||||||
- **`backend/package.json`** → Backend version, returned by `/health` endpoint
|
- **`backend/package.json`** → Backend version, returned by `/health` endpoint
|
||||||
- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define
|
- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define and used to construct the release link
|
||||||
|
|
||||||
**Both files MUST be updated to the new version before tagging a release.** If forgotten, the About modal will show the old version even after the update.
|
**Both files MUST be updated to the new version before tagging a release.** If forgotten:
|
||||||
|
- The About modal will show the old version
|
||||||
|
- The version link will point to a non-existent GitHub release page
|
||||||
|
|
||||||
### Manual Release (if script is not available)
|
### Manual Release (if script is not available)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-badges:
|
update-badges:
|
||||||
@@ -23,7 +22,7 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.BADGE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@@ -92,17 +91,14 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Commit and push badge updates
|
||||||
uses: peter-evans/create-pull-request@v7
|
run: |
|
||||||
with:
|
git config user.name "github-actions[bot]"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
commit-message: 'chore: update test count badges'
|
git add README.md
|
||||||
title: 'chore: update test count badges'
|
if git diff --cached --quiet; then
|
||||||
body: |
|
echo "No badge changes to commit"
|
||||||
Automated update of test count badges in README.md.
|
else
|
||||||
|
git commit -m "chore: update test count badges [skip ci]"
|
||||||
- Backend tests: ${{ steps.backend-tests.outputs.count }}
|
git push
|
||||||
- Frontend tests: ${{ steps.frontend-tests.outputs.count }}
|
fi
|
||||||
branch: chore/update-test-badges
|
|
||||||
delete-branch: true
|
|
||||||
labels: automated
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/Backend_Tests-454%2F454-brightgreen?logo=vitest" alt="Backend Tests 454/454" />
|
<img src="https://img.shields.io/badge/Backend_Tests-494%2F494-brightgreen?logo=vitest" alt="Backend Tests 454/454" />
|
||||||
<img src="https://img.shields.io/badge/Frontend_Tests-611%2F611-brightgreen?logo=vitest" alt="Frontend Tests 611/611" />
|
<img src="https://img.shields.io/badge/Frontend_Tests-639%2F639-brightgreen?logo=vitest" alt="Frontend Tests 611/611" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### 🤖 AI-Generated Code
|
### 🤖 AI-Generated Code
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-backend",
|
"name": "medassist-ng-backend",
|
||||||
"version": "1.7.1",
|
"version": "1.8.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "medassist-ng-backend",
|
"name": "medassist-ng-backend",
|
||||||
"version": "1.7.1",
|
"version": "1.8.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/cookie": "^10.0.1",
|
"@fastify/cookie": "^10.0.1",
|
||||||
"@fastify/cors": "^10.0.1",
|
"@fastify/cors": "^10.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-backend",
|
"name": "medassist-ng-backend",
|
||||||
"version": "1.8.3",
|
"version": "1.8.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ services:
|
|||||||
- /tmp:noexec,nosuid,size=64m
|
- /tmp:noexec,nosuid,size=64m
|
||||||
- /var/cache/nginx:noexec,nosuid,size=64m
|
- /var/cache/nginx:noexec,nosuid,size=64m
|
||||||
- /var/run:noexec,nosuid,size=64m
|
- /var/run:noexec,nosuid,size=64m
|
||||||
|
- /etc/nginx/conf.d:noexec,nosuid,size=1m
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ RUN npm run build
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
FROM nginxinc/nginx-unprivileged:1.27-alpine AS runner
|
FROM nginxinc/nginx-unprivileged:1.27-alpine AS runner
|
||||||
|
|
||||||
|
# Redirect envsubst output to /tmp (writable under read_only: true)
|
||||||
|
# and update nginx main config to include from there instead of /etc/nginx/conf.d/
|
||||||
|
ENV NGINX_ENVSUBST_OUTPUT_DIR=/tmp
|
||||||
|
RUN sed -i 's|include /etc/nginx/conf.d/\*.conf;|include /tmp/default.conf;|' /etc/nginx/nginx.conf
|
||||||
|
|
||||||
# Copy custom nginx config as template for envsubst processing
|
# Copy custom nginx config as template for envsubst processing
|
||||||
# nginx-unprivileged automatically substitutes env vars in .template files
|
# nginx-unprivileged automatically substitutes env vars in .template files
|
||||||
COPY nginx.conf /etc/nginx/templates/default.conf.template
|
COPY nginx.conf /etc/nginx/templates/default.conf.template
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-frontend",
|
"name": "medassist-ng-frontend",
|
||||||
"version": "1.7.1",
|
"version": "1.8.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "medassist-ng-frontend",
|
"name": "medassist-ng-frontend",
|
||||||
"version": "1.7.1",
|
"version": "1.8.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"i18next-browser-languagedetector": "^8.0.4",
|
"i18next-browser-languagedetector": "^8.0.4",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-frontend",
|
"name": "medassist-ng-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.8.3",
|
"version": "1.8.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { FRONTEND_VERSION, GITHUB_URL } from "../App";
|
import { FRONTEND_VERSION, GITHUB_URL } from "../App";
|
||||||
|
|
||||||
interface UpdateCheckResult {
|
interface UpdateCheckResult {
|
||||||
status: "checking" | "up-to-date" | "update-available" | "error";
|
status: "up-to-date" | "update-available" | "error";
|
||||||
latestVersion?: string;
|
latestVersion?: string;
|
||||||
lastChecked?: string;
|
lastChecked?: string;
|
||||||
}
|
}
|
||||||
@@ -15,19 +15,13 @@ interface AboutModalProps {
|
|||||||
|
|
||||||
export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [backendVersion, setBackendVersion] = useState<string | null>(null);
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
|
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
|
||||||
|
|
||||||
// Fetch backend version and cached update result on mount
|
// Load cached update check result on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
||||||
// Fetch backend version
|
|
||||||
fetch("/api/health")
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => setBackendVersion(data.version || "unknown"))
|
|
||||||
.catch(() => setBackendVersion("unknown"));
|
|
||||||
|
|
||||||
// Load cached update check result
|
// Load cached update check result
|
||||||
const cached = sessionStorage.getItem("updateCheckResult");
|
const cached = sessionStorage.getItem("updateCheckResult");
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -43,9 +37,13 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
|||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
async function checkForUpdates() {
|
async function checkForUpdates() {
|
||||||
setUpdateCheckResult({ status: "checking" });
|
setIsChecking(true);
|
||||||
|
const minDelay = new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`);
|
const [res] = await Promise.all([
|
||||||
|
fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`),
|
||||||
|
minDelay,
|
||||||
|
]);
|
||||||
if (!res.ok) throw new Error("Failed to fetch");
|
if (!res.ok) throw new Error("Failed to fetch");
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const latestVersion = (data.tag_name || "").replace(/^v/, "");
|
const latestVersion = (data.tag_name || "").replace(/^v/, "");
|
||||||
@@ -61,6 +59,8 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
|||||||
sessionStorage.setItem("updateCheckResult", JSON.stringify(result));
|
sessionStorage.setItem("updateCheckResult", JSON.stringify(result));
|
||||||
} catch {
|
} catch {
|
||||||
setUpdateCheckResult({ status: "error" });
|
setUpdateCheckResult({ status: "error" });
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,32 +74,27 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
|||||||
</button>
|
</button>
|
||||||
<div className="about-header">
|
<div className="about-header">
|
||||||
<div className="about-logo">
|
<div className="about-logo">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
<img src="/favicon.svg" alt="MedAssist-ng" />
|
||||||
<path d="M19.5 12c0 4.14-3.36 7.5-7.5 7.5S4.5 16.14 4.5 12 7.86 4.5 12 4.5s7.5 3.36 7.5 7.5z" />
|
|
||||||
<path d="M12 8v4l2.5 2.5" />
|
|
||||||
<path d="M9 2h6M12 2v2" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
<h2>{t("about.appName", "MedAssist-ng")}</h2>
|
<h2>{t("about.appName", "MedAssist-ng")}</h2>
|
||||||
<p className="about-tagline">{t("about.description", "Personal medication tracking and reminder app")}</p>
|
<p className="about-tagline">{t("about.description", "Personal medication tracking and reminder app")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="about-versions">
|
<div className="about-versions">
|
||||||
<div className="about-version-row">
|
<div className="about-version-row">
|
||||||
<span className="about-version-label">{t("about.frontendVersion", "Frontend")}</span>
|
<span className="about-version-label">{t("about.version", "Version")}</span>
|
||||||
<span className="about-version-value">{FRONTEND_VERSION}</span>
|
<a
|
||||||
</div>
|
href={`${GITHUB_URL}/releases/tag/v${FRONTEND_VERSION}`}
|
||||||
<div className="about-version-row">
|
target="_blank"
|
||||||
<span className="about-version-label">{t("about.backendVersion", "Backend")}</span>
|
rel="noopener noreferrer"
|
||||||
<span className="about-version-value">{backendVersion || "..."}</span>
|
className="about-version-value about-version-link"
|
||||||
|
>
|
||||||
|
{FRONTEND_VERSION}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="about-update-section">
|
<div className="about-update-section">
|
||||||
<button
|
<button className="about-update-btn" onClick={checkForUpdates} disabled={isChecking}>
|
||||||
className="about-update-btn"
|
{isChecking ? (
|
||||||
onClick={checkForUpdates}
|
|
||||||
disabled={updateCheckResult?.status === "checking"}
|
|
||||||
>
|
|
||||||
{updateCheckResult?.status === "checking" ? (
|
|
||||||
<>
|
<>
|
||||||
<span className="spinner-small"></span>
|
<span className="spinner-small"></span>
|
||||||
{t("about.checking", "Checking...")}
|
{t("about.checking", "Checking...")}
|
||||||
@@ -116,7 +111,7 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
{updateCheckResult && updateCheckResult.status !== "checking" && (
|
{updateCheckResult && (
|
||||||
<div className={`about-update-result ${updateCheckResult.status}`}>
|
<div className={`about-update-result ${updateCheckResult.status}`}>
|
||||||
{updateCheckResult.status === "up-to-date" && (
|
{updateCheckResult.status === "up-to-date" && (
|
||||||
<span className="update-status-text">✓ {t("about.upToDate", "You are up to date!")}</span>
|
<span className="update-status-text">✓ {t("about.upToDate", "You are up to date!")}</span>
|
||||||
|
|||||||
@@ -474,8 +474,6 @@
|
|||||||
"appName": "MedAssist-ng",
|
"appName": "MedAssist-ng",
|
||||||
"description": "Open-Source Medikamentenverwaltung und Planungsanwendung für selbst gehostete Umgebungen.",
|
"description": "Open-Source Medikamentenverwaltung und Planungsanwendung für selbst gehostete Umgebungen.",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"frontend": "Frontend",
|
|
||||||
"backend": "Backend",
|
|
||||||
"checkForUpdates": "Nach Updates suchen",
|
"checkForUpdates": "Nach Updates suchen",
|
||||||
"checking": "Prüfe...",
|
"checking": "Prüfe...",
|
||||||
"upToDate": "Du bist auf dem neuesten Stand!",
|
"upToDate": "Du bist auf dem neuesten Stand!",
|
||||||
|
|||||||
@@ -474,8 +474,6 @@
|
|||||||
"appName": "MedAssist-ng",
|
"appName": "MedAssist-ng",
|
||||||
"description": "Open-source medication tracking and planning application for self-hosted environments.",
|
"description": "Open-source medication tracking and planning application for self-hosted environments.",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"frontend": "Frontend",
|
|
||||||
"backend": "Backend",
|
|
||||||
"checkForUpdates": "Check for Updates",
|
"checkForUpdates": "Check for Updates",
|
||||||
"checking": "Checking...",
|
"checking": "Checking...",
|
||||||
"upToDate": "You're up to date!",
|
"upToDate": "You're up to date!",
|
||||||
|
|||||||
+17
-15
@@ -4551,18 +4551,12 @@ h3 .reminder-icon.info-tooltip {
|
|||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
|
|
||||||
border-radius: 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-shadow: 0 4px 12px rgba(var(--accent-rgb, 59, 130, 246), 0.25);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-logo svg {
|
.about-logo img {
|
||||||
width: 36px;
|
width: 64px;
|
||||||
height: 36px;
|
height: 64px;
|
||||||
stroke: white;
|
border-radius: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-header h2 {
|
.about-header h2 {
|
||||||
@@ -4586,15 +4580,12 @@ h3 .reminder-icon.info-tooltip {
|
|||||||
|
|
||||||
.about-version-row {
|
.about-version-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-version-row:not(:last-child) {
|
|
||||||
border-bottom: 1px dashed var(--border-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-version-label {
|
.about-version-label {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
@@ -4610,9 +4601,20 @@ h3 .reminder-icon.info-tooltip {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.about-version-link {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: text-decoration 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.about-version-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.about-update-section {
|
.about-update-section {
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
border-bottom: 1px solid var(--border-primary);
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
min-height: 148px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about-update-btn {
|
.about-update-btn {
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ describe("AboutModal", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
||||||
ok: true,
|
|
||||||
json: () => Promise.resolve({ version: "1.0.0" }),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns null when not open", () => {
|
it("returns null when not open", () => {
|
||||||
@@ -65,8 +61,10 @@ describe("AboutModal", () => {
|
|||||||
expect(links.length).toBeGreaterThan(0);
|
expect(links.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("fetches backend version on open", async () => {
|
it("renders version as link to GitHub release", () => {
|
||||||
render(<AboutModal {...defaultProps} />);
|
render(<AboutModal {...defaultProps} />);
|
||||||
expect(fetch).toHaveBeenCalledWith("/api/health");
|
const versionLink = screen.getByText("1.0.0").closest("a");
|
||||||
|
expect(versionLink).toHaveAttribute("href", "https://github.com/test/repo/releases/tag/v1.0.0");
|
||||||
|
expect(versionLink).toHaveAttribute("target", "_blank");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user