Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ on:
enable_ghcr:
description: 'Enable pushing to GitHub Container Registry'
type: boolean
default: false
default: true
dockerhub_org:
description: 'DockerHub organization name'
type: string
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/go-pr-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ on:
fail_on_coverage_threshold:
description: 'Fail the workflow if coverage is below threshold'
type: boolean
default: false
default: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
enable_lint:
description: 'Enable GolangCI-Lint'
type: boolean
Expand Down Expand Up @@ -213,7 +213,11 @@ jobs:
- name: Run GolangCI-Lint (direct)
if: steps.detect-make.outputs.use_make != 'true'
working-directory: ${{ matrix.app.working_dir }}
run: golangci-lint run ${{ inputs.golangci_lint_args }}
env:
GOLANGCI_LINT_ARGS: ${{ inputs.golangci_lint_args }}
run: |
read -ra golangci_args <<< "$GOLANGCI_LINT_ARGS"
golangci-lint run "${golangci_args[@]}"

# ============================================
# SECURITY SCANNING
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/go-pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,22 @@ on:
description: 'GolangCI-Lint version'
type: string
default: 'v1.62.2'
golangci_lint_args:
description: 'Extra arguments passed to golangci-lint (e.g. --timeout=5m)'
type: string
default: '--timeout=5m'
app_name_prefix:
description: 'Prefix used to namespace coverage/build artifacts'
type: string
default: ''
Comment thread
coderabbitai[bot] marked this conversation as resolved.
coverage_threshold:
description: 'Minimum coverage percentage required (0-100)'
type: number
default: 80
fail_on_coverage_threshold:
description: 'Fail the workflow if coverage is below threshold'
type: boolean
default: false
default: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
go_private_modules:
description: 'GOPRIVATE pattern for private Go modules (e.g., github.com/LerianStudio/*)'
type: string
Expand Down Expand Up @@ -208,6 +216,8 @@ jobs:
runner_type: ${{ inputs.runner_type }}
go_version: ${{ inputs.go_version }}
golangci_lint_version: ${{ inputs.golangci_lint_version }}
golangci_lint_args: ${{ inputs.golangci_lint_args }}
app_name_prefix: ${{ inputs.app_name_prefix }}
coverage_threshold: ${{ inputs.coverage_threshold }}
fail_on_coverage_threshold: ${{ inputs.fail_on_coverage_threshold }}
go_private_modules: ${{ inputs.go_private_modules }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/go-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ on:
enable_ghcr:
description: 'Enable pushing to GitHub Container Registry'
type: boolean
default: false
default: true
enable_gitops_artifacts:
description: 'Enable GitOps artifacts upload for the downstream gitops-update job'
type: boolean
Expand Down
104 changes: 70 additions & 34 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,33 @@ on:
type: string
default: 'openai/gpt-4o'

# ----------------- Backmerge -----------------
backmerge_enabled:
description: 'Backmerge the release branch into the target branch after a successful release'
required: false
type: boolean
default: true
backmerge_source:
description: 'Release branch eligible for backmerge; backmerge runs only when the release ref matches this'
required: false
type: string
default: 'main'
backmerge_target:
description: 'Branch that receives the backmerge'
required: false
type: string
default: 'develop'
backmerge_mode:
description: 'Backmerge strategy: direct | pr | direct-with-pr-fallback'
required: false
type: string
default: 'direct-with-pr-fallback'
dry_run:
description: 'Run semantic-release in dry-run mode (no tags/releases created) and preview the backmerge instead of applying it'
required: false
type: boolean
default: false

Comment thread
coderabbitai[bot] marked this conversation as resolved.
permissions:
contents: read

Expand Down Expand Up @@ -170,58 +197,68 @@ jobs:
npm install --save-dev \
@semantic-release/exec

# ----------------- Snapshot tags before release -----------------
- name: Snapshot tags before release
id: pre-tags
uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-snapshot@v1
# ----------------- Backmerge config preflight -----------------
- name: Validate backmerge inputs
if: inputs.backmerge_enabled
env:
BACKMERGE_MODE: ${{ inputs.backmerge_mode }}
BACKMERGE_SOURCE: ${{ inputs.backmerge_source }}
BACKMERGE_TARGET: ${{ inputs.backmerge_target }}
run: |
case "$BACKMERGE_MODE" in
direct|pr|direct-with-pr-fallback) ;;
*)
echo "::error::Invalid backmerge_mode '$BACKMERGE_MODE' (must be: direct, pr, direct-with-pr-fallback)"
exit 1
;;
esac
if [ "$BACKMERGE_SOURCE" = "$BACKMERGE_TARGET" ]; then
echo "::error::backmerge_source and backmerge_target must differ (got '$BACKMERGE_SOURCE')"
exit 1
fi
if ! git check-ref-format --branch "$BACKMERGE_SOURCE" >/dev/null 2>&1; then
echo "::error::Invalid backmerge_source ref: '$BACKMERGE_SOURCE'"
exit 1
fi
if ! git check-ref-format --branch "$BACKMERGE_TARGET" >/dev/null 2>&1; then
echo "::error::Invalid backmerge_target ref: '$BACKMERGE_TARGET'"
exit 1
fi

