fastlane 2.169.0 → 2.170.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -78
  3. data/deliver/lib/deliver/upload_metadata.rb +3 -3
  4. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
  5. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +4 -0
  6. data/fastlane/lib/fastlane/actions/onesignal.rb +13 -3
  7. data/fastlane/lib/fastlane/actions/upload_app_privacy_details_to_app_store.rb +289 -0
  8. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +1 -1
  9. data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +3 -0
  10. data/fastlane/lib/fastlane/version.rb +1 -1
  11. data/fastlane/swift/Deliverfile.swift +1 -1
  12. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  13. data/fastlane/swift/Fastlane.swift +44 -3
  14. data/fastlane/swift/Gymfile.swift +1 -1
  15. data/fastlane/swift/GymfileProtocol.swift +1 -1
  16. data/fastlane/swift/Matchfile.swift +1 -1
  17. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  18. data/fastlane/swift/Precheckfile.swift +1 -1
  19. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  20. data/fastlane/swift/Scanfile.swift +1 -1
  21. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  22. data/fastlane/swift/Screengrabfile.swift +1 -1
  23. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  24. data/fastlane/swift/Snapshotfile.swift +1 -1
  25. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  26. data/match/lib/match/runner.rb +1 -1
  27. data/spaceship/lib/spaceship/connect_api.rb +6 -0
  28. data/{sigh/lib/sigh/.options.rb.swp → spaceship/lib/spaceship/connect_api/models/.app_data_usage_data_protection.rb.swp} +0 -0
  29. data/spaceship/lib/spaceship/connect_api/models/app_data_usage.rb +59 -0
  30. data/spaceship/lib/spaceship/connect_api/models/app_data_usage_category.rb +65 -0
  31. data/spaceship/lib/spaceship/connect_api/models/app_data_usage_data_protection.rb +27 -0
  32. data/spaceship/lib/spaceship/connect_api/models/app_data_usage_grouping.rb +18 -0
  33. data/spaceship/lib/spaceship/connect_api/models/app_data_usage_purposes.rb +37 -0
  34. data/spaceship/lib/spaceship/connect_api/models/app_data_usages_publish_state.rb +36 -0
  35. data/spaceship/lib/spaceship/connect_api/models/device.rb +4 -0
  36. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +103 -0
  37. metadata +26 -22
  38. data/cert/lib/cert/.options.rb.swp +0 -0
  39. data/cert/lib/cert/.runner.rb.swp +0 -0
  40. data/match/lib/match/.options.rb.swp +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89942043b1528ca3c3eff8b253e5c13a8fe910178b1670a553197f27260bb7d5
4
- data.tar.gz: 476dffe7ef2b50121441191673e20041e4c91cdb8eecc5e1928a9f02930538b0
3
+ metadata.gz: 77bbcab6a1ba00a457477ca27ddaca5f81485448e5022897e9c7ef43ff566991
4
+ data.tar.gz: 188796684ccb2fed018b9e4ada265a176aa074d09812592ce24d22c69fc48b7b
5
5
  SHA512:
6
- metadata.gz: 26f4595813c6d3bdea582beabb32f4824b170dc660cf576d01aa1e6c696781e7538c6740bf09246f492ed06ac05a15db5eb0de88e21ccc07ed00e24b373c9380
7
- data.tar.gz: e711d070232cecbcb7749c47c0fa6ac72e19b137753856c39de6739a55fd6159d4943575f784eb06070e12ba2d02ba5aea478481b48be8b9561a360e0756fa27
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='jorge-revuelta-h'>
62
- <a href='https://github.com/minuscorp'>
63
- <img src='https://github.com/minuscorp.png?size=140'>
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/minuscorp'>Jorge Revuelta H</a></h4>
47
+ <h4 align='center'><a href='https://twitter.com/hjanuschka'>Helmut Januschka</a></h4>
66
48
  </td>
67
- </tr>
68
- <tr>
69
- <td id='jan-piotrowski'>
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/Sujan'>Jan Piotrowski</a></h4>
53
+ <h4 align='center'><a href='https://twitter.com/mgrebenets'>Maksym Grebenets</a></h4>
74
54
  </td>
75
- <td id='kohki-miki'>
76
- <a href='https://github.com/giginet'>
77
- <img src='https://github.com/giginet.png?size=140'>
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/giginet'>Kohki Miki</a></h4>
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
- <td id='iulian-onofrei'>
88
- <a href='https://github.com/revolter'>
89
- <img src='https://github.com/revolter.png?size=140'>
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/Revolt666'>Iulian Onofrei</a></h4>
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='helmut-januschka'>
120
- <a href='https://github.com/hjanuschka'>
121
- <img src='https://github.com/hjanuschka.png?size=140'>
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/snatchev'>Stefan Natchev</a></h4>
149
+ <h4 align='center'><a href='https://twitter.com/minuscorp'>Jorge Revuelta H</a></h4>
150
150
  </td>
151
- <td id='joshua-liebowitz'>
152
- <a href='https://github.com/taquitos'>
153
- <img src='https://github.com/taquitos.png?size=140'>
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/taquitos'>Joshua Liebowitz</a></h4>
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='olivier-halligon'>
166
- <a href='https://github.com/AliSoftware'>
167
- <img src='https://github.com/AliSoftware.png?size=140'>
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/aligatr'>Olivier Halligon</a></h4>
169
+ <h4 align='center'><a href='https://twitter.com/Sujan'>Jan Piotrowski</a></h4>
170
170
  </td>
171
- <td id='luka-mirosevic'>
172
- <a href='https://github.com/lmirosevic'>
173
- <img src='https://github.com/lmirosevic.png?size=140'>
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/lmirosevic'>Luka Mirosevic</a></h4>
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.language unless enabled_languages.include?(lang_folder.language)
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.language unless enabled_languages.include?(lang_folder.language)
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.language] ||= File.read(path)
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