fastlane-plugin-wpmreleasetoolkit 13.8.1 → 14.0.0

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: 55ca6dfc40a45267e4dd3de29e3a1a2f83044b957c5c5c8382110ae3b471f55a
4
- data.tar.gz: 9d14ab88d675c10f80ec4cb96b42b4037c63820fb2ac2b8d9d705197e6f24b7c
3
+ metadata.gz: 29636d76913839dee147b2f5ee9e346c561539c4fac3d9a329fa66529b9cf577
4
+ data.tar.gz: 54ac52ec5e5f57b066bc002763822907cb90b7343b4c6238bfadead2ff1ef4c4
5
5
  SHA512:
6
- metadata.gz: b86a4cc5de9c5f100e5cafaa174e38e083698ee713a6f2ecb0d0d43459249c1243831fffe376fd413421a998c975fab60291fe9a761f05fcfa47be444cfee1e4
7
- data.tar.gz: c3a7bb6400c890398f33d325e7d195bec40df55c8eda3d963cd8b0460cdababdcfbc80e6180ae380288464d2fef0d1889314b12517d611307e91ee005c13e757
6
+ metadata.gz: 505438695c2061a309f377d1ede630fac96f42adfd76a15b1ce0363066d1da0bba6b35fbbbc9883cbe55a2c3cf3c438f2ba0eec24d4d2c4c7b88d7f5c7a2ede6
7
+ data.tar.gz: ad8d25c6fc51f764203d13e5d340f3606946371a5a9ecfa7b068689b4ab2c9e7b2ad161d0edafbb650067cfdfbf125eb38c8af88b4ea4524aa872375a1d7895f
@@ -1,118 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fastlane/action'
4
- require_relative '../../helper/metadata/release_note_metadata_block'
5
- require_relative '../../helper/metadata/release_note_short_metadata_block'
6
- require_relative '../../helper/metadata/whats_new_metadata_block'
7
-
8
3
  module Fastlane
9
4
  module Actions
10
5
  class AnUpdateMetadataSourceAction < Action
11
6
  def self.run(params)
12
- # fastlane will take care of reading in the parameter and fetching the environment variable:
13
- UI.message "Parameter .po file path: #{params[:po_file_path]}"
14
- UI.message "Release version: #{params[:release_version]}"
15
-
16
- # Init
17
- create_block_parsers(params[:release_version], params[:source_files])
18
-
19
- # Do
20
- check_source_files(params[:source_files])
21
- temp_po_name = create_temp_po(params)
22
- swap_po(params[:po_file_path], temp_po_name)
23
-
24
- UI.message "File #{params[:po_file_path]} updated!"
25
- end
26
-
27
- # Verifies that all the source files are available
28
- # to this action
29
- def self.check_source_files(source_files)
30
- source_files.each_value do |file_path|
31
- UI.user_error!("Couldn't find file at path '#{file_path}'") unless File.exist?(file_path)
32
- end
33
- end
34
-
35
- # Creates a temp po file merging
36
- # new data for known tags
37
- # and the data already in the original
38
- # .po fo the others.
39
- def self.create_temp_po(params)
40
- orig = params[:po_file_path]
41
- target = create_target_file_path(orig)
42
-
43
- # Clear if older exists
44
- FileUtils.rm_f(target)
45
-
46
- # Create the new one
47
- begin
48
- File.open(target, 'a') do |fw|
49
- File.open(orig, 'r').each do |fr|
50
- write_target_block(fw, fr)
51
- end
52
- end
53
- rescue StandardError
54
- FileUtils.rm_f(target)
55
- raise
56
- end
57
-
58
- target
59
- end
60
-
61
- # Deletes the old po and moves the temp one
62
- # to the final location
63
- def self.swap_po(orig_file_path, temp_file_path)
64
- FileUtils.rm_f(orig_file_path)
65
- File.rename(temp_file_path, orig_file_path)
66
- end
67
-
68
- # Generates the temp file path
69
- def self.create_target_file_path(orig_file_path)
70
- "#{File.dirname(orig_file_path)}/#{File.basename(orig_file_path, '.*')}.tmp"
71
- end
72
-
73
- # Creates the block instances
74
- def self.create_block_parsers(release_version, block_files)
75
- @blocks = []
76
-
77
- # Inits default handler
78
- @blocks.push Fastlane::Helper::UnknownMetadataBlock.new
79
-
80
- # Init special handlers
81
- block_files.each do |key, file_path|
82
- case key
83
- when :release_note
84
- @blocks.push Fastlane::Helper::ReleaseNoteMetadataBlock.new(key, file_path, release_version)
85
- when :release_note_short
86
- @blocks.push Fastlane::Helper::ReleaseNoteShortMetadataBlock.new(key, file_path, release_version)
87
- else
88
- @blocks.push Fastlane::Helper::StandardMetadataBlock.new(key, file_path)
89
- end
90
- end
91
-
92
- # Sets the default
93
- @current_block = @blocks[0]
94
- end
95
-
96
- # Manages tags depending on the type
97
- def self.write_target_block(fw, line)
98
- if is_block_id(line)
99
- key = line.split[1].tr('\"', '')
100
- @blocks.each do |block|
101
- @current_block = block if block.is_handler_for(key)
102
- end
103
- end
104
-
105
- @current_block = @blocks.first if is_comment(line)
7
+ UI.deprecated('`an_update_metadata_source` is deprecated. Please use `gp_update_metadata_source` instead.')
106
8
 
