round14tech-fastlane-plugin-semantic_release 1.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a44dd8584a29a30b4a37f07f3944770a63fc3e1989154e2301a62c41b197c1c2
4
+ data.tar.gz: cf3b381c668ebf33d008b21e7d7901d27940ec86c63a791b285d6cf4024e1c2c
5
+ SHA512:
6
+ metadata.gz: f0a338632f66dba8757712b96e047402bbfff9a1f3ea432d11112246633255bf3c3fc5e29d1a560fbf1c4d774a59f0bc7d1b9b960fed8d0c2e2bbdcadc49cd05
7
+ data.tar.gz: 2d32cb1e2edc850575b5e14d96bef24e227ad2f009fbf52384ca1d21687e8cee220bf0fb3ae4dc80070313aceff8fe864309ed4fc9ffeb01a56ee46b47ba5bb5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Jiří Otáhal <xotahal@gmail.com>
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.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # semantic_release plugin for `fastlane`
2
+
3
+ [![CircleCI](https://circleci.com/gh/xotahal/fastlane-plugin-semantic_release.svg?style=svg)](https://circleci.com/gh/xotahal/fastlane-plugin-semantic_release) [![License](https://img.shields.io/github/license/SiarheiFedartsou/fastlane-plugin-versioning.svg)](https://github.com/SiarheiFedartsou/fastlane-plugin-versioning/blob/master/LICENSE) [![Gem Version](https://badge.fury.io/rb/fastlane-plugin-semantic_release.svg)](https://badge.fury.io/rb/fastlane-plugin-semantic_release) [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-versioning)
4
+
5
+ ## Getting Started
6
+
7
+ ```
8
+ fastlane add_plugin semantic_release
9
+ ```
10
+
11
+ ## About
12
+
13
+ Automated version managment and generator of release notes. Inspired by [semantic-release](https://github.com/semantic-release/semantic-release) for npm packages. Based on [conventional commits](https://www.conventionalcommits.org/).
14
+
15
+ ### Articles
16
+
17
+ [Semantic Release for Fastlane](https://medium.com/@xotahal/semantic-release-for-fastlane-781df4cf5888?source=friends_link&sk=5c02e32daca7a68539e27e0e1bac1092) @ Medium - By Jiri Otahal
18
+
19
+ ## Available Actions
20
+
21
+ ### `conventional_changelog`
22
+
23
+ - parses all commits since last version
24
+ - groups those commits by their type (fix, feat, docs, refactor, chore, etc)
25
+ - and creates formated release notes either in markdown or in slack format
26
+
27
+ Available parameters:
28
+
29
+ - `format: 'slack|markdown|plain'` (defaults to `markdown`). This formats the changelog for the destination you need. If you're using this for TestFlight changelogs, we suggest using the `plain` option
30
+ - `title: 'My Title'` - is appended to the release notes title, "1.1.8 My Title (YYYY-MM-DD)"
31
+ - `display_title: true|false` (defaults to true) - allows you to hide the entire first line of the changelog
32
+ - `display_links: true|false` (defaults to true) - allows you to hide links to commits from your changelog
33
+ - `commit_url: 'https://github.com/username/repository/commit'` - prepended to the commit ID to build usable links
34
+ - View other options by searching for `available_options` in `conventional_changelog.rb`
35
+
36
+ Example:
37
+
38
+ ```
39
+ notes = conventional_changelog(format: 'slack', title: 'Android Alpha')
40
+ ```
41
+
42
+ <img src="https://raw.githubusercontent.com/xotahal/fastlane-plugin-semantic_release/master/docs/Changelog.png" />
43
+
44
+ ### `analyze_commits`
45
+
46
+ - analyzes your git history
47
+ - finds last tag on current branch (for example ios/beta/1.3.2)
48
+ - parses the last version from tag (1.3.2)
49
+ - gets all commits since this tag
50
+ - analyzes subject of every single commit and increases version number if there is a need (check conventional commit rules)
51
+ - if next version number is higher then last version number it will recommend you to release this version
52
+
53
+ Options:
54
+
55
+ - `ignore_scopes: ['android','windows']`: allows you to ignore any commits which include a given scope, like this one: `feat(android): add functionality not relevant to the release we are producing`
56
+
57
+ Example usage:
58
+
59
+ ```
60
+ isReleasable = analyze_commits(match: 'ios/beta*')
61
+ ```
62
+
63
+ It provides these variables in `lane_context`.
64
+
65
+ ```
66
+ ['RELEASE_ANALYZED', 'True if commits were analyzed.'],
67
+ ['RELEASE_IS_NEXT_VERSION_HIGHER', 'True if next version is higher then last version'],
68
+ ['RELEASE_LAST_TAG_HASH', 'Hash of commit that is tagged as a last version'],
69
+ ['RELEASE_LAST_VERSION', 'Last version number - parsed from last tag.'],
70
+ ['RELEASE_NEXT_MAJOR_VERSION', 'Major number of the next version'],
71
+ ['RELEASE_NEXT_MINOR_VERSION', 'Minor number of the next version'],
72
+ ['RELEASE_NEXT_PATCH_VERSION', 'Patch number of the next version'],
73
+ ['RELEASE_NEXT_VERSION', 'Next version string in format (major.minor.patch)'],
74
+ ```
75
+
76
+ And you can access these like this:
77
+
78
+ `next_version = lane_context[SharedValues::RELEASE_NEXT_VERSION]`
79
+
80
+ <img src="https://raw.githubusercontent.com/xotahal/fastlane-plugin-semantic_release/master/docs/Analyze.png" />
81
+
82
+ ## Tests
83
+
84
+ To run the test suite (contained in `./spec`), call `bundle exec rake`
85
+
86
+ ## Questions
87
+
88
+ If you need anything ping us on [twitter](http://bit.ly/t-xotahal).
89
+
90
+ | Jiri Otahal |
91
+ | -------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | [<img src="https://avatars3.githubusercontent.com/u/3531955?v=4" width="100px;" style="border-radius:50px"/>](http://bit.ly/t-xotahal) |
@@ -0,0 +1,400 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/semantic_release_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ RELEASE_ANALYZED = :RELEASE_ANALYZED
8
+ RELEASE_IS_NEXT_VERSION_HIGHER = :RELEASE_IS_NEXT_VERSION_HIGHER
9
+ RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH = :RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH
10
+ RELEASE_LAST_TAG_HASH = :RELEASE_LAST_TAG_HASH
11
+ RELEASE_LAST_VERSION = :RELEASE_LAST_VERSION
12
+ RELEASE_NEXT_MAJOR_VERSION = :RELEASE_NEXT_MAJOR_VERSION
13
+ RELEASE_NEXT_MINOR_VERSION = :RELEASE_NEXT_MINOR_VERSION
14
+ RELEASE_NEXT_PATCH_VERSION = :RELEASE_NEXT_PATCH_VERSION
15
+ RELEASE_NEXT_VERSION = :RELEASE_NEXT_VERSION
16
+ RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION = :RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION
17
+ CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN = :CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN
18
+ end
19
+
20
+ class AnalyzeCommitsAction < Action
21
+ def self.get_last_tag(params)
22
+ # Try to find the tag
23
+ command = "git describe --tags --match=#{params[:match]}"
24
+ Actions.sh(command, log: params[:debug])
25
+ rescue
26
+ UI.message("Tag was not found for match pattern - #{params[:match]}")
27
+ ''
28
+ end
29
+
30
+ def self.get_last_tag_hash(params)
31
+ command = "git rev-list -n 1 refs/tags/#{params[:tag_name]}"
32
+ Actions.sh(command, log: params[:debug]).chomp
33
+ end
34
+
35
+ def self.get_commits_from_hash(params)
36
+ commits = Helper::SemanticReleaseHelper.git_log(
37
+ pretty: '%s|%b|>',
38
+ start: params[:hash],
39
+ debug: params[:debug]
40
+ )
41
+ commits.split("|>")
42
+ end
43
+
44
+ def self.get_beginning_of_next_sprint(params)
45
+ # command to get first commit
46
+ git_command = "git rev-list --max-parents=0 HEAD"
47
+
48
+ tag = get_last_tag(match: params[:match], debug: params[:debug])
49
+
50
+ # if tag doesn't exist it get's first commit or fallback tag (v*.*.*)
51
+ if tag.empty?
52
+ UI.message("It couldn't match tag for #{params[:match]}. Check if first commit can be taken as a beginning of next release")
53
+ # If there is no tag found we taking the first commit of current branch
54
+ hash_lines = Actions.sh("#{git_command} | wc -l", log: params[:debug]).chomp
55
+
56
+ if hash_lines.to_i == 1
57
+ UI.message("First commit of the branch is taken as a begining of next release")
58
+ return {
59
+ # here we know this command will return 1 line
60
+ hash: Actions.sh(git_command, log: params[:debug]).chomp
61
+ }
62
+ end
63
+
64
+ unless params[:prevent_tag_fallback]
65
+ # neither matched tag and first hash could be used - as fallback we try vX.Y.Z
66
+ UI.message("It couldn't match tag for #{params[:match]} and couldn't use first commit. Check if tag vX.Y.Z can be taken as a begining of next release")
67
+ tag = get_last_tag(match: "v*", debug: params[:debug])
68
+ end
69
+
70
+ # even fallback tag doesn't work
71
+ if tag.empty?
72
+ return false
73
+ end
74
+ end
75
+
76
+ # Tag's format is v2.3.4-5-g7685948
77
+ # See git describe man page for more info
78
+ # It can be also v2.3.4-5 if there is no commit after tag
79
+ tag_name = tag
80
+ if tag.split('-').length >= 3
81
+ tag_name = tag.split('-')[0...-2].join('-').strip
82
+ end
83
+ parsed_version = tag_name.match(params[:tag_version_match])
84
+
85
+ if parsed_version.nil?
86
+ UI.user_error!("Error while parsing version from tag #{tag_name} by using tag_version_match - #{params[:tag_version_match]}. Please check if the tag contains version as you expect and if you are using single brackets for tag_version_match parameter.")
87
+ end
88
+
89
+ version = parsed_version[0]
90
+ # Get a hash of last version tag
91
+ hash = get_last_tag_hash(
92
+ tag_name: tag_name,
93
+ debug: params[:debug]
94
+ )
95
+
96
+ UI.message("Found a tag #{tag_name} associated with version #{version}")
97
+
98
+ return {
99
+ hash: hash,
100
+ version: version
101
+ }
102
+ end
103
+
104
+ def self.is_releasable(params)
105
+ # Hash of the commit where is the last version
106
+ beginning = get_beginning_of_next_sprint(params)
107
+
108
+ unless beginning
109
+ UI.error('It could not find a begining of this sprint. How to fix this:')
110
+ UI.error('-- ensure there is only one commit with --max-parents=0 (this command should return one line: "git rev-list --max-parents=0 HEAD")')
111
+ UI.error('-- tell us explicitely where the release starts by adding tag like this: vX.Y.Z (where X.Y.Z is version from which it starts computing next version number)')
112
+ return false
113
+ end
114
+
115
+ # Default last version
116
+ version = beginning[:version] || '0.0.0'
117
+ # If the tag is not found we are taking HEAD as reference
118
+ hash = beginning[:hash] || 'HEAD'
119
+
120
+ # converts last version string to the int numbers
121
+ next_major = (version.split('.')[0] || 0).to_i
122
+ next_minor = (version.split('.')[1] || 0).to_i
123
+ next_patch = (version.split('.')[2] || 0).to_i
124
+
125
+ is_next_version_compatible_with_codepush = true
126
+
127
+ # Get commits log between last version and head
128
+ splitted = get_commits_from_hash(
129
+ hash: hash,
130
+ debug: params[:debug]
131
+ )
132
+
133
+ UI.message("Found #{splitted.length} commits since last release")
134
+ releases = params[:releases]
135
+
136
+ format_pattern = lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN]
137
+ splitted.each do |line|
138
+ parts = line.split("|")
139
+ subject = parts[0].to_s.strip
140
+ # conventional commits are in format
141
+ # type: subject (fix: app crash - for example)
142
+ commit = Helper::SemanticReleaseHelper.parse_commit(
143
+ commit_subject: subject,
144
+ commit_body: parts[1],
145
+ releases: releases,
146
+ pattern: format_pattern
147
+ )
148
+
149
+ next if Helper::SemanticReleaseHelper.should_exclude_commit(
150
+ commit_scope: commit[:scope],
151
+ include_scopes: params[:include_scopes],
152
+ ignore_scopes: params[:ignore_scopes]
153
+ )
154
+
155
+ if commit[:release] == "major" || commit[:is_breaking_change]
156
+ next_major += 1
157
+ next_minor = 0
158
+ next_patch = 0
159
+ elsif commit[:release] == "minor"
160
+ next_minor += 1
161
+ next_patch = 0
162
+ elsif commit[:release] == "patch"
163
+ next_patch += 1
164
+ end
165
+
166
+ unless commit[:is_codepush_friendly]
167
+ is_next_version_compatible_with_codepush = false
168
+ end
169
+
170
+ next_version = "#{next_major}.#{next_minor}.#{next_patch}"
171
+ UI.message("#{next_version}: #{subject}") if params[:show_version_path]
172
+ end
173
+
174
+ next_version = "#{next_major}.#{next_minor}.#{next_patch}"
175
+
176
+ is_next_version_releasable = Helper::SemanticReleaseHelper.semver_gt(next_version, version)
177
+
178
+ Actions.lane_context[SharedValues::RELEASE_ANALYZED] = true
179
+ Actions.lane_context[SharedValues::RELEASE_IS_NEXT_VERSION_HIGHER] = is_next_version_releasable
180
+ Actions.lane_context[SharedValues::RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH] = is_next_version_compatible_with_codepush
181
+ # Last release analysis
182
+ Actions.lane_context[SharedValues::RELEASE_LAST_TAG_HASH] = hash
183
+ Actions.lane_context[SharedValues::RELEASE_LAST_VERSION] = version
184
+ # Next release analysis
185
+ Actions.lane_context[SharedValues::RELEASE_NEXT_MAJOR_VERSION] = next_major
186
+ Actions.lane_context[SharedValues::RELEASE_NEXT_MINOR_VERSION] = next_minor
187
+ Actions.lane_context[SharedValues::RELEASE_NEXT_PATCH_VERSION] = next_patch
188
+ Actions.lane_context[SharedValues::RELEASE_NEXT_VERSION] = next_version
189
+
190
+ success_message = "Next version (#{next_version}) is higher than last version (#{version}). This version should be released."
191
+ UI.success(success_message) if is_next_version_releasable
192
+
193
+ is_next_version_releasable
194
+ end
195
+
196
+ def self.is_codepush_friendly(params)
197
+ git_command = "git rev-list --max-parents=0 HEAD"
198
+ # Begining of the branch is taken for codepush analysis
199
+ hash_lines = Actions.sh("#{git_command} | wc -l", log: params[:debug]).chomp
200
+ hash = Actions.sh(git_command, log: params[:debug]).chomp
201
+ next_major = 0
202
+ next_minor = 0
203
+ next_patch = 0
204
+ last_incompatible_codepush_version = '0.0.0'
205
+
206
+ if hash_lines.to_i > 1
207
+ UI.error("#{git_command} resulted to more than 1 hash")
208
+ UI.error('This usualy happens when you pull only part of a git history. Check out how you pull the repo! "git fetch" should be enough.')
209
+ Actions.sh(git_command, log: true).chomp
210
+ return false
211
+ end
212
+
213
+ # Get commits log between last version and head
214
+ splitted = get_commits_from_hash(
215
+ hash: hash,
216
+ debug: params[:debug]
217
+ )
218
+ releases = params[:releases]
219
+ codepush_friendly = params[:codepush_friendly]
220
+
221
+ format_pattern = lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN]
222
+ splitted.each do |line|
223
+ # conventional commits are in format
224
+ # type: subject (fix: app crash - for example)
225
+ commit = Helper::SemanticReleaseHelper.parse_commit(
226
+ commit_subject: line.split("|")[0],
227
+ commit_body: line.split("|")[1],
228
+ releases: releases,
229
+ pattern: format_pattern,
230
+ codepush_friendly: codepush_friendly
231
+ )
232
+
233
+ next if Helper::SemanticReleaseHelper.should_exclude_commit(
234
+ commit_scope: commit[:scope],
235
+ include_scopes: params[:include_scopes],
236
+ ignore_scopes: params[:ignore_scopes]
237
+ )
238
+
239
+ if commit[:release] == "major" || commit[:is_breaking_change]
240
+ next_major += 1
241
+ next_minor = 0
242
+ next_patch = 0
243
+ elsif commit[:release] == "minor"
244
+ next_minor += 1
245
+ next_patch = 0
246
+ elsif commit[:release] == "patch"
247
+ next_patch += 1
248
+ end
249
+
250
+ unless commit[:is_codepush_friendly]
251
+ last_incompatible_codepush_version = "#{next_major}.#{next_minor}.#{next_patch}"
252
+ end
253
+ end
254
+
255
+ Actions.lane_context[SharedValues::RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION] = last_incompatible_codepush_version
256
+ end
257
+
258
+ def self.run(params)
259
+ is_next_version_releasable = is_releasable(params)
260
+ is_codepush_friendly(params)
261
+
262
+ is_next_version_releasable
263
+ end
264
+
265
+ #####################################################
266
+ # @!group Documentation
267
+ #####################################################
268
+
269
+ def self.description
270
+ "Finds a tag of last release and determinates version of next release"
271
+ end
272
+
273
+ def self.details
274
+ "This action will find a last release tag and analyze all commits since the tag. It uses conventional commits. Every time when commit is marked as fix or feat it will increase patch or minor number (you can setup this default behaviour). After all it will suggest if the version should be released or not."
275
+ end
276
+
277
+ def self.available_options
278
+ # Define all options your action supports.
279
+
280
+ # Below a few examples
281
+ [
282
+ FastlaneCore::ConfigItem.new(
283
+ key: :match,
284
+ description: "Match parameter of git describe. See man page of git describe for more info",
285
+ verify_block: proc do |value|
286
+ UI.user_error!("No match for analyze_commits action given, pass using `match: 'expr'`") unless value && !value.empty?
287
+ end
288
+ ),
289
+ FastlaneCore::ConfigItem.new(
290
+ key: :commit_format,
291
+ description: "The commit format to apply. Presets are 'default' or 'angular', or you can provide your own Regexp. Note: the supplied regex _must_ have 4 capture groups, in order: type, scope, has_exclamation_mark, and subject",
292
+ default_value: "default",
293
+ is_string: false,
294
+ verify_block: proc do |value|
295
+ case value
296
+ when String
297
+ unless Helper::SemanticReleaseHelper.format_patterns.key?(value)
298
+ UI.user_error!("Invalid format preset: #{value}")
299
+ end
300
+
301
+ pattern = Helper::SemanticReleaseHelper.format_patterns[value]
302
+ when Regexp
303
+ pattern = value
304
+ else
305
+ UI.user_error!("Invalid option type: #{value.inspect}")
306
+ end
307
+ Actions.lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN] = pattern
308
+ end
309
+ ),
310
+ FastlaneCore::ConfigItem.new(
311
+ key: :releases,
312
+ description: "Map types of commit to release (major, minor, patch)",
313
+ default_value: { fix: "patch", feat: "minor" },
314
+ type: Hash
315
+ ),
316
+ FastlaneCore::ConfigItem.new(
317
+ key: :codepush_friendly,
318
+ description: "These types are consider as codepush friendly automatically",
319
+ default_value: ["chore", "test", "docs"],
320
+ type: Array,
321
+ optional: true
322
+ ),
323
+ FastlaneCore::ConfigItem.new(
324
+ key: :tag_version_match,
325
+ description: "To parse version number from tag name",
326
+ default_value: '\d+\.\d+\.\d+'
327
+ ),
328
+ FastlaneCore::ConfigItem.new(
329
+ key: :prevent_tag_fallback,
330
+ description: "Prevent tag from falling back to vX.Y.Z when there is no match",
331
+ default_value: false,
332
+ type: Boolean,
333
+ optional: true
334
+ ),
335
+ FastlaneCore::ConfigItem.new(
336
+ key: :include_scopes,
337
+ description: "To only include certain scopes when calculating releases",
338
+ default_value: [],
339
+ type: Array,
340
+ optional: true
341
+ ),
342
+ FastlaneCore::ConfigItem.new(
343
+ key: :ignore_scopes,
344
+ description: "To ignore certain scopes when calculating releases",
345
+ default_value: [],
346
+ type: Array,
347
+ optional: true
348
+ ),
349
+ FastlaneCore::ConfigItem.new(
350
+ key: :show_version_path,
351
+ description: "True if you want to print out the version calculated for each commit",
352
+ default_value: true,
353
+ type: Boolean,
354
+ optional: true
355
+ ),
356
+ FastlaneCore::ConfigItem.new(
357
+ key: :debug,
358
+ description: "True if you want to log out a debug info",
359
+ default_value: false,
360
+ type: Boolean,
361
+ optional: true
362
+ )
363
+ ]
364
+ end
365
+
366
+ def self.output
367
+ # Define the shared values you are going to provide
368
+ # Example
369
+ [
370
+ ['RELEASE_ANALYZED', 'True if commits were analyzed.'],
371
+ ['RELEASE_IS_NEXT_VERSION_HIGHER', 'True if next version is higher then last version'],
372
+ ['RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH', 'True if next version is compatible with codepush'],
373
+ ['RELEASE_LAST_TAG_HASH', 'Hash of commit that is tagged as a last version'],
374
+ ['RELEASE_LAST_VERSION', 'Last version number - parsed from last tag.'],
375
+ ['RELEASE_NEXT_MAJOR_VERSION', 'Major number of the next version'],
376
+ ['RELEASE_NEXT_MINOR_VERSION', 'Minor number of the next version'],
377
+ ['RELEASE_NEXT_PATCH_VERSION', 'Patch number of the next version'],
378
+ ['RELEASE_NEXT_VERSION', 'Next version string in format (major.minor.patch)'],
379
+ ['RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION', 'Last commit without codepush'],
380
+ ['CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN', 'The format pattern Regexp used to match commits (mainly for internal use)']
381
+ ]
382
+ end
383
+
384
+ def self.return_value
385
+ # If your method provides a return value, you can describe here what it does
386
+ "Returns true if the next version is higher then the last version"
387
+ end
388
+
389
+ def self.authors
390
+ # So no one will ever forget your contribution to fastlane :) You are awesome btw!
391
+ ["xotahal"]
392
+ end
393
+
394
+ def self.is_supported?(platform)
395
+ # you can do things like
396
+ true
397
+ end
398
+ end
399
+ end
400
+ end
@@ -0,0 +1,328 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/semantic_release_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ end
8
+
9
+ class ConventionalChangelogAction < Action
10
+ def self.get_commits_from_hash(params)
11
+ commits = Helper::SemanticReleaseHelper.git_log(
12
+ pretty: '%s|%b|%H|%h|%an|%at|>',
13
+ start: params[:hash],
14
+ debug: params[:debug]
15
+ )
16
+ commits.split("|>")
17
+ end
18
+
19
+ def self.run(params)
20
+ # Get next version number from shared values
21
+ analyzed = lane_context[SharedValues::RELEASE_ANALYZED]
22
+
23
+ # If analyze commits action was not run there will be no version in shared
24
+ # values. We need to run the action to get next version number
25
+ unless analyzed
26
+ UI.user_error!("Release hasn't been analyzed yet. Run analyze_commits action first please.")
27
+ # version = other_action.analyze_commits(match: params[:match])
28
+ end
29
+
30
+ last_tag_hash = lane_context[SharedValues::RELEASE_LAST_TAG_HASH]
31
+ version = lane_context[SharedValues::RELEASE_NEXT_VERSION]
32
+
33
+ # Get commits log between last version and head
34
+ commits = get_commits_from_hash(
35
+ hash: last_tag_hash,
36
+ debug: params[:debug]
37
+ )
38
+ parsed = parse_commits(commits, params)
39
+
40
+ commit_url = params[:commit_url]
41
+ format = params[:format]
42
+
43
+ result = note_builder(format, parsed, version, commit_url, params)
44
+
45
+ result
46
+ end
47
+
48
+ def self.note_builder(format, commits, version, commit_url, params)
49
+ sections = params[:sections]
50
+
51
+ result = ""
52
+
53
+ # Begining of release notes
54
+ if params[:display_title] == true
55
+ title = version
56
+ title += " #{params[:title]}" if params[:title]
57
+ title += " (#{Date.today})"
58
+
59
+ result = style_text(title, format, "title").to_s
60
+ result += "\n\n"
61
+ end
62
+
63
+ params[:order].each do |type|
64
+ # write section only if there is at least one commit
65
+ next if commits.none? { |commit| commit[:type] == type }
66
+
67
+ result += style_text(sections[type.to_sym], format, "heading").to_s
68
+ result += "\n"
69
+
70
+ commits.each do |commit|
71
+ next if commit[:type] != type || commit[:is_merge]
72
+
73
+ result += "-"
74
+
75
+ unless commit[:scope].nil?
76
+ formatted_text = style_text("#{commit[:scope]}:", format, "bold").to_s
77
+ result += " #{formatted_text}"
78
+ end
79
+
80
+ result += " #{commit[:subject]}"
81
+
82
+ if params[:display_links] == true
83
+ styled_link = build_commit_link(commit, commit_url, format).to_s
84
+ result += " (#{styled_link})"
85
+ end
86
+
87
+ if params[:display_author]
88
+ result += " - #{commit[:author_name]}"
89
+ end
90
+
91
+ result += "\n"
92
+ end
93
+ result += "\n"
94
+ end
95
+
96
+ if commits.any? { |commit| commit[:is_breaking_change] == true }
97
+ result += style_text("BREAKING CHANGES", format, "heading").to_s
98
+ result += "\n"
99
+
100
+ commits.each do |commit|
101
+ next unless commit[:is_breaking_change]
102
+ result += "- #{commit[:breaking_change]}" # This is the only unique part of this loop
103
+
104
+ if params[:display_links] == true
105
+ styled_link = build_commit_link(commit, commit_url, format).to_s
106
+ result += " (#{styled_link})"
107
+ end
108
+
109
+ if params[:display_author]
110
+ result += " - #{commit[:author_name]}"
111
+ end
112
+
113
+ result += "\n"
114
+ end
115
+
116
+ result += "\n"
117
+ end
118
+
119
+ # Trim any trailing newlines
120
+ result = result.rstrip!
121
+
122
+ result
123
+ end
124
+
125
+ def self.style_text(text, format, style)
126
+ # formats the text according to the style we're looking to use
127
+
128
+ # Skips all styling
129
+ case style
130
+ when "title"
131
+ if format == "markdown"
132
+ "# #{text}"
133
+ elsif format == "slack"
134
+ "*#{text}*"
135
+ else
136
+ text
137
+ end
138
+ when "heading"
139
+ if format == "markdown"
140
+ "### #{text}"
141
+ elsif format == "slack"
142
+ "*#{text}*"
143
+ else
144
+ "#{text}:"
145
+ end
146
+ when "bold"
147
+ if format == "markdown"
148
+ "**#{text}**"
149
+ elsif format == "slack"
150
+ "*#{text}*"
151
+ else
152
+ text
153
+ end
154
+ else
155
+ text # catchall, shouldn't be needed
156
+ end
157
+ end
158
+
159
+ def self.build_commit_link(commit, commit_url, format)
160
+ # formats the link according to the output format we need
161
+ short_hash = commit[:short_hash]
162
+ hash = commit[:hash]
163
+ url = "#{commit_url}/#{hash}"
164
+
165
+ case format
166
+ when "slack"
167
+ "<#{url}|#{short_hash}>"
168
+ when "markdown"
169
+ "[#{short_hash}](#{url})"
170
+ else
171
+ url
172
+ end
173
+ end
174
+
175
+ def self.parse_commits(commits, params)
176
+ parsed = []
177
+ # %s|%b|%H|%h|%an|%at
178
+ format_pattern = lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN]
179
+ commits.each do |line|
180
+ splitted = line.split("|")
181
+
182
+ commit = Helper::SemanticReleaseHelper.parse_commit(
183
+ commit_subject: splitted[0],
184
+ commit_body: splitted[1],
185
+ pattern: format_pattern
186
+ )
187
+
188
+ next if Helper::SemanticReleaseHelper.should_exclude_commit(
189
+ commit_scope: commit[:scope],
190
+ include_scopes: params[:include_scopes],
191
+ ignore_scopes: params[:ignore_scopes]
192
+ )
193
+
194
+ commit[:hash] = splitted[2]
195
+ commit[:short_hash] = splitted[3]
196
+ commit[:author_name] = splitted[4]
197
+ commit[:commit_date] = splitted[5]
198
+
199
+ parsed.push(commit)
200
+ end
201
+
202
+ parsed
203
+ end
204
+
205
+ #####################################################
206
+ # @!group Documentation
207
+ #####################################################
208
+
209
+ def self.description
210
+ "Get commits since last version and generates release notes"
211
+ end
212
+
213
+ def self.details
214
+ "Uses conventional commits. It groups commits by their types and generates release notes in markdown or slack format."
215
+ end
216
+
217
+ def self.available_options
218
+ # Define all options your action supports.
219
+
220
+ # Below a few examples
221
+ [
222
+ FastlaneCore::ConfigItem.new(
223
+ key: :format,
224
+ description: "You can use either markdown, slack or plain",
225
+ default_value: "markdown",
226
+ optional: true
227
+ ),
228
+ FastlaneCore::ConfigItem.new(
229
+ key: :title,
230
+ description: "Title for release notes",
231
+ optional: true
232
+ ),
233
+ FastlaneCore::ConfigItem.new(
234
+ key: :commit_url,
235
+ description: "Uses as a link to the commit",
236
+ optional: true
237
+ ),
238
+ FastlaneCore::ConfigItem.new(
239
+ key: :order,
240
+ description: "You can change the order of groups in release notes",
241
+ default_value: ["feat", "fix", "refactor", "perf", "chore", "test", "docs", "no_type"],
242
+ type: Array,
243
+ optional: true
244
+ ),
245
+ FastlaneCore::ConfigItem.new(
246
+ key: :sections,
247
+ description: "Map type to section title",
248
+ default_value: {
249
+ feat: "Features",
250
+ fix: "Bug fixes",
251
+ refactor: "Code refactoring",
252
+ perf: "Performance improvements",
253
+ chore: "Building system",
254
+ test: "Testing",
255
+ docs: "Documentation",
256
+ no_type: "Other work"
257
+ },
258
+ type: Hash,
259
+ optional: true
260
+ ),
261
+ FastlaneCore::ConfigItem.new(
262
+ key: :display_author,
263
+ description: "Whether you want to show the author of the commit",
264
+ default_value: false,
265
+ type: Boolean,
266
+ optional: true
267
+ ),
268
+ FastlaneCore::ConfigItem.new(
269
+ key: :display_title,
270
+ description: "Whether you want to hide the title/header with the version details at the top of the changelog",
271
+ default_value: true,
272
+ type: Boolean,
273
+ optional: true
274
+ ),
275
+ FastlaneCore::ConfigItem.new(
276
+ key: :display_links,
277
+ description: "Whether you want to display the links to commit IDs",
278
+ default_value: true,
279
+ type: Boolean,
280
+ optional: true
281
+ ),
282
+ FastlaneCore::ConfigItem.new(
283
+ key: :include_scopes,
284
+ description: "To only include certain scopes when calculating releases",
285
+ default_value: [],
286
+ type: Array,
287
+ optional: true
288
+ ),
289
+ FastlaneCore::ConfigItem.new(
290
+ key: :ignore_scopes,
291
+ description: "To ignore certain scopes when calculating releases",
292
+ default_value: [],
293
+ type: Array,
294
+ optional: true
295
+ ),
296
+ FastlaneCore::ConfigItem.new(
297
+ key: :debug,
298
+ description: "True if you want to log out a debug info",
299
+ default_value: false,
300
+ type: Boolean,
301
+ optional: true
302
+ )
303
+ ]
304
+ end
305
+
306
+ def self.output
307
+ # Define the shared values you are going to provide
308
+ # Example
309
+ []
310
+ end
311
+
312
+ def self.return_value
313
+ # If your method provides a return value, you can describe here what it does
314
+ "Returns generated release notes as a string"
315
+ end
316
+
317
+ def self.authors
318
+ # So no one will ever forget your contribution to fastlane :) You are awesome btw!
319
+ ["xotahal"]
320
+ end
321
+
322
+ def self.is_supported?(platform)
323
+ # you can do things like
324
+ true
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,118 @@
1
+ require 'fastlane_core/ui/ui'
2
+
3
+ module Fastlane
4
+ UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
5
+
6
+ module Helper
7
+ class SemanticReleaseHelper
8
+ def self.format_patterns
9
+ return {
10
+ "default" => /^(docs|fix|feat|chore|style|refactor|perf|test)(?:\((.*)\))?(!?)\: (.*)/,
11
+ "angular" => /^(\w*)(?:\((.*)\))?(): (.*)/
12
+ }
13
+ end
14
+
15
+ # class methods that you define here become available in your action
16
+ # as `Helper::SemanticReleaseHelper.your_method`
17
+ #
18
+ def self.git_log(params)
19
+ command = "git log --pretty='#{params[:pretty]}' --reverse #{params[:start]}..HEAD"
20
+ Actions.sh(command, log: params[:debug]).chomp
21
+ end
22
+
23
+ def self.should_exclude_commit(params)
24
+ commit_scope = params[:commit_scope]
25
+ scopes_to_include = params[:include_scopes]
26
+ scopes_to_ignore = params[:ignore_scopes]
27
+
28
+ unless scopes_to_include.empty?
29
+ return !scopes_to_include.include?(commit_scope)
30
+ end
31
+
32
+ unless commit_scope.nil?
33
+ return scopes_to_ignore.include?(commit_scope)
34
+ end
35
+ end
36
+
37
+ def self.parse_commit(params)
38
+ commit_subject = params[:commit_subject].to_s.strip
39
+ commit_body = params[:commit_body]
40
+ releases = params[:releases]
41
+ codepush_friendly = params[:codepush_friendly]
42
+ pattern = params[:pattern]
43
+ breaking_change_pattern = /BREAKING CHANGES?: (.*)/
44
+ codepush_pattern = /codepush?: (.*)/
45
+
46
+ matched = commit_subject.match(pattern)
47
+ result = {
48
+ is_valid: false,
49
+ subject: commit_subject,
50
+ is_merge: !(commit_subject =~ /^Merge/).nil?,
51
+ type: 'no_type'
52
+ }
53
+
54
+ unless matched.nil?
55
+ type = matched[1]
56
+ scope = matched[2]
57
+
58
+ result[:is_valid] = true
59
+ result[:type] = type
60
+ result[:scope] = scope
61
+ result[:has_exclamation_mark] = matched[3] == '!'
62
+ result[:subject] = matched[4]
63
+
64
+ unless releases.nil?
65
+ result[:release] = releases[type.to_sym]
66
+ end
67
+ unless codepush_friendly.nil?
68
+ result[:is_codepush_friendly] = codepush_friendly.include?(type)
69
+ end
70
+
71
+ unless commit_body.nil?
72
+ breaking_change_matched = commit_body.match(breaking_change_pattern)
73
+ codepush_matched = commit_body.match(codepush_pattern)
74
+
75
+ unless breaking_change_matched.nil?
76
+ result[:is_breaking_change] = true
77
+ result[:breaking_change] = breaking_change_matched[1]
78
+ end
79
+ unless codepush_matched.nil?
80
+ result[:is_codepush_friendly] = codepush_matched[1] == 'ok'
81
+ end
82
+ end
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ def self.semver_gt(first, second)
89
+ first_major = (first.split('.')[0] || 0).to_i
90
+ first_minor = (first.split('.')[1] || 0).to_i
91
+ first_patch = (first.split('.')[2] || 0).to_i
92
+
93
+ second_major = (second.split('.')[0] || 0).to_i
94
+ second_minor = (second.split('.')[1] || 0).to_i
95
+ second_patch = (second.split('.')[2] || 0).to_i
96
+
97
+ # Check if next version is higher then last version
98
+ if first_major > second_major
99
+ return true
100
+ elsif first_major == second_major
101
+ if first_minor > second_minor
102
+ return true
103
+ elsif first_minor == second_minor
104
+ if first_patch > second_patch
105
+ return true
106
+ end
107
+ end
108
+ end
109
+
110
+ return false
111
+ end
112
+
113
+ def self.semver_lt(first, second)
114
+ return !semver_gt(first, second)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1 @@
1
+ module Fastlane module SemanticRelease VERSION = "1.18.3" end end
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/semantic_release/version'
2
+
3
+ module Fastlane
4
+ module SemanticRelease
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::SemanticRelease.all_classes.each do |current|
15
+ require current
16
+ end
metadata ADDED
@@ -0,0 +1,175 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: round14tech-fastlane-plugin-semantic_release
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.18.3
5
+ platform: ruby
6
+ authors:
7
+ - Jiří Otáhal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec_junit_formatter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 0.49.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 0.49.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-require_tools
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: fastlane
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.117.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 2.117.1
139
+ description:
140
+ email: xotahal@gmail.com
141
+ executables: []
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - LICENSE
146
+ - README.md
147
+ - lib/fastlane/plugin/semantic_release.rb
148
+ - lib/fastlane/plugin/semantic_release/actions/analyze_commits.rb
149
+ - lib/fastlane/plugin/semantic_release/actions/conventional_changelog.rb
150
+ - lib/fastlane/plugin/semantic_release/helper/semantic_release_helper.rb
151
+ - lib/fastlane/plugin/semantic_release/version.rb
152
+ homepage: https://github.com/xotahal/fastlane-plugin-semantic_release
153
+ licenses:
154
+ - MIT
155
+ metadata: {}
156
+ post_install_message:
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '0'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubygems_version: 3.5.11
172
+ signing_key:
173
+ specification_version: 4
174
+ summary: Automated version managment and generator of release notes.
175
+ test_files: []