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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ A plugin including commonly used automation logic for RevenueCat SDKs.
- `commit_current_changes`: This action will commit all currently modified files into the current branch in the local repository. (This will not include untracked files)
- `get_latest_github_release_within_same_major`: This action will return the latest release found in github for the same major version as the one given as parameter.
- `update_hybrids_versions_file`: This action is meant for hybrid sdks only. It will update the `VERSIONS.md` file given with a new entry including the new version if the SDK and the iOS, Android and hybrid common sdk versions.
- `validate_version_not_in_maven_central`: This action checks if a specific version of Maven artifacts already exists in Maven Central before deployment. It prevents accidental re-releases by failing if any of the specified artifacts are already published.

## Example

Expand Down
9 changes: 9 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ lane :sample_determine_next_version_using_labels_action do |options|
)
end

lane :sample_validate_version_not_in_maven_central_action do |options|
validate_version_not_in_maven_central(
group_id: 'com.revenuecat.purchases',
artifact_ids: ['purchases', 'purchases-ui'],
version: '7.0.0',
auth_token: 'maven-central-auth-token' # This can also be obtained from ENV FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL
)
end

lane :sample_flaky_test do
test_artifact_path = File.absolute_path('./test_output/xctest/ios')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
require 'fastlane/action'
require 'fastlane_core/configuration/config_item'
require 'fastlane_core/ui/ui'
require_relative '../helper/maven_central_helper'

module Fastlane
module Actions
class ValidateVersionNotInMavenCentralAction < Action
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.

Note this doesn't move the logic to calculate what artifacts to check for from the android repo since that's mostly in gradle, with a small bit of logic in fastlane. We might want to consider creating a gradle plugin we can share with that part of the logic, but for now, this felt like the simpler approach.

def self.run(params)
group_id = params[:group_id]
artifact_ids = params[:artifact_ids]
version = params[:version]
auth_token = params[:auth_token] || ENV.fetch("FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL", nil)

UI.message("Checking if version #{version} already exists in Maven Central...")

if artifact_ids.empty?
UI.user_error!("No artifacts provided. Please provide at least one artifact ID to check")
else
UI.message("Found #{artifact_ids.length} artifacts to check: #{artifact_ids.join(', ')}")
Helper::MavenCentralHelper.check_version_not_published(version, group_id, artifact_ids, auth_token)
UI.success("Version #{version} does not exist in Maven Central. Proceeding with deployment.")
end
end

def self.description
"Checks if a specific version of Maven artifacts already exists in Maven Central before deployment"
end

def self.authors
["RevenueCat"]
end

def self.available_options
[
FastlaneCore::ConfigItem.new(key: :group_id,
description: "Maven group ID (e.g., 'com.revenuecat.purchases')",
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :artifact_ids,
description: "Array of artifact IDs to check (e.g., ['purchases', 'purchases-ui'])",
optional: false,
type: Array),
FastlaneCore::ConfigItem.new(key: :version,
description: "Version to check (e.g., '7.0.0')",
optional: false,
type: String),
FastlaneCore::ConfigItem.new(key: :auth_token,
description: "Authentication token for Maven Central API (defaults to FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL env var)",
optional: true,
type: String)
]
end

def self.is_supported?(platform)
true
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require 'fastlane_core/ui/ui'
require 'fastlane/action'
require 'rest-client'
require 'json'
require 'uri'

module Fastlane
UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)

module Helper
class MavenCentralHelper
def self.check_version_not_published(version, group_id, artifact_ids, auth_token)
if auth_token.nil? || auth_token.empty?
UI.user_error!("FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL environment variable is not set. Please provide a valid token to check Maven Central publications.")
end

base_url = "https://central.sonatype.com/api/v1/publisher/published"
existing_artifacts = []

artifact_ids.each do |artifact_id|
# Build query parameters for the API endpoint with proper URI encoding
# The API uses 'name' parameter which corresponds to the artifact_id
api_url = "#{base_url}?namespace=#{URI.encode_www_form_component(group_id)}&name=#{URI.encode_www_form_component(artifact_id)}&version=#{URI.encode_www_form_component(version)}"

UI.verbose("Checking Sonatype API for publication status: #{group_id}:#{artifact_id}:#{version}")

begin
response = RestClient.get(
api_url,
{
'Authorization' => "Bearer #{auth_token}",
'accept' => 'application/json'
}
)

# Parse JSON response
response_data = JSON.parse(response.body)

# Check if published field is true
if response_data["published"] == true
existing_artifacts << artifact_id
UI.important("Artifact #{group_id}:#{artifact_id}:#{version} already exists in Maven Central")
end
rescue StandardError => e
UI.user_error!("Failed to check #{group_id}:#{artifact_id}:#{version}: #{e.message}")
end
end

unless existing_artifacts.empty?
error_message = "Version #{version} already exists in Maven Central for the following artifacts:\n"
existing_artifacts.each do |artifact_id|
error_message += " - #{group_id}:#{artifact_id}:#{version}\n"
end
error_message += "\nDeployment cancelled to prevent duplicate releases."
UI.user_error!(error_message)
end

true
end
end
end
end
Loading