Skip to content

Commit 01ec738

Browse files
committed
feat: implement tag-based release workflow (#458)
Add football terminology naming convention and separate CI/CD pipelines. Changes: - Add CHANGELOG.md with A-Z football terminology naming table - Add Release Management section to AGENTS.md - Add Releases section to README.md with 3-step workflow - Create node-ci.yml for CI only (no Docker publishing) - Create node-cd.yml for tag-triggered CD: - Build/push Docker images (linux/amd64) to GHCR - Tag with semver, term name, and latest - Auto-generate changelog from commits - Create GitHub Releases with auto-generated notes - Remove old node.js.yml (merge-based publishing) - Add Node.js CD badge to README.md Release format: v{MAJOR}.{MINOR}.{PATCH}-{TERM} (e.g., v1.0.0-assist ⚽)
1 parent 87f89c2 commit 01ec738

File tree

5 files changed

+429
-43
lines changed

5 files changed

+429
-43
lines changed

.github/workflows/node-cd.yml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Continuous Deployment - Tag-based releases
2+
# Publishes Docker images and creates GitHub Releases
3+
4+
name: Node.js CD
5+
6+
on:
7+
push:
8+
tags:
9+
- "v*.*.*-*"
10+
11+
env:
12+
NODE_VERSION: "lts/krypton"
13+
REGISTRY: ghcr.io
14+
IMAGE_NAME: ${{ github.repository }}
15+
16+
jobs:
17+
test:
18+
runs-on: ubuntu-latest
19+
20+
permissions:
21+
contents: read
22+
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v6.0.2
26+
27+
- name: Set up Node.js
28+
uses: actions/setup-node@v6.2.0
29+
with:
30+
node-version: ${{ env.NODE_VERSION }}
31+
cache: "npm"
32+
cache-dependency-path: package-lock.json
33+
34+
- name: Install dependencies
35+
run: npm ci
36+
37+
- name: Compile application
38+
run: npm run build
39+
40+
- name: Run tests with coverage
41+
run: npm run coverage
42+
43+
release:
44+
needs: test
45+
runs-on: ubuntu-latest
46+
47+
permissions:
48+
contents: write
49+
packages: write
50+
51+
steps:
52+
- name: Checkout repository
53+
uses: actions/checkout@v6.0.2
54+
with:
55+
fetch-depth: 0
56+
57+
- name: Extract version from tag
58+
id: version
59+
run: |
60+
TAG_NAME=${GITHUB_REF#refs/tags/}
61+
echo "tag=${TAG_NAME}" >> $GITHUB_OUTPUT
62+
VERSION=$(echo ${TAG_NAME} | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+)-.*/\1/')
63+
echo "semver=${VERSION}" >> $GITHUB_OUTPUT
64+
TERM=$(echo ${TAG_NAME} | sed -E 's/^v[0-9]+\.[0-9]+\.[0-9]+-(.+)/\1/')
65+
echo "term=${TERM}" >> $GITHUB_OUTPUT
66+
67+
- name: Log in to GitHub Container Registry
68+
uses: docker/login-action@v3.7.0
69+
with:
70+
registry: ${{ env.REGISTRY }}
71+
username: ${{ github.actor }}
72+
password: ${{ secrets.GITHUB_TOKEN }}
73+
74+
- name: Set up Docker Buildx
75+
uses: docker/setup-buildx-action@v3.12.0
76+
77+
- name: Build and push Docker image
78+
uses: docker/build-push-action@v6.18.0
79+
with:
80+
context: .
81+
push: true
82+
platforms: linux/amd64
83+
provenance: false
84+
cache-from: type=gha
85+
cache-to: type=gha,mode=max
86+
tags: |
87+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
88+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.semver }}
89+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.term }}
90+
91+
- name: Generate changelog
92+
id: changelog
93+
run: |
94+
PREVIOUS_TAG=$(git tag --sort=-creatordate | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-' | sed -n '2p')
95+
if [ -z "$PREVIOUS_TAG" ]; then
96+
CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges)
97+
else
98+
CHANGELOG=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" --no-merges)
99+
fi
100+
echo "changelog<<EOF" >> $GITHUB_OUTPUT
101+
echo "$CHANGELOG" >> $GITHUB_OUTPUT
102+
echo "EOF" >> $GITHUB_OUTPUT
103+
104+
- name: Create GitHub Release
105+
uses: softprops/action-gh-release@v2.5.0
106+
with:
107+
name: ${{ steps.version.outputs.tag }} ⚽
108+
body: |
109+
## Release ${{ steps.version.outputs.semver }} - ${{ steps.version.outputs.term }} ⚽
110+
111+
### 📦 Docker Images
112+
113+
Pull this release:
114+
115+
```bash
116+
# By semantic version (recommended)
117+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.semver }}
118+
119+
# By term name
120+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.term }}
121+
122+
# Latest
123+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
124+
```
125+
126+
### 📝 Changes
127+
128+
${{ steps.changelog.outputs.changelog }}
129+
130+
### 🔗 Links
131+
132+
- [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/master/CHANGELOG.md)
133+
- [Docker Image](${{ env.REGISTRY }}/${{ env.IMAGE_NAME }})
134+
- [API Documentation](https://github.com/${{ github.repository }}#api-endpoints)
135+
generate_release_notes: true
136+
draft: false
137+
prerelease: false
Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -102,39 +102,3 @@ jobs:
102102
uses: codacy/codacy-coverage-reporter-action@v1.3.0
103103
with:
104104
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
105-
106-
container:
107-
needs: coverage
108-
runs-on: ubuntu-latest
109-
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
110-
111-
permissions:
112-
contents: read
113-
packages: write
114-
115-
steps:
116-
- name: Checkout repository
117-
uses: actions/checkout@v6.0.2
118-
119-
- name: Log in to GitHub Container Registry
120-
uses: docker/login-action@v3.7.0
121-
with:
122-
registry: ghcr.io
123-
username: ${{ github.actor }}
124-
password: ${{ secrets.GITHUB_TOKEN }}
125-
126-
- name: Set up Docker Buildx
127-
uses: docker/setup-buildx-action@v3.12.0
128-
129-
- name: Build and push Docker image to GitHub Container Registry
130-
uses: docker/build-push-action@v6.18.0
131-
with:
132-
context: .
133-
push: true
134-
platforms: linux/amd64
135-
provenance: false
136-
cache-from: type=gha
137-
cache-to: type=gha,mode=max
138-
tags: |
139-
ghcr.io/${{ github.repository }}:latest
140-
ghcr.io/${{ github.repository }}:sha-${{ github.sha }}

AGENTS.md

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# AGENTS.md
22

3-
> **⚡ Token Efficiency Note**: This file contains complete operational instructions (~2,500 tokens).
4-
> **Auto-loaded**: NO (load explicitly with `#file:AGENTS.md` when you need detailed procedures)
5-
> **When to load**: Complex workflows, troubleshooting, CI/CD setup, detailed architecture questions
3+
> **⚡ Token Efficiency Note**: This file contains complete operational instructions (~2,500 tokens).
4+
> **Auto-loaded**: NO (load explicitly with `#file:AGENTS.md` when you need detailed procedures)
5+
> **When to load**: Complex workflows, troubleshooting, CI/CD setup, detailed architecture questions
66
> **Related files**: See `#file:.github/copilot-instructions.md` for quick context (auto-loaded, ~500 tokens)
77
88
---
@@ -70,11 +70,13 @@ npx tsc --noEmit
7070
```
7171

7272
**Pre-commit checklist**:
73+
7374
1. Run `npm run lint` - must pass with no errors
7475
2. Run `npx tsc --noEmit` - must pass type checking
7576
3. Run `npm run coverage` - all tests must pass with coverage
7677

7778
**Style rules**:
79+
7880
- ESLint configuration in `eslint.config.mjs`
7981
- Prettier configuration in `.prettierrc`
8082
- TypeScript strict mode enabled in `tsconfig.json`
@@ -134,20 +136,106 @@ curl http://localhost:9000/health
134136

135137
**First run behavior**: Container initializes SQLite database with seed data. Volume persists data between runs.
136138

139+
## Release Management
140+
141+
### CHANGELOG Maintenance
142+
143+
**Important**: Update CHANGELOG.md continuously as you work, not just before releases.
144+
145+
**For every meaningful commit**:
146+
147+
1. Add your changes to the `[Unreleased]` section in CHANGELOG.md
148+
2. Categorize under the appropriate heading:
149+
- **Added**: New features
150+
- **Changed**: Changes in existing functionality
151+
- **Deprecated**: Soon-to-be removed features
152+
- **Removed**: Removed features
153+
- **Fixed**: Bug fixes
154+
- **Security**: Security vulnerability fixes
155+
3. Use clear, user-facing descriptions (not just commit messages)
156+
4. Include PR/issue numbers when relevant (#123)
157+
158+
**Example**:
159+
160+
```markdown
161+
## [Unreleased]
162+
163+
### Added
164+
- User authentication with JWT tokens (#145)
165+
- Rate limiting middleware for API endpoints
166+
167+
### Deprecated
168+
- Legacy authentication endpoint /api/v1/auth (use /api/v2/auth instead)
169+
170+
### Fixed
171+
- Null reference exception in player service (#147)
172+
173+
### Security
174+
- Fix SQL injection vulnerability in search endpoint (#148)
175+
```
176+
177+
### Creating a Release
178+
179+
When ready to release:
180+
181+
1. **Update CHANGELOG.md**: Move items from `[Unreleased]` to a new versioned section:
182+
183+
```markdown
184+
## [1.1.0 - bicyclekick] - YYYY-MM-DD
185+
```
186+
187+
Commit and push this change before creating the tag.
188+
189+
2. **Create and push tag**:
190+
191+
```bash
192+
git tag -a v1.1.0-bicyclekick -m "Release 1.1.0 - Bicycle-kick"
193+
git push origin v1.1.0-bicyclekick
194+
```
195+
196+
3. **CD workflow runs automatically** to publish Docker images and create GitHub Release
197+
198+
See [CHANGELOG.md](CHANGELOG.md#how-to-release) for complete release instructions and football terminology naming convention.
199+
137200
## CI/CD Pipeline
138201

139-
### Continuous Integration (node.js.yml)
202+
### Continuous Integration (node-ci.yml)
140203

141-
**Trigger**: Push to `main`/`master` or PR
204+
**Trigger**: Push to `master` or PR
142205

143206
**Jobs**:
207+
144208
1. **Setup**: Node.js 24 installation, npm ci (clean install)
145209
2. **Lint**: ESLint + commitlint validation
146210
3. **Build**: TypeScript compilation (`npm run build`)
147211
4. **Test**: Jest with coverage report
148212
5. **Coverage**: Upload to Codecov and Codacy
149213

214+
**Note**: CI only validates code - it does NOT publish Docker images.
215+
216+
### Continuous Deployment (node-cd.yml)
217+
218+
**Trigger**: Version tags in format `v{MAJOR}.{MINOR}.{PATCH}-{TERM}`
219+
220+
Example:
221+
222+
```bash
223+
git tag -a v1.0.0-assist -m "Release 1.0.0 - Assist"
224+
git push origin v1.0.0-assist
225+
```
226+
227+
**Pipeline automatically**:
228+
229+
- Runs full test suite with coverage
230+
- Builds multi-stage Docker image
231+
- Pushes to GHCR with multiple tags (version, term name, latest)
232+
- Generates changelog from commits
233+
- Creates GitHub Release with auto-generated notes
234+
235+
**Football terminology convention**: Alphabetically ordered codenames (assist, bicyclekick, corner, etc.)
236+
150237
**Local validation** (run this before pushing):
238+
151239
```bash
152240
# Matches CI exactly
153241
npm run lint && \
@@ -192,6 +280,7 @@ tests/ # Integration tests
192280
```
193281

194282
**Key patterns**:
283+
195284
- Native ESM (not CommonJS) - uses `import/export`
196285
- TypeScript strict mode - full type safety
197286
- Sequelize ORM for database operations
@@ -215,12 +304,14 @@ DB_PATH=./storage/players.db
215304
## Troubleshooting
216305

217306
### Port already in use
307+
218308
```bash
219309
# Kill process on port 9000
220310
lsof -ti:9000 | xargs kill -9
221311
```
222312

223313
### Module import errors
314+
224315
```bash
225316
# Clean install dependencies
226317
rm -rf node_modules package-lock.json
@@ -231,6 +322,7 @@ node --version # Should be v24.x
231322
```
232323

233324
### TypeScript compilation errors
325+
234326
```bash
235327
# Clean build artifacts
236328
rm -rf dist/
@@ -243,6 +335,7 @@ npx tsc --noEmit
243335
```
244336

245337
### Database locked errors
338+
246339
```bash
247340
# Stop all running instances
248341
pkill -f "node dist/server.js"
@@ -253,6 +346,7 @@ rm storage/players.db
253346
```
254347

255348
### Jest test failures
349+
256350
```bash
257351
# Clear Jest cache
258352
npx jest --clearCache
@@ -262,6 +356,7 @@ npm test -- --verbose
262356
```
263357

264358
### Docker issues
359+
265360
```bash
266361
# Clean slate
267362
npm run docker:down
@@ -272,12 +367,15 @@ npm run docker:up
272367
## Testing the API
273368

274369
### Using Swagger UI (Recommended)
275-
Open http://localhost:9000/api-docs - Interactive documentation with "Try it out"
370+
371+
Open <http://localhost:9000/api-docs> - Interactive documentation with "Try it out"
276372

277373
### Using Postman
374+
278375
Pre-configured collection available in `postman-collections/`
279376

280377
### Using curl
378+
281379
```bash
282380
# Health check
283381
curl http://localhost:9000/health

0 commit comments

Comments
 (0)