feat: add versioning and release automation

- Add semantic-release with changelog and git plugins
- Add manual version bump script (patch/minor/major)
- Add GitHub Actions workflow for automated releases
- Add npm scripts for version management
- Setup .releaserc.json for semantic-release configuration
This commit is contained in:
Brian Madison
2025-06-14 18:19:44 -05:00
parent 413c7230e4
commit 0ea5e50aa7
5 changed files with 6421 additions and 0 deletions

46
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Release
'on':
push:
branches:
- main
workflow_dispatch:
inputs:
version_type:
description: Version bump type
required: true
default: patch
type: choice
options:
- patch
- minor
- major
jobs:
release:
runs-on: ubuntu-latest
if: '!contains(github.event.head_commit.message, ''[skip ci]'')'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests and validation
run: |
npm run validate
npm run format
- name: Manual version bump
if: github.event_name == 'workflow_dispatch'
run: npm run version:${{ github.event.inputs.version_type }}
- name: Semantic Release
if: github.event_name == 'push'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm run release

14
.releaserc.json Normal file
View File

@@ -0,0 +1,14 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
["@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
"@semantic-release/github"
]
}

6282
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,10 @@
"validate": "node tools/cli.js validate", "validate": "node tools/cli.js validate",
"install:bmad": "node tools/installer/bin/bmad.js install", "install:bmad": "node tools/installer/bin/bmad.js install",
"format": "prettier --write \"**/*.md\" && node tools/yaml-format.js **/*.md **/*.yml **/*.yaml .roo/.roomodes", "format": "prettier --write \"**/*.md\" && node tools/yaml-format.js **/*.md **/*.yml **/*.yaml .roo/.roomodes",
"version:patch": "node tools/version-bump.js patch",
"version:minor": "node tools/version-bump.js minor",
"version:major": "node tools/version-bump.js major",
"release": "semantic-release",
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
@@ -46,9 +50,12 @@
"node": ">=14.0.0" "node": ">=14.0.0"
}, },
"devDependencies": { "devDependencies": {
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.1.1", "lint-staged": "^16.1.1",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"semantic-release": "^24.2.5",
"yaml-lint": "^1.7.0" "yaml-lint": "^1.7.0"
}, },
"lint-staged": { "lint-staged": {

72
tools/version-bump.js Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env node
const fs = require('fs');
const { execSync } = require('child_process');
const chalk = require('chalk');
/**
* Simple version bumping script for BMAD-METHOD
* Usage: node tools/version-bump.js [patch|minor|major]
*/
function getCurrentVersion() {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
return packageJson.version;
}
function bumpVersion(type = 'patch') {
const validTypes = ['patch', 'minor', 'major'];
if (!validTypes.includes(type)) {
console.error(chalk.red(`Invalid version type: ${type}. Use: ${validTypes.join(', ')}`));
process.exit(1);
}
console.log(chalk.blue(`🔄 Bumping ${type} version...`));
// Use npm version to bump and create git tag
try {
const newVersion = execSync(`npm version ${type} --no-git-tag-version`, { encoding: 'utf8' }).trim();
console.log(chalk.green(`✅ Version bumped to ${newVersion}`));
// Stage the package.json change
execSync('git add package.json');
// Create commit and tag
execSync(`git commit -m "chore: bump version to ${newVersion}"`);
execSync(`git tag -a ${newVersion} -m "Release ${newVersion}"`);
console.log(chalk.green(`✅ Created git tag: ${newVersion}`));
console.log(chalk.yellow(`💡 Run 'git push && git push --tags' to publish`));
return newVersion;
} catch (error) {
console.error(chalk.red('❌ Version bump failed:'), error.message);
process.exit(1);
}
}
function main() {
const type = process.argv[2] || 'patch';
const currentVersion = getCurrentVersion();
console.log(chalk.blue(`Current version: ${currentVersion}`));
// Check if working directory is clean
try {
execSync('git diff-index --quiet HEAD --');
} catch (error) {
console.error(chalk.red('❌ Working directory is not clean. Commit your changes first.'));
process.exit(1);
}
const newVersion = bumpVersion(type);
console.log(chalk.green(`\n🎉 Version bump complete!`));
console.log(chalk.blue(`📦 ${currentVersion}${newVersion}`));
}
if (require.main === module) {
main();
}
module.exports = { bumpVersion, getCurrentVersion };