diff --git a/.github/workflows/compile-changelogs.yml b/.github/workflows/compile-changelogs.yml index 2634f65a91..308bba9553 100644 --- a/.github/workflows/compile-changelogs.yml +++ b/.github/workflows/compile-changelogs.yml @@ -14,19 +14,19 @@ on: required: true type: string sdk_version: - description: 'SDK version override (empty = auto-derive from prowler/CHANGELOG.md + pending fragment types; "skip" = hold this component back)' + description: 'SDK version override (empty = mirrors prowler_version; "skip" = hold this component back)' required: false type: string api_version: - description: 'API version override (empty = auto-derive; "skip" = hold back)' + description: 'API version override (empty = auto-derive 1..; "skip" = hold back)' required: false type: string ui_version: - description: 'UI version override (empty = auto-derive; "skip" = hold back)' + description: 'UI version override (empty = auto-derive 1..; "skip" = hold back)' required: false type: string mcp_version: - description: 'MCP Server version override (empty = auto-derive; "skip" = hold back)' + description: 'MCP Server version override (empty = auto-derive from pending fragment types; "skip" = hold back)' required: false type: string @@ -209,17 +209,48 @@ jobs: errors=1 continue fi - IFS=. read -r major minor patch <<< "$current" - major=$((10#$major)) - minor=$((10#$minor)) - patch=$((10#$patch)) - if echo "$fragments" | grep -qE '\.(added|changed|deprecated)(\.[0-9]+)?\.md$'; then - effective="${major}.$((minor + 1)).0" - else - effective="${major}.${minor}.$((patch + 1))" + # SDK, UI, and API versions are deterministic mirrors of the + # Prowler version (the scheme bump-version.yml codifies): the SDK + # mirrors it directly, the UI tracks 1.., and the + # API is the independent 1.. stream. Only the + # MCP Server has its own cadence, derived from fragment types. + IFS=. read -r _ prowler_minor prowler_patch <<< "$PROWLER_VERSION" + prowler_minor=$((10#$prowler_minor)) + prowler_patch=$((10#$prowler_patch)) + case "$component" in + prowler) effective="$PROWLER_VERSION" ;; + ui) effective="1.${prowler_minor}.${prowler_patch}" ;; + api) effective="1.$((prowler_minor + 1)).${prowler_patch}" ;; + mcp_server) + IFS=. read -r major minor patch <<< "$current" + major=$((10#$major)) + minor=$((10#$minor)) + patch=$((10#$patch)) + # Prowler patch releases (vN.N target) are maintenance + # releases, so the MCP Server bumps patch regardless of + # fragment types; a deliberate exception needs the explicit + # version input. + if [ "$TARGET_BRANCH" != "master" ]; then + effective="${major}.${minor}.$((patch + 1))" + if echo "$fragments" | grep -qE '\.(added|deprecated)(\.[0-9]+)?\.md$'; then + echo "::warning::${component}: 'added'/'deprecated' fragments are shipping in a Prowler patch; auto-derived a patch bump (${current} -> ${effective}), pass the version input to override" + fi + elif echo "$fragments" | grep -qE '\.(added|changed|deprecated)(\.[0-9]+)?\.md$'; then + effective="${major}.$((minor + 1)).0" + else + effective="${major}.${minor}.$((patch + 1))" + fi + ;; + esac + current_key=$(version_key "$current") + effective_key=$(version_key "$effective") + if [[ "$effective_key" < "$current_key" || "$effective_key" == "$current_key" ]]; then + echo "::error::${component}: auto-derived version '${effective}' is not greater than the latest released version (${current}); check prowler_version or pass the version input explicitly" + errors=1 + continue fi mode="auto" - echo "::notice::${component}: version auto-derived ${current} -> ${effective} from the pending fragment types" + echo "::notice::${component}: version auto-derived ${current} -> ${effective}" fi if [ "$removed_fragments" = "true" ]; then @@ -336,6 +367,7 @@ jobs: author: prowler-bot <179230569+prowler-bot@users.noreply.github.com> labels: | no-changelog + skip-sync # Patch compiles (target_branch = v5.X) leave master holding the consumed # fragments and missing the new version block. This applies the equivalent @@ -366,7 +398,7 @@ jobs: local block_file="$2" local changelog="${component}/CHANGELOG.md" local incoming_heading incoming_release incoming_key - local marker_line insertion_line duplicate_line + local marker_line insertion_line duplicate_line total_lines local line heading existing_release existing_key marker_line=$(grep -n -m1 '^$' "$changelog" | cut -d: -f1) @@ -405,9 +437,25 @@ jobs: insertion_line=$((marker_line + 1)) fi + # The captured block window can be off by one blank line on either + # end (towncrier re-emits the blank after the marker), so strip the + # outer blank lines and pad exactly one on each side: the block + # must never glue to the marker above or the next heading below. + awk ' + /[^[:space:]]/ { for (i = 0; i < pending; i++) print ""; pending = 0; print; started = 1; next } + started { pending++ } + ' "$block_file" > "${RUNNER_TEMP}/block-normalized.md" + + total_lines=$(wc -l < "$changelog") { head -n "$((insertion_line - 1))" "$changelog" - cat "$block_file" + if [ "$insertion_line" -gt 1 ] && [ -n "$(sed -n "$((insertion_line - 1))p" "$changelog")" ]; then + echo "" + fi + cat "${RUNNER_TEMP}/block-normalized.md" + if [ "$insertion_line" -le "$total_lines" ]; then + echo "" + fi tail -n +"$insertion_line" "$changelog" } > "${RUNNER_TEMP}/changelog.tmp" mv "${RUNNER_TEMP}/changelog.tmp" "$changelog" @@ -476,3 +524,4 @@ jobs: author: prowler-bot <179230569+prowler-bot@users.noreply.github.com> labels: | no-changelog + skip-sync diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index ff78a2d8e4..9e8b541130 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -4,7 +4,6 @@ All notable changes to the **Prowler API** are documented in this file. - ## [1.34.1] (Prowler v5.33.1) ### 🐞 Fixed @@ -19,6 +18,7 @@ All notable changes to the **Prowler API** are documented in this file. - `LIGHTHOUSE_AI_OPENAI_COMPATIBLE_ALLOWED_HOSTS` environment variable to allow internal hosts as OpenAI-compatible Lighthouse AI base URLs [(#11942)](https://github.com/prowler-cloud/prowler/pull/11942) --- + ## [1.34.0] (Prowler v5.33.0) ### 🚀 Added diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 15b0142163..3a9ebff986 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -4,7 +4,6 @@ All notable changes to the **Prowler SDK** are documented in this file. - ## [5.33.1] (Prowler v5.33.1) ### 🐞 Fixed @@ -17,6 +16,7 @@ All notable changes to the **Prowler SDK** are documented in this file. - Azure Function App optional permission failures now log as warnings, and Function App environment variable fields use the correct spelling internally [(#11926)](https://github.com/prowler-cloud/prowler/pull/11926) --- + ## [5.33.0] (Prowler v5.33.0) ### 🐞 Fixed diff --git a/skills/prowler-changelog/SKILL.md b/skills/prowler-changelog/SKILL.md index 369da2ba55..fef70fa9bc 100644 --- a/skills/prowler-changelog/SKILL.md +++ b/skills/prowler-changelog/SKILL.md @@ -153,9 +153,9 @@ The `pr-check-changelog.yml` workflow enforces fragments: ## Release flow (compile) -- At release time, the `compile-changelogs` workflow (manual dispatch: `prowler_version` + `target_branch`; per-component versions are auto-derived from each changelog's latest stamped heading plus the pending fragment types, with optional explicit overrides or `skip`) resolves each fragment's PR from git history, runs the compiler per component, and opens a `chore(changelog): vX.Y.Z` PR (labeled `no-changelog`) that inserts the stamped `## [X.Y.Z] (Prowler vX.Y.Z)` block into each `CHANGELOG.md` and deletes the consumed fragments. A human reviews and squash-merges it. `prepare-release.yml` then extracts the stamped sections exactly as before. +- At release time, the `compile-changelogs` workflow (manual dispatch: `prowler_version` + `target_branch`; per-component versions are auto-derived by mirroring the Prowler version — SDK mirrors it directly, UI is `1..`, API is `1..`, and only the MCP Server derives from its pending fragment types — with optional explicit overrides or `skip`) resolves each fragment's PR from git history, runs the compiler per component, and opens a `chore(changelog): vX.Y.Z` PR (labeled `no-changelog` and `skip-sync`) that inserts the stamped `## [X.Y.Z] (Prowler vX.Y.Z)` block into each `CHANGELOG.md` and deletes the consumed fragments. A human reviews and squash-merges it. `prepare-release.yml` then extracts the stamped sections exactly as before. - **Minor release (X.Y.0):** compile on `master` and merge the compile PR BEFORE cutting the `v5.X` branch. -- **Patch release (X.Y.Z):** fixes are backported to `v5.X` with their fragment files (conflict-free); compile on `v5.X` and merge its PR there. The same workflow run automatically opens a second forward-sync PR against master (labeled `no-changelog`) that inserts the same stamped block under master's marker and deletes the consumed fragments, so the next minor cannot re-release them; merge it right after. Fragments that only existed on `v5.X` are skipped with a notice. No manual git is involved. +- **Patch release (X.Y.Z):** fixes are backported to `v5.X` with their fragment files (conflict-free); compile on `v5.X` and merge its PR there. The same workflow run automatically opens a second forward-sync PR against master (labeled `no-changelog` and `skip-sync`) that inserts the same stamped block under master's marker and deletes the consumed fragments, so the next minor cannot re-release them; merge it right after. Fragments that only existed on `v5.X` are skipped with a notice. No manual git is involved. - Entries within a section are ordered by PR number ascending (approximately chronological). Do not fight this ordering. ## Fixing an already-released entry diff --git a/tests/github/changelog_fragments_test.py b/tests/github/changelog_fragments_test.py index 13cf65b389..05a751125e 100644 --- a/tests/github/changelog_fragments_test.py +++ b/tests/github/changelog_fragments_test.py @@ -312,3 +312,68 @@ def test_compile_workflow_requires_removed_fragments_in_major_releases(): assert "effective_major" in workflow assert "effective_minor" in workflow assert "effective_patch" in workflow + + +def test_compile_workflow_prs_skip_cloud_sync(): + workflow = read_workflow("compile-changelogs.yml") + labels_blocks = re.findall(r"labels: \|\n((?:\s+[a-z-]+\n)+)", workflow) + + assert len(labels_blocks) == 2 + for block in labels_blocks: + assert "no-changelog" in block.split() + assert "skip-sync" in block.split() + + +def test_compile_workflow_auto_derives_versions_by_mirroring_prowler_version(): + workflow = read_workflow("compile-changelogs.yml") + + assert 'prowler) effective="$PROWLER_VERSION" ;;' in workflow + assert 'ui) effective="1.${prowler_minor}.${prowler_patch}" ;;' in workflow + assert 'api) effective="1.$((prowler_minor + 1)).${prowler_patch}" ;;' in workflow + assert ( + "auto-derived version '${effective}' is not greater than the latest released version" + in workflow + ) + + +def test_compile_workflow_auto_derives_mcp_patch_bumps_on_patch_releases(): + workflow = read_workflow("compile-changelogs.yml") + + assert "'added'/'deprecated' fragments are shipping in a Prowler patch" in workflow + assert ( + "elif echo \"$fragments\" | grep -qE '\\.(added|changed|deprecated)(\\.[0-9]+)?\\.md$'; then" + in workflow + ) + + +def test_forward_sync_pads_release_blocks_with_blank_lines(): + workflow = read_workflow("compile-changelogs.yml") + + assert "block-normalized.md" in workflow + assert 'total_lines=$(wc -l < "$changelog")' in workflow + assert '[ -n "$(sed -n "$((insertion_line - 1))p" "$changelog")" ]' in workflow + assert 'if [ "$insertion_line" -le "$total_lines" ]; then' in workflow + + +def test_component_changelogs_separate_release_blocks_with_blank_lines(): + for component in COMPONENTS: + lines = (REPO_ROOT / component / "CHANGELOG.md").read_text().splitlines() + marker_line = lines.index("") + + assert ( + lines[marker_line + 1] == "" + ), f"{component}/CHANGELOG.md: expected a blank line after the marker" + assert ( + lines[marker_line + 2] != "" + ), f"{component}/CHANGELOG.md: expected a single blank line after the marker" + for index, line in enumerate(lines): + if line == "---" and index + 1 < len(lines): + assert lines[index + 1] == "", ( + f"{component}/CHANGELOG.md line {index + 2}: " + "expected a blank line after '---'" + ) + if line.startswith("## ["): + assert lines[index - 1] == "", ( + f"{component}/CHANGELOG.md line {index}: " + "expected a blank line before a release heading" + ) diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index e9442e920f..82682da883 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -4,8 +4,7 @@ All notable changes to the **Prowler UI** are documented in this file. - -## [1.34.0] (Prowler v5.33.1) +## [1.33.1] (Prowler v5.33.1) ### 🔄 Changed @@ -17,6 +16,7 @@ All notable changes to the **Prowler UI** are documented in this file. - Jira dispatch polling now reports failed issue creation tasks instead of treating partial failures as successful [(#11925)](https://github.com/prowler-cloud/prowler/pull/11925) --- + ## [1.33.0] (Prowler v5.33.0) ### 🚀 Added