fastlane-plugin-wpmreleasetoolkit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +339 -0
  3. data/README.md +38 -0
  4. data/bin/drawText +19 -0
  5. data/ext/drawText/extconf.rb +36 -0
  6. data/lib/fastlane/plugin/wpmreleasetoolkit.rb +16 -0
  7. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/README.md +20 -0
  8. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/an_localize_libs_action.rb +53 -0
  9. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/an_update_metadata_source_action.rb +171 -0
  10. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/an_validate_lib_strings_action.rb +63 -0
  11. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_betabuild_prechecks.rb +103 -0
  12. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_build_prechecks.rb +83 -0
  13. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_build_preflight.rb +54 -0
  14. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_beta.rb +69 -0
  15. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_final_release.rb +58 -0
  16. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_hotfix.rb +77 -0
  17. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_bump_version_release.rb +89 -0
  18. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_codefreeze_prechecks.rb +79 -0
  19. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_completecodefreeze_prechecks.rb +68 -0
  20. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_create_xml_release_notes.rb +63 -0
  21. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_current_branch_is_hotfix.rb +44 -0
  22. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_file_by_version.rb +79 -0
  23. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_download_translations_action.rb +115 -0
  24. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_finalize_prechecks.rb +71 -0
  25. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_get_alpha_version.rb +44 -0
  26. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_get_app_version.rb +44 -0
  27. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_get_release_version.rb +44 -0
  28. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_hotifx_prechecks.rb +78 -0
  29. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_merge_translators_strings.rb +106 -0
  30. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_tag_build.rb +51 -0
  31. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_metadata.rb +52 -0
  32. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/android/android_update_release_notes.rb +56 -0
  33. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/circleci_trigger_job_action.rb +63 -0
  34. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/close_milestone_action.rb +56 -0
  35. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_new_milestone_action.rb +59 -0
  36. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/create_release_action.rb +91 -0
  37. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/extract_release_notes_for_version_action.rb +89 -0
  38. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/get_prs_list_action.rb +64 -0
  39. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/gp_downloadmetadata_action.rb +90 -0
  40. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/gp_update_metadata_source.rb +170 -0
  41. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/promo_screenshots_action.rb +247 -0
  42. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/removebranchprotection_action.rb +57 -0
  43. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setbranchprotection_action.rb +56 -0
  44. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/setfrozentag_action.rb +81 -0
  45. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_add_files_to_copy_action.rb +96 -0
  46. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_apply_action.rb +139 -0
  47. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_download_action.rb +57 -0
  48. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_setup_action.rb +86 -0
  49. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_update_action.rb +139 -0
  50. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/configure/configure_validate_action.rb +134 -0
  51. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/add_development_certificates_to_provisioning_profiles.rb +77 -0
  52. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/add_devices_to_provisioning_profiles.rb +79 -0
  53. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_betabuild_prechecks.rb +92 -0
  54. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_build_prechecks.rb +74 -0
  55. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_build_preflight.rb +78 -0
  56. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_bump_version_beta.rb +68 -0
  57. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_bump_version_hotfix.rb +87 -0
  58. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_bump_version_release.rb +114 -0
  59. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_check_beta_deps.rb +62 -0
  60. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_clear_intermediate_tags.rb +60 -0
  61. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_codefreeze_prechecks.rb +70 -0
  62. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_completecodefreeze_prechecks.rb +63 -0
  63. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_current_branch_is_hotfix.rb +40 -0
  64. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_final_tag.rb +52 -0
  65. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_finalize_prechecks.rb +64 -0
  66. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_get_app_version.rb +47 -0
  67. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_get_build_version.rb +60 -0
  68. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_get_store_app_sizes.rb +121 -0
  69. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_hotifx_prechecks.rb +78 -0
  70. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_lint_localizations.rb +167 -0
  71. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_localize_project.rb +44 -0
  72. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_merge_translators_strings.rb +93 -0
  73. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_tag_build.rb +44 -0
  74. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_metadata.rb +40 -0
  75. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_metadata_source.rb +81 -0
  76. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_update_release_notes.rb +56 -0
  77. data/lib/fastlane/plugin/wpmreleasetoolkit/actions/ios/ios_validate_ci_build.rb +46 -0
  78. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/an_metadata_update_helper.rb +152 -0
  79. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_git_helper.rb +44 -0
  80. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb +359 -0
  81. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_version_helper.rb +475 -0
  82. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ci_helper.rb +91 -0
  83. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/configure_helper.rb +282 -0
  84. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/encryption_helper.rb +51 -0
  85. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/filesystem_helper.rb +93 -0
  86. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/git_helper.rb +224 -0
  87. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb +135 -0
  88. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_adc_app_sizes_helper.rb +74 -0
  89. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_git_helper.rb +80 -0
  90. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +208 -0
  91. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb +348 -0
  92. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata_download_helper.rb +107 -0
  93. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata_update_helper.rb +182 -0
  94. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/promo_screenshots_helper.rb +399 -0
  95. data/lib/fastlane/plugin/wpmreleasetoolkit/helper/release_notes_helper.rb +21 -0
  96. data/lib/fastlane/plugin/wpmreleasetoolkit/models/configuration.rb +40 -0
  97. data/lib/fastlane/plugin/wpmreleasetoolkit/models/file_reference.rb +86 -0
  98. data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +5 -0
  99. metadata +449 -0
