Skip to content

Commit c464efb

Browse files
committed
feat: add release tracking with What's New page
- Add releases.json to track version history and changes - Create generate-release-notes.sh script for CI/CD automation - Update release workflow to auto-generate notes from PR titles - Add "What's New" tab to Help page showing release history - Display version, date, SHA, and change list for each release
1 parent 9b2fdfc commit c464efb

File tree

4 files changed

+337
-0
lines changed

4 files changed

+337
-0
lines changed

.github/workflows/release.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,47 @@ env:
2020
IMAGE_NAME: ${{ github.repository }}
2121

2222
jobs:
23+
# Step 0: Generate release notes
24+
generate-release-notes:
25+
name: 0. Generate Release Notes
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: write
29+
steps:
30+
- uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0 # Full history for release notes generation
33+
token: ${{ secrets.GITHUB_TOKEN }}
34+
35+
- name: Setup Node.js
36+
uses: actions/setup-node@v4
37+
with:
38+
node-version: '20'
39+
40+
- name: Get version from package.json
41+
id: version
42+
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
43+
44+
- name: Generate release notes
45+
run: ./scripts/generate-release-notes.sh ${{ steps.version.outputs.VERSION }}
46+
47+
- name: Commit release notes
48+
run: |
49+
git config user.name "github-actions[bot]"
50+
git config user.email "github-actions[bot]@users.noreply.github.com"
51+
git add public/releases.json
52+
git diff --staged --quiet || git commit -m "chore: update release notes for v${{ steps.version.outputs.VERSION }} [skip ci]"
53+
git push
54+
2355
# Step 1: Run all tests
2456
test:
2557
name: 1. Run Tests
2658
runs-on: ubuntu-latest
59+
needs: generate-release-notes
2760
steps:
2861
- uses: actions/checkout@v4
62+
with:
63+
ref: ${{ github.ref }} # Get latest including release notes commit
2964

3065
- name: Setup Node.js
3166
uses: actions/setup-node@v4

public/releases.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"releases": [
3+
{
4+
"version": "0.0.0",
5+
"date": "2025-11-28",
6+
"sha": "9b2fdfc",
7+
"changes": [
8+
"Initial release tracking setup",
9+
"Manufacturing execution system for sheet metal fabrication",
10+
"Real-time production tracking with time management",
11+
"3D CAD viewer for STEP files",
12+
"Issue management system",
13+
"REST API with webhooks for integrations"
14+
]
15+
}
16+
],
17+
"lastUpdated": "2025-11-28T00:00:00Z"
18+
}

