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