107
- @current_block.handle_line(fw, line)
108
- end
109
-
110
- def self.is_block_id(line)
111
- line.start_with?('msgctxt')
112
- end
113
-
114
- def self.is_comment(line)
115
- line.start_with?('#')
9
+ other_action.gp_update_metadata_source(
10
+ po_file_path: params[:po_file_path],
11
+ source_files: params[:source_files],
12
+ release_version: params[:release_version]
13
+ )
116
14
  end
117
15
 
118
16
  #####################################################
@@ -120,38 +18,34 @@ module Fastlane
120
18
  #####################################################
121
19
 
122
20
  def self.description
123
- 'Updates a .po file with new data from .txt files'
21
+ 'Generates a .po file from source .txt files'
124
22
  end
125
23
 
126
24
  def self.details
127
- 'You can use this action to update the .po file that contains the string to load to GlotPress for localization.'
25
+ 'Generates a .po file from source .txt files for localization via GlotPress.'
128
26
  end
129
27
 
130
28
  def self.available_options
131
- # Define all options your action supports.
132
-
133
- # Below a few examples
134
29
  [
135
30
  FastlaneCore::ConfigItem.new(key: :po_file_path,
136
31
  env_name: 'FL_UPDATE_METADATA_SOURCE_PO_FILE_PATH',
137
- description: 'The path of the .po file to update',
32
+ description: 'The path of the .po file to generate',
138
33
  type: String,
139
34
  verify_block: proc do |value|
140
- UI.user_error!("No .po file path for UpdateMetadataSourceAction given, pass using `po_file_path: 'file path'`") unless value && !value.empty?
141
- UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
35
+ UI.user_error!("No .po file path given, pass using `po_file_path: 'file path'`") unless value && !value.empty?
142
36
  end),
143
37
  FastlaneCore::ConfigItem.new(key: :release_version,
144
38
  env_name: 'FL_UPDATE_METADATA_SOURCE_RELEASE_VERSION',
145
- description: 'The release version of the app (to use to mark the release notes)',
39
+ description: 'The release version of the app (used for release notes)',
146
40
  verify_block: proc do |value|
147
- UI.user_error!("No relase version for UpdateMetadataSourceAction given, pass using `release_version: 'version'`") unless value && !value.empty?
41
+ UI.user_error!("No release version given, pass using `release_version: 'version'`") unless value && !value.empty?
148
42
  end),
149
43
  FastlaneCore::ConfigItem.new(key: :source_files,
150
44
  env_name: 'FL_UPDATE_METADATA_SOURCE_SOURCE_FILES',
151
- description: 'The hash with the path to the source files and the key to use to include their content',
45
+ description: 'Hash mapping keys to source file paths',
152
46
  type: Hash,
153
47
  verify_block: proc do |value|
154
- UI.user_error!("No source file hash for UpdateMetadataSourceAction given, pass using `source_files: 'source file hash'`") unless value && !value.empty?
48
+ UI.user_error!("No source files given, pass using `source_files: { key: 'path' }`") unless value && !value.empty?
155
49
  end),
156
50
  ]
157
51
  end
@@ -160,7 +54,6 @@ module Fastlane
160
54
  end
161
55
 
162
56
  def self.return_value
163
- # If your method provides a return value, you can describe here what it does
164
57
  end
165
58
 
166
59
  def self.authors
@@ -170,6 +63,10 @@ module Fastlane
170
63
  def self.is_supported?(platform)
171
64
  [:android].include?(platform)
172
65
  end
66
+
67
+ def self.deprecated?
68
+ true
69
+ end
173
70
  end
174
71
  end
175
72
  end
@@ -1,117 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../helper/metadata/release_note_metadata_block'
4
- require_relative '../../helper/metadata/release_note_short_metadata_block'
5
- require_relative '../../helper/metadata/whats_new_metadata_block'
3
+ require_relative '../../helper/metadata/po_file_generator'
6
4
 
7
5
  module Fastlane
8
6
  module Actions
9
7
  class GpUpdateMetadataSourceAction < Action
10
8
  def self.run(params)
11
- # fastlane will take care of reading in the parameter and fetching the environment variable:
12
- UI.message "Parameter .po file path: #{params[:po_file_path]}"
9
+ UI.message "PO file path: #{params[:po_file_path]}"
13
10
  UI.message "Release version: #{params[:release_version]}"
14
11
 
15
- # Init
16
- create_block_parsers(params[:release_version], params[:source_files])
12
+ validate_source_files(params[:source_files])
17
13
 
