Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 093aa419af | |||
| 8132da3c3d | |||
| 2b59233af2 | |||
| f341a2aad2 | |||
| 263033adfd | |||
| 4e2920ddfc | |||
| f7ffefb719 | |||
| c7f81a301f |
@@ -4,8 +4,18 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
|
paths:
|
||||||
|
- 'backend/**'
|
||||||
|
- 'frontend/**'
|
||||||
|
- 'docker-compose*.yml'
|
||||||
|
- '.github/workflows/docker-build.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'backend/**'
|
||||||
|
- 'frontend/**'
|
||||||
|
- 'docker-compose*.yml'
|
||||||
|
- '.github/workflows/docker-build.yml'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -67,3 +77,63 @@ jobs:
|
|||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Create GitHub Release (only on tag push)
|
||||||
|
# =============================================================================
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-and-push
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for changelog generation
|
||||||
|
|
||||||
|
- name: Get previous tag
|
||||||
|
id: prev_tag
|
||||||
|
run: |
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||||
|
echo "tag=${PREV_TAG}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Generate changelog
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
CURRENT_TAG=${GITHUB_REF#refs/tags/}
|
||||||
|
PREV_TAG="${{ steps.prev_tag.outputs.tag }}"
|
||||||
|
|
||||||
|
echo "## What's Changed" > changelog.md
|
||||||
|
echo "" >> changelog.md
|
||||||
|
|
||||||
|
if [ -n "$PREV_TAG" ]; then
|
||||||
|
# Get commits between tags
|
||||||
|
git log ${PREV_TAG}..${CURRENT_TAG} --pretty=format:"* %s (%h)" --no-merges >> changelog.md
|
||||||
|
else
|
||||||
|
# First release - get recent commits
|
||||||
|
git log -20 --pretty=format:"* %s (%h)" --no-merges >> changelog.md
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> changelog.md
|
||||||
|
echo "" >> changelog.md
|
||||||
|
echo "## Docker Images" >> changelog.md
|
||||||
|
echo "" >> changelog.md
|
||||||
|
echo '```bash' >> changelog.md
|
||||||
|
echo "docker pull ghcr.io/${{ github.repository_owner }}/medassist-ng-backend:${CURRENT_TAG#v}" >> changelog.md
|
||||||
|
echo "docker pull ghcr.io/${{ github.repository_owner }}/medassist-ng-frontend:${CURRENT_TAG#v}" >> changelog.md
|
||||||
|
echo '```' >> changelog.md
|
||||||
|
echo "" >> changelog.md
|
||||||
|
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${CURRENT_TAG}" >> changelog.md
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body_path: changelog.md
|
||||||
|
generate_release_notes: false
|
||||||
|
draft: false
|
||||||
|
prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-backend",
|
"name": "medassist-ng-backend",
|
||||||
"version": "0.1.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "medassist-ng-backend",
|
"name": "medassist-ng-backend",
|
||||||
"version": "0.1.0",
|
"version": "1.0.1",
|
||||||
"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": "0.1.0",
|
"version": "1.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -109,6 +109,6 @@ export const doseTracking = sqliteTable("dose_tracking", {
|
|||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
userId: integer("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||||
doseId: text("dose_id", { length: 255 }).notNull(), // e.g. "med-5-1-86400000-1735200000000"
|
doseId: text("dose_id", { length: 255 }).notNull(), // e.g. "med-5-1-86400000-1735200000000"
|
||||||
takenAt: integer("taken_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
|
takenAt: integer("taken_at", { mode: "timestamp" }).notNull().default(sql`(strftime('%s','now'))`),
|
||||||
markedBy: text("marked_by", { length: 100 }), // null = user, "Daniel" = via share link
|
markedBy: text("marked_by", { length: 100 }), // null = user, "Daniel" = via share link
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "../db/client.js";
|
import { db } from "../db/client.js";
|
||||||
import { medications } from "../db/schema.js";
|
import { medications, doseTracking } from "../db/schema.js";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, like, sql } from "drizzle-orm";
|
||||||
import { createWriteStream, existsSync, unlinkSync } from "fs";
|
import { createWriteStream, existsSync, unlinkSync } from "fs";
|
||||||
import { resolve, extname } from "path";
|
import { resolve, extname } from "path";
|
||||||
import { pipeline } from "stream/promises";
|
import { pipeline } from "stream/promises";
|
||||||
@@ -199,6 +199,33 @@ export async function medicationRoutes(app: FastifyInstance) {
|
|||||||
|
|
||||||
if (!result.length) return reply.notFound();
|
if (!result.length) return reply.notFound();
|
||||||
|
|
||||||
|
// Clean up dose tracking entries that are before the earliest start date
|
||||||
|
// This ensures consistency when the user changes the start date
|
||||||
|
const earliestStart = Math.min(...blisters.map(b => new Date(b.start).getTime()));
|
||||||
|
if (!Number.isNaN(earliestStart)) {
|
||||||
|
// Get all dose tracking entries for this medication and filter out invalid ones
|
||||||
|
const allDoses = await db.select().from(doseTracking)
|
||||||
|
.where(and(
|
||||||
|
eq(doseTracking.userId, userId),
|
||||||
|
like(doseTracking.doseId, `${idNum}-%`)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Find doses with timestamps before the earliest start date
|
||||||
|
const dosesToDelete = allDoses.filter(dose => {
|
||||||
|
const parts = dose.doseId.split("-");
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const timestamp = parseInt(parts[2], 10);
|
||||||
|
return !Number.isNaN(timestamp) && timestamp < earliestStart;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete invalid doses
|
||||||
|
for (const dose of dosesToDelete) {
|
||||||
|
await db.delete(doseTracking).where(eq(doseTracking.id, dose.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: result[0].id,
|
id: result[0].id,
|
||||||
name: result[0].name,
|
name: result[0].name,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1013 KiB After Width: | Height: | Size: 6.8 MiB |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "medassist-ng-frontend",
|
"name": "medassist-ng-frontend",
|
||||||
"version": "0.1.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "medassist-ng-frontend",
|
"name": "medassist-ng-frontend",
|
||||||
"version": "0.1.0",
|
"version": "1.0.1",
|
||||||
"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": "0.1.0",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
+46
-48
@@ -3158,9 +3158,14 @@ function calculateCoverage(
|
|||||||
if (parts.length >= 3) {
|
if (parts.length >= 3) {
|
||||||
const medId = parseInt(parts[0], 10);
|
const medId = parseInt(parts[0], 10);
|
||||||
const blisterIdx = parseInt(parts[1], 10);
|
const blisterIdx = parseInt(parts[1], 10);
|
||||||
|
const doseTimestamp = parseInt(parts[2], 10);
|
||||||
if (medId === m.id && m.blisters[blisterIdx]) {
|
if (medId === m.id && m.blisters[blisterIdx]) {
|
||||||
// Each taken dose (regardless of person) consumes the usage amount
|
// Only count doses that are on or after the blister's start date
|
||||||
consumed += m.blisters[blisterIdx].usage;
|
const blisterStart = new Date(m.blisters[blisterIdx].start).getTime();
|
||||||
|
if (!Number.isNaN(blisterStart) && doseTimestamp >= blisterStart) {
|
||||||
|
// Each taken dose (regardless of person) consumes the usage amount
|
||||||
|
consumed += m.blisters[blisterIdx].usage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3620,31 +3625,32 @@ function SharedSchedule() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [token, t]);
|
}, [token, t]);
|
||||||
|
|
||||||
// Build schedule from medications
|
// Build schedule from medications - matches buildSchedulePreview logic exactly
|
||||||
const schedule = useMemo(() => {
|
const schedule = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
||||||
const todayStart = new Date();
|
// Use same logic as buildSchedulePreview in main app
|
||||||
todayStart.setHours(0, 0, 0, 0);
|
const now = new Date();
|
||||||
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // Midnight today
|
||||||
const todayStartTime = todayStart.getTime();
|
const todayStartTime = todayStart.getTime();
|
||||||
|
|
||||||
// Calculate end time: today midnight + scheduleDays days
|
// Use 180 days horizon like main app (scheduleDays only limits futureDays display)
|
||||||
const endDate = new Date(todayStart);
|
const end = new Date();
|
||||||
endDate.setDate(endDate.getDate() + data.scheduleDays);
|
end.setDate(end.getDate() + 180);
|
||||||
const endTime = endDate.getTime();
|
const endTime = end.getTime();
|
||||||
|
|
||||||
const doses: { id: string; when: number; medName: string; usage: number; timeStr: string; isPast: boolean; takenBy: string[] }[] = [];
|
const doses: { id: string; when: number; medName: string; usage: number; timeStr: string; isPast: boolean; takenBy: string[]; dateStr: string }[] = [];
|
||||||
|
|
||||||
for (const med of data.medications) {
|
for (const med of data.medications) {
|
||||||
med.blisters.forEach((blister, blisterIdx) => {
|
med.blisters.forEach((blister, blisterIdx) => {
|
||||||
const startDate = new Date(blister.start);
|
const startDate = new Date(blister.start);
|
||||||
const intervalMs = blister.every * 24 * 60 * 60 * 1000;
|
if (Number.isNaN(startDate.getTime())) return;
|
||||||
let t = startDate.getTime();
|
|
||||||
|
// Use the same iteration method as buildSchedulePreview (setDate instead of adding ms)
|
||||||
// Start from the very first dose (blister start)
|
// This ensures identical timestamps even across DST changes
|
||||||
while (t <= endTime) {
|
for (let d = new Date(startDate); d <= end; d.setDate(d.getDate() + blister.every)) {
|
||||||
const d = new Date(t);
|
const t = d.getTime();
|
||||||
const isPast = t < todayStartTime;
|
const isPast = d < todayStart;
|
||||||
// Generate dose ID matching Dashboard format: ${med.id}-${blisterIdx}-${whenMs}
|
// Generate dose ID matching Dashboard format: ${med.id}-${blisterIdx}-${whenMs}
|
||||||
const doseId = `${med.id}-${blisterIdx}-${t}`;
|
const doseId = `${med.id}-${blisterIdx}-${t}`;
|
||||||
doses.push({
|
doses.push({
|
||||||
@@ -3655,48 +3661,40 @@ function SharedSchedule() {
|
|||||||
isPast,
|
isPast,
|
||||||
takenBy: med.takenBy || [],
|
takenBy: med.takenBy || [],
|
||||||
timeStr: d.toLocaleTimeString(i18n.language, { hour: "2-digit", minute: "2-digit" }),
|
timeStr: d.toLocaleTimeString(i18n.language, { hour: "2-digit", minute: "2-digit" }),
|
||||||
|
dateStr: d.toLocaleDateString(i18n.language, { weekday: "short", day: "2-digit", month: "short" }),
|
||||||
});
|
});
|
||||||
t += intervalMs;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doses.sort((a, b) => a.when - b.when);
|
doses.sort((a, b) => a.when - b.when);
|
||||||
|
|
||||||
// Group by date
|
// Group by date - matches groupedSchedule logic in main app
|
||||||
const grouped: { dateStr: string; date: Date; isPast: boolean; meds: { medName: string; total: number; lastWhen: number; doses: typeof doses }[] }[] = [];
|
type DoseInfo = typeof doses[number];
|
||||||
const byDate = new Map<string, typeof doses>();
|
const days = new Map<string, { dateStr: string; date: Date; isPast: boolean; meds: Map<string, { medName: string; total: number; doses: DoseInfo[]; lastWhen: number }> }>();
|
||||||
|
|
||||||
for (const dose of doses) {
|
for (const dose of doses.slice(0, 2000)) {
|
||||||
const dateKey = new Date(dose.when).toLocaleDateString(i18n.language, {
|
const day = days.get(dose.dateStr) ?? { dateStr: dose.dateStr, date: new Date(dose.when), isPast: dose.isPast, meds: new Map() };
|
||||||
weekday: "long",
|
const medEntry = day.meds.get(dose.medName) ?? { medName: dose.medName, total: 0, doses: [], lastWhen: dose.when };
|
||||||
day: "2-digit",
|
medEntry.total += dose.usage;
|
||||||
month: "short",
|
medEntry.doses.push(dose);
|
||||||
});
|
medEntry.lastWhen = Math.max(medEntry.lastWhen, dose.when);
|
||||||
if (!byDate.has(dateKey)) byDate.set(dateKey, []);
|
day.meds.set(dose.medName, medEntry);
|
||||||
byDate.get(dateKey)!.push(dose);
|
days.set(dose.dateStr, day);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [dateStr, dayDoses] of byDate) {
|
return Array.from(days.values()).map((d) => ({
|
||||||
const byMed = new Map<string, typeof doses>();
|
dateStr: d.dateStr,
|
||||||
for (const dose of dayDoses) {
|
date: d.date,
|
||||||
if (!byMed.has(dose.medName)) byMed.set(dose.medName, []);
|
isPast: d.isPast,
|
||||||
byMed.get(dose.medName)!.push(dose);
|
meds: Array.from(d.meds.values())
|
||||||
}
|
}));
|
||||||
const meds = Array.from(byMed.entries()).map(([medName, medDoses]) => ({
|
|
||||||
medName,
|
|
||||||
total: medDoses.reduce((sum, d) => sum + d.usage, 0),
|
|
||||||
lastWhen: Math.max(...medDoses.map(d => d.when)),
|
|
||||||
doses: medDoses,
|
|
||||||
}));
|
|
||||||
grouped.push({ dateStr, date: new Date(dayDoses[0].when), isPast: dayDoses[0].isPast, meds });
|
|
||||||
}
|
|
||||||
|
|
||||||
return grouped;
|
|
||||||
}, [data, i18n.language]);
|
}, [data, i18n.language]);
|
||||||
|
|
||||||
|
// Split into past and future - matches main app logic
|
||||||
const pastDays = useMemo(() => schedule.filter(d => d.isPast), [schedule]);
|
const pastDays = useMemo(() => schedule.filter(d => d.isPast), [schedule]);
|
||||||
const futureDays = useMemo(() => schedule.filter(d => !d.isPast), [schedule]);
|
// Limit future days by scheduleDays setting (same as main app)
|
||||||
|
const futureDays = useMemo(() => schedule.filter(d => !d.isPast).slice(0, data?.scheduleDays ?? 30), [schedule, data?.scheduleDays]);
|
||||||
|
|
||||||
// Calculate coverage for stock status colors (matches main app logic)
|
// Calculate coverage for stock status colors (matches main app logic)
|
||||||
// This needs to account for taken doses and calculate depletion time
|
// This needs to account for taken doses and calculate depletion time
|
||||||
|
|||||||
Executable
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# MedAssist Release Script
|
||||||
|
# =============================================================================
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/release.sh patch # 1.0.0 -> 1.0.1 (bugfixes)
|
||||||
|
# ./scripts/release.sh minor # 1.0.0 -> 1.1.0 (new features)
|
||||||
|
# ./scripts/release.sh major # 1.0.0 -> 2.0.0 (breaking changes)
|
||||||
|
# ./scripts/release.sh 1.2.3 # explicit version
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Secondary remote (self-hosted git)
|
||||||
|
SECONDARY_REMOTE="git@git.danielvolz.org:daniel/medassist-ng.git"
|
||||||
|
|
||||||
|
# Get script directory and project root
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Check for uncommitted changes
|
||||||
|
if [[ -n $(git status --porcelain) ]]; then
|
||||||
|
echo -e "${RED}Error: You have uncommitted changes. Commit or stash them first.${NC}"
|
||||||
|
git status --short
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get current version from backend/package.json
|
||||||
|
CURRENT_VERSION=$(grep '"version"' backend/package.json | sed 's/.*"version": "\(.*\)".*/\1/')
|
||||||
|
echo -e "${BLUE}Current version: ${YELLOW}v${CURRENT_VERSION}${NC}"
|
||||||
|
|
||||||
|
# Calculate new version
|
||||||
|
if [[ -z "$1" ]]; then
|
||||||
|
echo -e "${RED}Usage: $0 <patch|minor|major|x.y.z>${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
patch)
|
||||||
|
IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
|
||||||
|
NEW_VERSION="$major.$minor.$((patch + 1))"
|
||||||
|
;;
|
||||||
|
minor)
|
||||||
|
IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
|
||||||
|
NEW_VERSION="$major.$((minor + 1)).0"
|
||||||
|
;;
|
||||||
|
major)
|
||||||
|
IFS='.' read -r major minor patch <<< "$CURRENT_VERSION"
|
||||||
|
NEW_VERSION="$((major + 1)).0.0"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Assume explicit version (validate format)
|
||||||
|
if [[ ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo -e "${RED}Invalid version format. Use: x.y.z${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
NEW_VERSION="$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${GREEN}New version: ${YELLOW}v${NEW_VERSION}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm
|
||||||
|
read -p "Release v${NEW_VERSION}? (y/N) " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update version in package.json files
|
||||||
|
echo -e "${BLUE}Updating package.json files...${NC}"
|
||||||
|
sed -i '' "s/\"version\": \"${CURRENT_VERSION}\"/\"version\": \"${NEW_VERSION}\"/" backend/package.json
|
||||||
|
sed -i '' "s/\"version\": \"${CURRENT_VERSION}\"/\"version\": \"${NEW_VERSION}\"/" frontend/package.json 2>/dev/null || true
|
||||||
|
|
||||||
|
# Commit version bump
|
||||||
|
echo -e "${BLUE}Committing version bump...${NC}"
|
||||||
|
git add backend/package.json frontend/package.json 2>/dev/null || git add backend/package.json
|
||||||
|
git commit -m "chore: release v${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Check if tag exists
|
||||||
|
if git rev-parse "v${NEW_VERSION}" >/dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}Tag v${NEW_VERSION} already exists. Overwriting...${NC}"
|
||||||
|
git tag -d "v${NEW_VERSION}"
|
||||||
|
git push origin ":refs/tags/v${NEW_VERSION}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create and push tag
|
||||||
|
echo -e "${BLUE}Creating signed tag v${NEW_VERSION}...${NC}"
|
||||||
|
git tag -s "v${NEW_VERSION}" -m "Release v${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Push
|
||||||
|
echo -e "${BLUE}Pushing to origin (GitHub)...${NC}"
|
||||||
|
git push origin main
|
||||||
|
git push origin "v${NEW_VERSION}"
|
||||||
|
|
||||||
|
# Ask about secondary remote
|
||||||
|
echo ""
|
||||||
|
read -p "Also push to git.danielvolz.org? (y/N) " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo -e "${BLUE}Pushing to git.danielvolz.org...${NC}"
|
||||||
|
git push "$SECONDARY_REMOTE" main
|
||||||
|
git push "$SECONDARY_REMOTE" "v${NEW_VERSION}"
|
||||||
|
echo -e "${GREEN}✓ Pushed to git.danielvolz.org${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}✓ Released v${NEW_VERSION}${NC}"
|
||||||
|
echo -e "${BLUE}GitHub Actions will now build and publish Docker images.${NC}"
|
||||||
|
echo -e "Track progress: ${YELLOW}https://github.com/DanielVolz/medassist-ng/actions${NC}"
|
||||||
Reference in New Issue
Block a user