with one click
update-release
Automates the release process for the ts-oauth2-server project.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Automates the release process for the ts-oauth2-server project.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | update-release |
| description | Automates the release process for the ts-oauth2-server project. |
| license | Complete terms in LICENSE |
Automates the release process for the ts-oauth2-server project by:
package.json and jsr.jsonpnpm typecheck && pnpm build && pnpm test) before release, aborting if any step fails.github/workflows/publish.yml)Use this skill when the user requests:
package.json and jsr.json files presentCHANGELOG.md file existsSupported version bump types (following semver):
Prerelease format (REQUIRED):
-rc.#. This project's prereleases are alwaysX.Y.Z-rc.N, starting atrc.0and incrementing the trailing number (rc.0→rc.1→ …). Never use bare-0or-next.Nsuffixes. The new prerelease number must be strictly greater than the previous one for the sameX.Y.Z.
import { readFileSync } from 'fs';
function getCurrentVersion(): string {
const pkg = JSON.parse(readFileSync('package.json', 'utf-8'));
return pkg.version;
}
// Prereleases use the REQUIRED `-rc.#` convention (e.g. 5.0.0-rc.0, 5.0.0-rc.1).
function bumpVersion(current: string, bumpType: string): string {
const parts = current.split('-');
const [major, minor, patch] = parts[0].split('.').map(Number);
const prerelease = parts[1]; // e.g. "rc.0"
switch (bumpType) {
case 'major':
return `${major + 1}.0.0`;
case 'minor':
return `${major}.${minor + 1}.0`;
case 'patch':
return `${major}.${minor}.${patch + 1}`;
case 'premajor':
return `${major + 1}.0.0-rc.0`;
case 'preminor':
return `${major}.${minor + 1}.0-rc.0`;
case 'prepatch':
return `${major}.${minor}.${patch + 1}-rc.0`;
case 'prerelease':
if (prerelease) {
// Increment the trailing rc number: "rc.0" -> "rc.1". Reject non-rc suffixes.
const match = prerelease.match(/^rc\.(\d+)$/);
if (!match) {
throw new Error(`Prerelease must use the -rc.# format, got: ${prerelease}`);
}
return `${parts[0]}-rc.${parseInt(match[1], 10) + 1}`;
}
return `${major}.${minor}.${patch + 1}-rc.0`;
default:
throw new Error(`Invalid bump type: ${bumpType}`);
}
}
# Get the last tag
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
# Get commits since last tag
if [ -z "$last_tag" ]; then
# No previous tag, get all commits
commits=$(git log --pretty=format:"%s" --no-merges)
else
# Get commits since last tag
commits=$(git log ${last_tag}..HEAD --pretty=format:"%s" --no-merges)
fi
Parse commits and categorize them based on conventional commit format:
feat: or feature: → Added sectionfix: → Fixed sectionBREAKING CHANGE: or !: → Changed section (breaking)docs: → Skip (or note in documentation)chore:, refactor:, test: → Skip or group under "Changed"security: → Security sectionFollow Keep a Changelog format:
## [Version] - YYYY-MM-DD
### Added
- New feature A
- New feature B
### Changed
- **BREAKING**: Changed behavior X
- Updated Y
### Fixed
- Fixed bug in Z
- Resolved issue with W
### Security
- Security fix for vulnerability V
Update both package.json and jsr.json with the new version, and prepend the new changelog entry to CHANGELOG.md under the ## [Unreleased] section.
Always run the full pre-flight locally before pausing for confirmation. The publish workflow runs pnpm build && pnpm test in CI, but failures there fire only after the release event — too late to undo a consumed version. Catch them locally first.
Run all three, in order, and abort the release if any fails:
pnpm typecheck # tsc -p tsconfig.build.json --noEmit
pnpm build # tsdown
pnpm test # vitest run
If any step fails, do NOT proceed to commit/tag/release — surface the failure and stop. pnpm build writes to the gitignored dist/, so it does not dirty the release commit; confirm git status --short shows only CHANGELOG.md, package.json, and jsr.json before committing.
After the file edits land and the pre-flight is green, stop and ask the user for confirmation before any git mutation. Do NOT proceed automatically — the user may want to review the diff or hand-edit changelog wording first.
Once confirmed, run these in order:
# 1. Commit the version bump + changelog
git add CHANGELOG.md package.json jsr.json
git commit -m "chore: release vX.Y.Z"
# 2. Tag and push (tag push alone does NOT trigger publish)
git tag vX.Y.Z
git push && git push --tags
# 3. Create GitHub release — THIS is what triggers .github/workflows/publish.yml
# (workflow listens on `release: [released, prereleased]`, not on tag push)
gh release create vX.Y.Z --title "vX.Y.Z" --notes "<changelog body for this version>"
For the --notes body, pass the section content from CHANGELOG.md for the new version (everything between the ## [X.Y.Z] header and the next ## header), without the version header itself. Use a HEREDOC for multiline notes.
For prereleases (always -rc.#, e.g. 5.0.0-rc.0), add --prerelease to gh release create so the workflow's prerelease branch publishes to npm under the next dist-tag.
After gh release create returns the release URL, share it with the user and note that npm + JSR publishing is now running in CI.
// User request: "Create a patch release"
const currentVersion = "4.1.1";
const newVersion = "4.1.2";
// 1. Get commits since v4.1.1
// 2. Categorize commits
// 3. Generate changelog entry
// 4. Update package.json, jsr.json, CHANGELOG.md
// User request: "Bump minor version and update changelog"
const currentVersion = "4.1.1";
const newVersion = "4.2.0";
// Changelog generated from commits:
// - feat: Add new grant type support
// - feat: Enhance token validation
// - fix: Resolve refresh token issue
// User request: "Create major release for breaking changes"
const currentVersion = "4.1.1";
const newVersion = "5.0.0";
// Identify BREAKING CHANGE commits
// Place them under ### Changed section
Here's a complete implementation approach:
interface ChangelogEntry {
added: string[];
changed: string[];
deprecated: string[];
removed: string[];
fixed: string[];
security: string[];
}
async function createRelease(bumpType: string): Promise<void> {
// 1. Read current version
const currentVersion = getCurrentVersion();
console.log(`Current version: ${currentVersion}`);
// 2. Calculate new version
const newVersion = bumpVersion(currentVersion, bumpType);
console.log(`New version: ${newVersion}`);
// 3. Get last tag and commits
const lastTag = await getLastTag();
const commits = await getCommitsSinceTag(lastTag);
console.log(`Found ${commits.length} commits since ${lastTag || 'beginning'}`);
// 4. Parse and categorize commits
const changelog = categorizeCommits(commits);
// 5. Generate changelog entry
const changelogEntry = generateChangelogEntry(newVersion, changelog);
console.log('Generated changelog entry');
// 6. Update files
await updatePackageJson(newVersion);
await updateJsrJson(newVersion);
await updateChangelog(changelogEntry);
console.log(`✓ Successfully prepared release ${newVersion}`);
console.log('Next: ask user to confirm, then commit + tag + push + `gh release create`.');
console.log('The GitHub release event triggers .github/workflows/publish.yml (npm + JSR).');
}
function categorizeCommits(commits: string[]): ChangelogEntry {
const entry: ChangelogEntry = {
added: [],
changed: [],
deprecated: [],
removed: [],
fixed: [],
security: [],
};
for (const commit of commits) {
const lower = commit.toLowerCase();
if (lower.includes('breaking change') || lower.includes('!:')) {
entry.changed.push(commit.replace(/^[^:]+:\s*/, ''));
} else if (lower.startsWith('feat:') || lower.startsWith('feature:')) {
entry.added.push(commit.replace(/^[^:]+:\s*/, ''));
} else if (lower.startsWith('fix:')) {
entry.fixed.push(commit.replace(/^[^:]+:\s*/, ''));
} else if (lower.startsWith('security:')) {
entry.security.push(commit.replace(/^[^:]+:\s*/, ''));
} else if (lower.startsWith('deprecate:')) {
entry.deprecated.push(commit.replace(/^[^:]+:\s*/, ''));
} else if (lower.startsWith('remove:')) {
entry.removed.push(commit.replace(/^[^:]+:\s*/, ''));
}
// Skip chore, docs, test, refactor, style, etc.
}
return entry;
}
function generateChangelogEntry(version: string, changelog: ChangelogEntry): string {
const date = new Date().toISOString().split('T')[0];
let entry = `## [${version}] - ${date}\n\n`;
if (changelog.added.length > 0) {
entry += '### Added\n';
for (const item of changelog.added) {
entry += `- ${item}\n`;
}
entry += '\n';
}
if (changelog.changed.length > 0) {
entry += '### Changed\n';
for (const item of changelog.changed) {
entry += `- ${item}\n`;
}
entry += '\n';
}
if (changelog.deprecated.length > 0) {
entry += '### Deprecated\n';
for (const item of changelog.deprecated) {
entry += `- ${item}\n`;
}
entry += '\n';
}
if (changelog.removed.length > 0) {
entry += '### Removed\n';
for (const item of changelog.removed) {
entry += `- ${item}\n`;
}
entry += '\n';
}
if (changelog.fixed.length > 0) {
entry += '### Fixed\n';
for (const item of changelog.fixed) {
entry += `- ${item}\n`;
}
entry += '\n';
}
if (changelog.security.length > 0) {
entry += '### Security\n';
for (const item of changelog.security) {
entry += `- ${item}\n`;
}
entry += '\n';
}
return entry;
}
The skill should provide clear feedback:
Current version: 4.1.1
Bumping version: patch
New version: 4.1.2
Found 5 commits since v4.1.1
Updated package.json, jsr.json, CHANGELOG.md
Pre-flight: typecheck ✓ build ✓ test ✓
Files prepared for v4.1.2.
Confirm to proceed with: commit + tag + push + GitHub release
(GitHub release triggers npm + JSR publish via .github/workflows/publish.yml)
After user confirmation, run the git+gh commands and report the release URL.
This skill respects the project's:
gh release create in sequence — the GitHub release is what triggers .github/workflows/publish.yml to publish to npm and JSR (a tag push alone does NOT trigger publish)