fastlane-plugin-betadrop 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ce7c339c0a0073c167fda130b72ccd3bab1565ec12866aac66d6343b7b429f7
4
+ data.tar.gz: 9d13fdd00f6549f200e5f93ca30a3de75316757a69bb20dce441e8d3ca7545cf
5
+ SHA512:
6
+ metadata.gz: 5ec6c7a516786704d3c2dada1e775334949059f0550d5a0570e680c07dbafe285c69532426db556cec3bac521d3f4dc15096ee289cecd03f69153224acc52212
7
+ data.tar.gz: 69c1fac304243b36d47c3039ddaaf36756399c7d18a29d25c00c8184788f5794f737bd860dc65b3ec161255fb5cf31dc33744bdc40d14f65a933d05b097700cb
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BetaDrop
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # fastlane-plugin-betadrop
2
+
3
+ Upload the `.ipa` or `.apk` your lane just built to [BetaDrop](https://betadrop.app) and get an
4
+ over-the-air install link — no TestFlight review, no Play track, no tester accounts.
5
+
6
+ ```bash
7
+ fastlane add_plugin betadrop
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ ```ruby
13
+ lane :beta do
14
+ gym(export_method: "ad-hoc")
15
+ betadrop(
16
+ notes: last_git_commit[:message],
17
+ standing_link: "acmebeta", # optional: one URL testers keep (free: 1, Pro/Studio: up to 50)
18
+ inspect: true # refuse to upload a build that will not install from a link
19
+ )
20
+ slack(message: "New build: #{lane_context[SharedValues::BETADROP_INSTALL_URL]}")
21
+ end
22
+ ```
23
+
24
+ Set `BETADROP_TOKEN` in your CI secrets (create one at betadrop.app → Settings → Developer → API
25
+ tokens, or with `npx @betadrop/cli tokens create ci`). With no `file:`, the action uploads what
26
+ `gym` or `gradle` produced.
27
+
28
+ ## Actions
29
+
30
+ ### `betadrop`
31
+
32
+ | Option | Env | Description |
33
+ | --- | --- | --- |
34
+ | `api_token` | `BETADROP_TOKEN` | API token. Required. |
35
+ | `file` | `BETADROP_FILE` | Path to the build. Defaults to `IPA_OUTPUT_PATH` / `GRADLE_APK_OUTPUT_PATH`. |
36
+ | `name` | | Override the build name. |
37
+ | `notes` | `BETADROP_NOTES` | Release notes shown on the install page. |
38
+ | `standing_link` | `BETADROP_STANDING_LINK` | Standing link slug to point at the build. Fails the lane if it could not be moved. |
39
+ | `expires_in_days` · `max_downloads` · `max_devices` | | Limits on the install link; with more than one, it stops at whichever comes first. |
40
+ | `no_expiry` | | Keep the link until the build is deleted (paid plans). |
41
+ | `app_version` · `build_number` · `bundle_id` | | Override what is read from the build. |
42
+ | `inspect` | | Check the build first and fail, with nothing uploaded, if it will not install from a link. |
43
+ | `api_url` | `BETADROP_API_URL` | API host. Default `https://api.betadrop.app`. |
44
+
45
+ Sets `BETADROP_INSTALL_URL`, `BETADROP_BUILD_ID` and `BETADROP_STANDING_LINK_URL` in
46
+ `lane_context`, and returns the install URL.
47
+
48
+ ### `betadrop_inspect`
49
+
50
+ Checks a build without uploading it to your account: provisioning profile type and expiry, a
51
+ profile issued for another bundle ID, unsigned or `testOnly` APKs, v1-only signatures on apps
52
+ targeting Android 11+. A read-only token is enough. `strict: true` fails on warnings too;
53
+ `fail_on_error: false` only reports. The report is in `BETADROP_INSPECT_REPORT`.
54
+
55
+ ## Also
56
+
57
+ - Webhooks to Slack, Discord or your own service: <https://betadrop.app/docs/webhooks/>
58
+ - The CLI the GitHub Action uses: <https://betadrop.app/docs/cli/>
@@ -0,0 +1,154 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/betadrop_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ BETADROP_INSTALL_URL = :BETADROP_INSTALL_URL
8
+ BETADROP_BUILD_ID = :BETADROP_BUILD_ID
9
+ BETADROP_STANDING_LINK_URL = :BETADROP_STANDING_LINK_URL
10
+ end
11
+
12
+ class BetadropAction < Action
13
+ def self.run(params)
14
+ file = params[:file] ||
15
+ Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] ||
16
+ Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
17
+ UI.user_error!('No build to upload. Pass file:, or run gym / gradle first.') if file.to_s.empty?
18
+
19
+ client = Helper::BetadropHelper.new(api_token: params[:api_token], api_url: params[:api_url])
20
+
21
+ if params[:inspect]
22
+ UI.message("Checking #{File.basename(file)} will install from a link…")
23
+ report = client.inspect_build(file)
24
+ (report['checks'] || []).each do |check|
25
+ case check['level']
26
+ when 'error' then UI.error(check['message'])
27
+ when 'warning' then UI.important(check['message'])
28
+ else UI.success(check['message'])
29
+ end
30
+ end
31
+ unless report['installableFromLink']
32
+ UI.user_error!("#{File.basename(file)} will not install from a link, so it was not uploaded. Fix the errors above.")
33
+ end
34
+ end
35
+
36
+ fields = {
37
+ 'name' => params[:name],
38
+ 'notes' => params[:notes],
39
+ 'version' => params[:app_version],
40
+ 'buildNumber' => params[:build_number],
41
+ 'bundleId' => params[:bundle_id],
42
+ 'channel' => params[:standing_link]
43
+ }.merge(Helper::BetadropHelper.expiry_fields(
44
+ days: params[:expires_in_days],
45
+ downloads: params[:max_downloads],
46
+ devices: params[:max_devices],
47
+ permanent: params[:no_expiry]
48
+ ))
49
+
50
+ UI.message("Uploading #{File.basename(file)} to BetaDrop…")
51
+ result = client.publish(file, fields)
52
+
53
+ app_url = (ENV['BETADROP_APP_URL'] || 'https://betadrop.app').sub(%r{/+\z}, '')
54
+ install_url = "#{app_url}/install/?i=#{result['shortId']}"
55
+ Actions.lane_context[SharedValues::BETADROP_INSTALL_URL] = install_url
56
+ Actions.lane_context[SharedValues::BETADROP_BUILD_ID] = result['id']
57
+
58
+ UI.important(result['retentionClamp']['message']) if result['retentionClamp']
59
+ UI.important("#{result['duplicateOf']['message']} (#{result['duplicateOf']['url']})") if result['duplicateOf']
60
+
61
+ if result['channel']
62
+ Actions.lane_context[SharedValues::BETADROP_STANDING_LINK_URL] = result['channel']['url']
63
+ UI.success("Standing link #{result['channel']['slug']} now serves this build: #{result['channel']['url']}")
64
+ end
65
+ UI.success("Install link: #{install_url}")
66
+ # A standing link that was asked for and did not move is a failed release, even though the
67
+ # build itself is live — the same rule the CLI and the GitHub Action apply.
68
+ UI.user_error!(result['channelWarning']) if result['channelWarning']
69
+
70
+ install_url
71
+ rescue Helper::BetadropHelper::Error => e
72
+ UI.user_error!(e.message)
73
+ end
74
+
75
+ def self.description
76
+ 'Upload an .ipa or .apk to BetaDrop and get an over-the-air install link'
77
+ end
78
+
79
+ def self.details
80
+ 'Publishes the build gym or gradle just produced. Optionally checks it will install from a ' \
81
+ 'link first, points a standing link at it, and limits how long or how widely the link works.'
82
+ end
83
+
84
+ def self.authors
85
+ ['BetaDrop']
86
+ end
87
+
88
+ def self.return_value
89
+ 'The install URL'
90
+ end
91
+
92
+ def self.output
93
+ [
94
+ ['BETADROP_INSTALL_URL', 'Install link for the uploaded build'],
95
+ ['BETADROP_BUILD_ID', 'BetaDrop build id'],
96
+ ['BETADROP_STANDING_LINK_URL', 'The standing link URL, when standing_link was set']
97
+ ]
98
+ end
99
+
100
+ def self.available_options
101
+ positive = proc do |name|
102
+ proc do |value|
103
+ UI.user_error!("#{name} must be a whole number of 1 or more") unless value.to_s =~ /\A[1-9]\d*\z/
104
+ end
105
+ end
106
+ [
107
+ FastlaneCore::ConfigItem.new(key: :api_token, env_name: 'BETADROP_TOKEN', sensitive: true,
108
+ description: 'BetaDrop API token (bd_live_…)', optional: false),
109
+ FastlaneCore::ConfigItem.new(key: :file, env_name: 'BETADROP_FILE', optional: true,
110
+ description: 'Path to the .ipa or .apk. Defaults to the output of gym or gradle'),
111
+ FastlaneCore::ConfigItem.new(key: :name, optional: true, description: 'Override the build name'),
112
+ FastlaneCore::ConfigItem.new(key: :notes, env_name: 'BETADROP_NOTES', optional: true,
113
+ description: 'Release notes shown on the install page'),
114
+ FastlaneCore::ConfigItem.new(key: :standing_link, env_name: 'BETADROP_STANDING_LINK', optional: true,
115
+ description: 'Standing link slug to point at this build (must already be claimed)'),
116
+ FastlaneCore::ConfigItem.new(key: :expires_in_days, optional: true, type: Integer,
117
+ verify_block: positive.call('expires_in_days'),
118
+ description: 'Stop the install link working after this many days'),
119
+ FastlaneCore::ConfigItem.new(key: :max_downloads, optional: true, type: Integer,
120
+ verify_block: positive.call('max_downloads'),
121
+ description: 'Stop the install link after this many downloads'),
122
+ FastlaneCore::ConfigItem.new(key: :max_devices, optional: true, type: Integer,
123
+ verify_block: positive.call('max_devices'),
124
+ description: 'Stop the install link after this many devices'),
125
+ FastlaneCore::ConfigItem.new(key: :no_expiry, optional: true, type: Boolean, default_value: false,
126
+ description: 'Keep the link working until the build is deleted (paid plans)'),
127
+ FastlaneCore::ConfigItem.new(key: :app_version, optional: true, description: 'Override the version string'),
128
+ FastlaneCore::ConfigItem.new(key: :build_number, optional: true, description: 'Override the build number'),
129
+ FastlaneCore::ConfigItem.new(key: :bundle_id, optional: true, description: 'Override the bundle / package id'),
130
+ FastlaneCore::ConfigItem.new(key: :inspect, optional: true, type: Boolean, default_value: false,
131
+ description: 'Check the build will install from a link before uploading, and fail if not'),
132
+ FastlaneCore::ConfigItem.new(key: :api_url, env_name: 'BETADROP_API_URL', optional: true,
133
+ default_value: 'https://api.betadrop.app', description: 'API host')
134
+ ]
135
+ end
136
+
137
+ def self.is_supported?(platform)
138
+ %i[ios android].include?(platform)
139
+ end
140
+
141
+ def self.example_code
142
+ [
143
+ 'betadrop',
144
+ 'betadrop(notes: last_git_commit[:message], standing_link: "acmebeta", inspect: true)',
145
+ 'betadrop(file: "app/build/outputs/apk/release/app-release.apk", max_devices: 25)'
146
+ ]
147
+ end
148
+
149
+ def self.category
150
+ :beta
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,78 @@
1
+ require 'fastlane/action'
2
+ require_relative '../helper/betadrop_helper'
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ BETADROP_INSPECT_REPORT = :BETADROP_INSPECT_REPORT
8
+ end
9
+
10
+ # Check a build will install from a link without uploading it to the account — the step to put
11
+ # between gym and the rest of a release lane.
12
+ class BetadropInspectAction < Action
13
+ def self.run(params)
14
+ file = params[:file] || Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] ||
15
+ Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]
16
+ UI.user_error!('No build to inspect. Pass file:, or run gym / gradle first.') if file.to_s.empty?
17
+
18
+ report = Helper::BetadropHelper.new(api_token: params[:api_token], api_url: params[:api_url]).inspect_build(file)
19
+ Actions.lane_context[SharedValues::BETADROP_INSPECT_REPORT] = report
20
+
21
+ (report['checks'] || []).each do |check|
22
+ case check['level']
23
+ when 'error' then UI.error(check['message'])
24
+ when 'warning' then UI.important(check['message'])
25
+ else UI.success(check['message'])
26
+ end
27
+ end
28
+
29
+ failed = (report['checks'] || []).any? do |c|
30
+ c['level'] == 'error' || (params[:strict] && c['level'] == 'warning')
31
+ end
32
+ UI.user_error!("#{File.basename(file)} failed the install check.") if failed && params[:fail_on_error]
33
+ UI.success('Installable from a link.') unless failed
34
+ report
35
+ rescue Helper::BetadropHelper::Error => e
36
+ UI.user_error!(e.message)
37
+ end
38
+
39
+ def self.description
40
+ 'Check an .ipa or .apk will install from a link: profile type and expiry, signing, testOnly'
41
+ end
42
+
43
+ def self.authors
44
+ ['BetaDrop']
45
+ end
46
+
47
+ def self.return_value
48
+ 'The inspection report (a Hash)'
49
+ end
50
+
51
+ def self.available_options
52
+ [
53
+ FastlaneCore::ConfigItem.new(key: :api_token, env_name: 'BETADROP_TOKEN', sensitive: true,
54
+ description: 'BetaDrop API token; a read-only token is enough', optional: false),
55
+ FastlaneCore::ConfigItem.new(key: :file, optional: true, description: 'Path to the .ipa or .apk'),
56
+ FastlaneCore::ConfigItem.new(key: :strict, optional: true, type: Boolean, default_value: false,
57
+ description: 'Treat warnings as failures'),
58
+ FastlaneCore::ConfigItem.new(key: :fail_on_error, optional: true, type: Boolean, default_value: true,
59
+ description: 'Fail the lane when a check fails'),
60
+ FastlaneCore::ConfigItem.new(key: :api_url, env_name: 'BETADROP_API_URL', optional: true,
61
+ default_value: 'https://api.betadrop.app', description: 'API host')
62
+ ]
63
+ end
64
+
65
+ def self.is_supported?(platform)
66
+ %i[ios android].include?(platform)
67
+ end
68
+
69
+ def self.example_code
70
+ ['betadrop_inspect', 'betadrop_inspect(strict: true)']
71
+ end
72
+
73
+ def self.category
74
+ :beta
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,143 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'securerandom'
4
+ require 'stringio'
5
+ require 'uri'
6
+ require_relative '../version'
7
+
8
+ module Fastlane
9
+ module Helper
10
+ # The HTTP half of the plugin, kept free of fastlane so it can be tested with plain Ruby.
11
+ #
12
+ # Uploads stream from disk: a build can be hundreds of megabytes, and reading it into memory
13
+ # to build a multipart body is how a CI runner with a small heap falls over.
14
+ class BetadropHelper
15
+ DEFAULT_API_URL = 'https://api.betadrop.app'.freeze
16
+
17
+ class Error < StandardError
18
+ attr_reader :status, :reason
19
+
20
+ def initialize(message, status: nil, reason: nil)
21
+ super(message)
22
+ @status = status
23
+ @reason = reason
24
+ end
25
+ end
26
+
27
+ # An IO that reads several IOs one after another, so Net::HTTP can stream the multipart
28
+ # preamble, the file and the epilogue as one body with a known length.
29
+ class ChainIO
30
+ def initialize(*parts)
31
+ @parts = parts
32
+ end
33
+
34
+ def read(length = nil, outbuf = nil)
35
+ outbuf ||= +''
36
+ outbuf.clear
37
+ until @parts.empty?
38
+ chunk = @parts.first.read(length ? length - outbuf.bytesize : nil)
39
+ if chunk.nil? || chunk.empty?
40
+ @parts.shift
41
+ next
42
+ end
43
+ outbuf << chunk
44
+ break if length && outbuf.bytesize >= length
45
+ end
46
+ outbuf.empty? && length ? nil : outbuf
47
+ end
48
+ end
49
+
50
+ def initialize(api_token:, api_url: nil, user_agent: nil)
51
+ raise Error, 'A BetaDrop API token is required (api_token, or BETADROP_TOKEN).' if api_token.to_s.strip.empty?
52
+
53
+ @token = api_token.strip
54
+ @base = (api_url.to_s.strip.empty? ? DEFAULT_API_URL : api_url.strip).sub(%r{/+\z}, '')
55
+ @user_agent = user_agent || "fastlane-plugin-betadrop/#{Fastlane::Betadrop::VERSION}"
56
+ end
57
+
58
+ # POST /api/cli/publish. `fields` values that are nil or empty are not sent.
59
+ def publish(file, fields = {})
60
+ multipart('/api/cli/publish', file, fields)
61
+ end
62
+
63
+ # POST /api/inspect. Stores nothing on the account.
64
+ def inspect_build(file)
65
+ multipart('/api/inspect', file, {})
66
+ end
67
+
68
+ # Turn the three limit options into the API's expiry fields — the same rule the CLI uses:
69
+ # one limit is that kind, more than one is `combined`, none sends nothing.
70
+ def self.expiry_fields(days: nil, downloads: nil, devices: nil, permanent: false)
71
+ return { 'expiry_type' => 'none' } if permanent
72
+
73
+ set = { 'expiry_time_days' => days, 'expiry_download_limit' => downloads, 'expiry_device_limit' => devices }
74
+ .reject { |_, v| v.nil? || v.to_s.strip.empty? }
75
+ return {} if set.empty?
76
+
77
+ type = if set.size > 1 then 'combined'
78
+ elsif set.key?('expiry_time_days') then 'time'
79
+ elsif set.key?('expiry_download_limit') then 'downloads'
80
+ else 'devices'
81
+ end
82
+ set.merge('expiry_type' => type)
83
+ end
84
+
85
+ private
86
+
87
+ def multipart(path, file, fields)
88
+ raise Error, "No build found at #{file}" unless file && File.file?(file)
89
+
90
+ boundary = "----betadropfastlane#{SecureRandom.hex(16)}"
91
+ preamble = +''
92
+ fields.each do |key, value|
93
+ next if value.nil? || value.to_s.empty?
94
+
95
+ preamble << "--#{boundary}\r\nContent-Disposition: form-data; name=\"#{key}\"\r\n\r\n#{value}\r\n"
96
+ end
97
+ filename = File.basename(file).gsub(/["\\\r\n]/, '_')
98
+ preamble << "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n" \
99
+ "Content-Type: application/octet-stream\r\n\r\n"
100
+ epilogue = "\r\n--#{boundary}--\r\n"
101
+
102
+ uri = URI("#{@base}#{path}")
103
+ File.open(file, 'rb') do |io|
104
+ request = Net::HTTP::Post.new(uri)
105
+ request['Authorization'] = "Bearer #{@token}"
106
+ request['Accept'] = 'application/json'
107
+ request['User-Agent'] = @user_agent
108
+ request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
109
+ request['Content-Length'] = (preamble.bytesize + File.size(file) + epilogue.bytesize).to_s
110
+ request.body_stream = ChainIO.new(StringIO.new(preamble), io, StringIO.new(epilogue))
111
+ send_request(uri, request)
112
+ end
113
+ end
114
+
115
+ def send_request(uri, request)
116
+ http = Net::HTTP.new(uri.host, uri.port)
117
+ http.use_ssl = uri.scheme == 'https'
118
+ http.open_timeout = 30
119
+ # A large upload on a slow runner. The server answers once the file is stored and parsed.
120
+ http.read_timeout = 900
121
+ http.write_timeout = 900 if http.respond_to?(:write_timeout=)
122
+ response = http.request(request)
123
+ body = begin
124
+ JSON.parse(response.body.to_s)
125
+ rescue JSON::ParserError
126
+ nil
127
+ end
128
+ status = response.code.to_i
129
+ if status == 401
130
+ raise Error.new('BetaDrop rejected the API token: it is invalid, expired or revoked.', status: status)
131
+ end
132
+ if body.nil?
133
+ raise Error.new("Unexpected response from BetaDrop (HTTP #{status}).", status: status)
134
+ end
135
+ unless status.between?(200, 299) && body['success'] != false
136
+ raise Error.new(body['error'] || body['message'] || "Request failed (HTTP #{status}).",
137
+ status: status, reason: body['reason'])
138
+ end
139
+ body['data']
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module Betadrop
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'fastlane/plugin/betadrop/version'
2
+
3
+ module Fastlane
4
+ module Betadrop
5
+ # Loads every action and helper in this plugin, the way fastlane's plugin loader expects.
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ Fastlane::Betadrop.all_classes.each do |current|
13
+ require current
14
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-betadrop
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - BetaDrop
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Publishes the .ipa or .apk your lane built to BetaDrop, optionally checks
14
+ it will install from a link first, and can point a standing link at it.
15
+ email: support@betadrop.app
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/fastlane/plugin/betadrop.rb
23
+ - lib/fastlane/plugin/betadrop/actions/betadrop_action.rb
24
+ - lib/fastlane/plugin/betadrop/actions/betadrop_inspect_action.rb
25
+ - lib/fastlane/plugin/betadrop/helper/betadrop_helper.rb
26
+ - lib/fastlane/plugin/betadrop/version.rb
27
+ homepage: https://betadrop.app/integrations/fastlane/
28
+ licenses:
29
+ - MIT
30
+ metadata:
31
+ source_code_uri: https://github.com/betadrop-app/fastlane-plugin-betadrop
32
+ documentation_uri: https://betadrop.app/integrations/fastlane/
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '2.6'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.0.3.1
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Upload iOS and Android builds to BetaDrop and get an over-the-air install
52
+ link.
53
+ test_files: []