fastlane-plugin-branch 0.5.1 → 0.6.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 +4 -4
- data/README.md +45 -45
- data/lib/fastlane/plugin/branch/actions/setup_branch_action.rb +15 -206
- data/lib/fastlane/plugin/branch/actions/validate_universal_links_action.rb +3 -43
- data/lib/fastlane/plugin/branch/helper/branch_options.rb +16 -0
- data/lib/fastlane/plugin/branch/version.rb +1 -1
- metadata +15 -32
- data/lib/fastlane/plugin/branch/helper/android_helper.rb +0 -86
- data/lib/fastlane/plugin/branch/helper/branch_helper.rb +0 -25
- data/lib/fastlane/plugin/branch/helper/configuration_helper.rb +0 -132
- data/lib/fastlane/plugin/branch/helper/ios_helper.rb +0 -505
@@ -0,0 +1,16 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Helper
|
3
|
+
class BranchOptions
|
4
|
+
attr_reader :params
|
5
|
+
|
6
|
+
# :param: params [FastlaneCore::Configuration] Params from an action
|
7
|
+
def initialize(params)
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method_sym, *arguments, &block)
|
12
|
+
return params[method_sym]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-branch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Branch
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-11-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: branch_io_cli
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
@@ -26,27 +26,13 @@ dependencies:
|
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '0'
|
35
|
-
type: :runtime
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: xcodeproj
|
29
|
+
name: pry
|
44
30
|
requirement: !ruby/object:Gem::Requirement
|
45
31
|
requirements:
|
46
32
|
- - ">="
|
47
33
|
- !ruby/object:Gem::Version
|
48
34
|
version: '0'
|
49
|
-
type: :
|
35
|
+
type: :development
|
50
36
|
prerelease: false
|
51
37
|
version_requirements: !ruby/object:Gem::Requirement
|
52
38
|
requirements:
|
@@ -54,7 +40,7 @@ dependencies:
|
|
54
40
|
- !ruby/object:Gem::Version
|
55
41
|
version: '0'
|
56
42
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
43
|
+
name: bundler
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
59
45
|
requirements:
|
60
46
|
- - ">="
|
@@ -68,7 +54,7 @@ dependencies:
|
|
68
54
|
- !ruby/object:Gem::Version
|
69
55
|
version: '0'
|
70
56
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
57
|
+
name: rspec
|
72
58
|
requirement: !ruby/object:Gem::Requirement
|
73
59
|
requirements:
|
74
60
|
- - ">="
|
@@ -82,7 +68,7 @@ dependencies:
|
|
82
68
|
- !ruby/object:Gem::Version
|
83
69
|
version: '0'
|
84
70
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
71
|
+
name: rake
|
86
72
|
requirement: !ruby/object:Gem::Requirement
|
87
73
|
requirements:
|
88
74
|
- - ">="
|
@@ -96,7 +82,7 @@ dependencies:
|
|
96
82
|
- !ruby/object:Gem::Version
|
97
83
|
version: '0'
|
98
84
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
85
|
+
name: rspec_junit_formatter
|
100
86
|
requirement: !ruby/object:Gem::Requirement
|
101
87
|
requirements:
|
102
88
|
- - ">="
|
@@ -127,16 +113,16 @@ dependencies:
|
|
127
113
|
name: rubocop
|
128
114
|
requirement: !ruby/object:Gem::Requirement
|
129
115
|
requirements:
|
130
|
-
- - "
|
116
|
+
- - "~>"
|
131
117
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
118
|
+
version: 0.50.0
|
133
119
|
type: :development
|
134
120
|
prerelease: false
|
135
121
|
version_requirements: !ruby/object:Gem::Requirement
|
136
122
|
requirements:
|
137
|
-
- - "
|
123
|
+
- - "~>"
|
138
124
|
- !ruby/object:Gem::Version
|
139
|
-
version:
|
125
|
+
version: 0.50.0
|
140
126
|
- !ruby/object:Gem::Dependency
|
141
127
|
name: simplecov
|
142
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,10 +164,7 @@ files:
|
|
178
164
|
- lib/fastlane/plugin/branch.rb
|
179
165
|
- lib/fastlane/plugin/branch/actions/setup_branch_action.rb
|
180
166
|
- lib/fastlane/plugin/branch/actions/validate_universal_links_action.rb
|
181
|
-
- lib/fastlane/plugin/branch/helper/
|
182
|
-
- lib/fastlane/plugin/branch/helper/branch_helper.rb
|
183
|
-
- lib/fastlane/plugin/branch/helper/configuration_helper.rb
|
184
|
-
- lib/fastlane/plugin/branch/helper/ios_helper.rb
|
167
|
+
- lib/fastlane/plugin/branch/helper/branch_options.rb
|
185
168
|
- lib/fastlane/plugin/branch/version.rb
|
186
169
|
homepage: https://github.com/BranchMetrics/fastlane-plugin-branch
|
187
170
|
licenses:
|
@@ -203,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
186
|
version: '0'
|
204
187
|
requirements: []
|
205
188
|
rubyforge_project:
|
206
|
-
rubygems_version: 2.6.
|
189
|
+
rubygems_version: 2.6.14
|
207
190
|
signing_key:
|
208
191
|
specification_version: 4
|
209
192
|
summary: Adds Branch keys, custom URI schemes and domains to iOS and Android projects.
|
@@ -1,86 +0,0 @@
|
|
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
|
@@ -1,25 +0,0 @@
|
|
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
|
@@ -1,132 +0,0 @@
|
|
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
|
-
|
85
|
-
def podfile_path_from_params(params)
|
86
|
-
# Disable Podfile update if add_sdk: false is present
|
87
|
-
return nil unless add_sdk? params
|
88
|
-
|
89
|
-
# Use the :podfile parameter if present
|
90
|
-
if params[:podfile]
|
91
|
-
UI.user_error! ":podfile argument must specify a path ending in '/Podfile'" unless params[:podfile] =~ %r{/Podfile$}
|
92
|
-
podfile_path = File.expand_path params[:podfile], Bundler.root
|
93
|
-
return podfile_path if File.exist? podfile_path
|
94
|
-
UI.user_error! "#{podfile_path} not found"
|
95
|
-
end
|
96
|
-
|
97
|
-
# Look in the same directory as the project (typical setup)
|
98
|
-
podfile_path = File.expand_path "../Podfile", params[:xcodeproj]
|
99
|
-
return podfile_path if File.exist? podfile_path
|
100
|
-
end
|
101
|
-
|
102
|
-
def cartfile_path_from_params(params)
|
103
|
-
# Disable Cartfile update if add_sdk: false is present
|
104
|
-
return nil unless add_sdk? params
|
105
|
-
|
106
|
-
# Use the :cartfile parameter if present
|
107
|
-
if params[:cartfile]
|
108
|
-
UI.user_error! ":cartfile argument must specify a path ending in '/Cartfile'" unless params[:cartfile] =~ %r{/Cartfile$}
|
109
|
-
cartfile_path = File.expand_path params[:cartfile], Bundler.root
|
110
|
-
return cartfile_path if File.exist? cartfile_path
|
111
|
-
UI.user_error! "#{cartfile_path} not found"
|
112
|
-
end
|
113
|
-
|
114
|
-
# Look in the same directory as the project (typical setup)
|
115
|
-
cartfile_path = File.expand_path "../Cartfile", params[:xcodeproj]
|
116
|
-
return cartfile_path if File.exist? cartfile_path
|
117
|
-
end
|
118
|
-
|
119
|
-
def add_sdk?(params)
|
120
|
-
add_sdk_param = params[:add_sdk]
|
121
|
-
return false if add_sdk_param.nil?
|
122
|
-
|
123
|
-
case add_sdk_param
|
124
|
-
when String
|
125
|
-
add_sdk_param.casecmp? "true"
|
126
|
-
else
|
127
|
-
add_sdk_param
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
@@ -1,505 +0,0 @@
|
|
1
|
-
require "fastlane/plugin/patch"
|
2
|
-
require "plist"
|
3
|
-
|
4
|
-
module Fastlane
|
5
|
-
module Helper
|
6
|
-
module IOSHelper
|
7
|
-
APPLINKS = "applinks"
|
8
|
-
ASSOCIATED_DOMAINS = "com.apple.developer.associated-domains"
|
9
|
-
CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS"
|
10
|
-
DEVELOPMENT_TEAM = "DEVELOPMENT_TEAM"
|
11
|
-
PRODUCT_BUNDLE_IDENTIFIER = "PRODUCT_BUNDLE_IDENTIFIER"
|
12
|
-
RELEASE_CONFIGURATION = "Release"
|
13
|
-
|
14
|
-
def add_keys_to_info_plist(project, target_name, keys, configuration = RELEASE_CONFIGURATION)
|
15
|
-
update_info_plist_setting project, target_name, configuration do |info_plist|
|
16
|
-
# add/overwrite Branch key(s)
|
17
|
-
if keys.count > 1
|
18
|
-
info_plist["branch_key"] = keys
|
19
|
-
elsif keys[:live]
|
20
|
-
info_plist["branch_key"] = keys[:live]
|
21
|
-
else # no need to validate here, which was done by the action
|
22
|
-
info_plist["branch_key"] = keys[:test]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def add_branch_universal_link_domains_to_info_plist(project, target_name, domains, configuration = RELEASE_CONFIGURATION)
|
28
|
-
# Add all supplied domains unless all are app.link domains.
|
29
|
-
return if domains.all? { |d| d =~ /app\.link$/ }
|
30
|
-
|
31
|
-
update_info_plist_setting project, target_name, configuration do |info_plist|
|
32
|
-
info_plist["branch_universal_link_domains"] = domains
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def update_info_plist_setting(project, target_name, configuration = RELEASE_CONFIGURATION, &b)
|
37
|
-
# raises
|
38
|
-
target = target_from_project project, target_name
|
39
|
-
|
40
|
-
# find the Info.plist paths for this configuration
|
41
|
-
info_plist_path = expanded_build_setting target, "INFOPLIST_FILE", configuration
|
42
|
-
|
43
|
-
raise "Info.plist not found for configuration #{configuration}" if info_plist_path.nil?
|
44
|
-
|
45
|
-
project_parent = File.dirname project.path
|
46
|
-
|
47
|
-
info_plist_path = File.expand_path info_plist_path, project_parent
|
48
|
-
|
49
|
-
# try to open and parse the Info.plist (raises)
|
50
|
-
info_plist = File.open(info_plist_path) { |f| Plist.parse_xml f }
|
51
|
-
raise "Failed to parse #{info_plist_path}" if info_plist.nil?
|
52
|
-
|
53
|
-
yield info_plist
|
54
|
-
|
55
|
-
Plist::Emit.save_plist info_plist, info_plist_path
|
56
|
-
add_change info_plist_path
|
57
|
-
end
|
58
|
-
|
59
|
-
def add_universal_links_to_project(project, target_name, domains, remove_existing, configuration = RELEASE_CONFIGURATION)
|
60
|
-
# raises
|
61
|
-
target = target_from_project project, target_name
|
62
|
-
|
63
|
-
relative_entitlements_path = expanded_build_setting target, CODE_SIGN_ENTITLEMENTS, configuration
|
64
|
-
project_parent = File.dirname project.path
|
65
|
-
|
66
|
-
if relative_entitlements_path.nil?
|
67
|
-
relative_entitlements_path = File.join target.name, "#{target.name}.entitlements"
|
68
|
-
entitlements_path = File.expand_path relative_entitlements_path, project_parent
|
69
|
-
|
70
|
-
# Add CODE_SIGN_ENTITLEMENTS setting to each configuration
|
71
|
-
target.build_configuration_list.set_setting CODE_SIGN_ENTITLEMENTS, relative_entitlements_path
|
72
|
-
|
73
|
-
# Add the file to the project
|
74
|
-
project.new_file relative_entitlements_path
|
75
|
-
|
76
|
-
entitlements = {}
|
77
|
-
current_domains = []
|
78
|
-
|
79
|
-
add_change project.path
|
80
|
-
new_path = entitlements_path
|
81
|
-
else
|
82
|
-
entitlements_path = File.expand_path relative_entitlements_path, project_parent
|
83
|
-
# Raises
|
84
|
-
entitlements = File.open(entitlements_path) { |f| Plist.parse_xml f }
|
85
|
-
raise "Failed to parse entitlements file #{entitlements_path}" if entitlements.nil?
|
86
|
-
|
87
|
-
if remove_existing
|
88
|
-
current_domains = []
|
89
|
-
else
|
90
|
-
current_domains = entitlements[ASSOCIATED_DOMAINS]
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
current_domains += domains.map { |d| "#{APPLINKS}:#{d}" }
|
95
|
-
all_domains = current_domains.uniq
|
96
|
-
|
97
|
-
entitlements[ASSOCIATED_DOMAINS] = all_domains
|
98
|
-
|
99
|
-
Plist::Emit.save_plist entitlements, entitlements_path
|
100
|
-
add_change entitlements_path
|
101
|
-
|
102
|
-
new_path
|
103
|
-
end
|
104
|
-
|
105
|
-
def team_and_bundle_from_app_id(identifier)
|
106
|
-
team = identifier.sub(/\..+$/, "")
|
107
|
-
bundle = identifier.sub(/^[^.]+\./, "")
|
108
|
-
[team, bundle]
|
109
|
-
end
|
110
|
-
|
111
|
-
def update_team_and_bundle_ids_from_aasa_file(project, target_name, domain)
|
112
|
-
# raises
|
113
|
-
identifiers = app_ids_from_aasa_file domain
|
114
|
-
raise "Multiple appIDs found in AASA file" if identifiers.count > 1
|
115
|
-
|
116
|
-
identifier = identifiers[0]
|
117
|
-
team, bundle = team_and_bundle_from_app_id identifier
|
118
|
-
|
119
|
-
update_team_and_bundle_ids project, target_name, team, bundle
|
120
|
-
add_change project.path.expand_path
|
121
|
-
end
|
122
|
-
|
123
|
-
def validate_team_and_bundle_ids_from_aasa_files(project, target_name, domains = [], remove_existing = false, configuration = RELEASE_CONFIGURATION)
|
124
|
-
@errors = []
|
125
|
-
valid = true
|
126
|
-
|
127
|
-
# Include any domains already in the project.
|
128
|
-
# Raises. Returns a non-nil array of strings.
|
129
|
-
if remove_existing
|
130
|
-
# Don't validate domains to be removed (#16)
|
131
|
-
all_domains = domains
|
132
|
-
else
|
133
|
-
all_domains = (domains + domains_from_project(project, target_name, configuration)).uniq
|
134
|
-
end
|
135
|
-
|
136
|
-
if all_domains.empty?
|
137
|
-
# Cannot get here from SetupBranchAction, since the domains passed in will never be empty.
|
138
|
-
# If called from ValidateUniversalLinksAction, this is a failure, possibly caused by
|
139
|
-
# failure to add applinks:.
|
140
|
-
@errors << "No Universal Link domains in project. Be sure each Universal Link domain is prefixed with applinks:."
|
141
|
-
return false
|
142
|
-
end
|
143
|
-
|
144
|
-
all_domains.each do |domain|
|
145
|
-
domain_valid = validate_team_and_bundle_ids project, target_name, domain, configuration
|
146
|
-
valid &&= domain_valid
|
147
|
-
UI.message "Valid Universal Link configuration for #{domain} ✅" if domain_valid
|
148
|
-
end
|
149
|
-
valid
|
150
|
-
end
|
151
|
-
|
152
|
-
def app_ids_from_aasa_file(domain)
|
153
|
-
data = contents_of_aasa_file domain
|
154
|
-
# errors reported in the method above
|
155
|
-
return nil if data.nil?
|
156
|
-
|
157
|
-
# raises
|
158
|
-
file = JSON.parse data
|
159
|
-
|
160
|
-
applinks = file[APPLINKS]
|
161
|
-
@errors << "[#{domain}] No #{APPLINKS} found in AASA file" and return if applinks.nil?
|
162
|
-
|
163
|
-
details = applinks["details"]
|
164
|
-
@errors << "[#{domain}] No details found for #{APPLINKS} in AASA file" and return if details.nil?
|
165
|
-
|
166
|
-
identifiers = details.map { |d| d["appID"] }.uniq
|
167
|
-
@errors << "[#{domain}] No appID found in AASA file" and return if identifiers.count <= 0
|
168
|
-
identifiers
|
169
|
-
rescue JSON::ParserError => e
|
170
|
-
@errors << "[#{domain}] Failed to parse AASA file: #{e.message}"
|
171
|
-
nil
|
172
|
-
end
|
173
|
-
|
174
|
-
def contents_of_aasa_file(domain)
|
175
|
-
uris = [
|
176
|
-
URI("https://#{domain}/.well-known/apple-app-site-association"),
|
177
|
-
URI("https://#{domain}/apple-app-site-association")
|
178
|
-
# URI("http://#{domain}/.well-known/apple-app-site-association"),
|
179
|
-
# URI("http://#{domain}/apple-app-site-association")
|
180
|
-
]
|
181
|
-
|
182
|
-
data = nil
|
183
|
-
|
184
|
-
uris.each do |uri|
|
185
|
-
break unless data.nil?
|
186
|
-
|
187
|
-
Net::HTTP.start uri.host, uri.port, use_ssl: uri.scheme == "https" do |http|
|
188
|
-
request = Net::HTTP::Get.new uri
|
189
|
-
response = http.request request
|
190
|
-
|
191
|
-
# Better to use Net::HTTPRedirection and Net::HTTPSuccess here, but
|
192
|
-
# having difficulty with the unit tests.
|
193
|
-
if (300..399).cover?(response.code.to_i)
|
194
|
-
UI.important "#{uri} cannot result in a redirect. Ignoring."
|
195
|
-
next
|
196
|
-
elsif response.code.to_i != 200
|
197
|
-
# Try the next URI.
|
198
|
-
UI.message "Could not retrieve #{uri}: #{response.code} #{response.message}. Ignoring."
|
199
|
-
next
|
200
|
-
end
|
201
|
-
|
202
|
-
content_type = response["Content-type"]
|
203
|
-
@errors << "[#{domain}] AASA Response does not contain a Content-type header" and next if content_type.nil?
|
204
|
-
|
205
|
-
case content_type
|
206
|
-
when %r{application/pkcs7-mime}
|
207
|
-
# Verify/decrypt PKCS7 (non-Branch domains)
|
208
|
-
cert_store = OpenSSL::X509::Store.new
|
209
|
-
signature = OpenSSL::PKCS7.new response.body
|
210
|
-
# raises
|
211
|
-
signature.verify nil, cert_store, nil, OpenSSL::PKCS7::NOVERIFY
|
212
|
-
data = signature.data
|
213
|
-
else
|
214
|
-
@error << "[#{domain}] Unsigned AASA files must be served via HTTPS" and next if uri.scheme == "http"
|
215
|
-
data = response.body
|
216
|
-
end
|
217
|
-
|
218
|
-
UI.message "GET #{uri}: #{response.code} #{response.message} (Content-type:#{content_type}) ✅"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
@errors << "[#{domain}] Failed to retrieve AASA file" and return nil if data.nil?
|
223
|
-
|
224
|
-
data
|
225
|
-
rescue IOError, SocketError => e
|
226
|
-
@errors << "[#{domain}] Socket error: #{e.message}"
|
227
|
-
nil
|
228
|
-
rescue OpenSSL::PKCS7::PKCS7Error => e
|
229
|
-
@errors << "[#{domain}] Failed to verify signed AASA file: #{e.message}"
|
230
|
-
nil
|
231
|
-
end
|
232
|
-
|
233
|
-
def validate_team_and_bundle_ids(project, target_name, domain, configuration)
|
234
|
-
# raises
|
235
|
-
target = target_from_project project, target_name
|
236
|
-
|
237
|
-
product_bundle_identifier = expanded_build_setting target, PRODUCT_BUNDLE_IDENTIFIER, configuration
|
238
|
-
development_team = expanded_build_setting target, DEVELOPMENT_TEAM, configuration
|
239
|
-
|
240
|
-
identifiers = app_ids_from_aasa_file domain
|
241
|
-
return false if identifiers.nil?
|
242
|
-
|
243
|
-
app_id = "#{development_team}.#{product_bundle_identifier}"
|
244
|
-
match_found = identifiers.include? app_id
|
245
|
-
|
246
|
-
unless match_found
|
247
|
-
@errors << "[#{domain}] appID mismatch. Project: #{app_id}. AASA: #{identifiers}"
|
248
|
-
end
|
249
|
-
|
250
|
-
match_found
|
251
|
-
end
|
252
|
-
|
253
|
-
def validate_project_domains(expected, project, target, configuration = RELEASE_CONFIGURATION)
|
254
|
-
@errors = []
|
255
|
-
project_domains = domains_from_project project, target, configuration
|
256
|
-
valid = expected.count == project_domains.count
|
257
|
-
if valid
|
258
|
-
sorted = expected.sort
|
259
|
-
project_domains.sort.each_with_index do |domain, index|
|
260
|
-
valid = false and break unless sorted[index] == domain
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
unless valid
|
265
|
-
@errors << "Project domains do not match :domains parameter"
|
266
|
-
@errors << "Project domains: #{project_domains}"
|
267
|
-
@errors << ":domains parameter: #{expected}"
|
268
|
-
end
|
269
|
-
|
270
|
-
valid
|
271
|
-
end
|
272
|
-
|
273
|
-
def update_team_and_bundle_ids(project, target_name, team, bundle)
|
274
|
-
# raises
|
275
|
-
target = target_from_project project, target_name
|
276
|
-
|
277
|
-
target.build_configuration_list.set_setting PRODUCT_BUNDLE_IDENTIFIER, bundle
|
278
|
-
target.build_configuration_list.set_setting DEVELOPMENT_TEAM, team
|
279
|
-
|
280
|
-
# also update the team in the first test target
|
281
|
-
target = project.targets.find(&:test_target_type?)
|
282
|
-
return if target.nil?
|
283
|
-
|
284
|
-
target.build_configuration_list.set_setting DEVELOPMENT_TEAM, team
|
285
|
-
end
|
286
|
-
|
287
|
-
def target_from_project(project, target_name)
|
288
|
-
if target_name
|
289
|
-
target = project.targets.find { |t| t.name == target_name }
|
290
|
-
raise "Target #{target} not found" if target.nil?
|
291
|
-
else
|
292
|
-
# find the first application target
|
293
|
-
target = project.targets.find { |t| !t.extension_target_type? && !t.test_target_type? }
|
294
|
-
raise "No application target found" if target.nil?
|
295
|
-
end
|
296
|
-
target
|
297
|
-
end
|
298
|
-
|
299
|
-
def domains_from_project(project, target_name, configuration = RELEASE_CONFIGURATION)
|
300
|
-
# Raises. Does not return nil.
|
301
|
-
target = target_from_project project, target_name
|
302
|
-
|
303
|
-
relative_entitlements_path = expanded_build_setting target, CODE_SIGN_ENTITLEMENTS, configuration
|
304
|
-
return [] if relative_entitlements_path.nil?
|
305
|
-
|
306
|
-
project_parent = File.dirname project.path
|
307
|
-
entitlements_path = File.expand_path relative_entitlements_path, project_parent
|
308
|
-
|
309
|
-
# Raises
|
310
|
-
entitlements = File.open(entitlements_path) { |f| Plist.parse_xml f }
|
311
|
-
raise "Failed to parse entitlements file #{entitlements_path}" if entitlements.nil?
|
312
|
-
|
313
|
-
entitlements[ASSOCIATED_DOMAINS].select { |d| d =~ /^applinks:/ }.map { |d| d.sub(/^applinks:/, "") }
|
314
|
-
end
|
315
|
-
|
316
|
-
def expanded_build_setting(target, setting_name, configuration)
|
317
|
-
setting_value = target.resolved_build_setting(setting_name)[configuration]
|
318
|
-
return if setting_value.nil?
|
319
|
-
|
320
|
-
search_position = 0
|
321
|
-
while (matches = /\$\(([^(){}]*)\)|\$\{([^(){}]*)\}/.match(setting_value, search_position))
|
322
|
-
macro_name = matches[1] || matches[2]
|
323
|
-
search_position = setting_value.index(macro_name) - 2
|
324
|
-
|
325
|
-
expanded_macro = macro_name == "SRCROOT" ? "." : expanded_build_setting(target, macro_name, configuration)
|
326
|
-
search_position += macro_name.length + 3 and next if expanded_macro.nil?
|
327
|
-
|
328
|
-
setting_value.gsub!(/\$\(#{macro_name}\)|\$\{#{macro_name}\}/, expanded_macro)
|
329
|
-
search_position += expanded_macro.length
|
330
|
-
end
|
331
|
-
setting_value
|
332
|
-
end
|
333
|
-
|
334
|
-
def add_system_frameworks(project, target_name, frameworks)
|
335
|
-
target = target_from_project project, target_name
|
336
|
-
|
337
|
-
target.add_system_framework frameworks
|
338
|
-
end
|
339
|
-
|
340
|
-
def patch_app_delegate_swift(project)
|
341
|
-
app_delegate_swift = project.files.find { |f| f.path =~ /AppDelegate.swift$/ }
|
342
|
-
return false if app_delegate_swift.nil?
|
343
|
-
|
344
|
-
app_delegate_swift_path = app_delegate_swift.real_path.to_s
|
345
|
-
|
346
|
-
app_delegate = File.open(app_delegate_swift_path, &:read)
|
347
|
-
return false if app_delegate =~ /import\s+Branch/
|
348
|
-
|
349
|
-
UI.message "Patching #{app_delegate_swift_path}"
|
350
|
-
|
351
|
-
Actions::PatchAction.run(
|
352
|
-
files: app_delegate_swift_path,
|
353
|
-
regexp: /^\s*import .*$/,
|
354
|
-
text: "\nimport Branch",
|
355
|
-
mode: :prepend,
|
356
|
-
offset: 0
|
357
|
-
)
|
358
|
-
|
359
|
-
# TODO: This is Swift 3. Support other versions, esp. 4.
|
360
|
-
init_session_text = <<-EOF
|
361
|
-
#if DEBUG
|
362
|
-
Branch.setUseTestBranchKey(true)
|
363
|
-
#endif
|
364
|
-
|
365
|
-
Branch.getInstance().initSession(launchOptions: launchOptions) {
|
366
|
-
universalObject, linkProperties, error in
|
367
|
-
|
368
|
-
// TODO: Route Branch links
|
369
|
-
}
|
370
|
-
EOF
|
371
|
-
|
372
|
-
Actions::PatchAction.run(
|
373
|
-
files: app_delegate_swift_path,
|
374
|
-
regexp: /didFinishLaunchingWithOptions.*?\{[^\n]*\n/m,
|
375
|
-
text: init_session_text,
|
376
|
-
mode: :append,
|
377
|
-
offset: 0
|
378
|
-
)
|
379
|
-
|
380
|
-
unless app_delegate =~ /application:.*continueUserActivity:.*restorationHandler:/
|
381
|
-
# Add the application:continueUserActivity:restorationHandler method if it does not exist
|
382
|
-
continue_user_activity_text = <<-EOF
|
383
|
-
|
384
|
-
|
385
|
-
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
|
386
|
-
return Branch.getInstance().continue(userActivity)
|
387
|
-
}
|
388
|
-
EOF
|
389
|
-
|
390
|
-
Actions::PatchAction.run(
|
391
|
-
files: app_delegate_swift_path,
|
392
|
-
regexp: /\n\s*\}[^{}]*\Z/m,
|
393
|
-
text: continue_user_activity_text,
|
394
|
-
mode: :prepend,
|
395
|
-
offset: 0
|
396
|
-
)
|
397
|
-
end
|
398
|
-
|
399
|
-
add_change app_delegate_swift_path
|
400
|
-
true
|
401
|
-
end
|
402
|
-
|
403
|
-
def patch_app_delegate_objc(project)
|
404
|
-
app_delegate_objc = project.files.find { |f| f.path =~ /AppDelegate.m$/ }
|
405
|
-
return false if app_delegate_objc.nil?
|
406
|
-
|
407
|
-
app_delegate_objc_path = app_delegate_objc.real_path.to_s
|
408
|
-
|
409
|
-
app_delegate = File.open(app_delegate_objc_path, &:read)
|
410
|
-
return false if app_delegate =~ %r{^\s+#import\s+<Branch/Branch.h>|^\s+@import\s+Branch;}
|
411
|
-
|
412
|
-
UI.message "Patching #{app_delegate_objc_path}"
|
413
|
-
|
414
|
-
Actions::PatchAction.run(
|
415
|
-
files: app_delegate_objc_path,
|
416
|
-
regexp: /^\s+@import|^\s+#import.*$/,
|
417
|
-
text: "\n#import <Branch/Branch.h>",
|
418
|
-
mode: :prepend,
|
419
|
-
offset: 0
|
420
|
-
)
|
421
|
-
|
422
|
-
init_session_text = <<-EOF
|
423
|
-
#ifdef DEBUG
|
424
|
-
[Branch setUseTestBranchKey:YES];
|
425
|
-
#endif // DEBUG
|
426
|
-
|
427
|
-
[[Branch getInstance] initSessionWithLaunchOptions:launchOptions
|
428
|
-
andRegisterDeepLinkHandlerUsingBranchUniversalObject:^(BranchUniversalObject *universalObject, BranchLinkProperties *linkProperties, NSError *error){
|
429
|
-
// TODO: Route Branch links
|
430
|
-
}];
|
431
|
-
EOF
|
432
|
-
|
433
|
-
Actions::PatchAction.run(
|
434
|
-
files: app_delegate_objc_path,
|
435
|
-
regexp: /didFinishLaunchingWithOptions.*?\{[^\n]*\n/m,
|
436
|
-
text: init_session_text,
|
437
|
-
mode: :append,
|
438
|
-
offset: 0
|
439
|
-
)
|
440
|
-
|
441
|
-
unless app_delegate =~ /application:.*continueUserActivity:.*restorationHandler:/
|
442
|
-
# Add the application:continueUserActivity:restorationHandler method if it does not exist
|
443
|
-
continue_user_activity_text = <<-EOF
|
444
|
-
|
445
|
-
|
446
|
-
- (BOOL)application:(UIApplication *)app continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray * _Nullable))restorationHandler
|
447
|
-
{
|
448
|
-
return [[Branch getInstance] continueUserActivity:userActivity];
|
449
|
-
}
|
450
|
-
EOF
|
451
|
-
|
452
|
-
Actions::PatchAction.run(
|
453
|
-
files: app_delegate_objc_path,
|
454
|
-
regexp: /\n\s*@end[^@]*\Z/m,
|
455
|
-
text: continue_user_activity_text,
|
456
|
-
mode: :prepend,
|
457
|
-
offset: 0
|
458
|
-
)
|
459
|
-
end
|
460
|
-
|
461
|
-
add_change app_delegate_objc_path
|
462
|
-
true
|
463
|
-
end
|
464
|
-
|
465
|
-
def patch_podfile(podfile_path)
|
466
|
-
podfile = File.open(podfile_path, &:read)
|
467
|
-
|
468
|
-
# Podfile already contains the Branch pod
|
469
|
-
return false if podfile =~ /pod\s+('Branch'|"Branch")/
|
470
|
-
|
471
|
-
UI.message "Adding pod \"Branch\" to #{podfile_path}"
|
472
|
-
|
473
|
-
# TODO: Improve this patch. Should work in the majority of cases for now.
|
474
|
-
Actions::PatchAction.run(
|
475
|
-
files: podfile_path,
|
476
|
-
regexp: /^(\s*)pod\s*/,
|
477
|
-
text: "\n\\1pod \"Branch\"\n",
|
478
|
-
mode: :prepend,
|
479
|
-
offset: 0
|
480
|
-
)
|
481
|
-
|
482
|
-
true
|
483
|
-
end
|
484
|
-
|
485
|
-
def patch_cartfile(cartfile_path)
|
486
|
-
cartfile = File.open(cartfile_path, &:read)
|
487
|
-
|
488
|
-
# Cartfile already contains the Branch framework
|
489
|
-
return false if cartfile =~ /git.+Branch/
|
490
|
-
|
491
|
-
UI.message "Adding \"Branch\" to #{cartfile_path}"
|
492
|
-
|
493
|
-
Actions::PatchAction.run(
|
494
|
-
files: cartfile_path,
|
495
|
-
regexp: /\z/,
|
496
|
-
text: "git \"https://github.com/BranchMetrics/ios-branch-deep-linking\"\n",
|
497
|
-
mode: :append,
|
498
|
-
offset: 0
|
499
|
-
)
|
500
|
-
|
501
|
-
true
|
502
|
-
end
|
503
|
-
end
|
504
|
-
end
|
505
|
-
end
|