scripts/generate-release-notes.sh

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/bin/bash
2+
# generate-release-notes.sh
3+
# Generates release notes from merged PRs and updates releases.json
4+
# Usage: ./scripts/generate-release-notes.sh <version>
5+
6+
set -e
7+
8+
VERSION="${1:-}"
9+
RELEASES_FILE="public/releases.json"
10+
11+
if [ -z "$VERSION" ]; then
12+
echo "Usage: $0 <version>"
13+
echo "Example: $0 1.0.0"
14+
exit 1
15+
fi
16+
17+
# Get the current date
18+
DATE=$(date -u +"%Y-%m-%d")
19+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
20+
21+
# Get the short SHA of HEAD
22+
SHA=$(git rev-parse --short HEAD)
23+
24+
# Find the previous release tag (if any)
25+
PREV_TAG=$(git tag --list 'v*' --sort=-version:refname | head -n 1 || echo "")
26+
27+
echo "Generating release notes for v$VERSION"
28+
echo "Previous tag: ${PREV_TAG:-none}"
29+
echo "Current SHA: $SHA"
30+
31+
# Get PR titles from merge commits since last tag
32+
# This looks for commits with "Merge pull request" or PR number patterns
33+
if [ -n "$PREV_TAG" ]; then
34+
# Get commits since last tag
35+
COMMITS=$(git log "$PREV_TAG"..HEAD --oneline --merges 2>/dev/null || git log --oneline --merges -20)
36+
else
37+
# Get recent merge commits if no previous tag
38+
COMMITS=$(git log --oneline --merges -20)
39+
fi
40+
41+
# Extract PR titles from merge commits
42+
# Format: "Merge pull request #123 from user/branch" followed by PR title
43+
CHANGES=()
44+
45+
while IFS= read -r line; do
46+
# Skip empty lines
47+
[ -z "$line" ] && continue
48+
49+
# Try to extract PR number and get the PR title from commit message
50+
if [[ "$line" =~ Merge\ pull\ request\ \#([0-9]+) ]]; then
51+
PR_NUM="${BASH_REMATCH[1]}"
52+
# Get the full commit message which contains the PR title
53+
SHA_FULL=$(echo "$line" | cut -d' ' -f1)
54+
PR_TITLE=$(git log -1 --format=%b "$SHA_FULL" | head -n 1)
55+
if [ -n "$PR_TITLE" ]; then
56+
CHANGES+=("$PR_TITLE")
57+
fi
58+
else
59+
# For non-PR merges, use the commit subject (removing SHA prefix)
60+
SUBJECT=$(echo "$line" | cut -d' ' -f2-)
61+
# Skip if it's just a merge commit without meaningful title
62+
if [[ ! "$SUBJECT" =~ ^Merge ]]; then
63+
CHANGES+=("$SUBJECT")
64+
fi
65+
fi
66+
done <<< "$COMMITS"
67+
68+
# If no changes found from merges, get recent non-merge commits
69+
if [ ${#CHANGES[@]} -eq 0 ]; then
70+
echo "No merge commits found, using recent commits..."
71+
if [ -n "$PREV_TAG" ]; then
72+
RECENT=$(git log "$PREV_TAG"..HEAD --oneline --no-merges -10)
73+
else
74+
RECENT=$(git log --oneline --no-merges -10)
75+
fi
76+
77+
while IFS= read -r line; do
78+
[ -z "$line" ] && continue
79+
SUBJECT=$(echo "$line" | cut -d' ' -f2-)
80+
# Skip automated commits
81+
if [[ ! "$SUBJECT" =~ ^(chore:|Merge|Auto-generated) ]]; then
82+
CHANGES+=("$SUBJECT")
83+
fi
84+
done <<< "$RECENT"
85+
fi
86+
87+
# Build the changes JSON array
88+
CHANGES_JSON="["
89+
FIRST=true
90+
for change in "${CHANGES[@]}"; do
91+
# Escape quotes and special characters for JSON
92+
ESCAPED=$(echo "$change" | sed 's/"/\\"/g' | sed 's/\\/\\\\/g')
93+
if [ "$FIRST" = true ]; then
94+
CHANGES_JSON+="\"$ESCAPED\""
95+
FIRST=false
96+
else
97+
CHANGES_JSON+=",\"$ESCAPED\""
98+
fi
99+
done
100+
CHANGES_JSON+="]"
101+
102+
# If still no changes, add a default message
103+
if [ "$CHANGES_JSON" = "[]" ]; then
104+
CHANGES_JSON='["Various improvements and bug fixes"]'
105+
fi
106+
107+
echo "Changes found: ${#CHANGES[@]}"
108+
109+
# Create new release entry
110+
NEW_RELEASE=$(cat <<EOF
111+
{
112+
"version": "$VERSION",
113+
"date": "$DATE",
114+
"sha": "$SHA",
115+
"changes": $CHANGES_JSON
116+
}
117+
EOF
118+
)
119+
120+
# Check if releases.json exists
121+
if [ -f "$RELEASES_FILE" ]; then
122+
# Read existing releases and prepend new release
123+
# Using node since it's available in CI and handles JSON properly
124+
node -e "
125+
const fs = require('fs');
126+
const data = JSON.parse(fs.readFileSync('$RELEASES_FILE', 'utf8'));
127+
const newRelease = $NEW_RELEASE;
128+
129+
// Check if this version already exists, update if so
130+
const existingIndex = data.releases.findIndex(r => r.version === newRelease.version);
131+
if (existingIndex >= 0) {
132+
data.releases[existingIndex] = newRelease;
133+
} else {
134+
data.releases.unshift(newRelease);
135+
}
136+
137+
// Keep only last 20 releases
138+
data.releases = data.releases.slice(0, 20);
139+
data.lastUpdated = '$TIMESTAMP';
140+
141+
fs.writeFileSync('$RELEASES_FILE', JSON.stringify(data, null, 2));
142+
console.log('Updated $RELEASES_FILE');
143+
"
144+
else
145+
# Create new file
146+
node -e "
147+
const fs = require('fs');
148+
const data = {
149+
releases: [$NEW_RELEASE],
150+
lastUpdated: '$TIMESTAMP'
151+
};
152+
fs.writeFileSync('$RELEASES_FILE', JSON.stringify(data, null, 2));
153+
console.log('Created $RELEASES_FILE');
154+
"
155+
fi
156+
157+
echo "Release notes generated successfully for v$VERSION"

src/pages/common/Help.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import {
1414
Code,
1515
Wrench,
1616
ExternalLink,
17+
Sparkles,
18+
Calendar,
19+
GitBranch,
1720
} from "lucide-react";
1821
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
1922
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -26,8 +29,48 @@ import {
2629
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
2730
import { Separator } from "@/components/ui/separator";
2831
import { cn } from "@/lib/utils";
32+
import { Badge } from "@/components/ui/badge";
33+
34+
interface Release {
35+
version: string;
36+
date: string;
37+
sha: string;
38+
changes: string[];
39+
}
40+
41+
interface ReleasesData {
42+
releases: Release[];
43+
lastUpdated: string;
44+
}
2945

3046
export default function Help() {
47+
const [releases, setReleases] = React.useState<Release[]>([]);
48+
const [releasesLoading, setReleasesLoading] = React.useState(true);
49+
50+
React.useEffect(() => {
51+
fetch('/releases.json')
52+
.then(res => res.json())
53+
.then((data: ReleasesData) => {
54+
setReleases(data.releases || []);
55+
setReleasesLoading(false);
56+
})
57+
.catch(() => {
58+
setReleasesLoading(false);
59+
});
60+
}, []);
61+
62+
const formatDate = (dateStr: string) => {
63+
try {
64+
return new Date(dateStr).toLocaleDateString('en-US', {
65+
year: 'numeric',
66+
month: 'long',
67+
day: 'numeric'
68+
});
69+
} catch {
70+
return dateStr;
71+
}
72+
};
73+
3174
return (
3275
<div className="container max-w-5xl mx-auto py-8 px-4">
3376
{/* Header */}
@@ -105,6 +148,13 @@ export default function Help() {
105148
<Bug className="h-4 w-4" />
106149
FAQ
107150
</TabsTrigger>
151+
<TabsTrigger
152+
value="whats-new"
153+
className="data-[state=active]:bg-white/10 rounded-none border-b-2 border-transparent data-[state=active]:border-primary gap-2 px-4 py-3"
154+
>
155+
<Sparkles className="h-4 w-4" />
156+
What's New
157+
</TabsTrigger>
108158
</TabsList>
109159

110160
{/* Getting Started Tab */}
@@ -548,6 +598,83 @@ Effective Time = Total Time - Pause Time`}
548598
</AccordionItem>
549599
</Accordion>
550600
</TabsContent>
601+
602+
{/* What's New Tab */}
603+
<TabsContent value="whats-new" className="p-6">
604+
<div className="flex items-center justify-between mb-6">
605+
<div>
606+
<h2 className="text-2xl font-semibold mb-2">What's New</h2>
607+
<p className="text-muted-foreground">
608+
Latest updates and improvements to Eryxon MES
609+
</p>
610+
</div>
611+
<a
612+
href="https://github.com/SheetMetalConnect/eryxon-flow/releases"
613+
target="_blank"
614+
rel="noopener noreferrer"
615+
className="text-sm text-primary hover:underline flex items-center gap-1"
616+
>
617+
View all releases
618+
<ExternalLink className="h-3 w-3" />
619+
</a>
620+
</div>
621+
622+
{releasesLoading ? (
623+
<div className="flex items-center justify-center py-12">
624+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
625+
</div>
626+
) : releases.length === 0 ? (
627+
<Alert className="bg-primary/10 border-primary/30">
628+
<AlertDescription>
629+
No release notes available yet. Check back after the next release.
630+
</AlertDescription>
631+
</Alert>
632+
) : (
633+
<div className="space-y-6">
634+
{releases.map((release, index) => (
635+
<Card key={release.version} className={cn(
636+
"border-white/10",
637+
index === 0 ? "bg-primary/5 border-primary/20" : "bg-white/5"
638+
)}>
639+
<CardHeader className="pb-3">
640+
<div className="flex items-center justify-between flex-wrap gap-2">
641+
<div className="flex items-center gap-3">
642+
<CardTitle className="text-lg">
643+
v{release.version}
644+
</CardTitle>
645+
{index === 0 && (
646+
<Badge variant="secondary" className="bg-primary/20 text-primary border-0">
647+
Latest
648+
</Badge>
649+
)}
650+
</div>
651+
<div className="flex items-center gap-4 text-sm text-muted-foreground">
652+
<span className="flex items-center gap-1.5">
653+
<Calendar className="h-3.5 w-3.5" />
654+
{formatDate(release.date)}
655+
</span>
656+
<span className="flex items-center gap-1.5">
657+
<GitBranch className="h-3.5 w-3.5" />
658+
<code className="text-xs">{release.sha}</code>
659+
</span>
660+
</div>
661+
</div>
662+
</CardHeader>
663+
<CardContent>
664+
<ul className="space-y-2">
665+
{release.changes.map((change, changeIndex) => (
666+
<li key={changeIndex} className="flex items-start gap-2">
667+
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
668+
<span className="text-sm">{change}</span>
669+
</li>
670+
))}
671+
</ul>
672+
</CardContent>
673+
</Card>
674+
))}
675+
</div>
676+
)}
677+
</TabsContent>
551678
</Tabs>
552679
</Card>
553680
</div>

0 commit comments

Comments
 (0)