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 +7 -0
- data/LICENSE +21 -0
- data/README.md +165 -0
- data/lib/fastlane/plugin/branch.rb +16 -0
- data/lib/fastlane/plugin/branch/actions/setup_branch_action.rb +200 -0
- data/lib/fastlane/plugin/branch/actions/validate_universal_links_action.rb +121 -0
- data/lib/fastlane/plugin/branch/helper/android_helper.rb +86 -0
- data/lib/fastlane/plugin/branch/helper/branch_helper.rb +25 -0
- data/lib/fastlane/plugin/branch/helper/configuration_helper.rb +86 -0
- data/lib/fastlane/plugin/branch/helper/ios_helper.rb +331 -0
- data/lib/fastlane/plugin/branch/version.rb +5 -0
- metadata +166 -0
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
|
+
[](https://rubygems.org/gems/fastlane-plugin-branch)
|
4
|
+
[](https://rubygems.org/gems/fastlane-plugin-branch)
|
5
|
+
[](https://rubygems.org/gems/fastlane-plugin-branch)
|
6
|
+
[](https://github.com/jdee/settings-bundle/blob/master/LICENSE)
|
7
|
+
[](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
|
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:
|