fastlane-plugin-branch 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|