Compilar y distribuir apps para Mac e iOS sin abrir Xcode
(scottwillsey.com)- Después de completar la configuración inicial de la cuenta de Apple y los certificados, es posible automatizar la compilación, firma y distribución de apps para Mac e iOS usando solo las herramientas CLI internas de Xcode.app
- Con XcodeGen se regenera el
.xcodeprojdesdeproject.yml, y conxcodebuild,notarytool,staplerydevicectlse cubre desde la creación del proyecto hasta la notarización en Mac y la instalación en iPhone - La distribución para Mac ejecuta en orden, con un solo
scripts/release.sh, el archivado, la firma Developer ID, la notarización, el grapado del ticket, la verificación de Gatekeeper y la instalación en/Applications, y se detiene de inmediato si algo falla - Para compilaciones y pruebas rápidas se puede usar
CODE_SIGNING_ALLOWED=NO, pero esta build con firma temporal no pasa Gatekeeper y los permisos de iCloud KVS y App Group tampoco quedan vinculados al Team ID real - La clave privada de firma y la contraseña de notarización se guardan en el llavero de inicio de sesión, y si se documentan los comandos y reglas de distribución en
CLAUDE.mdoAGENTS.md, agentes como Claude Code pueden ejecutar repetidamente todo el flujo de trabajo
Alcance de lo que se puede hacer sin la GUI de Xcode
- Xcode en sí debe estar instalado, pero no hace falta ejecutarlo;
xcodebuild,notarytool,staplerydevicectlse ejecutan de forma independiente como herramientas de shell dentro de Xcode.app - La configuración inicial, como iniciar sesión con Apple ID, crear certificados Developer ID y guardar la contraseña de notarización, requiere la GUI o una terminal interactiva
- Una vez completada la configuración inicial, se puede manejar sin mouse la creación del proyecto, la compilación, el archivado, la exportación de apps firmadas, la notarización, la verificación, la instalación y hasta la distribución en un iPhone real
- Si te trabas al escribir configuraciones o scripts, puedes darle a Claude Code u otra herramienta de codificación con LLM los requisitos de la tarea y los valores específicos del proyecto para que genere el flujo de trabajo
Seleccionar la toolchain completa de Xcode
- Primero hay que verificar que el resultado de
xcode-select -psea/Applications/Xcode.app/Contents/Developer - Si están seleccionadas las Command Line Tools independientes, usa el siguiente comando para especificar la toolchain completa de Xcode
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
/Library/Developer/CommandLineTools, instalado conxcode-select --install, incluyeclangygit, pero no contiene el SDK de iOS,notarytool,devicectlni el conjunto completo de herramientas para desarrollo de apps- Si Xcode ya está instalado, no hace falta instalar por separado el paquete de Command Line Tools
- La aceptación de la licencia y la instalación de componentes adicionales también se pueden manejar desde la CLI
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch
Reproducir el proyecto con XcodeGen
- XcodeGen administra la configuración de proyectos Xcode en YAML y regenera el
.xcodeprojcada vez que se compila - Se puede descargar desde GitHub o instalar con Homebrew
brew install xcodegen
- En macOS, un
.xcodeprojparece un archivo, pero es una carpeta; como Xcode cambia constantemente sus archivos internos y referencias, puede generar conflictos o cambios innecesarios en el repositorio Git - Si solo se commitea
project.ymlen Git y se ignora el.xcodeprojgenerado, la configuración del proyecto se mantiene reproducible
xcodegen generate
Configurar la cuenta de desarrollador de Apple y los certificados
- Hay que agregar el Apple ID en
Settings → Accountsde Xcode - Para distribuir y notarizar apps se necesita una cuenta paga de Apple Developer
- El certificado de distribución se crea en
Settings → Accounts → Apple ID → Manage Certificates… → + → Developer ID Application - Apple Development y Developer ID Application tienen usos distintos
- Apple Development se usa para ejecutar en tus propios dispositivos o transferir a un iPhone para depuración local
- Developer ID Application es necesario para distribuir una
.appnotarizada que pase Gatekeeper también en las Mac de otras personas
- Al crear un certificado Developer ID Application, el certificado y su clave privada correspondiente se instalan en el llavero de inicio de sesión
- La clave privada se usa para la firma real y no se puede volver a descargar, así que no la borres y haz backup del llavero
Guardar credenciales de notarización en el llavero
- La notarización de apps para Mac es el proceso de subir una app firmada a Apple para que la analice en busca de malware
notarytoolse autentica con un perfil guardado en el llavero, y al crear el perfil por primera vez hay que ingresar de forma interactiva una contraseña específica de app
xcrun notarytool store-credentials App-Name \
--apple-id "you@example.com" \
--team-id YOUR-TEAM-ID
- No conviene reutilizar el perfil de notarización de otra app: hay que crearlo según el nombre de la app para evitar fallas silenciosas en otros entornos
- La contraseña específica de app es distinta de la contraseña del Apple ID y se genera en
Sign-In & Security → App-Specific Passwordsdel sitio de Apple ID - Si cambias la contraseña del Apple ID, las contraseñas específicas de app pueden expirar sin una advertencia aparte
- Si durante la notarización aparece
401 invalid credentials, antes de sospechar de toda la configuración conviene revisar si expiró la contraseña específica de app
- Si durante la notarización aparece
- Para verificar si el perfil está guardado, usa el siguiente comando
xcrun notarytool history --keychain-profile App-Name
- Si guardas la contraseña específica de app en una bóveda de 1Password accesible para Claude Code, también puedes delegarle al agente la creación de perfiles de notarización para nuevas apps
- Es posible automatizar incluso el ingreso de la contraseña con
--password, pero como puede quedar en el historial del shell, se recomienda ingresarla manualmente una vez y luego conservarla en el llavero o en 1Password
Configuración de firma local y separación de secretos
- El Team ID y el prefijo de bundle necesarios para la firma real se guardan en
Local.xcconfig
cp Local.xcconfig.example Local.xcconfig
BUNDLE_PREFIX = your.real.prefix
DEVELOPMENT_TEAM = YOUR-TEAM-ID
- Agrega
Local.xcconfiga.gitignorepara que la configuración local de cada proyecto no entre al repositorio - La clave privada de firma queda en el llavero de inicio de sesión y la contraseña específica de app para notarización queda en el perfil de llavero de
notarytool, por lo que los secretos no se registran en Git
Automatizar la distribución para Mac con release.sh
- El
scripts/release.shdel repositorio ejecuta en un solo comando todo el pipeline de distribución de la app para Mac
./scripts/release.sh
-
El script funciona en este orden
- Verifica de antemano que existan
xcodegeny el perfil de notarización - Regenera el proyecto con
xcodegen generate - Crea un archivo Release con
xcodebuild archive - Exporta la app firmada con Developer ID usando
ExportOptions.plistyxcodebuild -exportArchive - Empaqueta la app como ZIP y la envía a Apple con
notarytool submit --wait - Adjunta el ticket de notarización con
stapler staple - Verifica con
spctlsi Gatekeeper la permite - Termina el proceso existente y copia la app a
/Applications - Registra la app instalada con
lsregister - Vuelve a verificar el ticket y el estado de Gatekeeper del bundle instalado
- Verifica de antemano que existan
-
Aunque el modelo conozca el procedimiento general de distribución, el usuario debe proporcionar datos específicos del proyecto como nombre del scheme, Team ID, nombre del perfil de notarización y ubicación de instalación
-
Si el primer script falla, hace falta un proceso iterativo de corrección basado en el resultado de la ejecución, pero una vez estabilizado el flujo de trabajo se puede ejecutar solo el comando de distribución sin modificaciones adicionales
-
Evitar fallas y distribuciones incompletas
set -euo pipefaildetiene de inmediato el script si falla cualquier comando, para no tratar un estado incompleto como exitosocd "$(dirname "$0")/.."mueve el script a la raíz del repositorio aunque se lo invoque desde cualquier directorio- El bloque de verificaciones previas revisa
xcodegeny el perfil de notarización antes del archivado, que puede tardar bastante - No solo se inspecciona la app exportada, sino también la app copiada a
/Applications, para detectar casos donde el bundle se dañe durante la copia - Si hace falta otro perfil de notarización, se puede sobrescribir con una variable de entorno
TZED_NOTARY_PROFILE=<name> ./scripts/release.sh
Comunicar las reglas de distribución al agente
- Si
release.shrealiza la distribución con un solo comando,CLAUDE.mdoAGENTS.mdhacen que el agente use ese comando en cada sesión sin instrucciones adicionales - El documento debe registrar la regeneración del proyecto, las pruebas unitarias, la build rápida para macOS y el comando de distribución real
xcodegen generate
swift test
xcodebuild -project YOUR-APP-NAME.xcodeproj \
-scheme YOUR-APP-NAME-macOS \
-destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO build
./scripts/release.sh
- También debe explicitar la diferencia entre las dos rutas de build
- La build con
CODE_SIGNING_ALLOWED=NOes adecuada para CI y verificaciones locales rápidas - Para la distribución real de una app de barra de menú, pasar atributos de cuarentena y sincronizar con iCloud, se necesita
release.sh, que realiza la firma Developer ID y la notarización
- La build con
- Si le indicas que use como referencia el método creado para una app en otro repositorio, Claude Code puede replicar la misma configuración en apps posteriores
Builds y pruebas rápidas sin firma
- Las pruebas unitarias basadas en Swift Package Manager se ejecutan sin una build de Xcode
swift test
- La verificación rápida de compilación de una app para macOS se hace con la firma desactivada
xcodebuild -project TZed.xcodeproj \
-scheme TZed-macOS \
-destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO build
- Las builds para simulador de apps iOS y extensiones de widgets también pueden ejecutarse del mismo modo
xcodebuild -project TZed.xcodeproj \
-scheme TZed-iOS \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO build
- Una build con firma temporal creada con
CODE_SIGNING_ALLOWED=NOpuede usarse para compilar y ejecutar en el simulador - Gatekeeper rechazará esta build y, al no tener prefijo de equipo, permisos como iCloud KVS y App Group no quedarán vinculados, por lo que no puede usarse para distribución
Distribuir de forma headless en un iPhone real
- Para distribuir en dispositivos iOS no hay una etapa de notarización como en las apps para Mac; se usan
xcodebuildydevicectl - La app para dispositivo real se compila y firma con un certificado Apple Development y un perfil de provisioning de desarrollo
xcodebuild -project TZed.xcodeproj \
-scheme TZed-iOS \
-destination 'generic/platform=iOS' \
-allowProvisioningUpdates \
-derivedDataPath build/ios \
archive -archivePath build/TZed-iOS.xcarchive
- La
.appgenerada se instala especificando el UDID del dispositivo conectado
xcrun devicectl device install app \
--device <DEVICE-UDID> build/ios/…/TZed.app
devicectl list devicesmuestra los dispositivos conectados y emparejados, junto con sus UDID- Al especificar
-allowProvisioningUpdates, se obtienen los perfiles de provisioning de desarrollo necesarios
Cómo funciona la firma de código sin GUI
-
Cuando se crea un certificado Developer ID Application, Apple emite el certificado y la Mac genera la clave privada correspondiente; ambos se guardan en el llavero de inicio de sesión
-
codesign, invocado porxcodebuild, firma el binario con la clave privada e incluye en la app el certificado que encadena hasta el certificado raíz de Apple, para que pueda verificarse el firmante -
Si se usa
signingStyle: automatic,xcodebuildselecciona un certificado que coincida con el Team ID y obtiene de Apple los perfiles de provisioning necesarios -
Por lo tanto, no hace falta commitear perfiles de provisioning en el repositorio
-
Permisos, notarización y stapling
- El archivo
.entitlementsde cada target contiene permisos como sandbox, cliente de red, iCloud KVS y App Group - Los permisos se vinculan al firmar con el Team ID real, así que en builds con firma temporal iCloud y App Group no funcionan correctamente
- La firma y la notarización son etapas distintas
- La firma demuestra quién creó la app
- La notarización es el procedimiento mediante el cual Apple analiza una app firmada en busca de malware y emite un ticket
- El stapling adjunta ese ticket a la app para que Gatekeeper confíe en ella incluso sin conexión
- En apps
LSUIElement, como las de barra de menú que usan una UI oculta, la notarización es necesaria para evitar detecciones de XProtect
- El archivo
-
Comandos de verificación manual
codesign -dv --verbose=4 /Applications/TZed.app spctl -a -vvv -t exec /Applications/TZed.app stapler validate /Applications/TZed.appcodesignrevisa el firmante y el certificadospctlverifica si Gatekeeper permitirá la ejecuciónstapler validatecomprueba si el ticket de notarización está adjunto
Herramientas estándar que ejecuta el agente
- No hace falta un servidor MCP separado para builds ni un plugin dedicado; Claude Code ejecuta la CLI estándar en un shell no interactivo
- Las herramientas usadas son
xcodebuild,xcrun notarytool,xcrun stapler,spctl,codesign,devicectl,xcodegenyswift CLAUDE.mdle comunica al agente las reglas de nomenclatura de perfiles de notarización, la diferencia entre build rápida y distribución real, y la regla de usarrelease.shpara distribuir- Solo el primer
notarytool store-credentialsqueda como etapa interactiva; todo el proceso posterior puede automatizarse
Reemplazar tareas de la GUI de Xcode por la CLI
- La creación del
.xcodeprojque Xcode administra se reemplaza conproject.ymlyxcodegen generate ⌘Bo el botón Run corresponden axcodebuild … buildProduct → Archivese maneja conxcodebuild … archive- La distribución de apps desde Organizer puede reemplazarse con
xcodebuild -exportArchive - La carga desde Organizer se realiza con
xcrun notarytool submit --wait - Para adjuntar automáticamente el ticket se usa
xcrun stapler staple - El proceso de instalación de arrastrar y soltar a
/Applicationsse maneja concp -Rylsregister - La acción Run en un iPhone real se reemplaza con
xcodebuild archiveydevicectl device install - Una vez instalados Xcode y XcodeGen, completada la configuración inicial de credenciales, y escritos
release.shyCLAUDE.md, las nuevas builds se pueden distribuir con una frase o un comando
1 comentarios
Opiniones en Hacker News
Ejecuto agentes de IA directamente en mi Mac porque hay muchas cosas que no se pueden resolver en un sandbox, pero el incidente en el que xAI subió un directorio home que incluso incluía claves SSH me hace reconsiderar seriamente esa decisión
No me preocupa demasiado que la máquina en sí se rompa. La mayoría de las cosas las tengo respaldadas o confirmadas en Git, así que podría recuperarlas en unas horas. Pero definitivamente no quiero que mi directorio
.sshse envíe a un agente de IA, y eso no debería intentar bloquearse con prompts, sino a nivel del sistema. Estoy considerando crear un usuario separado y aplicar permisos700al directorio home. La seguridad parece haber retrocedido a los años 90, pero la otra cara es que a cambio se puede terminar el trabajo a una velocidad sin precedentes. Yo también uso este método riesgoso, así que no pretendo criticar a los demásComo soluciones empaquetadas se pueden usar las open source Tart https://tart.run/ o VirtualBuddy https://github.com/insidegui/VirtualBuddy, o también puedes hacer vibe coding de tu propia herramienta que envuelva las API del sistema operativo https://developer.apple.com/documentation/virtualization. Recientemente también apareció Apple Container https://github.com/apple/container
agent. Hace un año fracasé por OAuth y Keychain, pero recientemente logré implementarlo gracias a la indicación de Claude de que se puede usarCLAUDE_CODE_OAUTH_TOKENSe necesita una herramienta auxiliar para configurar permisos con ACL, pero actualmente funciona en macOS. El nivel de riesgo queda más o menos a medio camino entre el sandbox básico de Claude y el aislamiento completo por contenedor o máquina. A medida que el propio sandbox de Claude ha mejorado, dudé del retorno de inversión de este proyecto de defensa en profundidad, pero si consideramos la posibilidad de filtración del directorio home, hay motivos suficientes para seguir. Todavía no lo publiqué como open source, pero estoy dispuesto a hablar con quien tenga interés
Por diversión estoy compilando y probando apps de iOS usando solo Linux, y es sorprendentemente sencillo; https://github.com/xtool-org/xtool funciona muy bien
Incluso en Linux se puede instalar directamente en un iPhone por USB, sin necesidad de subirla a TestFlight ni al App Store. Si no estás seguro, puedes empezar fácilmente pidiéndole a un agente de programación que cree y suba una app Hello World para iOS
Mi proyecto open source Axiom ayuda a que las herramientas de ejecución de código realicen eficazmente tareas de desarrollo para sistemas operativos de Apple
Incluye varias tecnologías y agentes, además de herramientas para LLM como
xclog,xcprof,xcsymyxcui. Estas exponen las funciones necesarias de forma eficiente en tokens y también son útiles para tecnologías o agentes fuera de Axiom. Axiom: https://charleswiltgen.github.io/Axiom/, herramientas CLI: https://charleswiltgen.github.io/Axiom/tools/Leer la frase “Le hice a Claude Code crear un script que, sin abrir Xcode, realizara el archivado, la firma con Developer ID, la notarización, el stapling y la instalación en
/Applications, y que se detuviera claramente si fallaba cualquier paso” se siente extraño. Porque el texto que estamos leyendo también parece como si Claude nos estuviera hablandoTambién se siente raro que en casi todos los pasos del artículo se repita la indicación de volver a preguntarle al LLM, con frases como “muéstrale este artículo a Claude Code o a la herramienta de programación con LLM que prefieras y deja que lo resuelva”, “si no sabes, pídele al LLM que te ayude a configurarlo”, “el objetivo de usar un LLM es evitar el trabajo manual no deseado” o “pídele que cree un flujo de trabajo”
Desde mi experiencia desarrollando apps para Mac e iOS con agentes durante más de un año, creo que este artículo da un mal consejo. En particular, el excelente Xcode MCP requiere que Xcode esté en ejecución
Con las herramientas MCP se puede acceder a varias funciones de forma mucho más rápida y concisa que con
xcodebuild, y también se pueden generar y renderizar#Preview, algo imposible si Xcode está cerrado. El MCP y el agente integrado de Xcode 27 también controlan muy bien los simuladores mediante DeviceHub. DeviceHub reemplaza a Simulator.app, y parece que el agente puede entenderlo y manejarlo sin tener que manipular de forma indirecta las API de accesibilidad con herramientas comoaxeXcode MCP no es perfecto. El problema de que aparezca una ventana de permisos cada vez que se ejecuta el agente lo esquivé aprobándola automáticamente con Keyboard Maestro. Aun así, Xcode 27 mejoró mucho la usabilidad del vibe coding en Xcode. Ahora ejecuto Codex desde la propia UI de agente de Xcode, sin manejar MCP por separado, y ese entorno de ejecución guía mejor al modelo para que use las herramientas disponibles. Al final se obtiene lo más importante: feedback más rápido y verificación más robusta
Xcode-mcp-proxy, que actúa como proxy de Xcode MCP, hace clic en los cuadros de diálogo y también recupera la conexión. Me pregunto si para probar apps de macOS usa Peekaboo o automatización común de capturas de pantallaSi quieres tocar lo menos posible los IDE de iOS y Android, React Native y Expo también son una buena opción
Xcode y Android Studio solo se usan para instalar los simuladores de dispositivos, y el resto, incluida la distribución en las tiendas, lo maneja la cadena de herramientas de Expo. Incluso en Windows, donde no se puede instalar Xcode, con una app administrada de Expo puedes ejecutar una app de iOS directamente en un iPhone. Como punto de partida basado en RN y Expo, Ignite es excelente y está bien mantenido: https://github.com/infinitered/ignite
Estrictamente hablando, se sigue usando Xcode. Xcode es una GUI muy llena de bugs que envuelve varias utilidades y apps UNIX a nivel de sistema, por lo general estables
Lanzar apps desde la CLI es una práctica antigua y, al menos, ha sido posible desde 2012, cuando publiqué mi primera app con Xcode
Al menos para parte del debugging, todo el mundo no tiene más remedio que usar Xcode
Los agentes ya pueden manejar la mayor parte del build desde la línea de comandos, y si se les dan instrucciones explícitas según el caso, pueden compilar más rápido. Si usas los grupos de sincronización del sistema de archivos de Xcode, XcodeGen quizá sea trabajo adicional innecesario
En iOS, lo que más recomendaría es agregarle al agente funcionalidad de App Store Connect: https://github.com/rorkai/App-Store-Connect-CLI. Así no solo no hace falta tener Xcode siempre abierto, sino que tampoco necesitas estar cerca de la MacBook. Puedes hacer cambios con Codex desde el iPhone, indicarle a
ascque compile y suba a TestFlight, descargar la nueva versión, ejecutarla e iterarMe sigue preocupando la realidad de que los desarrolladores sin presupuesto para comprar una Mac quedan prácticamente excluidos del desarrollo iOS. Cada vez que suben los precios de los productos Apple, aumenta la cantidad de personas que no pueden participar en el desarrollo
Si vas a hacer vibe coding de una app, a largo plazo Expo habría sido una mejor opción. Al estar basado en React, hay muchos más datos de entrenamiento de LLM con código React que con Swift, y la calidad del código resultante también es mayor
Todo se puede hacer desde la línea de comandos y, al ser una vista web, el debugging también es fácil. Una vez compilada como app nativa de iOS, se siente similar a otras apps nativas. Con la combinación de Expo y Fastlane se puede automatizar por completo el envío y la distribución en iOS, y subir una nueva versión a App Store con un solo comando