- name: Semantic Release
uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6
id: semantic
continue-on-error: true
with:
ci: false
dry_run: ${{ inputs.dry_run }}
semantic_version: ${{ inputs.semantic_version }}
working_directory: ${{ matrix.app.working_dir }}
extra_plugins: |
conventional-changelog-conventionalcommits@v7.0.2
@saithodev/semantic-release-backmerge
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GIT_AUTHOR_NAME: ${{ secrets.LERIAN_CI_CD_USER_NAME }}
GIT_AUTHOR_EMAIL: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }}
GIT_COMMITTER_NAME: ${{ secrets.LERIAN_CI_CD_USER_NAME }}
GIT_COMMITTER_EMAIL: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }}

# ----------------- Detect release via git tag -----------------
- name: Detect if release was published
if: always() && steps.semantic.outcome == 'failure'
id: detect-release
uses: LerianStudio/github-actions-shared-workflows/src/config/release-tag-check@v1
with:
previous-tag: ${{ steps.pre-tags.outputs.latest-tag }}

# ----------------- Backmerge Fallback -----------------
- name: Backmerge PR fallback
# ----------------- Backmerge -----------------
- name: Backmerge ${{ inputs.backmerge_source }} → ${{ inputs.backmerge_target }}
if: |
always() && steps.semantic.outcome == 'failure' && (
steps.semantic.outputs.new_release_published == 'true' ||
steps.detect-release.outputs.release-published == 'true'
)
uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@v1
inputs.backmerge_enabled &&
steps.semantic.outputs.new_release_published == 'true' &&
github.ref_name == inputs.backmerge_source
uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-sync@v1
with:
github-token: ${{ steps.app-token.outputs.token }}
source-branch: ${{ github.ref_name }}
version: ${{ steps.semantic.outputs.new_release_version || steps.detect-release.outputs.release-version }}

- name: Fail if release itself failed
if: |
always() && steps.semantic.outcome == 'failure' &&
steps.semantic.outputs.new_release_published != 'true' &&
steps.detect-release.outputs.release-published != 'true'
run: |
echo "::error::Semantic release failed before publishing a new version"
exit 1
source-branch: ${{ inputs.backmerge_source }}
target-branch: ${{ inputs.backmerge_target }}
mode: ${{ inputs.backmerge_mode }}
dry-run: ${{ inputs.dry_run }}
commit-message: "chore(release): backmerge ${source} into ${target} [skip ci]"
pr-title: "chore(release): backmerge ${source} → ${target} (v${{ steps.semantic.outputs.new_release_version }})"
git-user-name: ${{ secrets.LERIAN_CI_CD_USER_NAME }}
git-user-email: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
# ----------------- Per-leg release publish marker -----------------
# Matrix job outputs are last-writer-wins, so we persist each leg's
Expand All @@ -230,12 +267,11 @@ jobs:
if: always()
env:
SEMANTIC_PUBLISHED: ${{ steps.semantic.outputs.new_release_published }}
DETECT_PUBLISHED: ${{ steps.detect-release.outputs.release-published }}
APP_NAME: ${{ matrix.app.name }}
STATUS_DIR: ${{ runner.temp }}/publish-status
run: |
mkdir -p "$STATUS_DIR"
if [[ "$SEMANTIC_PUBLISHED" == "true" || "$DETECT_PUBLISHED" == "true" ]]; then
if [[ "$SEMANTIC_PUBLISHED" == "true" ]]; then
echo "true" > "$STATUS_DIR/${APP_NAME}.txt"
echo "✅ ${APP_NAME}: release published"
else
Expand Down
9 changes: 0 additions & 9 deletions .releaserc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,9 @@ plugins:
failComment: false
labels: []

