Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0e17a8a
refactor(pr-validation): modularize workflow into composites under sr…
bedatty Mar 24, 2026
a8aebf0
fix(pr-validation): address CodeRabbit and CodeQL review findings
bedatty Mar 24, 2026
99dd556
fix(helm-update-chart): use VALUES_KEY for template file paths instea…
gandalf-at-lerian Mar 25, 2026
f7b22fa
fix(helm-update-chart): quote GITHUB_OUTPUT and GITHUB_STEP_SUMMARY r…
bedatty Mar 25, 2026
566bf29
fix(helm-update-chart): resolve CodeQL medium findings
bedatty Mar 25, 2026
1bb25cd
docs(rules): enforce commit SHA pinning for third-party actions
bedatty Mar 25, 2026
dc22651
Merge pull request #168 from LerianStudio/fix/helm-update-chart-path-bug
bedatty Mar 25, 2026
bc414d0
refactor(pr-validation): extract pr-checks-summary composite and use …
bedatty Mar 25, 2026
1fd5fa8
fix(pr-validation): add missing README and fix broken doc link
bedatty Mar 25, 2026
fd277d0
refactor(pr-validation): optimize to 2-tier fail-fast model
bedatty Mar 25, 2026
1748e31
fix(pr-changelog): remove comment logic — changelog is auto-generated
bedatty Mar 25, 2026
943db14
fix(pr-validation): default enforce_source_branches to true
bedatty Mar 25, 2026
83d82e5
fix(pr-description): validate real content instead of raw length
bedatty Mar 25, 2026
3b51195
feat(pr-metadata): auto-assign PR author instead of warning
bedatty Mar 25, 2026
8bc6ebd
fix(pr-size): skip label update when unchanged and remove XL comment
bedatty Mar 25, 2026
2a4a541
fix(pr-labels): pin actions/labeler to commit SHA
bedatty Mar 25, 2026
8d11a57
refactor(pr-validation): remove changelog check and pin all actions b…
bedatty Mar 25, 2026
c15e1db
fix(pr-checks-summary): use markdown tables grouped by tier
bedatty Mar 25, 2026
4a8e866
fix(pr-validation): address CodeRabbit review findings
bedatty Mar 25, 2026
865936d
fix(pr-validation): sync defaults, fix caller, update docs
bedatty Mar 25, 2026
606c88a
fix(pr-validation): pin composite refs to v1.19.1-beta.2
bedatty Mar 25, 2026
4a70c58
Merge pull request #164 from LerianStudio/refactor/modularize-pr-vali…
bedatty Mar 25, 2026
bd3661f
fix(lint): enforce SHA pinning for externals, warnings for internals
bedatty Mar 25, 2026
823880d
fix(pr-validation): pin composite refs to v1.20.0
bedatty Mar 26, 2026
55d36b3
fix(pr-blocking-collect): add README and pin ref to v1.20.0
bedatty Mar 26, 2026
bd7fc56
fix(pr-blocking-collect): use branch ref for testing
bedatty Mar 26, 2026
b4f95c9
docs(pr-blocking-collect): fix terminology — step outputs, not job ou…
bedatty Mar 26, 2026
20b73d4
fix(pr-validation): pin composite refs to v1.20.0 (#172)
bedatty Mar 26, 2026
36cc523
Merge remote-tracking branch 'origin/main' into develop
bedatty Mar 26, 2026
9b488d4
feat(release): fallback to PR when backmerge push fails
bedatty Mar 26, 2026
f908258
fix(pr-validation): pin composite refs to v1.20.1
bedatty Mar 26, 2026
f260b47
feat(release): extract backmerge fallback into reusable composite
bedatty Mar 26, 2026
7e444f0
Merge branch 'fix/pin-refs-v1.20.0' into develop
bedatty Mar 26, 2026
c8b2867
Merge branch 'main' into develop
bedatty Mar 26, 2026
0f38162
fix(release): use @develop ref for backmerge-pr composite
bedatty Mar 26, 2026
b18a15d
fix(backmerge-pr): use heredoc to avoid indentation in PR body
bedatty Mar 26, 2026
67ff2cc
fix(pr-description): validate checkboxes only, not description content
bedatty Mar 26, 2026
3aa1a20
fix(pr-description): simplify to empty body check only
bedatty Mar 26, 2026
14e764a
fix(ci): use @develop ref for pr-description, sync backmerge-pr
bedatty Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ on:
description: 'Require scope in PR title'
type: boolean
default: false
min_description_length:
description: 'Minimum PR description content length (after stripping template boilerplate)'
type: number
default: 30
enable_auto_labeler:
description: 'Enable automatic labeling based on changed files'
type: boolean
Expand Down Expand Up @@ -93,7 +89,7 @@ jobs:
id: source-branch
if: inputs.enforce_source_branches
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-source-branch@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-source-branch@v1.20.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
allowed-branches: ${{ inputs.allowed_source_branches }}
Expand All @@ -103,7 +99,7 @@ jobs:
- name: Validate PR title
id: title
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-title@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-title@v1.20.1
with:
github-token: ${{ github.token }}
types: ${{ inputs.pr_title_types }}
Expand All @@ -113,13 +109,11 @@ jobs:
- name: Validate PR description
id: description
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-description@v1.20.0
with:
min-length: ${{ inputs.min_description_length }}
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-description@develop

- name: Collect results and enforce blocking
id: collect
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-blocking-collect@fix/pin-refs-v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-blocking-collect@v1.20.1
with:
source-branch-outcome: ${{ steps.source-branch.outcome || 'skipped' }}
title-outcome: ${{ steps.title.outcome }}
Expand All @@ -145,15 +139,15 @@ jobs:
- name: Check PR metadata
id: metadata
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-metadata@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-metadata@v1.20.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
dry-run: ${{ inputs.dry_run && 'true' || 'false' }}

- name: Check PR size
id: size
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-size@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-size@v1.20.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
base-ref: ${{ github.base_ref }}
Expand All @@ -163,7 +157,7 @@ jobs:
id: labels
if: inputs.enable_auto_labeler && !inputs.dry_run
continue-on-error: true
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-labels@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-labels@v1.20.1
with:
github-token: ${{ secrets.MANAGE_TOKEN || github.token }}
config-path: ${{ inputs.labeler_config_path }}
Expand All @@ -186,7 +180,7 @@ jobs:

steps:
- name: PR Checks Summary
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-checks-summary@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-checks-summary@v1.20.1
with:
source-branch-result: ${{ needs.blocking-checks.outputs.source-branch-result || 'skipped' }}
title-result: ${{ needs.blocking-checks.outputs.title-result || 'skipped' }}
Expand All @@ -201,7 +195,7 @@ jobs:
name: Notify
needs: [blocking-checks, advisory-checks, pr-checks-summary]
if: always() && github.event.pull_request.draft != true && !inputs.dry_run
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/slack-notify.yml@v1.20.0
uses: LerianStudio/github-actions-shared-workflows/.github/workflows/slack-notify.yml@v1.20.1
with:
status: ${{ (needs.blocking-checks.outputs.source-branch-result == 'failure' || needs.blocking-checks.outputs.title-result == 'failure' || needs.blocking-checks.outputs.description-result == 'failure') && 'failure' || 'success' }}
workflow_name: "PR Validation"
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@
gpg_fingerprint: ${{ steps.import_gpg.outputs.fingerprint }}

steps:
- uses: actions/create-github-app-token@v2

Check failure on line 106 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: - uses: actions/create-github-app-token@v2 (use full commit SHA with a # vX.Y.Z comment)
id: app-token
with:
app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }}
private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }}

