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 +4 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_localize_helper.rb +10 -11
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/glotpress_downloader.rb +127 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_helper.rb +14 -7
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata_download_helper.rb +17 -39
- data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46149eab0c077a2b31f02f27b4ef5ea3bc5f485835ef1e8b0e63546e40523e7a
|
4
|
+
data.tar.gz: 1870bc699234b49685901b4f324feb284c34a5ff8ac652212d3e4cb9c141a8d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
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
|
-
|
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} (#{
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
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.
|
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-
|
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
|