18
- # Do
19
- check_source_files(params[:source_files])
20
- temp_po_name = create_temp_po(params)
21
- swap_po(params[:po_file_path], temp_po_name)
14
+ generator = Fastlane::Helper::PoFileGenerator.new(
15
+ release_version: params[:release_version],
16
+ source_files: params[:source_files]
17
+ )
22
18
 
23
- UI.message "File #{params[:po_file_path]} updated!"
24
- end
25
-
26
- # Verifies that all the source files are available
27
- # to this action
28
- def self.check_source_files(source_files)
29
- source_files.each_value do |file_path|
30
- UI.user_error!("Couldn't find file at path '#{file_path}'") unless File.exist?(file_path)
31
- end
32
- end
33
-
34
- # Creates a temp po file merging
35
- # new data for known tags
36
- # and the data already in the original
37
- # .po fo the others.
38
- def self.create_temp_po(params)
39
- orig = params[:po_file_path]
40
- target = create_target_file_path(orig)
41
-
42
- # Clear if older exists
43
- FileUtils.rm_f(target)
44
-
45
- # Create the new one
46
- begin
47
- File.open(target, 'a') do |fw|
48
- File.open(orig, 'r').each do |fr|
49
- write_target_block(fw, fr)
50
- end
51
- end
52
- rescue StandardError
53
- FileUtils.rm_f(target)
54
- raise
55
- end
56
-
57
- target
58
- end
19
+ generator.write(params[:po_file_path])
59
20
 
60
- # Deletes the old po and moves the temp one
61
- # to the final location
62
- def self.swap_po(orig_file_path, temp_file_path)
63
- FileUtils.rm_f(orig_file_path)
64
- File.rename(temp_file_path, orig_file_path)
65
- end
21
+ UI.success "File #{params[:po_file_path]} updated!"
66
22
 
67
- # Generates the temp file path
68
- def self.create_target_file_path(orig_file_path)
69
- "#{File.dirname(orig_file_path)}/#{File.basename(orig_file_path, '.*')}.tmp"
23
+ commit_changes(params) if params[:commit_changes]
70
24
  end
71
25
 
72
- # Creates the block instances
73
- def self.create_block_parsers(release_version, block_files)
74
- @blocks = []
75
-
76
- # Inits default handler
77
- @blocks.push Fastlane::Helper::UnknownMetadataBlock.new
78
-
79
- # Init special handlers
80
- block_files.each do |key, file_path|
81
- case key
82
- when :release_note
83
- @blocks.push Fastlane::Helper::ReleaseNoteMetadataBlock.new(key, file_path, release_version)
84
- when :whats_new
85
- @blocks.push Fastlane::Helper::WhatsNewMetadataBlock.new(key, file_path, release_version)
86
- else
87
- @blocks.push Fastlane::Helper::StandardMetadataBlock.new(key, file_path)
88
- end
89
- end
90
-
91
- # Sets the default
92
- @current_block = @blocks[0]
93
- end
94
-
95
- # Manages tags depending on the type
96
- def self.write_target_block(fw, line)
97
- if is_block_id(line)
98
- key = line.split[1].tr('\"', '')
99
- @blocks.each do |block|
100
- @current_block = block if block.is_handler_for(key)
101
- end
26
+ def self.validate_source_files(source_files)
27
+ source_files.each_value do |value|
28
+ file_path = value.is_a?(Hash) ? value[:path] : value
29
+ UI.user_error!("Couldn't find file at path '#{file_path}'") unless File.exist?(file_path)
102
30
  end
103
-
104
- @current_block = @blocks.first if is_comment(line)
105
-
106
- @current_block.handle_line(fw, line)
107
31
  end
108
32
 
109
- def self.is_block_id(line)
110
- line.start_with?('msgctxt')
111
- end
112
-
113
- def self.is_comment(line)
114
- line.start_with?('#')
33
+ def self.commit_changes(params)
34
+ other_action.git_add(path: params[:po_file_path])
35
+ other_action.git_commit(
36
+ path: params[:po_file_path],
37
+ message: 'Update metadata strings',
38
+ allow_nothing_to_commit: true
39
+ )
115
40
  end
116
41
 
117
42
  #####################################################
@@ -119,39 +44,56 @@ module Fastlane
119
44
  #####################################################
120
45
 
121
46
  def self.description
122
- 'Updates a .po file with new data from .txt files'
47
+ 'Generates a .po file from source .txt files'
123
48
  end
124
49
 
125
50
  def self.details
126
- 'You can use this action to update the .po file that contains the string to load to GlotPress for localization.'
51
+ <<~DETAILS
52
+ Generates a .po file from source .txt files for localization via GlotPress.
53
+
54
+ The `source_files` parameter accepts either simple file paths or hashes with path and comment:
55
+
56
+ ```ruby
57
+ source_files: {
58
+ # Simple path (no translator comment)
59
+ app_name: 'path/to/name.txt',
60
+
61
+ # Hash with path and translator comment
62
+ app_store_subtitle: {
63
+ path: 'path/to/subtitle.txt',
64
+ comment: 'translators: Limit to 30 characters!'
65
+ }
66
+ }
67
+ ```
68
+ DETAILS
127
69
  end
