fastlane 2.169.0 → 2.170.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +78 -78
- data/deliver/lib/deliver/upload_metadata.rb +3 -3
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +4 -0
- data/fastlane/lib/fastlane/actions/onesignal.rb +13 -3
- data/fastlane/lib/fastlane/actions/upload_app_privacy_details_to_app_store.rb +289 -0
- data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +1 -1
- data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +3 -0
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +1 -1
- data/fastlane/swift/Fastlane.swift +44 -3
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +1 -1
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
- data/match/lib/match/runner.rb +1 -1
- data/spaceship/lib/spaceship/connect_api.rb +6 -0
- data/{sigh/lib/sigh/.options.rb.swp → spaceship/lib/spaceship/connect_api/models/.app_data_usage_data_protection.rb.swp} +0 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage.rb +59 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_category.rb +65 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_data_protection.rb +27 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_grouping.rb +18 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usage_purposes.rb +37 -0
- data/spaceship/lib/spaceship/connect_api/models/app_data_usages_publish_state.rb +36 -0
- data/spaceship/lib/spaceship/connect_api/models/device.rb +4 -0
- data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +103 -0
- metadata +26 -22
- data/cert/lib/cert/.options.rb.swp +0 -0
- data/cert/lib/cert/.runner.rb.swp +0 -0
- data/match/lib/match/.options.rb.swp +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77bbcab6a1ba00a457477ca27ddaca5f81485448e5022897e9c7ef43ff566991
|
4
|
+
data.tar.gz: 188796684ccb2fed018b9e4ada265a176aa074d09812592ce24d22c69fc48b7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 298f64e50908a6b7e40faa8e160d5e8b064054c2622bcaec9008f0aa861de6022187e0968a4398af07940d0f6383fbbfcb49cdc9136dab6cf6995f497f4d5440
|
7
|
+
data.tar.gz: 98361a556a41a534a68eddad93af5418be1bdcc8ae7b41b69308d109c37582896336ee5ff0e4ce791696320eacd7c21fc757125ef1bf8cfa7e945231ad279b87
|
data/README.md
CHANGED
@@ -34,49 +34,29 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
34
34
|
<!-- This table is regenerated and resorted on each release -->
|
35
35
|
<table id='team'>
|
36
36
|
<tr>
|
37
|
-
<td id='josh-holtz'>
|
38
|
-
<a href='https://github.com/joshdholtz'>
|
39
|
-
<img src='https://github.com/joshdholtz.png?size=140'>
|
40
|
-
</a>
|
41
|
-
<h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
|
42
|
-
</td>
|
43
|
-
<td id='maksym-grebenets'>
|
44
|
-
<a href='https://github.com/mgrebenets'>
|
45
|
-
<img src='https://github.com/mgrebenets.png?size=140'>
|
46
|
-
</a>
|
47
|
-
<h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
|
48
|
-
</td>
|
49
|
-
<td id='matthew-ellis'>
|
50
|
-
<a href='https://github.com/matthewellis'>
|
51
|
-
<img src='https://github.com/matthewellis.png?size=140'>
|
52
|
-
</a>
|
53
|
-
<h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
|
54
|
-
</td>
|
55
37
|
<td id='aaron-brager'>
|
56
38
|
<a href='https://github.com/getaaron'>
|
57
39
|
<img src='https://github.com/getaaron.png?size=140'>
|
58
40
|
</a>
|
59
41
|
<h4 align='center'><a href='https://twitter.com/getaaron'>Aaron Brager</a></h4>
|
60
42
|
</td>
|
61
|
-
<td id='
|
62
|
-
<a href='https://github.com/
|
63
|
-
<img src='https://github.com/
|
43
|
+
<td id='helmut-januschka'>
|
44
|
+
<a href='https://github.com/hjanuschka'>
|
45
|
+
<img src='https://github.com/hjanuschka.png?size=140'>
|
64
46
|
</a>
|
65
|
-
<h4 align='center'><a href='https://twitter.com/
|
47
|
+
<h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
|
66
48
|
</td>
|
67
|
-
|
68
|
-
<
|
69
|
-
<
|
70
|
-
<a href='https://github.com/janpio'>
|
71
|
-
<img src='https://github.com/janpio.png?size=140'>
|
49
|
+
<td id='maksym-grebenets'>
|
50
|
+
<a href='https://github.com/mgrebenets'>
|
51
|
+
<img src='https://github.com/mgrebenets.png?size=140'>
|
72
52
|
</a>
|
73
|
-
<h4 align='center'><a href='https://twitter.com/
|
53
|
+
<h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
|
74
54
|
</td>
|
75
|
-
<td id='
|
76
|
-
<a href='https://github.com/
|
77
|
-
<img src='https://github.com/
|
55
|
+
<td id='danielle-tomlinson'>
|
56
|
+
<a href='https://github.com/endocrimes'>
|
57
|
+
<img src='https://github.com/endocrimes.png?size=140'>
|
78
58
|
</a>
|
79
|
-
<h4 align='center'><a href='https://twitter.com/
|
59
|
+
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
80
60
|
</td>
|
81
61
|
<td id='max-ott'>
|
82
62
|
<a href='https://github.com/max-ott'>
|
@@ -84,11 +64,25 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
84
64
|
</a>
|
85
65
|
<h4 align='center'><a href='https://twitter.com/ott_max'>Max Ott</a></h4>
|
86
66
|
</td>
|
87
|
-
|
88
|
-
<
|
89
|
-
<
|
67
|
+
</tr>
|
68
|
+
<tr>
|
69
|
+
<td id='andrew-mcburney'>
|
70
|
+
<a href='https://github.com/armcburney'>
|
71
|
+
<img src='https://github.com/armcburney.png?size=140'>
|
90
72
|
</a>
|
91
|
-
<h4 align='center'><a href='https://twitter.com/
|
73
|
+
<h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
|
74
|
+
</td>
|
75
|
+
<td id='jérôme-lacoste'>
|
76
|
+
<a href='https://github.com/lacostej'>
|
77
|
+
<img src='https://github.com/lacostej.png?size=140'>
|
78
|
+
</a>
|
79
|
+
<h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
|
80
|
+
</td>
|
81
|
+
<td id='joshua-liebowitz'>
|
82
|
+
<a href='https://github.com/taquitos'>
|
83
|
+
<img src='https://github.com/taquitos.png?size=140'>
|
84
|
+
</a>
|
85
|
+
<h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
|
92
86
|
</td>
|
93
87
|
<td id='fumiya-nakamura'>
|
94
88
|
<a href='https://github.com/nafu'>
|
@@ -96,14 +90,46 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
96
90
|
</a>
|
97
91
|
<h4 align='center'><a href='https://twitter.com/nafu003'>Fumiya Nakamura</a></h4>
|
98
92
|
</td>
|
93
|
+
<td id='matthew-ellis'>
|
94
|
+
<a href='https://github.com/matthewellis'>
|
95
|
+
<img src='https://github.com/matthewellis.png?size=140'>
|
96
|
+
</a>
|
97
|
+
<h4 align='center'><a href='https://twitter.com/mellis1995'>Matthew Ellis</a></h4>
|
98
|
+
</td>
|
99
99
|
</tr>
|
100
100
|
<tr>
|
101
|
+
<td id='stefan-natchev'>
|
102
|
+
<a href='https://github.com/snatchev'>
|
103
|
+
<img src='https://github.com/snatchev.png?size=140'>
|
104
|
+
</a>
|
105
|
+
<h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
|
106
|
+
</td>
|
107
|
+
<td id='luka-mirosevic'>
|
108
|
+
<a href='https://github.com/lmirosevic'>
|
109
|
+
<img src='https://github.com/lmirosevic.png?size=140'>
|
110
|
+
</a>
|
111
|
+
<h4 align='center'><a href='https://twitter.com/lmirosevic'>Luka Mirosevic</a></h4>
|
112
|
+
</td>
|
101
113
|
<td id='felix-krause'>
|
102
114
|
<a href='https://github.com/KrauseFx'>
|
103
115
|
<img src='https://github.com/KrauseFx.png?size=140'>
|
104
116
|
</a>
|
105
117
|
<h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
|
106
118
|
</td>
|
119
|
+
<td id='iulian-onofrei'>
|
120
|
+
<a href='https://github.com/revolter'>
|
121
|
+
<img src='https://github.com/revolter.png?size=140'>
|
122
|
+
</a>
|
123
|
+
<h4 align='center'><a href='https://twitter.com/Revolt666'>Iulian Onofrei</a></h4>
|
124
|
+
</td>
|
125
|
+
<td id='olivier-halligon'>
|
126
|
+
<a href='https://github.com/AliSoftware'>
|
127
|
+
<img src='https://github.com/AliSoftware.png?size=140'>
|
128
|
+
</a>
|
129
|
+
<h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
|
130
|
+
</td>
|
131
|
+
</tr>
|
132
|
+
<tr>
|
107
133
|
<td id='daniel-jankowski'>
|
108
134
|
<a href='https://github.com/mollyIV'>
|
109
135
|
<img src='https://github.com/mollyIV.png?size=140'>
|
@@ -116,43 +142,17 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
116
142
|
</a>
|
117
143
|
<h4 align='center'><a href='https://twitter.com/acrooow'>Manu Wallner</a></h4>
|
118
144
|
</td>
|
119
|
-
<td id='
|
120
|
-
<a href='https://github.com/
|
121
|
-
<img src='https://github.com/
|
122
|
-
</a>
|
123
|
-
<h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
|
124
|
-
</td>
|
125
|
-
<td id='danielle-tomlinson'>
|
126
|
-
<a href='https://github.com/endocrimes'>
|
127
|
-
<img src='https://github.com/endocrimes.png?size=140'>
|
128
|
-
</a>
|
129
|
-
<h4 align='center'><a href='https://twitter.com/endocrimes'>Danielle Tomlinson</a></h4>
|
130
|
-
</td>
|
131
|
-
</tr>
|
132
|
-
<tr>
|
133
|
-
<td id='andrew-mcburney'>
|
134
|
-
<a href='https://github.com/armcburney'>
|
135
|
-
<img src='https://github.com/armcburney.png?size=140'>
|
136
|
-
</a>
|
137
|
-
<h4 align='center'><a href='https://twitter.com/armcburney'>Andrew McBurney</a></h4>
|
138
|
-
</td>
|
139
|
-
<td id='jérôme-lacoste'>
|
140
|
-
<a href='https://github.com/lacostej'>
|
141
|
-
<img src='https://github.com/lacostej.png?size=140'>
|
142
|
-
</a>
|
143
|
-
<h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
|
144
|
-
</td>
|
145
|
-
<td id='stefan-natchev'>
|
146
|
-
<a href='https://github.com/snatchev'>
|
147
|
-
<img src='https://github.com/snatchev.png?size=140'>
|
145
|
+
<td id='jorge-revuelta-h'>
|
146
|
+
<a href='https://github.com/minuscorp'>
|
147
|
+
<img src='https://github.com/minuscorp.png?size=140'>
|
148
148
|
</a>
|
149
|
-
<h4 align='center'><a href='https://twitter.com/
|
149
|
+
<h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
|
150
150
|
</td>
|
151
|
-
<td id='
|
152
|
-
<a href='https://github.com/
|
153
|
-
<img src='https://github.com/
|
151
|
+
<td id='kohki-miki'>
|
152
|
+
<a href='https://github.com/giginet'>
|
153
|
+
<img src='https://github.com/giginet.png?size=140'>
|
154
154
|
</a>
|
155
|
-
<h4 align='center'><a href='https://twitter.com/
|
155
|
+
<h4 align='center'><a href='https://twitter.com/giginet'>Kohki Miki</a></h4>
|
156
156
|
</td>
|
157
157
|
<td id='jimmy-dee'>
|
158
158
|
<a href='https://github.com/jdee'>
|
@@ -162,17 +162,17 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
|
|
162
162
|
</td>
|
163
163
|
</tr>
|
164
164
|
<tr>
|
165
|
-
<td id='
|
166
|
-
<a href='https://github.com/
|
167
|
-
<img src='https://github.com/
|
165
|
+
<td id='jan-piotrowski'>
|
166
|
+
<a href='https://github.com/janpio'>
|
167
|
+
<img src='https://github.com/janpio.png?size=140'>
|
168
168
|
</a>
|
169
|
-
<h4 align='center'><a href='https://twitter.com/
|
169
|
+
<h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
|
170
170
|
</td>
|
171
|
-
<td id='
|
172
|
-
<a href='https://github.com/
|
173
|
-
<img src='https://github.com/
|
171
|
+
<td id='josh-holtz'>
|
172
|
+
<a href='https://github.com/joshdholtz'>
|
173
|
+
<img src='https://github.com/joshdholtz.png?size=140'>
|
174
174
|
</a>
|
175
|
-
<h4 align='center'><a href='https://twitter.com/
|
175
|
+
<h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
|
176
176
|
</td>
|
177
177
|
</table>
|
178
178
|
|
@@ -375,7 +375,7 @@ module Deliver
|
|
375
375
|
# Check folder list (an empty folder signifies a language is required)
|
376
376
|
ignore_validation = options[:ignore_language_directory_validation]
|
377
377
|
Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
|
378
|
-
enabled_languages << lang_folder.
|
378
|
+
enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
|
379
379
|
end
|
380
380
|
|
381
381
|
return unless enabled_languages.include?("default")
|
@@ -414,7 +414,7 @@ module Deliver
|
|
414
414
|
# Check folder list (an empty folder signifies a language is required)
|
415
415
|
ignore_validation = options[:ignore_language_directory_validation]
|
416
416
|
Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
|
417
|
-
enabled_languages << lang_folder.
|
417
|
+
enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
|
418
418
|
end
|
419
419
|
|
420
420
|
# Mapping to strings because :default symbol can be passed in
|
@@ -531,7 +531,7 @@ module Deliver
|
|
531
531
|
|
532
532
|
UI.message("Loading '#{path}'...")
|
533
533
|
options[key] ||= {}
|
534
|
-
options[key][lang_folder.
|
534
|
+
options[key][lang_folder.basename] ||= File.read(path)
|
535
535
|
end
|
536
536
|
end
|
537
537
|
|
@@ -390,7 +390,7 @@ lane :beta do
|
|
390
390
|
end
|
391
391
|
```
|
392
392
|
|
393
|
-
By using the `force_for_new_devices` parameter, _match_ will check if the device count has changed since the last time you ran _match_, and automatically re-generate the provisioning profile if necessary. You can also use `force: true` to re-generate the provisioning profile on each run.
|
393
|
+
By using the `force_for_new_devices` parameter, _match_ will check if the (enabled) device count has changed since the last time you ran _match_, and automatically re-generate the provisioning profile if necessary. You can also use `force: true` to re-generate the provisioning profile on each run.
|
394
394
|
|
395
395
|
_**Important:** The `force_for_new_devices` parameter is ignored for App Store provisioning profiles since they don't contain any device information._
|
396
396
|
|
@@ -439,6 +439,10 @@ Use the `submission_information` parameter for additional submission specifiers,
|
|
439
439
|
fastlane deliver submit_build --build_number 830 --submission_information "{\"export_compliance_uses_encryption\": false, \"add_id_info_uses_idfa\": false }"
|
440
440
|
```
|
441
441
|
|
442
|
+
### App Privacy Details
|
443
|
+
|
444
|
+
Starting on December 8, 2020, Apple announced that developers are required to provide app privacy details that will help users understand an app's privacy practies. _deliver_ does not allow for updating of this information but this can be done with the _upload_app_privacy_details_to_app_store_ action. More information on [Uploading App Privacy Details](https://docs.fastlane.tools/uploading-app-privacy-details)
|
445
|
+
|
442
446
|
# Credentials
|
443
447
|
|
444
448
|
A detailed description about how your credentials are handled is available in a [credentials_manager](https://github.com/fastlane/fastlane/tree/master/credentials_manager).
|
@@ -17,6 +17,7 @@ module Fastlane
|
|
17
17
|
apns_p12_password = params[:apns_p12_password]
|
18
18
|
android_token = params[:android_token]
|
19
19
|
android_gcm_sender_id = params[:android_gcm_sender_id]
|
20
|
+
organization_id = params[:organization_id]
|
20
21
|
|
21
22
|
has_app_id = !app_id.empty?
|
22
23
|
has_app_name = !app_name.empty?
|
@@ -43,6 +44,7 @@ module Fastlane
|
|
43
44
|
|
44
45
|
payload["gcm_key"] = android_token unless android_token.nil?
|
45
46
|
payload["android_gcm_sender_id"] = android_gcm_sender_id unless android_gcm_sender_id.nil?
|
47
|
+
payload["organization_id"] = organization_id unless organization_id.nil?
|
46
48
|
|
47
49
|
# here's the actual lifting - POST or PUT to OneSignal
|
48
50
|
|
@@ -135,7 +137,13 @@ module Fastlane
|
|
135
137
|
env_name: "APNS_ENV",
|
136
138
|
description: "APNS environment",
|
137
139
|
optional: true,
|
138
|
-
default_value: 'production')
|
140
|
+
default_value: 'production'),
|
141
|
+
|
142
|
+
FastlaneCore::ConfigItem.new(key: :organization_id,
|
143
|
+
env_name: "ONE_SIGNAL_ORGANIZATION_ID",
|
144
|
+
sensitive: true,
|
145
|
+
description: "OneSignal Organization ID",
|
146
|
+
optional: true)
|
139
147
|
]
|
140
148
|
end
|
141
149
|
|
@@ -163,7 +171,8 @@ module Fastlane
|
|
163
171
|
android_gcm_sender_id: "Your Android GCM Sender ID (optional)",
|
164
172
|
apns_p12: "Path to Apple .p12 file (optional)",
|
165
173
|
apns_p12_password: "Password for .p12 file (optional)",
|
166
|
-
apns_env: "production/sandbox (defaults to production)"
|
174
|
+
apns_env: "production/sandbox (defaults to production)",
|
175
|
+
organization_id: "Onesignal organization id (optional)"
|
167
176
|
)',
|
168
177
|
'onesignal(
|
169
178
|
app_id: "Your OneSignal App ID",
|
@@ -173,7 +182,8 @@ module Fastlane
|
|
173
182
|
android_gcm_sender_id: "Your Android GCM Sender ID (optional)",
|
174
183
|
apns_p12: "Path to Apple .p12 file (optional)",
|
175
184
|
apns_p12_password: "Password for .p12 file (optional)",
|
176
|
-
apns_env: "production/sandbox (defaults to production)"
|
185
|
+
apns_env: "production/sandbox (defaults to production)",
|
186
|
+
organization_id: "Onesignal organization id (optional)"
|
177
187
|
)'
|
178
188
|
]
|
179
189
|
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class UploadAppPrivacyDetailsToAppStoreAction < Action
|
4
|
+
DEFAULT_PATH = Fastlane::Helper.fastlane_enabled_folder_path
|
5
|
+
DEFAULT_FILE_NAME = "app_privacy_details.json"
|
6
|
+
|
7
|
+
def self.run(params)
|
8
|
+
require 'spaceship'
|
9
|
+
|
10
|
+
# Prompts select team if multiple teams and none specified
|
11
|
+
UI.message("Login to App Store Connect (#{params[:username]})")
|
12
|
+
Spaceship::ConnectAPI.login(params[:username], use_portal: false, use_tunes: true, tunes_team_id: params[:team_id], team_name: params[:team_name])
|
13
|
+
UI.message("Login successful")
|
14
|
+
|
15
|
+
# Get App
|
16
|
+
app = Spaceship::ConnectAPI::App.find(params[:app_identifier])
|
17
|
+
unless app
|
18
|
+
UI.user_error!("Could not find app with bundle identifier '#{params[:app_identifier]}' on account #{params[:username]}")
|
19
|
+
end
|
20
|
+
|
21
|
+
# Attempt to load JSON file
|
22
|
+
usages_config = load_json_file(params)
|
23
|
+
|
24
|
+
# Start interactive questions to generate and save JSON file
|
25
|
+
unless usages_config
|
26
|
+
usages_config = ask_interactive_questions_for_json
|
27
|
+
|
28
|
+
if params[:skip_json_file_saving]
|
29
|
+
UI.message("Skipping JSON file saving...")
|
30
|
+
else
|
31
|
+
json = JSON.pretty_generate(usages_config)
|
32
|
+
path = output_path(params)
|
33
|
+
|
34
|
+
UI.message("Writing file to #{path}")
|
35
|
+
File.write(path, json)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Process JSON file to save app data usages to API
|
40
|
+
if params[:skip_upload]
|
41
|
+
UI.message("Skipping uploading of data... (so you can verify your JSON file)")
|
42
|
+
else
|
43
|
+
upload_app_data_usages(params, app, usages_config)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.load_json_file(params)
|
48
|
+
path = params[:json_path]
|
49
|
+
return nil if path.nil?
|
50
|
+
return JSON.parse(File.read(path))
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.output_path(params)
|
54
|
+
path = params[:output_json_path]
|
55
|
+
return File.absolute_path(path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.ask_interactive_questions_for_json(show_intro = true)
|
59
|
+
if show_intro
|
60
|
+
UI.important("You did not provide a JSON file for updating the app data usages")
|
61
|
+
UI.important("fastlane will now run you through interactive question to generate the JSON file")
|
62
|
+
UI.important("")
|
63
|
+
UI.important("This JSON file can be saved in source control and used in this action with the :json_file option")
|
64
|
+
|
65
|
+
unless UI.confirm("Ready to start?")
|
66
|
+
UI.user_error!("Cancelled")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Fetch categories and purposes used for generating interactive questions
|
71
|
+
categories = Spaceship::ConnectAPI::AppDataUsageCategory.all(includes: "grouping")
|
72
|
+
purposes = Spaceship::ConnectAPI::AppDataUsagePurpose.all
|
73
|
+
|
74
|
+
json = []
|
75
|
+
|
76
|
+
unless UI.confirm("Are you collecting data?")
|
77
|
+
json << {
|
78
|
+
"data_protections" => [Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED]
|
79
|
+
}
|
80
|
+
|
81
|
+
return json
|
82
|
+
end
|
83
|
+
|
84
|
+
categories.each do |category|
|
85
|
+
# Ask if using category
|
86
|
+
next unless UI.confirm("Collect data for #{category.id}?")
|
87
|
+
|
88
|
+
purpose_names = purposes.map(&:id).join(', ')
|
89
|
+
UI.message("How will this data be used? You'll be offered with #{purpose_names}")
|
90
|
+
|
91
|
+
# Ask purposes
|
92
|
+
selected_purposes = []
|
93
|
+
loop do
|
94
|
+
purposes.each do |purpose|
|
95
|
+
selected_purposes << purpose if UI.confirm("Used for #{purpose.id}?")
|
96
|
+
end
|
97
|
+
|
98
|
+
break unless selected_purposes.empty?
|
99
|
+
break unless UI.confirm("No purposes selected. Do you want to try again?")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Skip asking protections if purposes were skipped
|
103
|
+
next if selected_purposes.empty?
|
104
|
+
|
105
|
+
# Ask protections
|
106
|
+
is_linked_to_user = UI.confirm("Is #{category.id} linked to the user?")
|
107
|
+
is_used_for_tracking = UI.confirm("Is #{category.id} used for tracking purposes?")
|
108
|
+
|
109
|
+
# Map answers to values for API requests
|
110
|
+
protection_id = is_linked_to_user ? Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_LINKED_TO_YOU : Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_LINKED_TO_YOU
|
111
|
+
tracking_id = is_used_for_tracking ? Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_USED_TO_TRACK_YOU : nil
|
112
|
+
|
113
|
+
json << {
|
114
|
+
"category" => category.id,
|
115
|
+
"purposes" => selected_purposes.map(&:id),
|
116
|
+
"data_protections" => [
|
117
|
+
protection_id, tracking_id
|
118
|
+
].compact
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
# Recursively call this method if no categories were selected for data collection
|
123
|
+
if json.empty?
|
124
|
+
UI.error("No categories were selected for data collection.")
|
125
|
+
json = ask_interactive_questions_for_json(false)
|
126
|
+
end
|
127
|
+
|
128
|
+
return json
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.upload_app_data_usages(params, app, usages_config)
|
132
|
+
UI.message("Preparing to upload App Data Usage")
|
133
|
+
|
134
|
+
# Delete all existing usages for new ones
|
135
|
+
all_usages = Spaceship::ConnectAPI::AppDataUsage.all(app_id: app.id, includes: "category,grouping,purpose,dataProtection", limit: 500)
|
136
|
+
all_usages.each(&:delete!)
|
137
|
+
|
138
|
+
usages_config.each do |usage_config|
|
139
|
+
category = usage_config["category"]
|
140
|
+
purposes = usage_config["purposes"] || []
|
141
|
+
data_protections = usage_config["data_protections"] || []
|
142
|
+
|
143
|
+
# There will not be any purposes if "not collecting data"
|
144
|
+
# However, an AppDataUsage still needs to be created for not collecting data
|
145
|
+
# Creating an array with nil so that purposes can be iterated over and
|
146
|
+
# that AppDataUsage can be created
|
147
|
+
purposes = [nil] if purposes.empty?
|
148
|
+
|
149
|
+
purposes.each do |purpose|
|
150
|
+
data_protections.each do |data_protection|
|
151
|
+
if data_protection == Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED
|
152
|
+
UI.message("Setting #{data_protection}")
|
153
|
+
else
|
154
|
+
UI.message("Setting #{category} and #{purpose} to #{data_protection}")
|
155
|
+
end
|
156
|
+
|
157
|
+
Spaceship::ConnectAPI::AppDataUsage.create(
|
158
|
+
app_id: app.id,
|
159
|
+
app_data_usage_category_id: category,
|
160
|
+
app_data_usage_protection_id: data_protection,
|
161
|
+
app_data_usage_purpose_id: purpose
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Publish
|
168
|
+
if params[:skip_publish]
|
169
|
+
UI.message("Skipping app data usage publishing... (so you can verify on App Store Connect)")
|
170
|
+
else
|
171
|
+
publish_state = Spaceship::ConnectAPI::AppDataUsagesPublishState.get(app_id: app.id)
|
172
|
+
if publish_state.published
|
173
|
+
UI.important("App data usage is already published")
|
174
|
+
else
|
175
|
+
UI.important("App data usage not published! Going to publish...")
|
176
|
+
publish_state.publish!
|
177
|
+
UI.important("App data usage is now published")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.description
|
183
|
+
"Upload App Privacy Details for an app in App Store Connect"
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.available_options
|
187
|
+
user = CredentialsManager::AppfileConfig.try_fetch_value(:itunes_connect_id)
|
188
|
+
user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
189
|
+
|
190
|
+
[
|
191
|
+
FastlaneCore::ConfigItem.new(key: :username,
|
192
|
+
env_name: "FASTLANE_USER",
|
193
|
+
description: "Your Apple ID Username for App Store Connect",
|
194
|
+
default_value: user,
|
195
|
+
default_value_dynamic: true),
|
196
|
+
FastlaneCore::ConfigItem.new(key: :app_identifier,
|
197
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_APP_IDENTIFIER",
|
198
|
+
description: "The bundle identifier of your app",
|
199
|
+
code_gen_sensitive: true,
|
200
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier),
|
201
|
+
default_value_dynamic: true),
|
202
|
+
FastlaneCore::ConfigItem.new(key: :team_id,
|
203
|
+
env_name: "FASTLANE_ITC_TEAM_ID",
|
204
|
+
description: "The ID of your App Store Connect team if you're in multiple teams",
|
205
|
+
optional: true,
|
206
|
+
is_string: false, # as we also allow integers, which we convert to strings anyway
|
207
|
+
code_gen_sensitive: true,
|
208
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_id),
|
209
|
+
default_value_dynamic: true),
|
210
|
+
FastlaneCore::ConfigItem.new(key: :team_name,
|
211
|
+
env_name: "FASTLANE_ITC_TEAM_NAME",
|
212
|
+
description: "The name of your App Store Connect team if you're in multiple teams",
|
213
|
+
optional: true,
|
214
|
+
code_gen_sensitive: true,
|
215
|
+
default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_name),
|
216
|
+
default_value_dynamic: true),
|
217
|
+
|
218
|
+
# JSON paths
|
219
|
+
FastlaneCore::ConfigItem.new(key: :json_path,
|
220
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_JSON_PATH",
|
221
|
+
description: "Path to the app usage data JSON",
|
222
|
+
is_string: true,
|
223
|
+
optional: true,
|
224
|
+
verify_block: proc do |value|
|
225
|
+
UI.user_error!("Could not find JSON file at path '#{File.expand_path(value)}'") unless File.exist?(value)
|
226
|
+
UI.user_error!("'#{value}' doesn't seem to be a JSON file") unless FastlaneCore::Helper.json_file?(File.expand_path(value))
|
227
|
+
end),
|
228
|
+
FastlaneCore::ConfigItem.new(key: :output_json_path,
|
229
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_JSON_PATH",
|
230
|
+
description: "Path to the app usage data JSON file generated by interactive questions",
|
231
|
+
conflicting_options: [:skip_json_file_saving],
|
232
|
+
default_value: File.join(DEFAULT_PATH, DEFAULT_FILE_NAME)),
|
233
|
+
|
234
|
+
# Skipping options
|
235
|
+
FastlaneCore::ConfigItem.new(key: :skip_json_file_saving,
|
236
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_JSON_FILE_SAVING",
|
237
|
+
description: "Whether to skip the saving of the JSON file",
|
238
|
+
conflicting_options: [:skip_output_json_path],
|
239
|
+
type: Boolean,
|
240
|
+
default_value: false),
|
241
|
+
FastlaneCore::ConfigItem.new(key: :skip_upload,
|
242
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_UPLOAD",
|
243
|
+
description: "Whether to skip the upload and only create the JSON file with interactive questions",
|
244
|
+
conflicting_options: [:skip_publish],
|
245
|
+
type: Boolean,
|
246
|
+
default_value: false),
|
247
|
+
FastlaneCore::ConfigItem.new(key: :skip_publish,
|
248
|
+
env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_SKIP_PUBLISH",
|
249
|
+
description: "Whether to skip the publishing",
|
250
|
+
conflicting_options: [:skip_upload],
|
251
|
+
type: Boolean,
|
252
|
+
default_value: false)
|
253
|
+
]
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.author
|
257
|
+
"joshdholtz"
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.is_supported?(platform)
|
261
|
+
[:ios, :mac, :tvos].include?(platform)
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.details
|
265
|
+
"Upload App Privacy Details for an app in App Store Connect. For more detail information, view https://docs.fastlane.tools/uploading-app-privacy-details"
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.example_code
|
269
|
+
[
|
270
|
+
'upload_app_privacy_details_to_app_store(
|
271
|
+
username: "your@email.com",
|
272
|
+
team_name: "Your Team",
|
273
|
+
app_identifier: "com.your.bundle"
|
274
|
+
)',
|
275
|
+
'upload_app_privacy_details_to_app_store(
|
276
|
+
username: "your@email.com",
|
277
|
+
team_name: "Your Team",
|
278
|
+
app_identifier: "com.your.bundle",
|
279
|
+
json_path: "fastlane/app_data_usages.json"
|
280
|
+
)'
|
281
|
+
]
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.category
|
285
|
+
:production
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|