Skip to content

Instantly share code, notes, and snippets.

@SamadiPour
Last active June 5, 2022 19:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SamadiPour/34126da2c8bae567b3b0dd8a83bc8b66 to your computer and use it in GitHub Desktop.
Save SamadiPour/34126da2c8bae567b3b0dd8a83bc8b66 to your computer and use it in GitHub Desktop.
Flutter Automated iOS Deployment
#!/bin/bash
issuer_id="9f4xxxx-xxxx-xxxx-xxxx-xxxxxxxxx"
key_id="2F9XXXXXXX"
key_path="./private_keys/AuthKey_2F9XXXXXXX.p8"
expiration_time=$(date -v +60S +%s)
function base64_urlsafe {
base64 | tr -d '\r\n=' | tr '+/' '-_'
}
header_enc=$(echo -n '{"typ":"JWT", "alg":"ES256", "kid":"'"$key_id"'"}' | base64_urlsafe)
payload_enc=$(echo -n '{"aud":"appstoreconnect-v1","iss":"'"$issuer_id"'","exp":'"$expiration_time"'}' | base64_urlsafe)
# see https://gist.github.com/timmc/d2814d7da19521dda1883dd3cc046217
# also https://github.com/madaster97/openssl-jws/blob/master/signature.md
sig="$(echo -n "$header_enc.$payload_enc" | openssl dgst -sha256 -sign "$key_path" -keyform PEM | openssl asn1parse -inform DER | perl -n -e'/INTEGER :([0-9A-Z]*)$/ && print $1' | xxd -p -r | base64_urlsafe)"
echo "$header_enc.$payload_enc.$sig"
#!/usr/bin/env python3
import datetime
import jwt
import requests
import subprocess
import argparse
import shlex
import re
# ================================================================
# Argument parser
parser = argparse.ArgumentParser("python3 ios.py")
parser.add_argument("flavor", help="Enter the flavor name - prod, dev", type=str, choices=['prod', 'dev'])
args = parser.parse_args()
# ================================================================
# API keys
ISSUER_ID = '9f4xxxx-xxxx-xxxx-xxxx-xxxxxxxxx'
KEY_ID = '2F9XXXXXXX'
PRIVATE_KEY_PATH = './private_keys/AuthKey_2F9XXXXXXX.p8'
if args.flavour == 'prod':
APP_ID = '1500000000'
elif args.flavour == 'dev':
APP_ID = '1600000000'
# ================================================================
# Constants
BASE_URL = 'https://api.appstoreconnect.apple.com/v1'
EXP_IN_SEC = 60
# ================================================================
# Helpers
def run(command: str, check: bool = None, output: bool = True, cwd: str = None):
return subprocess.run(shlex.split(command), check=check, stdout=subprocess.DEVNULL if not output else None, cwd=cwd)
def change_pub_version(version: str):
with open('pubspec.yaml', 'r') as f:
data = f.read()
data = re.sub(r'^(version:\s*)(.+)', f'version: {version}', data, flags=re.MULTILINE)
f.close()
with open('pubspec.yaml', 'w') as f:
f.write(data)
f.close()
# ================================================================
with open(PRIVATE_KEY_PATH, 'r+b') as f:
private_key = f.read()
expiration_time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(seconds=EXP_IN_SEC)
encoded_jwt = jwt.encode(
{
'iss': ISSUER_ID,
'exp': expiration_time,
'aud': 'appstoreconnect-v1'
},
private_key,
algorithm='ES256',
headers={
'alg': 'ES256',
'kid': KEY_ID,
'typ': 'JWT',
}
)
headers = {'Authorization': f'Bearer {encoded_jwt}'}
r = requests.get(f'{BASE_URL}/preReleaseVersions', headers=headers, params={'limit': 1, 'filter[app]': APP_ID}).json()
build_id = r['data'][0]['id']
build_name = r['data'][0]['attributes']['version']
r = requests.get(f'{BASE_URL}/preReleaseVersions/{build_id}/builds', headers=headers).json()
build_number = r['data'][0]['attributes']['version']
print(f'App version: {build_name}')
print(f'Build number: {build_number}')
print('============================')
new_build_name = input('Enter new app version: ')
new_build_number_str = input('Enter new build number(enter to auto increment): ')
if new_build_number_str == '':
new_build_number = int(build_number) + 1
else:
new_build_number = int(new_build_number_str)
if new_build_name == '':
exit('No new version number entered')
print('============================')
print(f'Building app with: {new_build_name} - {new_build_number}')
# Update dependencies
print('Getting pub...')
run('flutter pub get', check=True, output=False)
print('Installing pod dependencies...')
run('rm ios/Podfile.lock', check=True, output=False)
run('pod install --repo-update', cwd="ios/", check=True, output=False)
# build the app with new version number and name
# you can also specify version when building like this:
# `flutter build ipa --build-name {new_app_version} --build-number {new_build_number}`
change_pub_version(f'{new_build_name}+{new_build_number}')
run('rm -rf build/ios')
run(f'flutter build ipa --no-pub --flavor {args.flavour} -t lib/main_{args.flavour}.dart', check=True)
e = run(f'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey {KEY_ID} --apiIssuer {ISSUER_ID}')
if e.returncode != 0:
run('open build/ios/ipa')
run('open -n /Applications/Transporter.app')
#!/usr/bin/env python3
import datetime
import jwt
import requests
import subprocess
import argparse
import shlex
import re
# ================================================================
# API keys
ISSUER_ID = '9f4xxxx-xxxx-xxxx-xxxx-xxxxxxxxx'
KEY_ID = '2F9XXXXXXX'
PRIVATE_KEY_PATH = './private_keys/AuthKey_2F9XXXXXXX.p8'
APP_ID = '1500000000'
# ================================================================
# Constants
BASE_URL = 'https://api.appstoreconnect.apple.com/v1'
EXP_IN_SEC = 60
# ================================================================
# Helpers
def run(command: str, check: bool = None, output: bool = True, cwd: str = None):
return subprocess.run(shlex.split(command), check=check, stdout=subprocess.DEVNULL if not output else None, cwd=cwd)
def change_pub_version(version: str):
with open('pubspec.yaml', 'r') as f:
data = f.read()
data = re.sub(r'^(version:\s*)(.+)', f'version: {version}', data, flags=re.MULTILINE)
f.close()
with open('pubspec.yaml', 'w') as f:
f.write(data)
f.close()
# ================================================================
with open(PRIVATE_KEY_PATH, 'r+b') as f:
private_key = f.read()
expiration_time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(seconds=EXP_IN_SEC)
encoded_jwt = jwt.encode(
{
'iss': ISSUER_ID,
'exp': expiration_time,
'aud': 'appstoreconnect-v1'
},
private_key,
algorithm='ES256',
headers={
'alg': 'ES256',
'kid': KEY_ID,
'typ': 'JWT',
}
)
headers = {'Authorization': f'Bearer {encoded_jwt}'}
r = requests.get(f'{BASE_URL}/preReleaseVersions', headers=headers, params={'limit': 1, 'filter[app]': APP_ID}).json()
build_id = r['data'][0]['id']
build_name = r['data'][0]['attributes']['version']
r = requests.get(f'{BASE_URL}/preReleaseVersions/{build_id}/builds', headers=headers).json()
build_number = r['data'][0]['attributes']['version']
print(f'App version: {build_name}')
print(f'Build number: {build_number}')
print('============================')
new_build_name = input('Enter new app version: ')
new_build_number_str = input('Enter new build number(enter to auto increment): ')
if new_build_number_str == '':
new_build_number = int(build_number) + 1
else:
new_build_number = int(new_build_number_str)
if new_build_name == '':
exit('No new version number entered')
print('============================')
print(f'Building app with: {new_build_name} - {new_build_number}')
# Update dependencies
print('Getting pub...')
run('flutter pub get', check=True, output=False)
print('Installing pod dependencies...')
run('rm ios/Podfile.lock', check=True, output=False)
run('pod install --repo-update', cwd="ios/", check=True, output=False)
# build the app with new version number and name
# you can also specify version when building like this:
# `flutter build ipa --build-name {new_app_version} --build-number {new_build_number}`
change_pub_version(f'{new_build_name}+{new_build_number}')
run('rm -rf build/ios')
run(f'flutter build ipa --no-pub', check=True)
e = run(f'xcrun altool --upload-app --type ios -f build/ios/ipa/*.ipa --apiKey {KEY_ID} --apiIssuer {ISSUER_ID}')
if e.returncode != 0:
run('open build/ios/ipa')
run('open -n /Applications/Transporter.app')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment