Skip to content

Commit c466d3e

Browse files
committed
chore: add changelog and bump release skill
1 parent cd96fbc commit c466d3e

File tree

2 files changed

+707
-0
lines changed

2 files changed

+707
-0
lines changed
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
---
2+
name: artifacts-builder
3+
description: Automates the release process for the ts-oauth2-server project.
4+
license: Complete terms in LICENSE
5+
---
6+
7+
# Release Creator Skill
8+
9+
## Purpose
10+
Automates the release process for the ts-oauth2-server project by:
11+
1. Bumping version numbers in both `package.json` and `jsr.json`
12+
2. Generating changelog entries based on git commits since the last tagged version
13+
3. Updating the CHANGELOG.md file with proper formatting
14+
4. Maintaining Keep a Changelog format and Semantic Versioning compliance
15+
16+
## When to Use This Skill
17+
Use this skill when the user requests:
18+
- Creating a new release
19+
- Bumping the version (patch, minor, major, premajor, preminor, prepatch, prerelease)
20+
- Updating the changelog
21+
- Preparing for a release
22+
- Publishing a new version
23+
24+
## Prerequisites
25+
- Git repository with commit history
26+
- Existing tags for version tracking
27+
- `package.json` and `jsr.json` files present
28+
- `CHANGELOG.md` file exists
29+
30+
## Core Functionality
31+
32+
### Version Bump Types
33+
Supported version bump types (following semver):
34+
- **patch**: Bug fixes (1.0.0 → 1.0.1)
35+
- **minor**: New features (1.0.0 → 1.1.0)
36+
- **major**: Breaking changes (1.0.0 → 2.0.0)
37+
- **prepatch**: Pre-release patch (1.0.0 → 1.0.1-0)
38+
- **preminor**: Pre-release minor (1.0.0 → 1.1.0-0)
39+
- **premajor**: Pre-release major (1.0.0 → 2.0.0-0)
40+
- **prerelease**: Increment pre-release (1.0.0-0 → 1.0.0-1)
41+
42+
### Implementation Steps
43+
44+
#### 1. Get Current Version
45+
```typescript
46+
import { readFileSync } from 'fs';
47+
48+
function getCurrentVersion(): string {
49+
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
50+
return pkg.version;
51+
}
52+
```
53+
54+
#### 2. Calculate New Version
55+
```typescript
56+
function bumpVersion(current: string, bumpType: string): string {
57+
const parts = current.split('-');
58+
const [major, minor, patch] = parts[0].split('.').map(Number);
59+
const prerelease = parts[1];
60+
61+
switch (bumpType) {
62+
case 'major':
63+
return `${major + 1}.0.0`;
64+
case 'minor':
65+
return `${major}.${minor + 1}.0`;
66+
case 'patch':
67+
return `${major}.${minor}.${patch + 1}`;
68+
case 'premajor':
69+
return `${major + 1}.0.0-0`;
70+
case 'preminor':
71+
return `${major}.${minor + 1}.0-0`;
72+
case 'prepatch':
73+
return `${major}.${minor}.${patch + 1}-0`;
74+
case 'prerelease':
75+
if (prerelease) {
76+
const num = parseInt(prerelease) || 0;
77+
return `${parts[0]}-${num + 1}`;
78+
}
79+
return `${major}.${minor}.${patch + 1}-0`;
80+
default:
81+
throw new Error(`Invalid bump type: ${bumpType}`);
82+
}
83+
}
84+
```
85+
86+
#### 3. Get Git Commits Since Last Tag
87+
```bash
88+
# Get the last tag
89+
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
90+
91+
# Get commits since last tag
92+
if [ -z "$last_tag" ]; then
93+
# No previous tag, get all commits
94+
commits=$(git log --pretty=format:"%s" --no-merges)
95+
else
96+
# Get commits since last tag
97+
commits=$(git log ${last_tag}..HEAD --pretty=format:"%s" --no-merges)
98+
fi
99+
```
100+
101+
#### 4. Categorize Commits
102+
Parse commits and categorize them based on conventional commit format:
103+
- `feat:` or `feature:` → Added section
104+
- `fix:` → Fixed section
105+
- `BREAKING CHANGE:` or `!:` → Changed section (breaking)
106+
- `docs:` → Skip (or note in documentation)
107+
- `chore:`, `refactor:`, `test:` → Skip or group under "Changed"
108+
- `security:` → Security section
109+
110+
#### 5. Generate Changelog Entry
111+
Follow Keep a Changelog format:
112+
```markdown
113+
## [Version] - YYYY-MM-DD
114+
115+
### Added
116+
- New feature A
117+
- New feature B
118+
119+
### Changed
120+
- **BREAKING**: Changed behavior X
121+
- Updated Y
122+
123+
### Fixed
124+
- Fixed bug in Z
125+
- Resolved issue with W
126+
127+
### Security
128+
- Security fix for vulnerability V
129+
```
130+
131+
#### 6. Update Files
132+
Update both `package.json` and `jsr.json` with the new version, and prepend the new changelog entry to CHANGELOG.md under the `## [Unreleased]` section.
133+
134+
## Usage Examples
135+
136+
### Example 1: Patch Release
137+
```typescript
138+
// User request: "Create a patch release"
139+
const currentVersion = "4.1.1";
140+
const newVersion = "4.1.2";
141+
142+
// 1. Get commits since v4.1.1
143+
// 2. Categorize commits
144+
// 3. Generate changelog entry
145+
// 4. Update package.json, jsr.json, CHANGELOG.md
146+
```
147+
148+
### Example 2: Minor Release with Features
149+
```typescript
150+
// User request: "Bump minor version and update changelog"
151+
const currentVersion = "4.1.1";
152+
const newVersion = "4.2.0";
153+
154+
// Changelog generated from commits:
155+
// - feat: Add new grant type support
156+
// - feat: Enhance token validation
157+
// - fix: Resolve refresh token issue
158+
```
159+
160+
### Example 3: Major Release with Breaking Changes
161+
```typescript
162+
// User request: "Create major release for breaking changes"
163+
const currentVersion = "4.1.1";
164+
const newVersion = "5.0.0";
165+
166+
// Identify BREAKING CHANGE commits
167+
// Place them under ### Changed section
168+
```
169+
170+
## Implementation Script
171+
172+
Here's a complete implementation approach:
173+
174+
```typescript
175+
interface ChangelogEntry {
176+
added: string[];
177+
changed: string[];
178+
deprecated: string[];
179+
removed: string[];
180+
fixed: string[];
181+
security: string[];
182+
}
183+
184+
async function createRelease(bumpType: string): Promise<void> {
185+
// 1. Read current version
186+
const currentVersion = getCurrentVersion();
187+
console.log(`Current version: ${currentVersion}`);
188+
189+
// 2. Calculate new version
190+
const newVersion = bumpVersion(currentVersion, bumpType);
191+
console.log(`New version: ${newVersion}`);
192+
193+
// 3. Get last tag and commits
194+
const lastTag = await getLastTag();
195+
const commits = await getCommitsSinceTag(lastTag);
196+
console.log(`Found ${commits.length} commits since ${lastTag || 'beginning'}`);
197+
198+
// 4. Parse and categorize commits
199+
const changelog = categorizeCommits(commits);
200+
201+
// 5. Generate changelog entry
202+
const changelogEntry = generateChangelogEntry(newVersion, changelog);
203+
console.log('Generated changelog entry');
204+
205+
// 6. Update files
206+
await updatePackageJson(newVersion);
207+
await updateJsrJson(newVersion);
208+
await updateChangelog(changelogEntry);
209+
210+
console.log(`✓ Successfully prepared release ${newVersion}`);
211+
console.log('Next steps:');
212+
console.log(' 1. Review the changes');
213+
console.log(' 2. Commit: git add -A && git commit -m "chore: release v' + newVersion + '"');
214+
console.log(' 3. Tag: git tag v' + newVersion);
215+
console.log(' 4. Push: git push && git push --tags');
216+
}
217+
218+
function categorizeCommits(commits: string[]): ChangelogEntry {
219+
const entry: ChangelogEntry = {
220+
added: [],
221+
changed: [],
222+
deprecated: [],
223+
removed: [],
224+
fixed: [],
225+
security: [],
226+
};
227+
228+
for (const commit of commits) {
229+
const lower = commit.toLowerCase();
230+
231+
if (lower.includes('breaking change') || lower.includes('!:')) {
232+
entry.changed.push(commit.replace(/^[^:]+:\s*/, ''));
233+
} else if (lower.startsWith('feat:') || lower.startsWith('feature:')) {
234+
entry.added.push(commit.replace(/^[^:]+:\s*/, ''));
235+
} else if (lower.startsWith('fix:')) {
236+
entry.fixed.push(commit.replace(/^[^:]+:\s*/, ''));
237+
} else if (lower.startsWith('security:')) {
238+
entry.security.push(commit.replace(/^[^:]+:\s*/, ''));
239+
} else if (lower.startsWith('deprecate:')) {
240+
entry.deprecated.push(commit.replace(/^[^:]+:\s*/, ''));
241+
} else if (lower.startsWith('remove:')) {
242+
entry.removed.push(commit.replace(/^[^:]+:\s*/, ''));
243+
}
244+
// Skip chore, docs, test, refactor, style, etc.
245+
}
246+
247+
return entry;
248+
}
249+
250+
function generateChangelogEntry(version: string, changelog: ChangelogEntry): string {
251+
const date = new Date().toISOString().split('T')[0];
252+
let entry = `## [${version}] - ${date}\n\n`;
253+
254+
if (changelog.added.length > 0) {
255+
entry += '### Added\n';
256+
for (const item of changelog.added) {
257+
entry += `- ${item}\n`;
258+
}
259+
entry += '\n';
260+
}
261+
262+
if (changelog.changed.length > 0) {
263+
entry += '### Changed\n';
264+
for (const item of changelog.changed) {
265+
entry += `- ${item}\n`;
266+
}
267+
entry += '\n';
268+
}
269+
270+
if (changelog.deprecated.length > 0) {
271+
entry += '### Deprecated\n';
272+
for (const item of changelog.deprecated) {
273+
entry += `- ${item}\n`;
274+
}
275+
entry += '\n';
276+
}
277+
278+
if (changelog.removed.length > 0) {
279+
entry += '### Removed\n';
280+
for (const item of changelog.removed) {
281+
entry += `- ${item}\n`;
282+
}
283+
entry += '\n';
284+
}
285+
286+
if (changelog.fixed.length > 0) {
287+
entry += '### Fixed\n';
288+
for (const item of changelog.fixed) {
289+
entry += `- ${item}\n`;
290+
}
291+
entry += '\n';
292+
}
293+
294+
if (changelog.security.length > 0) {
295+
entry += '### Security\n';
296+
for (const item of changelog.security) {
297+
entry += `- ${item}\n`;
298+
}
299+
entry += '\n';
300+
}
301+
302+
return entry;
303+
}
304+
```
305+
306+
## Best Practices
307+
308+
1. **Always verify versions match**: Ensure package.json and jsr.json versions are in sync before and after bumping
309+
2. **Review commits carefully**: Not all commits should appear in changelog
310+
3. **Use conventional commits**: Encourage the team to use conventional commit format
311+
4. **Breaking changes**: Always highlight breaking changes prominently
312+
5. **Date format**: Use ISO date format (YYYY-MM-DD) for consistency
313+
6. **Manual review**: Always review generated changelog before committing
314+
315+
## Error Handling
316+
317+
- **No git repository**: Warn user and exit gracefully
318+
- **Uncommitted changes**: Warn user to commit or stash changes first
319+
- **Invalid bump type**: Show available options and exit
320+
- **Version mismatch**: If package.json and jsr.json versions don't match, warn user
321+
- **No commits**: If no commits since last tag, warn user and ask if they want to proceed
322+
323+
## Output Format
324+
325+
The skill should provide clear feedback:
326+
```
327+
📦 Current version: 4.1.1
328+
🔼 Bumping version: patch
329+
📝 New version: 4.1.2
330+
🔍 Found 5 commits since v4.1.1
331+
📋 Categorized commits:
332+
- Added: 2
333+
- Fixed: 3
334+
✍️ Generated changelog entry
335+
✅ Updated package.json
336+
✅ Updated jsr.json
337+
✅ Updated CHANGELOG.md
338+
339+
✓ Successfully prepared release v4.1.2
340+
341+
Next steps:
342+
1. Review the changes
343+
2. Commit: git add -A && git commit -m "chore: release v4.1.2"
344+
3. Tag: git tag v4.1.2
345+
4. Push: git push && git push --tags
346+
```
347+
348+
## Integration with Project
349+
350+
This skill respects the project's:
351+
- Semantic versioning (as stated in CHANGELOG.md)
352+
- Keep a Changelog format (as shown in existing CHANGELOG.md)
353+
- Git workflow and tagging conventions
354+
- Dual package manager support (npm and JSR)
355+
356+
## Notes
357+
358+
- The skill does NOT automatically commit, tag, or push changes
359+
- It prepares all files for review before manual commit
360+
- This allows the developer to review and adjust if needed
361+
- The skill is idempotent - can be run multiple times safely
362+
- ALWAYS Ensure package.json and jsr.json versions are in sync before and after bumping

0 commit comments

Comments
 (0)