Commit 105569b4 authored by Kourser's avatar Kourser
Browse files

chore: add automated release script (macOS → GitLab + iOS → TestFlight)



scripts/release.sh builds, notarizes and publishes the macOS app to a GitLab Release (notes from the CHANGELOG), and optionally archives + uploads the iOS build to TestFlight when App Store Connect API keys are configured. Secrets live in scripts/release.env (gitignored); never printed.

Co-Authored-By: default avatarClaude <claude@anthropic.com>
parent 313d03f2
Loading
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -21,3 +21,6 @@ Packages/*/.build/
/public/
/site/
.venv/

# Release secrets (Apple ID, app-specific password, GitLab token) — never commit
scripts/release.env
+14 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>method</key>
	<string>app-store-connect</string>
	<key>teamID</key>
	<string>88NB46UUP7</string>
	<key>signingStyle</key>
	<string>automatic</string>
	<key>destination</key>
	<string>upload</string>
</dict>
</plist>
+30 −0
Original line number Diff line number Diff line
# Copie ce fichier en `scripts/release.env` et renseigne tes valeurs.
# `scripts/release.env` est ignoré par git — n'y mets jamais de secret dans le dépôt.

# Identifiant Apple utilisé pour la notarisation
APPLE_ID="ton.email@exemple.com"
TEAM_ID="88NB46UUP7"

# Notarisation — DEUX options (choisis-en une) :
#  1) Profil Trousseau (recommandé). Crée-le une fois :
#       xcrun notarytool store-credentials skingomz \
#         --apple-id "ton.email@exemple.com" --team-id 88NB46UUP7 \
#         --password "MOT-DE-PASSE-APP-SPECIFIQUE"
#     puis renseigne seulement NOTARY_PROFILE et laisse NOTARY_PASSWORD vide.
NOTARY_PROFILE="skingomz"
#  2) Mot de passe app-spécifique en clair (https://appleid.apple.com) :
NOTARY_PASSWORD=""

# Jeton GitLab avec le scope "api" (Preferences > Access Tokens)
GITLAB_TOKEN=""
GITLAB_BASE="https://git.cythin.eu/api/v4"
PROJECT_PATH="Kourser%2Fskingomz-app"

# --- iOS / TestFlight (optionnel) ------------------------------------------
# Renseigne ces 3 variables pour qu'en plus du .app macOS, le script archive
# l'app iOS et l'envoie sur TestFlight. Laisse vide pour ignorer l'iOS.
# Crée une clé API dans App Store Connect (Users and Access > Integrations >
# App Store Connect API) ; place le fichier .p8 hors du dépôt.
ASC_KEY_ID=""                 # ex. ABCD123456
ASC_ISSUER_ID=""              # ex. 69a6de70-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ASC_KEY_PATH=""               # ex. ~/.appstoreconnect/private_keys/AuthKey_ABCD123456.p8

scripts/release.sh

0 → 100755
+155 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash
#
# Build, notarize and publish a Skingomz macOS release to GitLab.
#
#   ./scripts/release.sh [version]
#
# `version` defaults to MARKETING_VERSION read from the Xcode project.
# Secrets/config are read from `scripts/release.env` (gitignored) or the
# environment. The token is never printed or logged.
#
# Steps: archive → export (Developer ID) → notarize + staple → zip →
# upload to the GitLab generic package registry → create the GitLab Release
# (notes extracted from the matching CHANGELOG section). The git tag is created
# and pushed only if it does not already exist.

set -euo pipefail
cd "$(dirname "$0")/.."

# --- Load config -----------------------------------------------------------
ENV_FILE="${RELEASE_ENV:-scripts/release.env}"
if [ -f "$ENV_FILE" ]; then
  set -a; . "$ENV_FILE"; set +a
fi

: "${APPLE_ID:?Renseigne APPLE_ID (scripts/release.env)}"
: "${GITLAB_TOKEN:?Renseigne GITLAB_TOKEN (scope api)}"
TEAM_ID="${TEAM_ID:-88NB46UUP7}"
GITLAB_BASE="${GITLAB_BASE:-https://git.cythin.eu/api/v4}"
PROJECT_PATH="${PROJECT_PATH:-Kourser%2Fskingomz-app}"
export DEVELOPER_DIR="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}"

for tool in xcodebuild xcrun ditto curl python3 git; do
  command -v "$tool" >/dev/null || { echo "✗ outil requis manquant : $tool" >&2; exit 1; }
done

# --- Version & paths -------------------------------------------------------
VERSION="${1:-$(grep -m1 'MARKETING_VERSION' Skingomz.xcodeproj/project.pbxproj \
  | sed 's/.*= *//; s/;.*//' | tr -d ' ')}"
[ -n "$VERSION" ] || { echo "✗ version introuvable" >&2; exit 1; }
TAG="v$VERSION"

ARCHIVE="build/Skingomz-macOS.xcarchive"
EXPORT_DIR="build/macos-export"
APP="$EXPORT_DIR/Skingomz.app"
ZIP="build/Skingomz-$VERSION-macOS.zip"
FILENAME="$(basename "$ZIP")"
ASSET_URL="$GITLAB_BASE/projects/$PROJECT_PATH/packages/generic/skingomz/$VERSION/$FILENAME"

echo "▶︎ Release Skingomz $VERSION (tag $TAG)"
mkdir -p build