# Plugin to perform the backmerge between the main and develop branches
- path: "@saithodev/semantic-release-backmerge"
backmergeBranches:
- from: main
to: develop
backmergeStrategy: merge
message: "chore(release): backmerge ${nextRelease.version} [skip ci]"

- path: "@semantic-release/exec"

branches:
- name: main
- name: develop
prerelease: beta

2 changes: 1 addition & 1 deletion docs/build-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
| `filter_paths` | string | `''` | Newline-separated list of path prefixes. If empty, builds from root (single-app mode) |
| `path_level` | string | `2` | Directory depth for app name extraction |
| `enable_dockerhub` | boolean | `true` | Enable pushing to DockerHub |
| `enable_ghcr` | boolean | `false` | Enable pushing to GitHub Container Registry |
| `enable_ghcr` | boolean | `true` | Enable pushing to GitHub Container Registry (requires `MANAGE_TOKEN`) |
| `dockerhub_org` | string | `lerianstudio` | DockerHub organization name |
| `ghcr_org` | string | `''` | GHCR organization (defaults to repository owner) |
| `dockerfile_name` | string | `Dockerfile` | Name of the Dockerfile |
Expand Down
4 changes: 2 additions & 2 deletions docs/go-pr-analysis-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
| `golangci_lint_version` | GolangCI-Lint version | No | `v1.62.2` |
| `golangci_lint_args` | Additional golangci-lint arguments | No | `--timeout=5m` |
| `coverage_threshold` | Minimum coverage percentage (0-100) | No | `80` |
| `fail_on_coverage_threshold` | Fail if coverage below threshold | No | `false` |
| `fail_on_coverage_threshold` | Fail if coverage below threshold | No | `true` |
| `enable_lint` | Enable GolangCI-Lint | No | `true` |
| `enable_security` | Enable security scanning (gosec, govulncheck) | No | `true` |
| `enable_tests` | Enable unit tests | No | `true` |
Expand Down Expand Up @@ -316,7 +316,7 @@ swagger.go