128
70
 
129
71
  def self.available_options
130
- # Define all options your action supports.
131
-
132
- # Below a few examples
133
72
  [
134
73
  FastlaneCore::ConfigItem.new(key: :po_file_path,
135
74
  env_name: 'FL_UPDATE_METADATA_SOURCE_PO_FILE_PATH',
136
- description: 'The path of the .po file to update',
75
+ description: 'The path of the .po file to generate',
137
76
  type: String,
138
77
  verify_block: proc do |value|
139
- UI.user_error!("No .po file path for UpdateMetadataSourceAction given, pass using `po_file_path: 'file path'`") unless value && !value.empty?
140
- UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
78
+ UI.user_error!("No .po file path given, pass using `po_file_path: 'file path'`") unless value && !value.empty?
141
79
  end),
142
80
  FastlaneCore::ConfigItem.new(key: :release_version,
143
81
  env_name: 'FL_UPDATE_METADATA_SOURCE_RELEASE_VERSION',
144
- description: 'The release version of the app (to use to mark the release notes)',
82
+ description: 'The release version of the app (used for release notes)',
145
83
  verify_block: proc do |value|
146
- UI.user_error!("No relase version for UpdateMetadataSourceAction given, pass using `release_version: 'version'`") unless value && !value.empty?
84
+ UI.user_error!("No release version given, pass using `release_version: 'version'`") unless value && !value.empty?
147
85
  end),
148
86
  FastlaneCore::ConfigItem.new(key: :source_files,
149
87
  env_name: 'FL_UPDATE_METADATA_SOURCE_SOURCE_FILES',
150
- description: 'The hash with the path to the source files and the key to use to include their content',
88
+ description: 'Hash mapping keys to file paths (String) or hashes with :path and optional :comment',
151
89
  type: Hash,
152
90
  verify_block: proc do |value|
153
- UI.user_error!("No source file hash for UpdateMetadataSourceAction given, pass using `source_files: 'source file hash'`") unless value && !value.empty?
91
+ UI.user_error!("No source files given, pass using `source_files: { key: 'path' }`") unless value && !value.empty?
154
92
  end),
93
+ FastlaneCore::ConfigItem.new(key: :commit_changes,
94
+ description: 'If true, adds and commits the changes',
95
+ type: Boolean,
96
+ default_value: false),
155
97
  ]
156
98
  end
157
99
 
@@ -159,7 +101,6 @@ module Fastlane
159
101
  end
160
102
 
161
103
  def self.return_value
162
- # If your method provides a return value, you can describe here what it does
163
104
  end
164
105
 
165
106
  def self.authors
@@ -62,6 +62,8 @@ module Fastlane
62
62
 
63
63
  UI.message("Unable to find device #{entry['device']}.") if device.nil?
64
64
 
65
+ UI.verbose("Processing entry:\n#{JSON.pretty_generate(entry)}")
66
+
65
67
  width = device['canvas_size'][0]
66
68
  height = device['canvas_size'][1]
67
69
 
@@ -93,7 +95,7 @@ module Fastlane
93
95
  FileUtils.rm_rf(path)
94
96
  Dir.mkdir(path)
95
97
  else
96
- UI.user_error!("Exiting to avoid overwriting #{description}.")
98
+ UI.abort_with_message!("Exiting to avoid overwriting #{description}.")
97
99
  end
98
100
  else
99
101
  Dir.mkdir(path)
@@ -4,23 +4,14 @@ module Fastlane
4
4
  module Actions
5
5
  class IosUpdateMetadataSourceAction < Action
6
6
  def self.run(params)
7
- # Check local repo status
8
- other_action.ensure_git_status_clean
7
+ UI.deprecated('`ios_update_metadata_source` is deprecated. Please use `gp_update_metadata_source` with `commit_changes: true` instead.')
9
8
 
10
- other_action.gp_update_metadata_source(po_file_path: params[:po_file_path],
11
- source_files: params[:source_files],
12
- release_version: params[:release_version])
13
-
14
- Action.sh("git add #{params[:po_file_path]}")
15
- params[:source_files].each_value do |file|
16
- Action.sh("git add #{file}")
17
- end
18
-
19
- repo_status = Actions.sh('git status --porcelain')
20
- repo_clean = repo_status.empty?
21
- return if repo_clean
22
-
23
- Action.sh('git commit -m "Update metadata strings"')
9
+ other_action.gp_update_metadata_source(
10
+ po_file_path: params[:po_file_path],
11
+ source_files: params[:source_files],
12
+ release_version: params[:release_version],
13
+ commit_changes: true
14
+ )
24
15
  end
25
16
 
26
17
  #####################################################
