Files
InvestmentTrackerApp/Scripts/archive_and_upload_appstore.sh
T

198 lines
6.6 KiB
Bash
Executable File

#!/bin/zsh
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
PROJECT_PATH="${PROJECT_PATH:-$ROOT_DIR/PortfolioJournal.xcodeproj}"
SCHEME="${SCHEME:-PortfolioJournal}"
CONFIGURATION="${CONFIGURATION:-Release}"
ARCHIVE_DIR="${ARCHIVE_DIR:-$ROOT_DIR/build/appstore}"
ARCHIVE_PATH="${ARCHIVE_PATH:-$ARCHIVE_DIR/${SCHEME}.xcarchive}"
EXPORT_PATH="${EXPORT_PATH:-$ARCHIVE_DIR/export}"
EXPORT_OPTIONS_PLIST="${EXPORT_OPTIONS_PLIST:-$ARCHIVE_DIR/ExportOptions.plist}"
# Credenciales desde pass (GPG store). Se puede sobreescribir con variables de entorno.
_pass_or_env() {
local env_val="$1" pass_key="$2"
if [[ -n "$env_val" ]]; then
echo "$env_val"
elif command -v pass >/dev/null 2>&1; then
pass show "$pass_key" 2>/dev/null || fail "No se encontro la credencial en pass: $pass_key"
else
fail "pass no esta disponible y la variable de entorno no esta definida para: $pass_key"
fi
}
KEY_ID="$(_pass_or_env "${APPSTORE_KEY_ID:-}" appstore/api-key-id)"
ISSUER_ID="$(_pass_or_env "${APPSTORE_ISSUER_ID:-}" appstore/issuer-id)"
TEAM_ID="$(_pass_or_env "${DEVELOPMENT_TEAM_ID:-}" appstore/team-id)"
P8_PATH="${APPSTORE_P8_PATH:-}"
P8_DIR=""
# Fichero .p8 temporal a limpiar al salir. OJO zsh: un `trap ... EXIT` puesto DENTRO
# de una función se dispara al RETORNAR la función (no al salir el script, como bash),
# lo que borraba la clave antes del archive. Por eso la limpieza va en TRAPEXIT global.
P8_TEMP=""
TRAPEXIT() {
[[ -n "$P8_TEMP" ]] && rm -f "$P8_TEMP"
}
fail() {
echo "Error: $*" >&2
exit 1
}
# Resuelve la clave .p8 y la deja en la variable global P8_PATH.
# IMPORTANTE: NO llamar con $(find_p8_file). El fichero temporal extraído de pass se
# limpia con un `trap EXIT`; si la función corre dentro de un subshell de sustitución
# de comandos, ese trap se dispara al cerrarse el subshell y borra el .p8 ANTES del
# export (xcodebuild fallaba con "-authenticationKeyPath ... existing file").
find_p8_file() {
# 1. Variable de entorno explícita
if [[ -n "$P8_PATH" ]]; then
[[ -f "$P8_PATH" ]] || fail "No existe el fichero APPSTORE_P8_PATH=$P8_PATH"
return
fi
# 2. Extraer de pass y escribir en fichero temporal (limpieza al salir del script)
if command -v pass >/dev/null 2>&1 && pass show appstore/api-key-p8 &>/dev/null; then
# OJO: macOS `mktemp` solo sustituye las X si están al FINAL del patrón; con
# `AuthKey_XXXXXX.p8` deja el nombre literal y xcodebuild no encuentra el fichero.
# Además `altool` exige que la key se llame AuthKey_<KEY_ID>.p8 dentro de
# API_PRIVATE_KEYS_DIR, así que usamos ese nombre fijo directamente.
local tmp_p8="/tmp/AuthKey_${KEY_ID}.p8"
pass show appstore/api-key-p8 > "$tmp_p8"
chmod 600 "$tmp_p8"
# Limpieza al salir del script vía TRAPEXIT global (ver arriba por qué no un trap local).
P8_TEMP="$tmp_p8"
P8_PATH="$tmp_p8"
return
fi
# 3. Fichero por defecto en HOME
local default_path="$HOME/AuthKey_${KEY_ID}.p8"
if [[ -f "$default_path" ]]; then
P8_PATH="$default_path"
return
fi
# 4. Único .p8 en HOME
local matches=("${HOME}"/*.p8(N))
if (( ${#matches[@]} == 1 )); then
P8_PATH="${matches[1]}"
return
fi
if (( ${#matches[@]} > 1 )); then
fail "Hay varios ficheros .p8 en $HOME. Define APPSTORE_P8_PATH o inserta la clave en pass: appstore/api-key-p8"
fi
fail "No se encontro la clave .p8. Insértala con: cat AuthKey.p8 | pass insert -f -e appstore/api-key-p8"
}
create_export_options() {
mkdir -p "$ARCHIVE_DIR"
cat > "$EXPORT_OPTIONS_PLIST" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>export</string>
<key>manageAppVersionAndBuildNumber</key>
<false/>
<key>method</key>
<string>app-store-connect</string>
<!-- Firma MANUAL obligatoria: el cloud signing con API key falla al exportar la
extensión QuickUpdate ("No profiles ... were found"). Los 3 targets se firman
con "Apple Distribution" + sus perfiles App Store instalados localmente. -->
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>provisioningProfiles</key>
<dict>
<key>com.alexandrevazquez.PortfolioJournal</key>
<string>porfoliojournal</string>
<key>com.alexandrevazquez.PortfolioJournal.PortfolioJournalWidget</key>
<string>Portfolio Journalwidget</string>
<key>com.alexandrevazquez.PortfolioJournal.QuickUpdate</key>
<string>PortfolioJournal QuickUpdate AppStore</string>
</dict>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>${TEAM_ID}</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
EOF
}
archive_app() {
echo "==> Archivando ${SCHEME}"
# -allowProvisioningUpdates con API key: permite registrar bundle ids nuevos
# (p.ej. extensiones recién añadidas) y crear sus perfiles durante el archive.
xcodebuild \
-project "$PROJECT_PATH" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-archivePath "$ARCHIVE_PATH" \
-destination "generic/platform=iOS" \
-allowProvisioningUpdates \
-authenticationKeyPath "$P8_PATH" \
-authenticationKeyID "$KEY_ID" \
-authenticationKeyIssuerID "$ISSUER_ID" \
clean archive
}
export_ipa() {
echo "==> Exportando IPA"
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS_PLIST" \
-allowProvisioningUpdates \
-authenticationKeyPath "$P8_PATH" \
-authenticationKeyID "$KEY_ID" \
-authenticationKeyIssuerID "$ISSUER_ID"
}
upload_ipa() {
local ipa
ipa="$(find "$EXPORT_PATH" -maxdepth 1 -name '*.ipa' -print -quit)"
[[ -n "$ipa" ]] || fail "No se encontro ningun .ipa en $EXPORT_PATH"
echo "==> Subiendo $(basename "$ipa") a App Store Connect"
API_PRIVATE_KEYS_DIR="$P8_DIR" \
xcrun altool \
--upload-app \
--type ios \
--file "$ipa" \
--apiKey "$KEY_ID" \
--apiIssuer "$ISSUER_ID"
}
main() {
command -v xcodebuild >/dev/null 2>&1 || fail "xcodebuild no esta disponible"
command -v xcrun >/dev/null 2>&1 || fail "xcrun no esta disponible"
find_p8_file # asigna P8_PATH; NO usar $(...) o el trap del subshell borra el .p8
P8_DIR="$(dirname "$P8_PATH")"
echo "Usando proyecto: $PROJECT_PATH"
echo "Usando scheme: $SCHEME"
echo "Usando clave API: $KEY_ID"
echo "Usando fichero .p8: $P8_PATH"
rm -rf "$ARCHIVE_PATH" "$EXPORT_PATH"
create_export_options
archive_app
export_ipa
upload_ipa
echo "==> Proceso completado"
}
main "$@"