- name: Checkout repository
uses: actions/checkout@v6

Check failure on line 113 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: uses: actions/checkout@v6 (use full commit SHA with a # vX.Y.Z comment)
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
Expand All @@ -121,7 +121,7 @@
git reset --hard origin/${{ github.ref_name }}

- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v7

Check failure on line 124 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: uses: crazy-max/ghaction-import-gpg@v7 (use full commit SHA with a # vX.Y.Z comment)
id: import_gpg
with:
gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }}
Expand All @@ -133,7 +133,7 @@
git_commit_gpgsign: true

- name: Setup Node.js
uses: actions/setup-node@v6

Check failure on line 136 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: uses: actions/setup-node@v6 (use full commit SHA with a # vX.Y.Z comment)
with:
node-version: '20'

Expand All @@ -148,8 +148,9 @@
@semantic-release/exec

- name: Semantic Release
uses: cycjimmy/semantic-release-action@v6

Check failure on line 151 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / Pinned Actions Check

External action not pinned by SHA: uses: cycjimmy/semantic-release-action@v6 (use full commit SHA with a # vX.Y.Z comment)
id: semantic
continue-on-error: true
with:
ci: false
semantic_version: ${{ inputs.semantic_version }}
Expand All @@ -164,6 +165,21 @@
GIT_COMMITTER_NAME: ${{ secrets.LERIAN_CI_CD_USER_NAME }}
GIT_COMMITTER_EMAIL: ${{ secrets.LERIAN_CI_CD_USER_EMAIL }}

# ----------------- Backmerge Fallback -----------------
- name: Backmerge PR fallback
if: steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published == 'true'
uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@develop
with:
github-token: ${{ steps.app-token.outputs.token }}
source-branch: ${{ github.ref_name }}
version: ${{ steps.semantic.outputs.new_release_version }}

- name: Fail if release itself failed
if: steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published != 'true'
run: |
echo "::error::Semantic release failed before publishing a new version"
exit 1

# Slack notification
notify:
name: Notify
Expand Down
2 changes: 1 addition & 1 deletion docs/release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Reusable workflow for semantic versioning and automated release management. Crea
- **GPG signing**: Signed commits and tags for security
- **GitHub App authentication**: Higher rate limits and better security
- **Hotfix support**: Separate configuration for hotfix branches
- **Backmerge support**: Automatic backmerging of releases
- **Backmerge support**: Automatic backmerging of releases (falls back to creating a PR if the direct push fails due to branch divergence)
- **Conventional commits**: Enforces commit message standards

## Usage
Expand Down
64 changes: 64 additions & 0 deletions src/config/backmerge-pr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td><img src="https://github.com/LerianStudio.png" width="72" alt="Lerian" /></td>
<td><h1>backmerge-pr</h1></td>
</tr>
</table>

Creates a PR to backmerge a source branch into a target branch when a direct push fails. Checks for existing open PRs to avoid duplicates.

Typically used as a fallback in the release workflow when the `@saithodev/semantic-release-backmerge` plugin fails to push directly (non-fast-forward).

## Inputs

| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `github-token` | GitHub token with pull-requests write permission | Yes | |
| `source-branch` | Source branch to merge from (e.g., main) | Yes | |
| `target-branch` | Target branch to merge into | No | `develop` |
| `version` | Release version for the PR title | Yes | |

## Outputs

| Output | Description |
|--------|-------------|
| `pr-url` | URL of the created or existing PR |
| `pr-number` | Number of the created or existing PR |

## Usage as composite step

```yaml
- name: Create backmerge PR
uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@v1.x.x
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
source-branch: main
target-branch: develop
version: ${{ steps.semantic.outputs.new_release_version }}
```

## Usage in release workflow (fallback pattern)

```yaml
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v6
id: semantic
continue-on-error: true
...

- name: Backmerge PR fallback
if: steps.semantic.outcome == 'failure' && steps.semantic.outputs.new_release_published == 'true'
uses: LerianStudio/github-actions-shared-workflows/src/config/backmerge-pr@v1.x.x
with:
github-token: ${{ steps.app-token.outputs.token }}
source-branch: ${{ github.ref_name }}
version: ${{ steps.semantic.outputs.new_release_version }}
```

## Required permissions

```yaml
permissions:
contents: read
pull-requests: write
```
67 changes: 67 additions & 0 deletions src/config/backmerge-pr/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Backmerge PR
description: "Creates a PR to backmerge a source branch into a target branch when a direct push fails."

inputs:
github-token:
description: GitHub token with pull-requests write permission
required: true
source-branch:
description: Source branch to merge from (e.g., main)
required: true
target-branch:
description: Target branch to merge into (e.g., develop)
required: false
default: develop
version:
description: Release version for the PR title (e.g., 1.20.1)
required: true

outputs:
pr-url:
description: URL of the created PR (empty if PR already existed or was not needed)
value: ${{ steps.create-pr.outputs.pr_url }}
pr-number:
description: Number of the created or existing PR
value: ${{ steps.create-pr.outputs.pr_number }}
Comment on lines +19 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Output description contradicts implementation.

Line 21 states pr-url is "empty if PR already existed" but the implementation at lines 43-46 sets pr_url for existing PRs. Either update the description or the behavior.

Suggested fix
 outputs:
   pr-url:
-    description: URL of the created PR (empty if PR already existed or was not needed)
+    description: URL of the created or existing backmerge PR
     value: ${{ steps.create-pr.outputs.pr_url }}
   pr-number:
     description: Number of the created or existing PR
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/backmerge-pr/action.yml` around lines 19 - 25, The output
description for pr-url is incorrect relative to the implementation: update the
outputs block to reflect actual behavior (or change the behavior). Specifically,
either modify the description of outputs.pr-url (and optionally
outputs.pr-number) to state that pr_url is populated even when an existing PR is
found (it comes from steps.create-pr.outputs.pr_url), or change the logic in the
create-pr step to leave pr_url empty for existing PRs; reference the outputs
keys "pr-url"/"pr-number" and the step "steps.create-pr.outputs.pr_url" to
locate where to update the description or behavior.


runs:
using: composite
steps:
- name: Create backmerge PR
id: create-pr
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
SOURCE_BRANCH: ${{ inputs.source-branch }}
TARGET_BRANCH: ${{ inputs.target-branch }}
VERSION: ${{ inputs.version }}
run: |
# Check if a backmerge PR already exists
EXISTING_PR=$(gh pr list --base "${TARGET_BRANCH}" --head "${SOURCE_BRANCH}" --state open --json number,url --jq '.[0]')
if [ -n "$EXISTING_PR" ]; then
PR_NUM=$(echo "$EXISTING_PR" | jq -r '.number')
PR_URL=$(echo "$EXISTING_PR" | jq -r '.url')
echo "::notice::Backmerge PR #${PR_NUM} already exists: ${PR_URL}"
echo "pr_number=${PR_NUM}" >> "$GITHUB_OUTPUT"
echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
exit 0
fi

PR_BODY="## Description

Automated backmerge of release \`${VERSION}\` from \`${SOURCE_BRANCH}\` to \`${TARGET_BRANCH}\`.

The automatic backmerge push failed because \`${TARGET_BRANCH}\` has diverged from \`${SOURCE_BRANCH}\`. This PR needs a manual merge to resolve any conflicts.

> **Note:** This PR was created automatically by the release workflow."

PR_URL=$(gh pr create \
--base "${TARGET_BRANCH}" \
--head "${SOURCE_BRANCH}" \
--title "chore(release): backmerge ${VERSION}" \
--body "${PR_BODY}")

PR_NUM=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "pr_number=${PR_NUM}" >> "$GITHUB_OUTPUT"
echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
echo "::notice::Backmerge push failed — created PR #${PR_NUM}: ${PR_URL}"
12 changes: 4 additions & 8 deletions src/validate/pr-description/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
</tr>
</table>

Validates that the PR description has real content beyond template boilerplate:
Validates that the PR template checkboxes are properly filled:

- **Description section**: extracts content under `## Description`, strips HTML comments, and checks minimum length
- **Type of Change**: verifies at least one checkbox is checked (`- [x]`)
- **Type of Change**: at least one checkbox must be checked (`- [x]`)
- **Testing**: at least one checkbox must be checked (`- [x]`)

## Inputs

| Input | Description | Required | Default |
|-------|-------------|----------|---------|
| `min-length` | Minimum content length in characters (after stripping template boilerplate) | No | `30` |
None.

## Usage as composite step

Expand All @@ -25,8 +23,6 @@ jobs:
steps:
- name: Validate PR Description
uses: LerianStudio/github-actions-shared-workflows/src/validate/pr-description@v1.x.x
with:
min-length: "50"
```
## Required permissions
Expand Down
56 changes: 5 additions & 51 deletions src/validate/pr-description/action.yml
Original file line number Diff line number Diff line change
@@ -1,60 +1,14 @@
name: Validate PR Description
description: "Validates that the PR description has real content beyond template boilerplate."

inputs:
min-length:
description: Minimum content length in characters (after stripping template boilerplate)
required: false
default: "30"
description: "Checks that the PR description is not empty."

runs:
using: composite
steps:
- name: Validate PR description
- name: Check description
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix yamllint spacing warning.

Pipeline reports: "too few spaces before comment: expected 2". Add a space before the version comment.

Proposed fix
-      uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+      uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd  # v8
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
🧰 Tools
🪛 GitHub Actions: Self — PR Validation

[warning] 8-8: Yamllint warning: too few spaces before comment: expected 2 (comments)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/validate/pr-description/action.yml` at line 8, The YAML linter warning is
caused by missing spacing before the inline comment on the uses line; update the
line containing uses:
actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd (the
actions/github-script version comment) to include two spaces before the "#" so
there is one space between the value and the comment (e.g., add a single space
before the version comment) to satisfy yamllint's "too few spaces before
comment" rule.

env:
MIN_LENGTH: ${{ inputs.min-length }}
with:
script: |
const body = context.payload.pull_request.body || '';
const minLength = parseInt(process.env.MIN_LENGTH, 10);
if (isNaN(minLength) || minLength <= 0) {
core.setFailed(`Invalid min-length input: '${process.env.MIN_LENGTH}'`);
return;
}
const errors = [];
const warnings = [];

// --- Extract content under "## Description" heading ---
const descriptionMatch = body.match(/## Description\s*\n([\s\S]*?)(?=\n## |\n---\s*$|$)/);
const descriptionContent = descriptionMatch ? descriptionMatch[1].trim() : '';

// Strip HTML comments <!-- ... -->
const cleaned = descriptionContent.replace(/<!--[\s\S]*?-->/g, '').trim();

if (cleaned.length === 0) {
errors.push('The "Description" section is empty. Please summarize what this PR does and why.');
} else if (cleaned.length < minLength) {
errors.push(`The "Description" section is too short (${cleaned.length} chars, minimum ${minLength}). Please provide more detail.`);
}

// --- Check that at least one "Type of Change" checkbox is checked ---
const typeMatch = body.match(/## Type of Change\s*\n([\s\S]*?)(?=\n## |$)/);
if (typeMatch) {
const typeSection = typeMatch[1];
const checked = typeSection.match(/- \[x\]/gi);
if (!checked) {
errors.push('No "Type of Change" checkbox is checked. Please mark at least one.');
}
} else {
errors.push('Missing "Type of Change" section. Please use the PR template.');
}

// --- Report ---
for (const w of warnings) {
core.warning(w);
}

if (errors.length > 0) {
core.setFailed(errors.join('\n'));
const body = (context.payload.pull_request.body || '').trim();
if (body.length === 0) {
core.setFailed('PR description is empty. Please provide a description.');
}
Loading