fastlane-plugin-wpmreleasetoolkit 13.5.2 → 13.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b2482314dd0f766ed17163b49308b9054982c3e85bcf017310611e53d43155c
4
- data.tar.gz: b942e2a2c4d0778420d2f15ec35d6a8b9f2718d237bb687aeb5ee6b6cdae86e1
3
+ metadata.gz: 46149eab0c077a2b31f02f27b4ef5ea3bc5f485835ef1e8b0e63546e40523e7a
4
+ data.tar.gz: 1870bc699234b49685901b4f324feb284c34a5ff8ac652212d3e4cb9c141a8d3
5
5
  SHA512:
6
- metadata.gz: c033a332896e9151be106d316960beecb8f275bfa14865ff716e663508318ef29240ee668507436337e8a16bb80ef296ae31abb7ca774597f1f3ea014c123489
7
- data.tar.gz: 4120e9a3d1c7974959b6c2a6b950ec827ea23c524535fb754c2c9130f95395f65f0ca202adb9fc93e5c94f36b34212512dc389022ea487d2e31b726991484a7d
6
+ metadata.gz: 5117ff6981b4893f78986ddf61674fc690b82bfe6d1ab3bef3d540656cc09c074b53edaeaaac5aa65b98a9a4e7dadf3c5f8d47efb0df840ceb86d05609f3fae5
7
+ data.tar.gz: 422c053f58a84609d4597367eff41c00408dd246aa11a5150fc8266ef34bb3192f711700f82c96213f2b0cac46b56dacc8a90ec6b8b5f17515f81395f19ef7da
@@ -4,6 +4,7 @@ require 'fastlane_core/ui/ui'
4
4
  require 'fileutils'
5
5
  require 'nokogiri'
6
6
  require 'open-uri'
7
+ require_relative '../glotpress_downloader'
7
8
 
8
9
  module Fastlane
9
10
  UI = FastlaneCore::UI unless Fastlane.const_defined?('UI')
@@ -285,17 +286,15 @@ module Fastlane
285
286
  #
286
287
  def self.download_glotpress_export_file(project_url:, locale:, filters:)
287
288
  query_params = filters.transform_keys { |k| "filters[#{k}]" }.merge(format: 'android')
288
- uri = URI.parse("#{project_url.chomp('/')}/#{locale}/default/export-translations/?#{URI.encode_www_form(query_params)}")
289
-
290
- # Set an unambiguous User Agent so GlotPress won't rate-limit us
291
- options = { 'User-Agent' => Wpmreleasetoolkit::USER_AGENT }
292
-
293
- begin
294
- uri.open(options) { |f| Nokogiri::XML(f.read.gsub("\t", ' '), nil, Encoding::UTF_8.to_s) }
295
- rescue StandardError => e
296
- UI.error "Error downloading #{locale} - #{e.message}"
297
- retry if e.is_a?(OpenURI::HTTPError) && UI.confirm("Retry downloading `#{locale}`?")
298
- nil
289
+ url = "#{project_url.chomp('/')}/#{locale}/default/export-translations/?#{URI.encode_www_form(query_params)}"
290
+
291
+ Fastlane::Helper::GlotPressDownloader.download(
292
+ url: url,
293
+ locale: locale,
294
+ auto_retry: true
295
+ ) do |response_body|
296
+ # Replace tabs with spaces (GlotPress uses tabs, but we prefer spaces)
297
+ Nokogiri::XML(response_body.gsub("\t", ' '), nil, Encoding::UTF_8.to_s)
299
298
  end
300
299
  end
