diff --git a/.circleci/Dangerfile-Lib.rb b/.circleci/Dangerfile-Lib.rb deleted file mode 100644 index af312eeb8c..0000000000 --- a/.circleci/Dangerfile-Lib.rb +++ /dev/null @@ -1,46 +0,0 @@ -gem 'plist' -require 'plist' - -# Markdown table character length without any issues -MAKRDOWN_LENGTH = 138 - -test_results = 'test_output/report.junit' -if File.file?(test_results) - junit.parse test_results - junit.show_skipped_tests = true - junit.report -end - -message = "### Clang Static Analysis Issues\n\n" -message << "File | Type | Category | Description | Line | Col |\n" -message << " --- | ---- | -------- | ----------- | ---- | --- |\n" - -# Parse Clang Plist files and report issues associated with files modified in this PR. -clang_libs = Dir['clangReport/StaticAnalyzer/*'].map { | x | x.split('/').last } -for lib in clang_libs; - files = Dir["clangReport/StaticAnalyzer/#{lib}/#{lib}/normal/x86_64/*.plist"].map { | x | x.split('/').last } - for file in files; - report = Plist.parse_xml("clangReport/StaticAnalyzer/#{lib}/#{lib}/normal/x86_64/#{file}") - absolute_file_path = report['files'][0] - unless absolute_file_path.nil? - file_path = (ENV.has_key?('JENKINS_HOME')) ? absolute_file_path.split('SalesforceMobileSDK-iOS-PR/').last : absolute_file_path.split('SalesforceMobileSDK-iOS/').last - if git.modified_files.include?(file_path) || git.added_files.include?(file_path) - issues = report['diagnostics'] - for i in 0..issues.count-1 - unless issues[i].nil? - message << "#{file_path.split('/').last} | #{issues[i]['type']} | #{issues[i]['category']} | #{issues[i]['description']} | #{issues[i]['location']['line']} | #{issues[i]['location']['col']}\n" - end - end - end - end - end -end - -# Only print Static Analysis table if there are issues -if message.length > MAKRDOWN_LENGTH - warn('Static Analysis found an issue with one or more files you modified. Please fix the issue(s).') - markdown message -end - -# State what Library the test failures are for (or don't post at all). -markdown "# Tests results for #{ENV['LIB']}" unless status_report[:errors].empty? && status_report[:warnings].empty? diff --git a/.circleci/Dangerfile-PR.rb b/.circleci/Dangerfile-PR.rb deleted file mode 100644 index 30744e9821..0000000000 --- a/.circleci/Dangerfile-PR.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Warn when there is a big PR -warn('Big PR, try to keep changes smaller if you can') if git.lines_of_code > 1000 - -# Encourage writing up some reasoning about the PR, rather than just leaving a title -if github.pr_body.length < 3 - warn 'Please provide a summary in the Pull Request description' -end - -# Make it more obvious that a PR is a work in progress and shouldn't be merged yet. -has_wip_label = github.pr_labels.any? { |label| label.include? 'WIP' } -has_wip_title = github.pr_title.include? '[WIP]' -has_dnm_label = github.pr_labels.any? { |label| label.include? 'DO NOT MERGE' } -has_dnm_title = github.pr_title.include? '[DO NOT MERGE]' -if has_wip_label || has_wip_title - warn('PR is classed as Work in Progress') -end -if has_dnm_label || has_dnm_title - warn('At the authors request please DO NOT MERGE this PR') -end - -fail 'Please re-submit this PR to dev, we may have already fixed your issue.' if github.branch_for_base != 'dev' diff --git a/.circleci/Gemfile b/.circleci/Gemfile deleted file mode 100644 index 21978c76d5..0000000000 --- a/.circleci/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -source("https://rubygems.org") - -gem 'fastlane', '>= 2.195.0' -gem 'xcpretty' -gem 'danger' -gem 'danger-junit' -gem 'plist' -gem 'json', '>= 2.3.0' diff --git a/.circleci/Gemfile.lock b/.circleci/Gemfile.lock deleted file mode 100644 index cbe295deec..0000000000 --- a/.circleci/Gemfile.lock +++ /dev/null @@ -1,267 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.6) - rexml - addressable (2.8.4) - public_suffix (>= 2.0.2, < 6.0) - artifactory (3.0.15) - atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.785.0) - aws-sdk-core (3.178.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.651.0) - aws-sigv4 (~> 1.5) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.71.0) - aws-sdk-core (~> 3, >= 3.177.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.129.0) - aws-sdk-core (~> 3, >= 3.177.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.6) - aws-sigv4 (1.6.0) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - claide (1.1.0) - claide-plugins (0.9.2) - cork - nap - open4 (~> 1.3) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - cork (0.3.0) - colored2 (~> 3.1) - danger (8.6.1) - claide (~> 1.0) - claide-plugins (>= 0.9.2) - colored2 (~> 3.1) - cork (~> 0.1) - faraday (>= 0.9.0, < 2.0) - faraday-http-cache (~> 2.0) - git (~> 1.7) - kramdown (~> 2.3) - kramdown-parser-gfm (~> 1.0) - no_proxy_fix - octokit (~> 4.7) - terminal-table (>= 1, < 4) - danger-junit (1.0.2) - danger (> 2.0) - ox (~> 2.0) - declarative (0.0.20) - digest-crc (0.6.5) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - emoji_regex (3.2.3) - excon (0.100.0) - faraday (1.10.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-http-cache (2.5.0) - faraday (>= 0.8) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.2.7) - fastlane (2.213.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (>= 2.0.0, < 3.0.0) - naturally (~> 2.2) - optparse (~> 0.1.1) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - gh_inspector (1.1.3) - git (1.18.0) - addressable (~> 2.8) - rchardet (~> 1.8) - google-apis-androidpublisher_v3 (0.45.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-core (0.11.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-playcustomapp_v1 (0.13.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.19.0) - google-apis-core (>= 0.9.0, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.1) - google-cloud-storage (1.44.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.19.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.6.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.6.2) - json (2.6.3) - jwt (2.7.1) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - memoist (0.16.2) - mini_magick (4.12.0) - mini_mime (1.1.2) - multi_json (1.15.0) - multipart-post (2.3.0) - nanaimo (0.3.0) - nap (1.1.0) - naturally (2.2.1) - no_proxy_fix (0.1.2) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - open4 (1.3.4) - optparse (0.1.1) - os (1.1.4) - ox (2.14.14) - plist (3.7.0) - public_suffix (5.0.3) - rake (13.0.6) - rchardet (1.8.0) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.5) - rouge (2.0.7) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - security (0.1.3) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.10) - CFPropertyList - naturally - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.8.1) - word_wrap (1.0.0) - xcodeproj (1.22.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - ruby - x86_64-darwin-20 - x86_64-linux - -DEPENDENCIES - danger - danger-junit - fastlane (>= 2.195.0) - json (>= 2.3.0) - plist - xcpretty - -BUNDLED WITH - 2.3.26 diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c91969139f..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,169 +0,0 @@ -orbs: - codecov: codecov/codecov@4.1.0 - - -# Xcode version announcments can be found here: https://discuss.circleci.com/c/announcements/ -# Each post contains a full image manifest, including iOS runtimes, devices, CocoaPods version, etc. -anchors: - - &latest-xcode "16.1.0" - - &latest-ios "18.1" - - &min-ios "17.5" - - &requres-string "test << matrix.lib >> iOS 17.5" # Keep this min version up to date - - &device "iPhone-SE-3rd-generation" - - &invalid "" - -executors: - mac: - macos: - xcode: *latest-xcode - -version: 2.1 -jobs: - run-tests: - parameters: - lib: - type: string - default: "SalesforceSDKCommon" - nightly-test: - type: boolean - default: false - xcode: - type: string - default: *latest-xcode - ios: - type: string - default: *latest-ios - device: - type: string - default: *device - macos: - xcode: << parameters.xcode >> - resource_class: macos.m1.medium.gen1 - working_directory: ~/SalesforceMobileSDK-iOS - environment: - DEVICE: << parameters.device >> - IOS_VERSION: << parameters.ios >> - LIB: << parameters.lib >> - NIGHTLY_TEST: << parameters.nightly-test >> - FASTLANE_SKIP_UPDATE_CHECK: "true" - HOMEBREW_NO_AUTO_UPDATE: 1 - steps: - - checkout - - restore_cache: - keys: - - v1-gem-cahce-{{ checksum ".circleci/Gemfile.lock" }} - - v1-gem-cahce - - run: - name: Install Dependencies - command: | - npm install shelljs@0.8.5 - ./install.sh - ./build/pre-build - cd .circleci - bundle check || sudo bundle install --path vendor/bundle - brew install xcbeautify - mkdir -p ~/.gnupg - - save_cache: - key: v1-gem-cahce{{ checksum ".circleci/Gemfile.lock" }} - paths: - - vendor/bundle - - run: - name: Run Tests - command: | - cd .circleci - fastlane PR lib:<< parameters.lib >> - no_output_timeout: 20m - - run: - name: Danger Lib - command: | - danger --dangerfile=.circleci/Dangerfile-Lib.rb --danger_id="${LIB}" --verbose - background: true - when: always - - codecov/upload: - flags: << parameters.lib >> - - store_test_results: - path: /Users/distiller/SalesforceMobileSDK-iOS/test_output/ - - store_artifacts: - path: /Users/distiller/SalesforceMobileSDK-iOS/test_output/ - destination: Test-Results - - store_artifacts: - path: /Users/distiller/SalesforceMobileSDK-iOS/clangReport - destination: Static-Analysis - -# Potential parameters that can come from the project GUI Triggers -parameters: - xcode: - type: string - default: *invalid - ios: - type: string - default: *invalid - device: - type: string - default: *device - -workflows: - version: 2 - - build-test-pr: - when: - and: - - equal: [ "webhook", << pipeline.trigger_source >> ] - jobs: - - run-tests: - context: iOS Unit Tests - matrix: - parameters: - lib: ["SalesforceSDKCommon", "SalesforceAnalytics", "SalesforceSDKCore", "SmartStore", "MobileSync"] - filters: - branches: - only: - - /pull.*/ - - # Build everything at 10 PM PST Tuesday/Thursday Nights - run-tests: - when: - and: - - not: << pipeline.parameters.xcode >> - - not: - equal: [ "webhook", << pipeline.trigger_source >> ] - jobs: - - run-tests: - name: test << matrix.lib >> iOS << matrix.ios >> - context: iOS Unit Tests - matrix: - parameters: - lib: ["SalesforceSDKCommon", "SalesforceAnalytics", "SalesforceSDKCore", "SmartStore", "MobileSync"] - nightly-test: [true] - ios: [*min-ios] - - run-tests: - name: test << matrix.lib >> iOS << matrix.ios >> - context: iOS Unit Tests - matrix: - parameters: - lib: ["SalesforceSDKCommon", "SalesforceAnalytics", "SalesforceSDKCore", "SmartStore", "MobileSync"] - nightly-test: [true] - ios: [*latest-ios] - requires: - - *requres-string - - # Build everything at 11 PM PST Tuesday/Thursday Nights - run-tests-beta: - when: - and: - - << pipeline.parameters.xcode >> - - << pipeline.parameters.ios >> - - not: - equal: [ "webhook", << pipeline.trigger_source >> ] - jobs: - - run-tests: - name: test << matrix.lib >> iOS << matrix.ios >> - context: iOS Unit Tests - matrix: - parameters: - xcode: [<< pipeline.parameters.xcode >>] - ios: [<< pipeline.parameters.ios >>] - device: [<< pipeline.parameters.device >>] - lib: ["SalesforceSDKCommon", "SalesforceAnalytics", "SalesforceSDKCore", "SmartStore", "MobileSync"] - nightly-test: [true] - diff --git a/.circleci/fastlane/Fastfile b/.circleci/fastlane/Fastfile deleted file mode 100644 index 4e5a354947..0000000000 --- a/.circleci/fastlane/Fastfile +++ /dev/null @@ -1,127 +0,0 @@ -$git_pr_api = "https://api.github.com/repos/%s/SalesforceMobileSDK-iOS/pulls/%s/files" -$schemes = ['SalesforceSDKCommon', 'SalesforceAnalytics', 'SalesforceSDKCore', 'SmartStore', 'MobileSync'] -ENV['DEVICE'] = 'iPhone-SE-3rd-generation' unless ENV.has_key?('DEVICE') -ENV['IOS_VERSION'] = '17.2' unless ENV.has_key?('IOS_VERSION') - -lane :PR do |options| - lib_to_test = options[:lib] - Dir.chdir('../') - schemes = Set.new - - if (ENV.has_key?('NIGHTLY_TEST') and ENV['NIGHTLY_TEST'] == 'true') - UI.important "Nightly, skipping modified check." - test_scheme(lib_to_test) - else - # Check if this is a PR. - # Rebuilds of PR's don't have the CIRCLE_PULL_REQUEST key, so check the branch instead. - if ENV.has_key?('CIRCLE_BRANCH') && ENV['CIRCLE_BRANCH'].include?('pull/') - # No PR Number indicates that this PR is running in a CircleCI env linked to a fork, so force the url to forcedotcom project. - if ENV.has_key?('CIRCLE_PR_NUMBER') - pr_files_api = $git_pr_api % [ENV['CIRCLE_PROJECT_USERNAME'], ENV['CIRCLE_PR_NUMBER']] - else - pr_files_api = $git_pr_api % ['forcedotcom', ENV['CIRCLE_BRANCH'].split('/').last] - end - pull_files = `#{"curl %s" % [pr_files_api]}` - else - UI.error 'Not a PR on CircleCI, stopping stop execution now.' - `circleci step halt` - next - end - - # Determine which libs have been modified - pr_files = JSON.parse(pull_files) - for pr_file in pr_files - path = pr_file['filename'] - $schemes.each do |scheme| - if path.include? scheme - schemes.merge($schemes[$schemes.index(scheme)..]) - break - end - end - end - UI.important "Schemes to test: " + schemes.to_a().join(',') - - if schemes.include? lib_to_test - test_scheme(lib_to_test) - else - UI.important "Lib #{lib_to_test} not modified by this PR, no need to test." - `circleci step halt` - end - end -end - -lane :LCL do - Dir.chdir('../') - index = 1 - puts 'Select scheme: ' - for scheme in $schemes - puts index.to_s + ': ' + scheme - index = index + 1 - end - - print 'Just enter a number or name: ' - selection = STDIN.gets.strip - - if $schemes.include? selection - test_scheme(selection) - # Not the best error handling, but sufficient for catching typos - elsif selection.to_i > 0 and selection.to_i <= $schemes.count - test_scheme($schemes[selection.to_i - 1]) - else - UI.user_error!('Invalid test selection.') - end -end - -lane :CI do - Dir.chdir('../') - test_scheme('UnitTests') -end - -def test_scheme(scheme) - analyze_scheme(scheme) - - device = ENV['DEVICE'].gsub(' ', '-') - ios_code = ENV['IOS_VERSION'].gsub('.', '-') - system('xcrun simctl delete test_device') or true - sim_id = `xcrun simctl create test_device com.apple.CoreSimulator.SimDeviceType.#{device} com.apple.CoreSimulator.SimRuntime.iOS-#{ios_code}`.delete("\n") - ios_version = `xcrun xctrace list devices | grep test_device | awk -F"[()]" '{print $2}'`.delete("\n") - - if (ios_version.empty?) - UI.user_error!('Invalid Test Device.') - end - - begin - scan( - workspace: 'SalesforceMobileSDK.xcworkspace', - scheme: scheme, - device: "test_device (#{ios_version})", - output_directory: 'test_output', - output_types: 'html,junit', - code_coverage: true, - skip_build: true, - number_of_retries: 1, - xcodebuild_formatter: "xcbeautify" - ) - ensure - system("mv ../test_output/report.html ../test_output/#{scheme}_results.html") - end -end - -def analyze_scheme(scheme) - begin - xcodebuild( - xcargs: 'CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR=./clangReport RUN_CLANG_STATIC_ANALYZER=YES ARCHS=x86_64', - workspace: 'SalesforceMobileSDK.xcworkspace', - scheme: scheme, - sdk: 'iphonesimulator', - ) - ensure - #move clangReports to one folder - system('mkdir -p ../clangReport/StaticAnalyzer') - system('mv ../libs/SalesforceSDKCommon/clangReport/StaticAnalyzer/SalesforceSDKCommon ../clangReport/StaticAnalyzer/') - system('mv ../libs/SalesforceAnalytics/clangReport/StaticAnalyzer/SalesforceAnalytics ../clangReport/StaticAnalyzer/') - system('mv ../libs/SalesforceSDKCore/clangReport/StaticAnalyzer/SalesforceSDKCore ../clangReport/StaticAnalyzer/') - system('mv ../libs/SmartStore/clangReport/StaticAnalyzer/SmartStore ../clangReport/StaticAnalyzer/') - system('mv ../libs/MobileSync/clangReport/StaticAnalyzer/MobileSync ../clangReport/StaticAnalyzer/') - end -end \ No newline at end of file diff --git a/readme.md b/readme.md index 2f0b161bb5..4d9a3885ff 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ # Salesforce.com Mobile SDK for iOS -[![CircleCI](https://circleci.com/gh/forcedotcom/SalesforceMobileSDK-iOS/tree/dev.svg?style=svg)](https://circleci.com/gh/forcedotcom/SalesforceMobileSDK-iOS/tree/dev) +[![Nightly Tests](https://github.com/forcedotcom/SalesforceMobileSDK-iOS/actions/workflows/nightly.yaml/badge.svg)](https://github.com/forcedotcom/SalesforceMobileSDK-iOS/actions/workflows/nightly.yaml) [![Known Vulnerabilities](https://snyk.io/test/github/forcedotcom/SalesforceMobileSDK-iOS/badge.svg)](https://snyk.io/test/github/forcedotcom/SalesforceMobileSDK-iOS) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/forcedotcom/SalesforceMobileSDK-iOS?sort=semver)