fastlane-plugin-branch 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
+ 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: