Skip to content

Commit 3e9c525

Browse files
pacphiclaude
andcommitted
docs: add project documentation and enhance release workflow
Add comprehensive documentation: API reference, contributing guide, getting started, release process, security policy, troubleshooting, and WebSocket protocol. Overhaul release workflow with tag validation, CI gate, Docker multi-platform builds, GitHub Release creation, and changelog generation script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2cdc1b5 commit 3e9c525

10 files changed

Lines changed: 1730 additions & 25 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
# Changelog Generation Script for Mimir
3+
# Usage: ./generate-changelog.sh <version> [output-file]
4+
#
5+
# Examples:
6+
# ./generate-changelog.sh 0.1.0
7+
# ./generate-changelog.sh 1.0.0-alpha.1 changelog-test.md
8+
#
9+
# Arguments:
10+
# version - Version number without v prefix (e.g., 0.1.0)
11+
# output-file - Output file path (default: changelog.md)
12+
13+
set -euo pipefail
14+
15+
if [[ $# -lt 1 ]]; then
16+
echo "Usage: $0 <version> [output-file]" >&2
17+
echo "Example: $0 0.1.0 changelog.md" >&2
18+
exit 1
19+
fi
20+
21+
VERSION="$1"
22+
OUTPUT_FILE="${2:-changelog.md}"
23+
24+
CURRENT_TAG="v${VERSION}"
25+
REPO="${GITHUB_REPOSITORY:-pacphi/mimir}"
26+
27+
echo "Generating changelog for $CURRENT_TAG" >&2
28+
29+
# Get previous tag (most recent tag before current)
30+
ALL_TAGS=$(git tag -l "v*" --sort=-version:refname)
31+
PREVIOUS_TAG=""
32+
33+
found_current=false
34+
while IFS= read -r tag; do
35+
[[ -z "$tag" ]] && continue
36+
if [[ "$found_current" == "true" ]]; then
37+
PREVIOUS_TAG="$tag"
38+
break
39+
fi
40+
if [[ "$tag" == "$CURRENT_TAG" ]]; then
41+
found_current=true
42+
fi
43+
done <<< "$ALL_TAGS"
44+
45+
if [[ -z "$PREVIOUS_TAG" ]]; then
46+
echo "No previous tag found, using all commits up to $CURRENT_TAG" >&2
47+
FIRST_COMMIT=$(git rev-list --reverse HEAD 2>/dev/null | head -1) || true
48+
if [[ -n "$FIRST_COMMIT" ]]; then
49+
COMMIT_RANGE="${FIRST_COMMIT}^..$CURRENT_TAG"
50+
else
51+
COMMIT_RANGE="$CURRENT_TAG"
52+
fi
53+
else
54+
echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" >&2
55+
COMMIT_RANGE="$PREVIOUS_TAG..$CURRENT_TAG"
56+
fi
57+
58+
# Initialize changelog sections
59+
features=""
60+
fixes=""
61+
docs=""
62+
deps=""
63+
perf=""
64+
refactor=""
65+
chore=""
66+
tests=""
67+
other=""
68+
69+
# Parse commits
70+
while IFS= read -r commit; do
71+
[[ -z "$commit" ]] && continue
72+
73+
hash="${commit:0:7}"
74+
message="${commit:8}"
75+
76+
case "$message" in
77+
feat:*|feat\(*) features+="- $message ($hash)"$'\n' ;;
78+
fix:*|fix\(*) fixes+="- $message ($hash)"$'\n' ;;
79+
docs:*|docs\(*) docs+="- $message ($hash)"$'\n' ;;
80+
deps:*|deps\(*) deps+="- $message ($hash)"$'\n' ;;
81+
perf:*|perf\(*) perf+="- $message ($hash)"$'\n' ;;
82+
refactor:*|refactor\(*) refactor+="- $message ($hash)"$'\n' ;;
83+
chore:*|chore\(*) chore+="- $message ($hash)"$'\n' ;;
84+
test:*|test\(*) tests+="- $message ($hash)"$'\n' ;;
85+
ci:*|ci\(*) chore+="- $message ($hash)"$'\n' ;;
86+
style:*|style\(*) chore+="- $message ($hash)"$'\n' ;;
87+
*) other+="- $message ($hash)"$'\n' ;;
88+
esac
89+
done < <(git log --oneline "$COMMIT_RANGE" 2>/dev/null || git log --oneline)
90+
91+
# Build changelog content
92+
changelog="## [${VERSION}] - $(date +%Y-%m-%d)"$'\n\n'
93+
94+
[[ -n "$features" ]] && changelog+="### Added"$'\n\n'"$features"$'\n'
95+
[[ -n "$fixes" ]] && changelog+="### Fixed"$'\n\n'"$fixes"$'\n'
96+
[[ -n "$docs" ]] && changelog+="### Documentation"$'\n\n'"$docs"$'\n'
97+
[[ -n "$deps" ]] && changelog+="### Dependencies"$'\n\n'"$deps"$'\n'
98+
[[ -n "$perf" ]] && changelog+="### Performance"$'\n\n'"$perf"$'\n'
99+
[[ -n "$refactor" ]] && changelog+="### Changed"$'\n\n'"$refactor"$'\n'
100+
[[ -n "$tests" ]] && changelog+="### Tests"$'\n\n'"$tests"$'\n'
101+
[[ -n "$chore" ]] && changelog+="### Maintenance"$'\n\n'"$chore"$'\n'
102+
[[ -n "$other" ]] && changelog+="### Other"$'\n\n'"$other"$'\n'
103+
104+
# Installation section
105+
changelog+="### Installation"$'\n\n'
106+
changelog+='```bash'$'\n'
107+
changelog+="# Pull Docker images"$'\n'
108+
changelog+="docker pull ghcr.io/${REPO}/api:${VERSION}"$'\n'
109+
changelog+="docker pull ghcr.io/${REPO}/web:${VERSION}"$'\n\n'
110+
changelog+="# Or use latest stable"$'\n'
111+
changelog+="docker pull ghcr.io/${REPO}/api:latest"$'\n'
112+
changelog+="docker pull ghcr.io/${REPO}/web:latest"$'\n'
113+
changelog+='```'$'\n\n'
114+
115+
# Add diff link if previous tag exists
116+
if [[ -n "$PREVIOUS_TAG" ]] && [[ "$PREVIOUS_TAG" != "$CURRENT_TAG" ]]; then
117+
changelog+="**Full Changelog**: https://github.com/${REPO}/compare/$PREVIOUS_TAG...$CURRENT_TAG"$'\n'
118+
fi
119+
120+
echo "$changelog" > "$OUTPUT_FILE"
121+
122+
echo "Changelog written to $OUTPUT_FILE" >&2
123+
echo "Changelog for ${CURRENT_TAG} generated successfully!" >&2

.github/workflows/release.yml

Lines changed: 185 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,127 @@ on:
55
tags:
66
- "v*"
77

8+
permissions:
9+
contents: write
10+
packages: write
11+
812
jobs:
9-
build-and-push:
10-
name: Build & Push Docker Images
13+
# ── Job 1: Validate tag format ─────────────────────────────────────
14+
validate-tag:
15+
name: Validate Tag
16+
runs-on: ubuntu-latest
17+
outputs:
18+
version: ${{ steps.parse.outputs.version }}
19+
is_prerelease: ${{ steps.parse.outputs.is_prerelease }}
20+
steps:
21+
- name: Parse and validate tag
22+
id: parse
23+
run: |
24+
TAG="${GITHUB_REF#refs/tags/}"
25+
echo "Tag: $TAG"
26+
27+
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
28+
echo "::error::Invalid tag format: $TAG (expected v<major>.<minor>.<patch>[-prerelease])"
29+
exit 1
30+
fi
31+
32+
VERSION="${TAG#v}"
33+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
34+
35+
if [[ "$VERSION" == *-* ]]; then
36+
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
37+
echo "Pre-release detected: $VERSION"
38+
else
39+
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
40+
echo "Stable release: $VERSION"
41+
fi
42+
43+
# ── Job 2: Full CI gate ─────────────────────────────────────────────
44+
ci:
45+
name: CI Gate
46+
needs: [validate-tag]
1147
runs-on: ubuntu-latest
12-
permissions:
13-
contents: read
14-
packages: write
48+
steps:
49+
- uses: actions/checkout@v6
50+
51+
- name: Set up Node.js
52+
uses: actions/setup-node@v6
53+
with:
54+
node-version: "24"
55+
56+
- name: Install pnpm
57+
uses: pnpm/action-setup@v4
1558

59+
- name: Get pnpm store directory
60+
id: pnpm-cache
61+
run: echo "store=$(pnpm store path)" >> "$GITHUB_OUTPUT"
62+
63+
- name: Cache pnpm store
64+
uses: actions/cache@v5
65+
with:
66+
path: ${{ steps.pnpm-cache.outputs.store }}
67+
key: ${{ runner.os }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
68+
restore-keys: |
69+
${{ runner.os }}-pnpm-
70+
71+
- name: Install dependencies
72+
run: pnpm install
73+
74+
- name: Format check
75+
run: pnpm format:check
76+
77+
- name: Generate Prisma client
78+
run: pnpm db:generate
79+
80+
- name: Type check
81+
run: pnpm typecheck
82+
83+
- name: Lint
84+
run: pnpm lint
85+
86+
- name: Unit tests
87+
run: pnpm test
88+
89+
- name: Production build
90+
run: pnpm build
91+
92+
# ── Job 3: Generate changelog ───────────────────────────────────────
93+
generate-changelog:
94+
name: Generate Changelog
95+
needs: [validate-tag]
96+
runs-on: ubuntu-latest
97+
outputs:
98+
changelog_file: changelog-${{ needs.validate-tag.outputs.version }}.md
99+
steps:
100+
- uses: actions/checkout@v6
101+
with:
102+
fetch-depth: 0
103+
104+
- name: Generate changelog
105+
run: |
106+
chmod +x .github/scripts/generate-changelog.sh
107+
.github/scripts/generate-changelog.sh \
108+
"${{ needs.validate-tag.outputs.version }}" \
109+
"changelog-${{ needs.validate-tag.outputs.version }}.md"
110+
111+
- name: Upload changelog artifact
112+
uses: actions/upload-artifact@v4
113+
with:
114+
name: changelog
115+
path: changelog-${{ needs.validate-tag.outputs.version }}.md
116+
117+
# ── Job 4: Build and push Docker images ─────────────────────────────
118+
docker-images:
119+
name: Docker Images
120+
needs: [validate-tag, ci]
121+
runs-on: ubuntu-latest
122+
strategy:
123+
matrix:
124+
include:
125+
- app: api
126+
dockerfile: apps/api/Dockerfile
127+
- app: web
128+
dockerfile: apps/web/Dockerfile
16129
steps:
17130
- uses: actions/checkout@v6
18131

@@ -26,30 +139,77 @@ jobs:
26139
username: ${{ github.actor }}
27140
password: ${{ secrets.GITHUB_TOKEN }}
28141

29-
- name: Extract version from tag
30-
id: version
31-
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
142+
- name: Generate image tags
143+
id: tags
144+
run: |
145+
VERSION="${{ needs.validate-tag.outputs.version }}"
146+
IS_PRE="${{ needs.validate-tag.outputs.is_prerelease }}"
147+
IMAGE="ghcr.io/${{ github.repository }}/${{ matrix.app }}"
148+
149+
TAGS="${IMAGE}:${VERSION}"
150+
151+
if [[ "$IS_PRE" == "false" ]]; then
152+
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
153+
TAGS="${TAGS},${IMAGE}:${MAJOR_MINOR},${IMAGE}:latest"
154+
fi
32155
33-
- name: Build and push API image
156+
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
157+
158+
- name: Build and push
34159
uses: docker/build-push-action@v6
35160
with:
36161
context: .
37-
file: apps/api/Dockerfile
162+
file: ${{ matrix.dockerfile }}
38163
push: true
39-
tags: |
40-
ghcr.io/${{ github.repository }}/api:${{ steps.version.outputs.version }}
41-
ghcr.io/${{ github.repository }}/api:latest
42-
cache-from: type=gha
43-
cache-to: type=gha,mode=max
164+
tags: ${{ steps.tags.outputs.tags }}
165+
cache-from: type=gha,scope=${{ matrix.app }}
166+
cache-to: type=gha,mode=max,scope=${{ matrix.app }}
44167

45-
- name: Build and push Web image
46-
uses: docker/build-push-action@v6
168+
# ── Job 5: Create GitHub Release + update CHANGELOG.md ─────────────
169+
create-release:
170+
name: Create Release
171+
needs: [validate-tag, ci, generate-changelog, docker-images]
172+
runs-on: ubuntu-latest
173+
steps:
174+
- uses: actions/checkout@v6
47175
with:
48-
context: .
49-
file: apps/web/Dockerfile
50-
push: true
51-
tags: |
52-
ghcr.io/${{ github.repository }}/web:${{ steps.version.outputs.version }}
53-
ghcr.io/${{ github.repository }}/web:latest
54-
cache-from: type=gha
55-
cache-to: type=gha,mode=max
176+
fetch-depth: 0
177+
token: ${{ secrets.GITHUB_TOKEN }}
178+
179+
- name: Download changelog artifact
180+
uses: actions/download-artifact@v4
181+
with:
182+
name: changelog
183+
184+
- name: Create GitHub Release
185+
uses: softprops/action-gh-release@v2
186+
with:
187+
body_path: changelog-${{ needs.validate-tag.outputs.version }}.md
188+
prerelease: ${{ needs.validate-tag.outputs.is_prerelease == 'true' }}
189+
generate_release_notes: false
190+
191+
- name: Update CHANGELOG.md
192+
run: |
193+
VERSION="${{ needs.validate-tag.outputs.version }}"
194+
CHANGELOG_FILE="changelog-${VERSION}.md"
195+
MARKER="<!-- This file is auto-maintained by the release workflow on each tag. -->"
196+
197+
if grep -q "$MARKER" CHANGELOG.md; then
198+
# Insert new entry after the marker line
199+
sed -i "/$MARKER/r $CHANGELOG_FILE" CHANGELOG.md
200+
# Add a blank line after the marker before the new content
201+
sed -i "/$MARKER/a\\" CHANGELOG.md
202+
else
203+
# Append to end of file
204+
echo "" >> CHANGELOG.md
205+
cat "$CHANGELOG_FILE" >> CHANGELOG.md
206+
fi
207+
208+
- name: Push CHANGELOG.md update
209+
run: |
210+
git config user.name "github-actions[bot]"
211+
git config user.email "github-actions[bot]@users.noreply.github.com"
212+
git add CHANGELOG.md
213+
git diff --cached --quiet && exit 0
214+
git commit -m "docs: update CHANGELOG.md for v${{ needs.validate-tag.outputs.version }} [skip ci]"
215+
git push origin HEAD:main

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
<!-- This file is auto-maintained by the release workflow on each tag. -->

0 commit comments

Comments
 (0)