Integración CI/CD
Deja compilación y firma a tu pipeline y entrega el artefacto a foxstore-cli. La CLI conecta con las tiendas desde tu Runner, sin iniciar Desktop; no ofrece HTTP API ni Runner alojado. Consulta comandos, parámetros, archivos JSON y códigos de salida en Referencia CLI / API.
Preparar Runner y entradas
Los ejemplos Apple requieren foxstore-cli preinstalado para tu sistema y arquitectura, en PATH, además de Bash y jq. Ejecuta primero foxstore-cli version --output json para registrar la versión. No se supone que el instalador Desktop instale CLI ni se necesita una Action adicional de instalación Fox Store.
La compilación debe generar antes un build/App.ipa firmado. Tu proyecto gestiona compilación, firma y permisos de tienda; la CLI no crea certificados ni compila aplicaciones. Publica secuencialmente la misma aplicación para evitar que dos pipelines escriban simultáneamente en la misma versión.
Configura estas variables:
| Nombre | Tipo | Contenido |
|---|---|---|
APPLE_PRIVATE_KEY | CI Secret | Clave privada PEM completa de App Store Connect API Key, conservando saltos de línea. |
APPLE_ISSUER_ID, APPLE_KEY_ID | CI variable | Issuer ID y Key ID correspondientes a la clave privada. |
APPLE_APP_ID | CI variable | ID numérico de la aplicación de destino en App Store Connect, enviado como string a la CLI. |
--private-key-env RELEASE_KEY recibe el nombre de la variable, no su valor; por defecto se lee APPLE_PRIVATE_KEY. Si la plataforma entrega una ruta de archivo Secret, usa --private-key-file "$APPLE_KEY_FILE"; no pongas la ruta en APPLE_PRIVATE_KEY.
Script de publicación compartido
Guarda el script siguiente como ci/publish-apple.sh en tu repositorio. Todos los ejemplos CI lo reutilizan, sin cambiar las herramientas de compilación existentes.
#!/usr/bin/env bash
set -euo pipefail
: "${APPLE_ISSUER_ID:?Set APPLE_ISSUER_ID}"
: "${APPLE_KEY_ID:?Set APPLE_KEY_ID}"
: "${APPLE_APP_ID:?Set APPLE_APP_ID}"
: "${APPLE_PRIVATE_KEY:?Set APPLE_PRIVATE_KEY secret}"
test -s build/App.ipa
mkdir -p release-results
foxstore-cli version --output json > release-results/version.json
foxstore-cli artifact inspect --file build/App.ipa --output json \
> release-results/artifact.json 2> release-results/artifact-error.json
foxstore-cli doctor apple \
--issuer-id "$APPLE_ISSUER_ID" --key-id "$APPLE_KEY_ID" --output json \
> release-results/doctor.json 2> release-results/doctor-error.json
foxstore-cli preflight apple --file build/App.ipa --output json \
> release-results/preflight.json 2> release-results/preflight-error.json
jq -e '.status == "success" and .data.status == "ready"' \
release-results/preflight.json > /dev/null
if foxstore-cli publish apple \
--issuer-id "$APPLE_ISSUER_ID" --key-id "$APPLE_KEY_ID" \
--app-id "$APPLE_APP_ID" --file build/App.ipa --output json \
> release-results/publish.json 2> release-results/publish-error.json; then
publish_exit=0
else
publish_exit=$?
fi
printf '%s\n' "$publish_exit" > release-results/publish-exit-code.txt
# 一次查询留存快照;保持原发布退出码,不自动重发。
if [ "$publish_exit" -eq 0 ] || [ "$publish_exit" -eq 7 ]; then
if foxstore-cli status apple \
--issuer-id "$APPLE_ISSUER_ID" --key-id "$APPLE_KEY_ID" --output json \
> release-results/status.json 2> release-results/status-error.json; then
printf '%s\n' '已保存远端构建快照,请核对目标应用与构建号。'
else
printf '%s\n' '状态查询失败,请在 App Store Connect 核对结果。' >&2
fi
fi
if [ "$publish_exit" -eq 7 ]; then
printf '%s\n' '发布结果未知,请先对账,禁止自动重发。' >&2
fi
exit "$publish_exit"El código 0 de Apple preflight solo indica análisis correcto; puede devolver data.status: "blocked", por eso el script comprueba ready. Un publish correcto devuelve buildUploadId, que confirma la llamada de subida, no el procesamiento Apple, disponibilidad TestFlight ni aprobación. status apple devuelve construcciones visibles sin filtro app-id; coteja bundleId, version y buildNumber de preflight.json. Puede haber retraso: una instantánea sin el Build no demuestra un fallo de subida.
Con --output json, los éxitos van a stdout y los sobres de error a stderr. --output ndjson también emite solo un resultado final, sin progreso continuo. Un proceso terminado por el Runner puede no producir un sobre completo; verifica el remoto como con el código 7.
GitHub Actions
Añade el job release a jobs del workflow de compilación existente. Depende de build, que debe subir App.ipa como artifact ipa, con el archivo en su raíz. La etiqueta personalizada foxstore indica un Runner con CLI, Bash y jq configurados; sustitúyela por la tuya. El repositorio debe contener el script anterior.
jobs:
# 保留已有 build job:构建签名后的 App.ipa,并上传名为 ipa 的 artifact。
release:
needs: build
runs-on: [self-hosted, foxstore]
permissions:
contents: read
concurrency:
group: apple-release-${{ github.repository }}
cancel-in-progress: false
env:
APPLE_ISSUER_ID: ${{ vars.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ vars.APPLE_KEY_ID }}
APPLE_APP_ID: ${{ vars.APPLE_APP_ID }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: ipa
path: build
- name: Preflight and upload
shell: bash
env:
APPLE_PRIVATE_KEY: ${{ secrets.APPLE_PRIVATE_KEY }}
run: bash ci/publish-apple.sh
- name: Save release results
if: always()
uses: actions/upload-artifact@v4
with:
name: release-results
path: release-results/
retention-days: 14Inyecta secretos mediante env, sin insertar la clave privada en el código shell. Consulta configuración y alcance en Secretos de GitHub Actions. El artifact solo debe incluir el directorio indicado, nunca claves privadas ni todo el workspace. Restringe también el acceso a los datos de aplicación en los resultados.
GitLab CI
Configura las tres variables ordinarias y un Secret protegido en CI/CD Variables. El ejemplo guarda PEM como valor; con tipo File de GitLab, el valor será una ruta temporal y debes adaptar el script a --private-key-file. Consulta Variables CI/CD de GitLab.
Integra en .gitlab-ci.yml manteniendo el job build, que debe archivar build/App.ipa. Los stages existentes deben incluir deploy; el Runner foxstore debe tener CLI, Bash y jq.
release_apple:
stage: deploy
tags: [foxstore]
needs:
- job: build
artifacts: true
resource_group: apple-release
script:
- bash ci/publish-apple.sh
artifacts:
when: always
paths:
- release-results/
expire_in: 14 daysJenkins
Coloca el stage siguiente después de la compilación del Declarative Pipeline existente, en el mismo workspace. Genera antes build/App.ipa y obtén ci/publish-apple.sh. El Agent necesita CLI, Bash y jq; configura los tres ID en environment y crea la credencial Secret text apple-private-key en Jenkins. El stage usa credentials binding; recomendamos disableConcurrentBuilds() en options del pipeline.
stage('Release Apple') {
environment {
APPLE_PRIVATE_KEY = credentials('apple-private-key')
}
steps {
sh 'bash ci/publish-apple.sh'
}
post {
always {
archiveArtifacts artifacts: 'release-results/*', allowEmptyArchive: true
}
}
}Consulta Secret binding y post en Jenkins Pipeline. No interpoles claves privadas en comandos Groovy ni actives registros de depuración que impriman secretos.
Cambiar de proveedor y gestionar fallos
Tras compilar, un pipeline puede llamar comandos independientes de varios proveedores; el CI existente organiza dependencias y concurrencia. Por ejemplo, para Android:
foxstore-cli preflight android --file build/App.aab \
--expected-package-name com.example.app --output json
foxstore-cli publish google-play --package-name com.example.app \
--file build/App.aab --track internal --release-status completed --output json
foxstore-cli status google-play --package-name com.example.app --output jsonInyecta previamente GOOGLE_PLAY_CREDENTIALS_JSON. Conserva la separación stdout/stderr y el tratamiento de códigos del script; no copies solo las tres líneas y actives reintentos incondicionales del job. WeChat requiere Node.js y dos paquetes npm miniprogram CI; doctor/status de Huawei Android exige elegir explícitamente API Client, véase Parámetros.
Los códigos 2/3 suelen exigir corregir entradas o credenciales; 4/5 requieren evaluar error.retryable y la fase de escritura remota; 6 indica no soportado; con 7, mantén el fallo y concilia primero; para 10, conserva versión y error. No existen wait, cancel, reconcile ni --dry-run unificados. Si necesitas esperar, configura consultas limitadas en el pipeline y no repitas publish hasta confirmar el estado remoto.
