Skip to content

Merge pull request #38 from cnumr/feat/2025-version #61

Merge pull request #38 from cnumr/feat/2025-version

Merge pull request #38 from cnumr/feat/2025-version #61

Workflow file for this run

# **************************
# Release Electron app
# Build and release on multiple OS (matrix) to generate installers
# Triggered after PR merge to main with changesets
# **************************
name: Release Electron app
on:
push:
branches:
- main
# Ne se déclenche que si le commit contient "chore: version packages"
# Cela évite de builder à chaque merge, seulement après la PR de version
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
check-release:
runs-on: ubuntu-latest
outputs:
should-release: ${{ steps.check.outputs.should-release }}
version: ${{ steps.check.outputs.version }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Check if version changed (should release)
id: check
run: |
# Vérifier si le dernier commit contient "chore: version packages" (créé par changeset)
# Ce commit est créé par le workflow changeset.yml après le merge de la PR de version
LAST_COMMIT=$(git log -1 --pretty=%B)
if echo "$LAST_COMMIT" | grep -q "chore: version packages"; then
VERSION=$(node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); console.log(pkg.version);")
echo "should-release=true" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "✅ Version détectée: $VERSION - Le build va être déclenché"
else
echo "should-release=false" >> $GITHUB_OUTPUT
echo "version=" >> $GITHUB_OUTPUT
echo "⏭️ Pas de version à publier (commit: $LAST_COMMIT)"
echo "⏭️ Le workflow changeset.yml doit d'abord créer et merger une PR de version"
fi
build:
needs: check-release
if: needs.check-release.outputs.should-release == 'true'
strategy:
fail-fast: false
matrix:
os:
- { name: 'linux', image: 'ubuntu-latest' }
- { name: 'windows', image: 'windows-latest' }
- { name: 'macos-intel', image: 'macos-15-intel' }
- { name: 'macos-arm', image: 'macos-15' }
runs-on: ${{ matrix.os.image }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Setup Python (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Cache node modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-modules-
- name: Install dependencies
run: npm ci
env:
PUPPETEER_SKIP_DOWNLOAD: true
- name: Debug APPLE_ID (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: echo ${APPLE_ID:0:3}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
- name: Debug APPLE_APP_SPECIFIC_PASSWORD (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: echo ${APPLE_APP_SPECIFIC_PASSWORD:0:3}
env:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- name: Debug APPLE_TEAM_ID (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: echo ${APPLE_TEAM_ID:0:3}
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Add macOS certificates (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
if [ -f "add-osx-cert.sh" ] && [ -n "$CERTIFICATE_OSX_APPLICATION" ] && [ -n "$CERTIFICATE_PASSWORD" ]; then
chmod +x add-osx-cert.sh && ./add-osx-cert.sh
echo "✅ Certificats macOS installés"
# Vérifier que le certificat est bien installé
echo "Vérification des certificats installés..."
security find-identity -v -p codesigning | grep "Developer ID" || echo "⚠️ Aucun certificat Developer ID trouvé"
else
echo "❌ ERREUR: Certificats macOS non configurés"
echo "Les secrets suivants doivent être configurés dans GitHub:"
echo " - APPLE_APPLICATION_CERT"
echo " - APPLE_APPLICATION_CERT_PASSWORD"
echo " - APPLE_IDENTITY"
echo " - APPLE_ID"
echo " - APPLE_APP_SPECIFIC_PASSWORD"
echo " - APPLE_TEAM_ID"
exit 1
fi
env:
CERTIFICATE_OSX_APPLICATION: ${{ secrets.APPLE_APPLICATION_CERT }}
CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERT_PASSWORD }}
- name: Verify signing configuration (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
if [ -z "$APPLE_IDENTITY" ] || [ -z "$APPLE_ID" ] || [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] || [ -z "$APPLE_TEAM_ID" ]; then
echo "❌ ERREUR: Configuration de signature macOS incomplète"
echo "Les secrets suivants doivent être configurés:"
echo " - APPLE_IDENTITY: ${APPLE_IDENTITY:+✅ défini}${APPLE_IDENTITY:-❌ manquant}"
echo " - APPLE_ID: ${APPLE_ID:+✅ défini}${APPLE_ID:-❌ manquant}"
echo " - APPLE_APP_SPECIFIC_PASSWORD: ${APPLE_APP_SPECIFIC_PASSWORD:+✅ défini}${APPLE_APP_SPECIFIC_PASSWORD:-❌ manquant}"
echo " - APPLE_TEAM_ID: ${APPLE_TEAM_ID:+✅ défini}${APPLE_TEAM_ID:-❌ manquant}"
exit 1
fi
echo "✅ Configuration de signature macOS complète"
echo "APPLE_IDENTITY complet: $APPLE_IDENTITY"
echo "APPLE_TEAM_ID: $APPLE_TEAM_ID"
# Vérifier que le certificat correspond exactement
echo "Recherche du certificat correspondant..."
CERT_FOUND=$(security find-identity -v -p codesigning | grep "$APPLE_IDENTITY" || echo "")
if [ -z "$CERT_FOUND" ]; then
echo "⚠️ ATTENTION: Le certificat '$APPLE_IDENTITY' n'a pas été trouvé exactement"
echo "Certificats disponibles:"
security find-identity -v -p codesigning | grep "Developer ID"
echo ""
echo "Vérifiez que APPLE_IDENTITY correspond EXACTEMENT au nom du certificat ci-dessus"
else
echo "✅ Certificat trouvé: $CERT_FOUND"
fi
env:
APPLE_IDENTITY: ${{ secrets.APPLE_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Set macOS environment variables
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
echo "APPLE_IDENTITY=${{ secrets.APPLE_IDENTITY }}" >> $GITHUB_ENV
echo "APPLE_ID=${{ secrets.APPLE_ID }}" >> $GITHUB_ENV
echo "APPLE_APP_SPECIFIC_PASSWORD=${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}" >> $GITHUB_ENV
echo "APPLE_TEAM_ID=${{ secrets.APPLE_TEAM_ID }}" >> $GITHUB_ENV
echo "DEBUG=@electron/osx-sign,electron-forge:*" >> $GITHUB_ENV
- name: Build application
shell: bash
run: |
# Vérifications macOS uniquement
if [ "${{ matrix.os.name }}" == "macos-intel" ] || [ "${{ matrix.os.name }}" == "macos-arm" ]; then
# Vérifier que les variables d'environnement sont bien définies
echo "Vérification des variables de signature..."
echo "APPLE_IDENTITY complet: $APPLE_IDENTITY"
echo "APPLE_ID: ${APPLE_ID:0:10}..."
echo "APPLE_TEAM_ID: $APPLE_TEAM_ID"
echo "APPLE_APP_SPECIFIC_PASSWORD: ${APPLE_APP_SPECIFIC_PASSWORD:+✅ défini}"
# Vérifier que APPLE_IDENTITY contient "Developer ID"
if [ -n "$APPLE_IDENTITY" ]; then
if echo "$APPLE_IDENTITY" | grep -q "Developer ID"; then
echo "✅ APPLE_IDENTITY contient 'Developer ID'"
else
echo "❌ ERREUR: APPLE_IDENTITY ne contient pas 'Developer ID'"
echo " Valeur: $APPLE_IDENTITY"
echo " Cela empêchera hasSigningConfig d'être true dans forge.config.js"
fi
else
echo "❌ ERREUR: APPLE_IDENTITY n'est pas défini"
fi
# Vérifier que le certificat est accessible dans le keychain par défaut
echo "Recherche du certificat dans le keychain..."
security find-identity -v -p codesigning
# Vérifier spécifiquement le certificat recherché
if [ -n "$APPLE_IDENTITY" ]; then
echo "Recherche du certificat exact: $APPLE_IDENTITY"
CERT_MATCH=$(security find-identity -v -p codesigning | grep -i "$APPLE_IDENTITY" || echo "")
if [ -z "$CERT_MATCH" ]; then
echo "⚠️ ATTENTION: Le certificat '$APPLE_IDENTITY' n'a pas été trouvé exactement"
echo "Certificats Developer ID disponibles:"
security find-identity -v -p codesigning | grep "Developer ID"
else
echo "✅ Certificat trouvé: $CERT_MATCH"
fi
fi
fi
echo ""
echo "🔍 Variables d'environnement avant npm run make:"
echo " APPLE_IDENTITY: ${APPLE_IDENTITY:+✅ défini}${APPLE_IDENTITY:-❌ non défini}"
if [ -n "$APPLE_IDENTITY" ]; then
echo " APPLE_IDENTITY contient 'Developer ID': $(echo "$APPLE_IDENTITY" | grep -q "Developer ID" && echo "✅ oui" || echo "❌ non")"
fi
npm run make
- name: Verify code signature (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
echo "Vérification de la signature de l'application..."
# Trouver l'application dans le répertoire de build
# Electron Forge place les apps dans out/make/zip/darwin/{arch}/
# Chercher dans tous les répertoires d'architecture possibles
APP_PATH=""
TEMP_EXTRACT=""
# Chercher dans tous les répertoires d'architecture
for ARCH_DIR in arm64 x64; do
ZIP_DIR="out/make/zip/darwin/${ARCH_DIR}"
if [ -d "$ZIP_DIR" ]; then
ZIP_FILE=$(find "$ZIP_DIR" -name "*.zip" -type f | head -1)
if [ -n "$ZIP_FILE" ]; then
echo "Extraction temporaire du ZIP pour vérification..."
TEMP_EXTRACT=$(mktemp -d)
ditto -xk "$ZIP_FILE" "$TEMP_EXTRACT"
APP_PATH=$(find "$TEMP_EXTRACT" -name "*.app" -type d | head -1)
if [ -n "$APP_PATH" ]; then
break
fi
fi
fi
done
# Si pas trouvé dans le ZIP, chercher directement
if [ -z "$APP_PATH" ]; then
APP_PATH=$(find out/make -name "*.app" -type d | head -1)
fi
if [ -z "$APP_PATH" ]; then
echo "❌ Aucune application .app trouvée"
echo "Contenu de out/make:"
ls -la out/make/ || true
echo "Contenu de out/make/zip/darwin:"
ls -la out/make/zip/darwin/ || true
exit 1
fi
echo "Application trouvée: $APP_PATH"
# Vérifier la signature (codesign retourne toujours un code de sortie, donc on capture la sortie)
SIGNATURE_OUTPUT=$(codesign -dv --verbose=4 "$APP_PATH" 2>&1)
SIGNATURE_EXIT=$?
echo "$SIGNATURE_OUTPUT"
# Vérifier si la signature est "adhoc" (non signée) ou valide
if echo "$SIGNATURE_OUTPUT" | grep -q "Signature=adhoc"; then
echo "❌ ERREUR: L'application n'est pas signée (signature adhoc)"
echo "Vérifiez que hasSigningConfig est true dans forge.config.js"
echo ""
echo "Debug: Variables d'environnement au moment du build:"
echo " APPLE_IDENTITY était défini: ${APPLE_IDENTITY:+oui}${APPLE_IDENTITY:-non}"
echo ""
echo "Vérifiez les logs du step 'Build application' pour voir si hasSigningConfig était true"
exit 1
elif echo "$SIGNATURE_OUTPUT" | grep -q "Signature=" && ! echo "$SIGNATURE_OUTPUT" | grep -q "Signature=adhoc"; then
echo "✅ Signature vérifiée (non-adhoc)"
elif [ $SIGNATURE_EXIT -eq 0 ]; then
echo "✅ Signature vérifiée (code de sortie: 0)"
else
echo "⚠️ Avertissement: Impossible de vérifier la signature (code: $SIGNATURE_EXIT)"
echo "Sortie complète:"
echo "$SIGNATURE_OUTPUT"
fi
# Vérifier la notarisation (peut prendre quelques minutes après la notarisation)
if spctl --assess --verbose --type execute "$APP_PATH" 2>&1; then
echo "✅ Notarisation vérifiée"
else
echo "⚠️ Avertissement: La notarisation n'est pas vérifiée (peut prendre quelques minutes après la notarisation)"
fi
# Afficher les détails de la signature
codesign -dvv "$APP_PATH" 2>&1 | head -20
# Nettoyer l'extraction temporaire
if [ -n "$TEMP_EXTRACT" ] && [ -d "$TEMP_EXTRACT" ]; then
rm -rf "$TEMP_EXTRACT"
fi
- name: Create DMG (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
node scripts/create-dmg.js
- name: Verify DMG signature (macOS only)
if: matrix.os.name == 'macos-intel' || matrix.os.name == 'macos-arm'
run: |
echo "Vérification de la signature dans le DMG..."
# Trouver le DMG créé
DMG_PATH=$(find out/make -name "*.dmg" -type f | head -1)
if [ -z "$DMG_PATH" ]; then
echo "⚠️ Aucun DMG trouvé"
exit 0
fi
echo "DMG trouvé: $DMG_PATH"
# Monter le DMG temporairement pour vérifier
MOUNT_POINT=$(mktemp -d)
hdiutil attach "$DMG_PATH" -mountpoint "$MOUNT_POINT" -quiet
APP_IN_DMG=$(find "$MOUNT_POINT" -name "*.app" -type d | head -1)
if [ -n "$APP_IN_DMG" ]; then
echo "Vérification de la signature de l'application dans le DMG..."
if codesign -dv --verbose=4 "$APP_IN_DMG" 2>&1; then
echo "✅ Signature vérifiée dans le DMG"
else
echo "❌ ERREUR: La signature n'est pas valide dans le DMG"
hdiutil detach "$MOUNT_POINT" -quiet
exit 1
fi
fi
hdiutil detach "$MOUNT_POINT" -quiet
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os.name }}-artifacts
path: |
out/**/*.deb
out/**/*.dmg
out/**/*setup.exe
out/**/*.rpm
out/**/*.zip
retention-days: 7
release:
needs: [check-release, build]
if: needs.check-release.outputs.should-release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Remove duplicate files
run: |
echo "Recherche des fichiers en double..."
# Créer un dossier temporaire pour les fichiers uniques
mkdir -p artifacts-unique
# Trouver tous les fichiers et les copier dans artifacts-unique en évitant les doublons
# Utiliser le nom de fichier comme clé unique
find artifacts -type f \( -name "*.deb" -o -name "*.dmg" -o -name "*.exe" -o -name "*.rpm" -o -name "*.zip" \) | while read file; do
filename=$(basename "$file")
if [ ! -f "artifacts-unique/$filename" ]; then
echo "Copie de $file vers artifacts-unique/$filename"
cp "$file" "artifacts-unique/$filename"
else
echo "Fichier $filename déjà copié, ignoré: $file"
fi
done
# Remplacer artifacts par artifacts-unique
rm -rf artifacts
mv artifacts-unique artifacts
echo "Fichiers uniques dans artifacts:"
find artifacts -type f | sort
- name: Generate Release.txt
run: echo ${{ github.sha }} > Release.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: Release Electron-app ${{ needs.check-release.outputs.version }}
tag_name: ${{ needs.check-release.outputs.version }}
files: |
Release.txt
artifacts/**/*.deb
artifacts/**/*.dmg
artifacts/**/*setup.exe
artifacts/**/*.rpm
artifacts/**/*.zip
generate_release_notes: true
fail_on_unmatched_files: false
overwrite_files: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to npm (if configured)
if: env.NPM_TOKEN != ''
run: npm publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}