├── .all-contributorsrc
├── .dockerignore
├── .editorconfig
├── .eslintrc
├── .github
├── FUNDING.yml
└── workflows
│ ├── canary-release.yml
│ └── test-and-lint.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── doc
└── screenshot.png
├── jest.config-e2e.js
├── lerna.json
├── misc
├── example.gif
├── interactive.gif
├── logo.png
└── package.sample.json
├── package.json
├── packages
├── gitmoji-changelog-cli
│ ├── .npmignore
│ ├── README.md
│ ├── package.json
│ └── src
│ │ ├── cli.e2e.js
│ │ ├── cli.js
│ │ ├── cli.spec.js
│ │ ├── index.js
│ │ ├── interactiveMode.js
│ │ ├── interactiveMode.spec.js
│ │ ├── presets
│ │ ├── cargo.js
│ │ ├── generic.js
│ │ ├── generic.spec.js
│ │ ├── helm.js
│ │ ├── helm.spec.js
│ │ ├── maven.js
│ │ ├── node.js
│ │ ├── node.spec.js
│ │ ├── python.js
│ │ └── python.spec.js
│ │ ├── repository.js
│ │ └── repository.spec.js
├── gitmoji-changelog-core
│ ├── .npmignore
│ ├── package.json
│ └── src
│ │ ├── errors.js
│ │ ├── fromGitFile.js
│ │ ├── groupMapping.js
│ │ ├── index.js
│ │ ├── index.spec.js
│ │ ├── logger.js
│ │ ├── parser.js
│ │ ├── parser.spec.js
│ │ ├── utils.js
│ │ └── utils.spec.js
├── gitmoji-changelog-documentation
│ ├── .nojekyll
│ ├── README.md
│ ├── index.html
│ └── package.json
└── gitmoji-changelog-markdown
│ ├── .npmignore
│ ├── package.json
│ └── src
│ ├── index.js
│ ├── index.spec.js
│ └── template.md
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "gitmoji-changelog",
3 | "projectOwner": "frinyvonnick",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "gitmoji",
12 | "contributors": [
13 | {
14 | "login": "frinyvonnick",
15 | "name": "Yvonnick FRIN",
16 | "avatar_url": "https://avatars0.githubusercontent.com/u/13099512?v=4",
17 | "profile": "https://yvonnickfrin.dev",
18 | "contributions": [
19 | "code"
20 | ]
21 | },
22 | {
23 | "login": "bpetetot",
24 | "name": "Benjamin Petetot",
25 | "avatar_url": "https://avatars3.githubusercontent.com/u/516360?v=4",
26 | "profile": "https://github.com/bpetetot",
27 | "contributions": [
28 | "code"
29 | ]
30 | },
31 | {
32 | "login": "fabienjuif",
33 | "name": "Fabien JUIF",
34 | "avatar_url": "https://avatars0.githubusercontent.com/u/17828231?v=4",
35 | "profile": "https://github.com/fabienjuif",
36 | "contributions": [
37 | "code"
38 | ]
39 | },
40 | {
41 | "login": "bgauduch",
42 | "name": "Baptiste Gauduchon",
43 | "avatar_url": "https://avatars0.githubusercontent.com/u/5585755?v=4",
44 | "profile": "https://github.com/bgauduch",
45 | "contributions": [
46 | "code"
47 | ]
48 | },
49 | {
50 | "login": "kefranabg",
51 | "name": "Franck Abgrall",
52 | "avatar_url": "https://avatars3.githubusercontent.com/u/9840435?v=4",
53 | "profile": "https://www.franck-abgrall.me/",
54 | "contributions": [
55 | "code"
56 | ]
57 | },
58 | {
59 | "login": "quentinncl",
60 | "name": "quentinncl",
61 | "avatar_url": "https://avatars0.githubusercontent.com/u/12430277?v=4",
62 | "profile": "https://github.com/quentinncl",
63 | "contributions": [
64 | "code"
65 | ]
66 | },
67 | {
68 | "login": "lhauspie",
69 | "name": "Logan HAUSPIE",
70 | "avatar_url": "https://avatars1.githubusercontent.com/u/25682509?v=4",
71 | "profile": "https://github.com/lhauspie",
72 | "contributions": [
73 | "code"
74 | ]
75 | },
76 | {
77 | "login": "gmembre-zenika",
78 | "name": "Guillaume Membré",
79 | "avatar_url": "https://avatars3.githubusercontent.com/u/7294764?v=4",
80 | "profile": "https://www.geekeries.fun",
81 | "contributions": [
82 | "code"
83 | ]
84 | },
85 | {
86 | "login": "yannbertrand",
87 | "name": "Yann Bertrand",
88 | "avatar_url": "https://avatars0.githubusercontent.com/u/5855339?v=4",
89 | "profile": "http://yann-bertrand.fr/",
90 | "contributions": [
91 | "code"
92 | ]
93 | },
94 | {
95 | "login": "snikic",
96 | "name": "s n",
97 | "avatar_url": "https://avatars3.githubusercontent.com/u/2975674?v=4",
98 | "profile": "https://github.com/snikic",
99 | "contributions": [
100 | "code"
101 | ]
102 | },
103 | {
104 | "login": "mathieutu",
105 | "name": "Mathieu TUDISCO",
106 | "avatar_url": "https://avatars3.githubusercontent.com/u/11351322?v=4",
107 | "profile": "https://mathieutu.dev",
108 | "contributions": [
109 | "code"
110 | ]
111 | },
112 | {
113 | "login": "charlyx",
114 | "name": "Charles-Henri GUERIN",
115 | "avatar_url": "https://avatars2.githubusercontent.com/u/481446?v=4",
116 | "profile": "http://charlyx.dev",
117 | "contributions": [
118 | "code"
119 | ]
120 | },
121 | {
122 | "login": "FBerthelot",
123 | "name": "Florent Berthelot",
124 | "avatar_url": "https://avatars1.githubusercontent.com/u/6927852?v=4",
125 | "profile": "https://github.com/FBerthelot",
126 | "contributions": [
127 | "code"
128 | ]
129 | },
130 | {
131 | "login": "EmmanuelDemey",
132 | "name": "Emmanuel DEMEY",
133 | "avatar_url": "https://avatars2.githubusercontent.com/u/555768?v=4",
134 | "profile": "http://gillespie59.github.io/",
135 | "contributions": [
136 | "code"
137 | ]
138 | },
139 | {
140 | "login": "christopherkade",
141 | "name": "Christopher Kade",
142 | "avatar_url": "https://avatars3.githubusercontent.com/u/15229355?v=4",
143 | "profile": "https://christopherkade.com",
144 | "contributions": [
145 | "blog"
146 | ]
147 | },
148 | {
149 | "login": "rudym",
150 | "name": "Rodion Martynov",
151 | "avatar_url": "https://avatars0.githubusercontent.com/u/863788?v=4",
152 | "profile": "http://numcom.herokuapp.com/",
153 | "contributions": [
154 | "doc"
155 | ]
156 | },
157 | {
158 | "login": "DanielTamkin",
159 | "name": "Daniel Tamkin",
160 | "avatar_url": "https://avatars1.githubusercontent.com/u/9532762?v=4",
161 | "profile": "http://danieltamkin.com",
162 | "contributions": [
163 | "doc"
164 | ]
165 | },
166 | {
167 | "login": "endormi",
168 | "name": "Erno Salo",
169 | "avatar_url": "https://avatars3.githubusercontent.com/u/39559256?v=4",
170 | "profile": "http://endormi.io",
171 | "contributions": [
172 | "doc"
173 | ]
174 | },
175 | {
176 | "login": "MarkLyck",
177 | "name": "Mark Lyck",
178 | "avatar_url": "https://avatars3.githubusercontent.com/u/6841110?v=4",
179 | "profile": "http://markdid.it",
180 | "contributions": [
181 | "code"
182 | ]
183 | },
184 | {
185 | "login": "horaklukas",
186 | "name": "Lukáš Horák",
187 | "avatar_url": "https://avatars.githubusercontent.com/u/996088?v=4",
188 | "profile": "https://github.com/horaklukas",
189 | "contributions": [
190 | "code"
191 | ]
192 | },
193 | {
194 | "login": "juwit",
195 | "name": "Julien WITTOUCK",
196 | "avatar_url": "https://avatars.githubusercontent.com/u/7531844?v=4",
197 | "profile": "https://github.com/juwit",
198 | "contributions": [
199 | "code"
200 | ]
201 | }
202 | ],
203 | "contributorsPerLine": 7,
204 | "skipCi": true
205 | }
206 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **
2 |
3 | !Dockerfile
4 | !package.json
5 | !yarn.lock
6 | !packages/
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb-base/legacy",
3 | "env": {
4 | "jest": true,
5 | "node": true,
6 | "es6": true
7 | },
8 | "parserOptions": {
9 | "ecmaVersion": 2018
10 | },
11 | "rules": {
12 | "semi": ["error", "never"],
13 | "no-use-before-define": ["error", { "functions": false }],
14 | "comma-dangle": ["error", "always-multiline"],
15 | "no-console": 0
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [frinyvonnick,bpetetot]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/canary-release.yml:
--------------------------------------------------------------------------------
1 | name: Canary release
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | strategy:
13 | matrix:
14 | node-version: [12]
15 |
16 | steps:
17 | - name: Checkout project
18 | uses: actions/checkout@v1
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | registry-url: 'https://registry.npmjs.org'
24 | - name: Install dependencies
25 | run: yarn install --frozen-lockfile
26 | - name: Publish Canary version
27 | run: npx lerna publish --canary --yes --exact
28 | env:
29 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-lint.yml:
--------------------------------------------------------------------------------
1 | name: Test and lint
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [12]
16 |
17 | steps:
18 | - name: Checkout project
19 | uses: actions/checkout@v1
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v1
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | - name: Install dependencies
25 | run: yarn install --frozen-lockfile
26 | - name: Lint
27 | run: yarn lint
28 | - name: Unit tests
29 | run: yarn test
30 | - name: End to end tests
31 | run: |
32 | git config --global user.email "test@example.com"
33 | git config --global user.name "Test gitmoji"
34 | yarn test:e2e
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | CHANGELOG.json
64 | .vscode
65 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 |
4 | ## 2.3.0 (2022-01-04)
5 |
6 | ### Added
7 |
8 | - ✨ Add Helm preset ([#229](https://github.com/frinyvonnick/gitmoji-changelog/issues/229)) [[8c16f72](https://github.com/frinyvonnick/gitmoji-changelog/commit/8c16f72302d44b20727d6e7b82adb68802afb72e)]
9 |
10 | ### Changed
11 |
12 | - ⬆️ Bump ssri from 6.0.1 to 6.0.2 ([#215](https://github.com/frinyvonnick/gitmoji-changelog/issues/215)) [[ed4a033](https://github.com/frinyvonnick/gitmoji-changelog/commit/ed4a03398242caa5f17dcd5cdda4f8b8815e6cc3)]
13 | - ⬆️ Bump normalize-url from 4.5.0 to 4.5.1 ([#219](https://github.com/frinyvonnick/gitmoji-changelog/issues/219)) [[9cb9e41](https://github.com/frinyvonnick/gitmoji-changelog/commit/9cb9e41ce0ab9e066616f9d60fa422094c2c1527)]
14 | - ⬆️ Bump y18n from 3.2.1 to 3.2.2 ([#220](https://github.com/frinyvonnick/gitmoji-changelog/issues/220)) [[0668d86](https://github.com/frinyvonnick/gitmoji-changelog/commit/0668d8604263776117a6052d29b33473b770c196)]
15 | - ⬆️ Upgrade dependencies using yarn upgrade ([#232](https://github.com/frinyvonnick/gitmoji-changelog/issues/232)) [[14f55bc](https://github.com/frinyvonnick/gitmoji-changelog/commit/14f55bc61cdff0863fc2c73af9086191cab93935)]
16 | - ⬆️ Upgrade vulnerable packages ([#230](https://github.com/frinyvonnick/gitmoji-changelog/issues/230)) [[27c53bd](https://github.com/frinyvonnick/gitmoji-changelog/commit/27c53bd253f17a94eac1c78b42a0630be3b299e9)]
17 |
18 | ### Miscellaneous
19 |
20 | - 👥 Add horaklukas as a contributor for code ([#234](https://github.com/frinyvonnick/gitmoji-changelog/issues/234)) [[1f94e98](https://github.com/frinyvonnick/gitmoji-changelog/commit/1f94e987d014bb5863eebf9811d5c3f01b71bb9b)]
21 | - 👥 Add juwit as a contributor for code ([#233](https://github.com/frinyvonnick/gitmoji-changelog/issues/233)) [[b643004](https://github.com/frinyvonnick/gitmoji-changelog/commit/b643004a1e5173d5387026530041b863bf930788)]
22 |
23 |
24 |
25 | ## 2.2.1 (2021-01-21)
26 |
27 | ### Changed
28 |
29 | - 📌 Fix yarn.lock not up to date ([#207](https://github.com/frinyvonnick/gitmoji-changelog/issues/207)) [[54d194f](https://github.com/frinyvonnick/gitmoji-changelog/commit/54d194f08ebb073c14626c80dc5ecfa494e5a3e2)]
30 |
31 | ### Fixed
32 |
33 | - 🐛 Fix unsupported key using read-pkg-up ([#206](https://github.com/frinyvonnick/gitmoji-changelog/issues/206)) [[7a2eb08](https://github.com/frinyvonnick/gitmoji-changelog/commit/7a2eb084c37e05300dd53fb940da1f24206f29f6)]
34 |
35 | ### Miscellaneous
36 |
37 | - 👥 Add MarkLyck as a contributor ([#208](https://github.com/frinyvonnick/gitmoji-changelog/issues/208)) [[134dfb9](https://github.com/frinyvonnick/gitmoji-changelog/commit/134dfb95ea360bd566d272256881443f30829c18)]
38 |
39 |
40 |
41 | ## 2.2.0 (2020-12-16)
42 |
43 | ### Added
44 |
45 | - ✨ Add Cargo preset ([#199](https://github.com/frinyvonnick/gitmoji-changelog/issues/199)) [[ceab8dc](https://github.com/frinyvonnick/gitmoji-changelog/commit/ceab8dcd86b3fbb1c91134bfad844a16f6f0f609)]
46 |
47 | ### Changed
48 |
49 | - 📌 Upgrade dependencies ([#191](https://github.com/frinyvonnick/gitmoji-changelog/issues/191)) [[cdf76f9](https://github.com/frinyvonnick/gitmoji-changelog/commit/cdf76f9a5f12a11703216de203f99a144436cfe9)]
50 |
51 | ### Fixed
52 |
53 | - 🐛 Fix init command ([#204](https://github.com/frinyvonnick/gitmoji-changelog/issues/204)) [[a9b1bfe](https://github.com/frinyvonnick/gitmoji-changelog/commit/a9b1bfe1642218de7005c8e6870e8a5cad31a4aa)]
54 | - 🐛 Fix a bug occuring when version is missing in configuration but passed as argument ([#190](https://github.com/frinyvonnick/gitmoji-changelog/issues/190)) [[20cf369](https://github.com/frinyvonnick/gitmoji-changelog/commit/20cf3690dd0acd0712f3068368c80ade1422cd5b)]
55 |
56 | ### Miscellaneous
57 |
58 | - 📝 Add the documentation site ([#194](https://github.com/frinyvonnick/gitmoji-changelog/issues/194)) [[5e0a971](https://github.com/frinyvonnick/gitmoji-changelog/commit/5e0a971775a7c986b11c7ee4fe9404ac4a2472cd)]
59 | - 🥅 Handle functional errors [[9a15406](https://github.com/frinyvonnick/gitmoji-changelog/commit/9a154061439a0e1e253e15b20ee5df6ae4b5388a)]
60 | - 📝 Improve documentation about presets ([#189](https://github.com/frinyvonnick/gitmoji-changelog/issues/189)) [[4e22239](https://github.com/frinyvonnick/gitmoji-changelog/commit/4e22239bd96e4b60eb966297e2e67e2070b03ea0)]
61 | - 📝 Improve Workflow section ([#152](https://github.com/frinyvonnick/gitmoji-changelog/issues/152)) [[1635963](https://github.com/frinyvonnick/gitmoji-changelog/commit/1635963b25fb6b611a563b918c5d78a5e11a3c16)]
62 |
63 |
64 |
65 | ## 2.1.0 (2020-01-21)
66 |
67 | ### Added
68 |
69 | - ✨ Add maven preset ([#172](https://github.com/frinyvonnick/gitmoji-changelog/issues/172)) [[1622775](https://github.com/frinyvonnick/gitmoji-changelog/commit/16227751f3c3a60192d6c277434ec85ab2d13b4f)] (by Guillaume Membré)
70 | - ✨ Generate a bug report when unexpected errors happens ([#137](https://github.com/frinyvonnick/gitmoji-changelog/issues/137)) [[6d68130](https://github.com/frinyvonnick/gitmoji-changelog/commit/6d6813032b735cb4832fd9020ee59527391e41b6)] (by Yvonnick FRIN)
71 | - ✨ Add a generic preset using configuration file ([#160](https://github.com/frinyvonnick/gitmoji-changelog/issues/160)) [[d41ebc6](https://github.com/frinyvonnick/gitmoji-changelog/commit/d41ebc64bb2ce8b11b4ba4e2f11540a0c48b815f)] (by Yvonnick FRIN)
72 | - ✨ Add configuration so users can customize commit mapping ([#155](https://github.com/frinyvonnick/gitmoji-changelog/issues/155)) [[be1fca2](https://github.com/frinyvonnick/gitmoji-changelog/commit/be1fca23df30c201956ca488c3bfa7b33a9c4381)] (by Yvonnick FRIN)
73 |
74 | ### Changed
75 |
76 | - ♻️ Make core independent from the git client. ([#171](https://github.com/frinyvonnick/gitmoji-changelog/issues/171)) [[4094bb9](https://github.com/frinyvonnick/gitmoji-changelog/commit/4094bb9abe01a731b0c20e0dcd006c8e08a66dc6)] (by Benjamin Petetot)
77 | - ♻️ Improve error messages for preset system ([#161](https://github.com/frinyvonnick/gitmoji-changelog/issues/161)) [[a51fee2](https://github.com/frinyvonnick/gitmoji-changelog/commit/a51fee25c633946b6aea9fe31f928e5e415a5171)] (by Yvonnick FRIN)
78 |
79 | ### Fixed
80 |
81 | - ✏️ Fix typo in CHANGELOG [[14662c4](https://github.com/frinyvonnick/gitmoji-changelog/commit/14662c46cc5fb9ad14f9f7350d01a7c00c1e972c)] (by FRIN Yvonnick)
82 |
83 | ### Miscellaneous
84 |
85 | - 📝 Correct sentence structure in documentation ([#173](https://github.com/frinyvonnick/gitmoji-changelog/issues/173)) [[36872a0](https://github.com/frinyvonnick/gitmoji-changelog/commit/36872a0fa8d02b264cab0d629869cb6538c126cb)] (by Daniel Tamkin)
86 | - 🐳 Upgrade Docker image version ([#167](https://github.com/frinyvonnick/gitmoji-changelog/issues/167)) [[8276d4a](https://github.com/frinyvonnick/gitmoji-changelog/commit/8276d4a9f8c67b6597116ba3a6472d44cdb29508)] (by Charles-Henri GUERIN)
87 | - 📝 Add documentation about generic preset ([#163](https://github.com/frinyvonnick/gitmoji-changelog/issues/163)) [[c940075](https://github.com/frinyvonnick/gitmoji-changelog/commit/c940075565d89c72c5d0114bc88e00133145f8be)] (by Yvonnick FRIN)
88 | - 📝 Fix presets path link ([#150](https://github.com/frinyvonnick/gitmoji-changelog/issues/150)) [[a27f818](https://github.com/frinyvonnick/gitmoji-changelog/commit/a27f8180ddc8f2a8280eb8b72acc8999ff66250d)] (by Rodion Martynov)
89 |
90 |
91 |
92 | ## 2.0.1 (2019-11-19)
93 |
94 | ### Added
95 |
96 | - 👷♂️ Add exact option to canary version ([#147](https://github.com/frinyvonnick/gitmoji-changelog/issues/147)) [[84c7086](https://github.com/frinyvonnick/gitmoji-changelog/commit/84c7086b94190d9259749262e997be8617e10345)] (by Benjamin Petetot)
97 | - 👷♂️ Publish Canary version on master push ([#141](https://github.com/frinyvonnick/gitmoji-changelog/issues/141)) [[84ab6f1](https://github.com/frinyvonnick/gitmoji-changelog/commit/84ab6f17a40831ca780f3ce60420ac3268624918)] (by Benjamin Petetot)
98 | - 👷♂️ Setup GitHub CI ([#140](https://github.com/frinyvonnick/gitmoji-changelog/issues/140)) [[a7f1228](https://github.com/frinyvonnick/gitmoji-changelog/commit/a7f1228ed313bbd507686e6cdcac5d9a5a8be46b)] (by Benjamin Petetot)
99 |
100 | ### Fixed
101 |
102 | - 🐛 Fix bug preventing generating changelog when previous version doesn't exist ([#145](https://github.com/frinyvonnick/gitmoji-changelog/issues/145)) [[4f04f45](https://github.com/frinyvonnick/gitmoji-changelog/commit/4f04f453af9c3fd82b388b082aa59b9a6f1d5374)] (by Yvonnick FRIN)
103 |
104 |
105 | ## 2.0.0 (2019-10-31)
106 |
107 | ### Added
108 |
109 | - ✨ Add a presets system to be independent from the package.json ([#128](https://github.com/frinyvonnick/gitmoji-changelog/issues/128)) [[24cead9](https://github.com/frinyvonnick/gitmoji-changelog/commit/24cead964ef3e9c1b5b7df9d53bfe357298a9191)] (by Yvonnick FRIN)
110 | - ✨ Add interactive option to manually select commits ([#81](https://github.com/frinyvonnick/gitmoji-changelog/issues/81)) [[d13ca2e](https://github.com/frinyvonnick/gitmoji-changelog/commit/d13ca2e1c77c3cd9694cde926442c303adb47fa3)] (by Franck Abgrall)
111 |
112 | ### Changed
113 |
114 | - ♻️ Change core function parameters from "mode" to "from" and "to" ([#127](https://github.com/frinyvonnick/gitmoji-changelog/issues/127)) [[42af500](https://github.com/frinyvonnick/gitmoji-changelog/commit/42af500df1b9b9d738bf9d47674537b50dd90870)] (by Yvonnick FRIN)
115 | - 🔧 Add editor config file ([#124](https://github.com/frinyvonnick/gitmoji-changelog/issues/124)) [[6c763a2](https://github.com/frinyvonnick/gitmoji-changelog/commit/6c763a2be93908232ffda7ca9d88499d38809c99)] (by quentinncl)
116 |
117 | ### Removed
118 |
119 | - 🔥 Remove some badges [[6d27a32](https://github.com/frinyvonnick/gitmoji-changelog/commit/6d27a32b900a1d1462f39a92d09b5ab1407be25b)] (by Yvonnick FRIN)
120 |
121 | ### Fixed
122 |
123 | - 🐛 Handle pre-release as latest version in markdown ([#126](https://github.com/frinyvonnick/gitmoji-changelog/issues/126)) [[d22699b](https://github.com/frinyvonnick/gitmoji-changelog/commit/d22699b6226cb03f634b22f65f0f825f980dc666)] (by Yvonnick FRIN)
124 | - 🐛 Fix updating changelog after tagging a version ([#115](https://github.com/frinyvonnick/gitmoji-changelog/issues/115)) [[8df6705](https://github.com/frinyvonnick/gitmoji-changelog/commit/8df6705bc369bbec16cc2dc1ff08c0ae55c20da3)] (by Yvonnick FRIN)
125 | - 🐛 Use last version from changelog file instead of previous git tag ([#82](https://github.com/frinyvonnick/gitmoji-changelog/issues/82)) [[d3c49d0](https://github.com/frinyvonnick/gitmoji-changelog/commit/d3c49d061cfbe2c271f9aa3739fae750dbf6327c)] (by Franck Abgrall)
126 | - 🐛 Fix dates sorting in commits ([#75](https://github.com/frinyvonnick/gitmoji-changelog/issues/75)) [[748e673](https://github.com/frinyvonnick/gitmoji-changelog/commit/748e6732a18f8bc5c529db12a558c0ffb458c8a1)] (by Mathieu TUDISCO)
127 |
128 | ### Miscellaneous
129 |
130 | - 📝 Add npx instruction on README ([#123](https://github.com/frinyvonnick/gitmoji-changelog/issues/123)) [[ffcbef5](https://github.com/frinyvonnick/gitmoji-changelog/commit/ffcbef55efd4558335f913d1c38411229a330a58)] (by quentinncl)
131 | - 📝 Add contributors to README ([#125](https://github.com/frinyvonnick/gitmoji-changelog/issues/125)) [[7580ff9](https://github.com/frinyvonnick/gitmoji-changelog/commit/7580ff9c6598b92aa3375836ce0b568e8378c65f)] (by Franck Abgrall)
132 | - 📝 Create CODE_OF_CONDUCT.md file [[51c1314](https://github.com/frinyvonnick/gitmoji-changelog/commit/51c1314fb0f6b2770bf2fdbdffc1912b8e06bbf7)] (by Yvonnick FRIN)
133 | - 📝 Propose to add badge to our users ([#103](https://github.com/frinyvonnick/gitmoji-changelog/issues/103)) [[f5981cb](https://github.com/frinyvonnick/gitmoji-changelog/commit/f5981cbeb102c8b6d36427c64f5e4f31932a938a)] (by Florent Berthelot)
134 | - 🐳 Add dependency in markdown package to fix docker image [[804a780](https://github.com/frinyvonnick/gitmoji-changelog/commit/804a780a957e2ae6364001331cd8ed818a9580a1)] (by FRIN Yvonnick)
135 | - 🐳 Improve Dockerfile and update docker image usage ([#109](https://github.com/frinyvonnick/gitmoji-changelog/issues/109)) [[55e24c2](https://github.com/frinyvonnick/gitmoji-changelog/commit/55e24c2bf8ae3ad2fd238b8480862498aa1a134e)] (by Baptiste Gauduchon)
136 | - 📝 Add docker usage section ([#99](https://github.com/frinyvonnick/gitmoji-changelog/issues/99)) [[53c96bc](https://github.com/frinyvonnick/gitmoji-changelog/commit/53c96bcd8c66f7a05e38739a1bde82585a3fee86)] (by Baptiste Gauduchon)
137 | - 🐳 Fix dependencies versions in Dockerfile ([#97](https://github.com/frinyvonnick/gitmoji-changelog/issues/97)) [[719541d](https://github.com/frinyvonnick/gitmoji-changelog/commit/719541dd414b62bba59178259a0830626ec70b2e)] (by Baptiste Gauduchon)
138 | - 📦 Add .npmignore file ([#73](https://github.com/frinyvonnick/gitmoji-changelog/issues/73)) [[b0a243f](https://github.com/frinyvonnick/gitmoji-changelog/commit/b0a243ffbfe5a84de4a8173f4aa79f19acc32b95)] (by Emmanuel DEMEY)
139 | - 🐳 Optimize context and image size [[a692310](https://github.com/frinyvonnick/gitmoji-changelog/commit/a692310da45654da5485622a189cc0e350684d0c)] (by Fabien JUIF)
140 |
141 |
142 | ## 1.1.0 (2018-11-10)
143 |
144 | ### Added
145 |
146 | - ✨ Check CLI version ([#52](https://github.com/frinyvonnick/gitmoji-changelog/issues/52)) [[18ead38](https://github.com/frinyvonnick/gitmoji-changelog/commit/18ead387032beda06f8097fbc3e8ad65d7268d42)] (by Fabien JUIF)
147 | - ✨ Do not override the top of the existing file ([#53](https://github.com/frinyvonnick/gitmoji-changelog/issues/53)) [[15edd6c](https://github.com/frinyvonnick/gitmoji-changelog/commit/15edd6cd49f672861e9b8195798d23746a39a7c1)] (by Fabien JUIF)
148 | - ✨ Add a breaking changes section ([#62](https://github.com/frinyvonnick/gitmoji-changelog/issues/62)) [[2a9904b](https://github.com/frinyvonnick/gitmoji-changelog/commit/2a9904b26843bb6721e2984049eb75d0914d4104)] (by Logan HAUSPIE)
149 | - ✨ Filter bookmark commit out ([#54](https://github.com/frinyvonnick/gitmoji-changelog/issues/54)) [[7858140](https://github.com/frinyvonnick/gitmoji-changelog/commit/78581402cde9f6ce4c3de6f558629a08941e9d0a)] (by Fabien JUIF)
150 | - ✨ Add the author in changelog lines ([#56](https://github.com/frinyvonnick/gitmoji-changelog/issues/56)) [[979da30](https://github.com/frinyvonnick/gitmoji-changelog/commit/979da30f5e52385b99bd4a58e1a946793bd1196d)] (by Benjamin Petetot)
151 |
152 | ### Fixed
153 |
154 | - 🐛 Remove empty versions ([#51](https://github.com/frinyvonnick/gitmoji-changelog/issues/51)) [[64ac5a7](https://github.com/frinyvonnick/gitmoji-changelog/commit/64ac5a72fe78877625a8b3cb77183a227a5fb881)] (by Fabien JUIF)
155 | - 🐛 Parse unicode emoji in commit ([#49](https://github.com/frinyvonnick/gitmoji-changelog/issues/49)) [[4d52747](https://github.com/frinyvonnick/gitmoji-changelog/commit/4d52747a585e46f118d39b4b1df199b41888b9c2)] (by Benjamin Petetot)
156 | - 🐛 Fix duplication with incremental update ([#60](https://github.com/frinyvonnick/gitmoji-changelog/issues/60)) [[c4ffc59](https://github.com/frinyvonnick/gitmoji-changelog/commit/c4ffc59c708452a5b9ed0d86f88031442ec44b82)] (by Benjamin Petetot)
157 | - 🐛 Fix emojies not identified in some commits ([#65](https://github.com/frinyvonnick/gitmoji-changelog/issues/65)) [[6a3b1b6](https://github.com/frinyvonnick/gitmoji-changelog/commit/6a3b1b642cb05c376079f4ad4a00a92445808722)] (by Benjamin Petetot)
158 |
159 | ### Miscellaneous
160 |
161 | - ⚗ Group similar commits together ([#50](https://github.com/frinyvonnick/gitmoji-changelog/issues/50)) [[06c8f3c](https://github.com/frinyvonnick/gitmoji-changelog/commit/06c8f3c5d9a28bbf2606cbe54b96f8932110fb10)] (by Fabien JUIF)
162 | - 🐳 Init dockerfile ([#61](https://github.com/frinyvonnick/gitmoji-changelog/issues/61)) [[afa7021](https://github.com/frinyvonnick/gitmoji-changelog/commit/afa70219878d39623f8337d479547a2cbadc4c4b)] (by Logan HAUSPIE) & ([#69](https://github.com/frinyvonnick/gitmoji-changelog/issues/69)) [[36c0e06](https://github.com/frinyvonnick/gitmoji-changelog/commit/36c0e061429e93b5ba6cb898f15ea874e4a60e67)] (by Charles-Henri GUERIN)
163 |
164 |
165 |
166 | ## 1.0.1 (2018-10-25)
167 |
168 | ### Fixed
169 |
170 | - 🐛 Prevent having right commits by version ([#30](https://github.com/frinyvonnick/gitmoji-changelog/issues/30)) ([de06319](https://github.com/frinyvonnick/gitmoji-changelog/commit/de063192baefebad16e05ce79061d815888a442f))
171 | - 🐛 Get correct commit ranges from tags ([#32](https://github.com/frinyvonnick/gitmoji-changelog/issues/32)) ([573a2d0](https://github.com/frinyvonnick/gitmoji-changelog/commit/573a2d0b583b426d358c388af0ba1dc48a4e0ddf))
172 |
173 |
174 |
175 | ## 1.0.0 (2018-10-25)
176 |
177 | ### Added
178 |
179 | - ✨ Generate full and partial changelog ([#23](https://github.com/frinyvonnick/gitmoji-changelog/issues/23)) ([e7c934a](https://github.com/frinyvonnick/gitmoji-changelog/commit/e7c934a1372344935ddd3739e32a9732d48dc0b8))
180 | - ✨ Group commits by emojis group ([#11](https://github.com/frinyvonnick/gitmoji-changelog/issues/11)) ([7b8b49b](https://github.com/frinyvonnick/gitmoji-changelog/commit/7b8b49b366d3d51f0cc75f4ffb67efcd633cca16))
181 | - ✨ Sort commits by emoji and date ([#24](https://github.com/frinyvonnick/gitmoji-changelog/issues/24)) ([08b2af3](https://github.com/frinyvonnick/gitmoji-changelog/commit/08b2af3241a13f6ebae9c24a3a51ef10b60f2879))
182 | - ✨ Support commit's URL and issues autolink in changelog ([#16](https://github.com/frinyvonnick/gitmoji-changelog/issues/16)) ([871f164](https://github.com/frinyvonnick/gitmoji-changelog/commit/871f16499acee400863a94976e2520a4bdbc6cea))
183 | - ✨ Add tag's date on version ([#22](https://github.com/frinyvonnick/gitmoji-changelog/issues/22)) ([ab3d9c6](https://github.com/frinyvonnick/gitmoji-changelog/commit/ab3d9c600e307dd0db16bc7abcbdf8a8a2c83ff5))
184 | - ✨ Generate changelog JSON file ([86ca3ea](https://github.com/frinyvonnick/gitmoji-changelog/commit/86ca3eaefb18fd9c9b6bb4256ed2f6fa711aef59))
185 | - ✨ Generate changelog markdown file ([b165f69](https://github.com/frinyvonnick/gitmoji-changelog/commit/b165f695f4c1a49ff16a5f03918545bfb36cf367))
186 |
187 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at frin.yvonnick@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Develop and contribute
2 |
3 | ## Setup
4 |
5 | ```sh
6 | git clone git@github.com:frinyvonnick/gitmoji-changelog.git
7 |
8 | cd gitmoji-changelog && yarn
9 | ```
10 |
11 | We are using lerna and yarn workspaces to split the library in modules:
12 | - [gitmoji-changelog-cli](https://github.com/frinyvonnick/gitmoji-changelog/tree/master/packages/gitmoji-changelog-cli) - the full-featured command line interface
13 | - [gitmoji-changelog-core](https://github.com/frinyvonnick/gitmoji-changelog/tree/master/packages/gitmoji-changelog-core) - the core lib generating changelog
14 | - [gitmoji-changelog-markdown](https://github.com/frinyvonnick/gitmoji-changelog/tree/master/packages/gitmoji-changelog-markdown) - the markdown changelog file writer
15 |
16 | ## Usage locally
17 |
18 | ```sh
19 | node [path-to-gitmoji-changelog-folder]/packages/gitmoji-changelog-cli/src/index.js
20 | ```
21 |
22 | ## Run unit tests
23 |
24 | ```sh
25 | yarn test
26 | # or
27 | yarn test --watch
28 | ```
29 |
30 | ## Run e2e tests
31 |
32 | ```sh
33 | yarn test:e2e
34 | ```
35 |
36 | ## Run linter
37 |
38 | We are using [airbnb-base](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base) as linter:
39 |
40 | ```sh
41 | yarn lint
42 | ```
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12.14.1-alpine3.11
2 | ENV NODE_ENV=production
3 |
4 | # install dependencies
5 | RUN apk add --no-cache git=2.24.1-r0
6 |
7 | # build gitmoji-changelog from source
8 | WORKDIR /usr/src/gitmoji-changelog
9 | COPY . .
10 | RUN yarn --frozen-lockfile && yarn cache clean
11 |
12 | # run gitmoji-changelog on container startup
13 | RUN ln -s /usr/src/gitmoji-changelog/node_modules/.bin/gitmoji-changelog /usr/bin
14 | WORKDIR /app
15 | ENTRYPOINT ["gitmoji-changelog"]
16 | USER node
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 FRIN Yvonnick
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | > Generate changelog for repositories using [gitmoji](https://gitmoji.carloscuesta.me/) commits convention.
14 |
15 |
16 | 
17 |
18 | ## 🚀 Usage
19 |
20 | Make sure you have [npx](https://www.npmjs.com/package/npx) installed (`npx` is shipped by default since npm `5.2.0`)
21 |
22 | Run the following command at the root of your project and answer questions. `gitmoji-changelog` uses a [preset system](https://docs.gitmoji-changelog.dev/#/?id=%e2%9a%99%ef%b8%8f-presets) to handle different type of project. The preset used by default is the Node.js one that look for project's information in the `package.json` file.
23 |
24 | with npx:
25 | ```sh
26 | npx gitmoji-changelog
27 | ```
28 |
29 | with npm:
30 | ```sh
31 | npm install -g gitmoji-changelog
32 |
33 | cd my-project
34 | gitmoji-changelog
35 | ```
36 |
37 | It exists a generic preset that works for every kind of project. It looks for information in a `.gitmoji-changelogrc` file at the root of your project. This file must contain three mandatory properties: `name`, `description` and `version`.
38 |
39 | .gitmoji-changelogrc:
40 | ```json
41 | {
42 | "project": {
43 | "name": "gitmoji-changelog",
44 | "description": "A changelog generator for gitmoji 😜",
45 | "version": "2.0.1"
46 | }
47 | }
48 | ```
49 |
50 | You can change the preset used by `gitmoji-changelog` with the preset option.
51 |
52 | ```sh
53 | npx gitmoji-changelog --preset generic
54 | ```
55 |
56 | ## 📖 Documentation
57 |
58 | :point_right: The full documentation is available [here](https://docs.gitmoji-changelog.dev).
59 |
60 | ## ✍ Author
61 |
62 | 👤 **Yvonnick FRIN (https://yvonnickfrin.dev)**
63 |
64 | * Twitter: [@YvonnickFrin](https://twitter.com/YvonnickFrin)
65 | * Github: [@frinyvonnick](https://github.com/frinyvonnick)
66 |
67 | ## 🤝 Contributing
68 |
69 | Contributions, issues and feature requests are welcome! Feel free to check [issues page](https://github.com/frinyvonnick/gitmoji-changelog/issues). You can also take a look at our [contributing guide](CONTRIBUTING.md).
70 |
71 | ## 🙏 Show your support
72 |
73 | Give a ⭐️ if this project helped you!
74 |
75 | You also can add a badge in the README.md of your repository to promote `gitmoji-changelog`. All you need is to copy/past the code below:
76 |
77 | ```markdown
78 | [](https://github.com/frinyvonnick/gitmoji-changelog)
79 | ```
80 |
81 | It will add this badge: [](https://github.com/frinyvonnick/gitmoji-changelog)
82 |
83 |
84 | ## ✨ Contributors
85 |
86 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
87 |
88 |
89 |
90 |
91 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
127 |
128 | ## 📝 License
129 |
130 | Copyright © 2020 [Yvonnick FRIN (https://github.com/frinyvonnick)](https://github.com/frinyvonnick).
131 | This project is [MIT](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/LICENSE) licensed.
132 |
133 | ***
134 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
135 |
--------------------------------------------------------------------------------
/doc/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frinyvonnick/gitmoji-changelog/d0edd29b861584808f6c0ba0edf7c391d3ddcb33/doc/screenshot.png
--------------------------------------------------------------------------------
/jest.config-e2e.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(e2e).[jt]s?(x)"],
3 | }
4 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.11.0",
3 | "npmClient": "yarn",
4 | "useWorkspaces": true,
5 | "packages": [
6 | "packages/*"
7 | ],
8 | "version": "2.3.0",
9 | "command": {
10 | "version": {
11 | "message": ":bookmark: Release %s"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/misc/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frinyvonnick/gitmoji-changelog/d0edd29b861584808f6c0ba0edf7c391d3ddcb33/misc/example.gif
--------------------------------------------------------------------------------
/misc/interactive.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frinyvonnick/gitmoji-changelog/d0edd29b861584808f6c0ba0edf7c391d3ddcb33/misc/interactive.gif
--------------------------------------------------------------------------------
/misc/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frinyvonnick/gitmoji-changelog/d0edd29b861584808f6c0ba0edf7c391d3ddcb33/misc/logo.png
--------------------------------------------------------------------------------
/misc/package.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitmoji-changelog",
3 | "version": "0.0.1",
4 | "description": "Some description",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "MIT"
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitmoji-changelog/workspace",
3 | "private": true,
4 | "version": "0.0.1",
5 | "description": "Tools for generating changelog based on gitmoji convention https://gitmoji.carloscuesta.me/",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/frinyvonnick/gitmoji-changelog.git"
9 | },
10 | "author": "Yvonnick FRIN (https://github.com/frinyvonnick)",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/frinyvonnick/gitmoji-changelog/issues"
14 | },
15 | "homepage": "https://github.com/frinyvonnick/gitmoji-changelog#readme",
16 | "scripts": {
17 | "test": "jest",
18 | "test:e2e": "jest --config ./jest.config-e2e.js",
19 | "lint": "eslint packages/**/src"
20 | },
21 | "devDependencies": {
22 | "all-contributors-cli": "^6.9.1",
23 | "eslint": "^5.4.0",
24 | "eslint-config-airbnb-base": "^13.1.0",
25 | "eslint-plugin-import": "^2.14.0",
26 | "jest": "^26.6.3",
27 | "lerna": "^4.0.0"
28 | },
29 | "workspaces": [
30 | "packages/*"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/.npmignore:
--------------------------------------------------------------------------------
1 | *.spec.js
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/README.md:
--------------------------------------------------------------------------------
1 | # gitmoji-changelog
2 |
3 | > Generate changelog for repositories using [gitmoji](https://gitmoji.carloscuesta.me/) commits convention.
4 |
5 | **Prerequisites:**
6 | - use [gitmoji](https://gitmoji.carloscuesta.me/) for commits convention
7 | - use [semver](https://semver.org/) for versions and tags convention
8 |
9 | ## Quick start
10 |
11 | ```bash
12 | npm install -g gitmoji-changelog
13 |
14 | cd my-project
15 |
16 | gitmoji-changelog
17 | ```
18 |
19 | If `CHANGELOG.md` file doesn't exist, it will generate all previous changelog based on semver tags of your repo.
20 |
21 | If `CHANGELOG.md` file already exists, _this will not overwrite any previous changelog_, it will generate a changelog based on commits since the last semver tag that match.
22 |
23 | All available commands and parameters can be listed using: `gitmoji-changelog --help`
24 |
25 | **Here an example output:** [CHANGELOG.md](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/CHANGELOG.md)
26 |
27 | ## Workflow
28 |
29 | Here the recommended workflow to generate your changelog file using `gitmoji-changelog`:
30 |
31 | **Important:** Before generating, be sure to have all tags locally (e.g. `git fetch origin`)
32 |
33 | 1. Make changes and commit: `git commit -m ":sparkles: my awesome feature"`
34 | 2. Bump version (ex: `1.0.0`) in `package.json` using [semver](https://semver.org/) convention
35 | 3. Run `gitmoji-changelog`, then the file `CHANGELOG.md` is created or updated with all changes
36 | 4. You can freely edit the new release in the changelog file, it will not be overwrite with the next generation
37 | 5. Commit `package.json` and `CHANGELOG.md` file
38 | 6. Tag your release: `git tag -a v1.0.0 -m "v1.0.0"` (or create a Github release)
39 | 7. Push to the remote `git push`
40 |
41 | ## Usage
42 |
43 | ```js
44 | gitmoji-changelog --help
45 | ```
46 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitmoji-changelog",
3 | "version": "2.3.0",
4 | "keywords": [
5 | "gitmoji",
6 | "generate",
7 | "changelog",
8 | "semver",
9 | "cli",
10 | "nantes",
11 | "version",
12 | "release",
13 | "markdown"
14 | ],
15 | "description": "Gitmoji Changelog CLI",
16 | "main": "src/index.js",
17 | "engines": {
18 | "node": ">=10"
19 | },
20 | "bin": {
21 | "gitmoji-changelog": "./src/index.js"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/frinyvonnick/gitmoji-changelog.git"
26 | },
27 | "author": "Yvonnick FRIN (https://github.com/frinyvonnick)",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/frinyvonnick/gitmoji-changelog/issues"
31 | },
32 | "homepage": "https://github.com/frinyvonnick/gitmoji-changelog#readme",
33 | "dependencies": {
34 | "@gitmoji-changelog/core": "^2.2.0",
35 | "@gitmoji-changelog/markdown": "^2.2.0",
36 | "hosted-git-info": "^3.0.2",
37 | "immutadot": "^1.0.0",
38 | "inquirer": "^6.3.1",
39 | "issue-reporter": "^0.2.0",
40 | "libnpm": "^3.0.1",
41 | "lodash": "^4.17.11",
42 | "pom-parser": "^1.2.0",
43 | "rc": "^1.2.8",
44 | "read-pkg-up": "^7.0.1",
45 | "semver": "^5.6.0",
46 | "semver-compare": "^1.0.0",
47 | "simple-git": "^1.113.0",
48 | "toml": "^3.0.0",
49 | "yaml": "^1.10.2",
50 | "yargs": "^17.3.1"
51 | },
52 | "publishConfig": {
53 | "access": "public",
54 | "registry": "https://registry.npmjs.org/"
55 | },
56 | "devDependencies": {
57 | "fs-extra": "^8.0.1"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/cli.e2e.js:
--------------------------------------------------------------------------------
1 | const os = require('os')
2 | const fs = require('fs')
3 | const { removeSync } = require('fs-extra')
4 | const path = require('path')
5 | const simpleGit = require('simple-git/promise')
6 | const childProcess = require('child_process')
7 |
8 | expect.extend({
9 | includes(str, items) {
10 | const pass = items.every(item => str.includes(item))
11 | return {
12 | pass,
13 | message: () => pass
14 | ? `Expected ${str} to not includes ${items.join(', ')}`
15 | : `Expected ${str} to includes ${items.join(', ')}`,
16 | }
17 | },
18 | })
19 |
20 | expect.extend({
21 | toDisplayError(str, message = '') {
22 | const pass = str.includes('Error') && str.includes(message)
23 | return {
24 | pass,
25 | message: () => pass ? 'It passes' : `Expected ${str} to includes Error and "${message}"`,
26 | }
27 | },
28 | })
29 |
30 | describe('generate changelog', () => {
31 | let testDir
32 | let repo
33 |
34 | beforeEach(async () => {
35 | testDir = path.join(os.tmpdir(), 'gitmoji-changelog')
36 | if (fs.existsSync(testDir)) removeSync(testDir)
37 | fs.mkdirSync(testDir)
38 | repo = simpleGit(testDir)
39 | await repo.init()
40 | fs.copyFileSync(
41 | path.join(__dirname, '..', '..', '..', 'misc', 'package.sample.json'),
42 | path.join(testDir, 'package.json'),
43 | )
44 | })
45 |
46 | afterEach(() => {
47 | removeSync(testDir)
48 | })
49 |
50 | describe('presets', () => {
51 | it('should throw an Error if the preset could not find configuration', () => {
52 | fs.unlinkSync(path.join(testDir, 'package.json'))
53 |
54 | const output = gitmojiChangelog()
55 |
56 | expect(output.toString('utf8')).includes(['Cannot retrieve configuration'])
57 | })
58 |
59 | it('should throw an Error if the preset did not return a version', () => {
60 | const packageJson = path.join(testDir, 'package.json')
61 | // eslint-disable-next-line global-require
62 | const content = JSON.parse(fs.readFileSync(packageJson).toString('utf8'))
63 | delete content.version
64 | fs.writeFileSync(packageJson, JSON.stringify(content))
65 |
66 | const output = gitmojiChangelog()
67 | console.log(output.toString('utf8'))
68 |
69 | expect(output.toString('utf8')).includes(['Cannot retrieve the version from your configuration'])
70 | })
71 | })
72 |
73 | describe('init', () => {
74 | it("should get a 1.0.0 version while initializing changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => {
75 | await makeChanges('file1')
76 | await commit(':sparkles: Add some file')
77 | await bumpVersion('1.0.0')
78 | gitmojiChangelog()
79 | await commit(':bookmark: Version 1.0.0')
80 |
81 | expect(getChangelog()).includes(['1.0.0'])
82 | })
83 |
84 | it("should get a 1.0.0 version while initializing changelog by calling cli with explicit init argument and having package.json's version set to 1.0.0", async () => {
85 | await makeChanges('file1')
86 | await commit(':sparkles: Add some file')
87 | await bumpVersion('1.0.0')
88 | gitmojiChangelog(['init'])
89 | await commit(':bookmark: Version 1.0.0')
90 |
91 | expect(getChangelog()).includes(['1.0.0'])
92 | })
93 |
94 | it('should use a custom commit mapping to create commit categories', async () => {
95 | makeCustomConfig({
96 | commitMapping: [
97 | { group: 'style', label: 'Style', emojis: ['lipstick'] },
98 | { group: 'changed', label: 'Changed', emojis: [] },
99 | ],
100 | })
101 | await makeChanges('file1')
102 | await commit(':sparkles: Add some file')
103 | await makeChanges('file2')
104 | await commit(':lipstick: Add some style file')
105 | await bumpVersion('1.0.0')
106 | gitmojiChangelog()
107 | await commit(':bookmark: Version 1.0.0')
108 |
109 | expect(getChangelog()).includes(['### Style'])
110 | expect(getChangelog()).not.includes(['### Changed'])
111 | })
112 |
113 | it("should get a 1.0.0 version while initializing changelog by calling cli with 1.0.0 and having package.json's version set to 0.0.1", async () => {
114 | await makeChanges('file1')
115 | await commit(':sparkles: Add some file')
116 | gitmojiChangelog('1.0.0')
117 | await commit(':bookmark: Version 1.0.0')
118 |
119 | expect(getChangelog()).includes(['1.0.0'])
120 | })
121 |
122 | it("should get a next version while initializing changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => {
123 | await makeChanges('file1')
124 | await commit(':sparkles: Add some file')
125 | await bumpVersion('1.0.0')
126 | await commit(':bookmark: Version 1.0.0')
127 | await tag('1.0.0')
128 |
129 | await makeChanges('file2')
130 | await commit(':sparkles: Add another file')
131 | gitmojiChangelog()
132 |
133 | expect(getChangelog()).includes(['1.0.0', 'next'])
134 | })
135 |
136 | it("should get two versions 1.0.0 and 2.0.0 while initializing changelog by calling cli without and having package.json's version set to 1.0.0", async () => {
137 | await makeChanges('file1')
138 | await commit(':sparkles: Add some file')
139 | await bumpVersion('1.0.0')
140 | await commit(':bookmark: Version 1.0.0')
141 | await tag('1.0.0')
142 |
143 | await makeChanges('file2')
144 | await commit(':sparkles: Add another file')
145 | await bumpVersion('2.0.0')
146 | gitmojiChangelog()
147 | await commit(':bookmark: Version 2.0.0')
148 |
149 | expect(getChangelog()).includes(['1.0.0', '2.0.0'])
150 | })
151 | })
152 |
153 | describe('update', () => {
154 | it("should get a 1.0.0 version while initializing changelog by calling cli without arguments two times and having package.json's version set to 1.0.0", async () => {
155 | await bumpVersion('1.0.0')
156 | await makeChanges('file1')
157 | await commit(':sparkles: Add some file')
158 | gitmojiChangelog()
159 | await makeChanges('file2')
160 | await commit(':sparkles: Add a second file')
161 | gitmojiChangelog()
162 |
163 | expect(getChangelog()).includes(['second file'])
164 | })
165 |
166 | it('should use a custom commit mapping to create commit categories', async () => {
167 | makeCustomConfig({
168 | commitMapping: [
169 | { group: 'style', label: 'Style', emojis: ['lipstick'] },
170 | { group: 'changed', label: 'Changed', emojis: [] },
171 | ],
172 | })
173 | await bumpVersion('1.0.0')
174 | await makeChanges('file1')
175 | await commit(':sparkles: Add some file')
176 | await makeChanges('file3')
177 | await commit(':lipstick: Add some file')
178 | gitmojiChangelog()
179 | await makeChanges('file2')
180 | await commit(':sparkles: Add a second file')
181 | await makeChanges('file4')
182 | await commit(':lipstick: Add some file')
183 | gitmojiChangelog()
184 |
185 | expect(getChangelog()).includes(['### Style'])
186 | expect(getChangelog()).not.includes(['### Changed'])
187 | })
188 |
189 | it("should get two versions 1.0.0 and next while updating changelog by calling cli without arguments and having package.json's version set to 1.0.0", async () => {
190 | await makeChanges('file1')
191 | await commit(':sparkles: Add some file')
192 | await bumpVersion('1.0.0')
193 | gitmojiChangelog()
194 | await commit(':bookmark: Version 1.0.0')
195 | await tag('1.0.0')
196 |
197 | await makeChanges('file2')
198 | await commit(':sparkles: Add another file')
199 | gitmojiChangelog()
200 |
201 | expect(getChangelog()).includes(['1.0.0', 'next'])
202 | })
203 | it("should get two versions 1.0.0 and 2.0.0 while updating changelog by calling cli without arguments and having package.json's version set to 2.0.0", async () => {
204 | await makeChanges('file1')
205 | await commit(':sparkles: Add some file')
206 | await bumpVersion('1.0.0')
207 | gitmojiChangelog()
208 | await commit(':bookmark: Version 1.0.0')
209 | await tag('1.0.0')
210 |
211 | await makeChanges('file2')
212 | await commit(':sparkles: Add another file')
213 | await bumpVersion('2.0.0')
214 | gitmojiChangelog()
215 |
216 | expect(getChangelog()).includes(['1.0.0', '2.0.0'])
217 | })
218 |
219 | it('should get two versions 1.0.0 and 2.0.0 while updating changelog after tagging a version 2.0.0', async () => {
220 | await makeChanges('file1')
221 | await commit(':sparkles: Add some file')
222 | await bumpVersion('1.0.0')
223 | gitmojiChangelog()
224 | await commit(':bookmark: Version 1.0.0')
225 | await tag('1.0.0')
226 |
227 | await makeChanges('file2')
228 | await commit(':sparkles: Add another file')
229 | await bumpVersion('2.0.0')
230 | await tag('2.0.0')
231 | gitmojiChangelog()
232 |
233 | expect(getChangelog()).includes(['1.0.0', '2.0.0'])
234 | })
235 |
236 | it('should get two versions 1.0.0-alpha.1 and 1.0.0-alpha.2 while updating changelog after tagging a version 1.0.0-alpha.2', async () => {
237 | await makeChanges('file1')
238 | await commit(':sparkles: Add some file')
239 | await bumpVersion('1.0.0-alpha.1')
240 | gitmojiChangelog()
241 | await commit(':bookmark: Version 1.0.0-alpha.1')
242 | await tag('1.0.0-alpha.1')
243 |
244 | await makeChanges('file2')
245 | await commit(':sparkles: Add another file')
246 | await bumpVersion('1.0.0-alpha.2')
247 | await tag('1.0.0-alpha.2')
248 | gitmojiChangelog()
249 |
250 | expect(getChangelog()).includes(['1.0.0-alpha.1', '1.0.0-alpha.2'])
251 | })
252 |
253 | it("should get two versions 1.0.0 and 2.0.0 while updating changelog by calling cli with 2.0.0 and having package.json's version set to 1.0.0", async () => {
254 | await makeChanges('file1')
255 | await commit(':sparkles: Add some file')
256 | await bumpVersion('1.0.0')
257 | gitmojiChangelog()
258 | await commit(':bookmark: Version 1.0.0')
259 | await tag('1.0.0')
260 |
261 | await makeChanges('file2')
262 | await commit(':sparkles: Add another file')
263 | gitmojiChangelog('2.0.0')
264 |
265 | expect(getChangelog()).includes(['1.0.0', '2.0.0'])
266 | })
267 | it('should get three versions 1.0.0, 2.0.0, 3.0.0 while updating changelog by calling cli without arguments and skipping two tags creation 2.0.0 and 3.0.0', async () => {
268 | await makeChanges('file1')
269 | await commit(':sparkles: Add some file')
270 | await bumpVersion('1.0.0')
271 | gitmojiChangelog()
272 | await commit(':bookmark: Version 1.0.0')
273 | await tag('1.0.0')
274 |
275 | await makeChanges('file2')
276 | await commit(':sparkles: Add another file')
277 | await bumpVersion('2.0.0')
278 | await commit(':bookmark: Version 2.0.0')
279 | await tag('2.0.0')
280 |
281 | await makeChanges('file3')
282 | await commit(':sparkles: Add a third file')
283 | await bumpVersion('3.0.0')
284 | gitmojiChangelog()
285 | await commit(':bookmark: Version 3.0.0')
286 | await tag('3.0.0')
287 |
288 | expect(getChangelog()).includes(['1.0.0', '2.0.0', '3.0.0'])
289 | })
290 |
291 | it('should get three versions 1.0.0, 2.0.0, 3.0.0 and next while updating changelog by calling cli without arguments and skipping two tags creation 2.0.0 and 3.0.0', async () => {
292 | await makeChanges('file1')
293 | await commit(':sparkles: Add some file')
294 | await bumpVersion('1.0.0')
295 | gitmojiChangelog()
296 | await commit(':bookmark: Version 1.0.0')
297 | await tag('1.0.0')
298 |
299 | await makeChanges('file2')
300 | await commit(':sparkles: Add another file')
301 | await bumpVersion('2.0.0')
302 | await commit(':bookmark: Version 2.0.0')
303 | await tag('2.0.0')
304 |
305 | await makeChanges('file3')
306 | await commit(':sparkles: Add a third file')
307 | await bumpVersion('3.0.0')
308 | await commit(':bookmark: Version 3.0.0')
309 | await tag('3.0.0')
310 |
311 | await makeChanges('file4')
312 | await commit(':sparkles: Add a fourth file')
313 | gitmojiChangelog()
314 |
315 | expect(getChangelog()).includes(['1.0.0', '2.0.0', '3.0.0', 'next'])
316 | })
317 |
318 | it('should get two versions 1.0.0, 2.0.0 and next while updating changelog by calling cli without arguments', async () => {
319 | await makeChanges('file1')
320 | await commit(':sparkles: Add some file')
321 | await bumpVersion('1.0.0')
322 | gitmojiChangelog()
323 | await commit(':bookmark: Version 1.0.0')
324 | await tag('1.0.0')
325 |
326 | await makeChanges('file2')
327 | await commit(':sparkles: Add another file')
328 | await bumpVersion('2.0.0')
329 | gitmojiChangelog()
330 | await commit(':bookmark: Version 2.0.0')
331 | await tag('2.0.0')
332 |
333 | await makeChanges('file4')
334 | await commit(':sparkles: Add a fourth file')
335 | gitmojiChangelog()
336 |
337 | expect(getChangelog()).includes(['1.0.0', '2.0.0', 'next'])
338 | })
339 |
340 | it('shouldn\'t generate changelog when gimoji-changelog if there isn\'t any changes', async () => {
341 | await makeChanges('file1')
342 | await commit(':sparkles: Add some file')
343 | await bumpVersion('1.0.0')
344 | gitmojiChangelog()
345 | await commit(':bookmark: Version 1.0.0')
346 | await tag('1.0.0')
347 | const output = gitmojiChangelog()
348 |
349 | expect(getChangelog()).includes(['1.0.0'])
350 | expect(output.toString('utf8')).toDisplayError('No changes found.')
351 | })
352 |
353 | it('should get two versions 1.0.0 and next after two generation while updating changelog by calling cli without arguments', async () => {
354 | await makeChanges('file1')
355 | await commit(':sparkles: Add some file')
356 | await bumpVersion('1.0.0')
357 | gitmojiChangelog()
358 | await commit(':bookmark: Version 1.0.0')
359 | await tag('1.0.0')
360 |
361 | await makeChanges('file2')
362 | await commit(':sparkles: Add another file')
363 | gitmojiChangelog()
364 |
365 | await makeChanges('file4')
366 | await commit(':sparkles: Add a fourth file')
367 | gitmojiChangelog()
368 |
369 | expect(getChangelog()).includes(['1.0.0', 'next'])
370 | })
371 |
372 | it('should get two versions 1.0.0 and 1.1.0 after three generations while updating changelog by calling cli with version 1.1.0', async () => {
373 | await makeChanges('file1')
374 | await commit(':sparkles: Add some file')
375 | await bumpVersion('1.0.0')
376 | gitmojiChangelog()
377 | await commit(':bookmark: Version 1.0.0')
378 | await tag('1.0.0')
379 |
380 | await makeChanges('file2')
381 | await commit(':sparkles: Add another file')
382 | gitmojiChangelog()
383 |
384 | await makeChanges('file4')
385 | await commit(':sparkles: Add a fourth file')
386 | gitmojiChangelog('1.1.0')
387 |
388 | expect(getChangelog()).includes(['1.0.0', '1.1.0'])
389 | })
390 |
391 | it('should get two versions 1.0.0 and next after three generations while updating changelog by calling cli without arguments', async () => {
392 | await makeChanges('file1')
393 | await commit(':sparkles: Add some file')
394 | await bumpVersion('1.0.0')
395 | gitmojiChangelog()
396 | await commit(':bookmark: Version 1.0.0')
397 | await tag('1.0.0')
398 |
399 | await makeChanges('file2')
400 | await commit(':sparkles: Add another file')
401 | gitmojiChangelog('1.1.0')
402 | gitmojiChangelog()
403 |
404 | expect(getChangelog()).not.includes(['1.1.0'])
405 | expect(getChangelog()).includes(['1.0.0', 'next'])
406 | })
407 |
408 | it('should get two versions 1.0.0 and 1.2.0 after three generations while updating changelog by calling cli without arguments', async () => {
409 | await makeChanges('file1')
410 | await commit(':sparkles: Add some file')
411 | await bumpVersion('1.0.0')
412 | gitmojiChangelog()
413 | await commit(':bookmark: Version 1.0.0')
414 | await tag('1.0.0')
415 |
416 | await makeChanges('file2')
417 | await commit(':sparkles: Add another file')
418 | gitmojiChangelog('1.1.0')
419 | gitmojiChangelog('1.2.0')
420 |
421 | expect(getChangelog()).not.includes(['1.1.0'])
422 | expect(getChangelog()).includes(['1.0.0', '1.2.0'])
423 | })
424 |
425 | it('should work by passing release as argument without package.json or configuration file', async () => {
426 | const packageJson = path.join(testDir, 'package.json')
427 | const { version, ...content } = JSON.parse(fs.readFileSync(packageJson).toString('utf8'))
428 | fs.writeFileSync(packageJson, JSON.stringify(content))
429 |
430 | await makeChanges('file1')
431 | await commit(':sparkles: Add some file')
432 | const output = gitmojiChangelog('1.0.0')
433 | console.log(output.toString('utf8'))
434 |
435 | expect(getChangelog()).includes(['1.0.0'])
436 | })
437 |
438 | it('should display an error if requested version isn\'t semver', async () => {
439 | const output = gitmojiChangelog('awesomeversion')
440 |
441 | expect(output.toString('utf8')).toDisplayError()
442 | })
443 | })
444 |
445 | async function makeChanges(fileName) {
446 | fs.writeFileSync(path.join(testDir, fileName), '')
447 | }
448 |
449 | async function makeCustomConfig(config) {
450 | fs.writeFileSync(path.join(testDir, '.gitmoji-changelogrc'), JSON.stringify(config, undefined, 2))
451 | }
452 |
453 | async function commit(message) {
454 | await repo.add('.')
455 | await repo.commit(message)
456 | }
457 |
458 | async function tag(version) {
459 | await repo.addTag(`v${version}`)
460 | }
461 |
462 | function gitmojiChangelog(args = []) {
463 | if (!Array.isArray(args)) {
464 | // eslint-disable-next-line no-param-reassign
465 | args = [args]
466 | }
467 | return childProcess.execFileSync('node', [path.join(__dirname, 'index.js'), ...args], { cwd: testDir })
468 | }
469 |
470 | function getChangelog() {
471 | return fs.readFileSync(path.join(testDir, 'CHANGELOG.md')).toString('utf8')
472 | }
473 |
474 | function bumpVersion(to) {
475 | const packageJson = path.join(testDir, 'package.json')
476 | // eslint-disable-next-line global-require
477 | const content = fs.readFileSync(packageJson).toString('utf8')
478 | const { version } = JSON.parse(content)
479 | const updatedContent = content.replace(version, to)
480 | fs.writeFileSync(packageJson, updatedContent)
481 | }
482 |
483 | /*
484 | * This function is useful to print cli ouput when debugging tests
485 | */
486 | // eslint-disable-next-line no-unused-vars
487 | function logOutput(ouput) {
488 | console.log(ouput.toString('utf8'))
489 | }
490 | })
491 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/cli.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const { get } = require('lodash')
3 | const { set } = require('immutadot')
4 | const libnpm = require('libnpm')
5 | const semver = require('semver')
6 | const semverCompare = require('semver-compare')
7 | const rc = require('rc')
8 |
9 | const { generateChangelog, logger, FunctionalError } = require('@gitmoji-changelog/core')
10 | const { buildMarkdownFile, getLatestVersion } = require('@gitmoji-changelog/markdown')
11 |
12 | const issueReporter = require('issue-reporter')
13 |
14 | const { executeInteractiveMode } = require('./interactiveMode')
15 | const getRepositoryInfo = require('./repository')
16 | const packageJson = require('../package.json')
17 |
18 | async function getGitmojiChangelogLatestVersion() {
19 | const watchdog = new Promise(resolve => {
20 | setTimeout(resolve, 500, { version: packageJson.version })
21 | })
22 | const request = libnpm.manifest('gitmoji-changelog@latest')
23 |
24 | const { version } = await Promise.race([watchdog, request])
25 |
26 | return version
27 | }
28 |
29 | async function main(options = {}) {
30 | logger.start(`gitmoji-changelog v${packageJson.version}`)
31 | logger.info(`${options.mode} ${options.output}`)
32 |
33 | const customConfiguration = rc('gitmoji-changelog')
34 | if (customConfiguration.configs) {
35 | logger.info('Custom configuration found')
36 | } else {
37 | logger.info('No custom configuration found')
38 | }
39 |
40 | try {
41 | const latestVersion = await getGitmojiChangelogLatestVersion()
42 | if (semverCompare(latestVersion, packageJson.version) > 0) {
43 | logger.warn(`You got an outdated version of gitmoji-changelog, please update! (yours: ${packageJson.version}, latest: ${latestVersion})`)
44 | logger.warn('Just do the following npm command to update it:')
45 | logger.warn('\t> npm install -g gitmoji-changelog@latest')
46 | }
47 | } catch (e) { /* ignore error */ }
48 |
49 | let projectInfo
50 | try {
51 | logger.info(`use preset ${options.preset}`)
52 |
53 | // eslint-disable-next-line global-require
54 | const loadProjectInfo = require(`./presets/${options.preset}.js`)
55 | projectInfo = await loadProjectInfo()
56 |
57 | if (!projectInfo) {
58 | throw Error(`Cannot retrieve configuration for preset ${options.preset}.`)
59 | }
60 |
61 | if (!projectInfo.version && options.release === 'from-package') {
62 | throw Error('Cannot retrieve the version from your configuration. Check it or you can do "gitmoji-changelog ".')
63 | }
64 | } catch (e) {
65 | logger.error(e)
66 | // Force quit if the requested preset doesn't exist
67 | return process.exit(0)
68 | }
69 |
70 | if (options.groupSimilarCommits) {
71 | logger.warn('⚗️ You are using a beta feature - may not working as expected')
72 | logger.warn('Feel free to open issues or PR into gitmoji-changelog')
73 | logger.warn('\t> https://github.com/frinyvonnick/gitmoji-changelog')
74 | }
75 |
76 | try {
77 | switch (options.format) {
78 | case 'json': {
79 | const changelog = await getChangelog(options, projectInfo)
80 |
81 | logMetaData(changelog)
82 |
83 | fs.writeFileSync(options.output, JSON.stringify(changelog))
84 | break
85 | }
86 | default: {
87 | const lastVersion = await getLatestVersion(options.output)
88 | const newOptions = set(options, 'meta.lastVersion', lastVersion)
89 |
90 | // Handle the case where changelog file exist but there isn't a previous version
91 | if (options.mode === 'update' && !lastVersion) {
92 | newOptions.mode = 'init'
93 |
94 | fs.unlinkSync(options.output)
95 | }
96 |
97 | const changelog = await getChangelog(newOptions, projectInfo)
98 |
99 | logMetaData(changelog)
100 | await buildMarkdownFile(changelog, newOptions)
101 | }
102 | }
103 | logger.success(`changelog updated into ${options.output}`)
104 | } catch (e) {
105 | if (e.name !== 'FunctionalError') {
106 | const repository = await getRepositoryInfo()
107 | await issueReporter({
108 | error: e,
109 | user: 'frinyvonnick',
110 | repo: 'gitmoji-changelog',
111 | sections: [
112 | {
113 | title: 'CLI options',
114 | content: options,
115 | },
116 | {
117 | title: 'Project info',
118 | content: projectInfo,
119 | },
120 | {
121 | title: 'Repository info',
122 | content: repository,
123 | },
124 | ],
125 | })
126 | } else {
127 | logger.error(e)
128 | }
129 | }
130 |
131 | // force quit (if the latest version request is pending, we don't wait for it)
132 | return process.exit(0)
133 | }
134 |
135 | async function getChangelog(options, projectInfo) {
136 | const repository = await getRepositoryInfo()
137 |
138 | const release = options.release === 'from-package' ? projectInfo.version : options.release
139 |
140 | if (!semver.valid(release)) {
141 | throw new FunctionalError(`${release} is not a valid semver version.`)
142 | }
143 |
144 | const enhancedOptions = {
145 | ...options,
146 | release,
147 | }
148 |
149 | let changelog
150 | if (options.mode === 'init') {
151 | changelog = await generateChangelog('', release, enhancedOptions)
152 | } else {
153 | const { meta } = options
154 | const lastVersion = get(meta, 'lastVersion')
155 |
156 | changelog = await generateChangelog(lastVersion, release, enhancedOptions)
157 | }
158 |
159 | if (options.interactive) {
160 | changelog = await executeInteractiveMode(changelog)
161 | }
162 |
163 | changelog.meta.project = projectInfo
164 | changelog.meta.repository = repository
165 |
166 | return changelog
167 | }
168 |
169 | function logMetaData(changelog) {
170 | if (changelog.meta.project) {
171 | const { name, version } = changelog.meta.project
172 | logger.info(`${name} v${version}`)
173 | }
174 | if (changelog.meta.repository) {
175 | logger.info(changelog.meta.repository.url)
176 | }
177 | }
178 |
179 | module.exports = { main }
180 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/cli.spec.js:
--------------------------------------------------------------------------------
1 | const { generateChangelog, logger } = require('@gitmoji-changelog/core')
2 | const { manifest } = require('libnpm')
3 | const { main } = require('./cli')
4 | const issueReporter = require('issue-reporter')
5 |
6 | describe('cli', () => {
7 | const realExitFunction = process.exit
8 | const options = { preset: 'node' }
9 | beforeEach(() => {
10 | process.exit = jest.fn(() => {})
11 | jest.clearAllMocks()
12 | })
13 | afterEach(() => {
14 | process.exit = realExitFunction
15 | })
16 |
17 | it('should throw an error if changelog generation fails', async () => {
18 | generateChangelog.mockRejectedValue(new Error())
19 |
20 | await main(options)
21 |
22 | expect(issueReporter).toHaveBeenCalled()
23 | })
24 |
25 | it('should call process.exit explicitly so promises are not waited for', async () => {
26 | await main(options)
27 |
28 | expect(process.exit).toHaveBeenCalledTimes(1)
29 | })
30 |
31 | it('should call process.exit if preset does not exist', async () => {
32 | await main()
33 |
34 | expect(process.exit).toHaveBeenCalledTimes(1)
35 | })
36 |
37 | describe('version control', () => {
38 | const findOutdatedMessage = () => logger.warn.mock.calls.find(([message]) => message.includes('outdated'))
39 |
40 | it('should print a warning about a new version', async () => {
41 | manifest.mockReturnValueOnce(Promise.resolve({ version: '2.0.0' }))
42 | await main(options)
43 |
44 | expect(findOutdatedMessage()).toBeTruthy()
45 | })
46 |
47 | it('should NOT print a warning about a new version', async () => {
48 | // older version in npm registry
49 | manifest.mockReturnValueOnce(Promise.resolve({ version: '0.5.0' }))
50 | await main(options)
51 |
52 | // same version in npm registry
53 | manifest.mockReturnValueOnce(Promise.resolve({ version: '1.0.0' }))
54 | await main(options)
55 |
56 | expect(manifest).toHaveBeenCalledTimes(2)
57 | expect(findOutdatedMessage()).toBeFalsy()
58 | })
59 |
60 | it('should NOT print a warning about a new version when request took to much time', async () => {
61 | manifest.mockImplementationOnce(() => new Promise((resolve) => { setTimeout(resolve, 1000, { version: '2.0.0' }) }))
62 | await main(options)
63 |
64 | expect(findOutdatedMessage()).toBeFalsy()
65 | })
66 |
67 | it('should NOT print a warning about a new version when request is on error', async () => {
68 | manifest.mockReturnValueOnce(Promise.reject(new Error('faked error (manifest)')))
69 | await main(options)
70 |
71 | expect(findOutdatedMessage()).toBeFalsy()
72 | })
73 | })
74 | })
75 |
76 | jest.mock('@gitmoji-changelog/core', () => ({
77 | generateChangelog: jest.fn(),
78 | logger: {
79 | start: jest.fn(),
80 | error: jest.fn(),
81 | success: jest.fn(),
82 | info: jest.fn(),
83 | warn: jest.fn(),
84 | },
85 | }))
86 |
87 | jest.mock('../package.json', () => ({
88 | version: '1.0.0',
89 | }))
90 |
91 | jest.mock('libnpm', () => ({
92 | manifest: jest.fn(),
93 | }))
94 |
95 | jest.mock('issue-reporter')
96 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/index.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | const fs = require('fs')
3 | const yargs = require('yargs')
4 | const { noop } = require('lodash')
5 |
6 | const { homepage } = require('../package.json')
7 | const { main } = require('./cli')
8 |
9 | function getOutputFile({ output, format }) {
10 | if (output) {
11 | return output
12 | }
13 | if (format === 'json') {
14 | return './CHANGELOG.json'
15 | }
16 | return './CHANGELOG.md'
17 | }
18 |
19 | const execute = mode => argv => main({
20 | ...argv,
21 | mode,
22 | output: getOutputFile(argv),
23 | })
24 |
25 | yargs
26 | .usage('Usage: $0 [options]')
27 |
28 | .command('$0 [release]', 'Generate changelog', (command) => {
29 | command.positional('release', {
30 | desc: 'Next version (from-package, next)',
31 | default: 'from-package',
32 | })
33 | }, (argv) => {
34 | const output = getOutputFile(argv)
35 | const existsOuput = fs.existsSync(output)
36 | const mode = existsOuput ? 'update' : 'init'
37 | execute(mode)(argv)
38 | })
39 |
40 | .command('init', 'Initialize changelog from tags', noop, (argv) => execute('init')({ ...argv, release: 'from-package' }))
41 |
42 | .command('update [release]', 'Update changelog with a new version', (command) => {
43 | command.positional('release', {
44 | desc: 'Next version (from-package, next)',
45 | default: 'from-package',
46 | })
47 | }, execute('update'))
48 |
49 | .option('format', { default: 'markdown', desc: 'changelog format (markdown, json)' })
50 | .option('preset', { default: 'node', desc: 'define preset mode', choices: ['node', 'generic', 'maven', 'cargo', 'helm', 'python'] })
51 | .option('output', { desc: 'output changelog file' })
52 | .option('group-similar-commits', { desc: '[⚗️ - beta] try to group similar commits', default: false })
53 | .option('author', { default: false, desc: 'add the author in changelog lines' })
54 | .option('interactive', { default: false, desc: 'select commits manually', alias: 'i' })
55 |
56 | .help('help')
57 | .epilog(`For more information visit: ${homepage}`)
58 | .parse()
59 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/interactiveMode.js:
--------------------------------------------------------------------------------
1 | const { get, isEmpty, cloneDeep } = require('lodash')
2 | const inquirer = require('inquirer')
3 | const { set } = require('immutadot')
4 |
5 | const interactiveMode = {
6 | buildFormattedChoices,
7 | getFilteredChangelog,
8 | executeInteractiveMode,
9 | }
10 |
11 | function buildFormattedChoices(changelog) {
12 | const formattedChoices = []
13 |
14 | changelog.changes
15 | .forEach(change => {
16 | change.groups
17 | .forEach(group => {
18 | formattedChoices.push(new inquirer.Separator(`${group.label} (version ${change.version})`))
19 |
20 | group.commits.forEach(commit => {
21 | formattedChoices.push({
22 | name: `${get(commit, 'emoji', '')} ${commit.message}`,
23 | value: commit.hash,
24 | checked: true,
25 | })
26 | })
27 | })
28 | })
29 |
30 | return formattedChoices
31 | }
32 |
33 | function getFilteredChangelog(changelog, selectedCommitsHashes) {
34 | const changes = changelog.changes.map(change => {
35 | const groups = change.groups.map(group => {
36 | const filteredCommits = group.commits.filter(commit => {
37 | return selectedCommitsHashes.find(hash => commit.hash === hash)
38 | })
39 |
40 | return set(group, 'commits', filteredCommits)
41 | }).filter(group => !isEmpty(group.commits))
42 |
43 | return set(change, 'groups', groups)
44 | }).filter(change => !isEmpty(change.groups))
45 |
46 | return {
47 | ...changelog,
48 | changes,
49 | }
50 | }
51 |
52 | async function executeInteractiveMode(initialChangelog) {
53 | const initialChangelogCopy = cloneDeep(initialChangelog)
54 | const formattedChoices = interactiveMode.buildFormattedChoices(initialChangelogCopy)
55 | const prompt = inquirer.createPromptModule()
56 | const question = {
57 | type: 'checkbox',
58 | name: 'selectedCommitsHashes',
59 | message: 'Select commits',
60 | choices: formattedChoices,
61 | pageSize: 10,
62 | }
63 |
64 | const { selectedCommitsHashes } = await prompt(question)
65 |
66 | return interactiveMode.getFilteredChangelog(initialChangelogCopy, selectedCommitsHashes)
67 | }
68 |
69 | module.exports = interactiveMode
70 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/interactiveMode.spec.js:
--------------------------------------------------------------------------------
1 | const inquirer = require('inquirer')
2 | const interactiveMode = require('./interactiveMode')
3 |
4 | describe('interactiveMode', () => {
5 | const commitAddProcess = {
6 | hash: '00c90b7844c3d030e967721eafea1b436ee51a6b',
7 | author: 'Franck',
8 | date: '2019-05-19T10:57:22+02:00',
9 | subject: ':sparkles: Add interactive process',
10 | emojiCode: 'sparkles',
11 | emoji: '✨',
12 | message: 'Add interactive process',
13 | group: 'added',
14 | siblings: [],
15 | body: '',
16 | }
17 |
18 | const commitAddOption = {
19 | hash: '3092ffd56e35fff7e35e8a9fcb7fff53005eac8a',
20 | author: 'Franck',
21 | date: '2019-05-18T18:39:44+02:00',
22 | subject: ':sparkles: Add interactive option',
23 | emojiCode: 'sparkles',
24 | emoji: '✨',
25 | message: 'Add interactive option',
26 | group: 'added',
27 | siblings: [],
28 | body: '',
29 | }
30 |
31 | const commitUpgradeDeps = {
32 | hash: 'b77199f96c8570b827dfcb11907d6f4edac98823',
33 | author: 's n',
34 | date: '2019-04-23T15:55:19+02:00',
35 | subject: ':arrow_up: Update handlebar to 4.0.14 (#78)',
36 | emojiCode: 'arrow_up',
37 | emoji: '⬆️',
38 | message: 'Update handlebar to 4.0.14 (#78)',
39 | group: 'changed',
40 | siblings: [],
41 | body: '',
42 | }
43 |
44 | const commitAddAuthor = {
45 | hash: '979da30f5e52385b99bd4a58e1a946793bd1196d',
46 | author: 'Benjamin Petetot',
47 | date: '2018-10-30T09:33:52+01:00',
48 | subject: ':sparkles: Add the author in changelog lines (#56)',
49 | emojiCode: 'sparkles',
50 | emoji: '✨',
51 | message: 'Add the author in changelog lines (#56)',
52 | group: 'added',
53 | siblings: [],
54 | body: '',
55 | }
56 |
57 | const groupAddedVersionNext = {
58 | group: 'added',
59 | label: 'Added',
60 | commits: [
61 | commitAddProcess,
62 | commitAddOption,
63 | ],
64 | }
65 |
66 | const groupChangedVersionNext = {
67 | group: 'changed',
68 | label: 'Changed',
69 | commits: [
70 | commitUpgradeDeps,
71 | ],
72 | }
73 |
74 | const groupAddedVersionOne = {
75 | group: 'added',
76 | label: 'Added',
77 | commits: [
78 | commitAddAuthor,
79 | ],
80 | }
81 |
82 | const versionNext = {
83 | version: 'next',
84 | groups: [
85 | groupAddedVersionNext,
86 | groupChangedVersionNext,
87 | ],
88 | }
89 |
90 | const versionOne = {
91 | version: '1.1.0',
92 | date: '2018-11-15',
93 | groups: [
94 | groupAddedVersionOne,
95 | ],
96 | }
97 |
98 | const initialChangelog = {
99 | changes: [
100 | versionNext,
101 | versionOne,
102 | ],
103 | }
104 |
105 | beforeEach(() => {
106 | jest.clearAllMocks()
107 | })
108 |
109 | describe('buildFormattedChoices', () => {
110 | it('should return a list of formatted choices from the given changelog', () => {
111 | const result = interactiveMode.buildFormattedChoices(initialChangelog)
112 |
113 | const expectedResult = [
114 | new inquirer.Separator(`${groupAddedVersionNext.label} (version ${versionNext.version})`),
115 | {
116 | name: `${commitAddProcess.emoji} ${commitAddProcess.message}`,
117 | value: commitAddProcess.hash,
118 | checked: true,
119 | },
120 | {
121 | name: `${commitAddOption.emoji} ${commitAddOption.message}`,
122 | value: commitAddOption.hash,
123 | checked: true,
124 | },
125 | new inquirer.Separator(`${groupChangedVersionNext.label} (version ${versionNext.version})`),
126 | {
127 | name: `${commitUpgradeDeps.emoji} ${commitUpgradeDeps.message}`,
128 | value: commitUpgradeDeps.hash,
129 | checked: true,
130 | },
131 | new inquirer.Separator(`${groupAddedVersionOne.label} (version ${versionOne.version})`),
132 | {
133 | name: `${commitAddAuthor.emoji} ${commitAddAuthor.message}`,
134 | value: commitAddAuthor.hash,
135 | checked: true,
136 | },
137 | ]
138 |
139 | expect(result).toEqual(expectedResult)
140 | })
141 | })
142 |
143 | describe('getFilteredChangelog', () => {
144 | it('should return a new filtered changelog from the given inital changelog and selected commits', () => {
145 | const selectedCommitsHashes = [commitAddProcess.hash, commitAddAuthor.hash]
146 |
147 | const result = interactiveMode.getFilteredChangelog(initialChangelog, selectedCommitsHashes)
148 |
149 | const expectedResult = {
150 | changes: [
151 | {
152 | version: 'next',
153 | groups: [{
154 | group: 'added',
155 | label: 'Added',
156 | commits: [
157 | commitAddProcess,
158 | ],
159 | }],
160 | },
161 | {
162 | version: '1.1.0',
163 | date: '2018-11-15',
164 | groups: [
165 | groupAddedVersionOne,
166 | ],
167 | },
168 | ],
169 | }
170 |
171 | expect(result).toEqual(expectedResult)
172 | })
173 | })
174 |
175 | describe('executeInteractiveMode', () => {
176 | it('should call buildFormattedChoices, createPromptModule and getFilteredChangelog with correct parameters', async () => {
177 | const formattedChoices = [{
178 | name: `${commitAddProcess.emoji} ${commitAddProcess.message}`,
179 | value: commitAddProcess.hash,
180 | checked: true,
181 | }]
182 | const selectedCommitsHashes = [commitAddProcess.hash, commitAddOption.hash]
183 | const prompt = jest.fn(() => Promise.resolve({ selectedCommitsHashes }))
184 |
185 | interactiveMode.buildFormattedChoices = jest.fn(() => formattedChoices)
186 | interactiveMode.getFilteredChangelog = jest.fn()
187 | inquirer.createPromptModule = jest.fn()
188 | inquirer.createPromptModule.mockReturnValueOnce(prompt)
189 |
190 | await interactiveMode.executeInteractiveMode(initialChangelog)
191 |
192 | expect(interactiveMode.buildFormattedChoices).toHaveBeenNthCalledWith(1, initialChangelog)
193 | expect(inquirer.createPromptModule).toHaveBeenCalledTimes(1)
194 | expect(prompt).toHaveBeenNthCalledWith(1, {
195 | type: 'checkbox',
196 | name: 'selectedCommitsHashes',
197 | message: 'Select commits',
198 | choices: formattedChoices,
199 | pageSize: 10,
200 | })
201 | expect(interactiveMode.getFilteredChangelog)
202 | .toHaveBeenNthCalledWith(1, initialChangelog, selectedCommitsHashes)
203 | })
204 | })
205 | })
206 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/cargo.js:
--------------------------------------------------------------------------------
1 | const toml = require('toml')
2 | const fs = require('fs')
3 |
4 | module.exports = async () => {
5 | try {
6 | const cargoPromise = new Promise((resolve, reject) => {
7 | try {
8 | resolve(toml.parse(fs.readFileSync('Cargo.toml', 'utf-8')))
9 | } catch (err) {
10 | reject(err)
11 | }
12 | })
13 |
14 | const {
15 | package: {
16 | name,
17 | version,
18 | description,
19 | },
20 | } = await cargoPromise
21 | return {
22 | name: name,
23 | version: version,
24 | description: description,
25 | }
26 | } catch (e) {
27 | return null
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/generic.js:
--------------------------------------------------------------------------------
1 | const rc = require('rc')
2 |
3 | module.exports = () => {
4 | try {
5 | const customConfiguration = rc('gitmoji-changelog')
6 | if (!customConfiguration.configs) throw Error('Configuration not found')
7 |
8 | return customConfiguration.project
9 | } catch (e) {
10 | return null
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/generic.spec.js:
--------------------------------------------------------------------------------
1 | const rc = require('rc')
2 |
3 | const loadProjectInfo = require('./generic.js')
4 |
5 | describe('getPackageInfo', () => {
6 | it('should extract github repo info from configuration file', async () => {
7 | rc.mockImplementationOnce(() => ({
8 | project: {
9 | name: 'gitmoji-changelog',
10 | version: '0.0.1',
11 | description: 'Gitmoji Changelog CLI',
12 | },
13 | configs: [],
14 | }))
15 |
16 | const result = await loadProjectInfo()
17 |
18 | expect(result).toEqual({
19 | name: 'gitmoji-changelog',
20 | version: '0.0.1',
21 | description: 'Gitmoji Changelog CLI',
22 | })
23 | })
24 | })
25 |
26 | jest.mock('rc')
27 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/helm.js:
--------------------------------------------------------------------------------
1 | const yaml = require('yaml')
2 | const fs = require('fs')
3 |
4 | module.exports = async () => {
5 | try {
6 | const chartYamlPromise = new Promise((resolve, reject) => {
7 | try {
8 | resolve(yaml.parse(fs.readFileSync('Chart.yaml', 'utf-8')))
9 | } catch (err) {
10 | reject(err)
11 | }
12 | })
13 |
14 | const {
15 | name,
16 | version,
17 | description,
18 | } = await chartYamlPromise
19 | return {
20 | name: name,
21 | version: version,
22 | description: description,
23 | }
24 | } catch (e) {
25 | return null
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/helm.spec.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | const loadProjectInfo = require('./helm.js')
4 |
5 | describe('getPackageInfo', () => {
6 | it('should extract info from Chart.yaml', async () => {
7 | fs.readFileSync.mockReturnValue(`
8 | name: chart-name
9 | version: 0.1.1
10 | description: Description of the chart
11 | `)
12 |
13 | const result = await loadProjectInfo()
14 |
15 | expect(result).toEqual({
16 | name: 'chart-name',
17 | version: '0.1.1',
18 | description: 'Description of the chart',
19 | })
20 | })
21 | })
22 |
23 | jest.mock('fs')
24 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/maven.js:
--------------------------------------------------------------------------------
1 | const pomParser = require('pom-parser')
2 |
3 | module.exports = async () => {
4 | try {
5 | const opts = {
6 | filePath: 'pom.xml',
7 | }
8 | const pomPromise = new Promise((resolve, reject) => {
9 | pomParser.parse(opts, (err, pomResponse) => {
10 | if (err) {
11 | reject(err)
12 | return
13 | }
14 |
15 | resolve(pomResponse.pomObject)
16 | })
17 | })
18 |
19 | const {
20 | project: {
21 | groupid, artifactid, version, description,
22 | },
23 | } = await pomPromise
24 | return {
25 | name: `${groupid}.${artifactid}`,
26 | version: version,
27 | description: description,
28 | }
29 | } catch (e) {
30 | return null
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/node.js:
--------------------------------------------------------------------------------
1 | const readPkgUp = require('read-pkg-up')
2 |
3 | module.exports = async () => {
4 | try {
5 | const packageInfo = await readPkgUp()
6 |
7 | if (!packageInfo.packageJson) throw Error('Empty package.json')
8 |
9 | return {
10 | name: packageInfo.packageJson.name,
11 | version: packageInfo.packageJson.version,
12 | description: packageInfo.packageJson.description,
13 | }
14 | } catch (e) {
15 | return null
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/node.spec.js:
--------------------------------------------------------------------------------
1 | const readPkgUp = require('read-pkg-up')
2 |
3 | const loadProjectInfo = require('./node.js')
4 |
5 | describe('getPackageInfo', () => {
6 | it('should extract github repo info from package.json', async () => {
7 | readPkgUp.mockImplementationOnce(() => Promise.resolve({
8 | packageJson: {
9 | name: 'gitmoji-changelog',
10 | version: '0.0.1',
11 | description: 'Gitmoji Changelog CLI',
12 | },
13 | }))
14 |
15 | const result = await loadProjectInfo()
16 |
17 | expect(result).toEqual({
18 | name: 'gitmoji-changelog',
19 | version: '0.0.1',
20 | description: 'Gitmoji Changelog CLI',
21 | })
22 | })
23 | })
24 |
25 | jest.mock('read-pkg-up')
26 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/python.js:
--------------------------------------------------------------------------------
1 | const toml = require('toml')
2 | const fs = require('fs')
3 |
4 | module.exports = async () => {
5 | try {
6 | const pyprojectPromise = new Promise((resolve, reject) => {
7 | try {
8 | resolve(toml.parse(fs.readFileSync('pyproject.toml', 'utf-8')))
9 | } catch (err) {
10 | reject(err)
11 | }
12 | })
13 |
14 | const projectFile = await pyprojectPromise
15 | const name = recursiveKeySearch('name', projectFile)[0]
16 | const version = recursiveKeySearch('version', projectFile)[0]
17 | let description = recursiveKeySearch('description', projectFile)[0]
18 |
19 | if (!name) {
20 | throw new Error('Could not find name metadata in pyproject.toml')
21 | }
22 | if (!version) {
23 | throw new Error('Could not find version metadata in pyproject.toml')
24 | }
25 | if (!description) {
26 | description = ''
27 | }
28 |
29 | return {
30 | name,
31 | version,
32 | description,
33 | }
34 | } catch (e) {
35 | return null
36 | }
37 | }
38 |
39 |
40 | function recursiveKeySearch(key, data) {
41 | // https://codereview.stackexchange.com/a/143914
42 | if (data === null) {
43 | return []
44 | }
45 |
46 | if (data !== Object(data)) {
47 | return []
48 | }
49 |
50 | let results = []
51 |
52 | if (data.constructor === Array) {
53 | for (let i = 0, len = data.length; i < len; i += 1) {
54 | results = results.concat(recursiveKeySearch(key, data[i]))
55 | }
56 | return results
57 | }
58 |
59 | for (let i = 0; i < Object.keys(data).length; i += 1) {
60 | const dataKey = Object.keys(data)[i]
61 | if (key === dataKey) {
62 | results.push(data[key])
63 | }
64 | results = results.concat(recursiveKeySearch(key, data[dataKey]))
65 | }
66 |
67 | return results
68 | }
69 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/presets/python.spec.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | const loadProjectInfo = require('./python.js')
4 |
5 | describe('getPackageInfo', () => {
6 | it('should extract metadata from a pyproject.toml made by poetry', async () =>{
7 | // Note the TOML section is distinct for poetry
8 | fs.readFileSync.mockReturnValue(`
9 | [tool.poetry]
10 | name = "poetry-package-name"
11 | version = "0.1.0"
12 | description = "Description of the poetry package"
13 | `)
14 |
15 | const result = await loadProjectInfo()
16 |
17 | expect(result).toEqual({
18 | name: 'poetry-package-name',
19 | version: '0.1.0',
20 | description: 'Description of the poetry package',
21 | })
22 | })
23 |
24 | it('should extract metadata from the PEP621 example pyproject.toml', async () =>{
25 | // [project] is the usual TOML section for the metadata
26 | fs.readFileSync.mockReturnValue(`
27 | [project]
28 | name = "spam"
29 | version = "2020.0.0"
30 | description = "Lovely Spam! Wonderful Spam!"
31 | readme = "README.rst"
32 | requires-python = ">=3.8"
33 | license = {file = "LICENSE.txt"}
34 | keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
35 | authors = [
36 | {email = "hi@pradyunsg.me"},
37 | {name = "Tzu-Ping Chung"}
38 | ]
39 | maintainers = [
40 | {name = "Brett Cannon", email = "brett@python.org"}
41 | ]
42 | classifiers = [
43 | "Development Status :: 4 - Beta",
44 | "Programming Language :: Python"
45 | ]
46 |
47 | dependencies = [
48 | "httpx",
49 | "gidgethub[httpx]>4.0.0",
50 | "django>2.1; os_name != 'nt'",
51 | "django>2.0; os_name == 'nt'"
52 | ]
53 |
54 | [project.optional-dependencies]
55 | test = [
56 | "pytest < 5.0.0",
57 | "pytest-cov[all]"
58 | ]
59 |
60 | [project.urls]
61 | homepage = "example.com"
62 | documentation = "readthedocs.org"
63 | repository = "github.com"
64 | changelog = "github.com/me/spam/blob/master/CHANGELOG.md"
65 |
66 | [project.scripts]
67 | spam-cli = "spam:main_cli"
68 |
69 | [project.gui-scripts]
70 | spam-gui = "spam:main_gui"
71 |
72 | [project.entry-points."spam.magical"]
73 | tomatoes = "spam:main_tomatoes"
74 | `)
75 |
76 | const result = await loadProjectInfo()
77 |
78 | expect(result).toEqual({
79 | name: 'spam',
80 | version: '2020.0.0',
81 | description: 'Lovely Spam! Wonderful Spam!',
82 | })
83 | })
84 |
85 | it('should extract metadata despite a missing description', async () =>{
86 | // The description metadata is optional.
87 | fs.readFileSync.mockReturnValue(`
88 | [project]
89 | name = "no-description"
90 | version = "0.0.1"
91 | readme = "README.rst"
92 | `)
93 |
94 | const result = await loadProjectInfo()
95 |
96 | expect(result).toEqual({
97 | name: 'no-description',
98 | version: '0.0.1',
99 | description: '',
100 | })
101 | })
102 |
103 | it('should use the first metadata value found from the top', async () =>{
104 | // Only the first occurance of the expected key names are taken.
105 | fs.readFileSync.mockReturnValue(`
106 | [other.section]
107 | somebody = "once told me the"
108 | world = "is gonna roll me"
109 |
110 | [project]
111 | name = "project-1"
112 | version = "0.0.1"
113 | description = "Project 1 Description"
114 |
115 | [tool.poetry]
116 | name = "project-2"
117 | version = "0.0.2"
118 | description = "Project 2 Description"
119 |
120 | [tool.something.else]
121 | name = "project-3"
122 | version = "0.0.3"
123 | description = "Project 3 Description"
124 | `)
125 |
126 | const result = await loadProjectInfo()
127 |
128 | expect(result).toEqual({
129 | name: 'project-1',
130 | version: '0.0.1',
131 | description: 'Project 1 Description',
132 | })
133 | })
134 | })
135 |
136 |
137 | jest.mock('fs')
138 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/repository.js:
--------------------------------------------------------------------------------
1 | const gitRemoteOriginUrl = require('git-remote-origin-url')
2 | const hostedGitInfo = require('hosted-git-info')
3 |
4 | const { isEmpty } = require('lodash')
5 |
6 | async function getRepositoryInfo() {
7 | try {
8 | const url = await gitRemoteOriginUrl()
9 | const repo = hostedGitInfo.fromUrl(url)
10 |
11 | if (isEmpty(repo)) return null
12 |
13 | return {
14 | type: repo.type,
15 | domain: repo.domain,
16 | user: repo.user,
17 | project: repo.project,
18 | url: repo.browse(),
19 | bugsUrl: repo.bugs(),
20 | }
21 | } catch (e) {
22 | return null
23 | }
24 | }
25 |
26 | module.exports = getRepositoryInfo
27 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-cli/src/repository.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const gitRemoteOriginUrl = require('git-remote-origin-url')
3 |
4 | const getRepositoryInfo = require('./repository')
5 |
6 | describe('getRepositoryInfo', () => {
7 | it('should return null if no git info found', async () => {
8 | gitRemoteOriginUrl.mockImplementationOnce(() => Promise.resolve(null))
9 |
10 | const result = await getRepositoryInfo()
11 |
12 | expect(result).toBeNull()
13 | })
14 |
15 | it('should extract GitHub info', async () => {
16 | gitRemoteOriginUrl.mockImplementationOnce(() => Promise.resolve('git+https://github.com/frinyvonnick/gitmoji-changelog.git'))
17 |
18 | const result = await getRepositoryInfo()
19 |
20 | expect(result).toEqual({
21 | type: 'github',
22 | domain: 'github.com',
23 | user: 'frinyvonnick',
24 | project: 'gitmoji-changelog',
25 | url: 'https://github.com/frinyvonnick/gitmoji-changelog',
26 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
27 | })
28 | })
29 |
30 | it('should extract gitlab repo info', async () => {
31 | gitRemoteOriginUrl.mockImplementationOnce(() => Promise.resolve('git+https://gitlab.com/gitlab-user/gitlab-project.git'))
32 | const result = await getRepositoryInfo()
33 |
34 | expect(result).toEqual({
35 | type: 'gitlab',
36 | domain: 'gitlab.com',
37 | user: 'gitlab-user',
38 | project: 'gitlab-project',
39 | url: 'https://gitlab.com/gitlab-user/gitlab-project',
40 | bugsUrl: 'https://gitlab.com/gitlab-user/gitlab-project/issues',
41 | })
42 | })
43 |
44 | it('should extract bitbucket repo info', async () => {
45 | gitRemoteOriginUrl.mockImplementationOnce(() => Promise.resolve('https://username@bitbucket.org/bitbucket-account/bitbucket-project.git'))
46 | const result = await getRepositoryInfo()
47 |
48 | expect(result).toEqual({
49 | type: 'bitbucket',
50 | domain: 'bitbucket.org',
51 | user: 'bitbucket-account',
52 | project: 'bitbucket-project',
53 | url: 'https://bitbucket.org/bitbucket-account/bitbucket-project',
54 | bugsUrl: undefined,
55 | })
56 | })
57 | })
58 |
59 | jest.mock('git-remote-origin-url')
60 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/.npmignore:
--------------------------------------------------------------------------------
1 | *.spec.js
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitmoji-changelog/core",
3 | "version": "2.3.0",
4 | "description": "Core tool that transform raw commits into a nice json structure",
5 | "main": "src/index.js",
6 | "engines": {
7 | "node": ">=10"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/frinyvonnick/gitmoji-changelog.git"
12 | },
13 | "author": "Yvonnick FRIN (https://github.com/frinyvonnick)",
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/frinyvonnick/gitmoji-changelog/issues"
17 | },
18 | "homepage": "https://github.com/frinyvonnick/gitmoji-changelog#readme",
19 | "dependencies": {
20 | "concat-stream": "^1.6.2",
21 | "fast-levenshtein": "^2.0.6",
22 | "git-raw-commits": "^2.0.0",
23 | "git-remote-origin-url": "^2.0.0",
24 | "git-semver-tags": "^4.1.1",
25 | "lodash": "^4.17.11",
26 | "node-emoji": "^1.8.1",
27 | "normalize-package-data": "^2.4.0",
28 | "read-pkg-up": "^7.0.1",
29 | "semver": "^5.6.0",
30 | "semver-compare": "^1.0.0",
31 | "signale": "^1.3.0",
32 | "split-lines": "^2.0.0",
33 | "through2": "^2.0.3"
34 | },
35 | "publishConfig": {
36 | "access": "public",
37 | "registry": "https://registry.npmjs.org/"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/errors.js:
--------------------------------------------------------------------------------
1 | class FunctionalError extends Error {
2 | constructor(message) {
3 | super(message)
4 | this.name = 'FunctionalError'
5 | }
6 | }
7 |
8 | module.exports = {
9 | FunctionalError,
10 | }
11 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/fromGitFile.js:
--------------------------------------------------------------------------------
1 | const gitRawCommits = require('git-raw-commits')
2 | const splitLines = require('split-lines')
3 | const { promisify } = require('util')
4 | const gitSemverTags = require('git-semver-tags')
5 | const through = require('through2')
6 | const concat = require('concat-stream')
7 |
8 | const COMMIT_FORMAT = '%n%H%n%an%n%cI%n%s%n%b'
9 |
10 | function parseCommit(commit) {
11 | const lines = splitLines(commit)
12 | const [hash, author, date, subject, ...body] = lines.splice(
13 | 1,
14 | lines.length - 2
15 | )
16 | return {
17 | hash, author, date, subject, body,
18 | }
19 | }
20 |
21 | function getCommits(from, to) {
22 | return new Promise(resolve => {
23 | gitRawCommits({
24 | format: COMMIT_FORMAT,
25 | from,
26 | to,
27 | })
28 | .pipe(
29 | through.obj((data, enc, next) => {
30 | next(null, parseCommit(data.toString()))
31 | })
32 | )
33 | .pipe(
34 | concat(data => {
35 | resolve(data)
36 | })
37 | )
38 | })
39 | }
40 |
41 | const getTags = promisify(gitSemverTags)
42 |
43 | module.exports = {
44 | getTags,
45 | getCommits,
46 | }
47 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/groupMapping.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | group: 'added',
4 | label: 'Added',
5 | emojis: [
6 | 'sparkles',
7 | 'tada',
8 | 'white_check_mark',
9 | 'construction_worker',
10 | 'chart_with_upwards_trend',
11 | 'heavy_plus_sign',
12 | 'loud_sound',
13 | ],
14 | },
15 | {
16 | group: 'changed',
17 | label: 'Changed',
18 | emojis: [
19 | 'art',
20 | 'zap',
21 | 'lipstick',
22 | 'rotating_light',
23 | 'arrow_down',
24 | 'arrow_up',
25 | 'pushpin',
26 | 'recycle',
27 | 'wrench',
28 | 'rewind',
29 | 'alien',
30 | 'truck',
31 | 'bento',
32 | 'wheelchair',
33 | 'speech_balloon',
34 | 'card_file_box',
35 | 'children_crossing',
36 | 'building_construction',
37 | 'iphone',
38 | ],
39 | },
40 | {
41 | group: 'breaking_changes',
42 | label: 'Breaking changes',
43 | emojis: [
44 | 'boom',
45 | ],
46 | },
47 | {
48 | group: 'deprecated',
49 | label: 'Deprecated',
50 | emojis: [],
51 | },
52 | {
53 | group: 'removed',
54 | label: 'Removed',
55 | emojis: ['fire', 'heavy_minus_sign', 'mute'],
56 | },
57 | {
58 | group: 'fixed',
59 | label: 'Fixed',
60 | emojis: [
61 | 'bug',
62 | 'ambulance',
63 | 'apple',
64 | 'penguin',
65 | 'checkered_flag',
66 | 'robot',
67 | 'green_apple',
68 | 'green_heart',
69 | 'pencil2',
70 | ],
71 | },
72 | {
73 | group: 'security',
74 | label: 'Security',
75 | emojis: ['lock'],
76 | },
77 | {
78 | group: 'useless',
79 | label: 'Useless',
80 | emojis: [
81 | 'bookmark',
82 | ],
83 | },
84 | {
85 | group: 'misc',
86 | label: 'Miscellaneous',
87 | emojis: [],
88 | },
89 | ]
90 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/index.js:
--------------------------------------------------------------------------------
1 | const semver = require('semver')
2 | const semverCompare = require('semver-compare')
3 |
4 | const { isEmpty } = require('lodash')
5 |
6 | const { parseCommit, getMergedGroupMapping } = require('./parser')
7 | const logger = require('./logger')
8 | const { groupSentencesByDistance } = require('./utils')
9 | const { FunctionalError } = require('./errors')
10 | const fromGitFileClient = require('./fromGitFile')
11 |
12 | const HEAD = ''
13 | const TAIL = ''
14 |
15 | function makeGroups(commits) {
16 | if (isEmpty(commits)) return []
17 |
18 | const mapCommits = groups => {
19 | return groups
20 | .map(({ group, label }) => ({
21 | group,
22 | label,
23 | commits: commits
24 | .filter(commit => commit.group === group)
25 | .sort((first, second) => second.date.localeCompare(first.date)),
26 | }))
27 | .filter(group => group.commits.length)
28 | }
29 |
30 | const mergedCommitMapping = getMergedGroupMapping()
31 | return mapCommits(mergedCommitMapping)
32 | }
33 |
34 | function sanitizeVersion(version) {
35 | try {
36 | return semver.valid(version, {
37 | loose: false,
38 | includePrerelease: true,
39 | })
40 | } catch (e) {
41 | if (e.name === 'TypeError') {
42 | throw FunctionalError(e.message)
43 | }
44 | throw e
45 | }
46 | }
47 |
48 | function filterCommits(commits) {
49 | return commits
50 | .filter(commit => commit.group !== 'useless')
51 | }
52 |
53 | async function generateVersion(options) {
54 | const {
55 | from,
56 | to,
57 | version,
58 | groupSimilarCommits,
59 | client,
60 | } = options
61 |
62 | const rawCommits = await client.getCommits(from, to)
63 |
64 | let commits = filterCommits(rawCommits.map(parseCommit))
65 |
66 | if (groupSimilarCommits) {
67 | commits = groupSentencesByDistance(commits.map(commit => commit.message))
68 | .map(indexes => indexes.map(index => commits[index]))
69 | .map(([first, ...siblings]) => ({
70 | ...first,
71 | siblings,
72 | }))
73 | }
74 |
75 | const result = {
76 | version,
77 | groups: makeGroups(commits),
78 | }
79 |
80 | if (version !== 'next') {
81 | result.date = getLastCommitDate(commits)
82 | }
83 |
84 | return result
85 | }
86 |
87 | function sortVersions(c1, c2) {
88 | if (c1.version === 'next') return -1
89 | if (c2.version === 'next') return 1
90 | return semverCompare(c2.version, c1.version)
91 | }
92 |
93 | function hasNextVersion(tags, release) {
94 | if (!release || release === 'next') return true
95 | try {
96 | return tags.some(tag => semver.eq(tag, release))
97 | } catch (e) {
98 | if (e.name === 'TypeError') {
99 | throw FunctionalError(e.message)
100 | }
101 | throw e
102 | }
103 | }
104 |
105 | async function generateVersions({
106 | tags,
107 | hasNext,
108 | release,
109 | groupSimilarCommits,
110 | client,
111 | }) {
112 | let nextTag = HEAD
113 | const targetVersion = hasNext ? 'next' : sanitizeVersion(release)
114 | const changes = await Promise.all(tags.map(async tag => {
115 | const version = sanitizeVersion(nextTag) || targetVersion
116 | const from = tag
117 | const to = nextTag
118 | nextTag = tag
119 | return generateVersion({
120 | from, to, version, groupSimilarCommits, client,
121 | })
122 | }))
123 | .then(versions => versions.sort(sortVersions))
124 |
125 | return changes
126 | }
127 |
128 | async function generateChangelog(from, to, {
129 | groupSimilarCommits, client = fromGitFileClient,
130 | } = {}) {
131 | const gitTags = await client.getTags()
132 | let tagsToProcess = [...gitTags]
133 | const hasNext = hasNextVersion(gitTags, to)
134 |
135 | if (from === TAIL) {
136 | tagsToProcess = [...tagsToProcess, TAIL]
137 | } else {
138 | try {
139 | const fromIndex = tagsToProcess.findIndex(tag => semver.eq(tag, from))
140 | tagsToProcess.splice(fromIndex + 1)
141 |
142 | if (hasNext && isEmpty(tagsToProcess)) {
143 | tagsToProcess.push(HEAD)
144 | }
145 | } catch (e) {
146 | if (e.name === 'TypeError') {
147 | throw FunctionalError(e.message)
148 | }
149 | throw e
150 | }
151 | }
152 |
153 | const changes = await generateVersions({
154 | tags: tagsToProcess,
155 | hasNext,
156 | release: to,
157 | groupSimilarCommits,
158 | client,
159 | })
160 |
161 | if (from !== TAIL && changes.length === 1 && isEmpty(changes[0].groups)) {
162 | throw new FunctionalError('No changes found. You may need to fetch or pull the last changes.')
163 | }
164 |
165 | return {
166 | meta: {
167 | lastVersion: sanitizeVersion(from),
168 | },
169 | changes: changes.filter(({ groups }) => groups.length),
170 | }
171 | }
172 |
173 | function getLastCommitDate(commits) {
174 | if (isEmpty(commits)) return null
175 |
176 | return commits
177 | .map((commit) => new Date(commit.date))
178 | .reduce((lastCommitDate, currentCommitDate) => {
179 | if (currentCommitDate > lastCommitDate) {
180 | return currentCommitDate
181 | }
182 | return lastCommitDate
183 | })
184 | .toISOString().split('T')[0]
185 | }
186 |
187 | module.exports = {
188 | generateChangelog,
189 | logger,
190 | FunctionalError,
191 | }
192 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/index.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const gitRawCommits = require('git-raw-commits')
3 | const gitSemverTags = require('git-semver-tags')
4 | const rc = require('rc')
5 |
6 | const { generateChangelog } = require('./index')
7 |
8 | const TAIL = ''
9 | const HEAD = ''
10 |
11 | const uselessCommit = {
12 | hash: '460b79497ae7e791bc8ba8475bda8f0b93630dd3',
13 | date: '2018-09-14T21:00:18+02:00',
14 | subject: ':bookmark: Bump version to 1.9.2',
15 | body: 'Yes!',
16 | emoji: '🔖',
17 | emojiCode: 'bookmark',
18 | group: 'useless',
19 | message: 'Bump version to 1.9.2',
20 | }
21 |
22 | const lockCommit = {
23 | hash: '460b79497ae7e791bc8ba8475bda8f0b93630dd9',
24 | author: 'John Doe',
25 | date: '2018-09-14T22:00:18+02:00',
26 | subject: ':lock: Improve security',
27 | body: 'Yes!',
28 | emoji: '🔒',
29 | emojiCode: 'lock',
30 | group: 'security',
31 | message: 'Improve security',
32 | siblings: [],
33 | }
34 |
35 | const sparklesCommit = {
36 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23a',
37 | author: 'John Doe',
38 | date: '2018-08-28T10:06:00+02:00',
39 | subject: ':sparkles: Add a brand new feature',
40 | body: 'Waouh this is awesome 2',
41 | emoji: '✨',
42 | emojiCode: 'sparkles',
43 | group: 'added',
44 | message: 'Add a brand new feature',
45 | siblings: [],
46 | }
47 |
48 | const recycleCommit = {
49 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c',
50 | author: 'John Doe',
51 | date: '2018-08-01T10:07:00+02:00',
52 | subject: ':recycle: Make some reworking on code',
53 | body: 'Waouh this is awesome 3',
54 | emoji: '♻️',
55 | emojiCode: 'recycle',
56 | group: 'changed',
57 | message: 'Make some reworking on code',
58 | siblings: [],
59 | }
60 |
61 | const secondRecycleCommit = {
62 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23d',
63 | author: 'John Doe',
64 | date: '2018-08-30T10:07:00+02:00',
65 | subject: ':recycle: Upgrade another brand new feature',
66 | body: 'Waouh this is awesome 4',
67 | emoji: '♻️',
68 | emojiCode: 'recycle',
69 | group: 'changed',
70 | message: 'Upgrade another brand new feature',
71 | siblings: [],
72 | }
73 |
74 | const lipstickCommit = {
75 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23e',
76 | author: 'John Doe',
77 | date: '2018-08-10T10:07:00+02:00',
78 | subject: ':lipstick: Change graphics for a feature',
79 | body: 'Waouh this is awesome 5',
80 | emoji: '💄',
81 | emojiCode: 'lipstick',
82 | group: 'changed',
83 | message: 'Change graphics for a feature',
84 | siblings: [],
85 | }
86 |
87 | const secondLipstickCommit = {
88 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23f',
89 | author: 'John Doe',
90 | date: '2018-08-18T10:07:00+02:00',
91 | subject: ':lipstick: Change more graphics for a feature',
92 | body: 'Waouh this is awesome 6',
93 | emoji: '💄',
94 | emojiCode: 'lipstick',
95 | group: 'changed',
96 | message: 'Change more graphics for a feature',
97 | siblings: [],
98 | }
99 |
100 | describe('changelog', () => {
101 | beforeEach(() => {
102 | rc.mockImplementation(() => ({}))
103 | })
104 |
105 | it('should generate changelog for next release on init', async () => {
106 | mockGroup([sparklesCommit])
107 |
108 | gitSemverTags.mockImplementation(cb => cb(null, []))
109 |
110 | const { changes } = await generateChangelog(TAIL, 'next')
111 |
112 | expect(changes).toEqual([
113 | {
114 | version: 'next',
115 | groups: [
116 | {
117 | group: 'added',
118 | label: 'Added',
119 | commits: expect.arrayContaining([expect.objectContaining(sparklesCommit)]),
120 | },
121 | ],
122 | },
123 | ])
124 | })
125 |
126 | it('should generate changelog using custom commit mapping', async () => {
127 | const customConfiguration = {
128 | commitMapping: [
129 | { group: 'added', label: 'Added', emojis: ['sparkles'] },
130 | { group: 'style', label: 'Style', emojis: ['lipstick'] },
131 | { group: 'changed', label: 'Changed', emojis: [] },
132 | ],
133 | }
134 |
135 | rc.mockImplementation(() => customConfiguration)
136 | mockGroup([sparklesCommit, lipstickCommit, lockCommit])
137 |
138 | gitSemverTags.mockImplementation(cb => cb(null, []))
139 |
140 | const { changes } = await generateChangelog(TAIL, 'next')
141 |
142 | expect(changes).toEqual([
143 | {
144 | version: 'next',
145 | groups: [
146 | {
147 | group: 'added',
148 | label: 'Added',
149 | commits: expect.arrayContaining([expect.objectContaining(sparklesCommit)]),
150 | },
151 | {
152 | group: 'security',
153 | label: 'Security',
154 | commits: expect.arrayContaining([expect.objectContaining(lockCommit)]),
155 | },
156 | {
157 | group: 'style',
158 | label: 'Style',
159 | commits: expect.arrayContaining([expect.objectContaining({ ...lipstickCommit, group: 'style' })]),
160 | },
161 | ],
162 | },
163 | ])
164 | })
165 |
166 | it('should generate changelog for next release', async () => {
167 | mockGroup([sparklesCommit])
168 |
169 | gitSemverTags.mockImplementation(cb => cb(null, []))
170 |
171 | const { changes } = await generateChangelog('v1.0.0', 'next')
172 |
173 | expect(changes).toEqual([
174 | {
175 | version: 'next',
176 | groups: [
177 | {
178 | group: 'added',
179 | label: 'Added',
180 | commits: expect.arrayContaining([expect.objectContaining(sparklesCommit)]),
181 | },
182 | ],
183 | },
184 | ])
185 | })
186 |
187 | it('should generate changelog for all tags', async () => {
188 | mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit])
189 | mockGroup([sparklesCommit])
190 |
191 | gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0']))
192 |
193 | const { changes } = await generateChangelog(TAIL, HEAD)
194 |
195 | expect(changes).toEqual([
196 | {
197 | version: 'next',
198 | groups: [
199 | {
200 | group: 'changed',
201 | label: 'Changed',
202 | commits: [
203 | expect.objectContaining({ subject: secondRecycleCommit.subject }),
204 | expect.objectContaining({ subject: secondLipstickCommit.subject }),
205 | expect.objectContaining({ subject: lipstickCommit.subject }),
206 | expect.objectContaining({ subject: recycleCommit.subject }),
207 | ],
208 | },
209 | ],
210 | },
211 | {
212 | version: '1.0.0',
213 | date: '2018-08-28',
214 | groups: [
215 | {
216 | group: 'added',
217 | label: 'Added',
218 | commits: [
219 | expect.objectContaining({ subject: sparklesCommit.subject }),
220 | ],
221 | },
222 | ],
223 | },
224 | ])
225 | })
226 |
227 | it('should generate a changelog with only next since lastVersion is provided to v1.0.0', async () => {
228 | mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit])
229 |
230 | gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0']))
231 |
232 | const { changes } = await generateChangelog('v1.0.0', 'next')
233 |
234 | expect(changes).toEqual([
235 | {
236 | version: 'next',
237 | groups: [
238 | {
239 | group: 'changed',
240 | label: 'Changed',
241 | commits: [
242 | expect.objectContaining({ subject: secondRecycleCommit.subject }),
243 | expect.objectContaining({ subject: secondLipstickCommit.subject }),
244 | expect.objectContaining({ subject: lipstickCommit.subject }),
245 | expect.objectContaining({ subject: recycleCommit.subject }),
246 | ],
247 | },
248 | ],
249 | },
250 | ])
251 | })
252 |
253 | it('should group similar commits', async () => {
254 | mockGroup([])
255 | mockGroup([recycleCommit, secondRecycleCommit, lipstickCommit, secondLipstickCommit])
256 |
257 | gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0']))
258 |
259 | const { changes } = await generateChangelog(TAIL, HEAD, { groupSimilarCommits: true })
260 |
261 | expect(changes[0].groups[0].commits).toEqual([
262 | secondRecycleCommit,
263 | {
264 | ...lipstickCommit,
265 | siblings: [secondLipstickCommit],
266 | },
267 | recycleCommit,
268 | ])
269 | })
270 |
271 | it('should filter some commits out', async () => {
272 | gitRawCommits.mockReset()
273 | mockGroup([uselessCommit, lipstickCommit])
274 |
275 | gitSemverTags.mockImplementation(cb => cb(null, []))
276 |
277 | const { changes } = await generateChangelog(TAIL, HEAD)
278 |
279 | expect(changes).toEqual([
280 | expect.objectContaining({
281 | groups: [
282 | expect.objectContaining({
283 | commits: [lipstickCommit],
284 | }),
285 | ],
286 | }),
287 | ])
288 | })
289 |
290 | it('should throw an error if no commits', async () => {
291 | mockNoCommits()
292 |
293 | gitSemverTags.mockImplementation(cb => cb(null, []))
294 |
295 | let message = false
296 | try {
297 | await generateChangelog('v1.0.0', 'next')
298 | } catch (e) {
299 | message = e.message
300 | }
301 | expect(message).toBeTruthy()
302 | })
303 |
304 | it('should get previous tag in from', async () => {
305 | mockGroup([])
306 | mockGroup([])
307 | mockGroup([])
308 |
309 | gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.1', 'v1.0.0']))
310 |
311 | await generateChangelog(TAIL, HEAD)
312 |
313 | expect(gitRawCommits).toHaveBeenCalledWith(expect.objectContaining({ from: 'v1.0.1', to: '' }))
314 | expect(gitRawCommits).toHaveBeenCalledWith(
315 | expect.objectContaining({ from: 'v1.0.0', to: 'v1.0.1' })
316 | )
317 | expect(gitRawCommits).toHaveBeenCalledWith(expect.objectContaining({ from: '', to: 'v1.0.0' }))
318 | })
319 |
320 | it('should filter empty groups out', async () => {
321 | mockGroup([sparklesCommit])
322 | mockGroup([recycleCommit])
323 | mockGroup([]) // empty group
324 | mockGroup([lipstickCommit])
325 |
326 | gitSemverTags.mockImplementation(cb => cb(null, ['v1.0.0', '1.0.0', 'v1.1.1']))
327 |
328 | const { changes } = await generateChangelog(TAIL, HEAD)
329 |
330 | // inputs has 4 group (4 versions)
331 | // but output should only has 3, since the 3rd is empty
332 | expect(changes).toHaveLength(3)
333 | })
334 |
335 | it('should sort commits by date', async () => {
336 | mockGroup([
337 | { date: '2019-02-02T00:00:00+00:00', body: '3' },
338 | { date: '2019-02-01T01:01:00+00:00', body: '7' },
339 | { date: '2019-02-03T00:00:00+00:00', body: '2' },
340 | { date: '2019-02-04T00:00:00+00:00', body: '1' },
341 | { date: '2019-02-01T01:01:01+01:01', body: '4' },
342 | { date: '2019-02-01T00:00:00+00:00', body: '9' },
343 | { date: '2019-02-01T01:01:01+01:00', body: '5' },
344 | { date: '2019-02-01T01:00:00+00:00', body: '8' },
345 | { date: '2019-02-01T01:01:01+00:00', body: '6' },
346 | ])
347 |
348 | gitSemverTags.mockImplementation(cb => cb(null, []))
349 |
350 | const { changes } = await generateChangelog(TAIL, HEAD)
351 |
352 | expect(changes[0].groups[0].commits.map(({ date, body }) => ({ date, body })))
353 | .toEqual([
354 | { date: '2019-02-04T00:00:00+00:00', body: '1' },
355 | { date: '2019-02-03T00:00:00+00:00', body: '2' },
356 | { date: '2019-02-02T00:00:00+00:00', body: '3' },
357 | { date: '2019-02-01T01:01:01+01:01', body: '4' },
358 | { date: '2019-02-01T01:01:01+01:00', body: '5' },
359 | { date: '2019-02-01T01:01:01+00:00', body: '6' },
360 | { date: '2019-02-01T01:01:00+00:00', body: '7' },
361 | { date: '2019-02-01T01:00:00+00:00', body: '8' },
362 | { date: '2019-02-01T00:00:00+00:00', body: '9' },
363 | ])
364 | })
365 | })
366 |
367 | jest.mock('git-raw-commits')
368 | jest.mock('git-semver-tags')
369 | jest.mock('rc')
370 |
371 | function mockGroup(commits) {
372 | gitRawCommits.mockImplementationOnce(() => {
373 | const stream = require('stream')
374 | const readable = new stream.Readable()
375 | commits.forEach(commit => {
376 | const {
377 | hash, author, date, subject, body,
378 | } = commit
379 | readable.push(`\n${hash}\n${author}\n${date}\n${subject}\n${body}\n`)
380 | })
381 | readable.push(null)
382 | readable.emit('close')
383 | return readable
384 | })
385 | }
386 |
387 | function mockNoCommits() {
388 | gitRawCommits.mockReset()
389 |
390 | gitRawCommits.mockImplementationOnce(() => {
391 | const stream = require('stream')
392 | const readable = new stream.Readable()
393 | readable.push(null)
394 | readable.emit('close')
395 | return readable
396 | })
397 | }
398 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/logger.js:
--------------------------------------------------------------------------------
1 | const { Signale } = require('signale')
2 |
3 | const options = {
4 | disabled: false,
5 | interactive: false,
6 | stream: process.stdout,
7 | }
8 |
9 | const signale = new Signale(options)
10 |
11 | module.exports = signale
12 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/parser.js:
--------------------------------------------------------------------------------
1 | const nodeEmoji = require('node-emoji')
2 | const groupMapping = require('./groupMapping')
3 | const rc = require('rc')
4 |
5 | function parseSubject(subject) {
6 | if (!subject) return {}
7 |
8 | const unemojified = nodeEmoji.unemojify(subject)
9 |
10 | const matches = unemojified.match(/:(\w*):(.*)/)
11 |
12 | if (matches) {
13 | const [, emojiCode, message] = matches
14 |
15 | if (nodeEmoji.hasEmoji(emojiCode)) {
16 | return {
17 | emojiCode,
18 | emoji: nodeEmoji.get(emojiCode),
19 | message: nodeEmoji.emojify(message.trim()),
20 | }
21 | }
22 | }
23 |
24 | return {
25 | message: subject,
26 | }
27 | }
28 |
29 | function getMergedGroupMapping() {
30 | const customConfiguration = rc('gitmoji-changelog')
31 | const customGroupMapping = customConfiguration ? customConfiguration.commitMapping : undefined
32 | if (!customGroupMapping) return groupMapping
33 | const newCategories = customGroupMapping.filter(cg => {
34 | return !groupMapping.some(g => g.group === cg.group)
35 | })
36 |
37 | const overridedCategories = groupMapping.map(group => {
38 | const customGroup = customGroupMapping.find(cg => cg.group === group.group)
39 | return customGroup || group
40 | })
41 |
42 | const miscellaneousIndex = overridedCategories.findIndex(g => g.group === 'misc')
43 | const miscellaneousCategory = overridedCategories.splice(miscellaneousIndex, 1)[0]
44 |
45 | return [
46 | ...overridedCategories,
47 | ...newCategories,
48 | miscellaneousCategory,
49 | ]
50 | }
51 |
52 | function getCommitGroup(emojiCode) {
53 | const group = getMergedGroupMapping()
54 | .find(({ emojis }) => emojis.includes(emojiCode))
55 | if (!group) return 'misc'
56 | return group.group
57 | }
58 |
59 | function parseCommit({
60 | hash, author, date, subject = '', body = '',
61 | }) {
62 | const { emoji, emojiCode, message } = parseSubject(subject)
63 | const group = getCommitGroup(emojiCode)
64 |
65 | return {
66 | hash,
67 | author,
68 | date,
69 | subject,
70 | emojiCode,
71 | emoji,
72 | message,
73 | group,
74 | siblings: [],
75 | body: Array.isArray(body) ? body.join('\n') : body,
76 | }
77 | }
78 |
79 | module.exports = {
80 | parseCommit,
81 | getCommitGroup,
82 | getMergedGroupMapping,
83 | }
84 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/parser.spec.js:
--------------------------------------------------------------------------------
1 | const { getMergedGroupMapping, parseCommit } = require('./parser.js')
2 | const rc = require('rc')
3 |
4 | const sparklesCommit = {
5 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23f',
6 | author: 'John Doe',
7 | date: '2018-08-28T10:06:00+02:00',
8 | subject: ':sparkles: Upgrade brand new feature',
9 | body: 'Waouh this is awesome 2',
10 | }
11 |
12 | describe('group mapping', () => {
13 | it('should place miscellaneous category at the end', () => {
14 | const mergeGroupMapping = getMergedGroupMapping([
15 | { group: 'added', emojis: [] },
16 | { group: 'custom', emojis: ['sparkles'] },
17 | ])
18 |
19 | expect(mergeGroupMapping.pop()).toEqual(expect.objectContaining({ group: 'misc' }))
20 | })
21 | })
22 |
23 | describe('commits parser', () => {
24 | it('should parse a single commit', () => {
25 | expect(parseCommit(sparklesCommit)).toEqual(expect.objectContaining(sparklesCommit))
26 | })
27 |
28 | it('should parse a unicode emoji', () => {
29 | const parsed = parseCommit({
30 | ...sparklesCommit,
31 | subject: '✨ Upgrade brand new feature',
32 | })
33 | expect(parsed.emoji).toEqual('✨')
34 | expect(parsed.emojiCode).toEqual('sparkles')
35 | expect(parsed.message).toEqual('Upgrade brand new feature')
36 | })
37 |
38 | it('should parse a single commit without a body', () => {
39 | const parsed = parseCommit({
40 | ...sparklesCommit,
41 | body: '',
42 | })
43 |
44 | expect(parseCommit(parsed)).toEqual(expect.objectContaining({
45 | ...sparklesCommit,
46 | body: '',
47 | }))
48 | })
49 |
50 | it('should parse a single commit without a subject', () => {
51 | const {
52 | hash,
53 | author,
54 | date,
55 | } = sparklesCommit
56 | expect(parseCommit({
57 | hash,
58 | author,
59 | date,
60 | })).toEqual(expect.objectContaining({
61 | ...sparklesCommit,
62 | subject: '',
63 | body: '',
64 | }))
65 | })
66 |
67 | it('should add the group to a commit', () => {
68 | expect(parseCommit(sparklesCommit)).toEqual(expect.objectContaining({ group: 'added' }))
69 | })
70 |
71 | it('should handle custom commit mapping', () => {
72 | const customConfiguration = {
73 | commitMapping: [
74 | { group: 'added', emojis: [] },
75 | { group: 'custom', emojis: ['sparkles'] },
76 | ],
77 | }
78 | rc.mockImplementation(() => customConfiguration)
79 |
80 | expect(parseCommit(sparklesCommit)).toEqual(expect.objectContaining({ group: 'custom' }))
81 | })
82 | })
83 |
84 | jest.mock('rc')
85 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/utils.js:
--------------------------------------------------------------------------------
1 | const { deburr } = require('lodash')
2 | const levenshtein = require('fast-levenshtein')
3 |
4 | // this is a magic number, this comes from various testing
5 | // feel free to tweak it
6 | const MAX_DISTANCE_PERCENT = 0.30
7 |
8 | function groupSentencesByDistance(texts = []) {
9 | const textsWithSortedWords = texts
10 | .map(text => (
11 | // to basic latin characters
12 | deburr(text)
13 | // replace specials characters by a filler
14 | .replace(/[^\w\s]/gi, '▩')
15 | // split words
16 | .split(' ')
17 | // little words are replaces by fillers
18 | // this way -> we remove useless word like (a, of, etc)
19 | // we keep the string length for the algorithm
20 | .map(word => word.length < 4 ? Array.from({ length: word.length }).join('▩') : word)
21 | // we sort words
22 | .sort()
23 | // we make them a sentence
24 | .join('')
25 | ))
26 |
27 | const alreadyProcessedWords = new Set()
28 | const keyGroups = []
29 |
30 | for (
31 | let indexesFromStart = 0;
32 | indexesFromStart < textsWithSortedWords.length;
33 | indexesFromStart += 1
34 | ) {
35 | if (!alreadyProcessedWords.has(indexesFromStart)) {
36 | alreadyProcessedWords.add(indexesFromStart)
37 | const group = [indexesFromStart]
38 | keyGroups.push(group)
39 |
40 | for (
41 | let indexesFromNext = indexesFromStart + 1;
42 | indexesFromNext < textsWithSortedWords.length;
43 | indexesFromNext += 1
44 | ) {
45 | const textA = textsWithSortedWords[indexesFromStart]
46 | const textB = textsWithSortedWords[indexesFromNext]
47 | const distance = levenshtein.get(textA, textB)
48 | const textAverageLength = (textA.length + textB.length) / 2
49 |
50 | if (
51 | // close distance
52 | (textAverageLength * MAX_DISTANCE_PERCENT) >= distance
53 | // not already in a group
54 | && !alreadyProcessedWords.has(indexesFromNext)
55 | ) {
56 | group.push(indexesFromNext)
57 | alreadyProcessedWords.add(indexesFromNext)
58 | }
59 | }
60 | }
61 | }
62 |
63 | return keyGroups
64 | }
65 |
66 | module.exports = {
67 | groupSentencesByDistance,
68 | }
69 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-core/src/utils.spec.js:
--------------------------------------------------------------------------------
1 | const { groupSentencesByDistance } = require('./utils')
2 |
3 | describe('utils', () => {
4 | describe('groupSentencesByDistance', () => {
5 | it('should group values together', () => {
6 | const messages = [
7 | 'add levenshtein', // 0 - group1
8 | 'fix a bug about failures graph', // 1 - group2
9 | 'levenshtein', // 2 - group1
10 | 'fix levenshtein', // 3 - group1
11 | 'nothing to group with me',
12 | 'fix a graph of failures bug', // 5 - group2
13 | ]
14 |
15 | expect(groupSentencesByDistance(messages)).toEqual([[0, 2, 3], [1, 5], [4]])
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-documentation/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frinyvonnick/gitmoji-changelog/d0edd29b861584808f6c0ba0edf7c391d3ddcb33/packages/gitmoji-changelog-documentation/.nojekyll
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-documentation/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## 🚀 Usage
4 |
5 | Make sure you have [npx](https://www.npmjs.com/package/npx) installed (`npx` is shipped by default since npm `5.2.0`)
6 |
7 | Run the following command at the root of your project and answer questions. `gitmoji-changelog` uses a [preset system](?id=⚙%ef%b8%8f-presets) to handle different type of project. The preset used by default is the Node.js one that look for project's information in the `package.json` file.
8 |
9 | with npx:
10 | ```sh
11 | npx gitmoji-changelog
12 | ```
13 |
14 | with npm:
15 | ```sh
16 | npm install -g gitmoji-changelog
17 |
18 | cd my-project
19 | gitmoji-changelog
20 | ```
21 |
22 | It exists a generic preset that works for every kind of project. It looks for information in a `.gitmoji-changelogrc` file at the root of your project. This file must contain three mandatory properties: `name`, `description` and `version`.
23 |
24 | .gitmoji-changelogrc:
25 | ```json
26 | {
27 | "project": {
28 | "name": "gitmoji-changelog",
29 | "description": "A changelog generator for gitmoji 😜",
30 | "version": "2.0.1"
31 | }
32 | }
33 | ```
34 |
35 | You can change the preset used by `gitmoji-changelog` with the preset option.
36 |
37 | ```sh
38 | npx gitmoji-changelog --preset generic
39 | ```
40 |
41 |
42 | ### Available commands
43 |
44 | ```sh
45 | gitmoji-changelog [release]
46 | gitmoji-changelog init
47 | gitmoji-changelog update [release]
48 | ```
49 |
50 | The first command listed above is the idiomatic usage of `gitmoji-changelog` (read the `How it works` for more information).
51 |
52 | `release` argument is by default valued to `from-package`. It will retrieve the version from the selected `preset` (read `Presets` for more information). You can overwrite it with the tag you want to generate in the changelog.
53 |
54 | ### Options
55 |
56 | | option | description | default value |
57 | |-------------------------|-----------------------------------------|------------------------------------|
58 | | --version | display version | |
59 | | --format | changelog format (markdown, json) | markdown |
60 | | --preset | define preset mode | node |
61 | | --output | output file path | ./CHANGELOG.md or ./CHANGELOG.json |
62 | | --group-similar-commits | [⚗️,- beta] try to group similar commits | false |
63 | | --author | add the author in changelog lines | false |
64 | | --interactive -i | select commits manually | false |
65 | | --help | display help | |
66 |
67 | ### Example
68 |
69 | **Here an example output:** [CHANGELOG.md](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/CHANGELOG.md)
70 |
71 | ## 📚 How it works
72 |
73 | ### Behavior
74 |
75 | `CHANGELOG.md` doesn't exist:
76 |
77 | The CLI will generate all previous changelog based on semver tags of your repo.
78 |
79 | `CHANGELOG.md` exists:
80 |
81 | _All previous semvers tags remain unchanged_. The CLI will add each tag since the last semver tag found in the `CHANGELOG.md` file.
82 |
83 | **By default when you generate your changelog with `gitmoji-changelog`, the following mapping is used to group commits: [groupMapping.js](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/packages/gitmoji-changelog-core/src/groupMapping.js)**
84 |
85 | ### Workflow
86 |
87 | Here the recommended workflow to generate your changelog file using `gitmoji-changelog`:
88 |
89 | **Important:** Before generating, be sure to have all tags locally (e.g. `git fetch origin`)
90 |
91 | 1. Make changes and commit: `git commit -m ":sparkles: my awesome feature"`
92 | 2. Bump version (ex: `1.0.0`) in `package.json` using [semver](https://semver.org/) convention
93 | 3. Run `gitmoji-changelog`, then the file `CHANGELOG.md` is created or updated with all changes
94 | 4. You can freely edit the new release in the changelog file, it will not be overwritten with the next generation
95 | 5. Commit `package.json` and `CHANGELOG.md` file
96 | 6. Tag your release: `git tag -a v1.0.0 -m "v1.0.0"` (or create a Github release)
97 | 7. Push to the remote `git push --follow-tags`
98 |
99 | _This workflow is related to the `node` preset but can be adapted to your own technology._
100 |
101 | ## ⚙️ Presets
102 |
103 | `gitmoji-changelog` use presets to get project metadata useful for its smooth operation. Here is the list of available presets:
104 |
105 | - node (default preset)
106 | - generic
107 | - maven
108 | - cargo
109 | - helm
110 |
111 | Didn't see the preset you need in the list? Consider adding it. Presets are stored in a [presets](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/packages/gitmoji-changelog-cli/src/presets) folder in the `cli` package.
112 |
113 | ### Existing presets
114 |
115 | #### Node
116 |
117 | The node preset looks for a `package.json` file.
118 |
119 | ```json
120 | {
121 | "name": "project-name",
122 | "version": "0.0.1",
123 | "description": "Some description",
124 | }
125 | ```
126 |
127 | #### Generic
128 |
129 | The generic preset looks a `gitmoji-changelogrc` file.
130 |
131 | ```json
132 | {
133 | "project": {
134 | "name": "yvonnickfrin.dev",
135 | "description": "My blog",
136 | "version": "1.1.0"
137 | }
138 | }
139 | ```
140 |
141 | It must contain all mandatory properties in a `project` property. The `gitmoji-changelogrc` file can contain other configuration properties like a custom commit categorization.
142 |
143 | #### Maven
144 |
145 | The maven preset looks for 4 properties in you `pom.xml`:
146 |
147 | - groupid
148 | - artifactid
149 | - version
150 | - description
151 |
152 | #### Cargo
153 |
154 | The cargo preset looks for 3 properties in your `Cargo.toml`:
155 |
156 | - name
157 | - version
158 | - description
159 |
160 | #### Helm
161 |
162 | The helm preset looks for 3 properties in your `Chart.yaml`:
163 |
164 | - name
165 | - version
166 | - description
167 |
168 | #### Python
169 |
170 | The python preset looks for 3 properties in your `pyproject.toml`:
171 |
172 | - name
173 | - version
174 | - description
175 |
176 | (The value taken is the first one found in your `pyproject.toml` that matches the expected key name given above.)
177 |
178 | ### Add a preset
179 |
180 | A preset need to export a function. When called this function must return three mandatory information about the project in which the cli has been called. The name of the project, a short description of it and its current version.
181 |
182 | Let's dissect the `node` preset to see how it works. First of all the module must export a function. In case something went wrong return `null`. The cli will tell the user a problem occurred.
183 |
184 | ```js
185 | module.exports = () => {
186 | return null
187 | }
188 | ```
189 |
190 | There is a package called `read-pkg-up` to get the first `package.json` in the parent folder structure. It returns its content as a JavaScript object. If we can't find a `package.json` or it is empty we return `null`.
191 |
192 | ```js
193 | const readPkgUp = require('read-pkg-up')
194 |
195 | module.exports = async () => {
196 | try {
197 | const packageInfo = await readPkgUp()
198 |
199 | if (!packageInfo.packageJson) {
200 | throw Error('Empty package.json')
201 | }
202 | } catch (e) {
203 | return null
204 | }
205 | }
206 | ```
207 |
208 | If everything went fine we return the three mandatory information (name, description, version).
209 |
210 | ```js
211 | const readPkgUp = require('read-pkg-up')
212 |
213 | module.exports = async () => {
214 | try {
215 | const packageInfo = await readPkgUp()
216 |
217 | if (!packageInfo.packageJson) {
218 | throw Error('Empty package.json')
219 | }
220 |
221 | return {
222 | name: packageInfo.packageJson.name,
223 | description: packageInfo.packageJson.description,
224 | version: packageInfo.packageJson.version,
225 | }
226 | } catch (e) {
227 | return null
228 | }
229 | }
230 | ```
231 |
232 | That's it. Feel free to open an issue to ask for a new preset or open a pull request to add one.
233 |
234 | All preset needs at least 3 pieces of informations to work:
235 |
236 | - A project name
237 | - A current version
238 | - A description
239 |
240 | They have their own way to get these.
241 |
242 |
243 | ## 🐳 Using Docker image
244 |
245 | Launch `gitmoji-changelog` using the [official Docker image](https://hub.docker.com/r/yvonnick/gitmoji-changelog):
246 | ```sh
247 | docker container run -it -v $(pwd):/app --rm yvonnick/gitmoji-changelog:latest
248 | ```
249 |
250 | > `/app` is the directory where gitmoji-changelog expect your project in the container.
251 |
252 | You can also build the image locally and use it directly:
253 | ```sh
254 | # build the image:
255 | docker image build -t yvonnick/gitmoji-changelog:dev .
256 | # use it:
257 | docker container run -it -v $(pwd):/app --rm yvonnick/gitmoji-changelog:dev
258 | ```
259 |
260 | ### Supported tags and respective Dockerfile links
261 |
262 | * [yvonnick/gitmoji-changelog:latest](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/Dockerfile)
263 |
264 |
265 | ## 🐥 Canary version
266 |
267 | If you want to test the incoming release of gitmoji-changelog, you can use or install the canary version.
268 | Be aware, it's a development in progress version, feel free to report any bugs plus give feedback.
269 |
270 | with npx:
271 | ```sh
272 | npx gitmoji-changelog@canary --version
273 | ```
274 |
275 | with npm:
276 | ```sh
277 | npm install -g gitmoji-changelog@canary
278 | ```
279 |
280 | ## 📝 License
281 |
282 | Copyright © 2020 [Yvonnick FRIN (https://github.com/frinyvonnick)](https://github.com/frinyvonnick).
283 | This project is [MIT](https://github.com/frinyvonnick/gitmoji-changelog/blob/master/LICENSE) licensed.
284 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-documentation/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | gitmoji-changelog | docs
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-documentation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": "true",
3 | "name": "@gitmoji-changelog/documentation",
4 | "version": "2.2.0",
5 | "main": "index.html",
6 | "license": "MIT",
7 | "scripts": {
8 | "start": "docsify serve"
9 | },
10 | "devDependencies": {
11 | "docsify-cli": "^4.4.1"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-markdown/.npmignore:
--------------------------------------------------------------------------------
1 | *.spec.js
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-markdown/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitmoji-changelog/markdown",
3 | "version": "2.3.0",
4 | "description": "Gitmoji Changelog markdown formatter",
5 | "main": "src/index.js",
6 | "bin": {
7 | "gitmoji-changelog": "./src/index.js"
8 | },
9 | "engines": {
10 | "node": ">=10"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/frinyvonnick/gitmoji-changelog.git"
15 | },
16 | "author": "Yvonnick FRIN (https://github.com/frinyvonnick)",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/frinyvonnick/gitmoji-changelog/issues"
20 | },
21 | "homepage": "https://github.com/frinyvonnick/gitmoji-changelog#readme",
22 | "dependencies": {
23 | "git-semver-tags": "^4.1.1",
24 | "handlebars": "^4.0.14",
25 | "immutadot": "^1.0.0",
26 | "lodash": "^4.17.11",
27 | "semver": "^5.6.0"
28 | },
29 | "publishConfig": {
30 | "access": "public",
31 | "registry": "https://registry.npmjs.org/"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-markdown/src/index.js:
--------------------------------------------------------------------------------
1 | const semver = require('semver')
2 | const { promisify } = require('util')
3 | const fs = require('fs')
4 | const path = require('path')
5 | const { Transform } = require('stream')
6 | const handlebars = require('handlebars')
7 | const { update } = require('immutadot')
8 | const { isEmpty } = require('lodash')
9 | const gitSemverTags = require('git-semver-tags')
10 |
11 | const gitSemverTagsAsync = promisify(gitSemverTags)
12 |
13 | const MARKDOWN_TEMPLATE = path.join(__dirname, 'template.md')
14 |
15 | function buildMarkdownFile(changelog = {}, options = {}) {
16 | if (options.mode === 'init') {
17 | return markdownFromScratch(changelog, options)
18 | }
19 | return markdownIncremental(changelog, options)
20 | }
21 |
22 | function mapCommit(meta, options) {
23 | const { author } = options
24 |
25 | return commit => ({
26 | ...commit,
27 | hash: getShortHash(commit.hash, meta.repository),
28 | subject: autolink(commit.subject, meta.repository),
29 | message: autolink(commit.message, meta.repository),
30 | body: autolink(commit.body, meta.repository),
31 | author: author ? commit.author : null,
32 | siblings: commit.siblings.map(mapCommit(meta, options)),
33 | })
34 | }
35 |
36 | function toMarkdown({ meta, changes }, options) {
37 | const template = fs.readFileSync(MARKDOWN_TEMPLATE, 'utf-8')
38 | const compileTemplate = handlebars.compile(template)
39 | const changelog = update(changes, '[:].groups[:].commits[:]', mapCommit(meta, options))
40 |
41 | return compileTemplate({ changelog })
42 | }
43 |
44 | function markdownFromScratch({ meta, changes }, options) {
45 | return promisify(fs.writeFile)(options.output, `# Changelog\n\n${toMarkdown({ meta, changes }, options)}`)
46 | }
47 |
48 | function markdownIncremental({ meta, changes }, options) {
49 | const { lastVersion } = meta
50 | const { output } = options
51 |
52 | const tempFile = `${output}.tmp`
53 |
54 | return new Promise((resolve, reject) => {
55 | const readStream = fs.createReadStream(output, { encoding: 'utf-8' })
56 | const writeStream = fs.createWriteStream(tempFile, { encoding: 'utf-8' })
57 |
58 | let previousNextFound = false
59 | let previousVersionFound = false
60 | let nextVersionWritten = false
61 |
62 | readStream
63 | .pipe(new Transform({
64 | transform(chunk, encoding, callback) {
65 | const string = chunk.toString()
66 |
67 | callback(
68 | null,
69 | string.split('\n')
70 | .reduce(
71 | (content, nextLine, index, array) => {
72 | previousVersionFound = matchVersionBreakpoint(nextLine, lastVersion)
73 | previousNextFound = previousNextFound || matchVersionBreakpoint(nextLine)
74 |
75 | // Remove old release (next version)
76 | if (previousNextFound && !previousVersionFound && !nextVersionWritten) {
77 | return content
78 | }
79 |
80 | // Rewrite the release (next version)
81 | if (previousVersionFound && !nextVersionWritten) {
82 | nextVersionWritten = true
83 | return `${content}${toMarkdown({ meta, changes }, options)}${nextLine}\n`
84 | }
85 |
86 | // Just push the line without changing anything
87 | if (index !== array.length - 1) {
88 | return `${content}${nextLine}\n`
89 | }
90 | return `${content}${nextLine}`
91 | },
92 | '',
93 | )
94 | )
95 | },
96 | }))
97 | .pipe(writeStream)
98 | .on('error', reject)
99 | .on('close', () => {
100 | fs.rename(tempFile, output, resolve)
101 | })
102 | })
103 | }
104 |
105 | function matchVersionBreakpoint(tested, version = '.*') {
106 | const regex = new RegExp(` `)
107 | return regex.test(tested)
108 | }
109 |
110 | async function getLatestVersion(markdownFile) {
111 | let markdownContent
112 |
113 | try {
114 | markdownContent = fs.readFileSync(markdownFile, 'utf-8')
115 | } catch (err) {
116 | return null
117 | }
118 |
119 | const versions = markdownContent.match(/<\/a>/g)
120 |
121 | if (!versions) return null
122 |
123 | const [lastVersion, previousVersion] = versions
124 |
125 | const tags = await gitSemverTagsAsync()
126 | const result = lastVersion.match(/ <\/a>/)
127 | const isNext = result[1] === 'next' || !tags.some(tag => semver.eq(tag, result[1]))
128 | if (!isNext) return result[1]
129 |
130 | if (!previousVersion) return null
131 |
132 | const previousResult = previousVersion.match(/ <\/a>/)
133 | return previousResult[1]
134 | }
135 |
136 | function getShortHash(hash, repository) {
137 | if (!hash) return null
138 |
139 | const shortHash = hash.slice(0, 7)
140 |
141 | if (isEmpty(repository) || !repository.url) return shortHash
142 |
143 | return `[${shortHash}](${repository.url}/commit/${hash})`
144 | }
145 |
146 | const ISSUE_REGEXP = /#{1}(\d+)/gm
147 |
148 | function autolink(message, repository) {
149 | if (!message) return null
150 |
151 | if (isEmpty(repository) || !repository.bugsUrl) return message
152 |
153 | const matches = message.match(ISSUE_REGEXP)
154 | if (!matches) return message
155 |
156 | return message.replace(ISSUE_REGEXP, `[#$1](${repository.bugsUrl}/$1)`)
157 | }
158 |
159 | module.exports = {
160 | buildMarkdownFile,
161 | matchVersionBreakpoint,
162 | getShortHash,
163 | autolink,
164 | getLatestVersion,
165 | }
166 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-markdown/src/index.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const fs = require('fs')
3 | const { Writable, Readable } = require('stream')
4 |
5 | const {
6 | buildMarkdownFile,
7 | matchVersionBreakpoint,
8 | autolink,
9 | getShortHash,
10 | } = require('./index')
11 |
12 | describe('Markdown converter', () => {
13 | it('should generate full changelog into markdown from scratch', async () => {
14 | fs.writeFile = jest.fn((path, content, cb) => cb(null, 'done'))
15 |
16 | const changelog = {
17 | meta: {
18 | repository: {
19 | type: 'github',
20 | domain: 'github.com',
21 | user: 'frinyvonnick',
22 | project: 'gitmoji-changelog',
23 | url: 'https://github.com/frinyvonnick/gitmoji-changelog',
24 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
25 | },
26 | },
27 | changes: [
28 | {
29 | version: 'next',
30 | groups: [
31 | {
32 | group: 'changed',
33 | label: 'Changed',
34 | commits: [
35 | {
36 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c',
37 | author: 'John Doe',
38 | date: '2018-08-28T10:07:00+02:00',
39 | subject: ':recycle: Upgrade brand new feature',
40 | emoji: '♻️',
41 | message: 'Upgrade brand new feature',
42 | body: 'Waouh this is awesome 3',
43 | siblings: [],
44 | },
45 | ],
46 | },
47 | ],
48 | },
49 | {
50 | version: '1.0.0',
51 | date: '2018-08-28',
52 | groups: [
53 | {
54 | group: 'added',
55 | label: 'Added',
56 | commits: [
57 | {
58 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23f',
59 | author: 'John Doe',
60 | date: '2018-08-28T10:06:00+02:00',
61 | subject: ':sparkles: Upgrade brand new feature',
62 | emoji: '✨',
63 | message: 'Upgrade brand new feature',
64 | body: 'Waouh this is awesome 2',
65 | siblings: [],
66 | },
67 | ],
68 | },
69 | ],
70 | },
71 | ],
72 | }
73 |
74 | await buildMarkdownFile(changelog, { mode: 'init', output: './CHANGELOG.md' })
75 |
76 | expect(fs.writeFile).toHaveBeenCalledTimes(1)
77 | expect(fs.writeFile.mock.calls[0][0]).toBe('./CHANGELOG.md')
78 | expect(fs.writeFile.mock.calls[0][1]).toEqual(`# Changelog
79 |
80 |
81 | ## next
82 |
83 | ### Changed
84 |
85 | - ♻️ Upgrade brand new feature [[c40ee86](https://github.com/frinyvonnick/gitmoji-changelog/commit/c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c)]
86 |
87 |
88 |
89 | ## 1.0.0 (2018-08-28)
90 |
91 | ### Added
92 |
93 | - ✨ Upgrade brand new feature [[c40ee86](https://github.com/frinyvonnick/gitmoji-changelog/commit/c40ee8669ba7ea5151adc2942fa8a7fc98d9e23f)]
94 |
95 |
96 | `)
97 | })
98 |
99 | it('should generate full changelog into markdown from scratch with author', async () => {
100 | fs.writeFile = jest.fn((path, content, cb) => cb(null, 'done'))
101 |
102 | const changelog = {
103 | meta: {
104 | repository: {
105 | type: 'github',
106 | domain: 'github.com',
107 | user: 'frinyvonnick',
108 | project: 'gitmoji-changelog',
109 | url: 'https://github.com/frinyvonnick/gitmoji-changelog',
110 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
111 | },
112 | },
113 | changes: [
114 | {
115 | version: 'next',
116 | groups: [
117 | {
118 | group: 'changed',
119 | label: 'Changed',
120 | commits: [
121 | {
122 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c',
123 | author: 'John Doe',
124 | date: '2018-08-28T10:07:00+02:00',
125 | subject: ':recycle: Upgrade brand new feature',
126 | emoji: '♻️',
127 | message: 'Upgrade brand new feature',
128 | body: 'Waouh this is awesome 3',
129 | siblings: [],
130 | },
131 | ],
132 | },
133 | ],
134 | },
135 | ],
136 | }
137 |
138 | await buildMarkdownFile(changelog, { mode: 'init', output: './CHANGELOG.md', author: true })
139 |
140 | expect(fs.writeFile).toHaveBeenCalledTimes(1)
141 | expect(fs.writeFile.mock.calls[0][0]).toBe('./CHANGELOG.md')
142 | expect(fs.writeFile.mock.calls[0][1]).toEqual(`# Changelog
143 |
144 |
145 | ## next
146 |
147 | ### Changed
148 |
149 | - ♻️ Upgrade brand new feature [[c40ee86](https://github.com/frinyvonnick/gitmoji-changelog/commit/c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c)] (by John Doe)
150 |
151 |
152 | `)
153 | })
154 |
155 | it('should generate incremental markdown changelog', async () => {
156 | let result
157 | fs.createWriteStream = jest.fn(() => new Writable({
158 | write(chunk, encoding, callback) {
159 | result = chunk.toString()
160 | callback()
161 | this.emit('close')
162 | },
163 | }))
164 | fs.createReadStream = jest.fn(() => {
165 | let index = 0
166 | return new Readable({
167 | read() {
168 | if (index === 0) {
169 | this.push(`# Changelog
170 | > Custom header
171 |
172 |
173 | ## 1.0.0
174 | I am the last version
175 | `)
176 | index += 1
177 | return
178 | }
179 |
180 | this.push(null)
181 | },
182 | })
183 | })
184 | fs.rename = jest.fn((fileA, fileB, cb) => cb(null, 'done'))
185 |
186 | const changelog = {
187 | meta: {
188 | repository: {
189 | type: 'github',
190 | domain: 'github.com',
191 | user: 'frinyvonnick',
192 | project: 'gitmoji-changelog',
193 | url: 'https://github.com/frinyvonnick/gitmoji-changelog',
194 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
195 | },
196 | lastVersion: '1.0.0',
197 | },
198 | changes: [
199 | {
200 | version: 'next',
201 | groups: [
202 | {
203 | group: 'changed',
204 | label: 'Changed',
205 | commits: [
206 | {
207 | hash: 'c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c',
208 | author: 'John Doe',
209 | date: '2018-08-28T10:07:00+02:00',
210 | subject: ':recycle: Upgrade brand new feature',
211 | emoji: '♻️',
212 | message: 'Upgrade brand new feature',
213 | body: 'Waouh this is awesome 3',
214 | siblings: [],
215 | },
216 | ],
217 | },
218 | ],
219 | },
220 | ],
221 | }
222 |
223 | await buildMarkdownFile(changelog, { release: 'next', mode: 'incremental', output: './CHANGELOG.md' })
224 |
225 | expect(result).toEqual(`# Changelog
226 | > Custom header
227 |
228 |
229 | ## next
230 |
231 | ### Changed
232 |
233 | - ♻️ Upgrade brand new feature [[c40ee86](https://github.com/frinyvonnick/gitmoji-changelog/commit/c40ee8669ba7ea5151adc2942fa8a7fc98d9e23c)]
234 |
235 |
236 |
237 | ## 1.0.0
238 | I am the last version
239 | `)
240 | })
241 | })
242 |
243 | describe('getHashUrl', () => {
244 | it('should return null if no hash given', () => {
245 | const result = getShortHash()
246 | expect(result).toBeNull()
247 | })
248 |
249 | it('should return short hash if no gitInfo given', () => {
250 | const result = getShortHash('xxxxxxxxxxxxxxxxx')
251 | expect(result).toBe('xxxxxxx')
252 | })
253 |
254 | it('should return short hash markdown with repo url', () => {
255 | const result = getShortHash('xxxxxxxxxxxxxxxxx', {
256 | url: 'https://github.com/frinyvonnick/gitmoji-changelog',
257 | })
258 | expect(result).toBe('[xxxxxxx](https://github.com/frinyvonnick/gitmoji-changelog/commit/xxxxxxxxxxxxxxxxx)')
259 | })
260 | })
261 |
262 | describe('autolink', () => {
263 | it('should return null if no message given', () => {
264 | const result = autolink()
265 | expect(result).toBeNull()
266 | })
267 |
268 | it('should return the message if no gitInfo given', () => {
269 | const result = autolink('message without gitinfo')
270 | expect(result).toBe('message without gitinfo')
271 | })
272 |
273 | it('should return the message if nothing match', () => {
274 | const result = autolink('nothing match in this message')
275 | expect(result).toBe('nothing match in this message')
276 | })
277 |
278 | it('should autolink with markdown one hashtag issue in message', () => {
279 | const result = autolink(':bug: fix issue #123', {
280 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
281 | })
282 | expect(result).toBe(':bug: fix issue [#123](https://github.com/frinyvonnick/gitmoji-changelog/issues/123)')
283 | })
284 |
285 | it('should autolink with markdown severals hashtag issues in message', () => {
286 | const result = autolink(':bug: fix issue #123 and #456', {
287 | bugsUrl: 'https://github.com/frinyvonnick/gitmoji-changelog/issues',
288 | })
289 | expect(result).toBe(':bug: fix issue [#123](https://github.com/frinyvonnick/gitmoji-changelog/issues/123) and [#456](https://github.com/frinyvonnick/gitmoji-changelog/issues/456)')
290 | })
291 | })
292 |
293 | describe('matchVersionBreakpoint', () => {
294 | it('should return true if match with the given version breakpoint', () => {
295 | const result = matchVersionBreakpoint(' ', '1.0.0')
296 | expect(result).toBe(true)
297 | })
298 |
299 | it('should return true if match with any version breakpoint', () => {
300 | const result = matchVersionBreakpoint(' ')
301 | expect(result).toBe(true)
302 | })
303 |
304 | it('should return false if no match with a version breakpoint', () => {
305 | const result = matchVersionBreakpoint('hello world', '1.0.0')
306 | expect(result).toBe(false)
307 | })
308 | })
309 |
--------------------------------------------------------------------------------
/packages/gitmoji-changelog-markdown/src/template.md:
--------------------------------------------------------------------------------
1 | {{#each changelog}}
2 |
3 | ## {{version}}{{#if date}} ({{date}}){{/if}}
4 |
5 | {{#each groups}}
6 | ### {{label}}
7 |
8 | {{#each commits}}
9 | - {{emoji}} {{message}} [{{hash}}]{{#if author}} (by {{author}}){{/if}}
10 | {{#each siblings}}
11 | * {{emoji}} {{message}} ({{hash}})
12 | {{/each}}
13 | {{/each}}
14 |
15 | {{/each}}
16 |
17 | {{/each}}
18 |
--------------------------------------------------------------------------------