Skip to content

Commit 26649fa

Browse files
committed
Merge remote-tracking branch 'origin/trunk' into foundation
# Conflicts: # CHANGELOG.md
2 parents 4fe6dfc + 88bfb27 commit 26649fa

11 files changed

Lines changed: 676 additions & 11 deletions

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ _None_
1010

1111
### New Features
1212

13-
- Added `find_or_create_pull_request` action and `GithubHelper#find_pull_request`: returns the URL of the open Pull Request for a head branch, creating one only if none exists yet. Useful for "rolling" automations (e.g. a daily translations or dependency-update job) that force-push the same head branch on every run. [#733]
13+
_None_
1414

1515
### Bug Fixes
1616

@@ -20,12 +20,27 @@ _None_
2020
- `L10nHelper.merge_strings` now applies the key prefix via a comment-aware tokenizer, so it correctly prefixes unquoted keys containing `. - $ : /`, lines with an *unquoted* value (e.g. `CFBundleName = WordPress;`), and keys sitting behind an inter-token `.strings` comment (e.g. `CFBundleName /* note */ = WordPress;`) — matching the grammar `plutil` accepts. Previously the line-based matcher left those keys written to the merged file without the prefix while still bookkeeping them *with* it, leaving the output inconsistent with the reported keys (which could resurface the very collisions the prefix avoids and break downstream key extraction). [#741]
2121
- `StringsFileValidationHelper.find_duplicated_keys` (and `scan_for_duplicate_keys`) now tokenize dictionary- and array-valued entries (`"k" = { … };`, `"k" = ( … );`, nesting allowed), skipping the container body — so a `:text` file that uses them is scanned for duplicate *top-level* keys instead of returning `:unscannable`. [#750]
2222
- `L10nHelper.merge_strings` now prefixes the outer key of a dictionary/array-valued entry and copies the value through verbatim, instead of crashing on it — valid input `plutil` accepts that the flat-`.strings` tokenizer previously couldn't rewrite. If a file still holds a construct the tokenizer can't rewrite, its lines are copied through unprefixed with a `UI.important` warning (and its keys are bookkept unprefixed to match, so a genuine cross-file collision is still reported rather than silently collapsed) instead of aborting the whole merge. [#750]
23+
- Bump `faraday` and `nokogiri` to address security vulnerabilities. [#749]
24+
- Bump `concurrent-ruby` to address CVE-2026-54904 / GHSA-h8w8-99g7-qmvj. [#751]
2325

2426
### Internal Changes
2527

2628
- Centralized `.strings` duplicate-key detection behind `StringsFileValidationHelper.scan_for_duplicate_keys`, which detects the file format and returns a `[:scanned | :unsupported_format | :unscannable, payload]` tri-state that callers map to their own policy (warn-and-skip vs fail-closed). `ios_lint_localizations` now uses it. [#741]
2729
- `L10nHelper.strings_file_type` (and `StringsFileValidationHelper.scan_for_duplicate_keys`) accept `assume_valid:` to skip a redundant `plutil -lint` when the caller has already parsed the file. [#741]
2830

31+
## 14.9.0
32+
33+
### New Features
34+
35+
- `upload_github_release_assets` action: uploads assets on an existing GitHub release without disturbing unrelated assets. If assets exists already, it replaces them. [#743]
36+
37+
## 14.8.0
38+
39+
### New Features
40+
41+
- `find_or_create_pull_request` action: returns the URL of the open Pull Request for a head branch, creating one only if none exists yet. [#733]
42+
- `ContinuousBuildCodeFormatter`: derives an Android Play Store `versionCode` for a "continuous trunk" release model as `(major * 10 + minor) * 10^build_digits + build_number` (default `build_digits: 6`). [#735]
43+
2944
## 14.7.0
3045

3146
### New Features

Gemfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ gem 'danger-dangermattic', '~> 1.0'
1010
gem 'webmock', require: false
1111
gem 'yard'
1212

13-
# Security: https://github.com/lostisland/faraday/pull/1665
13+
# Security:
14+
# - https://github.com/lostisland/faraday/pull/1665
15+
# - https://github.com/lostisland/faraday/pull/1681
1416
# Faraday 2.0 is not compatible with Fastlane
15-
gem 'faraday', '~> 1.10', '>= 1.10.5'
17+
gem 'faraday', '~> 1.10', '>= 1.10.6'

Gemfile.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
fastlane-plugin-wpmreleasetoolkit (14.7.0)
4+
fastlane-plugin-wpmreleasetoolkit (14.9.0)
55
buildkit (~> 1.5)
66
chroma (= 0.2.0)
77
diffy (~> 3.3)
@@ -11,7 +11,7 @@ PATH
1111
git (~> 1.3)
1212
google-cloud-storage (~> 1.31)
1313
java-properties (~> 0.3.0)
14-
nokogiri (~> 1.19, >= 1.19.3)
14+
nokogiri (~> 1.19, >= 1.19.4)
1515
octokit (~> 6.1)
1616
parallel (~> 1.14)
1717
plist (~> 3.1)
@@ -83,7 +83,7 @@ GEM
8383
colored2 (3.1.2)
8484
commander (4.6.0)
8585
highline (~> 2.0.0)
86-
concurrent-ruby (1.3.6)
86+
concurrent-ruby (1.3.7)
8787
connection_pool (3.0.2)
8888
cork (0.3.0)
8989
colored2 (~> 3.1)
@@ -127,7 +127,7 @@ GEM
127127
emoji_regex (3.2.3)
128128
erubi (1.13.1)
129129
excon (0.112.0)
130-
faraday (1.10.5)
130+
faraday (1.10.6)
131131
faraday-em_http (~> 1.0)
132132
faraday-em_synchrony (~> 1.0)
133133
faraday-excon (~> 1.1)
@@ -298,7 +298,7 @@ GEM
298298
nap (1.1.0)
299299
naturally (2.3.0)
300300
nkf (0.2.0)
301-
nokogiri (1.19.3)
301+
nokogiri (1.19.4)
302302
mini_portile2 (~> 2.8.2)
303303
racc (~> 1.4)
304304
observer (0.1.2)
@@ -439,7 +439,7 @@ DEPENDENCIES
439439
bundler (~> 2.0)
440440
codecov
441441
danger-dangermattic (~> 1.0)
442-
faraday (~> 1.10, >= 1.10.5)
442+
faraday (~> 1.10, >= 1.10.6)
443443
fastlane (~> 2.210)
444444
fastlane-plugin-wpmreleasetoolkit!
445445
pry (~> 0.12.2)

fastlane-plugin-wpmreleasetoolkit.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
3434
spec.add_dependency 'gettext', '~> 3.5'
3535
spec.add_dependency 'git', '~> 1.3'
3636
spec.add_dependency 'java-properties', '~> 0.3.0'
37-
spec.add_dependency 'nokogiri', '~> 1.19', '>= 1.19.3'
37+
spec.add_dependency 'nokogiri', '~> 1.19', '>= 1.19.4'
3838
spec.add_dependency 'octokit', '~> 6.1'
3939
spec.add_dependency 'parallel', '~> 1.14'
4040
spec.add_dependency 'plist', '~> 3.1'
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
require 'fastlane/action'
4+
require_relative '../../helper/github_helper'
5+
6+
module Fastlane
7+
module Actions
8+
class UploadGithubReleaseAssetsAction < Action
9+
def self.run(params)
10+
repository = params[:repository]
11+
version = params[:version]
12+
assets = params[:release_assets]
13+
replace_existing = params[:replace_existing]
14+
15+
UI.message("Uploading #{assets.count} GitHub Release asset(s) to #{repository} #{version}.")
16+
17+
github_helper = Fastlane::Helper::GithubHelper.new(github_token: params[:github_token])
18+
url = github_helper.upload_release_assets(
19+
repository: repository,
20+
version: version,
21+
assets: assets,
22+
replace_existing: replace_existing
23+
)
24+
25+
UI.success("Successfully uploaded GitHub Release assets. You can see the release at '#{url}'")
26+
url
27+
end
28+
29+
def self.description
30+
'Uploads assets to an existing GitHub Release'
31+
end
32+
33+
def self.authors
34+
['Automattic']
35+
end
36+
37+
def self.return_value
38+
'The URL of the GitHub Release'
39+
end
40+
41+
def self.details
42+
'Uploads assets to an existing GitHub Release. By default, existing release assets with matching filenames are replaced; when replace_existing is false, matching assets cause the action to fail.'
43+
end
44+
45+
def self.available_options
46+
[
47+
FastlaneCore::ConfigItem.new(key: :repository,
48+
description: 'The slug (`<org>/<repo>`) of the GitHub repository containing the release',
49+
optional: false,
50+
type: String,
51+
verify_block: proc do |value|
52+
UI.user_error!('Repository cannot be empty') if value.to_s.empty?
53+
end),
54+
FastlaneCore::ConfigItem.new(key: :version,
55+
description: 'The version of the release. Used as the git tag name',
56+
optional: false,
57+
type: String,
58+
verify_block: proc do |value|
59+
UI.user_error!('Version cannot be empty') if value.to_s.empty?
60+
end),
61+
FastlaneCore::ConfigItem.new(key: :release_assets,
62+
description: 'Assets to upload',
63+
type: Array,
64+
optional: false,
65+
verify_block: proc do |value|
66+
UI.user_error!('You must provide at least one release asset') if value.nil? || value.empty?
67+
value.each do |asset|
68+
UI.user_error!('release_assets must contain file paths') unless asset.is_a?(String) && !asset.empty?
69+
end
70+
end),
71+
FastlaneCore::ConfigItem.new(key: :replace_existing,
72+
description: 'True to delete existing release assets with matching filenames before uploading. False to fail if a matching asset exists',
73+
optional: true,
74+
default_value: true,
75+
type: Boolean),
76+
Fastlane::Helper::GithubHelper.github_token_config_item,
77+
]
78+
end
79+
80+
def self.is_supported?(platform)
81+
true
82+
end
83+
end
84+
end
85+
end

lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,58 @@ def create_release(repository:, version:, description:, assets:, prerelease:, is
192192
release[:html_url]
193193
end
194194

195+
# Returns the GitHub release matching a given tag/version, including draft releases.
196+
#
197+
# @param [String] repository The repository to fetch the GitHub release from. Typically a repo slug (<org>/<repo>).
198+
# @param [String] version The release version/tag to fetch.
199+
# @return [Sawyer::Resource] The matching GitHub Release.
200+
# @raise [Fastlane::UI::Error] UI.user_error! if the release does not exist.
201+
#
202+
def get_release(repository:, version:)
203+
release = client.releases(repository).find { |candidate| candidate.tag_name == version }
204+
return release unless release.nil?
205+
206+
UI.user_error!("Could not find GitHub Release for tag #{version} in #{repository}")
207+
end
208+
209+
# Uploads assets to an existing GitHub release, optionally replacing matching filenames.
210+
#
211+
# @param [String] repository The repository to upload the GitHub release assets to. Typically a repo slug (<org>/<repo>).
212+
# @param [String] version The release version/tag to upload assets to.
213+
# @param [Array<String>] assets List of local file paths to attach as release assets.
214+
# @param [TrueClass|FalseClass] replace_existing Delete existing same-filename assets before uploading. When false, fail if a matching asset exists.
215+
# @return [String] URL of the corresponding GitHub Release.
216+
# @raise [Fastlane::UI::Error] UI.user_error! if the release or any local asset file does not exist.
217+
#
218+
def upload_release_assets(repository:, version:, assets:, replace_existing: true)
219+
asset_paths = validate_release_assets!(assets)
220+
release = get_release(repository: repository, version: version)
221+
existing_assets = client.release_assets(release.url)
222+
223+
asset_paths.each do |file_path|
224+
file_name = File.basename(file_path)
225+
matching_assets = existing_assets.select { |asset| asset.name == file_name }
226+
227+
unless matching_assets.empty?
228+
if replace_existing
229+
matching_assets.each do |asset|
230+
UI.message("Deleting existing GitHub Release asset #{asset.name}")
231+
client.delete_release_asset(asset.url)
232+
end
233+
existing_assets -= matching_assets
234+
else
235+
UI.user_error!("GitHub Release #{version} already has an asset named #{file_name}. Set replace_existing: true to replace it.")
236+
end
237+
end
238+
239+
UI.message("Uploading #{file_path} to GitHub Release #{version}")
240+
uploaded_asset = client.upload_asset(release.url, file_path, content_type: 'application/octet-stream')
241+
existing_assets << uploaded_asset unless uploaded_asset.nil?
242+
end
243+
244+
release.html_url
245+
end
246+
195247
# Use the GitHub API to generate release notes based on the list of PRs between current tag and previous tag.
196248
# @note This API uses the `.github/release.yml` config file to classify the PRs by category in the generated list according to PR labels.
197249
#
@@ -368,6 +420,26 @@ def set_branch_protection(repository:, branch:, **options)
368420
client.protect_branch(repository, branch, options)
369421
end
370422

423+
def validate_release_assets!(assets)
424+
asset_paths = Array(assets)
425+
UI.user_error!('You must provide at least one release asset') if asset_paths.empty?
426+
427+
asset_paths.each do |file_path|
428+
UI.user_error!('release_assets must contain file paths') unless file_path.is_a?(String) && !file_path.empty?
429+
end
430+
431+
file_names = asset_paths.map { |file_path| File.basename(file_path) }
432+
UI.user_error!('release_assets must not contain duplicate filenames') if file_names.uniq.length != file_names.length
433+
434+
asset_paths.each do |file_path|
435+
UI.user_error!("Can't find file #{file_path}!") unless File.file?(file_path)
436+
end
437+
438+
asset_paths
439+
end
440+
441+
private :validate_release_assets!
442+
371443
# Convert a response from the `/branch-protection` API endpoint into a Hash
372444
# suitable to be returned and/or reused to pass to a subsequent `/branch-protection` API request
373445
# @param [Sawyer::Resource] response The API response returned by `#get_branch_protection` or `#set_branch_protection`

lib/fastlane/plugin/wpmreleasetoolkit/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
module Fastlane
44
module Wpmreleasetoolkit
55
NAME = 'fastlane-plugin-wpmreleasetoolkit'
6-
VERSION = '14.7.0'
6+
VERSION = '14.9.0'
77
end
88
end

0 commit comments

Comments
 (0)