├── .gitignore ├── LICENSE ├── Readme.MD └── action.yml /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | ### macOS Patch ### 31 | # iCloud generated files 32 | *.icloud 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Microsoft Corporation 5 | Copyright (c) 2018 GitHub, Inc. and contributors 6 | Copyright (c) 2020 Itty Bitty Apps Pty Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /Readme.MD: -------------------------------------------------------------------------------- 1 | 2 | # XCode-Deploy 1.5 3 | This action will archive, export, and upload your project or workspace to App Store Connect (TestFlight). 4 | It is designed to run on a containerized VM, such as a GitHub Hosted Runner. 5 | If self-hosting, some of these steps may be unnecessary or redundant. 6 | 7 | ## Inputs 8 | 9 | ### `xcode-version` 10 | 11 | The version of XCode to use, in SemVer. Default is latest stable release: `xcode-latest`. See also [setup-xcode][0]. 12 | 13 | ### `configuration` 14 | 15 | The configuration to archive. Default is `Release`. See also `xcodebuild`'s `-configuration`. 16 | 17 | ### `scheme` 18 | 19 | The scheme to archive. If left blank, the action will search the project or workspace file for a default scheme. See also `xcodebuild`'s `-scheme`. 20 | 21 | ### `path-to-export-options` 22 | 23 | The path to the `ExportOptions.plist` file, required for `-exportArchive`. Will default to `ExportOptions.plist` in the `${{ github.workspace }}` directory. See also `xcodebuild`'s `-exportArchive`. 24 | 25 | ### `update-build` 26 | 27 | A boolean value that will set the `version-number` to the commit depth. If false, nothing happens. See also `agvtool`'s `-new-version`. 28 | 29 | ### `install-pods` 30 | 31 | A boolean value that will run `pod install` if true. If false, nothing happens. 32 | 33 | ### `resolve-package-dependencies` 34 | 35 | A boolean value that will run `xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .` if true. If false, nothing happens. 36 | 37 | ### `distribution-certificate-p12` 38 | 39 | The `base64` representation of your Apple/iOS Distribution Certificate and private key pair. 40 | 41 | ### `distribution-certificate-password` 42 | 43 | The password to unlock the Distribution Certificate and private key pair. 44 | 45 | ### `app-store-provisioning-profile` 46 | 47 | The `base64` representation of your App Store Provisioning Profile. 48 | 49 | ### `auth-key-id` 50 | 51 | The ID of the Authentication Key provided by App Store Connect. 52 | 53 | ### `auth-key-issuer-id` 54 | 55 | The Issuer ID provided by App Store Connect. 56 | 57 | ### `auth-key-p8` 58 | 59 | The `base64` representation of the private key provided by App Store Connect. 60 | 61 | Additionally, an `ExportOptions.plist` file must be included in the repository at the location and filename that matches `path-to-export-options`. 62 | You can generate one of these by doing a local export in XCode and then copy it into your repository, or use `/usr/libexec/PlistBuddy`. 63 | 64 | ## Sample Usage 65 | ```yml 66 | - name: Deploy 67 | uses: vfrascello/xcode-deploy@v1.5 68 | with: 69 | xcode-version: '14.0' 70 | configuration: 'Release' 71 | scheme: 'MyScheme' 72 | path-to-export-options: 'ExportOptions.plist' 73 | update-build: true 74 | install-pods: false 75 | resolve-package-dependencies: true 76 | distribution-certificate-p12: ${{ secrets.DISTRIBUTION_CERTIFICATE_P12 }} 77 | distribution-certificate-password: ${{ secrets.DISTRIBUTION_CERTIFICATE_PASSWORD }} 78 | app-store-provisioning-profile: ${{ secrets.APPSTORE_PROVISIONING_PROFILE}} 79 | auth-key-id: ${{ secrets.AUTH_KEY_ID }} 80 | auth-key-issuer-id: ${{ secrets.AUTH_KEY_ISSUER_ID }} 81 | auth-key-p8: ${{ secrets.AUTH_KEY_P8 }} 82 | ``` 83 | 84 | See [action.yml][1] for more details. 85 | 86 | ## Contributions 87 | 88 | This composite action was written by me, Vincent Frascello, but uses actions by other open-source contributors: 89 | 90 | [Maxim Lobanov][0] 91 | 92 | [Oliver Jones][2] 93 | 94 | [Florian Fried][3] 95 | 96 | [Akio Jinsenji][4] 97 | 98 | [Github Actions Team][5] 99 | 100 | ## License 101 | Any contributions made under this project will be governed by the [MIT License][6]. 102 | 103 | [0]: https://github.com/maxim-lobanov/setup-xcode 104 | [1]: https://github.com/vfrascello/xcode-deploy/blob/main/action.yml 105 | [2]: https://github.com/orj 106 | [3]: https://github.com/ffried 107 | [4]: https://github.com/akiojin 108 | [5]: https://github.com/actions 109 | [6]: https://github.com/vfrascello/xcode-deploy/blob/main/LICENSE 110 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'XCode-Deploy' 2 | description: 'Archive, Export, and Upload a build to App Store Connect (TestFlight)' 3 | author: 'Vincent Frascello' 4 | branding: 5 | icon: 'upload-cloud' 6 | color: 'yellow' 7 | 8 | inputs: 9 | xcode-version: 10 | description: 'The version of XCode to use. Defaults to the latest stable version.' 11 | requred: false 12 | default: 'latest-stable' 13 | configuration: 14 | description: 'Configuration (default: Release)' 15 | required: true 16 | default: 'Release' 17 | scheme: 18 | description: 'Leave blank and Action will search for default scheme.' 19 | required: false 20 | default: '' 21 | path-to-export-options: 22 | description: 'Relative path and filename to ExportOptions.plist' 23 | required: true 24 | default: 'ExportOptions.plist' 25 | update-build: 26 | description: 'Sets Build Number to # of commits.' 27 | required: true 28 | default: true 29 | type: choice 30 | options: 31 | - true 32 | - false 33 | install-pods: 34 | description: 'Run Pod Install' 35 | required: true 36 | default: false 37 | type: choice 38 | options: 39 | - true 40 | - false 41 | resolve-package-dependencies: 42 | description: 'Resolve Package Dependencies' 43 | required: true 44 | default: false 45 | type: choice 46 | options: 47 | - true 48 | - false 49 | distribution-certificate-p12: 50 | description: 'base64 representation of the distribution certificate.' 51 | required: true 52 | distribution-certificate-password: 53 | description: 'password to unlock the distribution certificate.' 54 | required: true 55 | app-store-provisioning-profile: 56 | description: 'base64 representation of the provisioning profile.' 57 | required: true 58 | auth-key-id: 59 | description: 'App Store Connect API Auth Key ID.' 60 | required: true 61 | auth-key-issuer-id: 62 | description: 'App Store Connect API Issuer ID.' 63 | required: true 64 | auth-key-p8: 65 | description: 'base64 representation of the App Store Connect AuthKey.' 66 | required: true 67 | 68 | runs: 69 | using: 'composite' 70 | steps: 71 | - name: Sanitize input 72 | shell: bash 73 | env: 74 | SCHEME: ${{ inputs.scheme }} 75 | CONFIGURATION: ${{ inputs.configuration }} 76 | PATH_TO_EXPORT_OPTIONS: ${{ inputs.path-to-export-options }} 77 | run: | 78 | echo "[XCode-Deploy]: Checking Input for invalid characters..." 79 | if [[ "$SCHEME" == ${SCHEME//[^a-zA-Z0-9_\.- ]/} ]] && \ 80 | [[ "$CONFIGURATION" == ${CONFIGURATION//[^a-zA-Z0-9_\.- ]/} ]] && \ 81 | [[ "$PATH_TO_EXPORT_OPTIONS" == ${PATH_TO_EXPORT_OPTIONS//^[a-zA-Z0-9](?:[a-zA-Z0-9 ._-]*[a-zA-Z0-9])?\.[a-zA-Z0-9_-]+$/} ]]; then 82 | echo "Inputs OK" 83 | exit 0 84 | else 85 | echo "Bad Inputs" 86 | exit 1 87 | fi 88 | - name: Checkout 89 | uses: actions/checkout@v3 90 | with: 91 | fetch-depth: 0 92 | - name: Determine File To Build 93 | shell: bash 94 | run: | 95 | echo "[XCode-Deploy]: Determining file to build..." 96 | if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" \ 97 | && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; \ 98 | else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi 99 | file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` 100 | echo "TYPE=$filetype_parameter" >> $GITHUB_ENV 101 | echo "FILE_TO_BUILD=$file_to_build" >> $GITHUB_ENV 102 | echo "PROJECT_NAME=$(echo "$file_to_build" | cut -f 1 -d '.')" >> $GITHUB_ENV 103 | - name: Setup Pods 104 | if: inputs.install-pods == 'true' 105 | shell: bash 106 | run: | 107 | echo "[XCode-Deploy]: Installing Pods..." 108 | pod install 109 | - name: Resolve Package Dependencies 110 | if: inputs.resolve-package-dependencies == 'true' 111 | shell: bash 112 | run: | 113 | echo "[XCode-Deploy]: Resolving Package Dependencies..." 114 | xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath . 115 | - name: Setup Scheme 116 | shell: bash 117 | run: | 118 | echo "[XCode-Deploy]: Searching for default Scheme..." 119 | if [ "${{ inputs.scheme }}" == "" ]; then 120 | scheme_list=$(xcodebuild -list -json | tr -d "\n") 121 | scheme=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") 122 | echo $scheme | cat >scheme 123 | echo "[XCode-Deploy]: Using default scheme: $scheme..." 124 | else 125 | echo "[XCode-Deploy]: Using provided Scheme: ${{ inputs.scheme }}" 126 | scheme=${{ inputs.scheme }} 127 | fi 128 | echo "SCHEME=$scheme" >> $GITHUB_ENV 129 | - name: Import Certificates 130 | uses: apple-actions/import-codesign-certs@v1 131 | id: codesign 132 | with: 133 | p12-file-base64: ${{ inputs.distribution-certificate-p12 }} 134 | p12-password: ${{ inputs.distribution-certificate-password }} 135 | keychain: codesign 136 | - name: Install App Store Profile 137 | uses: akiojin/install-provisioning-profile-github-action@v1.0 138 | with: 139 | base64: ${{ inputs.app-store-provisioning-profile }} 140 | - name: Select Xcode 141 | uses: maxim-lobanov/setup-xcode@v1.4.1 142 | with: 143 | xcode-version: ${{ inputs.xcode-version }} 144 | - name: Increment Build Number 145 | shell: bash 146 | if: inputs.update-build == 'true' 147 | run: | 148 | echo "[XCode-Deploy]: Updating Build Number to commit depth..." 149 | count=`git rev-list --count HEAD` 150 | xcrun agvtool new-version -all $count 151 | - name: Build and Archive 152 | uses: sersoft-gmbh/xcodebuild-action@v2 153 | with: 154 | action: archive 155 | ${{ env.TYPE }}: ${{ env.FILE_TO_BUILD }} 156 | scheme: ${{ env.SCHEME }} 157 | sdk: iphoneos 158 | build-settings: > 159 | -archivePath ${{ env.PROJECT_NAME }}.xcarchive 160 | derived-data-path: build/derivedData 161 | destination: generic/platform=iOS 162 | configuration: ${{ inputs.configuration }} 163 | - name: Get App Store Connect API Key 164 | uses: timheuer/base64-to-file@v1.1 165 | with: 166 | fileName: AuthKey_${{ inputs.auth-key-id }}.p8 167 | fileDir: ${{ github.workspace }}/private_keys 168 | encodedString: ${{ inputs.auth-key-p8 }} 169 | - name: Export Xcode archive 170 | shell: bash 171 | run: | 172 | echo "[XCode-Deploy]: Exporting archive using xcodebuild..." 173 | xcodebuild -exportArchive -verbose \ 174 | -sdk iphoneos \ 175 | -archivePath ${{ github.workspace }}/${{ env.PROJECT_NAME }}.xcarchive \ 176 | -exportOptionsPlist ${{ github.workspace }}/${{ inputs.path-to-export-options }} \ 177 | -exportPath ${{ github.workspace }} \ -authenticationKeyIssuerID ${{ inputs.auth-key-issuer-id }} \ 178 | -authenticationKeyID ${{ inputs.auth-key-id }} \ 179 | -authenticationKeyPath ${{ github.workspace }}/private_keys/AuthKey_${{ inputs.auth-key-id }}.p8 \ 180 | - name: Upload to App Store Connect 181 | shell: bash 182 | run: | 183 | echo "[XCode-Deploy]: Uploading archive using altool..." 184 | xcrun altool --upload-app -f ${{ github.workspace }}/${{ env.PROJECT_NAME }}.ipa -t iOS \ 185 | --apiIssuer ${{ inputs.auth-key-issuer-id }} --apiKey ${{ inputs.auth-key-id }} 186 | - name: Cleanup 187 | shell: bash 188 | run: | 189 | echo "[XCode-Deploy]: Removing Keychain and private_keys folder..." 190 | security delete-keychain codesign.keychain 191 | rm -rf ${{ github.workspace }}/private_keys || true --------------------------------------------------------------------------------