# --- 1. Archive ------------------------------------------------------------
echo "▶︎ Archive…"
rm -rf "$ARCHIVE"
xcodebuild -project Skingomz.xcodeproj -scheme Skingomz -configuration Release \
  -destination 'generic/platform=macOS' -archivePath "$ARCHIVE" archive

# --- 2. Export (Developer ID) ---------------------------------------------
echo "▶︎ Export Developer ID…"
rm -rf "$EXPORT_DIR"
xcodebuild -exportArchive -archivePath "$ARCHIVE" \
  -exportOptionsPlist ExportOptions.plist -exportPath "$EXPORT_DIR"
[ -d "$APP" ] || { echo "✗ $APP introuvable après export" >&2; exit 1; }

# --- 3. Notarize + staple --------------------------------------------------
echo "▶︎ Notarisation (peut prendre quelques minutes)…"
ditto -c -k --keepParent "$APP" build/notarize.zip
if [ -n "${NOTARY_PROFILE:-}" ]; then
  xcrun notarytool submit build/notarize.zip --keychain-profile "$NOTARY_PROFILE" --wait
else
  : "${NOTARY_PASSWORD:?Renseigne NOTARY_PROFILE ou NOTARY_PASSWORD}"
  xcrun notarytool submit build/notarize.zip \
    --apple-id "$APPLE_ID" --team-id "$TEAM_ID" --password "$NOTARY_PASSWORD" --wait
fi
xcrun stapler staple "$APP"

# --- 4. Final zip ----------------------------------------------------------
echo "▶︎ Zip final…"
rm -f "$ZIP"
ditto -c -k --keepParent "$APP" "$ZIP"

# --- 5. Release notes (from CHANGELOG) ------------------------------------
echo "▶︎ Notes de release (section $VERSION du CHANGELOG)…"
awk -v v="$VERSION" '
  $0 ~ "^## \\[" v "\\]" { f=1; next }
  f && /^## \[/ { exit }
  f { print }
' CHANGELOG.md | sed '/./,$!d' > build/notes.md
[ -s build/notes.md ] || echo "Skingomz $VERSION" > build/notes.md

# --- 6. Ensure tag exists --------------------------------------------------
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
  echo "▶︎ Création + push du tag $TAG…"
  git tag -a "$TAG" -m "Skingomz $VERSION"
  git push origin "$TAG"
fi

# --- 7. Upload artifact -----------------------------------------------------
echo "▶︎ Upload de l'artefact sur GitLab…"
curl --fail --silent --show-error --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  --upload-file "$ZIP" "$ASSET_URL" >/dev/null
echo "  ✓ $ASSET_URL"

# --- 8. Create the Release --------------------------------------------------
echo "▶︎ Création de la Release $TAG…"
python3 - "$VERSION" "$TAG" "$ASSET_URL" "$FILENAME" > build/release.json <<'PY'
import json, sys
version, tag, url, filename = sys.argv[1:5]
notes = open("build/notes.md", encoding="utf-8").read().strip()
print(json.dumps({
    "name": f"Skingomz {version}",
    "tag_name": tag,
    "description": notes,
    "assets": {"links": [{
        "name": f"Skingomz {version} — macOS (.zip notarisé)",
        "url": url,
        "link_type": "package",
    }]},
}))
PY

http_code=$(curl --silent --output build/release-response.json --write-out '%{http_code}' \
  --header "PRIVATE-TOKEN: $GITLAB_TOKEN" --header "Content-Type: application/json" \
  --data @build/release.json "$GITLAB_BASE/projects/$PROJECT_PATH/releases")

if [ "$http_code" = "201" ]; then
  echo "✓ Release $TAG publiée."
elif [ "$http_code" = "409" ]; then
  echo "⚠ La Release $TAG existe déjà — rien créé. Édite-la dans GitLab si besoin." >&2
else
  echo "✗ Échec création Release (HTTP $http_code) :" >&2
  cat build/release-response.json >&2; echo >&2
  exit 1
fi

# --- 9. iOS / TestFlight (optionnel) ---------------------------------------
# Exécuté uniquement si une clé App Store Connect est configurée. L'app iOS
# doit déjà exister dans App Store Connect (bundle eu.cythin.skingomz).
if [ -n "${ASC_KEY_ID:-}" ] && [ -n "${ASC_ISSUER_ID:-}" ] && [ -n "${ASC_KEY_PATH:-}" ]; then
  echo "▶︎ iOS : archive + envoi TestFlight…"
  IOS_ARCHIVE="build/Skingomz-iOS.xcarchive"
  rm -rf "$IOS_ARCHIVE" build/ios-export
  xcodebuild -project Skingomz.xcodeproj -scheme Skingomz -configuration Release \
    -destination 'generic/platform=iOS' -archivePath "$IOS_ARCHIVE" archive
  # ExportOptions-iOS.plist a destination=upload → envoi direct à App Store Connect.
  xcodebuild -exportArchive -archivePath "$IOS_ARCHIVE" \
    -exportOptionsPlist ExportOptions-iOS.plist -exportPath build/ios-export \
    -authenticationKeyPath "$ASC_KEY_PATH" \
    -authenticationKeyID "$ASC_KEY_ID" \
    -authenticationKeyIssuerID "$ASC_ISSUER_ID"
  echo "✓ Build iOS $VERSION envoyé à App Store Connect (TestFlight)."
else
  echo "▶︎ iOS/TestFlight ignoré (ASC_KEY_ID / ASC_ISSUER_ID / ASC_KEY_PATH non configurés)."
fi