From d09b56e7092f7785b417836b3b073020f351b548 Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Thu, 4 Dec 2025 10:02:19 +0400 Subject: [PATCH] chore(ci): add changelog.yml workflow --- .github/workflows/changelog.yml | 46 ++++++ CHANGELOG.md | 105 ++++++++++--- cliff.toml | 268 ++++++++++++++++++++++++++++++++ 3 files changed, 394 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/changelog.yml create mode 100644 cliff.toml diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..d95c33d --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,46 @@ +name: update-changelog +on: + push: + branches: + - main +permissions: + contents: write +jobs: + changelog: + name: Update CHANGELOG + runs-on: ubuntu-latest + timeout-minutes: 15 + if: | + !startsWith(github.event.head_commit.message, '[Release]') && + !startsWith(github.event.head_commit.message, 'chore(changelog): update CHANGELOG.md') && + github.event.head_commit.author.name != 'github-actions[bot]' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - uses: jdx/mise-action@v3 + with: + experimental: true + - name: Generate CHANGELOG.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: git cliff --config ./cliff.toml -o ./CHANGELOG.md + - name: Check for CHANGELOG changes + id: changelog-changes + run: | + if git diff --quiet CHANGELOG.md; then + echo "No changes in CHANGELOG.md" + echo "has-changes=false" >> $GITHUB_OUTPUT + else + echo "CHANGELOG.md has changes" + echo "has-changes=true" >> $GITHUB_OUTPUT + fi + - name: Commit CHANGELOG + uses: stefanzweifel/git-auto-commit-action@v7 + if: steps.changelog-changes.outputs.has-changes == 'true' + with: + commit_message: "chore(changelog): update CHANGELOG.md" + commit_options: '--no-verify' + file_pattern: CHANGELOG.md + commit_user_name: github-actions[bot] + commit_user_email: github-actions[bot]@users.noreply.github.com \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a4870..ceb8f71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ -# Change Log +# Changelog + All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + #### 1.x Releases - `1.4.x` Releases - [1.4.0](#140) - `1.3.x` Releases - [1.3.0](#130) @@ -8,41 +12,92 @@ All notable changes to this project will be documented in this file. - `1.1.x` Releases - [1.1.0](#110) - `1.0.x` Releases - [1.0.0](#100) +--- +## [Unreleased] + +### Features +- Switch from Makefile to Mise + - Implemented by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#19](https://github.com/space-code/flex-ui/pull/19). + +### Miscellaneous Tasks +- Add conventional-pr.yml for PR validation + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#20](https://github.com/space-code/flex-ui/pull/20). +- Update config + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#26](https://github.com/space-code/flex-ui/pull/26). + +### Uncategorized Changes +- Add renovate.json + - Contributed by [@renovate[bot]](https://github.com/renovate[bot]) in Pull Request [#22](https://github.com/space-code/flex-ui/pull/22). + +### New Contributors +* @renovate[bot] made their first contribution in [#28](https://github.com/space-code/flex-ui/pull/28) + ## [1.4.0](https://github.com/space-code/flex-ui/releases/tag/1.4.0) -Released on 2025-05-23. -#### Added -- Update the `setImage(_:)` method signature. - - Added in Pull Request [#12](https://github.com/space-code/flex-ui/pull/12). -- Implement the `setContentHuggingPriority` & `setContentCompressionResistancePriority` methods. - - Added in Pull Request [#13](https://github.com/space-code/flex-ui/pull/13). +Released on 2025-05-23. All issues associated with this milestone can be found using this [filter](https://github.com/space-code/flex-ui/milestones?state=closed&q=1.4.0). + +### Uncategorized Changes +- Release `1.4.0` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#15](https://github.com/space-code/flex-ui/pull/15). +- Update `CHANGELOG.md` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#14](https://github.com/space-code/flex-ui/pull/14). +- Implement the `setContentHuggingPriority` & `setContentCompressionResistancePriority` methods + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#13](https://github.com/space-code/flex-ui/pull/13). +- Update the `setImage(_:)` method signature + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#12](https://github.com/space-code/flex-ui/pull/12). ## [1.3.0](https://github.com/space-code/flex-ui/releases/tag/1.3.0) -Released on 2025-05-08. -#### Added -- Implement a new `setImage` method for the `UIButton` class. - - Added in Pull Request [#9](https://github.com/space-code/flex-ui/pull/9). -- Implement setting an optional value as the UILabel's text. - - Added in Pull Request [#8](https://github.com/space-code/flex-ui/pull/8). +Released on 2025-05-08. All issues associated with this milestone can be found using this [filter](https://github.com/space-code/flex-ui/milestones?state=closed&q=1.3.0). + +### Uncategorized Changes +- Release `1.3.0` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#11](https://github.com/space-code/flex-ui/pull/11). +- Update `CHANGELOG.md` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#10](https://github.com/space-code/flex-ui/pull/10). +- Implement a new `setImage` method for the `UIButton` class + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#9](https://github.com/space-code/flex-ui/pull/9). +- Implement setting an optional value as the UILabel's text + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#8](https://github.com/space-code/flex-ui/pull/8). ## [1.2.0](https://github.com/space-code/flex-ui/releases/tag/1.2.0) -Released on 2025-02-15. -#### Added -- Implement the extension for the `UIControl` instance. - - Added in Pull Request [#6](https://github.com/space-code/flex-ui/pull/6). +Released on 2025-02-15. All issues associated with this milestone can be found using this [filter](https://github.com/space-code/flex-ui/milestones?state=closed&q=1.2.0). + +### Uncategorized Changes +- Implement the extension for the `UIControl` instance + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#7](https://github.com/space-code/flex-ui/pull/7). +- Implement the extension for the UIControl instance + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#6](https://github.com/space-code/flex-ui/pull/6). ## [1.1.0](https://github.com/space-code/flex-ui/releases/tag/1.1.0) -Released on 2025-01-27. -#### Added -- Implement adding an action to a `UITextField`. - - Added in Pull Request [#3](https://github.com/space-code/flex-ui/pull/3). +Released on 2025-01-27. All issues associated with this milestone can be found using this [filter](https://github.com/space-code/flex-ui/milestones?state=closed&q=1.1.0). + +### Uncategorized Changes +- Release `1.1.0` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#4](https://github.com/space-code/flex-ui/pull/4). +- Update `ci.yml` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#5](https://github.com/space-code/flex-ui/pull/5). +- Implement adding an action to a `UITextField` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#3](https://github.com/space-code/flex-ui/pull/3). ## [1.0.0](https://github.com/space-code/flex-ui/releases/tag/1.0.0) -Released on 2025-01-07. -#### Added -- Initial release of FlexUI. - - Added by [Nikita Vasilev](https://github.com/ns-vasilev). +Released on 2025-01-07. All issues associated with this milestone can be found using this [filter](https://github.com/space-code/flex-ui/milestones?state=closed&q=1.0.0). + +### Uncategorized Changes +- Release `1.0.0` + - Contributed by [@ns-vasilev](https://github.com/ns-vasilev) in Pull Request [#2](https://github.com/space-code/flex-ui/pull/2). +- Bump actions/checkout from 2 to 4 + - Contributed by [@dependabot[bot]](https://github.com/dependabot[bot]) in Pull Request [#1](https://github.com/space-code/flex-ui/pull/1). + +### New Contributors +* @dependabot[bot] made their first contribution in [#1](https://github.com/space-code/flex-ui/pull/1) + +[unreleased]: https://github.com/space-code/flex-ui/compare/1.4.0..HEAD +[1.4.0]: https://github.com/space-code/flex-ui/compare/1.3.0..1.4.0 +[1.3.0]: https://github.com/space-code/flex-ui/compare/1.2.0..1.3.0 +[1.2.0]: https://github.com/space-code/flex-ui/compare/1.1.0..1.2.0 +[1.1.0]: https://github.com/space-code/flex-ui/compare/1.0.0..1.1.0 + diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..0eb62f8 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,268 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[remote.github] +owner = "space-code" +repo = "flex-ui" +# If you are using a token to fetch GitHub usernames, uncomment below: +# token = "${GITHUB_TOKEN}" + +[changelog] +# GUARANTEE: Skip releases if they contain no commits (for tagged versions) +skip_empty_releases = true +# Maximum number of releases to display in the changelog +# number_of_releases = 10 + +header = """ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +{#- LOGIC: Generate table of contents - group versions by major version -#} +{%- set_global major_versions = [] -%} +{%- for release in releases -%} + {%- if release.version -%} + {%- set version_clean = release.version | trim_start_matches(pat="v") -%} + {%- set major = version_clean | split(pat=".") | first -%} + {%- if major not in major_versions -%} + {%- set_global major_versions = major_versions | concat(with=major) -%} + {%- endif -%} + {%- endif -%} +{%- endfor -%} +{%- set sorted_majors = major_versions | sort | reverse -%} + +{#- MAIN LOOP: Iterate over major versions -#} +{%- for major in sorted_majors -%} + {#- VISUAL: Add double newline before the header to separate from previous block -#} + {{ "\n\n" }}#### {{ major }}.x Releases + + {#- LOGIC: Filter releases for the current major version -#} + {%- set_global major_releases = [] -%} + {%- for release in releases -%} + {%- if release.version -%} + {%- set version_clean = release.version | trim_start_matches(pat="v") -%} + {%- set rel_major = version_clean | split(pat=".") | first -%} + {%- if rel_major == major -%} + {%- set_global major_releases = major_releases | concat(with=version_clean) -%} + {%- endif -%} + {%- endif -%} + {%- endfor -%} + + {#- LOGIC: Separate into Stable, RC, and Beta -#} + {%- set_global stable_versions = [] -%} + {%- set_global rc_versions = [] -%} + {%- set_global beta_versions = [] -%} + {%- for version in major_releases -%} + {%- if version is containing("-rc") -%} + {%- set_global rc_versions = rc_versions | concat(with=version) -%} + {%- elif version is containing("-beta") -%} + {%- set_global beta_versions = beta_versions | concat(with=version) -%} + {%- else -%} + {%- set_global stable_versions = stable_versions | concat(with=version) -%} + {%- endif -%} + {%- endfor -%} + + {#- LOGIC: Group stable versions by minor version -#} + {%- set_global minor_versions = [] -%} + {%- for version in stable_versions -%} + {%- set parts = version | split(pat=".") -%} + {%- set minor_key = parts | slice(end=2) | join(sep=".") -%} + {%- if minor_key not in minor_versions -%} + {%- set_global minor_versions = minor_versions | concat(with=minor_key) -%} + {%- endif -%} + {%- endfor -%} + {%- set sorted_minors = minor_versions | sort | reverse -%} + + {#- OUTPUT: Stable releases -#} + {%- for minor_key in sorted_minors -%} + {%- set_global minor_release_versions = [] -%} + {%- for version in stable_versions -%} + {%- set parts = version | split(pat=".") -%} + {%- set ver_minor = parts | slice(end=2) | join(sep=".") -%} + {%- if ver_minor == minor_key -%} + {%- set_global minor_release_versions = minor_release_versions | concat(with=version) -%} + {%- endif -%} + {%- endfor -%} + {%- set versions_list = minor_release_versions | sort | reverse -%} +{{ "\n" }}- `{{ minor_key }}.x` Releases - {% for version in versions_list -%} +[{{ version }}](#{{ version | replace(from=".", to="") | replace(from="-", to="") | lower }}) + {%- if not loop.last %} | {% endif -%} + {%- endfor -%} + {%- endfor -%} + + {#- OUTPUT: RC versions -#} + {%- if rc_versions | length > 0 -%} + {%- set rc_versions_sorted = rc_versions | sort | reverse -%} + {%- set rc_base = rc_versions_sorted | first | split(pat="-") | first -%} +{{ "\n" }}- `{{ rc_base }}` Release Candidates - {% for version in rc_versions_sorted -%} +[{{ version }}](#{{ version | replace(from=".", to="") | replace(from="-", to="") | lower }}) + {%- if not loop.last %} | {% endif -%} + {%- endfor -%} + {%- endif -%} + + {#- OUTPUT: Beta versions -#} + {%- if beta_versions | length > 0 -%} + {%- set beta_versions_sorted = beta_versions | sort | reverse -%} + {%- set beta_base = beta_versions_sorted | first | split(pat="-") | first -%} +{{ "\n" }}- `{{ beta_base }}` Betas - {% for version in beta_versions_sorted -%} +[{{ version }}](#{{ version | replace(from=".", to="") | replace(from="-", to="") | lower }}) + {%- if not loop.last %} | {% endif -%} + {%- endfor -%} + {%- endif -%} +{%- endfor -%}{{ "\n" }} +--- +""" + +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{%- set_global renderable_commits = [] -%} +{%- for commit in commits -%} + {# Filter commits that have a Conventional Commit type or a PR number (your rendering condition) #} + {%- if commit.conventional or commit.remote.pr_number -%} + {%- set_global renderable_commits = renderable_commits | concat(with=commit) -%} + {%- endif -%} +{%- endfor -%} + +{%- if renderable_commits | length > 0 -%} + {#- LOGIC: Version Header with Link -#} + {%- if version -%} + {{ "\n" }} + ## [{{ version | trim_start_matches(pat="v") }}]({{ self::remote_url() }}/releases/tag/{{ version | trim_start_matches(pat="v") }}) + {{ "\n" }}Released on {{ timestamp | date(format="%Y-%m-%d") }}. All issues associated with this milestone can be found using this [filter]({{ self::remote_url() }}/milestones?state=closed&q={{ version }}). + {%- else -%} + ## [Unreleased] + {%- endif -%} + + {#- LOGIC: Loop through commit groups -#} + {%- for group, commits_in_group in renderable_commits | group_by(attribute="group") -%} + {%- if group == "Uncategorized Changes" and commits_in_group | length == 0 -%} + {%- continue -%} + {%- endif -%} + + {# We also check that it is not the system group 'Other', which might be empty #} + {%- if group == "Other" and commits_in_group | length == 0 -%} + {%- continue -%} + {%- endif -%} + + {%- set action_verb = "Contributed by" -%} + {%- if group == "Features" -%} + {%- set action_verb = "Implemented by" -%} + {%- elif group == "Bug Fixes" -%} + {%- set action_verb = "Fixed by" -%} + {%- elif group == "Performance" -%} + {%- set action_verb = "Optimized by" -%} + {%- elif group == "Documentation" -%} + {%- set action_verb = "Documented by" -%} + {%- endif -%} + + {{ "\n" }}{{ "\n" }}### {{ group | upper_first }} + + {#- THE LOOP NOW USES FILTERED COMMITS AND DOESN'T NEED AN INNER IF -#} + {%- for commit in commits_in_group -%} + {%- set message = commit.message | split(pat="\n") | first | upper_first | trim -%} + + {#- VISUAL: Commit message line -#} + {{ "\n" }}- {{ message }} + + {%- if commit.remote.username and commit.remote.pr_number -%} + {#- VISUAL: Dynamic verb line -#} + {{ "\n" }} - {{ action_verb }} [@{{ commit.remote.username }}](https://github.com/{{ commit.remote.username }}) in Pull Request [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}). + {%- endif -%} + {%- endfor -%} + {%- endfor -%} + + {#- LOGIC: New Contributors Section -#} + {%- set new_contributors = github.contributors | filter(attribute="is_first_time", value=true) -%} + + {%- if new_contributors | length > 0 -%} + {%- set_global real_new_contributors = [] -%} + {%- for contributor in new_contributors -%} + {#- IMPORTANT: Filtering out your login "ns-vasilev" and Renovate -#} + {%- set username_lower = contributor.username | default(value="") | lower | trim -%} + {%- if username_lower != "ns-vasilev" and username_lower != "renovate" -%} + {%- set_global real_new_contributors = real_new_contributors | concat(with=contributor) -%} + {%- endif -%} + {%- endfor -%} + + {%- if real_new_contributors | length > 0 -%} + {{ "\n" }}{{ "\n" }}### New Contributors + {%- for contributor in real_new_contributors -%} + {{ "\n" }}* @{{ contributor.username }} made their first contribution in{{ " " }} + {%- if contributor.pr_number -%} + [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) + {%- endif -%} + {%- endfor -%} + {%- endif -%} + {%- endif -%} +{%- endif -%} +""" + +footer = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} + +{{ "\n" }} +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + {{ self::remote_url() }}/compare/{{ release.previous.version }}..{{ release.version }} + {% endif -%} + {% else -%} + [unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}..HEAD + {% endif -%} +{% endfor %} +""" +trim = true +postprocessors = [] + +[git] +conventional_commits = true +# SET TO false to include old (unconventional) commits +filter_unconventional = false +split_commits = false +commit_preprocessors = [ + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" }, +] +commit_parsers = [ + { message = "^chore\\(changelog\\)", skip = true }, + { message = "^chore.*changelog", skip = true }, + { message = "^docs: update CHANGELOG\\.md \\[skip ci\\]$", skip = true }, + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^doc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(spm.*\\)", skip = false }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore|^ci", group = "Miscellaneous Tasks" }, + { body = ".*security", group = "Security" }, + # CATCH-ALL PARSER for old (unconventional) commits + { message = ".*", group = "Uncategorized Changes" }, +] + +# SET TO false to avoid filtering out Uncategorized Changes +filter_commits = false +protect_breaking_commits = false +tag_pattern = "^[0-9].*" +skip_tags = "beta|alpha|cli-.*" +ignore_tags = "rc|web-.*" +topo_order = false +sort_commits = "newest" + +[bump] +breaking_always_bump_major = true +features_always_bump_minor = true +initial_tag = "0.1.0" \ No newline at end of file