@@ -36,17 +27,13 @@ module Fastlane
36
27
  end
37
28
 
38
29
  def self.available_options
39
- # Define all options your action supports.
40
-
41
- # Below a few examples
42
30
  [
43
31
  FastlaneCore::ConfigItem.new(key: :po_file_path,
44
32
  env_name: 'FL_IOS_UPDATE_METADATA_SOURCE_PO_FILE_PATH',
45
- description: 'The path of the .po file to update',
33
+ description: 'The path of the .po file to generate',
46
34
  type: String,
47
35
  verify_block: proc do |value|
48
36
  UI.user_error!("No .po file path for UpdateMetadataSourceAction given, pass using `po_file_path: 'file path'`") unless value && !value.empty?
49
- UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
50
37
  end),
51
38
  FastlaneCore::ConfigItem.new(key: :release_version,
52
39
  env_name: 'FL_IOS_UPDATE_METADATA_SOURCE_RELEASE_VERSION',
@@ -77,6 +64,10 @@ module Fastlane
77
64
  def self.is_supported?(platform)
78
65
  %i[ios mac].include?(platform)
79
66
  end
67
+
68
+ def self.deprecated?
69
+ true
70
+ end
80
71
  end
81
72
  end
82
73
  end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gettext/po'
4
+ require 'gettext/po_entry'
5
+ require_relative '../../version'
6
+
7
+ module Fastlane
8
+ module Helper
9
+ # Generates PO/POT files from source text files.
10
+ #
11
+ # This class generates gettext PO files from a hash of source files,
12
+ # handling special cases like versioned release notes and what's new entries.
13
+ #
14
+ # @example Basic usage with file paths
15
+ # generator = PoFileGenerator.new(
16
+ # release_version: '1.0',
17
+ # source_files: {
18
+ # app_name: '/path/to/name.txt',
19
+ # description: '/path/to/desc.txt'
20
+ # }
21
+ # )
22
+ #
23
+ # @example With translator comments
24
+ # generator = PoFileGenerator.new(
25
+ # release_version: '1.0',
26
+ # source_files: {
27
+ # app_store_subtitle: {
28
+ # path: '/path/to/subtitle.txt',
29
+ # comment: 'translators: Limit to 30 characters!'
30
+ # },
31
+ # description: '/path/to/desc.txt' # no comment
32
+ # }
33
+ # )
34
+ class PoFileGenerator
35
+ # @param release_version [String] The release version (e.g., "1.23")
36
+ # @param source_files [Hash] A hash mapping keys to file paths (String) or hashes with :path and :comment keys
37
+ def initialize(release_version:, source_files:)
38
+ @release_version = release_version
39
+ @source_files = source_files
40
+ end
41
+
42
+ # Generates the PO file content as a string
43
+ # @return [String] The generated PO file content
44
+ def generate
45
+ po = GetText::PO.new
46
+ # Disable GetText's internal sorting so we control entry order via our own sort_by(:msgctxt)
47
+ po.order = :none
48
+
49
+ # Add standard PO header
50
+ add_header(po)
51
+
52
+ # Collect all entries first, then sort by msgctxt for deterministic output
53
+ entries = []
54
+ @source_files.each do |key, value|
55
+ path, comment = extract_path_and_comment(value)
56
+ content = File.read(path)
57
+ entries.concat(create_entries_for_key(key.to_sym, content, comment))
58
+ end
59
+
60
+ # Sort entries alphabetically by msgctxt and add to PO
61
+ entries.sort_by(&:msgctxt).each do |entry|
62
+ po[entry.msgctxt, entry.msgid] = entry
63
+ end
64
+
65
+ # GetText::PO#to_s doesn't add a trailing newline
66
+ "#{po}\n"
67
+ end
68
+
69
+ # Writes the generated PO content to a file
70
+ # @param output_path [String] The path to write to
71
+ def write(output_path)
72
+ File.write(output_path, generate)
73
+ end
74
+
75
+ private
76
+
77
+ def add_header(po_data)
78
+ revision_date = Time.now.strftime('%Y-%m-%d %H:%M%z')
79
+ generator = "#{Fastlane::Wpmreleasetoolkit::NAME} #{Fastlane::Wpmreleasetoolkit::VERSION}"
80
+
81
+ header_content = <<~HEADER
82
+ PO-Revision-Date: #{revision_date}
83
+ MIME-Version: 1.0
84
+ Content-Type: text/plain; charset=UTF-8
85
+ Content-Transfer-Encoding: 8bit
86
+ Plural-Forms: nplurals=2; plural=n != 1;
87
+ X-Generator: #{generator}
88
+ HEADER
89
+
90
+ header = GetText::POEntry.new(:normal)
91
+ header.msgid = ''
92
+ header.msgstr = header_content
93
+ po_data[header.msgctxt, header.msgid] = header
94
+ end
95
+
96
+ # Extracts path and comment from a source_files value
97
+ # @param value [String, Hash] Either a file path string or a hash with :path and optional :comment
98
+ # @return [Array<String, String|nil>] A tuple of [path, comment]
99
+ def extract_path_and_comment(value)
100
+ case value
101
+ when String
102
+ [value, nil]
103
+ when Hash
104
+ UI.user_error!("Hash must contain :path key, got: #{value.keys}") unless value.key?(:path)
105
+ [value[:path], value[:comment]]
106
+ else
107
+ raise ArgumentError, "Invalid source_files value: expected String or Hash, got #{value.class}"
108
+ end
109
+ end
110
+
111
+ def create_entries_for_key(key, content, comment = nil)
112
+ case key
113
+ when :whats_new
114
+ create_whats_new_entries(content, comment)
115
+ when :release_note
116
+ create_release_note_entries(content, comment)
117
+ when :release_note_short
118
+ create_release_note_short_entries(content, comment)
119
+ else
120
+ [create_standard_entry(key.to_s, content, comment)]
121
+ end
122
+ end
123
+
124
+ def create_standard_entry(msgctxt, content, comment = nil)
125
+ create_entry(msgctxt, content.rstrip, comment)
126
+ end
127
+
128
+ def create_whats_new_entries(content, comment = nil)
129
+ return [] if content.strip.empty?
130
+
131
+ msgctxt = "v#{@release_version}-whats-new"
132
+ # Ensure content ends with newline for multiline formatting
133
+ msgid = content.end_with?("\n") ? content : "#{content}\n"
134
+ [create_entry(msgctxt, msgid, comment)]
135
+ end
136
+
137
+ def create_release_note_entries(content, comment = nil)
138
+ key = release_note_key_for_version(@release_version)
139
+ msgid = "#{@release_version}:\n#{content}"
140
+ msgid = "#{msgid}\n" unless msgid.end_with?("\n")
141
+ [create_entry(key, msgid, comment)]
142
+ end
143
+
144
+ def create_release_note_short_entries(content, comment = nil)
145
+ return [] if content.strip.empty?
146
+
147
+ key = release_note_short_key_for_version(@release_version)
148
+ msgid = "#{@release_version}:\n#{content}"
149
+ msgid = "#{msgid}\n" unless msgid.end_with?("\n")
150
+ [create_entry(key, msgid, comment)]
151
+ end
152
+
153
+ def create_entry(msgctxt, msgid, comment = nil)
154
+ entry = GetText::POEntry.new(:msgctxt)
155
+ entry.msgctxt = msgctxt
156
+ entry.msgid = msgid
157
+ entry.msgstr = ''
158
+ entry.extracted_comment = comment if comment
159
+ entry
160
+ end
161
+
162
+ def release_note_key_for_version(version)
163
+ versioned_key('release_note', version)
164
+ end
165
+
166
+ def release_note_short_key_for_version(version)
167
+ versioned_key('release_note_short', version)
168
+ end
169
+
170
+ def versioned_key(prefix, version)
171
+ major, minor = parse_version(version)
172
+ "#{prefix}_#{major.to_s.rjust(2, '0')}#{minor}"
173
+ end
174
+
175
+ def parse_version(version)
176
+ parts = version.split('.')
177
+ UI.user_error!("Invalid version format '#{version}': expected 'major.minor' (e.g., '1.2')") if parts.length < 2
178
+
179
+ begin
180
+ [Integer(parts[0]), Integer(parts[1])]
181
+ rescue ArgumentError
182
+ UI.user_error!("Invalid version format '#{version}': major and minor must be integers")
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -33,7 +33,13 @@ module Fastlane
33
33
  UI.user_error!(message)
34
34
  end
35
35
 
36
- UI.user_error!('`drawText` not found – install it using `brew install automattic/build-tools/drawText`.') unless system('command -v drawText')
36
+ UI.user_error!('`drawText` not found – install it using `brew install automattic/build-tools/drawText`.') unless self.class.draw_text_available?
37
+ end
38
+
39
+ def self.draw_text_available?
40
+ return @draw_text_available if defined?(@draw_text_available)
41
+
42
+ @draw_text_available = system('command -v drawText', %i[out err] => File::NULL)
37
43
  end
38
44
 
39
45
  def read_config(config_file_path)
@@ -287,7 +293,17 @@ module Fastlane
287
293
  begin
288
294
  temp_text_file = Tempfile.new
289
295
 
290
- Action.sh('drawText', "html=#{text}", "maxWidth=#{width}", "maxHeight=#{height}", "output=#{temp_text_file.path}", "fontSize=#{font_size}", "stylesheet=#{stylesheet_path}", "alignment=#{position}")
296
+ Actions.sh(
297
+ 'drawText',
298
+ "html=#{text}",
299
+ "maxWidth=#{width}",
300
+ "maxHeight=#{height}",
301
+ "output=#{temp_text_file.path}",
302
+ "fontSize=#{font_size}",
303
+ "stylesheet=#{stylesheet_path}",
304
+ "alignment=#{position}",
305
+ log: false
306
+ )
291
307
 
292
308
  text_content = open_image(temp_text_file.path).trim
293
309
  text_frame = create_image(width, height)
@@ -407,8 +423,8 @@ module Fastlane
407
423
  working_background = background.frozen? ? background.dup : background
408
424
  working_background.paint.to_hex
409
425
 
410
- Image.new(width, height) do
411
- self.background_color = working_background
426
+ Image.new(width, height) do |info|
427
+ info.background_color = working_background
412
428
  end
413
429
  end
414
430
 
@@ -432,8 +448,12 @@ module Fastlane
432
448
  return resolved_path if !resolved_path.nil? && resolved_path.exist?
433
449
  end
434
450
 
435
- message = "Unable to locate #{path}"
436
- UI.crash!(message)
451
+ message = <<~MESSAGE
452
+ Unable to locate #{path}.
453
+
454
+ Did you run the automation to generate the screenshots?
455
+ MESSAGE
456
+ UI.user_error!(message)
437
457
  end
438
458
 
439
459
  def resolve_text_into_path(text, locale)
@@ -444,6 +464,7 @@ module Fastlane
444
464
  elsif can_resolve_path(localized_file)
445
465
  resolve_path(localized_file).realpath.to_s
446
466
  else
467
+ UI.important("Could not identify '#{localized_file}' as a file path or the file was not found. Will use its value as a raw string. This may result in undesired annotations.")
447
468
  format(text, 'source')
448
469
  end
449
470
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Fastlane
4
4
  module Wpmreleasetoolkit
5
- VERSION = '13.8.1'
5
+ NAME = 'fastlane-plugin-wpmreleasetoolkit'
6
+ VERSION = '14.0.0'
6
7
  end
7
8
  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.8.1
4
+ version: 14.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Automattic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-15 00:00:00.000000000 Z
11
+ date: 2026-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -72,14 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2.213'
75
+ version: '2.231'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2.213'
82
+ version: '2.231'
83
+ - !ruby/object:Gem::Dependency
84
+ name: gettext
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: git
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -473,12 +487,7 @@ files:
473
487
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_linter_helper.rb
474
488
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_strings_file_validation_helper.rb
475
489
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_version_helper.rb
476
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/metadata_block.rb
477
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/release_note_metadata_block.rb
478
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/release_note_short_metadata_block.rb
479
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/standard_metadata_block.rb
480
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/unknown_metadata_block.rb
481
- - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/whats_new_metadata_block.rb
490
+ - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata/po_file_generator.rb
482
491
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/metadata_download_helper.rb
483
492
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/promo_screenshots_helper.rb
484
493
  - lib/fastlane/plugin/wpmreleasetoolkit/helper/release_notes_helper.rb
@@ -528,5 +537,5 @@ requirements: []
528
537
  rubygems_version: 3.4.10
529
538
  signing_key:
530
539
  specification_version: 4
531
- summary: GitHub helper functions
540
+ summary: Fastlane plugin for release automation
532
541
  test_files: []
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fastlane
4
- module Helper
5
- # Basic line handler
6
- class MetadataBlock
7
- attr_reader :block_key
8
-
9
- def initialize(block_key)
10
- @block_key = block_key
11
- end
12
-
13
- def handle_line(file, line)
14
- file.puts(line) # Standard line handling: just copy
15
- end
16
-
17
- def is_handler_for(key)
18
- true
19
- end
20
- end
21
- end
22
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'metadata_block'
4
- require_relative 'standard_metadata_block'
5
-
6
- module Fastlane
7
- module Helper
8
- class ReleaseNoteMetadataBlock < StandardMetadataBlock
9
- attr_reader :new_key, :keep_key, :rel_note_key, :release_version
10
-
11
- def initialize(block_key, content_file_path, release_version)
12
- super(block_key, content_file_path)
13
- @rel_note_key = 'release_note'
14
- @release_version = release_version
15
- generate_keys(release_version)
16
- end
17
-
18
- def generate_keys(release_version)
19
- values = release_version.split('.')
20
- version_major = Integer(values[0])
21
- version_minor = Integer(values[1])
22
- @new_key = "#{@rel_note_key}_#{version_major.to_s.rjust(2, '0')}#{version_minor}"
23
-
24
- version_major -= 1 if version_minor.zero?
25
- version_minor = version_minor.zero? ? 9 : version_minor - 1
26
-
27
- @keep_key = "#{@rel_note_key}_#{version_major.to_s.rjust(2, '0')}#{version_minor}"
28
- end
29
-
30
- def is_handler_for(key)
31
- values = key.split('_')
32
- key.start_with?(@rel_note_key) && values.length == 3 && is_int?(values[2].sub(/^0*/, ''))
33
- end
34
-
35
- def handle_line(file, line)
36
- # put content on block start or if copying the latest one
37
- # and skip all the other content
38
- if line.start_with?('msgctxt')
39
- key = extract_key(line)
40
- @is_copying = (key == @keep_key)
41
- generate_block(file) if @is_copying
42
- end
43
-
44
- file.puts(line) if @is_copying
45
- end
46
-
47
- def generate_block(file)
48
- # init
49
- file.puts("msgctxt \"#{@new_key}\"")
50
- file.puts('msgid ""')
51
- file.puts("\"#{@release_version}:\\n\"")
52
-
53
- # insert content
54
- File.open(@content_file_path, 'r').each do |line|
55
- file.puts("\"#{line.strip}\\n\"")
56
- end
57
-
58
- # close
59
- file.puts('msgstr ""')
60
- file.puts('')
61
- end
62
-
63
- def extract_key(line)
64
- line.split[1].tr('\"', '')
65
- end
66
-
67
- def is_int?(value)
68
- true if Integer(value)
69
- rescue StandardError
70
- false
71
- end
72
- end
73
- end
74
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'release_note_metadata_block'
4
-
5
- module Fastlane
6
- module Helper
7
- class ReleaseNoteShortMetadataBlock < ReleaseNoteMetadataBlock
8
- def initialize(block_key, content_file_path, release_version)
9
- super
10
- @rel_note_key = 'release_note_short'
11
- @release_version = release_version
12
- generate_keys(release_version)
13
- end
14
-
15
- def is_handler_for(key)
16
- values = key.split('_')
17
- key.start_with?(@rel_note_key) && values.length == 4 && is_int?(values[3].sub(/^0*/, ''))
18
- end
19
-
20
- def generate_block(file)
21
- super unless File.empty?(@content_file_path)
22
- end
23
- end
24
- end
25
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'metadata_block'
4
-
5
- module Fastlane
6
- module Helper
7
- class StandardMetadataBlock < MetadataBlock
8
- attr_reader :content_file_path
9
-
10
- def initialize(block_key, content_file_path)
11
- super(block_key)
12
- @content_file_path = content_file_path
13
- end
14
-
15
- def is_handler_for(key)
16
- key == @block_key.to_s
17
- end
18
-
19
- def handle_line(file, line)
20
- # put the new content on block start
21
- # and skip all the other content
22
- generate_block(file) if line.start_with?('msgctxt')
23
- end
24
-
25
- def generate_block(file)
26
- # init
27
- file.puts("msgctxt \"#{@block_key}\"")
28
- line_count = File.foreach(@content_file_path).inject(0) { |c, _line| c + 1 }
29
-
30
- if line_count <= 1
31
- # Single line output
32
- file.puts("msgid \"#{File.read(@content_file_path).rstrip}\"")
33
- else
34
- # Multiple line output
35
- file.puts('msgid ""')
36
-
37
- # insert content
38
- File.open(@content_file_path, 'r').each do |line|
39
- file.puts("\"#{line.strip}\\n\"")
40
- end
41
- end
42
-
43
- # close
44
- file.puts('msgstr ""')
45
- file.puts('')
46
- end
47
- end
48
- end
49
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'metadata_block'
4
-
5
- module Fastlane
6
- module Helper
7
- class UnknownMetadataBlock < MetadataBlock
8
- attr_reader :content_file_path
9
-
10
- def initialize
11
- super(nil)
12
- end
13
- end
14
- end
15
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'standard_metadata_block'
4
-
5
- module Fastlane
6
- module Helper
7
- class WhatsNewMetadataBlock < StandardMetadataBlock
8
- attr_reader :new_key, :old_key, :rel_note_key, :release_version
9
-
10
- def initialize(block_key, content_file_path, release_version)
11
- super(block_key, content_file_path)
12
- @rel_note_key = 'whats_new'
13
- @release_version = release_version
14
- generate_keys(release_version)
15
- end
16
-
17
- def generate_keys(release_version)
18
- values = release_version.split('.')
19
- version_major = Integer(values[0])
20
- version_minor = Integer(values[1])
21
- @new_key = "v#{release_version}-whats-new"
22
-
23
- version_major -= 1 if version_minor.zero?
24
- version_minor = version_minor.zero? ? 9 : version_minor - 1
25
-
26
- @old_key = "v#{version_major}.#{version_minor}-whats-new"
27
- end
28
-
29
- def is_handler_for(key)
30
- key.start_with?('v') && key.end_with?('-whats-new')
31
- end
32
-
33
- def handle_line(file, line)
34
- # put content on block start or if copying the latest one
35
- # and skip all the other content
36
- generate_block(file) if line.start_with?('msgctxt')
37
- end
38
-
39
- def generate_block(file)
40
- # init
41
- file.puts("msgctxt \"#{@new_key}\"")
42
- file.puts('msgid ""')
43
-
44
- # insert content
45
- File.open(@content_file_path, 'r').each do |line|
46
- file.puts("\"#{line.strip}\\n\"")
47
- end
48
-
49
- # close
50
- file.puts('msgstr ""')
51
- file.puts('')
52
- end
53
- end
54
- end
55
- end