diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 853e259..7ae6729 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -130,50 +130,9 @@ jobs: uses: ruby/setup-ruby@v1 with: bundler-cache: true - - name: Prepare Play Store credentials - env: - PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }} - run: | - bundle exec ruby -rbase64 -rjson -e ' - raw = ENV.fetch("PLAY_STORE_JSON_KEY", "").strip - abort("PLAY_STORE_JSON_KEY is empty") if raw.empty? - - candidates = [raw] - begin - decoded = Base64.strict_decode64(raw.gsub(/\s+/, "")).strip - candidates << decoded unless decoded.empty? - rescue ArgumentError - nil - end - - json = candidates.lazy.map do |candidate| - begin - parsed = JSON.parse(candidate) - if parsed.is_a?(String) - nested = JSON.parse(parsed) - [parsed, nested] - else - [candidate, parsed] - end - rescue JSON::ParserError - nil - end - end.find do |entry| - next false unless entry - - parsed = entry[1] - parsed.is_a?(Hash) && - parsed["type"] == "service_account" && - !parsed["client_email"].to_s.empty? && - !parsed["private_key"].to_s.empty? - end&.first - - abort("PLAY_STORE_JSON_KEY must contain raw service account JSON or its base64-encoded contents") unless json - File.write("play-store-key.json", json + "\n") - ' - name: Deploy to Google Play Store via Fastlane env: - PLAY_STORE_JSON_KEY_PATH: ${{ github.workspace }}/play-store-key.json + PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }} run: bundle exec fastlane android deploy track:internal # default to internal deploy, change this when needed success: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6843886..33b9a9d 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,6 @@ require 'base64' require 'json' +require 'tmpdir' def parse_json_document(value) JSON.parse(value) @@ -14,24 +15,59 @@ def play_store_service_account?(parsed) !parsed['private_key'].to_s.empty? end -def normalize_play_store_json(raw_value) - candidates = [raw_value.to_s.strip].reject(&:empty?) +def add_play_store_json_candidate(candidates, value) + candidate = value.to_s.strip + candidate = candidate.sub(/\A(?:export\s+)?PLAY_STORE_JSON_KEY(?:_DATA)?\s*=\s*/, '').strip + + variants = [candidate] + if (candidate.start_with?('"') && candidate.end_with?('"')) || + (candidate.start_with?("'") && candidate.end_with?("'")) + variants << candidate[1...-1].strip + end + + variants.each do |variant| + candidates << variant unless variant.empty? || candidates.include?(variant) + end +end - begin - decoded = Base64.strict_decode64(raw_value.to_s.gsub(/\s+/, '')) - candidates << decoded.strip unless decoded.to_s.strip.empty? - rescue ArgumentError - nil +def base64_decode_play_store_json_candidate(candidate) + compact = candidate.to_s.gsub(/\s+/, '') + return nil if compact.empty? + + padded = compact + ('=' * ((4 - compact.length % 4) % 4)) + + [compact, padded].uniq.each do |value| + begin + return Base64.strict_decode64(value) + rescue ArgumentError + begin + return Base64.urlsafe_decode64(value) + rescue ArgumentError + nil + end + end end + nil +end + +def normalize_play_store_json(raw_value) + candidates = [] + add_play_store_json_candidate(candidates, raw_value) + candidates.each do |candidate| parsed = parse_json_document(candidate) - return candidate if play_store_service_account?(parsed) + return JSON.generate(parsed) if play_store_service_account?(parsed) if parsed.is_a?(String) - nested = parse_json_document(parsed) - return parsed if play_store_service_account?(nested) + add_play_store_json_candidate(candidates, parsed) + elsif parsed.is_a?(Hash) + %w[PLAY_STORE_JSON_KEY PLAY_STORE_JSON_KEY_DATA json_key_data json_key].each do |key| + add_play_store_json_candidate(candidates, parsed[key]) if parsed.key?(key) + end end + + add_play_store_json_candidate(candidates, base64_decode_play_store_json_candidate(candidate)) end FastlaneCore::UI.user_error!( @@ -39,6 +75,12 @@ def normalize_play_store_json(raw_value) ) end +def write_play_store_json_key(raw_value) + key_path = File.join(Dir.tmpdir, "echo-play-store-key-#{Process.pid}.json") + File.write(key_path, normalize_play_store_json(raw_value) + "\n") + key_path +end + def play_store_credentials_options key_path = ENV['PLAY_STORE_JSON_KEY_PATH'].to_s.strip return { json_key: key_path } unless key_path.empty? @@ -50,7 +92,7 @@ def play_store_credentials_options ) end - { json_key_data: normalize_play_store_json(raw_key) } + { json_key: write_play_store_json_key(raw_key) } end platform :android do