Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[Do iOS '24] Ship your app on a Friday...and en...

[Do iOS '24] Ship your app on a Friday...and enjoy your weekend!

Pol Piella Abadia

November 17, 2024
Tweet

More Decks by Pol Piella Abadia

Other Decks in Programming

Transcript

  1. Ship your app on a Friday and enjoy your weekend

    @POLPIELLADEV @[email protected] ! Do iOS 2024 /IN/POLPIELLADEV
  2. desc "Distributes a new build to App Store Connect" lane

    :distribute do version_number = git_branch.split("/", -1).last setup_ci xcode_select("/Applications/Xcode_16.app") sync_code_signing( type: "appstore", app_identifier: ["com.appdiggershq.fosi"], readonly: true ) increment_version_number(version_number: version_number, xcodeproj: "Fosi.xcodeproj") app_store_connect_api_key( key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"], issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"], key_content: ENV["APP_STORE_CONNECT_KEY_CONTENT"] ) build_number = latest_testflight_build_number + 1 increment_build_number(xcodeproj: "Fosi.xcodeproj", build_number: build_number) build_app(scheme: "Fosi", configuration: 'Release', output_name: "Fosi.ipa") upload_to_app_store(ipa: "Fosi.ipa", precheck_include_in_app_purchases: false) if !git_status(path: "Fosi.xcodeproj/project.pbxproj").empty? git_commit(path: "Fosi.xcodeproj/project.pbxproj", message: "[ci skip] [" release] Updating version") push_to_git_remote(remote: "origin", remote_branch: git_branch) end end
  3. name: Distribute to the App Store Connect concurrency: group: $!"

    github.workflow !#-$!" github.ref !# cancel-in-progress: true permissions: contents: write on: push: branches: - 'release!!$' jobs: distribute: runs-on: [macos-14] steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: bundler-cache: true - run: bundle exec fastlane distribute env: APP_STORE_CONNECT_API_KEY_ID: $!" secrets.APP_STORE_CONNECT_API_KEY_ID !# APP_STORE_CONNECT_ISSUER_ID: $!" secrets.APP_STORE_CONNECT_ISSUER_ID !# APP_STORE_CONNECT_KEY_CONTENT: $!" secrets.APP_STORE_CONNECT_KEY_CONTENT !# MATCH_GIT_BASIC_AUTHORIZATION: $!" secrets.MATCH_GIT_BASIC_AUTHORIZATION !# MATCH_USERNAME: $!" secrets.MATCH_USERNAME !# MATCH_PASSWORD: $!" secrets.MATCH_PASSWORD !#
  4. Where should you run tests? PRs to the development branch

    Before uploading to ASC On Nightly or Scheduled Workflows
  5. lane :nightly do # Let's make some magic happen %

    gym( project: "./AwesomeApp.xcodeproj", clean: true, derived_data_path: "./derived-data", output_directory: "./build", output_name: "AwesomeApp.ipa", scheme: "AwesomeApp", export_options: { provisioningProfiles: { "your.bundle.identifier" !% "Your Provisioning Profile Name", } } ) deliver( ipa: "build/AwesomeApp.ipa", verify_only: true ) end
  6. name: Nightly on: schedule: - cron: '0 0 * *

    *' jobs: nightly: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Run nightly lane run: bundle exec fastlane nightly
  7. enum ToggleableFeature: String { case homeSwiftUIRefactor = "homeRefactor" } struct

    HomeViewWrapper: View { @Environment(FeatureToggleService.self) var featureToggles var body: some View { if featureToggles.isOn(.homeSwiftUIRefactor) { HomeView() } else { LegacyHomeView() } } }
  8. import AWSLambdaRuntime import AWSLambdaEvents import AppStoreConnect_Swift_SDK @main struct PausePhasedReleaseWebhook: SimpleLambdaHandler

    { func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response { guard let body = event.body else { return .init(statusCode: .badRequest) } !' Read the webhook payload and decide whehter to stop the release or not!!( } }
  9. func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response

    { !' … !' Get latest live version let endpoint = APIEndpoint .v1 .apps .id("1234567890") .appStoreVersions .get(parameters: .init(filterPlatform: [.ios], filterAppVersionState: [.readyForDistribution])) let response = try await provider.request(endpoint) let allPhasedReleases = response.included? .compactMap { item in if case .appStoreVersionPhasedRelease(let phasedRelease) = item { return phasedRelease } return nil } guard let id = allPhasedReleases!)first!)id else { return .init(statusCode: .notFound) } }
  10. func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws !& APIGatewayV2Response

    { !' … let attributes = AppStoreVersionPhasedReleaseUpdateRequest.Data.Attributes( phasedReleaseState: .paused ) let data = AppStoreVersionPhasedReleaseUpdateRequest.Data( type: .appStoreVersionPhasedReleases, id: id, attributes: attributes ) let requestBody = AppStoreVersionPhasedReleaseUpdateRequest(data: data) let phasedReleaseEndpoint = APIEndpoint .v1 .appStoreVersionPhasedReleases .id(id) .patch(requestBody) _ = try await provider.request(phasedReleaseEndpoint) return .init(statusCode: .ok) }
  11. *Things that are out of your control and fail oMen

    *Your core business logic that your app depends on #7 Move risky* logic to the server!
  12. Gain confidence on your release process! Automate manual processes Use

    Feature Toggles Leverage the BE ecosystem Observe and Monitor Ship less, more o!en! Roll back to stable versions of your app How to use CI/CD to ship your mobile apps