301
300
  private_class_method :download_glotpress_export_file
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module Fastlane
7
+ module Helper
8
+ # A helper class to download files from GlotPress with proper error handling and retry mechanism
9
+ class GlotPressDownloader
10
+ AUTO_RETRY_SLEEP_TIME = 20
11
+ MAX_AUTO_RETRY_ATTEMPTS = 30
12
+
13
+ attr_reader :auto_retry, :auto_retry_attempt_counter, :url, :locale
14
+
15
+ # Initialize a new GlotPressDownloader
16
+ #
17
+ # @param [String] url The URL to download from
18
+ # @param [String] locale The locale being downloaded (for logging purposes)
19
+ # @param [Boolean] auto_retry Whether to automatically retry on rate limiting (429 errors)
20
+ #
21
+ def initialize(url:, locale:, auto_retry: false)
22
+ @url = url
23
+ @locale = locale
24
+ @auto_retry = auto_retry
25
+ @auto_retry_attempt_counter = 0
26
+ end
27
+
28
+ # Convenience class method to download in a single call
29
+ #
30
+ # @param [String] url The URL to download from
31
+ # @param [String] locale The locale being downloaded (for logging purposes)
32
+ # @param [Boolean] auto_retry Whether to automatically retry on rate limiting (429 errors)
33
+ # @yield [String] The response body if the download was successful
34
+ # @return The result of the block if provided, or true/false indicating success if no block provided
35
+ #
36
+ #
37
+ def self.download(url:, locale:, auto_retry: false, &)
38
+ new(url: url, locale: locale, auto_retry: auto_retry).download(&)
39
+ end
40
+
41
+ # Downloads data from GlotPress
42
+ #
43
+ # @yield [String] The response body if the download was successful
44
+ # @return The result of the block if provided, or true/false indicating success if no block provided
45
+ #
46
+ def download(&)
47
+ @auto_retry_attempt_counter = 0 # Reset counter only at start of download
48
+ download_from_url(@url, &)
49
+ end
50
+
51
+ private
52
+
53
+ def download_from_url(url, &)
54
+ uri = URI(url)
55
+ response = make_request(uri)
56
+ result = nil
57
+ success = handle_response(response: response, url: url, original_uri: uri) do |body|
58
+ result = yield body if block_given?
59
+ end
60
+ block_given? ? result : success
61
+ end
62
+
63
+ def make_request(uri)
64
+ http = Net::HTTP.new(uri.host, uri.port)
65
+ http.use_ssl = (uri.scheme == 'https')
66
+ request = Net::HTTP::Get.new(uri.request_uri)
67
+ request['User-Agent'] = Wpmreleasetoolkit::USER_AGENT
68
+ http.request(request)
69
+ rescue StandardError => e
70
+ # Network errors, connection errors, etc.
71
+ UI.error("Error downloading locale `#{@locale}` — #{e.message} (#{uri})")
72
+ retry if UI.interactive? && UI.confirm("Retry downloading `#{@locale}`?")
73
+ nil
74
+ end
75
+
76
+ def handle_response(response:, url:, original_uri:)
77
+ return false if response.nil?
78
+
79
+ case response.code
80
+ when '200'
81
+ UI.success("Successfully downloaded `#{@locale}`.")
82
+ yield response.body if block_given?
83
+ true
84
+ when '301', '302', '307', '308'
85
+ # Follow the redirect
86
+ UI.message("Received #{response.code} for `#{@locale}`. Following redirect...")
87
+ redirect_url = response['location']
88
+ if redirect_url.nil?
89
+ UI.error("Received #{response.code} but no location header found.")
90
+ false
91
+ else
92
+ # Follow redirect with the new URL
93
+ download_from_url(redirect_url) { |body| yield body if block_given? }
94
+ end
95
+ when '429'
96
+ # Rate limited
97
+ handle_rate_limiting(url: url) do |body|
98
+ yield body if block_given?
99
+ end
100
+ else
101
+ # Unexpected status code (including 404, 500, etc.)
102
+ status_line = "#{response.code} #{response.message}"
103
+ UI.error("Error downloading locale `#{@locale}` — #{status_line} (#{original_uri})")
104
+ if UI.interactive? && UI.confirm("Retry downloading `#{@locale}`?")
105
+ download_from_url(url) { |body| yield body if block_given? }
106
+ else
107
+ false
108
+ end
109
+ end
110
+ end
111
+
112
+ def handle_rate_limiting(url:)
113
+ if @auto_retry && @auto_retry_attempt_counter < MAX_AUTO_RETRY_ATTEMPTS
114
+ UI.message("Received 429 for `#{@locale}`. Auto retrying in #{AUTO_RETRY_SLEEP_TIME} seconds... (attempt #{@auto_retry_attempt_counter + 1}/#{MAX_AUTO_RETRY_ATTEMPTS})")
115
+ sleep(AUTO_RETRY_SLEEP_TIME)
116
+ @auto_retry_attempt_counter += 1
117
+ download_from_url(url) { |body| yield body if block_given? }
118
+ elsif UI.interactive? && UI.confirm("Retry downloading `#{@locale}` after receiving 429 from the API?")
119
+ download_from_url(url) { |body| yield body if block_given? }
120
+ else
121
+ UI.error("Abandoning `#{@locale}` download.")
122
+ false
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -6,6 +6,7 @@ require 'nokogiri'
6
6
  require 'open3'
7
7
  require 'open-uri'
8
8
  require 'tempfile'
9
+ require_relative '../glotpress_downloader'
9
10
 
10
11
  module Fastlane
11
12
  module Helper
@@ -165,16 +166,22 @@ module Fastlane
165
166
  #
166
167
  def self.download_glotpress_export_file(project_url:, locale:, filters:, destination:)
167
168
  query_params = (filters || {}).transform_keys { |k| "filters[#{k}]" }.merge(format: 'strings')
168
- uri = URI.parse("#{project_url.chomp('/')}/#{locale}/default/export-translations/?#{URI.encode_www_form(query_params)}")
169
-
170
- # Set an unambiguous User Agent so GlotPress won't rate-limit us
171
- options = { 'User-Agent' => Wpmreleasetoolkit::USER_AGENT }
169
+ url = "#{project_url.chomp('/')}/#{locale}/default/export-translations/?#{URI.encode_www_form(query_params)}"
172
170
 
173
171
  begin
