fastlane 2.240.1 → 2.241.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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -177
  3. data/bin/fastlane +4 -2
  4. data/credentials_manager/README.md +1 -1
  5. data/deliver/lib/deliver/app_screenshot.rb +0 -4
  6. data/deliver/lib/deliver/commands_generator.rb +1 -1
  7. data/deliver/lib/deliver/download_screenshots.rb +8 -1
  8. data/deliver/lib/deliver/sync_screenshots.rb +27 -28
  9. data/deliver/lib/deliver/upload_app_clip_default_experience_header_images.rb +3 -3
  10. data/deliver/lib/deliver/upload_metadata.rb +12 -16
  11. data/deliver/lib/deliver/upload_screenshots.rb +12 -14
  12. data/docs/README.md +25 -0
  13. data/fastlane/lib/assets/TeamTable.md.erb +28 -0
  14. data/fastlane/lib/fastlane/actions/appaloosa.rb +2 -1
  15. data/fastlane/lib/fastlane/actions/appium.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/apteligent.rb +8 -1
  17. data/fastlane/lib/fastlane/actions/build_and_upload_to_appetize.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  19. data/fastlane/lib/fastlane/actions/danger.rb +5 -5
  20. data/fastlane/lib/fastlane/actions/docs/build_app.md +1 -2
  21. data/fastlane/lib/fastlane/actions/docs/create_app_online.md +1 -2
  22. data/fastlane/lib/fastlane/actions/docs/get_certificates.md +0 -2
  23. data/fastlane/lib/fastlane/actions/docs/get_push_certificate.md +1 -3
  24. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +13 -3
  25. data/fastlane/lib/fastlane/actions/ensure_no_debug_code.rb +2 -2
  26. data/fastlane/lib/fastlane/actions/get_managed_play_store_publishing_rights.rb +1 -0
  27. data/fastlane/lib/fastlane/actions/github_api.rb +1 -2
  28. data/fastlane/lib/fastlane/actions/hg_commit_version_bump.rb +1 -1
  29. data/fastlane/lib/fastlane/actions/increment_build_number.rb +1 -1
  30. data/fastlane/lib/fastlane/actions/increment_version_number.rb +1 -1
  31. data/fastlane/lib/fastlane/actions/install_xcode_plugin.rb +16 -6
  32. data/fastlane/lib/fastlane/actions/lcov.rb +9 -2
  33. data/fastlane/lib/fastlane/actions/nexus_upload.rb +16 -4
  34. data/fastlane/lib/fastlane/actions/notification.rb +3 -2
  35. data/fastlane/lib/fastlane/actions/oclint.rb +8 -8
  36. data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +9 -4
  37. data/fastlane/lib/fastlane/actions/register_device.rb +1 -1
  38. data/fastlane/lib/fastlane/actions/say.rb +2 -4
  39. data/fastlane/lib/fastlane/actions/set_pod_key.rb +3 -3
  40. data/fastlane/lib/fastlane/actions/sonar.rb +16 -16
  41. data/fastlane/lib/fastlane/actions/sourcedocs.rb +8 -8
  42. data/fastlane/lib/fastlane/actions/splunkmint.rb +8 -1
  43. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +1 -1
  44. data/fastlane/lib/fastlane/actions/upload_to_app_store.rb +5 -2
  45. data/fastlane/lib/fastlane/actions/upload_to_testflight.rb +5 -2
  46. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -4
  47. data/fastlane/lib/fastlane/actions/verify_pod_keys.rb +3 -1
  48. data/fastlane/lib/fastlane/actions/xcode_server_get_assets.rb +20 -19
  49. data/fastlane/lib/fastlane/actions/xcodebuild.rb +5 -6
  50. data/fastlane/lib/fastlane/actions/zip.rb +2 -1
  51. data/fastlane/lib/fastlane/auto_complete.rb +1 -1
  52. data/fastlane/lib/fastlane/cli_tools_distributor.rb +14 -0
  53. data/fastlane/lib/fastlane/documentation/markdown_docs_generator.rb +23 -0
  54. data/fastlane/lib/fastlane/environment_printer.rb +12 -25
  55. data/fastlane/lib/fastlane/fast_file.rb +2 -1
  56. data/fastlane/lib/fastlane/fastlane_require.rb +5 -1
  57. data/fastlane/lib/fastlane/helper/sh_helper.rb +16 -6
  58. data/fastlane/lib/fastlane/new_action.rb +1 -1
  59. data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +1 -1
  60. data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +1 -1
  61. data/fastlane/lib/fastlane/plugins/plugin_manager.rb +36 -4
  62. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  63. data/fastlane/lib/fastlane/plugins/template/.github/workflows/test.yml +1 -1
  64. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +3 -7
  65. data/fastlane/lib/fastlane/swift_lane_manager.rb +22 -4
  66. data/fastlane/lib/fastlane/version.rb +3 -2
  67. data/fastlane/swift/Deliverfile.swift +1 -1
  68. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  69. data/fastlane/swift/Fastlane.swift +55 -9
  70. data/fastlane/swift/Gymfile.swift +1 -1
  71. data/fastlane/swift/GymfileProtocol.swift +1 -1
  72. data/fastlane/swift/MainProcess.swift +190 -20
  73. data/fastlane/swift/Matchfile.swift +1 -1
  74. data/fastlane/swift/MatchfileProtocol.swift +10 -3
  75. data/fastlane/swift/Precheckfile.swift +1 -1
  76. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  77. data/fastlane/swift/Scanfile.swift +1 -1
  78. data/fastlane/swift/ScanfileProtocol.swift +8 -1
  79. data/fastlane/swift/Screengrabfile.swift +1 -1
  80. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  81. data/fastlane/swift/Snapshotfile.swift +1 -1
  82. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  83. data/fastlane/swift/formatting/Rakefile +1 -0
  84. data/fastlane/swift/main.swift +4 -1
  85. data/fastlane_core/README.md +1 -1
  86. data/fastlane_core/lib/fastlane_core/analytics/analytics_event_builder.rb +1 -17
  87. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +11 -8
  88. data/fastlane_core/lib/fastlane_core/device_manager.rb +2 -2
  89. data/fastlane_core/lib/fastlane_core/helper.rb +34 -5
  90. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +0 -14
  91. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +24 -27
  92. data/fastlane_core/lib/fastlane_core/languages.rb +0 -1
  93. data/fastlane_core/lib/fastlane_core/print_table.rb +5 -2
  94. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +10 -19
  95. data/fastlane_core/lib/fastlane_core/secrets.rb +77 -0
  96. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +3 -3
  97. data/fastlane_core/lib/fastlane_core/ui/github_issue_inspector_reporter.rb +1 -1
  98. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +2 -1
  99. data/fastlane_core/lib/fastlane_core.rb +1 -0
  100. data/frameit/lib/frameit/device.rb +1 -1
  101. data/frameit/lib/frameit/frame_downloader.rb +2 -0
  102. data/frameit/lib/frameit/runner.rb +3 -2
  103. data/frameit/lib/frameit/screenshot.rb +0 -1
  104. data/gym/lib/gym/generators/build_command_generator.rb +4 -4
  105. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +3 -3
  106. data/gym/lib/gym/runner.rb +1 -1
  107. data/internal/README.md +1 -1
  108. data/match/lib/match/generator.rb +1 -0
  109. data/match/lib/match/options.rb +13 -5
  110. data/match/lib/match/storage/git_storage.rb +43 -7
  111. data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
  112. data/pem/lib/pem/module.rb +0 -4
  113. data/precheck/lib/precheck/rule_processor.rb +1 -0
  114. data/produce/lib/produce/developer_center.rb +49 -2
  115. data/produce/lib/produce/itunes_connect.rb +1 -1
  116. data/produce/lib/produce/module.rb +21 -0
  117. data/produce/lib/produce/options.rb +17 -0
  118. data/produce/lib/produce/service.rb +3 -5
  119. data/produce/lib/produce.rb +0 -1
  120. data/scan/lib/scan/options.rb +7 -0
  121. data/scan/lib/scan/runner.rb +1 -1
  122. data/scan/lib/scan/test_command_generator.rb +12 -9
  123. data/scan/lib/scan/test_result_parser.rb +1 -0
  124. data/scan/lib/scan/xcpretty_reporter_options_generator.rb +2 -2
  125. data/screengrab/lib/screengrab/page.html.erb +12 -7
  126. data/screengrab/lib/screengrab/reports_generator.rb +1 -1
  127. data/sigh/lib/assets/resign.sh +17 -0
  128. data/sigh/lib/sigh/local_manage.rb +1 -1
  129. data/sigh/lib/sigh/options.rb +5 -0
  130. data/sigh/lib/sigh/resign.rb +3 -3
  131. data/sigh/lib/sigh/runner.rb +6 -1
  132. data/snapshot/lib/snapshot/page.html.erb +17 -12
  133. data/snapshot/lib/snapshot/reports_generator.rb +1 -1
  134. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +1 -1
  135. data/snapshot/lib/snapshot.rb +0 -2
  136. data/spaceship/README.md +2 -3
  137. data/spaceship/lib/spaceship/base.rb +3 -1
  138. data/spaceship/lib/spaceship/client.rb +65 -47
  139. data/spaceship/lib/spaceship/connect_api/models/bundle_id.rb +13 -2
  140. data/spaceship/lib/spaceship/connect_api/models/bundle_id_capability.rb +6 -0
  141. data/spaceship/lib/spaceship/connect_api/models/profile.rb +7 -5
  142. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +25 -0
  143. data/spaceship/lib/spaceship/du/upload_file.rb +1 -1
  144. data/spaceship/lib/spaceship/errors.rb +7 -1
  145. data/spaceship/lib/spaceship/globals.rb +2 -2
  146. data/spaceship/lib/spaceship/portal/merchant.rb +127 -2
  147. data/spaceship/lib/spaceship/portal/portal_client.rb +58 -0
  148. data/spaceship/lib/spaceship/portal/ui/select_team.rb +0 -2
  149. data/spaceship/lib/spaceship/portal/website_push.rb +8 -2
  150. data/spaceship/lib/spaceship/test_flight/client.rb +0 -22
  151. data/spaceship/lib/spaceship/test_flight.rb +0 -1
  152. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -0
  153. data/supply/lib/supply/options.rb +1 -0
  154. data/supply/lib/supply.rb +0 -1
  155. metadata +54 -36
  156. data/produce/lib/produce/available_default_languages.rb +0 -62
  157. data/snapshot/lib/snapshot/screenshot_flatten.rb +0 -20
  158. data/snapshot/lib/snapshot/screenshot_rotate.rb +0 -36
  159. data/spaceship/lib/spaceship/test_flight/app_test_info.rb +0 -40
  160. data/supply/lib/supply/apk_listing.rb +0 -14
  161. /data/fastlane/lib/assets/{custom_action_template.rb → custom_action_template.rb.template} +0 -0
