From 701497995a56689db54c1f8e5708b8de5e7ba644 Mon Sep 17 00:00:00 2001 From: Serhii Donii Date: Thu, 4 Dec 2025 21:41:13 +0200 Subject: [PATCH 1/8] feat: add symfony package & release reusable workflow --- .github/workflows/release.yml | 167 ++++++++++++++ README.md | 21 ++ docs/workflows/release.md | 420 ++++++++++++++++++++++++++++++++++ 3 files changed, 608 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 docs/workflows/release.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a72be8c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,167 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 1.2.3 or v1.2.3)' + required: true + type: string + prerelease: + description: 'Mark as pre-release' + required: false + type: boolean + default: false + draft: + description: 'Create as draft release' + required: false + type: boolean + default: false + +permissions: + contents: write + discussions: write + +jobs: + validate-version: + name: Validate Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.validate.outputs.version }} + version_tag: ${{ steps.validate.outputs.version_tag }} + is_prerelease: ${{ steps.validate.outputs.is_prerelease }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate semantic version + id: validate + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ inputs.version }}" + IS_PRERELEASE="${{ inputs.prerelease }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + # Auto-detect prerelease from version string + if [[ "$VERSION" =~ -[a-zA-Z0-9]+ ]]; then + IS_PRERELEASE="true" + else + IS_PRERELEASE="false" + fi + fi + + # Strip 'v' prefix if present + VERSION_NO_V="${VERSION#v}" + VERSION_TAG="v${VERSION_NO_V}" + + # Validate semantic versioning format + if [[ ! "$VERSION_NO_V" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then + echo "Error: Version '$VERSION' does not follow semantic versioning (e.g., 1.2.3, 1.2.3-alpha.1, 1.2.3+build.123)" + exit 1 + fi + + echo "version=${VERSION_NO_V}" >> $GITHUB_OUTPUT + echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT + echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT + + echo "βœ… Version validated: ${VERSION_NO_V}" + echo "πŸ“Œ Tag: ${VERSION_TAG}" + echo "🏷️ Pre-release: ${IS_PRERELEASE}" + + create-release: + name: Create Release + runs-on: ubuntu-latest + needs: validate-version + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: changelog + run: | + # Get the previous tag + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "${{ needs.validate-version.outputs.version_tag }}" | head -n 1) + + if [ -z "$PREVIOUS_TAG" ]; then + echo "No previous tag found, using all commits" + PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) + fi + + echo "Generating changelog from $PREVIOUS_TAG to ${{ needs.validate-version.outputs.version_tag }}" + + # Generate changelog + CHANGELOG=$(git log ${PREVIOUS_TAG}..${{ needs.validate-version.outputs.version_tag }} \ + --pretty=format:"- %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" \ + --no-merges) + + if [ -z "$CHANGELOG" ]; then + CHANGELOG="- No changes recorded" + fi + + # Group changes by type + FEATURES=$(echo "$CHANGELOG" | grep -i "^- feat" || echo "") + FIXES=$(echo "$CHANGELOG" | grep -i "^- fix" || echo "") + DOCS=$(echo "$CHANGELOG" | grep -i "^- docs" || echo "") + CHORES=$(echo "$CHANGELOG" | grep -i "^- chore" || echo "") + OTHERS=$(echo "$CHANGELOG" | grep -v "^- feat" | grep -v "^- fix" | grep -v "^- docs" | grep -v "^- chore" || echo "") + + # Build formatted changelog + FORMATTED_CHANGELOG="## What's Changed\n\n" + + if [ ! -z "$FEATURES" ]; then + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### ✨ Features\n${FEATURES}\n\n" + fi + + if [ ! -z "$FIXES" ]; then + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ› Bug Fixes\n${FIXES}\n\n" + fi + + if [ ! -z "$DOCS" ]; then + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ“š Documentation\n${DOCS}\n\n" + fi + + if [ ! -z "$CHORES" ]; then + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ”§ Maintenance\n${CHORES}\n\n" + fi + + if [ ! -z "$OTHERS" ]; then + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ”„ Other Changes\n${OTHERS}\n\n" + fi + + FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ needs.validate-version.outputs.version_tag }}" + + # Save to file for GitHub release + echo -e "$FORMATTED_CHANGELOG" > CHANGELOG.md + + echo "changelog_file=CHANGELOG.md" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.validate-version.outputs.version_tag }} + name: Release ${{ needs.validate-version.outputs.version }} + body_path: CHANGELOG.md + draft: ${{ inputs.draft || false }} + prerelease: ${{ needs.validate-version.outputs.is_prerelease }} + generate_release_notes: false + discussion_category_name: Announcements + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Summary + run: | + echo "## πŸŽ‰ Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version:** ${{ needs.validate-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag:** ${{ needs.validate-version.outputs.version_tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Pre-release:** ${{ needs.validate-version.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY + echo "- **Draft:** ${{ inputs.draft || false }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "πŸ”— [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-version.outputs.version_tag }})" >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index f23a5f5..8e1ce01 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,15 @@ All reusable workflows are located in `.github/workflows` folder - ⚑ **Performance Optimized** - Parallel execution, intelligent caching - [πŸ“š Detailed Documentation](docs/workflows/symfony-php-reusable.md) +- [release.yml](.github/workflows/release.yml) - Automated release workflow with semantic versioning + - βœ… **Semantic Versioning** - Strict semver validation (MAJOR.MINOR.PATCH) + - πŸ“ **Automatic Changelog** - Grouped by commit types (features, fixes, docs) + - 🏷️ **Pre-release Support** - Auto-detection or manual specification + - πŸ“¦ **Draft Releases** - Create draft releases for review + - πŸ”„ **Multiple Triggers** - Tag push or manual workflow dispatch + - πŸ’¬ **Discussion Integration** - Automatic discussion creation + - [πŸ“š Detailed Documentation](docs/workflows/release.md) + ### Documentation - [Symfony PHP Reusable Workflow](docs/workflows/symfony-php-reusable.md) - Complete workflow documentation with: @@ -35,6 +44,17 @@ All reusable workflows are located in `.github/workflows` folder - πŸ“Š Analysis of MacPaw's Symfony repositories - πŸ“ˆ Best practices and recommendations +- [Release Workflow](docs/workflows/release.md) - Complete release workflow documentation with: + - πŸ“¦ Semantic versioning guidelines and validation + - πŸš€ Multiple usage examples (tag push, manual, pre-release, draft) + - πŸ“ Automatic changelog generation and formatting + - 🏷️ Pre-release detection and handling + - πŸ”„ Workflow process and diagrams + - βš™οΈ Configuration options and inputs + - πŸ”§ Troubleshooting guide + - πŸ“‹ Best practices and conventional commits + - πŸ› οΈ Advanced configuration examples + ### Contributing Contributions are welcome! When adding new features: @@ -58,4 +78,5 @@ For information about reporting security vulnerabilities, please see our [Securi - [MacPaw GitHub Organization](https://github.com/MacPaw/) - [GitHub Actions Documentation](https://docs.github.com/en/actions) - [Symfony PHP Package reusable workflow](docs/workflows/symfony-php-reusable.md) +- [Release workflow](docs/workflows/release.md) - [Repository Analysis](ANALYSIS.md) diff --git a/docs/workflows/release.md b/docs/workflows/release.md new file mode 100644 index 0000000..dce058f --- /dev/null +++ b/docs/workflows/release.md @@ -0,0 +1,420 @@ +# Release Workflow + +Automated release workflow with semantic versioning support for GitHub repositories. + +## Overview + +The release workflow automates the process of creating GitHub releases with proper semantic versioning, automatic changelog generation, and flexible release options. + +## Features + +- βœ… **Semantic Versioning** - Strict validation of semver format (MAJOR.MINOR.PATCH) +- πŸ“ **Automatic Changelog** - Grouped by commit types (features, fixes, docs, etc.) +- 🏷️ **Pre-release Support** - Auto-detection or manual specification +- πŸ“¦ **Draft Releases** - Create draft releases for review +- πŸ”„ **Multiple Triggers** - Tag push or manual workflow dispatch +- πŸ“Š **Release Notes** - Formatted changelog with commit links +- πŸ’¬ **Discussion Integration** - Automatically creates discussion in Announcements + +## Triggers + +### 1. Tag Push (Recommended) + +Automatically trigger release creation when pushing a semver tag: + +```bash +# Create and push a tag +git tag v1.2.3 +git push origin v1.2.3 + +# Or create with annotation +git tag -a v1.2.3 -m "Release version 1.2.3" +git push origin v1.2.3 +``` + +### 2. Manual Workflow Dispatch + +Manually trigger release creation from GitHub Actions UI or API: + +```bash +# Using GitHub CLI +gh workflow run release.yml -f version=1.2.3 + +# With options +gh workflow run release.yml \ + -f version=1.2.3 \ + -f prerelease=true \ + -f draft=true +``` + +## Semantic Versioning + +The workflow strictly follows [Semantic Versioning 2.0.0](https://semver.org/) specification: + +### Version Format + +``` +MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] +``` + +- **MAJOR** - Incompatible API changes +- **MINOR** - Backward-compatible functionality additions +- **PATCH** - Backward-compatible bug fixes +- **PRERELEASE** - Optional pre-release identifier (e.g., alpha.1, beta.2, rc.1) +- **BUILD** - Optional build metadata (e.g., +build.123) + +### Valid Examples + +βœ… `1.0.0` - Standard release +βœ… `v1.0.0` - With 'v' prefix (automatically stripped) +βœ… `1.2.3-alpha.1` - Pre-release +βœ… `1.2.3-beta.2` - Pre-release +βœ… `1.2.3-rc.1` - Release candidate +βœ… `1.2.3+build.123` - With build metadata +βœ… `1.2.3-alpha.1+build.123` - Pre-release with build metadata + +### Invalid Examples + +❌ `1.0` - Missing PATCH version +❌ `v1.0.0.0` - Too many version segments +❌ `1.0.0-` - Invalid pre-release format +❌ `latest` - Not a semver version + +## Workflow Inputs + +### Manual Trigger Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `version` | Yes | - | Version to release (e.g., `1.2.3` or `v1.2.3`) | +| `prerelease` | No | `false` | Mark as pre-release | +| `draft` | No | `false` | Create as draft release | + +## Workflow Outputs + +The workflow generates the following outputs: + +- **GitHub Release** - Created at `https://github.com/OWNER/REPO/releases/tag/vX.Y.Z` +- **Changelog** - Automatically generated and grouped by commit type +- **Discussion** - Created in the Announcements category + +## Changelog Generation + +The workflow automatically generates a changelog by analyzing commits between releases: + +### Commit Types + +Commits are automatically grouped by conventional commit prefixes: + +- **✨ Features** - `feat:` or `feature:` +- **πŸ› Bug Fixes** - `fix:` or `bugfix:` +- **πŸ“š Documentation** - `docs:` or `doc:` +- **πŸ”§ Maintenance** - `chore:` +- **πŸ”„ Other Changes** - Everything else + +### Example Changelog + +```markdown +## What's Changed + +### ✨ Features +- feat: add support for PHP 8.4 ([abc123](link)) +- feat: implement new caching strategy ([def456](link)) + +### πŸ› Bug Fixes +- fix: resolve memory leak in parser ([ghi789](link)) + +### πŸ“š Documentation +- docs: update installation guide ([jkl012](link)) + +### πŸ”§ Maintenance +- chore: update dependencies ([mno345](link)) + +**Full Changelog**: https://github.com/owner/repo/compare/v1.0.0...v1.1.0 +``` + +## Usage Examples + +### Example 1: Basic Release (Tag Push) + +Create a standard release by pushing a tag: + +```bash +# Ensure your main branch is up to date +git checkout main +git pull origin main + +# Create and push a tag +git tag v1.0.0 +git push origin v1.0.0 +``` + +**Result:** Creates release `v1.0.0` with auto-generated changelog + +### Example 2: Pre-release (Tag Push) + +Create a pre-release version: + +```bash +# Create a pre-release tag +git tag v2.0.0-beta.1 +git push origin v2.0.0-beta.1 +``` + +**Result:** Creates pre-release `v2.0.0-beta.1` (automatically detected as pre-release) + +### Example 3: Manual Release (Workflow Dispatch) + +Trigger release creation manually from GitHub: + +1. Go to **Actions** β†’ **Release** workflow +2. Click **Run workflow** +3. Fill in the inputs: + - Version: `1.2.3` + - Pre-release: ☐ (unchecked) + - Draft: ☐ (unchecked) +4. Click **Run workflow** + +### Example 4: Draft Release (Workflow Dispatch) + +Create a draft release for review before publishing: + +```bash +gh workflow run release.yml \ + -f version=1.3.0 \ + -f draft=true +``` + +**Result:** Creates draft release `v1.3.0` that requires manual publishing + +### Example 5: Pre-release with Draft (Workflow Dispatch) + +Create a draft pre-release: + +```bash +gh workflow run release.yml \ + -f version=2.0.0-rc.1 \ + -f prerelease=true \ + -f draft=true +``` + +**Result:** Creates draft pre-release `v2.0.0-rc.1` + +## Release Workflow Process + +```mermaid +graph TD + A[Trigger: Tag Push or Manual] --> B[Validate Version] + B --> C{Valid Semver?} + C -->|No| D[❌ Fail: Invalid Version] + C -->|Yes| E[Detect Pre-release] + E --> F[Generate Changelog] + F --> G[Group Commits by Type] + G --> H[Create GitHub Release] + H --> I[Create Discussion] + I --> J[βœ… Release Published] +``` + +## Best Practices + +### 1. Use Conventional Commits + +Follow [Conventional Commits](https://www.conventionalcommits.org/) for better changelog generation: + +```bash +git commit -m "feat: add new authentication method" +git commit -m "fix: resolve null pointer exception" +git commit -m "docs: update API documentation" +git commit -m "chore: update dependencies" +``` + +### 2. Version Bumping Guidelines + +Follow semantic versioning rules: + +- **MAJOR** (1.0.0 β†’ 2.0.0) - Breaking changes +- **MINOR** (1.0.0 β†’ 1.1.0) - New features (backward-compatible) +- **PATCH** (1.0.0 β†’ 1.0.1) - Bug fixes (backward-compatible) + +### 3. Pre-release Workflow + +Use pre-releases for testing: + +```bash +# Alpha release +git tag v2.0.0-alpha.1 +git push origin v2.0.0-alpha.1 + +# Beta release +git tag v2.0.0-beta.1 +git push origin v2.0.0-beta.1 + +# Release candidate +git tag v2.0.0-rc.1 +git push origin v2.0.0-rc.1 + +# Final release +git tag v2.0.0 +git push origin v2.0.0 +``` + +### 4. Protected Tags + +Consider protecting version tags in your repository settings: + +- Go to **Settings** β†’ **Tags** +- Add rule: `v*.*.*` +- Restrict tag creation to specific roles + +### 5. Automated Version Bumping + +Consider using tools to automate version bumping: + +```bash +# Using npm version (if package.json exists) +npm version patch # 1.0.0 β†’ 1.0.1 +npm version minor # 1.0.0 β†’ 1.1.0 +npm version major # 1.0.0 β†’ 2.0.0 +git push --follow-tags + +# Using semantic-release (automated) +npm install --save-dev semantic-release +``` + +## Permissions Required + +The workflow requires the following permissions: + +```yaml +permissions: + contents: write # Create releases and tags + discussions: write # Create release discussions +``` + +These are automatically granted when using `GITHUB_TOKEN`. + +## Troubleshooting + +### Issue: "Version does not follow semantic versioning" + +**Cause:** The provided version doesn't match semver format + +**Solution:** Ensure version follows `MAJOR.MINOR.PATCH` format: +```bash +# ❌ Invalid +git tag 1.0 +git tag v1.0.0.0 + +# βœ… Valid +git tag v1.0.0 +``` + +### Issue: "No previous tag found" + +**Cause:** This is the first release in the repository + +**Solution:** This is normal behavior. The workflow will include all commits in the changelog. + +### Issue: "Release already exists" + +**Cause:** A release with this tag already exists + +**Solution:** +- Delete the existing tag and release: + ```bash + git tag -d v1.0.0 + git push origin :refs/tags/v1.0.0 + gh release delete v1.0.0 --yes + ``` +- Use a different version number + +### Issue: "Permission denied when creating release" + +**Cause:** Insufficient permissions for the workflow + +**Solution:** +- Ensure repository settings allow Actions to create releases +- Go to **Settings** β†’ **Actions** β†’ **General** +- Under "Workflow permissions", select "Read and write permissions" + +### Issue: "Changelog is empty or incorrect" + +**Cause:** No commits between previous and current tag, or commits don't follow conventional format + +**Solution:** +- Ensure commits exist between tags +- Use conventional commit messages for better grouping +- Check that the previous tag reference is correct + +## Advanced Configuration + +### Custom Changelog Format + +If you need a custom changelog format, you can modify the workflow: + +```yaml +- name: Generate changelog + run: | + # Your custom changelog generation logic + git log --pretty=format:"* %s (%an)" > CHANGELOG.md +``` + +### Integration with Other Workflows + +Trigger other workflows after release: + +```yaml +# In another workflow file +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to production + run: echo "Deploying ${{ github.event.release.tag_name }}" +``` + +### Automatic Tagging from CI + +Create tags automatically in your CI pipeline: + +```yaml +# In your main CI workflow +- name: Create tag + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: | + VERSION=$(cat version.txt) + git tag "v${VERSION}" + git push origin "v${VERSION}" +``` + +## References + +- [Semantic Versioning 2.0.0](https://semver.org/) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [GitHub Releases Documentation](https://docs.github.com/en/repositories/releasing-projects-on-github) +- [softprops/action-gh-release](https://github.com/softprops/action-gh-release) + +## Examples from MacPaw + +While MacPaw's repositories don't currently use a centralized release workflow, this workflow follows GitHub Actions best practices and can be used across any repository requiring semantic versioning and automated releases. + +## Contributing + +When updating the release workflow: + +1. Test with draft releases first +2. Validate changelog generation accuracy +3. Update documentation to reflect changes +4. Test both tag push and manual triggers +5. Verify pre-release detection logic + +## Support + +For issues or questions: +- Open an issue in this repository +- Check the troubleshooting section above +- Review GitHub Actions logs for detailed error messages \ No newline at end of file From 9e9acdd93dc27ee756fd0598d7fdec0289bd9552 Mon Sep 17 00:00:00 2001 From: Serhii Donii Date: Thu, 4 Dec 2025 21:51:03 +0200 Subject: [PATCH 2/8] feat: fix secrets --- .github/workflows/symfony-php-reusable.yml | 109 +++++++++++---------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/.github/workflows/symfony-php-reusable.yml b/.github/workflows/symfony-php-reusable.yml index b7858c8..b9678b0 100644 --- a/.github/workflows/symfony-php-reusable.yml +++ b/.github/workflows/symfony-php-reusable.yml @@ -2,6 +2,13 @@ name: Symfony PHP Reusable Workflow on: workflow_call: + secrets: + CODECOV_TOKEN: + description: 'Token for Codecov uploads (optional)' + required: false + INFECTION_BADGE_API_KEY: + description: 'API key for Infection badge (optional)' + required: false inputs: versions-matrix: description: 'JSON object with package versions to test. Format: {"php": ["8.2", "8.3"], "symfony/framework-bundle": ["6.4.*", "7.0.*"], "package/name": ["1.0.*"]}' @@ -131,10 +138,6 @@ on: required: false type: string default: '' - secrets: - codecov-token: - description: 'Codecov token for coverage upload' - required: false env: COMPOSER_ROOT_VERSION: "1.0.0" @@ -143,7 +146,7 @@ jobs: commitlint: name: Commit Lint runs-on: ubuntu-latest - if: inputs.enable-commitlint + if: inputs['enable-commitlint'] steps: - name: Checkout code uses: actions/checkout@v4 @@ -180,7 +183,7 @@ jobs: composer-validate: name: Composer Validate runs-on: ubuntu-latest - if: inputs.enable-composer-validate + if: inputs['enable-composer-validate'] timeout-minutes: 5 steps: - name: Checkout code @@ -193,13 +196,13 @@ jobs: coverage: none - name: Validate composer.json and composer.lock - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: composer validate --strict --no-check-publish phpcs: name: PHP_CodeSniffer runs-on: ubuntu-latest - if: inputs.enable-phpcs + if: inputs['enable-phpcs'] timeout-minutes: 10 steps: - name: Checkout code @@ -210,11 +213,11 @@ jobs: with: php-version: '8.3' coverage: none - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -240,7 +243,7 @@ jobs: php-cs-fixer: name: PHP-CS-Fixer runs-on: ubuntu-latest - if: inputs.enable-php-cs-fixer + if: inputs['enable-php-cs-fixer'] timeout-minutes: 10 steps: - name: Checkout code @@ -251,11 +254,11 @@ jobs: with: php-version: '8.3' coverage: none - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -270,10 +273,10 @@ jobs: run: composer install --no-interaction --no-progress --prefer-dist - name: Run PHP-CS-Fixer - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: | - if [ -n "${{ inputs.php-cs-fixer-config }}" ]; then - vendor/bin/php-cs-fixer fix --dry-run --diff --verbose --config=${{ inputs.php-cs-fixer-config }} + if [ -n "${{ inputs['php-cs-fixer-config'] }}" ]; then + vendor/bin/php-cs-fixer fix --dry-run --diff --verbose --config=${{ inputs['php-cs-fixer-config'] }} else vendor/bin/php-cs-fixer fix --dry-run --diff --verbose fi @@ -281,7 +284,7 @@ jobs: phpstan: name: PHPStan runs-on: ubuntu-latest - if: inputs.enable-phpstan + if: inputs['enable-phpstan'] timeout-minutes: 10 steps: - name: Checkout code @@ -292,11 +295,11 @@ jobs: with: php-version: '8.3' coverage: none - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -311,18 +314,18 @@ jobs: run: composer install --no-interaction --no-progress --prefer-dist - name: Run PHPStan - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: | - if [ -n "${{ inputs.phpstan-config }}" ]; then - vendor/bin/phpstan analyse --level=${{ inputs.phpstan-level }} --no-progress -c ${{ inputs.phpstan-config }} + if [ -n "${{ inputs['phpstan-config'] }}" ]; then + vendor/bin/phpstan analyse --level=${{ inputs['phpstan-level'] }} --no-progress -c ${{ inputs['phpstan-config'] }} else - vendor/bin/phpstan analyse --level=${{ inputs.phpstan-level }} --no-progress + vendor/bin/phpstan analyse --level=${{ inputs['phpstan-level'] }} --no-progress fi rector: name: Rector runs-on: ubuntu-latest - if: inputs.enable-rector + if: inputs['enable-rector'] timeout-minutes: 10 steps: - name: Checkout code @@ -333,11 +336,11 @@ jobs: with: php-version: '8.3' coverage: none - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -352,10 +355,10 @@ jobs: run: composer install --no-interaction --no-progress --prefer-dist - name: Run Rector - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: | - if [ -n "${{ inputs.rector-config }}" ]; then - vendor/bin/rector process --dry-run --config ${{ inputs.rector-config }} + if [ -n "${{ inputs['rector-config'] }}" ]; then + vendor/bin/rector process --dry-run --config ${{ inputs['rector-config'] }} else vendor/bin/rector process --dry-run fi @@ -367,15 +370,15 @@ jobs: strategy: fail-fast: false matrix: - php: ${{ fromJson(fromJson(inputs.versions-matrix).php) }} + php: ${{ fromJson(inputs['versions-matrix']).php }} dependencies: ['highest'] - versions-matrix: ['${{ inputs.versions-matrix }}'] + versions-matrix: ['${{ inputs["versions-matrix"] }}'] include: - - php: ${{ fromJson(fromJson(inputs.versions-matrix).php)[0] }} + - php: ${{ fromJson(inputs['versions-matrix']).php[0] }} dependencies: 'lowest' - coverage: ${{ inputs.enable-code-coverage && 'xdebug' || 'none' }} - versions-matrix: '${{ inputs.versions-matrix }}' - exclude: ${{ fromJson(inputs.matrix-exclude) }} + coverage: ${{ inputs['enable-code-coverage'] && 'xdebug' || 'none' }} + versions-matrix: '${{ inputs["versions-matrix"] }}' + exclude: ${{ fromJson(inputs['matrix-exclude']) }} steps: - name: Checkout code @@ -386,11 +389,11 @@ jobs: with: php-version: ${{ matrix.php }} coverage: ${{ matrix.coverage || 'none' }} - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -405,7 +408,7 @@ jobs: run: | # Parse versions-matrix and apply constraints for all packages except PHP echo "Applying version constraints from matrix..." - VERSIONS_JSON='${{ matrix.versions-matrix }}' + VERSIONS_JSON='${{ matrix["versions-matrix"] }}' # Extract and apply Symfony constraint if present SYMFONY_VERSION=$(echo "$VERSIONS_JSON" | jq -r '."symfony/framework-bundle"[0] // empty') @@ -424,36 +427,36 @@ jobs: - name: Install dependencies (highest) if: matrix.dependencies == 'highest' - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: composer update --no-interaction --no-progress --prefer-dist - name: Install dependencies (lowest) if: matrix.dependencies == 'lowest' - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: composer update --no-interaction --no-progress --prefer-dist --prefer-lowest --prefer-stable - name: Run PHPUnit if: matrix.coverage != 'xdebug' - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: vendor/bin/phpunit --testdox - name: Run PHPUnit with coverage if: matrix.coverage == 'xdebug' - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: vendor/bin/phpunit --testdox --coverage-clover=coverage.xml - name: Upload coverage to Codecov - if: matrix.coverage == 'xdebug' && secrets.codecov-token != '' + if: matrix.coverage == 'xdebug' && secrets.CODECOV_TOKEN != '' uses: codecov/codecov-action@v4 with: - token: ${{ secrets.codecov-token }} - files: ${{ inputs.working-directory }}/coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ inputs['working-directory'] }}/coverage.xml fail_ci_if_error: false infection: name: Infection Mutation Testing runs-on: ubuntu-latest - if: inputs.enable-infection + if: inputs['enable-infection'] timeout-minutes: 20 steps: - name: Checkout code @@ -464,11 +467,11 @@ jobs: with: php-version: '8.3' coverage: xdebug - extensions: ${{ inputs.php-extensions }} + extensions: ${{ inputs['php-extensions'] }} - name: Get Composer cache directory id: composer-cache - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer dependencies @@ -479,15 +482,15 @@ jobs: restore-keys: ${{ runner.os }}-composer- - name: Install dependencies - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: composer install --no-interaction --no-progress --prefer-dist - name: Run Infection - working-directory: ${{ inputs.working-directory }} + working-directory: ${{ inputs['working-directory'] }} run: | - CMD="vendor/bin/infection --min-msi=${{ inputs.infection-min-msi }} --min-covered-msi=${{ inputs.infection-min-covered-msi }} --threads=4 --no-progress" - if [ -n "${{ inputs.infection-config }}" ]; then - CMD="$CMD --configuration=${{ inputs.infection-config }}" + CMD="vendor/bin/infection --min-msi=${{ inputs['infection-min-msi'] }} --min-covered-msi=${{ inputs['infection-min-covered-msi'] }} --threads=4 --no-progress" + if [ -n "${{ inputs['infection-config'] }}" ]; then + CMD="$CMD --configuration=${{ inputs['infection-config'] }}" fi $CMD env: From 89f3c999d017b5db46cb601c89653120e00f152b Mon Sep 17 00:00:00 2001 From: Serhii Donii Date: Thu, 4 Dec 2025 22:23:09 +0200 Subject: [PATCH 3/8] feat: fix secrets --- .github/workflows/release-reusable.yml | 73 ++ .github/workflows/release.yml | 173 +---- .github/workflows/symfony-php-reusable.yml | 16 +- .releaserc.json | 183 +++++ README.md | 43 +- docs/workflows/release.md | 825 +++++++++++++++------ docs/workflows/symfony-php-reusable.md | 47 +- 7 files changed, 938 insertions(+), 422 deletions(-) create mode 100644 .github/workflows/release-reusable.yml create mode 100644 .releaserc.json diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml new file mode 100644 index 0000000..749793c --- /dev/null +++ b/.github/workflows/release-reusable.yml @@ -0,0 +1,73 @@ +name: Release + +on: + workflow_call: + secrets: + GH_TOKEN: + description: 'Token for release' + required: true + inputs: + dry_run: + description: 'Dry run (no release will be created)' + required: false + type: boolean + default: false + +permissions: + contents: write + issues: write + pull-requests: write + discussions: write + +jobs: + release: + name: Semantic Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v4 + id: semantic + with: + dry_run: ${{ inputs.dry_run || false }} + extra_plugins: | + @semantic-release/changelog@6.0.3 + @semantic-release/git@10.0.1 + conventional-changelog-conventionalcommits@7.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + + - name: Summary + if: steps.semantic.outputs.new_release_published == 'true' + run: | + echo "## πŸŽ‰ Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Version:** ${{ steps.semantic.outputs.new_release_version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag:** v${{ steps.semantic.outputs.new_release_version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Channel:** ${{ steps.semantic.outputs.new_release_channel }}" >> $GITHUB_STEP_SUMMARY + echo "- **Release Notes:** Auto-generated by semantic-release" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "πŸ”— [View Release](https://github.com/${{ github.repository }}/releases/tag/v${{ steps.semantic.outputs.new_release_version }})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### πŸ“ Release Notes" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.semantic.outputs.new_release_notes }}" >> $GITHUB_STEP_SUMMARY + + - name: No Release Summary + if: steps.semantic.outputs.new_release_published != 'true' + run: | + echo "## ℹ️ No Release Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No new version was released. This could be because:" >> $GITHUB_STEP_SUMMARY + echo "- No commits since last release with valid conventional commit messages" >> $GITHUB_STEP_SUMMARY + echo "- All commits are non-release types (chore, docs, style, refactor, test)" >> $GITHUB_STEP_SUMMARY + echo "- Running in dry-run mode" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a72be8c..cd0f2c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,166 +2,27 @@ name: Release on: push: - tags: - - 'v*.*.*' + branches: + - main + - master + - next + - beta + - alpha + - '[0-9]+.x' + - '[0-9]+.[0-9]+.x' workflow_dispatch: inputs: - version: - description: 'Version to release (e.g., 1.2.3 or v1.2.3)' - required: true - type: string - prerelease: - description: 'Mark as pre-release' + dry_run: + description: 'Dry run (no release will be created)' required: false type: boolean default: false - draft: - description: 'Create as draft release' - required: false - type: boolean - default: false - -permissions: - contents: write - discussions: write jobs: - validate-version: - name: Validate Version - runs-on: ubuntu-latest - outputs: - version: ${{ steps.validate.outputs.version }} - version_tag: ${{ steps.validate.outputs.version_tag }} - is_prerelease: ${{ steps.validate.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Validate semantic version - id: validate - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ inputs.version }}" - IS_PRERELEASE="${{ inputs.prerelease }}" - else - VERSION="${GITHUB_REF#refs/tags/}" - # Auto-detect prerelease from version string - if [[ "$VERSION" =~ -[a-zA-Z0-9]+ ]]; then - IS_PRERELEASE="true" - else - IS_PRERELEASE="false" - fi - fi - - # Strip 'v' prefix if present - VERSION_NO_V="${VERSION#v}" - VERSION_TAG="v${VERSION_NO_V}" - - # Validate semantic versioning format - if [[ ! "$VERSION_NO_V" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$ ]]; then - echo "Error: Version '$VERSION' does not follow semantic versioning (e.g., 1.2.3, 1.2.3-alpha.1, 1.2.3+build.123)" - exit 1 - fi - - echo "version=${VERSION_NO_V}" >> $GITHUB_OUTPUT - echo "version_tag=${VERSION_TAG}" >> $GITHUB_OUTPUT - echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT - - echo "βœ… Version validated: ${VERSION_NO_V}" - echo "πŸ“Œ Tag: ${VERSION_TAG}" - echo "🏷️ Pre-release: ${IS_PRERELEASE}" - - create-release: - name: Create Release - runs-on: ubuntu-latest - needs: validate-version - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate changelog - id: changelog - run: | - # Get the previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "${{ needs.validate-version.outputs.version_tag }}" | head -n 1) - - if [ -z "$PREVIOUS_TAG" ]; then - echo "No previous tag found, using all commits" - PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) - fi - - echo "Generating changelog from $PREVIOUS_TAG to ${{ needs.validate-version.outputs.version_tag }}" - - # Generate changelog - CHANGELOG=$(git log ${PREVIOUS_TAG}..${{ needs.validate-version.outputs.version_tag }} \ - --pretty=format:"- %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" \ - --no-merges) - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="- No changes recorded" - fi - - # Group changes by type - FEATURES=$(echo "$CHANGELOG" | grep -i "^- feat" || echo "") - FIXES=$(echo "$CHANGELOG" | grep -i "^- fix" || echo "") - DOCS=$(echo "$CHANGELOG" | grep -i "^- docs" || echo "") - CHORES=$(echo "$CHANGELOG" | grep -i "^- chore" || echo "") - OTHERS=$(echo "$CHANGELOG" | grep -v "^- feat" | grep -v "^- fix" | grep -v "^- docs" | grep -v "^- chore" || echo "") - - # Build formatted changelog - FORMATTED_CHANGELOG="## What's Changed\n\n" - - if [ ! -z "$FEATURES" ]; then - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### ✨ Features\n${FEATURES}\n\n" - fi - - if [ ! -z "$FIXES" ]; then - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ› Bug Fixes\n${FIXES}\n\n" - fi - - if [ ! -z "$DOCS" ]; then - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ“š Documentation\n${DOCS}\n\n" - fi - - if [ ! -z "$CHORES" ]; then - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ”§ Maintenance\n${CHORES}\n\n" - fi - - if [ ! -z "$OTHERS" ]; then - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}### πŸ”„ Other Changes\n${OTHERS}\n\n" - fi - - FORMATTED_CHANGELOG="${FORMATTED_CHANGELOG}**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...${{ needs.validate-version.outputs.version_tag }}" - - # Save to file for GitHub release - echo -e "$FORMATTED_CHANGELOG" > CHANGELOG.md - - echo "changelog_file=CHANGELOG.md" >> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.validate-version.outputs.version_tag }} - name: Release ${{ needs.validate-version.outputs.version }} - body_path: CHANGELOG.md - draft: ${{ inputs.draft || false }} - prerelease: ${{ needs.validate-version.outputs.is_prerelease }} - generate_release_notes: false - discussion_category_name: Announcements - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Summary - run: | - echo "## πŸŽ‰ Release Created Successfully!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Version:** ${{ needs.validate-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- **Tag:** ${{ needs.validate-version.outputs.version_tag }}" >> $GITHUB_STEP_SUMMARY - echo "- **Pre-release:** ${{ needs.validate-version.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY - echo "- **Draft:** ${{ inputs.draft || false }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "πŸ”— [View Release](https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-version.outputs.version_tag }})" >> $GITHUB_STEP_SUMMARY + release: + name: Release + uses: ./.github/workflows/release-reusable.yml + secrets: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + dry_run: ${{ inputs.dry_run || false }} diff --git a/.github/workflows/symfony-php-reusable.yml b/.github/workflows/symfony-php-reusable.yml index b9678b0..4fcf688 100644 --- a/.github/workflows/symfony-php-reusable.yml +++ b/.github/workflows/symfony-php-reusable.yml @@ -10,6 +10,11 @@ on: description: 'API key for Infection badge (optional)' required: false inputs: + matrix-include: + description: 'JSON array of additional matrix include items (e.g. [{"php":"8.4","dependencies":"lowest","coverage":"xdebug"}])' + required: false + type: string + default: '[]' versions-matrix: description: 'JSON object with package versions to test. Format: {"php": ["8.2", "8.3"], "symfony/framework-bundle": ["6.4.*", "7.0.*"], "package/name": ["1.0.*"]}' required: false @@ -370,14 +375,9 @@ jobs: strategy: fail-fast: false matrix: - php: ${{ fromJson(inputs['versions-matrix']).php }} + php: ${{ (fromJSON(inputs['versions-matrix']))['php'] }} dependencies: ['highest'] - versions-matrix: ['${{ inputs["versions-matrix"] }}'] - include: - - php: ${{ fromJson(inputs['versions-matrix']).php[0] }} - dependencies: 'lowest' - coverage: ${{ inputs['enable-code-coverage'] && 'xdebug' || 'none' }} - versions-matrix: '${{ inputs["versions-matrix"] }}' + include: ${{ fromJson(inputs['matrix-include']) }} exclude: ${{ fromJson(inputs['matrix-exclude']) }} steps: @@ -408,7 +408,7 @@ jobs: run: | # Parse versions-matrix and apply constraints for all packages except PHP echo "Applying version constraints from matrix..." - VERSIONS_JSON='${{ matrix["versions-matrix"] }}' + VERSIONS_JSON='${{ inputs['versions-matrix'] }}' # Extract and apply Symfony constraint if present SYMFONY_VERSION=$(echo "$VERSIONS_JSON" | jq -r '."symfony/framework-bundle"[0] // empty') diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..573d429 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,183 @@ +{ + "branches": [ + "main", + "master", + { + "name": "next", + "prerelease": true + }, + { + "name": "beta", + "prerelease": true + }, + { + "name": "alpha", + "prerelease": true + }, + { + "name": "[0-9]+.x", + "range": "${name.replace(/\\.x$/, '')}", + "channel": "${name}" + }, + { + "name": "[0-9]+.[0-9]+.x", + "range": "${name.replace(/\\.x$/, '')}", + "channel": "${name}" + } + ], + "preset": "conventionalcommits", + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "perf", + "release": "patch" + }, + { + "type": "revert", + "release": "patch" + }, + { + "type": "docs", + "scope": "README", + "release": "patch" + }, + { + "type": "refactor", + "release": "patch" + }, + { + "type": "style", + "release": false + }, + { + "type": "chore", + "release": false + }, + { + "type": "test", + "release": false + }, + { + "scope": "no-release", + "release": false + }, + { + "breaking": true, + "release": "major" + } + ], + "parserOpts": { + "noteKeywords": [ + "BREAKING CHANGE", + "BREAKING CHANGES", + "BREAKING" + ] + } + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "✨ Features" + }, + { + "type": "fix", + "section": "πŸ› Bug Fixes" + }, + { + "type": "perf", + "section": "⚑ Performance Improvements" + }, + { + "type": "revert", + "section": "βͺ Reverts" + }, + { + "type": "docs", + "section": "πŸ“š Documentation" + }, + { + "type": "style", + "section": "πŸ’„ Styles", + "hidden": true + }, + { + "type": "refactor", + "section": "♻️ Code Refactoring" + }, + { + "type": "test", + "section": "βœ… Tests", + "hidden": true + }, + { + "type": "build", + "section": "πŸ—οΈ Build System" + }, + { + "type": "ci", + "section": "πŸ‘· CI/CD" + }, + { + "type": "chore", + "section": "πŸ”§ Maintenance", + "hidden": true + } + ] + }, + "writerOpts": { + "commitsSort": [ + "subject", + "scope" + ] + } + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines." + } + ], + [ + "@semantic-release/github", + { + "successComment": false, + "failComment": false, + "releasedLabels": false, + "addReleases": "bottom" + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md", + "package.json", + "package-lock.json", + "composer.json", + "composer.lock" + ], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/README.md b/README.md index 8e1ce01..de45a11 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,16 @@ All reusable workflows are located in `.github/workflows` folder - ⚑ **Performance Optimized** - Parallel execution, intelligent caching - [πŸ“š Detailed Documentation](docs/workflows/symfony-php-reusable.md) -- [release.yml](.github/workflows/release.yml) - Automated release workflow with semantic versioning - - βœ… **Semantic Versioning** - Strict semver validation (MAJOR.MINOR.PATCH) - - πŸ“ **Automatic Changelog** - Grouped by commit types (features, fixes, docs) - - 🏷️ **Pre-release Support** - Auto-detection or manual specification - - πŸ“¦ **Draft Releases** - Create draft releases for review - - πŸ”„ **Multiple Triggers** - Tag push or manual workflow dispatch - - πŸ’¬ **Discussion Integration** - Automatic discussion creation +- [release-reusable.yml](.github/workflows/release-reusable.yml) - Fully automated release workflow using semantic-release + - πŸ€– **Fully Automated** - No manual version bumping or changelog writing required + - βœ… **Semantic Versioning** - Automatic version calculation from commit messages + - 🏷️ **Auto Tag & Release** - Automatically creates tags and GitHub releases + - πŸ“ **Auto-Generated Changelogs** - Beautiful, categorized release notes + - 🎯 **Conventional Commits** - Based on conventional commit standards + - πŸ”€ **Multi-Branch Support** - main/master, next, beta, alpha, maintenance branches + - πŸ›‘οΈ **Safe & Idempotent** - Won't create duplicate releases + - πŸ§ͺ **Dry Run Mode** - Test releases without publishing + - πŸ”‘ **Secure Authentication** - Uses GH_TOKEN repository secret for release operations - [πŸ“š Detailed Documentation](docs/workflows/release.md) ### Documentation @@ -44,16 +47,22 @@ All reusable workflows are located in `.github/workflows` folder - πŸ“Š Analysis of MacPaw's Symfony repositories - πŸ“ˆ Best practices and recommendations -- [Release Workflow](docs/workflows/release.md) - Complete release workflow documentation with: - - πŸ“¦ Semantic versioning guidelines and validation - - πŸš€ Multiple usage examples (tag push, manual, pre-release, draft) - - πŸ“ Automatic changelog generation and formatting - - 🏷️ Pre-release detection and handling - - πŸ”„ Workflow process and diagrams - - βš™οΈ Configuration options and inputs - - πŸ”§ Troubleshooting guide - - πŸ“‹ Best practices and conventional commits - - πŸ› οΈ Advanced configuration examples +- [Release Workflow](docs/workflows/release.md) - Complete semantic-release documentation with: + - πŸ€– Fully automated release process using semantic-release + - πŸ“ Conventional commits specification and examples + - 🏷️ Automatic version calculation (feat β†’ minor, fix β†’ patch, BREAKING β†’ major) + - πŸš€ Multiple usage examples (feature, bugfix, breaking changes, pre-releases) + - πŸ”€ Multi-branch release channels (main, beta, alpha, maintenance) + - πŸ“Š Auto-generated categorized changelogs + - πŸ§ͺ Dry run mode for testing releases + - πŸ”‘ GH_TOKEN secret configuration and setup guide + - πŸ” GitHub App token support for organizations + - 🎯 Fine-grained Personal Access Token (PAT) instructions + - βš™οΈ Configuration via .releaserc.json + - πŸ”§ Comprehensive troubleshooting guide + - πŸ“‹ Best practices for conventional commits + - πŸ› οΈ Advanced configuration and customization examples + - πŸ”„ Migration guide from manual releases ### Contributing diff --git a/docs/workflows/release.md b/docs/workflows/release.md index dce058f..a83e340 100644 --- a/docs/workflows/release.md +++ b/docs/workflows/release.md @@ -1,284 +1,470 @@ # Release Workflow -Automated release workflow with semantic versioning support for GitHub repositories. +Automated release workflow using semantic-release for fully automated version management and package publishing. ## Overview -The release workflow automates the process of creating GitHub releases with proper semantic versioning, automatic changelog generation, and flexible release options. +The release workflow uses [semantic-release](https://semantic-release.gitbook.io/) to automate the entire release process based on [Conventional Commits](https://www.conventionalcommits.org/). It automatically determines the next version number, generates release notes, creates GitHub releases, and updates the changelog - all without manual intervention. ## Features -- βœ… **Semantic Versioning** - Strict validation of semver format (MAJOR.MINOR.PATCH) -- πŸ“ **Automatic Changelog** - Grouped by commit types (features, fixes, docs, etc.) -- 🏷️ **Pre-release Support** - Auto-detection or manual specification -- πŸ“¦ **Draft Releases** - Create draft releases for review -- πŸ”„ **Multiple Triggers** - Tag push or manual workflow dispatch -- πŸ“Š **Release Notes** - Formatted changelog with commit links -- πŸ’¬ **Discussion Integration** - Automatically creates discussion in Announcements +- πŸ€– **Fully Automated** - No manual version bumping or changelog writing +- βœ… **Semantic Versioning** - Automatic version calculation based on commit messages +- πŸ“ **Auto-Generated Changelogs** - Beautiful, categorized release notes +- 🏷️ **Auto Tag & Release** - Automatically creates tags and GitHub releases +- πŸ”€ **Multi-Branch Support** - main/master, next, beta, alpha, and maintenance branches +- 🎯 **Conventional Commits** - Enforces commit message standards +- πŸ›‘οΈ **Safe & Idempotent** - Won't create duplicate releases +- πŸ§ͺ **Dry Run Mode** - Test releases without publishing +- πŸ”‘ **Flexible Token** - Use default or custom GitHub token + +## How It Works + +1. **Analyze Commits** - Scans commits since last release using Conventional Commits format +2. **Determine Version** - Calculates next version based on commit types: + - `feat:` β†’ Minor version bump (1.0.0 β†’ 1.1.0) + - `fix:` β†’ Patch version bump (1.0.0 β†’ 1.0.1) + - `BREAKING CHANGE:` β†’ Major version bump (1.0.0 β†’ 2.0.0) +3. **Generate Changelog** - Creates categorized release notes +4. **Create Tag** - Creates git tag with version number +5. **Publish Release** - Creates GitHub release with notes +6. **Update Files** - Commits CHANGELOG.md back to repository ## Triggers -### 1. Tag Push (Recommended) +### 1. Automatic (Push to Branch) -Automatically trigger release creation when pushing a semver tag: +The workflow automatically runs when code is pushed to release branches: ```bash -# Create and push a tag -git tag v1.2.3 -git push origin v1.2.3 - -# Or create with annotation -git tag -a v1.2.3 -m "Release version 1.2.3" -git push origin v1.2.3 +# Push to main/master branch +git push origin main + +# Semantic-release will: +# 1. Analyze commits since last release +# 2. Determine if a release is needed +# 3. Calculate next version +# 4. Create tag and release automatically ``` -### 2. Manual Workflow Dispatch +**Supported branches:** +- `main` or `master` - Production releases +- `next` - Pre-releases for next major version +- `beta` - Beta pre-releases +- `alpha` - Alpha pre-releases +- `N.x` or `N.N.x` - Maintenance releases (e.g., `1.x`, `1.0.x`) + +### 2. Manual Trigger (Workflow Dispatch) -Manually trigger release creation from GitHub Actions UI or API: +Manually trigger the workflow from GitHub Actions UI or CLI: ```bash # Using GitHub CLI -gh workflow run release.yml -f version=1.2.3 +gh workflow run release.yml -# With options -gh workflow run release.yml \ - -f version=1.2.3 \ - -f prerelease=true \ - -f draft=true +# With dry run (no release will be created) +gh workflow run release.yml -f dry_run=true ``` -## Semantic Versioning +## Conventional Commits -The workflow strictly follows [Semantic Versioning 2.0.0](https://semver.org/) specification: +Semantic-release requires commits to follow the [Conventional Commits](https://www.conventionalcommits.org/) specification. -### Version Format +### Commit Format ``` -MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] +(): + + + +