1. **Pin to version tag**: Use `@v1.0.0` instead of `@v1.0.0` for production stability
2. **Custom linting**: Place `.golangci.yml` in each app directory for app-specific rules
3. **Coverage threshold**: Start with `fail_on_coverage_threshold: false` and enable once baseline is established
3. **Coverage threshold**: Enforced by default (`fail_on_coverage_threshold: true`); set it to `false` to temporarily report coverage without blocking while establishing a baseline
4. **Security findings**: GoSec results appear in GitHub Security tab when SARIF upload succeeds
5. **Performance**: Jobs run in parallel per app - more apps = more parallelism
6. **Makefile consistency**: Use Makefiles to ensure local dev matches CI behavior
Expand Down
4 changes: 3 additions & 1 deletion docs/go-pr-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ The `go-analysis`, `security` and `lib-version` pipelines each have a `*-gate` a
| `target_branches_for_source_check` | Target branches requiring source validation | string | `main` |
| `go_version` | Go version | string | `1.23` |
| `golangci_lint_version` | GolangCI-Lint version | string | `v1.62.2` |
| `golangci_lint_args` | Extra arguments passed to golangci-lint (e.g. `--timeout=5m`) | string | `--timeout=5m` |
| `app_name_prefix` | Prefix used to namespace coverage/build artifacts | string | `''` |
| `coverage_threshold` | Minimum coverage percentage (0-100) | number | `80` |
| `fail_on_coverage_threshold` | Fail when coverage is below threshold | boolean | `false` |
| `fail_on_coverage_threshold` | Fail when coverage is below threshold | boolean | `true` |
| `go_private_modules` | GOPRIVATE pattern for private modules | string | `''` |
| `enable_integration_tests` | Enable integration tests | boolean | `false` |
| `system_packages` | apt packages to install for CGO repos | string | `''` |
Expand Down
2 changes: 1 addition & 1 deletion docs/go-release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Umbrella reusable workflow for Go **service** repositories (deployable apps that
| `enable_major_tag` | Force-update the floating major tag (e.g. `v1`) | boolean | `false` |
| `stable_releases_only` | Only generate changelogs for stable releases | boolean | `true` |
| `enable_dockerhub` | Push image to DockerHub | boolean | `true` |
| `enable_ghcr` | Push image to GitHub Container Registry | boolean | `false` |
| `enable_ghcr` | Push image to GitHub Container Registry (requires `MANAGE_TOKEN`) | boolean | `true` |
| `enable_gitops_artifacts` | Upload GitOps artifacts for the downstream update | boolean | `false` |
| `app_name` | Override app/image name (single-app mode) | string | `''` (repo name) |
| `docker_build_args` | Newline-separated Docker build args | string | `''` |
Expand Down
13 changes: 9 additions & 4 deletions docs/release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ jobs:
|-------|------|---------|-------------|
| `semantic_version` | string | `23.0.8` | Semantic release version to use |
| `runner_type` | string | `firmino-lxc-runners` | GitHub runner type |
| `backmerge_enabled` | boolean | `true` | Backmerge the release branch into the target branch after a successful release |
| `backmerge_source` | string | `main` | Release branch eligible for backmerge; backmerge runs only when the release ref matches this |
| `backmerge_target` | string | `develop` | Branch that receives the backmerge |
| `backmerge_mode` | string | `direct-with-pr-fallback` | Backmerge strategy: `direct`, `pr`, or `direct-with-pr-fallback` |
| `dry_run` | boolean | `false` | Run semantic-release in dry-run mode (no tags/releases) and preview the backmerge instead of applying it |

## Secrets

Expand Down Expand Up @@ -205,11 +210,10 @@ plugins:
- "@semantic-release/release-notes-generator"
- "@semantic-release/changelog"
- "@semantic-release/github"
- - "@saithodev/semantic-release-backmerge"
- backmergeBranches: [develop]
backmergeStrategy: merge
```

> **Migration:** backmerge is now orchestrated by the workflow, not by semantic-release. Remove any `@saithodev/semantic-release-backmerge` plugin entry from your `.releaserc` and configure backmerge through the `backmerge_*` workflow inputs instead.

## Workflow Steps

1. **Create GitHub App Token**: Generate authentication token with higher rate limits
Expand Down Expand Up @@ -456,7 +460,8 @@ jobs:
- **@semantic-release/github**: Creates GitHub releases
- **@semantic-release/exec**: Executes custom scripts (installed automatically)
- **conventional-changelog-conventionalcommits**: Conventional commits support
- **@saithodev/semantic-release-backmerge**: Automatic backmerging

Backmerging is no longer handled by a semantic-release plugin. After a successful release, the workflow runs the `backmerge-sync` composite action (controlled by the `backmerge_*` inputs) to sync `backmerge_source` into `backmerge_target`. Behavior depends on `backmerge_mode`: `direct` (fail on conflict), `pr` (always open a PR), or `direct-with-pr-fallback` (attempt a direct merge, open a PR on conflict or rejected push).

### Custom Plugins

Expand Down