fastlane 2.169.0 → 2.170.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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