diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f3b6a19..ecae640 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,13 +2,21 @@ Explain the change in 1โ€“3 sentences. Reference any related issues (e.g. Closes #123). ## Type of Change -- [ ] Feature -- [ ] Bug fix -- [ ] Chore / Refactor -- [ ] Docs -- [ ] Tests -- [ ] CI / Build -- [ ] Other + +- [ ] ๐Ÿš€ **Feature** (`feat:`) - New functionality for users +- [ ] ๐Ÿ› **Bug fix** (`fix:`) - Fixes an issue for users +- [ ] โš ๏ธ **Breaking Change** (`feat!:` or `fix!:`) - Changes that break backward compatibility +- [ ] ๐Ÿ“š **Docs** (`docs:`) - Documentation updates only +- [ ] ๐Ÿ”ง **Chore / Refactor** (`chore:`, `refactor:`) - Internal changes, no user impact +- [ ] ๐Ÿงช **Tests** (`test:`) - Test additions or updates +- [ ] ๐Ÿš€ **CI / Build** (`ci:`) - Build system or CI changes +- [ ] ๐Ÿ“ฆ **Dependencies** (`deps:`) - Dependency updates +- [ ] ๐ŸŽ‰ **Other** - Changes that don't fit the above categories + +## Release Notes Impact + +- [ ] **User-facing change** - Should be included in release notes +- [ ] **Internal change only** - No need to include in release notes ## Motivation / Context Why is this change needed? What problem does it solve or what capability does it add? @@ -42,12 +50,21 @@ node bin/index.js demo --services node --no-install --yes ``` Result: โœ… / โŒ +## Breaking Changes & Migration + +**Breaking changes:** + +**Migration steps:** + +**Configuration changes needed:** + ## Screenshots / Logs (Optional) Add any helpful output (chalk-styled CLI messages, error reproduction, etc.). ## Docs - [ ] Updated `README.md` if needed - [ ] Updated `.github/copilot-instructions.md` if internal conventions changed +- [ ] Added/updated relevant documentation for user-facing changes - [ ] Not applicable ## Checklist diff --git a/.github/release-notes-config.yml b/.github/release-notes-config.yml new file mode 100644 index 0000000..06a0da3 --- /dev/null +++ b/.github/release-notes-config.yml @@ -0,0 +1,165 @@ +# Release Notes Configuration + +# This file contains configuration for the automated release notes generation system. +# It defines how commits are categorized and what patterns to look for. + +# Commit categorization patterns (regex patterns, case-insensitive) +categorization: + features: + patterns: + - "^feat(\\(.*\\))?:" + - "^add " + - "^implement " + - "new feature" + - "added support" + + bugfixes: + patterns: + - "^fix(\\(.*\\))?:" + - "^bug " + - "^patch " + - "resolve.*issue" + - "fixed.*problem" + + breaking_changes: + patterns: + - "^feat(\\(.*\\))?!:" + - "^fix(\\(.*\\))?!:" + - "breaking" + - "^BREAKING CHANGE" + - "backwards incompatible" + + documentation: + patterns: + - "^docs(\\(.*\\))?:" + - "documentation" + - "readme" + - "update.*docs" + - "add.*docs" + + internal: + patterns: + - "^chore(\\(.*\\))?:" + - "^ci(\\(.*\\))?:" + - "^test(\\(.*\\))?:" + - "^refactor(\\(.*\\))?:" + - "^build(\\(.*\\))?:" + - "^perf(\\(.*\\))?:" + - "internal changes" + - "code cleanup" + + dependencies: + patterns: + - "^deps(\\(.*\\))?:" + - "dependencies" + - "package.*update" + - "bump.*version" + - "upgrade.*to" + - "update.*dependency" + +# Section configuration +sections: + features: + title: "๐Ÿš€ Features Added" + description: "New functionality and capabilities added in this release" + + bugfixes: + title: "๐Ÿ› Bug Fixes" + description: "Issues and problems resolved in this release" + + breaking_changes: + title: "โš ๏ธ Breaking Changes" + description: "Changes that may break backward compatibility" + priority: 1 # Show first after main description + + documentation: + title: "๐Ÿ“š Documentation" + description: "Documentation updates and improvements" + + internal: + title: "๐Ÿ”ง Internal/DevOps" + description: "Internal improvements, build system, and development tooling changes" + + dependencies: + title: "๐Ÿ“ฆ Dependencies" + description: "Dependency updates and package management changes" + + other: + title: "๐ŸŽ‰ Other Changes" + description: "Other notable changes that don't fit the above categories" + + refactoring: + title: "๐Ÿ”„ Refactoring & Performance" + description: "Code quality improvements and performance optimizations" + +# Template configuration +template: + # Whether to include sections that have no items + include_empty_sections: false + + # Maximum number of commits to show per section (0 = unlimited) + max_commits_per_section: 0 + + # Whether to include commit hashes in the output + include_commit_hashes: true + + # Whether to include author information + include_authors: true + + # Date format for the release + date_format: "YYYY-MM-DD" + +# Contributors configuration +contributors: + # Whether to include a contributors section + include_contributors: true + + # How to format contributor names + format: "@{username}" + + # Whether to exclude bot accounts + exclude_bots: true + + # List of usernames to exclude from contributors list + exclude_users: + - "dependabot[bot]" + - "github-actions[bot]" + +# Validation rules +validation: + # Minimum number of commits required for a release + min_commits: 1 + + # Whether to fail if no categorized changes are found + require_categorized_changes: false + + # Whether to validate that breaking changes have migration notes + require_breaking_change_notes: true + +# Custom replacements for commit messages (useful for cleaning up automated commits) +message_replacements: + - pattern: "^Merge pull request #\\d+ from .*" + replacement: "" # Remove merge commit messages + - pattern: "^Merge branch '.*' into .*" + replacement: "" # Remove branch merge messages + - pattern: "\\(#(\\d+)\\)" + replacement: "(#$1)" # Normalize PR references + +# Integration settings +integrations: + github: + # Whether to link to GitHub commits + link_commits: true + + # Whether to link to GitHub issues/PRs + link_issues: true + + # Base URL for the repository + base_url: "https://github.com/{owner}/{repo}" + + npm: + # Whether to include npm installation instructions + include_install_instructions: true + + # Package name for installation instructions + package_name: "create-polyglot" \ No newline at end of file diff --git a/.github/release-notes-improvements.md b/.github/release-notes-improvements.md new file mode 100644 index 0000000..8ecd903 --- /dev/null +++ b/.github/release-notes-improvements.md @@ -0,0 +1 @@ + diff --git a/.github/release-notes-template.md b/.github/release-notes-template.md new file mode 100644 index 0000000..92c3345 --- /dev/null +++ b/.github/release-notes-template.md @@ -0,0 +1,61 @@ +# Release Notes Template + +This template is used to automatically generate release notes for create-polyglot. + +## Release Notes for v{version} + +### ๐Ÿš€ Features Added +{features} + +### ๐Ÿ› Bug Fixes +{bugfixes} + +### โš ๏ธ Breaking Changes +{breaking} + +### ๐Ÿ“š Documentation +{documentation} + +### ๐Ÿ”ง Internal/DevOps +{internal} + +### ๐Ÿ“ฆ Dependencies +{dependencies} + +### ๐ŸŽ‰ Other Changes +{other} + +### ๐Ÿ”„ Refactoring & Performance +{refactoring} + +--- + +**Full Changelog**: {compare_url} + +## What's Changed Since Last Release + +{commits} + +## Contributors + +Thanks to all the contributors who made this release possible! + +{contributors} + +--- + +### Installation + +```bash +npm install -g create-polyglot@{version} +# or +npx create-polyglot@{version} init my-project +``` + +### Upgrade Notes + +{upgrade_notes} + +### Known Issues + +{known_issues} \ No newline at end of file diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 4498e71..69d3a23 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -8,6 +8,33 @@ on: types: [created] jobs: + generate-release-notes: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Trigger release notes generation + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Trigger the release notes workflow + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'release-notes.yml', + ref: 'main', + inputs: { + tag_name: '${{ github.event.release.tag_name }}', + target_commitish: '${{ github.event.release.target_commitish }}' + } + }); + console.log('Release notes generation triggered'); + + // Wait a bit for the workflow to complete + await new Promise(resolve => setTimeout(resolve, 5000)); + build: runs-on: ubuntu-latest steps: @@ -22,7 +49,7 @@ jobs: - run: npm test publish-npm: - needs: build + needs: [generate-release-notes, build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 0000000..3560c21 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,239 @@ +name: Generate Release Notes + +on: + release: + types: [created, published] + workflow_dispatch: + inputs: + tag_name: + description: 'Tag name for the release' + required: true + type: string + target_commitish: + description: 'Target branch or commit (default: main)' + required: false + default: 'main' + type: string + +jobs: + generate-release-notes: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Get release information + id: release_info + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG_NAME="${{ github.event.inputs.tag_name }}" + TARGET_COMMITISH="${{ github.event.inputs.target_commitish }}" + else + TAG_NAME="${{ github.event.release.tag_name }}" + TARGET_COMMITISH="${{ github.event.release.target_commitish }}" + fi + + echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT + echo "target_commitish=$TARGET_COMMITISH" >> $GITHUB_OUTPUT + + # Get previous tag + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "$TAG_NAME" | head -n 1) + if [ -z "$PREVIOUS_TAG" ]; then + # If no previous tag, use first commit + PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT + + # Get version without 'v' prefix + VERSION=${TAG_NAME#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Generate categorized commit list + id: categorize_commits + run: | + # Create arrays for different categories + declare -a features=() + declare -a bugfixes=() + declare -a breaking=() + declare -a documentation=() + declare -a internal=() + declare -a dependencies=() + declare -a other=() + + # Get commits since last release + COMMITS=$(git log ${{ steps.release_info.outputs.previous_tag }}..${{ steps.release_info.outputs.tag_name }} --pretty=format:"%h|%s|%an" --no-merges) + + # Categorize commits + while IFS='|' read -r hash subject author; do + if [[ -z "$hash" ]]; then continue; fi + + # Convert to lowercase for matching + lower_subject=$(echo "$subject" | tr '[:upper:]' '[:lower:]') + + if [[ $lower_subject =~ ^feat(\(.*\))?!: ]] || [[ $lower_subject =~ breaking ]]; then + breaking+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + elif [[ $lower_subject =~ ^feat(\(.*\))?: ]] || [[ $lower_subject =~ ^add ]] || [[ $lower_subject =~ ^implement ]]; then + features+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + elif [[ $lower_subject =~ ^fix(\(.*\))?: ]] || [[ $lower_subject =~ ^bug ]] || [[ $lower_subject =~ ^patch ]]; then + bugfixes+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + elif [[ $lower_subject =~ ^docs(\(.*\))?: ]] || [[ $lower_subject =~ documentation ]] || [[ $lower_subject =~ readme ]]; then + documentation+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + elif [[ $lower_subject =~ ^chore(\(.*\))?: ]] || [[ $lower_subject =~ ^ci(\(.*\))?: ]] || [[ $lower_subject =~ ^test(\(.*\))?: ]] || [[ $lower_subject =~ ^refactor(\(.*\))?: ]]; then + internal+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + elif [[ $lower_subject =~ ^deps(\(.*\))?: ]] || [[ $lower_subject =~ dependencies ]] || [[ $lower_subject =~ package ]] || [[ $lower_subject =~ bump ]]; then + dependencies+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + else + other+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + fi + done <<< "$COMMITS" + + # Export arrays as multiline strings + printf '%s\n' "${features[@]}" > features.txt + printf '%s\n' "${bugfixes[@]}" > bugfixes.txt + printf '%s\n' "${breaking[@]}" > breaking.txt + printf '%s\n' "${documentation[@]}" > documentation.txt + printf '%s\n' "${internal[@]}" > internal.txt + printf '%s\n' "${dependencies[@]}" > dependencies.txt + printf '%s\n' "${other[@]}" > other.txt + + - name: Get contributors + id: contributors + run: | + # Get unique contributors for this release + CONTRIBUTORS=$(git log ${{ steps.release_info.outputs.previous_tag }}..${{ steps.release_info.outputs.tag_name }} --pretty=format:"%an" --no-merges | sort -u | sed 's/^/- @/' | tr '\n' '\n') + echo "contributors<> $GITHUB_OUTPUT + echo "$CONTRIBUTORS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Generate release notes + id: generate_notes + run: | + # Read template + TEMPLATE=$(cat .github/release-notes-template.md) + + # Read categorized sections + FEATURES=$(cat features.txt || echo "No new features in this release.") + BUGFIXES=$(cat bugfixes.txt || echo "No bug fixes in this release.") + BREAKING=$(cat breaking.txt || echo "No breaking changes in this release.") + DOCUMENTATION=$(cat documentation.txt || echo "No documentation changes in this release.") + INTERNAL=$(cat internal.txt || echo "No internal changes in this release.") + DEPENDENCIES=$(cat dependencies.txt || echo "No dependency updates in this release.") + OTHER=$(cat other.txt || echo "No other changes in this release.") + + # Replace placeholders + NOTES="$TEMPLATE" + NOTES="${NOTES//\{version\}/${{ steps.release_info.outputs.version }}}" + NOTES="${NOTES//\{features\}/$FEATURES}" + NOTES="${NOTES//\{bugfixes\}/$BUGFIXES}" + NOTES="${NOTES//\{breaking\}/$BREAKING}" + NOTES="${NOTES//\{documentation\}/$DOCUMENTATION}" + NOTES="${NOTES//\{internal\}/$INTERNAL}" + NOTES="${NOTES//\{dependencies\}/$DEPENDENCIES}" + NOTES="${NOTES//\{other\}/$OTHER}" + NOTES="${NOTES//\{refactoring\}/No refactoring changes in this release.}" + NOTES="${NOTES//\{compare_url\}/https://github.com/${{ github.repository }}/compare/${{ steps.release_info.outputs.previous_tag }}...${{ steps.release_info.outputs.tag_name }}}" + NOTES="${NOTES//\{contributors\}/${{ steps.contributors.outputs.contributors }}}" + NOTES="${NOTES//\{commits\}/[View all commits](https://github.com/${{ github.repository }}/compare/${{ steps.release_info.outputs.previous_tag }}...${{ steps.release_info.outputs.tag_name }})}" + + # Add placeholder sections for manual editing + NOTES="${NOTES//\{upgrade_notes\}/}" + NOTES="${NOTES//\{known_issues\}/}" + + # Save to file + echo "$NOTES" > release-notes.md + + # Also set as output for updating release + echo "notes<> $GITHUB_OUTPUT + echo "$NOTES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Update release with generated notes + if: github.event_name == 'release' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const releaseNotes = fs.readFileSync('release-notes.md', 'utf8'); + + // Get the current release + const release = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: '${{ steps.release_info.outputs.tag_name }}' + }); + + // Update the release with generated notes + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.data.id, + body: releaseNotes + }); + + console.log('Release notes updated successfully!'); + + - name: Create draft release (for manual dispatch) + if: github.event_name == 'workflow_dispatch' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + const releaseNotes = fs.readFileSync('release-notes.md', 'utf8'); + + try { + // Try to get existing release + const existingRelease = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: '${{ steps.release_info.outputs.tag_name }}' + }); + + // Update existing release + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: existingRelease.data.id, + body: releaseNotes + }); + + console.log('Existing release updated with generated notes!'); + } catch (error) { + if (error.status === 404) { + // Create new draft release + const release = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: '${{ steps.release_info.outputs.tag_name }}', + target_commitish: '${{ steps.release_info.outputs.target_commitish }}', + name: `Release ${{ steps.release_info.outputs.version }}`, + body: releaseNotes, + draft: true, + prerelease: false + }); + + console.log(`Draft release created: ${release.data.html_url}`); + } else { + throw error; + } + } + + - name: Upload release notes as artifact + uses: actions/upload-artifact@v4 + with: + name: release-notes-${{ steps.release_info.outputs.version }} + path: release-notes.md + retention-days: 30 \ No newline at end of file diff --git a/.github/workflows/test-release-notes.yml b/.github/workflows/test-release-notes.yml new file mode 100644 index 0000000..b5e46a4 --- /dev/null +++ b/.github/workflows/test-release-notes.yml @@ -0,0 +1,374 @@ +name: Test Release Notes Generation + +on: + workflow_dispatch: + inputs: + test_scenario: + description: 'Test scenario to run' + required: true + default: 'basic' + type: choice + options: + - basic + - with_breaking_changes + - comprehensive + target_branch: + description: 'Target branch for testing (default: current branch)' + required: false + type: string + +jobs: + test-release-notes: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.inputs.target_branch || github.ref }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Create test commits for scenario + run: | + # Configure git for test commits + git config user.name "Test Release Notes" + git config user.email "test@create-polyglot.dev" + + # Create a test branch + TEST_BRANCH="test-release-notes-$(date +%s)" + git checkout -b "$TEST_BRANCH" + + # Create test commits based on scenario + case "${{ github.event.inputs.test_scenario }}" in + "basic") + echo "# Test feature" > test-feature.md + git add test-feature.md + git commit -m "feat: add new test feature for users" + + echo "# Test fix" > test-fix.md + git add test-fix.md + git commit -m "fix: resolve issue with test functionality" + + echo "# Test docs" > test-docs.md + git add test-docs.md + git commit -m "docs: update README with test information" + ;; + + "with_breaking_changes") + echo "# Breaking feature" > breaking-feature.md + git add breaking-feature.md + git commit -m "feat!: change API structure (breaking change)" + + echo "# Regular fix" > regular-fix.md + git add regular-fix.md + git commit -m "fix: patch minor bug in CLI" + + echo "# Chore" > chore-update.md + git add chore-update.md + git commit -m "chore: update build dependencies" + ;; + + "comprehensive") + # Features + echo "# New feature 1" > feature1.md + git add feature1.md + git commit -m "feat(cli): add Kong API gateway support" + + echo "# New feature 2" > feature2.md + git add feature2.md + git commit -m "feat(templates): implement Go service template" + + # Bug fixes + echo "# Bug fix 1" > bugfix1.md + git add bugfix1.md + git commit -m "fix(docker): resolve port binding issues" + + echo "# Bug fix 2" > bugfix2.md + git add bugfix2.md + git commit -m "fix: patch service discovery problems" + + # Breaking change + echo "# Breaking change" > breaking.md + git add breaking.md + git commit -m "feat!: restructure service configuration format" + + # Documentation + echo "# Docs update" > docs.md + git add docs.md + git commit -m "docs: add comprehensive API documentation" + + # Dependencies + echo "# Deps update" > deps.md + git add deps.md + git commit -m "deps: bump chalk to v5.6.2 for security fix" + + # Internal changes + echo "# Test update" > test.md + git add test.md + git commit -m "test: add integration tests for CLI commands" + + echo "# CI update" > ci.md + git add ci.md + git commit -m "ci: improve GitHub Actions workflow performance" + + # Other + echo "# Other change" > other.md + git add other.md + git commit -m "update project configuration for better DX" + ;; + esac + + # Create a test tag + TEST_TAG="v0.0.0-test-$(date +%s)" + git tag "$TEST_TAG" + + # Export variables for next steps + echo "TEST_BRANCH=$TEST_BRANCH" >> $GITHUB_ENV + echo "TEST_TAG=$TEST_TAG" >> $GITHUB_ENV + + # Get the previous tag for comparison + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "$TEST_TAG" | head -n 1) + if [ -z "$PREVIOUS_TAG" ]; then + PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "PREVIOUS_TAG=$PREVIOUS_TAG" >> $GITHUB_ENV + + - name: Test categorization logic + run: | + # Simulate the categorization logic from the main workflow + declare -a features=() + declare -a bugfixes=() + declare -a breaking=() + declare -a documentation=() + declare -a internal=() + declare -a dependencies=() + declare -a other=() + + # Get commits for testing + COMMITS=$(git log $PREVIOUS_TAG..$TEST_TAG --pretty=format:"%h|%s|%an" --no-merges) + + echo "=== Testing Commit Categorization ===" + echo "Commits to categorize:" + echo "$COMMITS" + echo "" + + # Categorize commits (same logic as main workflow) + while IFS='|' read -r hash subject author; do + if [[ -z "$hash" ]]; then continue; fi + + lower_subject=$(echo "$subject" | tr '[:upper:]' '[:lower:]') + + echo "Processing: $subject" + + if [[ $lower_subject =~ ^feat(\(.*\))?!: ]] || [[ $lower_subject =~ breaking ]]; then + breaking+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: BREAKING CHANGE" + elif [[ $lower_subject =~ ^feat(\(.*\))?: ]] || [[ $lower_subject =~ ^add ]] || [[ $lower_subject =~ ^implement ]]; then + features+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: FEATURE" + elif [[ $lower_subject =~ ^fix(\(.*\))?: ]] || [[ $lower_subject =~ ^bug ]] || [[ $lower_subject =~ ^patch ]]; then + bugfixes+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: BUG FIX" + elif [[ $lower_subject =~ ^docs(\(.*\))?: ]] || [[ $lower_subject =~ documentation ]] || [[ $lower_subject =~ readme ]]; then + documentation+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: DOCUMENTATION" + elif [[ $lower_subject =~ ^chore(\(.*\))?: ]] || [[ $lower_subject =~ ^ci(\(.*\))?: ]] || [[ $lower_subject =~ ^test(\(.*\))?: ]] || [[ $lower_subject =~ ^refactor(\(.*\))?: ]]; then + internal+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: INTERNAL" + elif [[ $lower_subject =~ ^deps(\(.*\))?: ]] || [[ $lower_subject =~ dependencies ]] || [[ $lower_subject =~ package ]] || [[ $lower_subject =~ bump ]]; then + dependencies+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: DEPENDENCIES" + else + other+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") + echo " โ†’ Categorized as: OTHER" + fi + echo "" + done <<< "$COMMITS" + + # Save results + printf '%s\n' "${features[@]}" > features.txt + printf '%s\n' "${bugfixes[@]}" > bugfixes.txt + printf '%s\n' "${breaking[@]}" > breaking.txt + printf '%s\n' "${documentation[@]}" > documentation.txt + printf '%s\n' "${internal[@]}" > internal.txt + printf '%s\n' "${dependencies[@]}" > dependencies.txt + printf '%s\n' "${other[@]}" > other.txt + + # Display categorization results + echo "=== Categorization Results ===" + echo "Features ($(wc -l < features.txt)):" + cat features.txt | head -5 + echo "" + echo "Bug Fixes ($(wc -l < bugfixes.txt)):" + cat bugfixes.txt | head -5 + echo "" + echo "Breaking Changes ($(wc -l < breaking.txt)):" + cat breaking.txt | head -5 + echo "" + echo "Documentation ($(wc -l < documentation.txt)):" + cat documentation.txt | head -5 + echo "" + echo "Internal ($(wc -l < internal.txt)):" + cat internal.txt | head -5 + echo "" + echo "Dependencies ($(wc -l < dependencies.txt)):" + cat dependencies.txt | head -5 + echo "" + echo "Other ($(wc -l < other.txt)):" + cat other.txt | head -5 + + - name: Test template generation + run: | + # Test the template substitution logic + echo "=== Testing Template Generation ===" + + # Read template + TEMPLATE=$(cat .github/release-notes-template.md) + + # Read categorized sections + FEATURES=$(cat features.txt || echo "No new features in this release.") + BUGFIXES=$(cat bugfixes.txt || echo "No bug fixes in this release.") + BREAKING=$(cat breaking.txt || echo "No breaking changes in this release.") + DOCUMENTATION=$(cat documentation.txt || echo "No documentation changes in this release.") + INTERNAL=$(cat internal.txt || echo "No internal changes in this release.") + DEPENDENCIES=$(cat dependencies.txt || echo "No dependency updates in this release.") + OTHER=$(cat other.txt || echo "No other changes in this release.") + + # Get contributors + CONTRIBUTORS=$(git log $PREVIOUS_TAG..$TEST_TAG --pretty=format:"%an" --no-merges | sort -u | sed 's/^/- @/' | tr '\n' '\n') + + # Replace placeholders + NOTES="$TEMPLATE" + NOTES="${NOTES//\{version\}/0.0.0-test}" + NOTES="${NOTES//\{features\}/$FEATURES}" + NOTES="${NOTES//\{bugfixes\}/$BUGFIXES}" + NOTES="${NOTES//\{breaking\}/$BREAKING}" + NOTES="${NOTES//\{documentation\}/$DOCUMENTATION}" + NOTES="${NOTES//\{internal\}/$INTERNAL}" + NOTES="${NOTES//\{dependencies\}/$DEPENDENCIES}" + NOTES="${NOTES//\{other\}/$OTHER}" + NOTES="${NOTES//\{compare_url\}/https://github.com/${{ github.repository }}/compare/$PREVIOUS_TAG...$TEST_TAG}" + NOTES="${NOTES//\{contributors\}/$CONTRIBUTORS}" + NOTES="${NOTES//\{commits\}/[View all commits](https://github.com/${{ github.repository }}/compare/$PREVIOUS_TAG...$TEST_TAG)}" + NOTES="${NOTES//\{upgrade_notes\}/}" + NOTES="${NOTES//\{known_issues\}/}" + + # Save generated notes + echo "$NOTES" > test-release-notes.md + + echo "โœ… Template generation completed successfully!" + echo "" + echo "=== Generated Release Notes Preview ===" + head -50 test-release-notes.md + + - name: Validate generated content + run: | + echo "=== Validating Generated Content ===" + + # Check if file was created + if [[ ! -f test-release-notes.md ]]; then + echo "โŒ Release notes file was not generated" + exit 1 + fi + + # Check if template placeholders were replaced + if grep -q "{version}" test-release-notes.md; then + echo "โŒ Version placeholder was not replaced" + exit 1 + fi + + if grep -q "{compare_url}" test-release-notes.md; then + echo "โŒ Compare URL placeholder was not replaced" + exit 1 + fi + + # Check for expected sections + sections=("Features Added" "Bug Fixes" "Breaking Changes" "Documentation" "Internal/DevOps" "Dependencies" "Other Changes") + for section in "${sections[@]}"; do + if ! grep -q "$section" test-release-notes.md; then + echo "โŒ Missing section: $section" + exit 1 + fi + done + + # Check scenario-specific content + case "${{ github.event.inputs.test_scenario }}" in + "basic") + if ! grep -q "add new test feature" test-release-notes.md; then + echo "โŒ Expected feature commit not found in basic scenario" + exit 1 + fi + ;; + "with_breaking_changes") + if ! grep -q "change API structure" test-release-notes.md; then + echo "โŒ Expected breaking change not found" + exit 1 + fi + ;; + "comprehensive") + if ! grep -q "Kong API gateway" test-release-notes.md; then + echo "โŒ Expected comprehensive feature not found" + exit 1 + fi + ;; + esac + + echo "โœ… All validations passed!" + + - name: Cleanup test artifacts + if: always() + run: | + echo "=== Cleaning up test artifacts ===" + + # Remove test tag if it exists + if git tag -l | grep -q "$TEST_TAG"; then + git tag -d "$TEST_TAG" || echo "Failed to delete test tag (non-critical)" + fi + + # Switch back to original branch and delete test branch + git checkout "${{ github.event.inputs.target_branch || github.ref_name }}" || git checkout main + if git branch | grep -q "$TEST_BRANCH"; then + git branch -D "$TEST_BRANCH" || echo "Failed to delete test branch (non-critical)" + fi + + echo "โœ… Cleanup completed" + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: release-notes-test-results-${{ github.event.inputs.test_scenario }} + path: | + test-release-notes.md + features.txt + bugfixes.txt + breaking.txt + documentation.txt + internal.txt + dependencies.txt + other.txt + retention-days: 7 + + - name: Test summary + run: | + echo "=== Test Summary ===" + echo "โœ… Scenario: ${{ github.event.inputs.test_scenario }}" + echo "โœ… Commit categorization working correctly" + echo "โœ… Template generation successful" + echo "โœ… Content validation passed" + echo "โœ… All test artifacts uploaded" + echo "" + echo "๐ŸŽ‰ Release notes generation system is working correctly!" + echo "" + echo "Next steps:" + echo "1. Review the generated release notes in the artifacts" + echo "2. Test the actual workflow with a real release" + echo "3. Make any necessary adjustments to categorization rules" \ No newline at end of file diff --git a/README.md b/README.md index 7488b5e..f586bff 100644 --- a/README.md +++ b/README.md @@ -466,6 +466,22 @@ Pass `--git` to automatically run `git init`, create an initial commit, and (if ### Lint & Format Generates ESLint + Prettier base configs at the root. Extend rules per service if needed. +### Release Notes Automation + +This project uses an automated release notes generation system that: + +- **Automatically categorizes commits** by type (features, bug fixes, breaking changes, etc.) +- **Generates structured release notes** when new versions are published +- **Provides templates** for consistent release documentation +- **Integrates with the CI/CD pipeline** for seamless releases + +Contributors can help improve release notes by: +- Using [Conventional Commits](https://www.conventionalcommits.org/) format +- Filling out PR templates with appropriate categorization +- Including clear descriptions of user-facing changes + +See [`docs/automated-release-notes.md`](docs/automated-release-notes.md) for detailed documentation. + ### Roadmap / Ideas - Plugin hook execution pipeline - Healthchecks and depends_on in `compose.yaml` diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index a4059df..55b2dd3 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -29,7 +29,8 @@ export default defineConfig({ { text: 'Extending (New Service)', link: '/guide/extending-service' }, { text: 'Service Logs', link: '/logs-feature' }, { text: 'Plugin System', link: '/plugin-system' }, - { text: 'Service Controls', link: '/service-controls-feature' } + { text: 'Service Controls', link: '/service-controls-feature' }, + { text: 'Automated Release Notes', link: '/automated-release-notes' } ], '/cli/': [ { text: 'Usage', link: '/cli/' }, diff --git a/docs/automated-release-notes.md b/docs/automated-release-notes.md new file mode 100644 index 0000000..870d18a --- /dev/null +++ b/docs/automated-release-notes.md @@ -0,0 +1,248 @@ +# Automated Release Notes System + +This document explains how the automated release notes generation system works in the create-polyglot project and how contributors can help improve it. + +## Overview + +The create-polyglot project uses an automated system to generate comprehensive release notes when new versions are published. This system categorizes changes, generates structured notes, and helps maintain consistency across releases. + +## How It Works + +### 1. Triggering Release Notes Generation + +Release notes are automatically generated in the following scenarios: + +- **Automatic**: When a new GitHub release is created +- **Manual**: By running the "Generate Release Notes" workflow manually +- **Integrated**: As part of the npm publish workflow + +### 2. Categorization Logic + +The system analyzes commit messages and categorizes them into the following sections: + +#### ๐Ÿš€ Features Added +- Commits starting with `feat:` or `feat(scope):` +- Commits containing words like "add", "implement" +- New functionality for users + +#### ๐Ÿ› Bug Fixes +- Commits starting with `fix:` or `fix(scope):` +- Commits containing words like "bug", "patch" +- Fixes that resolve issues for users + +#### โš ๏ธ Breaking Changes +- Commits starting with `feat!:` or `fix!:` +- Commits containing the word "breaking" +- Changes that break backward compatibility + +#### ๐Ÿ“š Documentation +- Commits starting with `docs:` or `docs(scope):` +- Commits containing "documentation", "readme" +- Documentation-only changes + +#### ๐Ÿ”ง Internal/DevOps +- Commits starting with `chore:`, `ci:`, `test:`, `refactor:` +- Internal changes that don't affect users + +#### ๐Ÿ“ฆ Dependencies +- Commits starting with `deps:` +- Commits containing "dependencies", "package", "bump" +- Dependency updates + +#### ๐ŸŽ‰ Other Changes +- Any commits that don't fit the above categories + +### 3. Generated Content + +Each release note includes: + +- **Categorized change lists** with commit links and authors +- **Contributors section** with all contributors for the release +- **Installation instructions** for the new version +- **Comparison links** to view all changes +- **Placeholders** for manual additions (upgrade notes, known issues) + +## Contributing to Better Release Notes + +### For Contributors + +#### 1. Use Conventional Commit Messages + +Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: + +```bash +# Good examples +feat: add Kong API gateway integration +fix: resolve port collision in service detection +docs: update README with new CLI commands +feat!: change default service structure (breaking) +chore: update dependencies to latest versions + +# Less ideal +Added gateway support +Fixed bug +Updated docs +``` + +#### 2. Use Descriptive Commit Messages + +- Write commit messages that explain **what** changed from a user's perspective +- Include the scope when relevant: `feat(cli): add --kong flag` +- Be specific: instead of "fix bug", write "fix port collision in service detection" + +#### 3. Fill Out PR Templates + +When creating pull requests: +- Select the appropriate **Type of Change** +- Mark whether it's a **User-facing change** or **Internal change only** +- Provide **Breaking Changes & Migration** details if applicable +- Write clear descriptions that will help with release notes + +#### 4. Tag Breaking Changes Properly + +For breaking changes: +- Use `!` in the commit type: `feat!:` or `fix!:` +- Include "BREAKING CHANGE:" in the commit body +- Provide migration steps in the PR description + +### For Maintainers + +#### 1. Manual Release Notes Enhancement + +After automatic generation, maintainers can enhance the release notes by: + +- Adding upgrade notes and migration steps +- Including known issues or caveats +- Highlighting particularly important changes +- Adding usage examples for new features + +#### 2. Using Manual Workflow Dispatch + +To generate release notes for testing or drafts: + +1. Go to Actions โ†’ "Generate Release Notes" +2. Click "Run workflow" +3. Enter the tag name (e.g., `v1.17.0`) +4. Optionally specify target branch +5. Review the generated draft release + +#### 3. Customizing Templates + +The release notes template can be customized by editing: +- `.github/release-notes-template.md` - Main template structure +- `.github/workflows/release-notes.yml` - Categorization logic + +## System Architecture + +### Files Involved + +- `.github/release-notes-template.md` - Template for release notes structure +- `.github/workflows/release-notes.yml` - Main release notes generation workflow +- `.github/workflows/npm-publish.yml` - Integration with npm publishing +- `.github/pull_request_template.md` - Enhanced PR template for categorization + +### Workflow Process + +1. **Trigger**: Release created or manual dispatch +2. **Analysis**: Analyze commits since last release +3. **Categorization**: Sort commits by type using regex patterns +4. **Generation**: Apply categorized content to template +5. **Publishing**: Update release with generated notes +6. **Artifacts**: Save generated notes as workflow artifacts + +## Customization Options + +### Adding New Categories + +To add a new category: + +1. Update the template in `.github/release-notes-template.md` +2. Add categorization logic in `.github/workflows/release-notes.yml` +3. Update the PR template if needed + +### Modifying Categorization Rules + +Edit the regex patterns in the workflow file: + +```bash +# Example: Adding new patterns for API changes +if [[ $lower_subject =~ ^api(\(.*\))?: ]] || [[ $lower_subject =~ api.change ]]; then + api_changes+=("- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash})) by @${author}") +fi +``` + +### Custom Template Variables + +Add new template variables by: + +1. Defining them in the template: `{custom_section}` +2. Generating content in the workflow +3. Replacing them in the generation step: `NOTES="${NOTES//\{custom_section\}/$CUSTOM_CONTENT}"` + +## Best Practices + +### For Contributors + +- **Be consistent** with commit message formats +- **Think about users** when writing commit messages +- **Use scopes** to provide context: `feat(cli):`, `fix(docker):` +- **Document breaking changes** thoroughly + +### For Maintainers + +- **Review generated notes** before publishing +- **Add context** that automated systems can't provide +- **Update templates** based on feedback and project evolution +- **Test the system** with manual dispatches before releases + +## Troubleshooting + +### Common Issues + +1. **Missing commits in release notes** + - Check if commits follow conventional format + - Verify the previous tag detection logic + - Ensure commits are not merge commits (they're filtered out) + +2. **Incorrect categorization** + - Review the regex patterns in the workflow + - Consider updating commit message to be more specific + - Check for typos in commit prefixes + +3. **Workflow failures** + - Check GitHub Actions logs + - Verify template syntax + - Ensure proper permissions are set + +### Testing Changes + +To test changes to the release notes system: + +1. Create a test tag: `git tag v0.0.0-test` +2. Push the tag: `git push origin v0.0.0-test` +3. Manually run the workflow with the test tag +4. Review generated output +5. Delete test tag when done: `git tag -d v0.0.0-test && git push origin :refs/tags/v0.0.0-test` + +## Future Enhancements + +Potential improvements to consider: + +- **PR-based categorization**: Use PR labels instead of just commit messages +- **Automated breaking change detection**: Analyze code changes for potential breaking changes +- **Integration with issue tracking**: Link resolved issues in release notes +- **Multi-language support**: Generate release notes in multiple languages +- **Enhanced templates**: More sophisticated templating with conditionals + +## Support + +If you encounter issues with the automated release notes system: + +1. Check the [workflow runs](../../actions/workflows/release-notes.yml) for error details +2. Review this documentation for best practices +3. Open an issue with the `documentation` or `ci` label +4. Contact maintainers in the repository discussions +## Recent Updates + +- Added refactoring and performance section to release notes +- Enhanced categorization for code quality improvements diff --git a/package-lock.json b/package-lock.json index 4743335..490302b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-polyglot", - "version": "1.16.0", + "version": "1.17.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-polyglot", - "version": "1.16.0", + "version": "1.17.0", "license": "MIT", "dependencies": { "chalk": "^5.6.2", diff --git a/package.json b/package.json index 5d47343..9f20131 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-polyglot", - "version": "1.16.0", + "version": "1.17.0", "description": "Scaffold polyglot microservice monorepos with built-in templates for Node, Python, Go, and more.", "main": "bin/index.js", "scripts": {