diff --git a/README.md b/README.md index ceb9982..fbe0107 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,17 @@ Reusable GitHub Actions workflows forming the CI baseline for every BruzIT repos ## Features +- [Semantic Release Action](#semantic-release-composite-action) - [Semantic Release Workflow](#reusable-semantic-release-workflow) - [MegaLinter Workflow](#reusable-megalinter-workflow) +### Semantic Release Composite Action + +[Semantic Release composite action](semantic-release/action.yaml) using the Conventional Commits preset to automate versioning, tags with SemVer and major tag, generates [GitHub releases](https://github.com/bruzit/github-actions-and-workflows/releases), and updates the [CHANGELOG](CHANGELOG.md). It checks out the repository with the GitHub App token, so the changelog commit and the major tags are pushed as the GitHub App; without `app-id` it uses `GITHUB_TOKEN`. + ### Reusable Semantic Release Workflow -Reusable [Semantic Release workflow](.github/workflows/semantic-release.yaml) using the Conventional Commits preset to automate versioning, tags with SemVer and major tag, generates [GitHub releases](https://github.com/bruzit/github-actions-and-workflows/releases), and updates the [CHANGELOG](CHANGELOG.md). +Reusable [Semantic Release workflow](.github/workflows/semantic-release.yaml), similar to the [Semantic Release Action](#semantic-release-composite-action). ### Reusable MegaLinter Workflow @@ -17,7 +22,7 @@ Reusable [MegaLinter workflow](.github/workflows/megalinter.yaml) linting pull r ## Usage -### Use Semantic Release Workflow +### Use Semantic Release Action Create a workflow, for example, `.github/workflows/semantic-release.yaml`: @@ -33,18 +38,22 @@ on: jobs: release: name: Release - uses: bruzit/github-actions-and-workflows/.github/workflows/semantic-release.yaml@v0 + runs-on: ubuntu-latest permissions: contents: write issues: write pull-requests: write - with: - GH_SEM_REL_APP_ID: ${{ vars.GH_SEM_REL_APP_ID }} - semantic_release_plugins: "@semantic-release/exec" # OPTIONAL Space-separated list of additional semantic-release plugins to install. - secrets: - GH_SEM_REL_APP_PEM_FILE: ${{ secrets.GH_SEM_REL_APP_PEM_FILE }} + steps: + - name: Semantic Release + uses: bruzit/github-actions-and-workflows/semantic-release@v0 + with: + app-id: ${{ vars.GH_SEM_REL_APP_ID }} + app-private-key: ${{ secrets.GH_SEM_REL_APP_PEM_FILE }} + plugins: "@semantic-release/exec" # OPTIONAL Space-separated list of additional semantic-release plugins to install. ``` +The action checks out the repository itself. A local `uses: ./semantic-release` needs a prior `actions/checkout` with `persist-credentials: false`. + To create a GitHub App and a GitHub App Installation: - GitHub @@ -82,6 +91,34 @@ To create a GitHub App and a GitHub App Installation: Configure Semantic Release in the repository, for example like this repository's [`.releaserc.yaml`](.releaserc.yaml). +### Use Semantic Release Workflow + +Similar to [Use Semantic Release Action](#use-semantic-release-action), with the reusable workflow: + +```yaml +--- +name: Semantic Release + +on: + push: + branches: + - main + +jobs: + release: + name: Release + uses: bruzit/github-actions-and-workflows/.github/workflows/semantic-release.yaml@v0 + permissions: + contents: write + issues: write + pull-requests: write + with: + GH_SEM_REL_APP_ID: ${{ vars.GH_SEM_REL_APP_ID }} + semantic_release_plugins: "@semantic-release/exec" # OPTIONAL Space-separated list of additional semantic-release plugins to install. + secrets: + GH_SEM_REL_APP_PEM_FILE: ${{ secrets.GH_SEM_REL_APP_PEM_FILE }} +``` + ### Use MegaLinter Workflow Create `.github/workflows/megalinter.yaml`: diff --git a/semantic-release/action.yaml b/semantic-release/action.yaml new file mode 100644 index 0000000..fa7cf10 --- /dev/null +++ b/semantic-release/action.yaml @@ -0,0 +1,71 @@ +--- +name: Semantic Release +description: Run semantic-release with the Conventional Commits preset and move the major tags. +inputs: + app-id: + required: false + default: '' + description: GitHub App ID used to create the GitHub App token. + app-private-key: + required: false + default: '' + description: GitHub App private key used to create the GitHub App token. + plugins: + required: false + default: '' + description: Space-separated list of additional semantic-release plugins to install. +runs: + using: composite + steps: + - name: Create GitHub App token + uses: actions/create-github-app-token@v3 + id: gh-app-token + if: inputs.app-id != '' + with: + app-id: ${{ inputs.app-id }} + private-key: ${{ inputs.app-private-key }} + repositories: ${{ github.repository }} + permission-contents: write + permission-issues: write + permission-pull-requests: write + - name: Checkout repository + uses: actions/checkout@v7 + with: + token: ${{ steps.gh-app-token.outputs.token || github.token }} + fetch-depth: 0 + persist-credentials: true + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: 'lts/*' + - name: Install Semantic Release # zizmor: ignore[adhoc-packages] lockfiles are not committed by design + shell: bash + env: + SEMANTIC_RELEASE_PLUGINS: ${{ inputs.plugins }} + run: | + mapfile -t plugins <<< "$SEMANTIC_RELEASE_PLUGINS" + npm install --ignore-scripts semantic-release conventional-changelog-conventionalcommits@9 @semantic-release/changelog @semantic-release/git "${plugins[@]}" + - name: Verify Semantic Release + shell: bash + run: npm audit signatures + - name: Run Semantic Release + shell: bash + env: + GITHUB_TOKEN: ${{ steps.gh-app-token.outputs.token || github.token }} + run: npx semantic-release + - name: Move major tags + if: ${{ !cancelled() }} + shell: bash + run: | + git fetch --force --prune --prune-tags origin + re='^(v(0|[1-9][0-9]*))\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\+[0-9A-Za-z.-]+)?$' + declare -A latest=() + while read -r tag; do + [[ $tag =~ $re ]] && latest[${BASH_REMATCH[1]}]=$tag + done < <(git tag --list 'v*.*.*' --merged HEAD --sort=v:refname) + for major in "${!latest[@]}"; do + tag=${latest[$major]} + [ "$(git rev-parse -q --verify "refs/tags/$major^{commit}")" = "$(git rev-parse "$tag^{commit}")" ] && continue + git tag --force "$major" "$tag" + git push --force origin "refs/tags/$major" + done