Fastlane TestFlight (#1179)

* init

* get last values from secrets
This commit is contained in:
Ethan Pippin 2024-08-13 13:37:46 -06:00 committed by GitHub
parent 7f023b3830
commit 32f23ff295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 170 additions and 18 deletions

70
.github/workflows/testflight.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: "TestFlight ⚙️"
on:
repository_dispatch:
types: [testflight]
jobs:
test_build:
runs-on: macos-14
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: git Checkout
if: ${{ github.event.client_payload.hasCheckoutCommit }}
run: |
git checkout ${{ github.event.client_payload.commitSHA }}
- run: 'echo "$SSH_KEY" > key'
shell: bash
env:
SSH_KEY: ${{secrets.P12_PASSWORD}}
- name: Install the Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: "CI_PASSWORD"
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
sudo mkdir -p /Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
sudo cp $PP_PATH /Library/MobileDevice/Provisioning\ Profiles
- name: Build and Upload to TestFlight
run: sudo fastlane testFlightLane \
keyID:${{ secrets.APP_STORE_KEY_ID }} \
issuerID:${{ secrets.APP_STORE_ISSUER_ID }} \
keyContents:${{ secrets.APP_STORE_KEY_CONTENTS }} \
scheme:${{ github.event.client_payload.scheme }} \
codeSign64:${{ secrets.CODE_SIGN_64 }} \
profileName64:${{ secrets.PROFILE_NAME_64 }} \
xcodeVersion:${{ github.event.client_payload.xcodeVersion }} \
build:${{ github.event.client_payload.build }} \
version:${{ github.event.client_payload.version }}

View File

@ -63,6 +63,82 @@ class Fastfile: LaneFile {
) )
} }
// MARK: TestFlight
// TODO: verify tvOS
func testFlightLane(withOptions options: [String: String]?) {
guard let options,
let keyID = options["keyID"],
let issuerID = options["issuerID"],
let keyContents = options["keyContents"],
let scheme = options["scheme"],
let codeSign64 = options["codeSign64"],
let profileName64 = options["profileName64"]
else {
puts(message: "ERROR: missing or incorrect options")
exit(1)
}
guard let decodedCodeSignIdentity = decodeBase64(encoded: codeSign64) else {
puts(message: "ERROR: code sign identity not valid base 64")
exit(1)
}
guard let profileName = decodeBase64(encoded: profileName64) else {
puts(message: "ERROR: profile name not valid base 64")
exit(1)
}
if let xcodeVersion = options["xcodeVersion"] {
xcodes(version: xcodeVersion)
}
appStoreConnectApiKey(
keyId: keyID,
issuerId: .userDefined(issuerID),
keyContent: .userDefined(keyContents),
isKeyContentBase64: true,
duration: 1200,
inHouse: false
)
updateCodeSigningSettings(
path: "Swiftfin.xcodeproj",
useAutomaticSigning: false,
codeSignIdentity: .userDefined(decodedCodeSignIdentity),
profileName: .userDefined(profileName)
)
if let version = options["version"] {
incrementVersionNumber(
versionNumber: .userDefined(version),
xcodeproj: "Swiftfin.xcodeproj"
)
}
if let build = options["build"] {
incrementBuildNumber(
buildNumber: .userDefined(build),
xcodeproj: "Swiftfin.xcodeproj"
)
} else {
incrementBuildNumber(
xcodeproj: "Swiftfin.xcodeproj"
)
}
buildApp(
scheme: .userDefined(scheme),
skipArchive: .userDefined(false),
skipProfileDetection: false
)
uploadToTestflight(
ipa: "Swiftfin.ipa"
)
}
// MARK: Utilities // MARK: Utilities
private func decodeBase64(encoded: String) -> String? { private func decodeBase64(encoded: String) -> String? {

View File

@ -95,7 +95,7 @@ public protocol DeliverfileProtocol: AnyObject {
/// Path to the app rating's config /// Path to the app rating's config
var appRatingConfigPath: String? { get } var appRatingConfigPath: String? { get }
/// Extra information for the submission (e.g. compliance specifications, IDFA settings) /// Extra information for the submission (e.g. compliance specifications)
var submissionInformation: [String: Any]? { get } var submissionInformation: [String: Any]? { get }
/// The ID of your App Store Connect team if you're in multiple teams /// The ID of your App Store Connect team if you're in multiple teams
@ -272,4 +272,4 @@ public extension DeliverfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.124] // FastlaneRunnerAPIVersion [0.9.127]

View File

@ -190,7 +190,7 @@ public func appStoreBuildNumber(apiKeyPath: OptionalConfigValue<String?> = .fast
- parameters: - parameters:
- keyId: The key ID - keyId: The key ID
- issuerId: The issuer ID - issuerId: The issuer ID. It can be nil if the key is individual API key
- keyFilepath: The path to the key p8 file - keyFilepath: The path to the key p8 file
- keyContent: The content of the key p8 file - keyContent: The content of the key p8 file
- isKeyContentBase64: Whether :key_content is Base64 encoded or not - isKeyContentBase64: Whether :key_content is Base64 encoded or not
@ -201,7 +201,7 @@ public func appStoreBuildNumber(apiKeyPath: OptionalConfigValue<String?> = .fast
Load the App Store Connect API token to use in other fastlane tools and actions Load the App Store Connect API token to use in other fastlane tools and actions
*/ */
public func appStoreConnectApiKey(keyId: String, public func appStoreConnectApiKey(keyId: String,
issuerId: String, issuerId: OptionalConfigValue<String?> = .fastlaneDefault(nil),
keyFilepath: OptionalConfigValue<String?> = .fastlaneDefault(nil), keyFilepath: OptionalConfigValue<String?> = .fastlaneDefault(nil),
keyContent: OptionalConfigValue<String?> = .fastlaneDefault(nil), keyContent: OptionalConfigValue<String?> = .fastlaneDefault(nil),
isKeyContentBase64: OptionalConfigValue<Bool> = .fastlaneDefault(false), isKeyContentBase64: OptionalConfigValue<Bool> = .fastlaneDefault(false),
@ -210,7 +210,7 @@ public func appStoreConnectApiKey(keyId: String,
setSpaceshipToken: OptionalConfigValue<Bool> = .fastlaneDefault(true)) setSpaceshipToken: OptionalConfigValue<Bool> = .fastlaneDefault(true))
{ {
let keyIdArg = RubyCommand.Argument(name: "key_id", value: keyId, type: nil) let keyIdArg = RubyCommand.Argument(name: "key_id", value: keyId, type: nil)
let issuerIdArg = RubyCommand.Argument(name: "issuer_id", value: issuerId, type: nil) let issuerIdArg = issuerId.asRubyArgument(name: "issuer_id", type: nil)
let keyFilepathArg = keyFilepath.asRubyArgument(name: "key_filepath", type: nil) let keyFilepathArg = keyFilepath.asRubyArgument(name: "key_filepath", type: nil)
let keyContentArg = keyContent.asRubyArgument(name: "key_content", type: nil) let keyContentArg = keyContent.asRubyArgument(name: "key_content", type: nil)
let isKeyContentBase64Arg = isKeyContentBase64.asRubyArgument(name: "is_key_content_base64", type: nil) let isKeyContentBase64Arg = isKeyContentBase64.asRubyArgument(name: "is_key_content_base64", type: nil)
@ -674,7 +674,7 @@ public func appledoc(input: [String],
- resetRatings: Reset the summary rating when you release a new version of the application - resetRatings: Reset the summary rating when you release a new version of the application
- priceTier: The price tier of this application - priceTier: The price tier of this application
- appRatingConfigPath: Path to the app rating's config - appRatingConfigPath: Path to the app rating's config
- submissionInformation: Extra information for the submission (e.g. compliance specifications, IDFA settings) - submissionInformation: Extra information for the submission (e.g. compliance specifications)
- teamId: The ID of your App Store Connect team if you're in multiple teams - teamId: The ID of your App Store Connect team if you're in multiple teams
- teamName: The name of your App Store Connect team if you're in multiple teams - teamName: The name of your App Store Connect team if you're in multiple teams
- devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID! - devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID!
@ -3714,7 +3714,7 @@ public func deleteKeychain(name: OptionalConfigValue<String?> = .fastlaneDefault
- resetRatings: Reset the summary rating when you release a new version of the application - resetRatings: Reset the summary rating when you release a new version of the application
- priceTier: The price tier of this application - priceTier: The price tier of this application
- appRatingConfigPath: Path to the app rating's config - appRatingConfigPath: Path to the app rating's config
- submissionInformation: Extra information for the submission (e.g. compliance specifications, IDFA settings) - submissionInformation: Extra information for the submission (e.g. compliance specifications)
- teamId: The ID of your App Store Connect team if you're in multiple teams - teamId: The ID of your App Store Connect team if you're in multiple teams
- teamName: The name of your App Store Connect team if you're in multiple teams - teamName: The name of your App Store Connect team if you're in multiple teams
- devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID! - devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID!
@ -4096,7 +4096,7 @@ public func downloadAppPrivacyDetailsFromAppStore(username: String,
- appIdentifier: The bundle identifier of your app - appIdentifier: The bundle identifier of your app
- teamId: The ID of your App Store Connect team if you're in multiple teams - teamId: The ID of your App Store Connect team if you're in multiple teams
- teamName: The name of your App Store Connect team if you're in multiple teams - teamName: The name of your App Store Connect team if you're in multiple teams
- platform: The app platform for dSYMs you wish to download (ios, appletvos) - platform: The app platform for dSYMs you wish to download (ios, xros, appletvos)
- version: The app version for dSYMs you wish to download, pass in 'latest' to download only the latest build's dSYMs or 'live' to download only the live version dSYMs - version: The app version for dSYMs you wish to download, pass in 'latest' to download only the latest build's dSYMs or 'live' to download only the live version dSYMs
- buildNumber: The app build_number for dSYMs you wish to download - buildNumber: The app build_number for dSYMs you wish to download
- minVersion: The minimum app version for dSYMs you wish to download - minVersion: The minimum app version for dSYMs you wish to download
@ -9707,7 +9707,7 @@ public func setBuildNumberRepository(useHgRevisionNumber: OptionalConfigValue<Bo
- changelog: Changelog text that should be uploaded to App Store Connect - changelog: Changelog text that should be uploaded to App Store Connect
- teamId: The ID of your App Store Connect team if you're in multiple teams - teamId: The ID of your App Store Connect team if you're in multiple teams
- teamName: The name of your App Store Connect team if you're in multiple teams - teamName: The name of your App Store Connect team if you're in multiple teams
- platform: The platform of the app (ios, appletvos, mac) - platform: The platform of the app (ios, appletvos, xros, mac)
This is useful if you have only one changelog for all languages. This is useful if you have only one changelog for all languages.
You can store the changelog in `./changelog.txt` and it will automatically get loaded from there. This integration is useful if you support e.g. 10 languages and want to use the same "What's new"-text for all languages. You can store the changelog in `./changelog.txt` and it will automatically get loaded from there. This integration is useful if you support e.g. 10 languages and want to use the same "What's new"-text for all languages.
@ -10938,6 +10938,7 @@ public func splunkmint(dsym: OptionalConfigValue<String?> = .fastlaneDefault(nil
- xcprettyOutput: Specifies the output type for xcpretty. eg. 'test', or 'simple' - xcprettyOutput: Specifies the output type for xcpretty. eg. 'test', or 'simple'
- xcprettyArgs: Pass in xcpretty additional command line arguments (e.g. '--test --no-color' or '--tap --no-utf'), requires xcpretty_output to be specified also - xcprettyArgs: Pass in xcpretty additional command line arguments (e.g. '--test --no-color' or '--tap --no-utf'), requires xcpretty_output to be specified also
- verbose: Increase verbosity of informational output - verbose: Increase verbosity of informational output
- veryVerbose: Increase verbosity to include debug output
- simulator: Specifies the simulator to pass for Swift Compiler (one of: iphonesimulator, macosx) - simulator: Specifies the simulator to pass for Swift Compiler (one of: iphonesimulator, macosx)
- simulatorArch: Specifies the architecture of the simulator to pass for Swift Compiler (one of: x86_64, arm64). Requires the simulator option to be specified also, otherwise, it's ignored - simulatorArch: Specifies the architecture of the simulator to pass for Swift Compiler (one of: x86_64, arm64). Requires the simulator option to be specified also, otherwise, it's ignored
*/ */
@ -10953,6 +10954,7 @@ public func spm(command: String = "build",
xcprettyOutput: OptionalConfigValue<String?> = .fastlaneDefault(nil), xcprettyOutput: OptionalConfigValue<String?> = .fastlaneDefault(nil),
xcprettyArgs: OptionalConfigValue<String?> = .fastlaneDefault(nil), xcprettyArgs: OptionalConfigValue<String?> = .fastlaneDefault(nil),
verbose: OptionalConfigValue<Bool> = .fastlaneDefault(false), verbose: OptionalConfigValue<Bool> = .fastlaneDefault(false),
veryVerbose: OptionalConfigValue<Bool> = .fastlaneDefault(false),
simulator: OptionalConfigValue<String?> = .fastlaneDefault(nil), simulator: OptionalConfigValue<String?> = .fastlaneDefault(nil),
simulatorArch: String = "arm64") simulatorArch: String = "arm64")
{ {
@ -10968,6 +10970,7 @@ public func spm(command: String = "build",
let xcprettyOutputArg = xcprettyOutput.asRubyArgument(name: "xcpretty_output", type: nil) let xcprettyOutputArg = xcprettyOutput.asRubyArgument(name: "xcpretty_output", type: nil)
let xcprettyArgsArg = xcprettyArgs.asRubyArgument(name: "xcpretty_args", type: nil) let xcprettyArgsArg = xcprettyArgs.asRubyArgument(name: "xcpretty_args", type: nil)
let verboseArg = verbose.asRubyArgument(name: "verbose", type: nil) let verboseArg = verbose.asRubyArgument(name: "verbose", type: nil)
let veryVerboseArg = veryVerbose.asRubyArgument(name: "very_verbose", type: nil)
let simulatorArg = simulator.asRubyArgument(name: "simulator", type: nil) let simulatorArg = simulator.asRubyArgument(name: "simulator", type: nil)
let simulatorArchArg = RubyCommand.Argument(name: "simulator_arch", value: simulatorArch, type: nil) let simulatorArchArg = RubyCommand.Argument(name: "simulator_arch", value: simulatorArch, type: nil)
let array: [RubyCommand.Argument?] = [commandArg, let array: [RubyCommand.Argument?] = [commandArg,
@ -10982,6 +10985,7 @@ public func spm(command: String = "build",
xcprettyOutputArg, xcprettyOutputArg,
xcprettyArgsArg, xcprettyArgsArg,
verboseArg, verboseArg,
veryVerboseArg,
simulatorArg, simulatorArg,
simulatorArchArg] simulatorArchArg]
let args: [RubyCommand.Argument] = array let args: [RubyCommand.Argument] = array
@ -12536,7 +12540,7 @@ public func uploadSymbolsToSentry(apiHost: String = "https://app.getsentry.com/a
- resetRatings: Reset the summary rating when you release a new version of the application - resetRatings: Reset the summary rating when you release a new version of the application
- priceTier: The price tier of this application - priceTier: The price tier of this application
- appRatingConfigPath: Path to the app rating's config - appRatingConfigPath: Path to the app rating's config
- submissionInformation: Extra information for the submission (e.g. compliance specifications, IDFA settings) - submissionInformation: Extra information for the submission (e.g. compliance specifications)
- teamId: The ID of your App Store Connect team if you're in multiple teams - teamId: The ID of your App Store Connect team if you're in multiple teams
- teamName: The name of your App Store Connect team if you're in multiple teams - teamName: The name of your App Store Connect team if you're in multiple teams
- devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID! - devPortalTeamId: The short ID of your Developer Portal team, if you're in multiple teams. Different from your iTC team ID!
@ -13594,7 +13598,7 @@ public func xcov(workspace: OptionalConfigValue<String?> = .fastlaneDefault(nil)
coverallsServiceJobId: OptionalConfigValue<String?> = .fastlaneDefault(nil), coverallsServiceJobId: OptionalConfigValue<String?> = .fastlaneDefault(nil),
coverallsRepoToken: OptionalConfigValue<String?> = .fastlaneDefault(nil), coverallsRepoToken: OptionalConfigValue<String?> = .fastlaneDefault(nil),
xcconfig: OptionalConfigValue<String?> = .fastlaneDefault(nil), xcconfig: OptionalConfigValue<String?> = .fastlaneDefault(nil),
ideFoundationPath: String = "/Applications/Xcode-15.3.0.app/Contents/Developer/../Frameworks/IDEFoundation.framework/Versions/A/IDEFoundation", ideFoundationPath: String = "/Applications/Xcode_15.4.app/Contents/Developer/../Frameworks/IDEFoundation.framework/Versions/A/IDEFoundation",
legacySupport: OptionalConfigValue<Bool> = .fastlaneDefault(false)) legacySupport: OptionalConfigValue<Bool> = .fastlaneDefault(false))
{ {
let workspaceArg = workspace.asRubyArgument(name: "workspace", type: nil) let workspaceArg = workspace.asRubyArgument(name: "workspace", type: nil)
@ -13797,4 +13801,4 @@ public let snapshotfile: Snapshotfile = .init()
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.177] // FastlaneRunnerAPIVersion [0.9.180]

View File

@ -404,6 +404,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
@ -416,6 +417,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0; SWIFT_VERSION = 4.0;
}; };

View File

@ -208,4 +208,4 @@ public extension GymfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.127] // FastlaneRunnerAPIVersion [0.9.130]

View File

@ -228,4 +228,4 @@ public extension MatchfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.121] // FastlaneRunnerAPIVersion [0.9.124]

View File

@ -52,4 +52,4 @@ public extension PrecheckfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.120] // FastlaneRunnerAPIVersion [0.9.123]

View File

@ -320,4 +320,4 @@ public extension ScanfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.132] // FastlaneRunnerAPIVersion [0.9.135]

View File

@ -96,4 +96,4 @@ public extension ScreengrabfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.122] // FastlaneRunnerAPIVersion [0.9.125]

View File

@ -204,4 +204,4 @@ public extension SnapshotfileProtocol {
// Please don't remove the lines below // Please don't remove the lines below
// They are used to detect outdated files // They are used to detect outdated files
// FastlaneRunnerAPIVersion [0.9.116] // FastlaneRunnerAPIVersion [0.9.119]