# changelog
## tl;dr
Proponuję przyjąć stosowanie konwencji nazewnictwa commitów zaczynając od typu zmian `fix` lub `feat` (np. `feat: add ability to parse arrays`). Przynajmniej w repozytoriach libów/aplikacji z zewnętrznymi użytkownikami, tam gdzie potrzebny będzie CHANGELOG. Taka konwencja pozwala na wykorzstanie istniejącego procesu do generowania pliku `CHANGELOG.md` zawierającego tylko znaczące informacje na temat zmian z punktu widzenia użytkownika.
---
Ostatnio zajmowałem się tematem CHANGELOG na potrzeby `xls-parser-oca`, ale chciałem zaproponować jakieś uniwersalne rozwiązanie, więc wnioskami dzielę się do dyskusji.
Z jednej strony można pisać CHANGELOG z palca, ale to wiąże się z dodatkową, żmudną pracą dokumentowania zmian w aplikacji, która to praca nie jest wymuszona w procesie, więc z niezerowym prawdopodobieństem można założyć, że z czasem może brakować jakichś wpisów.
Z drugiej strony zmiany w kodzie są dokumentowane w gicie, więc możnaby całkowicie zautomatyzować proces generowania CHANGELOG na podstawie git log (mam na myśli log commitów, a nie komendę w gicie), ale wtedy informacje nieistotne dla klienta (np. poprawienie literówki w dokumentacji) będą się znajdować z informacjami kluczowymi (jak breaking changes).
Jednak przy odpowiedniej konwencji nazywania commitów można je wykorzystać w pół-automatycznym procesie generowania sensownego CHANGELOG. Dodatkow każdy wpis w CHANGELOG powiązany jest z commitem, który daną zmianę wprowadził.
## conventianal commits | Angular convention
Jedną z konwencji, która jest stworzona na bazie [propozycji Angulara](https://github.com/angular/angular/blob/68a6a07/CONTRIBUTING.md#commit), jest [conventional commits](https://www.conventionalcommits.org/). Opiera się na dodawaniu na początku commit message typu zmian, jakie w danym commicie się znajdują. Wrzucę fragment z ich specyfikacji:
>The commit message should be structured as follows:
>```
><type>[optional scope]: <description>
>
>[optional body]
>
>[optional footer(s)]
>```
> The commit contains the following structural elements, to communicate intent to the consumers of your library:
>
> 1. **fix:** a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
> 1. **feat:** a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
> 1. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
> 1. types other than `fix:` and `feat:` are allowed, for example `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others.
> 1. `footers` other than `BREAKING CHANGE: <description>` may be provided and follow a convention similar to git trailer format.
>
> Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`.
W ten sposób można wykorzystać narzędzia do generowania CHANGELOG na podstawie git log, które dzięki oznaczeniu typu commita, są w stanie zinterpretować jego ważność. Miłym dodatkiem jest też podpowiedź, jakiego rodzaju powinien być kolejny release zgodnie z SemVer.
## tools
Większość [narzędzi](https://www.conventionalcommits.org/en/about) jest napisanych w Go, PHP, JS i Python (🦀 + 😞). Skupiłem się na JS, bo w większości i tak mamy na głowie nodejs.
- [conventional-changelog-cli](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli#readme)
CLI do generowania pliku `CHANGELOG.md`. Np. [takiego](https://github.com/yargs/yargs/blob/main/CHANGELOG.md).
Zainstalowałem jako global (`npm install -g conventional-changelog-cli`) i dodałem alias w `~/.alias` (`alias chlog="conventional-changelog -p conventionalcommits -i CHANGELOG.md -s"`).
W ten sposób komenda `chlog` dodaje do istniejącego `CHANGELOG.md` ostatnie zmiany.
- [commitlint](https://github.com/conventional-changelog/commitlint#readme)
Linter commit message sprawdzający message z przyjętą konwencją.
Też jako global (`npm install -g @commitlint/cli @commitlint/config-conventional`), ale w tym przypadku potrzebny jest jeszcze dodatkowy plik konifuracyjny (`echo "module.exports = {extends: ['@commitlint/config-conventional']};" > ~/.commitlintrc.js`).
Dodałem też sobie prosty git hook w projekcie:
`.git/hooks/commit-msg`
```bash
#!/bin/sh
IS_AMEND=$(ps -ocommand= -p $PPID | grep -e '--amend');
commit_msg=$(cat "${1:?Missing commit message file}")
if [ -n "$IS_AMEND" ]; then
return;
fi
echo $commit_msg | commitlint
```
dzięki czemu domyślnie wymusza porawny commit message lub podpowiada co trzeba zmienić. Można ominąć lintera dodając flagę `-n` do `git commit`.
- [conventional-recommended-bump](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-recommended-bump#readme)
CLI które zwraca jakiego rodzaju powinien być release (`major`/`minor`/`patch`) na podstawie commitów.
Zainstalowany globalnie (`npm i -g conventional-recommended-bump conventional-changelog-angular`) i dodany alias `alias chb="conventional-recommended-bump -p angular"`
- dodatkowo [espanso](https://espanso.org/) ([GitHub](https://github.com/espanso/espanso))
> [...] program that detects when you type a specific **keyword** and replaces it with **something else**.
dodałem sobie triggery do pisania commitów (`gc` to alias na `git commit`):
```yaml
- trigger: "gc "
replace: "gc -m '$|$'"
- trigger: "gcx "
replace: "gc -m 'fix: $|$'"
- regex: "gcx\\((?P<scope>.*) "
replace: "gc -m 'fix({{scope}}): $|$'"
- trigger: "gcxx "
replace: "gc -m 'fix!: $|$' -m '' -m 'BREAKING CHANGE: '"
- regex: "gcxx\\((?P<scope>.*) "
replace: "gc -m 'fix({{scope}})!: $|$' -m '' -m 'BREAKING CHANGE: '"
- trigger: "gcf "
replace: "gc -m 'feat: $|$'"
- regex: "gcf\\((?P<scope>.*) "
replace: "gc -m 'feat({{scope}}): $|$'"
- trigger: "gcff "
replace: "gc -m 'feat!: $|$' -m '' -m 'BREAKING CHANGE: '"
- regex: "gcff\\((?P<scope>.*) "
replace: "gc -m 'feat({{scope}})!: $|$' -m '' -m 'BREAKING CHANGE: '"
- trigger: "bc "
replace: "BREAKING CHANGE: "
- trigger: "dep "
replace: "DEPRECATED: "
```
taraz jak wpiszę w konsoli `gc` + 2 spacje to zamienia mi to na `gc -m ''` i ustawia kursor między apostrofami. Z kolei `gcff(parser` + 2 spacje zamienia na `gc -m 'feat(parser)!: ' -m '' -m 'BREAKING CHANGE: '` z kursorem po `:`.
- [googleapis/release-please](https://github.com/googleapis/release-please)
Na to wcześniej nie patrzyłem, ale też wygląda ciekawie, więc zostawiam link.
## Przydatne źródła:
- https://www.conventionalcommits.org/
- https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
- https://keepachangelog.com/
- https://semver.org/