fastlane-plugin-branch 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8305f250691fbd01e191ca2133151dc6ffa22d55
4
+ data.tar.gz: 953577ba927bb98740c1b41ed282487b5c23c584
5
+ SHA512:
6
+ metadata.gz: ec225b08a24f31de602203c00c85c6f744d59df021c03f0243b977956f613890b0e69b03a8cd2d9a5fd76d8d06faa46b5e29d540f43ddd33c5e09560b494ee22
7
+ data.tar.gz: 3403679f0c139b346dcc2482c1d8b6db7de412ba15be91a5e8febfd4fbe2eae2007bef7cdd3e3b0a87f5f168861ad2e86e34ca17fe99599254b75ba71e54ccbd
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Jimmy Dee <jgvdthree@gmail.com>
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,165 @@
1
+ # branch plugin
2
+
3
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg?style=flat-square)](https://rubygems.org/gems/fastlane-plugin-branch)
4
+ [![Gem](https://img.shields.io/gem/v/fastlane-plugin-branch.svg?style=flat)](https://rubygems.org/gems/fastlane-plugin-branch)
5
+ [![Downloads](https://img.shields.io/gem/dt/fastlane-plugin-branch.svg?style=flat)](https://rubygems.org/gems/fastlane-plugin-branch)
6
+ [![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/jdee/settings-bundle/blob/master/LICENSE)
7
+ [![CircleCI](https://img.shields.io/circleci/project/github/BranchMetrics/fastlane-plugin-branch.svg)](https://circleci.com/gh/BranchMetrics/fastlane-plugin-branch)
8
+
9
+ ## Preliminary release
10
+
11
+ This is a preliminary release of this plugin. Please report any problems by opening issues in this repo.
12
+
13
+ ## Getting Started
14
+
15
+ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-branch`, add it to your project by running:
16
+
17
+ ```bash
18
+ fastlane add_plugin branch
19
+ ```
20
+
21
+ ## setup_branch action
22
+
23
+ ### Prerequisites
24
+
25
+ Before using this action, make sure to set up your app in the [Branch Dashboard](https://dashboard.branch.io). See https://dev.branch.io/basic-setup/ for details. To use the `setup_branch` action, you need:
26
+
27
+ - Branch key(s), either live, test or both
28
+ - Domain name(s) used for Branch links
29
+ - The custom URI scheme for your app, if any (Android only)
30
+ - Location(s) of your Android and/or iOS project(s)
31
+
32
+ ### Usage
33
+
34
+ This action automatically configures Xcode and Android projects that use the Branch SDK
35
+ for Universal Links, App Links and custom URI handling. It modifies Xcode project settings and entitlements as well as Info.plist and AndroidManifest.xml files.
36
+
37
+ ```ruby
38
+ setup_branch(
39
+ live_key: "key_live_xxxx",
40
+ test_key: "key_test_yyyy",
41
+ app_link_subdomain: "myapp",
42
+ uri_scheme: "myscheme", # Android only
43
+ android_project_path: "MyAndroidApp", # MyAndroidApp/src/main/AndroidManifest.xml
44
+ xcodeproj: "MyIOSApp.xcodeproj"
45
+ )
46
+ ```
47
+
48
+ Use the `:domains` parameter to specify custom domains, including non-Branch domains
49
+ ```ruby
50
+ setup_branch(
51
+ live_key: "key_live_xxxx",
52
+ domains: %w{example.com www.example.com}
53
+ xcodeproj: "MyIOSApp.xcodeproj"
54
+ )
55
+ ```
56
+
57
+ Available options:
58
+
59
+ |Fastfile key|Environment variable|description|type|default value|
60
+ |---|---|---|---|---|
61
+ |:live_key|BRANCH_LIVE_KEY|The Branch live key to use (:live_key or :test_key is required)|string||
62
+ |:test_key|BRANCH_TEST_KEY|The Branch test key to use (:live_key or :test_key is required)|string||
63
+ |:app_link_subdomain|BRANCH_APP_LINK_SUBDOMAIN|An app.link subdomain to use (:app_link_subdomain or :domains is required. The union of the two sets of domains will be used.)|string||
64
+ |:domains|BRANCH_DOMAINS|A list of domains (custom domains or Branch domains) to use (:app_link_subdomain or :domains is required. The union of the two sets of domains will be used.)|string array or comma-separated string||
65
+ |:uri_scheme|BRANCH_URI_SCHEME|A URI scheme to add to the manifest (Android only)|string||
66
+ |:android_project_path|BRANCH_ANDROID_PROJECT_PATH|Path to an Android project to use. Equivalent to 'android_manifest_path: "app/src/main/AndroidManifest.xml"`. Overridden by :android_manifest_path (:xcodeproj, :android_project_path or :android_manifest_path is required.)|string||
67
+ |:android_manifest_path|BRANCH_ANDROID_MANIFEST_PATH|Path to an Android manifest to modify. Overrides :android_project_path. (:xcodeproj, :android_project_path or :android_manifest_path is required.)|string||
68
+ |:xcodeproj|BRANCH_XCODEPROJ|Path to a .xcodeproj directory to use. (:xcodeproj, :android_project_path or :android_manifest_path is required.)|string||
69
+ |:activity_name|BRANCH_ACTIVITY_NAME|Name of the Activity to use (Android only; optional)|string||
70
+ |:target|BRANCH_TARGET|Name of the target to use in the Xcode project (iOS only; optional)|string||
71
+ |:update_bundle_and_team_ids|BRANCH_UPDATE_BUNDLE_AND_TEAM_IDS|If true, changes the bundle and team identifiers in the Xcode project to match the AASA file. Mainly useful for sample apps. (iOS only)|boolean|false|
72
+ |:remove_existing_domains|BRANCH_REMOVE_EXISTING_DOMAINS|If true, any domains currently configured in the Xcode project or Android manifest will be removed before adding the domains specified by the arguments. Mainly useful for sample apps.|boolean|false|
73
+ |:force|BRANCH_FORCE_UPDATE|Update project(s) even if Universal Link validation fails|boolean|false|
74
+ |:commit|BRANCH_COMMIT_CHANGES|Set to true to commit changes to Git; set to a string to commit with a custom message|boolean or string|false|
75
+
76
+ Individually, all parameters are optional, but the following conditions apply:
77
+
78
+ - :android_manifest_path, :android_project_path or :xcodeproj must be specified.
79
+ - :live_key or :test_key must be specified.
80
+ - :app_link_subdomain or :domains must be specified.
81
+
82
+ This action also supports an optional Branchfile to specify configuration options.
83
+ See the sample Branchfile at the root of this repo.
84
+
85
+ ## validate_universal_links action (iOS only)
86
+
87
+ This action validates all Universal Link domains configured in a project without making any modification.
88
+ It validates both Branch and non-Branch domains.
89
+
90
+
91
+ ```ruby
92
+ validate_universal_links
93
+ ```
94
+
95
+ ```ruby
96
+ validate_universal_links(xcodeproj: "MyProject.xcodeproj")
97
+ ```
98
+
99
+ ```ruby
100
+ validate_universal_links(xcodeproj: "MyProject.xcodeproj", target: "MyProject")
101
+ ```
102
+
103
+ ```ruby
104
+ validate_universal_links(domains: %w{example.com www.example.com})
105
+ ```
106
+
107
+ Available options:
108
+
109
+ |Fastfile key|Environment variable|description|type|default value|
110
+ |---|---|---|---|---|
111
+ |:xcodeproj|BRANCH_XCODEPROJ|Path to a .xcodeproj directory to use|string||
112
+ |:target|BRANCH_TARGET|Name of the target to use in the Xcode project|string||
113
+ |:domains|BRANCH_DOMAINS|A list of domains (custom domains or Branch domains) that must be present in the project.|string array or comma-separated string||
114
+
115
+ All parameters are optional. Without any parameters, the action looks for a single .xcodeproj
116
+ folder (with the exception of a Pods project) and reports an error if none or more than one is found.
117
+ It uses the first non-test, non-extension target in that project.
118
+
119
+ If the :domains parameter is not provided, validation will pass as long as there is at least
120
+ one Universal Link domain configured for the target, and all Universal Link domains pass
121
+ AASA validation. If the the :domains parameter is provided, the Universal Link domains in
122
+ the project must also match the value of this parameter without regard to order.
123
+
124
+ This action does not use the Branchfile.
125
+
126
+ ## Example
127
+
128
+ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. To try it:
129
+
130
+ ```bash
131
+ bundle install
132
+ bundle exec fastlane validate # The example project needs to be set up. This will fail.
133
+ bundle exec fastlane update # Also validates the UL configuration.
134
+ bundle exec fastlane update_and_commit # Also commit changes to Git. (git reset --hard HEAD^ to erase the last commit)
135
+ bundle exec fastlane validate # Now validation will pass.
136
+ ```
137
+
138
+ ## Run tests for this plugin
139
+
140
+ To run both the tests, and code style validation, run
141
+
142
+ ```
143
+ bundle exec rake
144
+ ```
145
+
146
+ To automatically fix many of the styling issues, use
147
+ ```
148
+ bundle exec rubocop -a
149
+ ```
150
+
151
+ ## Issues and Feedback
152
+
153
+ For any other issues and feedback about this plugin, please submit it to this repository.
154
+
155
+ ## Troubleshooting
156
+
157
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
158
+
159
+ ## Using _fastlane_ Plugins
160
+
161
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
162
+
163
+ ## About _fastlane_
164
+
165
+ _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
@@ -0,0 +1,16 @@
1
+ require 'fastlane/plugin/branch/version'
2
+
3
+ module Fastlane
4
+ module Branch
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::Branch.all_classes.each do |current|
15
+ require current
16
+ end
@@ -0,0 +1,200 @@
1
+ require "rexml/document"
2
+ require "xcodeproj"
3
+
4
+ module Fastlane
5
+ module Actions
6
+ class SetupBranchAction < Action
7
+ # rubocop: disable Metrics/PerceivedComplexity
8
+ def self.run(params)
9
+ # First augment with any defaults from Branchfile, if present
10
+ params.load_configuration_file("Branchfile")
11
+
12
+ helper = Helper::BranchHelper
13
+
14
+ keys = helper.keys_from_params params
15
+ raise "Must specify :live_key or :test_key." if keys.empty?
16
+
17
+ domains = helper.domains_from_params params
18
+ raise "Cannot determine domains to add to project. Specify :app_link_subdomain or :domains." if domains.empty?
19
+
20
+ if params[:xcodeproj].nil? and params[:android_project_path].nil? and params[:android_manifest_path].nil?
21
+ raise ":xcodeproj, :android_manifest_path or :android_project_path is required"
22
+ end
23
+
24
+ UI.message "live key: #{keys[:live]}" unless keys[:live].nil?
25
+ UI.message "test key: #{keys[:test]}" unless keys[:test].nil?
26
+ UI.message "domains: #{domains}"
27
+
28
+ if params[:xcodeproj]
29
+ # raises
30
+ xcodeproj = Xcodeproj::Project.open params[:xcodeproj]
31
+
32
+ target = params[:target] # may be nil
33
+
34
+ if params[:update_bundle_and_team_ids]
35
+ helper.update_team_and_bundle_ids_from_aasa_file xcodeproj, target, domains.first
36
+ elsif helper.validate_team_and_bundle_ids_from_aasa_files xcodeproj, target, domains, params[:remove_existing_domains]
37
+ UI.message "Universal Link configuration passed validation. ✅"
38
+ else
39
+ UI.error "Universal Link configuration failed validation."
40
+ helper.errors.each { |error| UI.error " #{error}" }
41
+ return unless params[:force]
42
+ end
43
+
44
+ # the following calls can all raise IOError
45
+ helper.add_keys_to_info_plist xcodeproj, target, keys
46
+ helper.add_branch_universal_link_domains_to_info_plist xcodeproj, target, domains
47
+ new_path = helper.add_universal_links_to_project xcodeproj, target, domains, params[:remove_existing_domains]
48
+ other_action.git_add path: new_path if params[:commit] && new_path
49
+ xcodeproj.save
50
+ end
51
+
52
+ if params[:android_project_path] || params[:android_manifest_path]
53
+ # :android_manifest_path overrides :android_project_path
54
+ manifest_path = params[:android_manifest_path] || "#{params[:android_project_path]}/app/src/main/AndroidManifest.xml"
55
+ manifest = File.open(manifest_path) { |f| REXML::Document.new f }
56
+
57
+ helper.add_keys_to_android_manifest manifest, keys
58
+ # :activity_name and :uri_scheme may be nil. :remove_existing_domains defaults to false
59
+ helper.add_intent_filters_to_android_manifest manifest,
60
+ domains,
61
+ params[:uri_scheme],
62
+ params[:activity_name],
63
+ params[:remove_existing_domains]
64
+
65
+ File.open(manifest_path, "w") do |f|
66
+ manifest.write f, 4
67
+ end
68
+
69
+ helper.add_change File.expand_path(manifest_path, Bundler.root)
70
+ end
71
+
72
+ if params[:commit]
73
+ message = params[:commit].kind_of?(String) ? params[:commit] : "[Fastlane] Branch SDK integration"
74
+ other_action.git_commit path: helper.changes.to_a, message: message
75
+ end
76
+ rescue => e
77
+ UI.user_error! "Error in SetupBranchAction: #{e.message}\n#{e.backtrace}"
78
+ end
79
+ # rubocop: enable Metrics/PerceivedComplexity
80
+
81
+ def self.description
82
+ "Adds Branch keys, custom URI schemes and domains to iOS and Android projects."
83
+ end
84
+
85
+ def self.authors
86
+ [
87
+ "Branch <integrations@branch.io>",
88
+ "Jimmy Dee <jgvdthree@gmail.com>"
89
+ ]
90
+ end
91
+
92
+ def self.details
93
+ "This action automatically configures Xcode and Android projects that use the Branch SDK " \
94
+ "for Universal Links, App Links and custom URI handling. It modifies Xcode project settings and " \
95
+ "entitlements as well as Info.plist and AndroidManifest.xml files. It also validates the Universal Link " \
96
+ "configuration for Xcode projects."
97
+ end
98
+
99
+ def self.example_code
100
+ [
101
+ <<-EOF
102
+ setup_branch live_key: "key_live_xxxx",
103
+ test_key: "key_test_yyyy",
104
+ app_link_subdomain: "myapp",
105
+ uri_scheme: "myscheme", # Android only
106
+ android_project_path: "MyAndroidApp", # MyAndroidApp/src/main/AndroidManifest.xml
107
+ xcodeproj: "MyIOSApp.xcodeproj"
108
+ EOF
109
+ ]
110
+ end
111
+
112
+ def self.available_options
113
+ [
114
+ FastlaneCore::ConfigItem.new(key: :xcodeproj,
115
+ env_name: "BRANCH_XCODEPROJ",
116
+ description: "Path to an Xcode project to modify",
117
+ optional: true,
118
+ type: String),
119
+ FastlaneCore::ConfigItem.new(key: :android_project_path,
120
+ env_name: "BRANCH_ANDROID_PROJECT_PATH",
121
+ description: "Path to an Android project to modify",
122
+ optional: true,
123
+ type: String),
124
+ FastlaneCore::ConfigItem.new(key: :android_manifest_path,
125
+ env_name: "BRANCH_ANDROID_MANIFEST_PATH",
126
+ description: "Path to and Android manifest to modify",
127
+ optional: true,
128
+ type: String),
129
+ FastlaneCore::ConfigItem.new(key: :live_key,
130
+ env_name: "BRANCH_LIVE_KEY",
131
+ description: "The Branch live key for your app",
132
+ optional: true,
133
+ type: String),
134
+ FastlaneCore::ConfigItem.new(key: :test_key,
135
+ env_name: "BRANCH_TEST_KEY",
136
+ description: "The Branch test key for your app",
137
+ optional: true,
138
+ type: String),
139
+ FastlaneCore::ConfigItem.new(key: :domains,
140
+ env_name: "BRANCH_DOMAINS",
141
+ description: "Branch (and/or non-Branch) Universal Link/App Link domains to add (comma-separated list or array)",
142
+ optional: true,
143
+ is_string: false),
144
+ FastlaneCore::ConfigItem.new(key: :app_link_subdomain,
145
+ env_name: "BRANCH_APP_LINK_SUBDOMAIN",
146
+ description: "app.link subdomain",
147
+ optional: true,
148
+ type: String),
149
+ FastlaneCore::ConfigItem.new(key: :uri_scheme,
150
+ env_name: "BRANCH_URI_SCHEME",
151
+ description: "Custom URI scheme used with Branch (Android only)",
152
+ optional: true,
153
+ type: String),
154
+ FastlaneCore::ConfigItem.new(key: :activity_name,
155
+ env_name: "BRANCH_ACTIVITY_NAME",
156
+ description: "Name of the Activity in the manifest containing Branch intent-filers (Android only)",
157
+ optional: true,
158
+ type: String),
159
+ FastlaneCore::ConfigItem.new(key: :target,
160
+ env_name: "BRANCH_TARGET",
161
+ description: "Name of the target in the Xcode project to modify (iOS only)",
162
+ optional: true,
163
+ type: String),
164
+ FastlaneCore::ConfigItem.new(key: :update_bundle_and_team_ids,
165
+ env_name: "BRANCH_UPDATE_BUNDLE_AND_TEAM_IDS",
166
+ description: "If set to true, updates the bundle and team identifiers to match the AASA file (iOS only)",
167
+ optional: true,
168
+ default_value: false,
169
+ is_string: false),
170
+ FastlaneCore::ConfigItem.new(key: :remove_existing_domains,
171
+ env_name: "BRANCH_REMOVE_EXISTING_DOMAINS",
172
+ description: "If set to true, removes any existing domains before adding Branch domains",
173
+ optional: true,
174
+ default_value: false,
175
+ is_string: false),
176
+ FastlaneCore::ConfigItem.new(key: :force,
177
+ env_name: "BRANCH_FORCE_UPDATE",
178
+ description: "Update project(s) even if Universal Link validation fails",
179
+ optional: true,
180
+ default_value: false,
181
+ is_string: false),
182
+ FastlaneCore::ConfigItem.new(key: :commit,
183
+ env_name: "BRANCH_COMMIT_CHANGES",
184
+ description: "Set to true to commit changes to Git; set to a string to commit with a custom message",
185
+ optional: true,
186
+ default_value: false,
187
+ is_string: false)
188
+ ]
189
+ end
190
+
191
+ def self.is_supported?(platform)
192
+ [:ios, :android].include? platform
193
+ end
194
+
195
+ def self.category
196
+ :project
197
+ end
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,121 @@
1
+ require "xcodeproj"
2
+
3
+ module Fastlane
4
+ module Actions
5
+ class ValidateUniversalLinksAction < Action
6
+ def self.run(params)
7
+ helper = Fastlane::Helper::BranchHelper
8
+
9
+ xcodeproj_path = helper.xcodeproj_path_from_params params
10
+ # Error reporting is done in the helper.
11
+ return false if xcodeproj_path.nil?
12
+
13
+ # raises
14
+ xcodeproj = Xcodeproj::Project.open xcodeproj_path
15
+
16
+ target = params[:target] # may be nil
17
+ domains = params[:domains] # may be nil
18
+
19
+ valid = true
20
+
21
+ unless domains.nil?
22
+ domains_valid = helper.validate_project_domains(
23
+ helper.custom_domains_from_params(params),
24
+ xcodeproj,
25
+ target
26
+ )
27
+
28
+ if domains_valid
29
+ UI.message "Project domains match :domains parameter: ✅"
30
+ else
31
+ UI.error "Project domains do not match specified :domains"
32
+ helper.errors.each { |error| UI.error " #{error}" }
33
+ end
34
+
35
+ valid &&= domains_valid
36
+ end
37
+
38
+ configuration_valid = helper.validate_team_and_bundle_ids_from_aasa_files xcodeproj, target
39
+ unless configuration_valid
40
+ UI.error "Universal Link configuration failed validation."
41
+ helper.errors.each { |error| UI.error " #{error}" }
42
+ end
43
+
44
+ valid &&= configuration_valid
45
+
46
+ UI.message "Universal Link configuration passed validation. ✅" if valid
47
+
48
+ valid
49
+ rescue => e
50
+ UI.user_error! "Error in ValidateUniversalLinksAction: #{e.message}\n#{e.backtrace}"
51
+ false
52
+ end
53
+
54
+ def self.description
55
+ "Validates Universal Link configuration for an Xcode project."
56
+ end
57
+
58
+ def self.authors
59
+ [
60
+ "Branch <integrations@branch.io>",
61
+ "Jimmy Dee <jgvdthree@gmail.com>"
62
+ ]
63
+ end
64
+
65
+ def self.details
66
+ "This action validates all the Universal Link domains found in an Xcode project's entitlements " \
67
+ "file by ensuring that the development team and bundle identifier combination is found in the " \
68
+ "domain's apple-app-site-association file."
69
+ end
70
+
71
+ def self.example_code
72
+ [
73
+ <<-EOF
74
+ validate_universal_links
75
+ EOF,
76
+ <<-EOF
77
+ validate_universal_links xcodeproj: "MyProject.xcodeproj"
78
+ EOF,
79
+ <<-EOF
80
+ validate_universal_links xcodeproj: "MyProject.xcodeproj", target: "MyProject"
81
+ EOF,
82
+ <<-EOF
83
+ validate_universal_links domains: %w{example.com www.example.com}
84
+ EOF
85
+ ]
86
+ end
87
+
88
+ def self.available_options
89
+ [
90
+ FastlaneCore::ConfigItem.new(key: :xcodeproj,
91
+ env_name: "BRANCH_XCODEPROJ",
92
+ description: "Path to an Xcode project to modify",
93
+ optional: true,
94
+ type: String),
95
+ FastlaneCore::ConfigItem.new(key: :target,
96
+ env_name: "BRANCH_TARGET",
97
+ description: "Name of the target in the Xcode project to modify (iOS only)",
98
+ optional: true,
99
+ type: String),
100
+ FastlaneCore::ConfigItem.new(key: :domains,
101
+ env_name: "BRANCH_DOMAINS",
102
+ description: "Branch (and/or non-Branch) Universal Link/App Link domains expected to be present in project (comma-separated list or array)",
103
+ optional: true,
104
+ is_string: false)
105
+ ]
106
+ end
107
+
108
+ def self.return_value
109
+ "Returns true for a valid configuration, false otherwise."
110
+ end
111
+
112
+ def self.is_supported?(platform)
113
+ platform == :ios
114
+ end
115
+
116
+ def self.category
117
+ :project
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,86 @@
1
+ module Fastlane
2
+ module Helper
3
+ module AndroidHelper
4
+ def add_keys_to_android_manifest(manifest, keys)
5
+ add_metadata_to_manifest manifest, "io.branch.sdk.BranchKey", keys[:live] unless keys[:live].nil?
6
+ add_metadata_to_manifest manifest, "io.branch.sdk.BranchKey.test", keys[:test] unless keys[:test].nil?
7
+ end
8
+
9
+ # TODO: Work on all XML/AndroidManifest formatting
10
+
11
+ def add_metadata_to_manifest(manifest, key, value)
12
+ element = manifest.elements["//manifest/application/meta-data[@android:name=\"#{key}\"]"]
13
+ if element.nil?
14
+ application = manifest.elements["//manifest/application"]
15
+ application.add_element "meta-data", "android:name" => key, "android:value" => value
16
+ else
17
+ element.attributes["android:value"] = value
18
+ end
19
+ end
20
+
21
+ def add_intent_filters_to_android_manifest(manifest, domains, uri_scheme, activity_name, remove_existing)
22
+ if activity_name
23
+ activity = manifest.elements["//manifest/application/activity[@android:name=\"#{activity_name}\""]
24
+ else
25
+ activity = find_activity manifest
26
+ end
27
+
28
+ raise "Failed to find an Activity in the Android manifest" if activity.nil?
29
+
30
+ if remove_existing
31
+ remove_existing_domains(activity)
32
+ end
33
+
34
+ add_intent_filter_to_activity activity, domains, uri_scheme
35
+ end
36
+
37
+ def find_activity(manifest)
38
+ # try to infer the right activity
39
+ # look for the first singleTask
40
+ single_task_activity = manifest.elements["//manifest/application/activity[@android:launchMode=\"singleTask\"]"]
41
+ return single_task_activity if single_task_activity
42
+
43
+ # no singleTask activities. Take the first Activity
44
+ # TODO: Add singleTask?
45
+ manifest.elements["//manifest/application/activity"]
46
+ end
47
+
48
+ def add_intent_filter_to_activity(activity, domains, uri_scheme)
49
+ # Add a single intent-filter with autoVerify and a data element for each domain and the optional uri_scheme
50
+ intent_filter = REXML::Element.new "intent-filter"
51
+ intent_filter.attributes["android:autoverify"] = true
52
+ intent_filter.add_element "action", "android:name" => "android.intent.action.VIEW"
53
+ intent_filter.add_element "category", "android:name" => "android.intent.category.DEFAULT"
54
+ intent_filter.add_element "category", "android:name" => "android.intent.category.BROWSABLE"
55
+ intent_filter.elements << uri_scheme_data_element(uri_scheme) unless uri_scheme.nil?
56
+ app_link_data_elements(domains).each { |e| intent_filter.elements << e }
57
+
58
+ activity.add_element intent_filter
59
+ end
60
+
61
+ def remove_existing_domains(activity)
62
+ # Find all intent-filters that include a data element with android:scheme
63
+ # TODO: Can this be done with a single css/at_css call?
64
+ activity.elements.each("//manifest//intent-filter") do |filter|
65
+ filter.remove if filter.elements["data[@android:scheme]"]
66
+ end
67
+ end
68
+
69
+ def app_link_data_elements(domains)
70
+ domains.map do |domain|
71
+ element = REXML::Element.new "data"
72
+ element.attributes["android:scheme"] = "https"
73
+ element.attributes["android:host"] = domain
74
+ element
75
+ end
76
+ end
77
+
78
+ def uri_scheme_data_element(uri_scheme)
79
+ element = REXML::Element.new "data"
80
+ element.attributes["android:scheme"] = uri_scheme
81
+ element.attributes["android:host"] = "open"
82
+ element
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
1
+ require "fastlane/plugin/branch/helper/android_helper"
2
+ require "fastlane/plugin/branch/helper/configuration_helper"
3
+ require "fastlane/plugin/branch/helper/ios_helper"
4
+
5
+ module Fastlane
6
+ module Helper
7
+ UI = FastlaneCore::UI
8
+
9
+ class BranchHelper
10
+ class << self
11
+ attr_accessor :changes # An array of file paths (Strings) that were modified
12
+ attr_accessor :errors # An array of error messages (Strings) from validation
13
+
14
+ include AndroidHelper
15
+ include ConfigurationHelper
16
+ include IOSHelper
17
+
18
+ def add_change(change)
19
+ @changes ||= Set.new
20
+ @changes << change.to_s
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,86 @@
1
+ module Fastlane
2
+ module Helper
3
+ module ConfigurationHelper
4
+ def keys_from_params(params)
5
+ live_key = params[:live_key]
6
+ test_key = params[:test_key]
7
+ keys = {}
8
+ keys[:live] = live_key unless live_key.nil?
9
+ keys[:test] = test_key unless test_key.nil?
10
+ keys
11
+ end
12
+
13
+ def xcodeproj_path_from_params(params)
14
+ return params[:xcodeproj] if params[:xcodeproj]
15
+
16
+ # Adapted from commit_version_bump
17
+ # https://github.com/fastlane/fastlane/blob/master/fastlane/lib/fastlane/actions/commit_version_bump.rb#L21
18
+
19
+ # This may not be a git project. Search relative to the Gemfile.
20
+ repo_path = Bundler.root
21
+
22
+ all_xcodeproj_paths = Dir[File.expand_path(File.join(repo_path, '**/*.xcodeproj'))]
23
+ # find an xcodeproj (ignoring the Cocoapods one)
24
+ xcodeproj_paths = Fastlane::Actions.ignore_cocoapods_path(all_xcodeproj_paths)
25
+
26
+ # no projects found: error
27
+ UI.user_error!('Could not find a .xcodeproj in the current repository\'s working directory.') and return nil if xcodeproj_paths.count == 0
28
+
29
+ # too many projects found: error
30
+ if xcodeproj_paths.count > 1
31
+ repo_pathname = Pathname.new repo_path
32
+ relative_projects = xcodeproj_paths.map { |e| Pathname.new(e).relative_path_from(repo_pathname).to_s }.join("\n")
33
+ UI.user_error!("Found multiple .xcodeproj projects in the current repository's working directory. Please specify your app's main project: \n#{relative_projects}")
34
+ return nil
35
+ end
36
+
37
+ # one project found: great
38
+ xcodeproj_paths.first
39
+ end
40
+
41
+ def domains_from_params(params)
42
+ app_link_subdomains = app_link_subdomains_from_params params
43
+ custom_domains = custom_domains_from_params params
44
+ (app_link_subdomains + custom_domains).uniq
45
+ end
46
+
47
+ def app_link_subdomains_from_params(params)
48
+ app_link_subdomain = params[:app_link_subdomain]
49
+ live_key = params[:live_key]
50
+ test_key = params[:test_key]
51
+ return [] if live_key.nil? and test_key.nil?
52
+ return [] if app_link_subdomain.nil?
53
+
54
+ domains = []
55
+ unless live_key.nil?
56
+ domains += [
57
+ "#{app_link_subdomain}.app.link",
58
+ "#{app_link_subdomain}-alternate.app.link"
59
+ ]
60
+ end
61
+ unless test_key.nil?
62
+ domains += [
63
+ "#{app_link_subdomain}.test-app.link",
64
+ "#{app_link_subdomain}-alternate.test-app.link"
65
+ ]
66
+ end
67
+ domains
68
+ end
69
+
70
+ def custom_domains_from_params(params)
71
+ domains = params[:domains]
72
+ return [] if domains.nil?
73
+
74
+ if domains.kind_of? Array
75
+ domains = domains.map(&:to_s)
76
+ elsif domains.kind_of? String
77
+ domains = domains.split(",")
78
+ else
79
+ raise ArgumentError, "Unsupported type #{domains.class.name} for :domains key"
80
+ end
81
+
82
+ domains
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,331 @@
1
+ require "plist"
2
+
3
+ module Fastlane
4
+ module Helper
5
+ module IOSHelper
6
+ APPLINKS = "applinks"
7
+ ASSOCIATED_DOMAINS = "com.apple.developer.associated-domains"
8
+ CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS"
9
+ DEVELOPMENT_TEAM = "DEVELOPMENT_TEAM"
10
+ PRODUCT_BUNDLE_IDENTIFIER = "PRODUCT_BUNDLE_IDENTIFIER"
11
+ RELEASE_CONFIGURATION = "Release"
12
+
13
+ def add_keys_to_info_plist(project, target_name, keys, configuration = RELEASE_CONFIGURATION)
14
+ update_info_plist_setting project, target_name, configuration do |info_plist|
15
+ # add/overwrite Branch key(s)
16
+ if keys.count > 1
17
+ info_plist["branch_key"] = keys
18
+ elsif keys[:live]
19
+ info_plist["branch_key"] = keys[:live]
20
+ else # no need to validate here, which was done by the action
21
+ info_plist["branch_key"] = keys[:test]
22
+ end
23
+ end
24
+ end
25
+
26
+ def add_branch_universal_link_domains_to_info_plist(project, target_name, domains, configuration = RELEASE_CONFIGURATION)
27
+ # Add all supplied domains unless all are app.link domains.
28
+ return if domains.all? { |d| d =~ /app\.link$/ }
29
+
30
+ update_info_plist_setting project, target_name, configuration do |info_plist|
31
+ info_plist["branch_universal_link_domains"] = domains
32
+ end
33
+ end
34
+
35
+ def update_info_plist_setting(project, target_name, configuration = RELEASE_CONFIGURATION, &b)
36
+ # raises
37
+ target = target_from_project project, target_name
38
+
39
+ # find the Info.plist paths for this configuration
40
+ info_plist_path = expanded_build_setting target, "INFOPLIST_FILE", configuration
41
+
42
+ raise "Info.plist not found for configuration #{configuration}" if info_plist_path.nil?
43
+
44
+ project_parent = File.dirname project.path
45
+
46
+ info_plist_path = File.expand_path info_plist_path, project_parent
47
+
48
+ # try to open and parse the Info.plist (raises)
49
+ info_plist = File.open(info_plist_path) { |f| Plist.parse_xml f }
50
+ raise "Failed to parse #{info_plist_path}" if info_plist.nil?
51
+
52
+ yield info_plist
53
+
54
+ Plist::Emit.save_plist info_plist, info_plist_path
55
+ add_change info_plist_path
56
+ end
57
+
58
+ def add_universal_links_to_project(project, target_name, domains, remove_existing, configuration = RELEASE_CONFIGURATION)
59
+ # raises
60
+ target = target_from_project project, target_name
61
+
62
+ relative_entitlements_path = expanded_build_setting target, CODE_SIGN_ENTITLEMENTS, configuration
63
+ project_parent = File.dirname project.path
64
+
65
+ if relative_entitlements_path.nil?
66
+ relative_entitlements_path = File.join target.name, "#{target.name}.entitlements"
67
+ entitlements_path = File.expand_path relative_entitlements_path, project_parent
68
+
69
+ # Add CODE_SIGN_ENTITLEMENTS setting to each configuration
70
+ target.build_configuration_list.set_setting CODE_SIGN_ENTITLEMENTS, relative_entitlements_path
71
+
72
+ # Add the file to the project
73
+ project.new_file relative_entitlements_path
74
+
75
+ entitlements = {}
76
+ current_domains = []
77
+
78
+ add_change project.path
79
+ new_path = entitlements_path
80
+ else
81
+ entitlements_path = File.expand_path relative_entitlements_path, project_parent
82
+ # Raises
83
+ entitlements = File.open(entitlements_path) { |f| Plist.parse_xml f }
84
+ raise "Failed to parse entitlements file #{entitlements_path}" if entitlements.nil?
85
+
86
+ if remove_existing
87
+ current_domains = []
88
+ else
89
+ current_domains = entitlements[ASSOCIATED_DOMAINS]
90
+ end
91
+ end
92
+
93
+ current_domains += domains.map { |d| "#{APPLINKS}:#{d}" }
94
+ all_domains = current_domains.uniq
95
+
96
+ entitlements[ASSOCIATED_DOMAINS] = all_domains
97
+
98
+ Plist::Emit.save_plist entitlements, entitlements_path
99
+ add_change entitlements_path
100
+
101
+ new_path
102
+ end
103
+
104
+ def team_and_bundle_from_app_id(identifier)
105
+ team = identifier.sub(/\..+$/, "")
106
+ bundle = identifier.sub(/^[^.]+\./, "")
107
+ [team, bundle]
108
+ end
109
+
110
+ def update_team_and_bundle_ids_from_aasa_file(project, target_name, domain)
111
+ # raises
112
+ identifiers = app_ids_from_aasa_file domain
113
+ raise "Multiple appIDs found in AASA file" if identifiers.count > 1
114
+
115
+ identifier = identifiers[0]
116
+ team, bundle = team_and_bundle_from_app_id identifier
117
+
118
+ update_team_and_bundle_ids project, target_name, team, bundle
119
+ add_change project.path.expand_path
120
+ end
121
+
122
+ def validate_team_and_bundle_ids_from_aasa_files(project, target_name, domains = [], remove_existing = false, configuration = RELEASE_CONFIGURATION)
123
+ @errors = []
124
+ valid = true
125
+
126
+ # Include any domains already in the project.
127
+ # Raises. Returns a non-nil array of strings.
128
+ if remove_existing
129
+ # Don't validate domains to be removed (#16)
130
+ all_domains = domains
131
+ else
132
+ all_domains = (domains + domains_from_project(project, target_name, configuration)).uniq
133
+ end
134
+
135
+ if all_domains.empty?
136
+ # Cannot get here from SetupBranchAction, since the domains passed in will never be empty.
137
+ # If called from ValidateUniversalLinksAction, this is a failure, possibly caused by
138
+ # failure to add applinks:.
139
+ @errors << "No Universal Link domains in project. Be sure each Universal Link domain is prefixed with applinks:."
140
+ return false
141
+ end
142
+
143
+ all_domains.each do |domain|
144
+ domain_valid = validate_team_and_bundle_ids project, target_name, domain, configuration
145
+ valid &&= domain_valid
146
+ UI.message "Valid Universal Link configuration for #{domain} ✅" if domain_valid
147
+ end
148
+ valid
149
+ end
150
+
151
+ def app_ids_from_aasa_file(domain)
152
+ data = contents_of_aasa_file domain
153
+ # errors reported in the method above
154
+ return nil if data.nil?
155
+
156
+ # raises
157
+ file = JSON.parse data
158
+
159
+ applinks = file[APPLINKS]
160
+ @errors << "[#{domain}] No #{APPLINKS} found in AASA file" and return if applinks.nil?
161
+
162
+ details = applinks["details"]
163
+ @errors << "[#{domain}] No details found for #{APPLINKS} in AASA file" and return if details.nil?
164
+
165
+ identifiers = details.map { |d| d["appID"] }.uniq
166
+ @errors << "[#{domain}] No appID found in AASA file" and return if identifiers.count <= 0
167
+ identifiers
168
+ rescue JSON::ParserError => e
169
+ @errors << "[#{domain}] Failed to parse AASA file: #{e.message}"
170
+ nil
171
+ end
172
+
173
+ def contents_of_aasa_file(domain)
174
+ uris = [
175
+ URI("https://#{domain}/.well-known/apple-app-site-association"),
176
+ URI("https://#{domain}/apple-app-site-association")
177
+ ]
178
+
179
+ data = nil
180
+
181
+ uris.each do |uri|
182
+ break unless data.nil?
183
+
184
+ Net::HTTP.start uri.host, uri.port, use_ssl: uri.scheme == "https" do |http|
185
+ request = Net::HTTP::Get.new uri
186
+ response = http.request request
187
+
188
+ # Better to use Net::HTTPRedirection and Net::HTTPSuccess here, but
189
+ # having difficulty with the unit tests.
190
+ if (300..399).cover?(response.code.to_i)
191
+ UI.important "#{uri} cannot result in a redirect. Ignoring."
192
+ next
193
+ elsif response.code.to_i != 200
194
+ # Try the next URI.
195
+ UI.message "Could not retrieve #{uri}: #{response.code} #{response.message}. Ignoring."
196
+ next
197
+ end
198
+
199
+ content_type = response["Content-type"]
200
+ @errors << "[#{domain}] AASA Response does not contain a Content-type header" and return nil if content_type.nil?
201
+
202
+ case content_type
203
+ when %r{application/pkcs7-mime}
204
+ # Verify/decrypt PKCS7 (non-Branch domains)
205
+ cert_store = OpenSSL::X509::Store.new
206
+ signature = OpenSSL::PKCS7.new response.body
207
+ # raises
208
+ signature.verify [http.peer_cert], cert_store, nil, OpenSSL::PKCS7::NOVERIFY
209
+ data = signature.data
210
+ else
211
+ data = response.body
212
+ end
213
+
214
+ UI.message "GET #{uri}: #{response.code} #{response.message} (Content-type:#{content_type}) ✅"
215
+ end
216
+ end
217
+
218
+ @errors << "[#{domain}] Failed to retrieve AASA file" and return nil if data.nil?
219
+
220
+ data
221
+ rescue IOError, SocketError => e
222
+ @errors << "[#{domain}] Socket error: #{e.message}"
223
+ nil
224
+ rescue OpenSSL::PKCS7::PKCS7Error => e
225
+ @errors << "[#{domain}] Failed to verify signed AASA file: #{e.message}"
226
+ nil
227
+ end
228
+
229
+ def validate_team_and_bundle_ids(project, target_name, domain, configuration)
230
+ # raises
231
+ target = target_from_project project, target_name
232
+
233
+ product_bundle_identifier = expanded_build_setting target, PRODUCT_BUNDLE_IDENTIFIER, configuration
234
+ development_team = expanded_build_setting target, DEVELOPMENT_TEAM, configuration
235
+
236
+ identifiers = app_ids_from_aasa_file domain
237
+ return false if identifiers.nil?
238
+
239
+ app_id = "#{development_team}.#{product_bundle_identifier}"
240
+ match_found = identifiers.include? app_id
241
+
242
+ unless match_found
243
+ @errors << "[#{domain}] appID mismatch. Project: #{app_id}. AASA: #{identifiers}"
244
+ end
245
+
246
+ match_found
247
+ end
248
+
249
+ def validate_project_domains(expected, project, target, configuration = RELEASE_CONFIGURATION)
250
+ @errors = []
251
+ project_domains = domains_from_project project, target, configuration
252
+ valid = expected.count == project_domains.count
253
+ if valid
254
+ sorted = expected.sort
255
+ project_domains.sort.each_with_index do |domain, index|
256
+ valid = false and break unless sorted[index] == domain
257
+ end
258
+ end
259
+
260
+ unless valid
261
+ @errors << "Project domains do not match :domains parameter"
262
+ @errors << "Project domains: #{project_domains}"
263
+ @errors << ":domains parameter: #{expected}"
264
+ end
265
+
266
+ valid
267
+ end
268
+
269
+ def update_team_and_bundle_ids(project, target_name, team, bundle)
270
+ # raises
271
+ target = target_from_project project, target_name
272
+
273
+ target.build_configuration_list.set_setting PRODUCT_BUNDLE_IDENTIFIER, bundle
274
+ target.build_configuration_list.set_setting DEVELOPMENT_TEAM, team
275
+
276
+ # also update the team in the first test target
277
+ target = project.targets.find(&:test_target_type?)
278
+ return if target.nil?
279
+
280
+ target.build_configuration_list.set_setting DEVELOPMENT_TEAM, team
281
+ end
282
+
283
+ def target_from_project(project, target_name)
284
+ if target_name
285
+ target = project.targets.find { |t| t.name == target_name }
286
+ raise "Target #{target} not found" if target.nil?
287
+ else
288
+ # find the first application target
289
+ target = project.targets.find { |t| !t.extension_target_type? && !t.test_target_type? }
290
+ raise "No application target found" if target.nil?
291
+ end
292
+ target
293
+ end
294
+
295
+ def domains_from_project(project, target_name, configuration = RELEASE_CONFIGURATION)
296
+ # Raises. Does not return nil.
297
+ target = target_from_project project, target_name
298
+
299
+ relative_entitlements_path = expanded_build_setting target, CODE_SIGN_ENTITLEMENTS, configuration
300
+ return [] if relative_entitlements_path.nil?
301
+
302
+ project_parent = File.dirname project.path
303
+ entitlements_path = File.expand_path relative_entitlements_path, project_parent
304
+
305
+ # Raises
306
+ entitlements = File.open(entitlements_path) { |f| Plist.parse_xml f }
307
+ raise "Failed to parse entitlements file #{entitlements_path}" if entitlements.nil?
308
+
309
+ entitlements[ASSOCIATED_DOMAINS].select { |d| d =~ /^applinks:/ }.map { |d| d.sub(/^applinks:/, "") }
310
+ end
311
+
312
+ def expanded_build_setting(target, setting_name, configuration)
313
+ setting_value = target.resolved_build_setting(setting_name)[configuration]
314
+ return if setting_value.nil?
315
+
316
+ search_position = 0
317
+ while (matches = /\$\(([^(){}]*)\)|\$\{([^(){}]*)\}/.match(setting_value, search_position))
318
+ macro_name = matches[1] || matches[2]
319
+ search_position = setting_value.index(macro_name) - 2
320
+
321
+ expanded_macro = macro_name == "SRCROOT" ? "." : expanded_build_setting(target, macro_name, configuration)
322
+ search_position += macro_name.length + 3 and next if expanded_macro.nil?
323
+
324
+ setting_value.gsub!(/\$\(#{macro_name}\)|\$\{#{macro_name}\}/, expanded_macro)
325
+ search_position += expanded_macro.length
326
+ end
327
+ setting_value
328
+ end
329
+ end
330
+ end
331
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module Branch
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-branch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jimmy Dee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: plist
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: xcodeproj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fastlane
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 2.26.1
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 2.26.1
125
+ description:
126
+ email: jgvdthree@gmail.com
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - LICENSE
132
+ - README.md
133
+ - lib/fastlane/plugin/branch.rb
134
+ - lib/fastlane/plugin/branch/actions/setup_branch_action.rb
135
+ - lib/fastlane/plugin/branch/actions/validate_universal_links_action.rb
136
+ - lib/fastlane/plugin/branch/helper/android_helper.rb
137
+ - lib/fastlane/plugin/branch/helper/branch_helper.rb
138
+ - lib/fastlane/plugin/branch/helper/configuration_helper.rb
139
+ - lib/fastlane/plugin/branch/helper/ios_helper.rb
140
+ - lib/fastlane/plugin/branch/version.rb
141
+ homepage: https://github.com/BranchMetrics/fastlane-plugin-branch
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.12
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Adds Branch keys, custom URI schemes and domains to iOS and Android projects.
165
+ test_files: []
166
+ has_rdoc: