fastlane-plugin-semantic_release_workflow 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 36cb5c231daacc42fd1609343b0aa04fd9c1fddbd45e213aa312341d140107ab
4
+ data.tar.gz: d039e4542a1eedd3e7219a3a5ec9898ee552bd757085117e23f6d366df85dcdc
5
+ SHA512:
6
+ metadata.gz: 5637899f9827e5259f32429e3cd85e4368fe4eec1a363c9b16679e469463c5a656edac86993c07baabe685095c9e18438b4f0e2d38d406ea8e5f410578c63b25
7
+ data.tar.gz: 310581a7d7070d645ab7e7205c790f7ce13700fa90a5d46f52bb1eb9df252033d0700a6e5b4daadd89f2be72b6ad69da5f271af4c62916c0a53dea292e264321
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,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
@@ -0,0 +1,342 @@
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.is_releasable(params)
45
+ # Hash of the commit where is the last version
46
+ # If the tag is not found we are taking HEAD as reference
47
+ hash = 'HEAD'
48
+ # Default last version
49
+ version = '0.0.0'
50
+
51
+ tag = get_last_tag(
52
+ match: params[:match],
53
+ debug: params[:debug]
54
+ )
55
+
56
+ if tag.empty?
57
+ UI.message("First commit of the branch is taken as a begining of next release")
58
+ # If there is no tag found we taking the first commit of current branch
59
+ hash = Actions.sh('git rev-list --max-parents=0 HEAD', log: params[:debug]).chomp
60
+ else
61
+ # Tag's format is v2.3.4-5-g7685948
62
+ # See git describe man page for more info
63
+ tag_name = tag.split('-')[0...-2].join('-').strip
64
+ parsed_version = tag_name.match(params[:tag_version_match])
65
+
66
+ if parsed_version.nil?
67
+ 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.")
68
+ end
69
+
70
+ version = parsed_version[0]
71
+ # Get a hash of last version tag
72
+ hash = get_last_tag_hash(
73
+ tag_name: tag_name,
74
+ debug: params[:debug]
75
+ )
76
+
77
+ UI.message("Found a tag #{tag_name} associated with version #{version}")
78
+ end
79
+
80
+ # converts last version string to the int numbers
81
+ next_major = (version.split('.')[0] || 0).to_i
82
+ next_minor = (version.split('.')[1] || 0).to_i
83
+ next_patch = (version.split('.')[2] || 0).to_i
84
+
85
+ is_next_version_compatible_with_codepush = true
86
+
87
+ # Get commits log between last version and head
88
+ splitted = get_commits_from_hash(
89
+ hash: hash,
90
+ debug: params[:debug]
91
+ )
92
+
93
+ UI.message("Found #{splitted.length} commits since last release")
94
+ releases = params[:releases]
95
+
96
+ format_pattern = lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN]
97
+ splitted.each do |line|
98
+ parts = line.split("|")
99
+ subject = parts[0].strip
100
+ # conventional commits are in format
101
+ # type: subject (fix: app crash - for example)
102
+ commit = Helper::SemanticReleaseHelper.parse_commit(
103
+ commit_subject: subject,
104
+ commit_body: parts[1],
105
+ releases: releases,
106
+ pattern: format_pattern
107
+ )
108
+
109
+ unless commit[:scope].nil?
110
+ # if this commit has a scope, then we need to inspect to see if that is one of the scopes we're trying to exclude
111
+ scope = commit[:scope]
112
+ scopes_to_ignore = params[:ignore_scopes]
113
+ # if it is, we'll skip this commit when bumping versions
114
+ next if scopes_to_ignore.include?(scope) #=> true
115
+ end
116
+
117
+ if commit[:release] == "major" || commit[:is_breaking_change]
118
+ next_major += 1
119
+ next_minor = 0
120
+ next_patch = 0
121
+ elsif commit[:release] == "minor"
122
+ next_minor += 1
123
+ next_patch = 0
124
+ elsif commit[:release] == "patch"
125
+ next_patch += 1
126
+ end
127
+
128
+ unless commit[:is_codepush_friendly]
129
+ is_next_version_compatible_with_codepush = false
130
+ end
131
+
132
+ next_version = "#{next_major}.#{next_minor}.#{next_patch}"
133
+ UI.message("#{next_version}: #{subject}") if params[:show_version_path]
134
+ end
135
+
136
+ next_version = "#{next_major}.#{next_minor}.#{next_patch}"
137
+
138
+ is_next_version_releasable = Helper::SemanticReleaseHelper.semver_gt(next_version, version)
139
+
140
+ Actions.lane_context[SharedValues::RELEASE_ANALYZED] = true
141
+ Actions.lane_context[SharedValues::RELEASE_IS_NEXT_VERSION_HIGHER] = is_next_version_releasable
142
+ Actions.lane_context[SharedValues::RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH] = is_next_version_compatible_with_codepush
143
+ # Last release analysis
144
+ Actions.lane_context[SharedValues::RELEASE_LAST_TAG_HASH] = hash
145
+ Actions.lane_context[SharedValues::RELEASE_LAST_VERSION] = version
146
+ # Next release analysis
147
+ Actions.lane_context[SharedValues::RELEASE_NEXT_MAJOR_VERSION] = next_major
148
+ Actions.lane_context[SharedValues::RELEASE_NEXT_MINOR_VERSION] = next_minor
149
+ Actions.lane_context[SharedValues::RELEASE_NEXT_PATCH_VERSION] = next_patch
150
+ Actions.lane_context[SharedValues::RELEASE_NEXT_VERSION] = next_version
151
+
152
+ success_message = "Next version (#{next_version}) is higher than last version (#{version}). This version should be released."
153
+ UI.success(success_message) if is_next_version_releasable
154
+
155
+ is_next_version_releasable
156
+ end
157
+
158
+ def self.is_codepush_friendly(params)
159
+ git_command = 'git rev-list --max-parents=0 HEAD'
160
+ # Begining of the branch is taken for codepush analysis
161
+ hash_lines = Actions.sh("#{git_command} | wc -l", log: params[:debug]).chomp
162
+ hash = Actions.sh(git_command, log: params[:debug]).chomp
163
+ next_major = 0
164
+ next_minor = 0
165
+ next_patch = 0
166
+ last_incompatible_codepush_version = '0.0.0'
167
+
168
+ if hash_lines.to_i > 1
169
+ UI.error("#{git_command} resulted to more than 1 hash")
170
+ 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.')
171
+ Actions.sh(git_command, log: true).chomp
172
+ return false
173
+ end
174
+
175
+ # Get commits log between last version and head
176
+ splitted = get_commits_from_hash(
177
+ hash: hash,
178
+ debug: params[:debug]
179
+ )
180
+ releases = params[:releases]
181
+ codepush_friendly = params[:codepush_friendly]
182
+
183
+ format_pattern = lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN]
184
+ splitted.each do |line|
185
+ # conventional commits are in format
186
+ # type: subject (fix: app crash - for example)
187
+ commit = Helper::SemanticReleaseHelper.parse_commit(
188
+ commit_subject: line.split("|")[0],
189
+ commit_body: line.split("|")[1],
190
+ releases: releases,
191
+ pattern: format_pattern,
192
+ codepush_friendly: codepush_friendly
193
+ )
194
+
195
+ if commit[:release] == "major" || commit[:is_breaking_change]
196
+ next_major += 1
197
+ next_minor = 0
198
+ next_patch = 0
199
+ elsif commit[:release] == "minor"
200
+ next_minor += 1
201
+ next_patch = 0
202
+ elsif commit[:release] == "patch"
203
+ next_patch += 1
204
+ end
205
+
206
+ unless commit[:is_codepush_friendly]
207
+ last_incompatible_codepush_version = "#{next_major}.#{next_minor}.#{next_patch}"
208
+ end
209
+ end
210
+
211
+ Actions.lane_context[SharedValues::RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION] = last_incompatible_codepush_version
212
+ end
213
+
214
+ def self.run(params)
215
+ is_next_version_releasable = is_releasable(params)
216
+ is_codepush_friendly(params)
217
+
218
+ is_next_version_releasable
219
+ end
220
+
221
+ #####################################################
222
+ # @!group Documentation
223
+ #####################################################
224
+
225
+ def self.description
226
+ "Finds a tag of last release and determinates version of next release"
227
+ end
228
+
229
+ def self.details
230
+ "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."
231
+ end
232
+
233
+ def self.available_options
234
+ # Define all options your action supports.
235
+
236
+ # Below a few examples
237
+ [
238
+ FastlaneCore::ConfigItem.new(
239
+ key: :match,
240
+ description: "Match parameter of git describe. See man page of git describe for more info",
241
+ verify_block: proc do |value|
242
+ UI.user_error!("No match for analyze_commits action given, pass using `match: 'expr'`") unless value && !value.empty?
243
+ end
244
+ ),
245
+ FastlaneCore::ConfigItem.new(
246
+ key: :commit_format,
247
+ 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",
248
+ default_value: "default",
249
+ is_string: false,
250
+ verify_block: proc do |value|
251
+ case value
252
+ when String
253
+ unless Helper::SemanticReleaseHelper.format_patterns.key?(value)
254
+ UI.user_error!("Invalid format preset: #{value}")
255
+ end
256
+
257
+ pattern = Helper::SemanticReleaseHelper.format_patterns[value]
258
+ when Regexp
259
+ pattern = value
260
+ else
261
+ UI.user_error!("Invalid option type: #{value.inspect}")
262
+ end
263
+ Actions.lane_context[SharedValues::CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN] = pattern
264
+ end
265
+ ),
266
+ FastlaneCore::ConfigItem.new(
267
+ key: :releases,
268
+ description: "Map types of commit to release (major, minor, patch)",
269
+ default_value: { fix: "patch", feat: "minor" },
270
+ type: Hash
271
+ ),
272
+ FastlaneCore::ConfigItem.new(
273
+ key: :codepush_friendly,
274
+ description: "These types are consider as codepush friendly automatically",
275
+ default_value: ["chore", "test", "docs"],
276
+ type: Array,
277
+ optional: true
278
+ ),
279
+ FastlaneCore::ConfigItem.new(
280
+ key: :tag_version_match,
281
+ description: "To parse version number from tag name",
282
+ default_value: '\d+\.\d+\.\d+'
283
+ ),
284
+ FastlaneCore::ConfigItem.new(
285
+ key: :ignore_scopes,
286
+ description: "To ignore certain scopes when calculating releases",
287
+ default_value: [],
288
+ type: Array,
289
+ optional: true
290
+ ),
291
+ FastlaneCore::ConfigItem.new(
292
+ key: :show_version_path,
293
+ description: "True if you want to print out the version calculated for each commit",
294
+ default_value: true,
295
+ type: Boolean,
296
+ optional: true
297
+ ),
298
+ FastlaneCore::ConfigItem.new(
299
+ key: :debug,
300
+ description: "True if you want to log out a debug info",
301
+ default_value: false,
302
+ type: Boolean,
303
+ optional: true
304
+ )
305
+ ]
306
+ end
307
+
308
+ def self.output
309
+ # Define the shared values you are going to provide
310
+ # Example
311
+ [
312
+ ['RELEASE_ANALYZED', 'True if commits were analyzed.'],
313
+ ['RELEASE_IS_NEXT_VERSION_HIGHER', 'True if next version is higher then last version'],
314
+ ['RELEASE_IS_NEXT_VERSION_COMPATIBLE_WITH_CODEPUSH', 'True if next version is compatible with codepush'],
315
+ ['RELEASE_LAST_TAG_HASH', 'Hash of commit that is tagged as a last version'],
316
+ ['RELEASE_LAST_VERSION', 'Last version number - parsed from last tag.'],
317
+ ['RELEASE_NEXT_MAJOR_VERSION', 'Major number of the next version'],
318
+ ['RELEASE_NEXT_MINOR_VERSION', 'Minor number of the next version'],
319
+ ['RELEASE_NEXT_PATCH_VERSION', 'Patch number of the next version'],
320
+ ['RELEASE_NEXT_VERSION', 'Next version string in format (major.minor.patch)'],
321
+ ['RELEASE_LAST_INCOMPATIBLE_CODEPUSH_VERSION', 'Last commit without codepush'],
322
+ ['CONVENTIONAL_CHANGELOG_ACTION_FORMAT_PATTERN', 'The format pattern Regexp used to match commits (mainly for internal use)']
323
+ ]
324
+ end
325
+
326
+ def self.return_value
327
+ # If your method provides a return value, you can describe here what it does
328
+ "Returns true if the next version is higher then the last version"
329
+ end
330
+
331
+ def self.authors
332
+ # So no one will ever forget your contribution to fastlane :) You are awesome btw!
333
+ ["xotahal"]
334
+ end
335
+
336
+ def self.is_supported?(platform)
337
+ # you can do things like
338
+ true
339
+ end
340
+ end
341
+ end
342
+ end
@@ -0,0 +1,308 @@
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)
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)
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
+ commit[:hash] = splitted[2]
189
+ commit[:short_hash] = splitted[3]
190
+ commit[:author_name] = splitted[4]
191
+ commit[:commit_date] = splitted[5]
192
+
193
+ parsed.push(commit)
194
+ end
195
+
196
+ parsed
197
+ end
198
+
199
+ #####################################################
200
+ # @!group Documentation
201
+ #####################################################
202
+
203
+ def self.description
204
+ "Get commits since last version and generates release notes"
205
+ end
206
+
207
+ def self.details
208
+ "Uses conventional commits. It groups commits by their types and generates release notes in markdown or slack format."
209
+ end
210
+
211
+ def self.available_options
212
+ # Define all options your action supports.
213
+
214
+ # Below a few examples
215
+ [
216
+ FastlaneCore::ConfigItem.new(
217
+ key: :format,
218
+ description: "You can use either markdown, slack or plain",
219
+ default_value: "markdown",
220
+ optional: true
221
+ ),
222
+ FastlaneCore::ConfigItem.new(
223
+ key: :title,
224
+ description: "Title for release notes",
225
+ optional: true
226
+ ),
227
+ FastlaneCore::ConfigItem.new(
228
+ key: :commit_url,
229
+ description: "Uses as a link to the commit",
230
+ optional: true
231
+ ),
232
+ FastlaneCore::ConfigItem.new(
233
+ key: :order,
234
+ description: "You can change the order of groups in release notes",
235
+ default_value: ["feat", "fix", "refactor", "perf", "chore", "test", "docs", "no_type"],
236
+ type: Array,
237
+ optional: true
238
+ ),
239
+ FastlaneCore::ConfigItem.new(
240
+ key: :sections,
241
+ description: "Map type to section title",
242
+ default_value: {
243
+ feat: "Features",
244
+ fix: "Bug fixes",
245
+ refactor: "Code refactoring",
246
+ perf: "Performance improvements",
247
+ chore: "Building system",
248
+ test: "Testing",
249
+ docs: "Documentation",
250
+ no_type: "Other work"
251
+ },
252
+ type: Hash,
253
+ optional: true
254
+ ),
255
+ FastlaneCore::ConfigItem.new(
256
+ key: :display_author,
257
+ description: "Whether you want to show the author of the commit",
258
+ default_value: false,
259
+ type: Boolean,
260
+ optional: true
261
+ ),
262
+ FastlaneCore::ConfigItem.new(
263
+ key: :display_title,
264
+ description: "Whether you want to hide the title/header with the version details at the top of the changelog",
265
+ default_value: true,
266
+ type: Boolean,
267
+ optional: true
268
+ ),
269
+ FastlaneCore::ConfigItem.new(
270
+ key: :display_links,
271
+ description: "Whether you want to display the links to commit IDs",
272
+ default_value: true,
273
+ type: Boolean,
274
+ optional: true
275
+ ),
276
+ FastlaneCore::ConfigItem.new(
277
+ key: :debug,
278
+ description: "True if you want to log out a debug info",
279
+ default_value: false,
280
+ type: Boolean,
281
+ optional: true
282
+ )
283
+ ]
284
+ end
285
+
286
+ def self.output
287
+ # Define the shared values you are going to provide
288
+ # Example
289
+ []
290
+ end
291
+
292
+ def self.return_value
293
+ # If your method provides a return value, you can describe here what it does
294
+ "Returns generated release notes as a string"
295
+ end
296
+
297
+ def self.authors
298
+ # So no one will ever forget your contribution to fastlane :) You are awesome btw!
299
+ ["xotahal"]
300
+ end
301
+
302
+ def self.is_supported?(platform)
303
+ # you can do things like
304
+ true
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,104 @@
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.parse_commit(params)
24
+ commit_subject = params[:commit_subject].strip
25
+ commit_body = params[:commit_body]
26
+ releases = params[:releases]
27
+ codepush_friendly = params[:codepush_friendly]
28
+ pattern = params[:pattern]
29
+ breaking_change_pattern = /BREAKING CHANGES?: (.*)/
30
+ codepush_pattern = /codepush?: (.*)/
31
+
32
+ matched = commit_subject.match(pattern)
33
+ result = {
34
+ is_valid: false,
35
+ subject: commit_subject,
36
+ is_merge: !(commit_subject =~ /^Merge/).nil?,
37
+ type: 'no_type'
38
+ }
39
+
40
+ unless matched.nil?
41
+ type = matched[1]
42
+ scope = matched[2]
43
+
44
+ result[:is_valid] = true
45
+ result[:type] = type
46
+ result[:scope] = scope
47
+ result[:has_exclamation_mark] = matched[3] == '!'
48
+ result[:subject] = matched[4]
49
+
50
+ unless releases.nil?
51
+ result[:release] = releases[type.to_sym]
52
+ end
53
+ unless codepush_friendly.nil?
54
+ result[:is_codepush_friendly] = codepush_friendly.include?(type)
55
+ end
56
+
57
+ unless commit_body.nil?
58
+ breaking_change_matched = commit_body.match(breaking_change_pattern)
59
+ codepush_matched = commit_body.match(codepush_pattern)
60
+
61
+ unless breaking_change_matched.nil?
62
+ result[:is_breaking_change] = true
63
+ result[:breaking_change] = breaking_change_matched[1]
64
+ end
65
+ unless codepush_matched.nil?
66
+ result[:is_codepush_friendly] = codepush_matched[1] == 'ok'
67
+ end
68
+ end
69
+ end
70
+
71
+ result
72
+ end
73
+
74
+ def self.semver_gt(first, second)
75
+ first_major = (first.split('.')[0] || 0).to_i
76
+ first_minor = (first.split('.')[1] || 0).to_i
77
+ first_patch = (first.split('.')[2] || 0).to_i
78
+
79
+ second_major = (second.split('.')[0] || 0).to_i
80
+ second_minor = (second.split('.')[1] || 0).to_i
81
+ second_patch = (second.split('.')[2] || 0).to_i
82
+
83
+ # Check if next version is higher then last version
84
+ if first_major > second_major
85
+ return true
86
+ elsif first_major == second_major
87
+ if first_minor > second_minor
88
+ return true
89
+ elsif first_minor == second_minor
90
+ if first_patch > second_patch
91
+ return true
92
+ end
93
+ end
94
+ end
95
+
96
+ return false
97
+ end
98
+
99
+ def self.semver_lt(first, second)
100
+ return !semver_gt(first, second)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1 @@
1
+ module Fastlane module SemanticRelease VERSION = "1.0.0" end end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-semantic_release_workflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Phong Nguyen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-04-13 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: phongnguyen180993@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/phongnguyen93/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.0.3
172
+ signing_key:
173
+ specification_version: 4
174
+ summary: Automated version managment, generator of release notes, create gitlab release
175
+ then notify
176
+ test_files: []