Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ _None_
- `StringsFileValidationHelper.find_duplicated_keys` now parses unquoted keys/values and inter-token comments, matching the grammar `plutil` accepts, instead of raising `Invalid character`. This lets `ios_lint_localizations`' `check_duplicate_keys` work on `InfoPlist.strings`-style files. [#741]
- `ios_lint_localizations`' `check_duplicate_keys` now warns and skips, rather than crashing, on a file that parses as a property list but isn't a tokenizable flat `.strings`. [#741]
- `L10nHelper.merge_strings` now prefixes keys via a comment-aware tokenizer, so unquoted keys, unquoted values, and keys behind an inter-token comment are prefixed in the output consistently with the reported keys. [#741]
- `upload_github_release_assets`: make release lookup more resilient by falling back to GitHub's direct release-by-tag lookup when the releases list does not include the requested release. [#753]

### Internal Changes

Expand Down
18 changes: 17 additions & 1 deletion lib/fastlane/plugin/wpmreleasetoolkit/helper/github_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def create_release(repository:, version:, description:, assets:, prerelease:, is
# @raise [Fastlane::UI::Error] UI.user_error! if the release does not exist.
#
def get_release(repository:, version:)
release = client.releases(repository).find { |candidate| candidate.tag_name == version }
release = find_release(repository: repository, version: version)
return release unless release.nil?

UI.user_error!("Could not find GitHub Release for tag #{version} in #{repository}")
Expand Down Expand Up @@ -244,6 +244,22 @@ def upload_release_assets(repository:, version:, assets:, replace_existing: true
release.html_url
end

def find_release(repository:, version:)
release = client.releases(repository).find { |candidate| candidate.tag_name == version }
return release unless release.nil?

release_for_tag(repository: repository, version: version)
end

def release_for_tag(repository:, version:)
client.release_for_tag(repository, version)
rescue Octokit::NotFound
nil
end
Comment on lines +254 to +258

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other errors that might get thrown? Are we okay to crash on them? Not sure how strong of a concern it is, but seeing a single rescue made me wonder...

@iangmaia iangmaia Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed up in #755


private :find_release
private :release_for_tag

# Use the GitHub API to generate release notes based on the list of PRs between current tag and previous tag.
# @note This API uses the `.github/release.yml` config file to classify the PRs by category in the generated list according to PR labels.
#
Expand Down
31 changes: 31 additions & 0 deletions spec/github_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -640,12 +640,14 @@ def create_release(is_draft:, assets: [], name: nil)

before do
allow(Octokit::Client).to receive(:new).and_return(client)
allow(client).to receive(:release_for_tag).with(test_repo, test_version).and_return(release)
allow(client).to receive(:releases).with(test_repo).and_return([release])
allow(client).to receive(:release_assets).with(release_url).and_return(existing_assets)
allow(client).to receive_messages(upload_asset: uploaded_asset, delete_release_asset: true)
end

it 'fails clearly if the release does not exist' do
allow(client).to receive(:release_for_tag).with(test_repo, test_version).and_raise(Octokit::NotFound)
allow(client).to receive(:releases).with(test_repo).and_return([])

with_tmp_file(named: 'test-app.zip') do |file_path|
Expand Down Expand Up @@ -702,6 +704,7 @@ def create_release(is_draft:, assets: [], name: nil)
draft_release = sawyer_resource_stub(url: release_url, html_url: release_html_url, tag_name: test_version, draft: true)
other_release = sawyer_resource_stub(url: 'https://api.github.com/repos/repo-test/project-test/releases/456', html_url: 'https://github.com/repo-test/project-test/releases/tag/0.9.0', tag_name: '0.9.0')

allow(client).to receive(:release_for_tag).with(test_repo, test_version).and_raise(Octokit::NotFound)
allow(client).to receive(:releases).with(test_repo).and_return([other_release, draft_release])
allow(client).to receive(:release_assets).with(release_url).and_return([])

Expand All @@ -714,6 +717,34 @@ def create_release(is_draft:, assets: [], name: nil)
end
end

it 'uses the release list if available' do
draft_release = sawyer_resource_stub(url: 'draft-api-url', html_url: 'draft-html-url', tag_name: test_version, draft: true)

allow(client).to receive(:releases).with(test_repo).and_return([draft_release])
expect(client).not_to receive(:release_for_tag)
allow(client).to receive(:release_assets).with(draft_release.url).and_return([])

with_tmp_file(named: 'test-app.zip') do |file_path|
expect(client).to receive(:upload_asset).with(draft_release.url, file_path, { content_type: 'application/octet-stream' })

result = upload_release_assets(assets: [file_path])

expect(result).to eq(draft_release.html_url)
end
end

it 'falls back to the direct release-by-tag lookup when the release list misses' do
with_tmp_file(named: 'test-app.zip') do |file_path|
allow(client).to receive(:releases).with(test_repo).and_return([])
allow(client).to receive(:release_for_tag).with(test_repo, test_version).and_return(release)
expect(client).to receive(:upload_asset).with(release_url, file_path, { content_type: 'application/octet-stream' })

result = upload_release_assets(assets: [file_path])

expect(result).to eq(release_html_url)
end
end

it 'uploads one asset to the existing release' do
with_tmp_file(named: 'test-app.zip') do |file_path|
expect(client).to receive(:upload_asset).with(release_url, file_path, { content_type: 'application/octet-stream' })
Expand Down