174
- IO.copy_stream(uri.open(options), destination)
172
+ Fastlane::Helper::GlotPressDownloader.download(
173
+ url: url,
174
+ locale: locale,
175
+ auto_retry: true
176
+ ) do |response_body|
177
+ if destination.is_a?(String)
178
+ File.write(destination, response_body)
179
+ else
180
+ destination.write(response_body)
181
+ end
182
+ end
175
183
  rescue StandardError => e
176
- UI.error "Error downloading locale `#{locale}` — #{e.message} (#{uri})"
177
- retry if e.is_a?(OpenURI::HTTPError) && UI.confirm("Retry downloading `#{locale}`?")
184
+ UI.error "Error downloading locale `#{locale}` — #{e.message} (#{url})"
178
185
  nil
179
186
  end
180
187
  end
@@ -2,13 +2,11 @@
2
2
 
3
3
  require 'net/http'
4
4
  require 'json'
5
+ require_relative 'glotpress_downloader'
5
6
 
6
7
  module Fastlane
7
8
  module Helper
8
9
  class MetadataDownloader
9
- AUTO_RETRY_SLEEP_TIME = 20
10
- MAX_AUTO_RETRY_ATTEMPTS = 30
11
-
12
10
  attr_reader :target_folder, :target_files
13
11
 
14
12
  def initialize(target_folder, target_files, auto_retry)
@@ -16,14 +14,17 @@ module Fastlane
16
14
  @target_files = target_files
17
15
  @auto_retry = auto_retry
18
16
  @alternates = {}
19
- @auto_retry_attempt_counter = 0
20
17
  end
21
18
 
22
19
  # Downloads data from GlotPress, in JSON format
23
20
  def download(target_locale, glotpress_url, is_source)
24
- uri = URI(glotpress_url)
25
- response = Net::HTTP.get_response(uri)
26
- handle_glotpress_download(response: response, locale: target_locale, is_source: is_source)
21
+ GlotPressDownloader.download(
22
+ url: glotpress_url,
23
+ locale: target_locale,
24
+ auto_retry: @auto_retry
25
+ ) do |response_body|
26
+ handle_glotpress_response(response_body: response_body, locale: target_locale, is_source: is_source)
27
+ end
27
28
  end
28
29
 
29
30
  # Parse JSON data and update the local files
@@ -105,39 +106,16 @@ module Fastlane
105
106
 
106
107
  private
107
108
 
108
- def handle_glotpress_download(response:, locale:, is_source:)
109
- case response.code
110
- when '200'
111
- # All good, parse the result
112
- UI.success("Successfully downloaded `#{locale}`.")
113
- @alternates.clear
114
- loc_data = begin
115
- JSON.parse(response.body)
116
- rescue StandardError
117
- loc_data = nil
118
- end
119
- parse_data(locale, loc_data, is_source)
120
- reparse_alternates(target_locale, loc_data, is_source) unless @alternates.empty?
121
- when '301'
122
- # Follow the redirect
123
- UI.message("Received 301 for `#{locale}`. Following redirect...")
124
- download(locale, response.header['location'], is_source)
125
- when '429'
126
- # We got rate-limited, auto_retry or offer to try again with a prompt
127
- if @auto_retry && @auto_retry_attempt_counter <= MAX_AUTO_RETRY_ATTEMPTS
128
- UI.message("Received 429 for `#{locale}`. Auto retrying in #{AUTO_RETRY_SLEEP_TIME} seconds...")
129
- sleep(AUTO_RETRY_SLEEP_TIME)
130
- @auto_retry_attempt_counter += 1
131
- download(locale, response.uri, is_source)
132
- elsif UI.confirm("Retry downloading `#{locale}` after receiving 429 from the API?")
133
- download(locale, response.uri, is_source)
134
- else
135
- UI.error("Abandoning `#{locale}` download as requested.")
136
- end
137
- else
138
- message = "Received unexpected #{response.code} from request to URI #{response.uri}."
139
- UI.abort_with_message!(message) unless UI.confirm("#{message} Continue anyway?")
109
+ def handle_glotpress_response(response_body:, locale:, is_source:)
110
+ # Parse the JSON response
111
+ @alternates.clear
112
+ loc_data = begin
113
+ JSON.parse(response_body)
114
+ rescue StandardError
115
+ nil
140
116
  end
117
+ parse_data(locale, loc_data, is_source)
118
+ reparse_alternates(locale, loc_data, is_source) unless @alternates.empty?
141
119
  end
142
120
  end
143
121
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fastlane
4
4
  module Wpmreleasetoolkit
5
- VERSION = '13.5.2'
5
+ VERSION = '13.5.3'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-wpmreleasetoolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.5.2
4
+ version: 13.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Automattic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-14 00:00:00.000000000 Z
11
+ date: 2025-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -465,6 +465,7 @@ files:
465
465
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/filesystem_helper.rb
466
466
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/git_helper.rb
467
467
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb
468
+ - lib/fastlane/plugin/wpmreleasetoolkit/helper/glotpress_downloader.rb
468
469
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/glotpress_helper.rb
469
470
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/interactive_prompt_reminder.rb
470
471
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_adc_app_sizes_helper.rb