@@ -0,0 +1,224 @@
1
+ require 'git'
2
+
3
+ module Fastlane
4
+ module Helper
5
+ # Helper methods to execute git-related operations
6
+ #
7
+ module GitHelper
8
+ # Checks if the given path, or current directory if no path is given, is
9
+ # inside a Git repository
10
+ #
11
+ # @param [String] path An optional path where to check if a Git repo
12
+ # exists.
13
+ #
14
+ # @return [Bool] True if the current directory is the root of a git repo
15
+ # (i.e. a local working copy) or a subdirectory of one.
16
+ #
17
+ def self.is_git_repo?(path: Dir.pwd)
18
+ # If the path doesn't exist, find its first ancestor.
19
+ path = first_existing_ancestor_of(path: path)
20
+ # Get the path's directory, so we can look in it for the Git folder
21
+ dir = path.directory? ? path : path.dirname
22
+
23
+ # Recursively look for the Git folder until it's found or we read the
24
+ # the file system root
25
+ dir = dir.parent until Dir.entries(dir).include?('.git') || dir.root?
26
+
27
+ # If we reached the root, we haven't found a repo. (Technically, there
28
+ # could be a repo in the root of the system, but that's a usecase that
29
+ # we don't need to support at this time)
30
+ return dir.root? == false
31
+ end
32
+
33
+ # Travels back the hierarchy of the given path until it finds an existing
34
+ # ancestor, or it reaches the root of the file system.
35
+ #
36
+ # @param [String] path The path to inspect
37
+ #
38
+ # @return [Pathname] The first existing ancestor, or `path` itself if it
39
+ # exists
40
+ #
41
+ def self.first_existing_ancestor_of(path:)
42
+ p = Pathname(path).expand_path
43
+ p = p.parent until p.exist? || p.root?
44
+ return p
45
+ end
46
+
47
+ # Check if the current directory has git-lfs enabled
48
+ #
49
+ # @return [Bool] True if the current directory is a git working copy and has git-lfs enabled.
50
+ #
51
+ def self.has_git_lfs?
52
+ return false unless is_git_repo?
53
+
54
+ `git config --get-regex lfs`.length > 0
55
+ end
56
+
57
+ # Switch to the given branch and pull its latest commits.
58
+ #
59
+ # @param [String,Hash] branch Name of the branch to pull.
60
+ # If you provide a Hash with a single key=>value pair, it will build the branch name as `"#{key}/#{value}"`,
61
+ # i.e. `checkout_and_pull(release: version)` is equivalent to `checkout_and_pull("release/#{version}")`.
62
+ #
63
+ # @return [Bool] True if it succeeded switching and pulling, false if there was an error during the switch or pull.
64
+ #
65
+ def self.checkout_and_pull(branch)
66
+ branch = branch.first.join('/') if branch.is_a?(Hash)
67
+ Action.sh('git', 'checkout', branch)
68
+ Action.sh('git', 'pull')
69
+ return true
70
+ rescue
71
+ return false
72
+ end
73
+
74
+ # Update every submodule in the current git repository
75
+ #
76
+ def self.update_submodules
77
+ Action.sh('git', 'submodule', 'update', '--init', '--recursive')
78
+ end
79
+
80
+ # Create a new branch named `branch_name`, cutting it from branch/commit/tag `from`, and push it
81
+ #
82
+ # If the branch with that name already exists, it will instead switch to it and pull new commits.
83
+ #
84
+ # @param [String] branch_name The full name of the new branch to create, e.g "release/1.2"
85
+ # @param [String?] from The branch or tag from which to cut the branch from.
86
+ # If `nil`, will cut the new branch from the current commit. Otherwise, will checkout that commit/branch/tag before cutting the branch.
87
+ # @param [Bool] push If true, will also push the branch to `origin`, tracking the upstream branch with the local one.
88
+ #
89
+ def self.create_branch(branch_name, from: nil, push: true)
90
+ if branch_exists?(branch_name)
91
+ UI.message("Branch #{branch_name} already exists. Skipping creation.")
92
+ Action.sh('git', 'checkout', branch_name)
93
+ Action.sh('git', 'pull', 'origin', branch_name)
94
+ else
95
+ Action.sh('git', 'checkout', from) unless from.nil?
96
+ Action.sh('git', 'checkout', '-b', branch_name)
97
+ Action.sh('git', 'push', '-u', 'origin', branch_name) if push
98
+ end
99
+ end
100
+
101
+ # `git add` the specified files (if any provided) then commit them using the provided message.
102
+ # Optionally, push the commit to the remote too.
103
+ #
104
+ # @param [String] message The commit message to use
105
+ # @param [String|Array<String>] files A file or array of files to git-add before creating the commit.
106
+ # use `nil` or `[]` if you already added the files in a separate step and don't wan't this method to add any new file before commit.
107
+ # Also accepts the special symbol `:all` to add all the files (`git commit -a -m …`).
108
+ # @param [Bool] push If true, will `git push` to `origin` after the commit has been created. Defaults to `false`.
109
+ #
110
+ # @return [Bool] True if commit and push were successful, false if there was an issue during commit & push (most likely being "nothing to commit").
111
+ #
112
+ def self.commit(message:, files: nil, push: false)
113
+ files = [files] if files.is_a?(String)
114
+ args = []
115
+ if files == :all
116
+ args = ['-a']
117
+ elsif !files.nil? && !files.empty?
118
+ Action.sh('git', 'add', *files)
119
+ end
120
+ begin
121
+ Action.sh('git', 'commit', *args, '-m', message)
122
+ Action.sh('git', 'push', 'origin', 'HEAD') if push
123
+ return true
124
+ rescue
125
+ return false
126
+ end
127
+ end
128
+
129
+ # Get the SHA of a given git ref. Typically useful to get the SHA of the current HEAD commit.
130
+ #
131
+ # @param [String] ref The git ref (commit, branch name, 'HEAD', …) to resolve as a SHA
132
+ # @return [String] The commit SHA of the ref
133
+ #
134
+ def self.get_commit_sha(ref: 'HEAD')
135
+ Git.open(Dir.pwd).revparse(ref)
136
+ end
137
+
138
+ # Creates a tag for the given version, and optionally push it to the remote.
139
+ #
140
+ # @param [String] version The name of the tag to push, e.g. "1.2"
141
+ # @param [Bool] push If true (the default), the tag will also be pushed to `origin`
142
+ #
143
+ def self.create_tag(version, push: true)
144
+ Action.sh('git', 'tag', version)
145
+ Action.sh('git', 'push', 'origin', version) if push
146
+ end
147
+
148
+ # Returns the list of tags that are pointing to the current commit (HEAD)
149
+ #
150
+ # @return [Array<String>] List of tags associated with the HEAD commit
151
+ #
152
+ def self.list_tags_on_current_commit
153
+ Action.sh('git', 'tag', '--points-at', 'HEAD').split("\n")
154
+ end
155
+
156
+ # List all the tags in the local working copy, optionally filtering the list using a pattern
157
+ #
158
+ # @param [String] matching The pattern of the tag(s) to match and filter on; use "*" for wildcards.
159
+ # For example, `"1.2.*"` will match every tag starting with `"1.2."`. Defaults to '*' which lists all tags.
160
+ #
161
+ # @return [Array<String>] The list of local tags matching the pattern
162
+ #
163
+ def self.list_local_tags(matching: '*')
164
+ Action.sh('git', 'tag', '--list', matching).split("\n")
165
+ end
166
+
167
+ # Delete the mentioned local tags in the local working copy, and optionally delete them on the remote too.
168
+ #
169
+ # @param [Array<String>] tags_to_delete The list of tags to delete
170
+ # @param [Bool] delete_on_remote If true, will also delete the tag from the remote. Otherwise, it will only be deleted locally.
171
+ #
172
+ def self.delete_tags(tags_to_delete, delete_on_remote: false)
173
+ g = Git.open(Dir.pwd)
174
+ local_tag_names = g.tags.map(&:name)
175
+
176
+ Array(tags_to_delete).each do |tag|
177
+ g.delete_tag(tag) if local_tag_names.include?(tag)
178
+ g.push('origin', ":refs/tags/#{tag}") if delete_on_remote
179
+ end
180
+ end
181
+
182
+ # Fetch all the tags from the remote.
183
+ #
184
+ def self.fetch_all_tags
185
+ Action.sh('git', 'fetch', '--tags')
186
+ end
187
+
188
+ # Checks if a branch exists locally.
189
+ #
190
+ # @param [String] branch_name The name of the branch to check for
191
+ #
192
+ # @return [Bool] True if the branch exists in the local working copy, false otherwise.
193
+ #
194
+ def self.branch_exists?(branch_name)
195
+ !Action.sh('git', 'branch', '--list', branch_name).empty?
196
+ end
197
+
198
+ # Ensure that we are on the expected branch, and abort if not.
199
+ #
200
+ # @param [String] branch_name The name of the branch we expect to be on
201
+ #
202
+ # @raise [UserError] Raises a user_error! and interrupts the lane if we are not on the expected branch.
203
+ #
204
+ def self.ensure_on_branch!(branch_name)
205
+ current_branch_name = Action.sh('git', 'symbolic-ref', '-q', 'HEAD')
206
+ UI.user_error!("This command works only on #{branch_name} branch") unless current_branch_name.include?(branch_name)
207
+ end
208
+
209
+ # Checks whether a given path is ignored by Git, relying on Git's
210
+ # `check-ignore` under the hood.
211
+ #
212
+ # @param [String] path The path to check against `.gitignore`
213
+ #
214
+ # @return [Bool] True if the given path is ignored or outside a Git repository, false otherwise.
215
+ def self.is_ignored?(path:)
216
+ return true unless is_git_repo?(path: path)
217
+
218
+ Actions.sh('git', 'check-ignore', path) do |status, _, _|
219
+ status.success?
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,135 @@
1
+ require 'fastlane_core/ui/ui'
2
+ require 'octokit'
3
+ require 'open-uri'
4
+
5
+ module Fastlane
6
+ UI = FastlaneCore::UI unless Fastlane.const_defined?('UI')
7
+
8
+ module Helper
9
+ class GithubHelper
10
+ def self.github_client
11
+ client = Octokit::Client.new(access_token: ENV['GHHELPER_ACCESS'])
12
+
13
+ # Fetch the current user
14
+ user = client.user
15
+ UI.message("Logged in as: #{user.name}")
16
+
17
+ # Auto-paginate to ensure we're not missing data
18
+ client.auto_paginate = true
19
+
20
+ client
21
+ end
22
+
23
+ def self.get_milestone(repository, release)
24
+ miles = github_client().list_milestones(repository)
25
+ mile = nil
26
+
27
+ miles&.each do |mm|
28
+ mile = mm if mm[:title].start_with?(release)
29
+ end
30
+
31
+ return mile
32
+ end
33
+
34
+ # Fetch all the PRs for a given milestone
35
+ #
36
+ # @param [String] repository The repository name, including the organization (e.g. `wordpress-mobile/wordpress-ios`)
37
+ # @param [String] milestone The name of the milestone we want to fetch the list of PRs for (e.g.: `16.9`)
38
+ # @return [<Sawyer::Resource>] A list of the PRs for the given milestone, sorted by number
39
+ #
40
+ def self.get_prs_for_milestone(repository, milestone)
41
+ github_client.search_issues(%(type:pr milestone:"#{milestone}" repo:#{repository}))[:items].sort_by(&:number)
42
+ end
43
+
44
+ def self.get_last_milestone(repository)
45
+ options = {}
46
+ options[:state] = 'open'
47
+
48
+ milestones = github_client().list_milestones(repository, options)
49
+ return nil if milestones.nil?
50
+
51
+ last_stone = nil
52
+ milestones.each do |mile|
53
+ if last_stone.nil?
54
+ last_stone = mile unless mile[:title].split(' ')[0].split('.').length < 2
55
+ else
56
+ begin
57
+ if mile[:title].split(' ')[0].split('.')[0] > last_stone[:title].split(' ')[0].split('.')[0]
58
+ last_stone = mile
59
+ elsif mile[:title].split(' ')[0].split('.')[1] > last_stone[:title].split(' ')[0].split('.')[1]
60
+ last_stone = mile
61
+ end
62
+ rescue StandardError
63
+ puts 'Found invalid milestone'
64
+ end
65
+ end
66
+ end
67
+
68
+ last_stone
69
+ end
70
+
71
+ def self.create_milestone(repository, newmilestone_number, newmilestone_duedate, need_submission)
72
+ submission_date = need_submission ? newmilestone_duedate.to_datetime.next_day(11) : newmilestone_duedate.to_datetime.next_day(14)
73
+ release_date = newmilestone_duedate.to_datetime.next_day(14)
74
+ comment = "Code freeze: #{newmilestone_duedate.to_datetime.strftime('%B %d, %Y')} App Store submission: #{submission_date.strftime('%B %d, %Y')} Release: #{release_date.strftime('%B %d, %Y')}"
75
+
76
+ options = {}
77
+ options[:due_on] = newmilestone_duedate
78
+ options[:description] = comment
79
+ github_client().create_milestone(repository, newmilestone_number, options)
80
+ end
81
+
82
+ # Creates a Release on GitHub as a Draft
83
+ #
84
+ # @param [String] repository The repository to create the GitHub release on. Typically a repo slug (<org>/<repo>).
85
+ # @param [String] version The version for which to create this release. Will be used both as the name of the tag and the name of the release.
86
+ # @param [String?] target The commit SHA or branch name that this release will point to when it's published and creates the tag.
87
+ # If nil (the default), will use the repo's current HEAD commit at the time this method is called.
88
+ # Unused if the tag already exists.
89
+ # @param [String] description The text to use as the release's body / description (typically the release notes)
90
+ # @param [Array<String>] assets List of file paths to attach as assets to the release
91
+ # @param [TrueClass|FalseClass] prerelease Indicates if this should be created as a pre-release (i.e. for alpha/beta)
92
+ #
93
+ def self.create_release(repository:, version:, target: nil, description:, assets:, prerelease:)
94
+ release = github_client().create_release(
95
+ repository,
96
+ version, # tag name
97
+ name: version, # release name
98
+ target_commitish: target || Git.open(Dir.pwd).log.first.sha,
99
+ draft: true,
100
+ prerelease: prerelease,
101
+ body: description
102
+ )
103
+ assets.each do |file_path|
104
+ github_client().upload_asset(release[:url], file_path, content_type: 'application/octet-stream')
105
+ end
106
+ end
107
+
108
+ # Downloads a file from the given GitHub tag
109
+ #
110
+ # @param [String] repository The repository name (including the organization)
111
+ # @param [String] tag The name of the tag we're downloading from
112
+ # @param [String] file_path The path, inside the project folder, of the file to download
113
+ # @param [String] download_folder The folder which the file should be downloaded into
114
+ # @return [String] The path of the downloaded file, or nil if something went wrong
115
+ #
116
+ def self.download_file_from_tag(repository:, tag:, file_path:, download_folder:)
117
+ repository = repository.delete_prefix('/').chomp('/')
118
+ file_path = file_path.delete_prefix('/').chomp('/')
119
+ file_name = File.basename(file_path)
120
+ download_path = File.join(download_folder, file_name)
121
+
122
+ begin
123
+ uri = URI.parse("https://raw.githubusercontent.com/#{repository}/#{tag}/#{file_path}")
124
+ uri.open do |remote_file|
125
+ File.write(download_path, remote_file.read)
126
+ end
127
+ rescue OpenURI::HTTPError
128
+ return nil
129
+ end
130
+
131
+ download_path
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,74 @@
1
+ require 'spaceship'
2
+
3
+ module Fastlane
4
+ module Helper
5
+ module Ios
6
+ module ADCAppSizesHelper
7
+ DEFAULT_DEVICES = ['Universal', 'iPhone 8', 'iPhone X']
8
+
9
+ # Fetch the App Sizes stats from ADC
10
+ #
11
+ # @return [Array<Hash>] app build details, one entry per app version found
12
+ # Each entry is a hash with keys `cfBundleVersion` and `sizesInBytes`.
13
+ # Value for key `sizeInBytes` is itself a Hash with one entry per device name (including special name "Universal")
14
+ # whose value is a Hash with keys `compressed` and `uncompressed`
15
+ #
16
+ def self.get_adc_sizes(adc_user:, adc_team: 'Automattic, Inc.', bundle_id:, only_version: nil, limit: 10)
17
+ UI.message 'Connecting to ADC...'
18
+ Spaceship::ConnectAPI.login(adc_user, team_name: adc_team)
19
+ app = Spaceship::ConnectAPI::App.find(bundle_id)
20
+
21
+ UI.message 'Fetching the list of versions...'
22
+ versions = app.app_store_versions.select { |v| v.version_string == only_version && !v.build.nil? }
23
+ versions = app.get_app_store_versions.reject { |v| v.build.nil? } if versions.empty?
24
+ UI.message "Found #{versions.count} versions." + (limit == 0 ? '' : " Limiting to last #{limit}")
25
+ versions = versions.first(limit) unless limit == 0
26
+
27
+ UI.message 'Fetching App Sizes...'
28
+
29
+ builds_details = versions.each_with_index.map do |v, idx|
30
+ print "Fetching info for: #{v.version_string.rjust(8)} (#{v.build.version.rjust(11)}) [#{idx.to_s.rjust(3)}/#{versions.count}]\r"
31
+ Spaceship::Tunes.client.build_details(app_id: app.id, train: v.version_string, build_number: v.build.version, platform: 'ios') rescue nil
32
+ end.compact.reverse
33
+ print(' ' * 55 + "\n")
34
+
35
+ builds_details
36
+ end
37
+
38
+ def self.sz(bytes)
39
+ (bytes.to_f / (1024 * 1024)).round(1)
40
+ end
41
+
42
+ def self.sz_mb(bytes)
43
+ sz(bytes).to_s.rjust(5) + ' MB'
44
+ end
45
+
46
+ def self.format_csv(app_sizes, devices: nil)
47
+ devices = DEFAULT_DEVICES if devices.nil? || devices.empty?
48
+ csv = "Version\t" + devices.join("\t") + "\n"
49
+ app_sizes.each do |details|
50
+ build_number = details['cfBundleVersion']
51
+ sizes = details['sizesInBytes'].select { |name, _| devices.include?(name) }
52
+ csv += "#{build_number}\t" + devices.map { |d| sz(sizes[d]['compressed']) }.join("\t") + "\n"
53
+ end
54
+ csv
55
+ end
56
+
57
+ def self.format_markdown(app_sizes, devices: nil)
58
+ devices = DEFAULT_DEVICES if devices.nil? || devices.empty?
59
+ app_sizes.map do |details|
60
+ build_number = details['cfBundleVersion']
61
+ sizes = details['sizesInBytes'].select { |name, _| devices.include?(name) }
62
+ col_size = devices.map(&:length).max
63
+ table = "| #{build_number.ljust(col_size)} | Download | Install |\n"
64
+ table += "|:#{'-' * col_size}-|---------:|---------:|\n"
65
+ sizes.each do |(device_name, size_info)|
66
+ table += "| #{device_name.ljust(col_size)} | #{sz_mb(size_info['compressed'])} | #{sz_mb(size_info['uncompressed'])} |\n"
67
+ end
68
+ table
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,80 @@
1
+ module Fastlane
2
+ module Helper
3
+ module Ios
4
+ # Helper methods to execute git-related operations that are specific to iOS projects
5
+ #
6
+ module GitHelper
7
+ # Commit and push the files that are modified when we bump version numbers on an iOS project
8
+ #
9
+ # This typically commits and pushes:
10
+ # - The files in `./config/*` – especially `Version.*.xcconfig` files
11
+ # - The `fastlane/Deliverfile` file (which contains the `app_version` line)
12
+ # - The `<ProjectRoot>/<ProjectName>/Resources/AppStoreStrings.pot` file, containing a key for that version's release notes
13
+ #
14
+ # @env PROJECT_ROOT_FOLDER The path to the git root of the project
15
+ # @env PROJECT_NAME The name of the directory containing the project code (especially containing the Resources/ subfolder)
16
+ #
17
+ # @param [Bool] include_deliverfile If true (the default), includes the `fastlane/Deliverfile` in files being commited
18
+ # @param [Bool] include_metadata If true (the default), includes the `fastlane/download_metadata.swift` file and the `.pot` file (which typically contains an entry or release notes for the new version)
19
+ #
20
+ def self.commit_version_bump(include_deliverfile: true, include_metadata: true)
21
+ files_list = [File.join(ENV['PROJECT_ROOT_FOLDER'], 'config', '.')]
22
+ files_list.append File.join('fastlane', 'Deliverfile') if include_deliverfile
23
+ if include_metadata
24
+ files_list.append File.join('fastlane', 'download_metadata.swift')
25
+ files_list.append File.join(ENV['PROJECT_ROOT_FOLDER'], ENV['PROJECT_NAME'], 'Resources', ENV['APP_STORE_STRINGS_FILE_NAME'])
26
+ end
27
+
28
+ Fastlane::Helper::GitHelper.commit(message: 'Bump version number', files: files_list, push: true)
29
+ end
30
+
31
+ # Calls the `Scripts/localize.py` script in the project root folder and push the `*.strings` files
32
+ #
33
+ # That script updates the `.strings` files with translations from GlotPress.
34
+ #
35
+ # @env PROJECT_ROOT_FOLDER The path to the git root of the project
36
+ # @env PROJECT_NAME The name of the directory containing the project code (especially containing the `build.gradle` file)
37
+ #
38
+ # @todo Migrate the scripts, currently in each host repo and called by this method, to be helpers and actions
39
+ # in the release-toolkit instead, and move this code away from `ios_git_helper`.
40
+ #
41
+ def self.localize_project
42
+ Action.sh("cd #{get_from_env!(key: 'PROJECT_ROOT_FOLDER')} && ./Scripts/localize.py")
43
+
44
+ Fastlane::Helper::GitHelper.commit(message: 'Update strings for localization', files: strings_files, push: true) || UI.message('No new strings, skipping commit')
45
+ end
46
+
47
+ # Call the `Scripts/update-translations.rb` then the `fastlane/download_metadata` Scripts from the host project folder
48
+ #
49
+ # @env PROJECT_ROOT_FOLDER The path to the git root of the project
50
+ # @env PROJECT_NAME The name of the directory containing the project code (especially containing the `build.gradle` file)
51
+ #
52
+ # @todo Migrate the scripts, currently in each host repo and called by this method, to be helpers and actions
53
+ # in the release-toolkit instead, and move this code away from `ios_git_helper`.
54
+ #
55
+ def self.update_metadata
56
+ Action.sh("cd #{get_from_env!(key: 'PROJECT_ROOT_FOLDER')} && ./Scripts/update-translations.rb")
57
+
58
+ Fastlane::Helper::GitHelper.commit(message: 'Update translations', files: strings_files, push: false)
59
+
60
+ Action.sh('cd fastlane && ./download_metadata.swift')
61
+
62
+ Fastlane::Helper::GitHelper.commit(message: 'Update metadata translations', files: './fastlane/metadata/', push: true)
63
+ end
64
+
65
+ def self.strings_files
66
+ project_root = get_from_env!(key: 'PROJECT_ROOT_FOLDER')
67
+ project_name = get_from_env!(key: 'PROJECT_NAME')
68
+
69
+ Dir.glob(File.join(project_root, project_name, '**', '*.strings'))
70
+ end
71
+
72
+ def self.get_from_env!(key:)
73
+ ENV.fetch(key) do
74
+ UI.user_error! "Could not find value for \"#{key}\" in environment."
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end