@@ -321,6 +321,23 @@ else
321
321
  error "Error: Only can resign .app files and .ipa files."
322
322
  fi
323
323
 
324
+ # Stop if a symbolic link resolves outside the temporary directory: later steps write through such links.
325
+ # Resolved with the cd -P and pwd -P builtins, as realpath and readlink -f are missing on older macOS.
326
+ TEMP_DIR_PHYSICAL="$(cd -P "$TEMP_DIR" && pwd -P)"
327
+ checkStatus
328
+ while IFS= read -d '' -r link; do
329
+ target="$(readlink "$link")"
330
+ [[ "$target" == /* ]] || target="$(dirname "$link")/$target"
331
+ if [ -d "$target" ]; then
332
+ resolved="$(cd -P "$target" 2>/dev/null && pwd -P)"
333
+ else
334
+ resolved="$(cd -P "$(dirname "$target")" 2>/dev/null && pwd -P)/$(basename "$target")"
335
+ fi
336
+ if [[ "$resolved" != "$TEMP_DIR_PHYSICAL" && "$resolved" != "$TEMP_DIR_PHYSICAL"/* ]]; then
337
+ error "Error: '${link#"$TEMP_DIR"/}' is a symbolic link to '$(readlink "$link")', outside of '$ORIGINAL_FILE'. Remove it before resigning."
338
+ fi
339
+ done < <(find "$TEMP_DIR" -type l -print0)
340
+
324
341
  # check the keychain
325
342
  if [ "${KEYCHAIN}" != "" ]; then
326
343
  security list-keychains -s "$KEYCHAIN"
@@ -134,7 +134,7 @@ module Sigh
134
134
 
135
135
  profiles = []
136
136
  profile_paths.each do |profile_path|
137
- profile = Plist.parse_xml(`security cms -D -i '#{profile_path}' 2> /dev/null`) # /dev/null: https://github.com/fastlane/fastlane/issues/6387
137
+ profile = FastlaneCore::ProvisioningProfile.parse(profile_path)
138
138
  profile['Path'] = profile_path
139
139
  profiles << profile
140
140
  end
@@ -63,6 +63,11 @@ module Sigh
63
63
  code_gen_sensitive: true,
64
64
  default_value: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier),
65
65
  default_value_dynamic: true),
66
+ FastlaneCore::ConfigItem.new(key: :offline_profile,
67
+ env_name: "SIGH_OFFLINE_PROFILE",
68
+ description: "Enable profile with 'Offline Support' (7 day validity). Requires Apple ID login, not supported with App Store Connect API key authentication",
69
+ is_string: false,
70
+ default_value: false),
66
71
 
67
72
  # App Store Connect API
68
73
  FastlaneCore::ConfigItem.new(key: :api_key_path,
@@ -45,10 +45,10 @@ module Sigh
45
45
  entitlements = "-e #{entitlements.shellescape}" if entitlements
46
46
 
47
47
  provisioning_options = create_provisioning_options(provisioning_profiles)
48
- version = "-n #{version}" if version
48
+ version = "-n #{version.shellescape}" if version
49
49
  display_name = "-d #{display_name.shellescape}" if display_name
50
- short_version = "--short-version #{short_version}" if short_version
51
- bundle_version = "--bundle-version #{bundle_version}" if bundle_version
50
+ short_version = "--short-version #{short_version.shellescape}" if short_version
51
+ bundle_version = "--bundle-version #{bundle_version.shellescape}" if bundle_version
52
52
  verbose = "-v" if FastlaneCore::Globals.verbose?
53
53
  bundle_id = "-b '#{new_bundle_id}'" if new_bundle_id
54
54
  use_app_entitlements_flag = "--use-app-entitlements" if use_app_entitlements
@@ -34,6 +34,10 @@ module Sigh
34
34
  UI.message("Successfully logged in")
35
35
  end
36
36
 
37
+ if Sigh.config[:offline_profile] && !Spaceship::ConnectAPI.token.nil?
38
+ UI.user_error!("The 'offline_profile' option requires logging in with an Apple ID. Apple's App Store Connect API (API key authentication) does not support the 'Offline Support' profile setting")
39
+ end
40
+
37
41
  profiles = [] if Sigh.config[:skip_fetch_profiles]
38
42
  profiles ||= fetch_profiles # download the profile if it's there
39
43
 
@@ -185,7 +189,8 @@ module Sigh
185
189
  profile_type: profile_type,
186
190
  bundle_id_id: bundle_id.id,
187
191
  certificate_ids: certificates_to_use.map(&:id),
188
- device_ids: devices_to_use.map(&:id)
192
+ device_ids: devices_to_use.map(&:id),
193
+ is_offline_profile: Sigh.config[:offline_profile]
189
194
  )
190
195
 
191
196
  profile
@@ -96,38 +96,38 @@
96
96
  <button class="tabLink" onclick="openTab(event, 'byScreen')">By Screen</button>
97
97
  </div>
98
98
  <div id="byLanguage" class="tabContent"><h1 class="tabTitle">By Language:</h1><% image_counter = 0 %><% @data_by_language.each do |language, content| %>
99
- <h2 id="<%= language %>"><%= language %></h2>
99
+ <h2 id="<%= ERB::Util.h(language) %>"><%= ERB::Util.h(language) %></h2>
100
100
  <hr>
101
101
  <table><% content.each do |device_name, screens| %>
102
102
  <tr>
103
103
  <th colspan="<%= screens.count %>">
104
- <a id="<%= language %>-<%= device_name %>" class="deviceName" href="#<%= language %>-<%= device_name %>"><%= device_name %></a>
104
+ <a id="<%= ERB::Util.h(language) %>-<%= ERB::Util.h(device_name) %>" class="deviceName" href="#<%= ERB::Util.h(language) %>-<%= ERB::Util.h(device_name) %>"><%= ERB::Util.h(device_name) %></a>
105
105
  </th>
106
106
  </tr>
107
107
  <tr><% screens.each do |screen_path| %><% next if screen_path.include?"_framed.png" %>
108
108
  <td><% image_counter += 1 %>
109
- <a href="<%= screen_path %>" target="_blank" class="screenshotLink">
110
- <img class="screenshot" src="<%= screen_path %>" style="width: 100%;" alt="<%= language %> <%= device_name %>" data-tab="1" data-counter="<%= image_counter %>">
109
+ <a href="<%= ERB::Util.h(screen_path) %>" target="_blank" class="screenshotLink">
110
+ <img class="screenshot" src="<%= ERB::Util.h(screen_path) %>" style="width: 100%;" alt="<%= ERB::Util.h(language) %> <%= ERB::Util.h(device_name) %>" data-tab="1" data-counter="<%= image_counter %>">
111
111
  </a>
112
112
  </td><% end %>
113
113
  </tr><% end %>
114
114
  </table><% end %>
115
115
  </div>
116
116
  <div id="byScreen" class="tabContent"><h1 class="tabTitle">By Screen:</h1><% image_counter = 0 %><% @data_by_screen.each do |screen, content| %>
117
- <h2 id="<%= screen %>" class="screen"><%= screen %></h2>
117
+ <h2 id="<%= ERB::Util.h(screen) %>" class="screen"><%= ERB::Util.h(screen) %></h2>
118
118
  <hr>
119
119
  <table><% content.each do |device_name, screens| %>
120
120
  <tr>
121
121
  <th colspan="<%= screens.count %>">
122
- <a id="<%= screen %>-<%= device_name %>" class="deviceName" href="#<%= screen %>-<%= device_name %>"><%= device_name %></a>
122
+ <a id="<%= ERB::Util.h(screen) %>-<%= ERB::Util.h(device_name) %>" class="deviceName" href="#<%= ERB::Util.h(screen) %>-<%= ERB::Util.h(device_name) %>"><%= ERB::Util.h(device_name) %></a>
123
123
  </th>
124
124
  </tr>
125
125
  <tr><% screens.each do |language, screen_path| %><% next if screen_path.include?"_framed.png" %>
126
126
  <td><% image_counter += 1 %>
127
- <a href="<%= screen_path %>" target="_blank" class="screenshotLink">
128
- <img class="screenshot" src="<%= screen_path %>" style="width: 100%;" alt="<%= language %> <%= device_name %>" data-tab="2" data-counter="<%= image_counter %>">
127
+ <a href="<%= ERB::Util.h(screen_path) %>" target="_blank" class="screenshotLink">
128
+ <img class="screenshot" src="<%= ERB::Util.h(screen_path) %>" style="width: 100%;" alt="<%= ERB::Util.h(language) %> <%= ERB::Util.h(device_name) %>" data-tab="2" data-counter="<%= image_counter %>">
129
129
  </a>
130
- <div class="caption"><%= language %></div>
130
+ <div class="caption"><%= ERB::Util.h(language) %></div>
131
131
  </td><% end %>
132
132
  </tr><% end %>
133
133
  </table><% end %>
@@ -222,9 +222,14 @@
222
222
  imageDisplay.alt = img.alt;
223
223
  imageDisplay.dataset.counter = img.dataset.counter;
224
224
 
225
- imageInfo.innerHTML = '<h3>'+img.alt+'</h3>';
226
- imageInfo.innerHTML += decodeURI(img.src.split("/").pop());
227
- imageInfo.innerHTML += '<br />'+tmpImg.height+'&times;'+tmpImg.width+'px';
225
+ // textContent, not innerHTML: file names must not be interpreted as HTML
226
+ var imageTitle = document.createElement('h3');
227
+ imageTitle.textContent = img.alt;
228
+ imageInfo.textContent = '';
229
+ imageInfo.appendChild(imageTitle);
230
+ imageInfo.appendChild(document.createTextNode(decodeURI(img.src.split("/").pop())));
231
+ imageInfo.appendChild(document.createElement('br'));
232
+ imageInfo.appendChild(document.createTextNode(tmpImg.height+'\u00d7'+tmpImg.width+'px'));
228
233
 
229
234
  overlay.style.display = "block";
230
235
  });
@@ -51,7 +51,7 @@ module Snapshot
51
51
 
52
52
  export_path = File.expand_path(export_path)
53
53
  UI.success("Successfully created HTML file with an overview of all the screenshots: '#{export_path}'")
54
- system("open '#{export_path}'") unless Snapshot.config[:skip_open_summary]
54
+ system("open", export_path) unless Snapshot.config[:skip_open_summary]
55
55
  end
56
56
 
57
57
  def xcode_8_and_below_device_name_mappings
@@ -82,7 +82,7 @@ module Snapshot
82
82
  unless launcher_config.headless
83
83
  simulator_path = File.join(Helper.xcode_path, 'Applications', 'Simulator.app')
84
84
  device_udid = TestCommandGenerator.device_udid(device_types.first)
85
- command = "open -a #{simulator_path} -g"
85
+ command = "open -a #{simulator_path.shellescape} -g"
86
86
  command += " --args -CurrentDeviceUDID #{device_udid}" if device_udid
87
87
  Helper.backticks(command, print: FastlaneCore::Globals.verbose?)
88
88
  end
@@ -1,8 +1,6 @@
1
1
  require_relative 'snapshot/runner'
2
2
  require_relative 'snapshot/reports_generator'
3
3
  require_relative 'snapshot/detect_values'
4
- require_relative 'snapshot/screenshot_flatten'
5
- require_relative 'snapshot/screenshot_rotate'
6
4
  require_relative 'snapshot/dependency_checker'
7
5
  require_relative 'snapshot/latest_os_version'
8
6
  require_relative 'snapshot/test_command_generator'
data/spaceship/README.md CHANGED
@@ -27,8 +27,7 @@ Get in contact with the creators on Twitter: [@FastlaneTools](https://twitter.co
27
27
  <a href="#whats-spaceship">Why?</a> &bull;
28
28
  <a href="#usage">Usage</a> &bull;
29
29
  <a href="#installation">Installation</a> &bull;
30
- <a href="#technical-details">Technical Details</a> &bull;
31
- <a href="#need-help">Need help?</a>
30
+ <a href="#technical-details">Technical Details</a>
32
31
  </p>
33
32
 
34
33
  -------
@@ -165,7 +164,7 @@ _spaceship_ does a lot of magic to get everything working so neatly:
165
164
  - **Multiple Spaceship**: You can launch multiple _spaceships_ with different Apple accounts to do things like syncing the registered devices.
166
165
 
167
166
  # Code of Conduct
168
- Help us keep _fastlane_ open and inclusive. Please read and follow our [Code of Conduct](https://github.com/fastlane/fastlane/blob/master/CODE_OF_CONDUCT.md).
167
+ Help us keep _fastlane_ open and inclusive. Please read and follow our [Code of Conduct](https://github.com/fastlane/fastlane/blob/master/docs/CODE_OF_CONDUCT.md).
169
168
 
170
169
  # License
171
170
  This project is licensed under the terms of the MIT license. See the LICENSE file.
@@ -224,7 +224,9 @@ module Spaceship
224
224
  # The initialize method accepts a parsed response from Apple and sets all
225
225
  # attributes that are defined by `attr_mapping`
226
226
  #
227
- # Do not override `initialize` in your own models.
227
+ # Do not override `initialize` in your own models, use `setup` instead. An
228
+ # override that has to take a value Apple's response does not carry must call
229
+ # `super`, or `attr_mapping`, `raw_data`, `client` and `setup` are all skipped.
228
230
  def initialize(attrs = {}, existing_client = nil)
229
231
  attrs.each do |key, val|
230
232
  self.send("#{key}=", val) if respond_to?("#{key}=")
@@ -605,16 +605,14 @@ module Spaceship
605
605
  # Now we know if the login is successful or if we need to do 2 factor
606
606
 
607
607
  case response.status
608
- when 403
609
- raise InvalidUserCredentialsError.new, "Invalid username and password combination. Used '#{user}' as the username."
610
608
  when 200
611
- fetch_olympus_session
609
+ fetch_olympus_session_after_signin
612
610
  return response
613
611
  when 409
614
612
  # 2 step/factor is enabled for this account, first handle that
615
613
  handle_two_step_or_factor(response)
616
614
  # and then get the olympus session
617
- fetch_olympus_session
615
+ fetch_olympus_session_after_signin
618
616
  return true
619
617
  else
620
618
  if (response.body || "").include?('invalid="true"')
@@ -692,6 +690,16 @@ module Spaceship
692
690
  return Spaceship::Hashcash.make(bits: bits, challenge: challenge)
693
691
  end
694
692
 
693
+ # Apple accepts a stale key for the sign in and 2FA, and only refuses the
694
+ # session afterwards, with a 401 that says nothing about the key.
695
+ def fetch_olympus_session_after_signin
696
+ fetch_olympus_session
697
+ rescue UnauthorizedAccessError
698
+ raise unless service_key_from_cache?
699
+
700
+ raise UnauthorizedAccessError.new, "App Store Connect refused the session after signing in. The App Store Connect API key came from the cache at #{itc_service_key_path} and may be stale: delete that file and try again."
701
+ end
702
+
695
703
  # Get the `itctx` from the new (22nd May 2017) API endpoint "olympus"
696
704
  # Update (29th March 2019) olympus migrates to new appstoreconnect API
697
705
  def fetch_olympus_session
@@ -721,58 +729,59 @@ module Spaceship
721
729
  exit(has_valid_session)
722
730
  end
723
731
 
724
- # <tmpdir>/spaceship_itc_service_key.txt
725
- #
726
- # Dir.tmpdir rather than a literal "/tmp", for the same reason as #logger,
727
- # and here the failure was worse than a missing file: itc_service_key
728
- # rescues everything, so the write failing on Windows was reported as an
729
- # App Store Connect outage. See #30198.
730
- #
731
- # On macOS this is also per user, so a cache written by one user no longer
732
- # blocks another.
732
+ # <fastlane_user_dir>/spaceship_itc_service_key.txt: per user, and kept between
733
+ # runs. Dir.tmpdir (#30197) is shared by every user on Linux, and a literal
734
+ # "/tmp" is not a directory on Windows.
733
735
  def itc_service_key_path
734
- File.join(Dir.tmpdir, "spaceship_itc_service_key.txt")
736
+ File.join(fastlane_user_dir, "spaceship_itc_service_key.txt")
735
737
  end
736
738
 
739
+ # Read on every login from GET /logout -> 302 Location: .../signout?widgetKey=<key>.
740
+ # A stale key only fails after 2FA, so the cache is just a fallback. See fastlane#30291.
737
741
  def itc_service_key
738
742
  return @service_key if @service_key
739
743
 
740
- # Check if we have a local cache of the key. Reading it is best effort:
741
- # an unreadable cache is a reason to fetch the key again, not to fail.
742
- begin
743
- return File.read(itc_service_key_path) if File.exist?(itc_service_key_path)
744
- rescue SystemCallError => ex
745
- logger.warn("Could not read the cached App Store Connect API key: #{ex.message}")
744
+ key = fetch_service_key_from_signout
745
+ if key.nil?
746
+ cached = cached_service_key
747
+ return @service_key = cached if cached
748
+
749
+ key = fetch_service_key_from_olympus
746
750
  end
747
751
 
748
- @service_key = fetch_service_key
752
+ cache_service_key(key)
753
+ @service_key = key
754
+ end
749
755
 
750
- # Cache the key locally. Best effort as well: having the key and failing
751
- # to save it is not a reason to fail the login. See fastlane#30198.
752
- begin
753
- File.write(itc_service_key_path, @service_key)
754
- rescue SystemCallError => ex
755
- logger.warn("Could not cache the App Store Connect API key at #{itc_service_key_path}: #{ex.message}")
756
- end
756
+ def service_key_from_cache?
757
+ @service_key_from_cache == true
758
+ end
759
+
760
+ # Reading the cache is best effort: an unreadable cache is a reason to
761
+ # fetch the key again, not to fail.
762
+ def cached_service_key
763
+ return nil unless File.exist?(itc_service_key_path)
757
764
 
758
- return @service_key
765
+ key = File.read(itc_service_key_path)
766
+ @service_key_from_cache = true
767
+ logger.warn("Using the cached App Store Connect API key from #{itc_service_key_path}")
768
+ key
769
+ rescue SystemCallError => ex
770
+ logger.warn("Could not read the cached App Store Connect API key: #{ex.message}")
771
+ nil
759
772
  end
760
773
 
761
- # Two sources, because the one this used to rely on is gone.
762
- #
763
- # App Store Connect's sign out route answers with a redirect that carries
764
- # the key its own front end is using:
765
- #
766
- # GET /logout -> 302 Location: .../appleauth/signout?widgetKey=<key>&...
767
- #
768
- # That is preferred over the olympus endpoint below, which Apple removed in
769
- # September 2026 and which now answers 404. See fastlane#30199. The olympus
770
- # call is kept as a fallback in case it comes back, since it is the source
771
- # Apple documented rather than one scraped out of a redirect.
772
- def fetch_service_key
773
- key = fetch_service_key_from_signout
774
- return key if key
774
+ # Best effort as well: having the key and failing to save it is not a reason
775
+ # to fail the login. See fastlane#30198.
776
+ def cache_service_key(key)
777
+ File.write(itc_service_key_path, key)
778
+ rescue SystemCallError => ex
779
+ logger.warn("Could not cache the App Store Connect API key: #{ex.message}")
780
+ end
775
781
 
782
+ # The source Apple documented, removed in September 2026 and answering 404
783
+ # since. Kept as the last fallback in case it comes back. See fastlane#30199.
784
+ def fetch_service_key_from_olympus
776
785
  # Fixes issue https://github.com/fastlane/fastlane/issues/13281
777
786
  # Even though we are using https://appstoreconnect.apple.com, the service key needs to still use a
778
787
  # hostname through itunesconnect.apple.com
@@ -830,10 +839,11 @@ module Spaceship
830
839
  # said instead of the real cause.
831
840
  #
832
841
  # Warn rather than debug. This is the source the key normally comes from,
833
- # so failing here means the run is about to depend on an endpoint Apple has
834
- # already removed once. If the fallback fails too, its message is all the
835
- # caller sees, and this line is the half that says why.
836
- logger.warn("Could not read the App Store Connect API key from the sign out redirect, falling back to the olympus endpoint: #{ex.message}")
842
+ # so failing here means the run is about to depend on a cached key that may
843
+ # be stale, or on an endpoint Apple has already removed once. If the
844
+ # fallback fails too, its message is all the caller sees, and this line is
845
+ # the half that says why.
846
+ logger.warn("Could not read the App Store Connect API key from the sign out redirect, falling back to the cache or the olympus endpoint: #{ex.message}")
837
847
  nil
838
848
  end
839
849
 
@@ -1189,6 +1199,11 @@ module Spaceship
1189
1199
  end
1190
1200
  end
1191
1201
 
1202
+ # signin/init, signin/complete, and the legacy signin
1203
+ def sign_in_request?(response)
1204
+ response.env.url.host == "idmsa.apple.com" && response.env.url.path.start_with?("/appleauth/auth/signin")
1205
+ end
1206
+
1192
1207
  def handle_error(response)
1193
1208
  case response.status
1194
1209
  when 401
@@ -1196,6 +1211,9 @@ module Spaceship
1196
1211
  logger.warn(msg)
1197
1212
  raise UnauthorizedAccessError.new, "Unauthorized Access"
1198
1213
  when 403
1214
+ # A refused sign-in, e.g. a locked Apple ID: like do_sirp, raise what Apple said, which with_retry does not retry. See fastlane#14387.
1215
+ raise UnexpectedResponse, response.body if sign_in_request?(response) && response.body.kind_of?(Hash) && response.body["serviceErrors"]
1216
+
1199
1217
  msg = "Access forbidden"
1200
1218
  logger.warn(msg)
1201
1219
  raise AccessForbiddenError.new, msg
@@ -81,12 +81,23 @@ module Spaceship
81
81
  return resp.to_models.first
82
82
  end
83
83
 
84
+ # `patch_bundle_id_capability` patches the wrong resource (bundleIds, with
85
+ # attributes nested inside a relationship's `data` entry) — invalid JSON:API,
86
+ # rejected by Apple. This uses the real per-capability endpoints instead:
87
+ # create to enable, PATCH to update settings, delete to disable.
84
88
  def update_capability(capability_type, enabled: false, settings: [], client: nil)
85
89
  raise "capability_type is required " if capability_type.nil?
86
90
 
87
91
  client ||= Spaceship::ConnectAPI
88
- resp = client.patch_bundle_id_capability(bundle_id_id: id, seed_id: seed_id, enabled: enabled, capability_type: capability_type, settings: settings)
89
- return resp.to_models.first
92
+ existing = get_capabilities(client: client).find { |capability| capability.is_type?(capability_type) }
93
+
94
+ if enabled
95
+ return create_capability(capability_type, settings: settings, client: client) unless existing
96
+ return existing.update!(client: client, settings: settings)
97
+ else
98
+ existing&.delete!(client: client)
99
+ return nil
100
+ end
90
101
  end
91
102
  end
92
103
  end
@@ -133,6 +133,12 @@ module Spaceship
133
133
  client ||= Spaceship::ConnectAPI
134
134
  client.delete_bundle_id_capability(bundle_id_capability_id: id)
135
135
  end
136
+
137
+ def update!(client: nil, enabled: true, settings: [])
138
+ client ||= Spaceship::ConnectAPI
139
+ resp = client.patch_bundle_id_capability_configuration(bundle_id_capability_id: id, enabled: enabled, settings: settings)
140
+ return resp.to_models.first
141
+ end
136
142
  end
137
143
  end
138
144
  end
@@ -84,16 +84,18 @@ module Spaceship
84
84
  return resps.flat_map(&:to_models)
85
85
  end
86
86
 
87
- def self.create(client: nil, name: nil, profile_type: nil, bundle_id_id: nil, certificate_ids: nil, device_ids: nil)
87
+ def self.create(client: nil, name: nil, profile_type: nil, bundle_id_id: nil, certificate_ids: nil, device_ids: nil, is_offline_profile: false)
88
88
  client ||= Spaceship::ConnectAPI
89
+ attributes = {
90
+ name: name,
91
+ profileType: profile_type
92
+ }
93
+ attributes[:isOfflineProfile] = true if is_offline_profile
89
94
  resp = client.post_profiles(
90
95
  bundle_id_id: bundle_id_id,
91
96
  certificates: certificate_ids,
92
97
  devices: device_ids,
93
- attributes: {
94
- name: name,
95
- profileType: profile_type
96
- }
98
+ attributes: attributes
97
99
  )
98
100
  return resp.to_models.first
99
101
  end
@@ -3,6 +3,11 @@ require 'spaceship/connect_api/provisioning/client'
3
3
  module Spaceship
4
4
  class ConnectAPI
5
5
  module Provisioning
6
+ # Raised when a profile with 'Offline Support' is requested while authenticated
7
+ # with an App Store Connect API key. Apple only exposes the isOfflineProfile
8
+ # attribute on the Developer Portal endpoint used by Apple ID (web session) logins.
9
+ class OfflineProfileNotSupportedError < StandardError; end
10
+
6
11
  module API
7
12
  module Version
8
13
  V1 = "v1"
@@ -84,6 +89,22 @@ module Spaceship
84
89
  provisioning_request_client.post("#{Version::V1}/bundleIdCapabilities", body)
85
90
  end
86
91
 
92
+ # Modify a Capability Configuration — patches the bundleIdCapability resource
93
+ # directly (attributes at the top level), not the bundleId it belongs to.
94
+ def patch_bundle_id_capability_configuration(bundle_id_capability_id:, enabled: false, settings: [])
95
+ body = {
96
+ data: {
97
+ type: "bundleIdCapabilities",
98
+ id: bundle_id_capability_id,
99
+ attributes: {
100
+ enabled: enabled,
101
+ settings: settings
102
+ }
103
+ }
104
+ }
105
+ provisioning_request_client.patch("#{Version::V1}/bundleIdCapabilities/#{bundle_id_capability_id}", body)
106
+ end
107
+
87
108
  def patch_bundle_id_capability(bundle_id_id:, seed_id:, enabled: false, capability_type:, settings: [])
88
109
  body = {
89
110
  data: {
@@ -222,6 +243,10 @@ module Spaceship
222
243
  end
223
244
 
224
245
  def post_profiles(bundle_id_id: nil, certificates: nil, devices: nil, attributes: {})
246
+ if attributes[:isOfflineProfile] && !provisioning_request_client.web_session?
247
+ raise OfflineProfileNotSupportedError, "Profiles with 'Offline Support' (isOfflineProfile) can only be created when logged in with an Apple ID; the App Store Connect API does not support this attribute"
248
+ end
249
+
225
250
  body = {
226
251
  data: {
227
252
  attributes: attributes,
@@ -34,7 +34,7 @@ module Spaceship
34
34
  # This will copy the image into the temporary directory to remove the
35
35
  # alpha channel there. That's done to not edit the original image.
36
36
  def remove_alpha_channel(original)
37
- path = File.join(Dir.tmpdir, "#{Digest::MD5.hexdigest(original)}.png")
37
+ path = File.join(Dir.mktmpdir("spaceship-upload-"), File.basename(original))
38
38
  FileUtils.copy(original, path)
39
39
  if mac? # sips is only available on macOS
40
40
  `sips -s format bmp '#{path}' &> /dev/null` # &> /dev/null since there is warning because of the extension
@@ -86,7 +86,13 @@ module Spaceship
86
86
  end
87
87
 
88
88
  def preferred_error_info
89
- return nil unless @error_info.kind_of?(Hash) && @error_info['resultString']
89
+ return nil unless @error_info.kind_of?(Hash)
90
+
91
+ # What Apple's authentication service answers, e.g. on a locked Apple ID
92
+ service_errors = Array(@error_info['serviceErrors'])
93
+ return ["Apple provided the following error info:"] + service_errors.map { |error| [error['message'], "(#{error['code']})"].compact.join(" ") } unless service_errors.empty?
94
+
95
+ return nil unless @error_info['resultString']
90
96
 
91
97
  [
92
98
  "Apple provided the following error info:",
@@ -7,8 +7,8 @@ module Spaceship
7
7
  # if spaceship is run with a FastlaneCore available respect the global state there
8
8
  # otherwise fallback to $verbose
9
9
  def self.verbose?
10
- if Object.const_defined?("FastlaneCore")
11
- return FastlaneCore::Globals.verbose? # rubocop:disable Require/MissingRequireStatement
10
+ if Object.const_defined?("FastlaneCore") && FastlaneCore.const_defined?("Globals")
11
+ return FastlaneCore::Globals.verbose?
12
12
  end
13
13
  return $verbose
14
14
  end