
Apple Native Dev
- 1 installs
- 17 repo stars
- Updated July 16, 2026
- blacktop/dotfiles
apple-native-dev is a Claude Code skill that builds, signs, and deploys iOS/macOS apps using XcodeGen, justfile, and Zed Editor without the Xcode UI.
About
apple-native-dev is a Claude Code skill for building, signing, and deploying iOS and macOS apps using CLI-only workflows and the Zed Editor instead of the Xcode UI. It covers generating projects with XcodeGen, managing credentials with xcconfig files, wiring up the Swift LSP in Zed, building and deploying to simulators or physical devices via justfile commands, and scaffolding GitHub Actions CI. A developer uses it to set up or run an Apple app project without depending on Xcode's interface.
- Builds, signs, and deploys iOS/macOS apps from the CLI and Zed Editor without the Xcode UI
- Uses XcodeGen, justfile commands, xcode-build-server, and devicectl for the full build-to-device loop
- Keeps the Team ID out of git via xcconfig files and scaffolds GitHub Actions CI for Apple apps
Apple Native Dev by the numbers
- 1 all-time installs (skills.sh)
- Ranked #959 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
apple-native-dev capabilities & compatibility
- Capabilities
- ios build · code signing · device deploy · ci cd
- Works with
- github
- Use cases
- devops · ci cd
- Platforms
- macOS
- IDEs
- zed
What apple-native-dev says it does
Build, sign, and deploy iOS/macOS apps using Zed Editor and CLI-only workflows without Xcode UI.
Use xcconfig files to keep Team ID out of git. See [references/credentials.md](references/credentials.md).
just ios::device-deploy # Build + install + launch on iPhone
npx skills add https://github.com/blacktop/dotfiles --skill apple-native-devAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 17 |
| Last updated | July 16, 2026 |
| Repository | blacktop/dotfiles ↗ |
What it does
Build, sign, and deploy iOS or macOS apps from the CLI and Zed Editor without using the Xcode UI.
Who is it for?
Developers building iOS/macOS apps from the CLI and Zed who want to avoid the Xcode UI.
Skip if: Developers who prefer or require the full Xcode IDE workflow.
When should I use this skill?
Setting up or building an iOS/macOS project without Xcode UI, or troubleshooting CLI Apple builds.
What you get
An iOS/macOS project is generated, credentials stay out of git, LSP works in Zed, and the app builds and deploys to simulator or device via CLI.
- generated Xcode project from project.yml
- xcconfig-based credential setup
- Zed LSP configuration
By the numbers
- 6 documented use cases in the description
- 5 bundled templates in assets/templates/
Files
Apple Native Development (CLI + Zed)
Build iOS/macOS apps without Xcode UI using XcodeGen, justfile, and Zed Editor.
Core Workflow
1. Project Setup
# Install tools
brew install xcodegen just xcode-build-server
# Generate Xcode project from project.yml
cd Apps/MyApp && xcodegen generate2. Credential Management
Use xcconfig files to keep Team ID out of git. See references/credentials.md.
Apps/MyApp/Configs/
├── Project.xcconfig # Committed (loads local + defaults)
├── Project.local.xcconfig # Gitignored (your Team ID)
└── Project.local.xcconfig.example # Committed templateQuick setup (auto-detects Team ID from keychain):
just ios::setup-credentials3. Zed Editor LSP
Generate buildServer.json for code completion. See references/zed-setup.md.
just ios::setup-lsp
# Or manually:
xcode-build-server config -project Apps/MyApp/MyApp.xcodeproj -scheme MyApp4. Build & Deploy
Simulator:
just ios::build # Build for simulator
just ios::run # Build + install + launchPhysical Device (iOS 17+):
just ios::device-deploy # Build + install + launch on iPhoneSee references/justfile.md for all commands.
5. CI/CD
GitHub Actions scaffold at .github/workflows/ios-build.yml.disabled. See references/ci-cd.md.
Key Files
| File | Purpose |
|---|---|
project.yml | XcodeGen project definition |
Configs/Project.xcconfig | Base build settings (committed) |
Configs/Project.local.xcconfig | Credentials (gitignored) |
buildServer.json | LSP configuration (gitignored) |
justfile or ios.just | Build commands |
Gitignore Patterns
# Credentials
*.local.xcconfig
!*.local.xcconfig.example
# Editor integration
buildServer.json
# XcodeGen output
*.xcodeproj/Templates
Copy from assets/templates/ when bootstrapping:
Project.xcconfig- Base xcconfig (committed)Project.local.xcconfig.example- Credential template (committed)project.yml- XcodeGen project definitionios.just- Justfile module for iOS commandsios-build.yml.disabled- GitHub Actions scaffold
Troubleshooting
"Unable to find module dependency" in CLI builds: Disable hardened runtime during development:
# project.yml
settings:
base:
ENABLE_HARDENED_RUNTIME: NO
ENABLE_ENHANCED_SECURITY: NONo code completion in Zed: Regenerate after building: just ios::setup-lsp
Device install fails: Ensure device is trusted and connected: just ios::devices
# iOS Build Workflow (Disabled)
# Rename to ios-build.yml when ready to enable CI/CD
#
# Required GitHub Secrets:
# DEVELOPMENT_TEAM - Apple Team ID (10 chars)
# CERTIFICATES_P12_B64 - Base64-encoded .p12 certificate
# CERTIFICATES_P12_PASS - Password for .p12
name: iOS Build
on:
push:
branches: [main]
paths:
- 'Sources/**'
- 'Apps/**'
- 'Package.swift'
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
DERIVED_DATA: .build/DerivedData
jobs:
build:
name: Build iOS App
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Show Xcode version
run: xcodebuild -version
- name: Install XcodeGen
run: brew install xcodegen
- name: Generate Xcode Project
run: cd Apps/MyApp && xcodegen generate
# Unsigned simulator build
- name: Build for Simulator
run: |
xcodebuild -project Apps/MyApp/MyApp.xcodeproj \
-scheme MyApp \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-derivedDataPath ${{ env.DERIVED_DATA }} \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
build
# Uncomment for signed builds (requires secrets)
# - name: Import Certificates
# uses: apple-actions/import-codesign-certs@v2
# with:
# p12-file-base64: ${{ secrets.CERTIFICATES_P12_B64 }}
# p12-password: ${{ secrets.CERTIFICATES_P12_PASS }}
#
# - name: Build for Device
# run: |
# xcodebuild -project Apps/MyApp/MyApp.xcodeproj \
# -scheme MyApp \
# -sdk iphoneos \
# -destination 'generic/platform=iOS' \
# -derivedDataPath ${{ env.DERIVED_DATA }} \
# DEVELOPMENT_TEAM=${{ secrets.DEVELOPMENT_TEAM }} \
# -allowProvisioningUpdates \
# build
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-Simulator
path: ${{ env.DERIVED_DATA }}/Build/Products/Debug-iphonesimulator/MyApp.app
retention-days: 7
test:
name: Run Tests
runs-on: macos-14
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Run Swift Tests
run: swift test
# iOS App Build Commands
# Usage: just ios::<recipe>
set positional-arguments := true
# Configuration - customize these
iphone_sim := "YOUR_SIMULATOR_UDID"
bundle_id := "com.example.myapp"
project := "Apps/MyApp/MyApp.xcodeproj"
# Default: list recipes
[private]
default:
@just --list --justfile {{justfile()}}
# -----------------------------------------------------------------------------
# Build
# -----------------------------------------------------------------------------
# Build for simulator
build:
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-derivedDataPath .build/DerivedData build
# Generate Xcode project from project.yml
xcodegen:
cd Apps/MyApp && xcodegen generate
# Open in Xcode
xcode:
open {{project}}
# -----------------------------------------------------------------------------
# Simulator
# -----------------------------------------------------------------------------
# Boot simulator
boot:
xcrun simctl boot {{iphone_sim}} 2>/dev/null || true
open -a Simulator
# Install on simulator
install:
xcrun simctl install {{iphone_sim}} \
.build/DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app
# Launch on simulator
launch:
xcrun simctl launch {{iphone_sim}} {{bundle_id}}
# Build + install + launch
run: build install launch
# List simulators
list:
xcrun simctl list devices available | grep -E "iPhone|iPad"
# -----------------------------------------------------------------------------
# Physical Device (iOS 17+)
# -----------------------------------------------------------------------------
# List connected devices
devices:
xcrun devicectl list devices 2>/dev/null | head -10
# Build for device
device-build:
@echo "Regenerating Xcode project..."
cd Apps/MyApp && xcodegen generate
@echo "Building for device..."
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-derivedDataPath .build/DerivedData \
-allowProvisioningUpdates \
build
# Install on connected device
device-install:
#!/usr/bin/env bash
set -e
APP_PATH=".build/DerivedData/Build/Products/Debug-iphoneos/MyApp.app"
if [ ! -d "$APP_PATH" ]; then
echo "Error: App not found. Run 'just ios::device-build' first"
exit 1
fi
DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -m1 'iPhone' | awk '{print $NF}')
if [ -z "$DEVICE_ID" ]; then
echo "Error: No iPhone connected"
exit 1
fi
echo "Installing to device: $DEVICE_ID"
xcrun devicectl device install app --device "$DEVICE_ID" "$APP_PATH"
# Launch on connected device
device-launch:
#!/usr/bin/env bash
set -e
DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -m1 'iPhone' | awk '{print $NF}')
if [ -z "$DEVICE_ID" ]; then
echo "Error: No iPhone connected"
exit 1
fi
xcrun devicectl device process launch --device "$DEVICE_ID" {{bundle_id}}
# Full device deploy
device-deploy: device-build device-install device-launch
# Release build
device-release:
cd Apps/MyApp && xcodegen generate
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-derivedDataPath .build/DerivedData \
-configuration Release \
-allowProvisioningUpdates \
build
# -----------------------------------------------------------------------------
# Setup
# -----------------------------------------------------------------------------
# Setup credentials (auto-detect Team ID)
setup-credentials:
#!/usr/bin/env bash
set -e
LOCAL_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig"
EXAMPLE_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig.example"
if [ -f "$LOCAL_CONFIG" ]; then
echo "Config exists: $LOCAL_CONFIG"
exit 0
fi
echo "Finding Team ID..."
TEAM_ID=$(security find-certificate -c "Apple Development" -p 2>/dev/null | \
openssl x509 -noout -subject 2>/dev/null | \
grep -o 'OU=[^,]*' | cut -d= -f2 || echo "")
if [ -z "$TEAM_ID" ]; then
echo "Could not detect Team ID. Copy manually:"
echo " cp $EXAMPLE_CONFIG $LOCAL_CONFIG"
exit 1
fi
echo "Found Team ID: $TEAM_ID"
cp "$EXAMPLE_CONFIG" "$LOCAL_CONFIG"
sed -i '' "s/YOUR_TEAM_ID_HERE/$TEAM_ID/" "$LOCAL_CONFIG"
echo "Created: $LOCAL_CONFIG"
# Setup Zed LSP
setup-lsp:
#!/usr/bin/env bash
set -e
if ! command -v xcode-build-server &> /dev/null; then
echo "Installing xcode-build-server..."
brew install xcode-build-server
fi
echo "Generating buildServer.json..."
xcode-build-server config -project {{project}} -scheme MyApp
echo "LSP setup complete!"
// Project.local.xcconfig.example
// Copy to Project.local.xcconfig and fill in your values
// DO NOT commit Project.local.xcconfig - it contains personal credentials!
//
// Setup:
// cp Project.local.xcconfig.example Project.local.xcconfig
// vim Project.local.xcconfig
//
// Find your Team ID:
// security find-certificate -c "Apple Development" -p | \
// openssl x509 -noout -subject | grep -o 'OU=[^,]*'
// Your Apple Development Team ID (10 characters)
DEVELOPMENT_TEAM_OVERRIDE = YOUR_TEAM_ID_HERE
// Optional: Override code signing identity
// CODE_SIGN_IDENTITY = iPhone Developer: Your Name (XXXXXXXXXX)
// Project.xcconfig
// Base configuration for iOS/macOS app
// Credentials loaded from optional local file or environment variables
// Include local credentials if present (gitignored)
#include? "Project.local.xcconfig"
// Development Team - loaded from local file or CI environment
DEVELOPMENT_TEAM = $(DEVELOPMENT_TEAM_OVERRIDE)
// Code signing configuration
CODE_SIGN_STYLE = Automatic
CODE_SIGN_IDENTITY = Apple Development
// Provisioning - let Xcode manage automatically
PROVISIONING_PROFILE_SPECIFIER =
name: MyApp
options:
bundleIdPrefix: com.example
deploymentTarget:
iOS: "18.0"
xcodeVersion: "16.0"
# Load credentials from xcconfig files
configFiles:
Debug: Configs/Project.xcconfig
Release: Configs/Project.xcconfig
settings:
base:
# Disabled during development - causes CLI build issues
ENABLE_HARDENED_RUNTIME: NO
ENABLE_ENHANCED_SECURITY: NO
# Xcode 16+ recommended settings
DEAD_CODE_STRIPPING: YES
ENABLE_USER_SCRIPT_SANDBOXING: YES
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS: YES
SWIFT_EMIT_LOC_STRINGS: YES
# App metadata
MARKETING_VERSION: "1.0.0"
CURRENT_PROJECT_VERSION: "1"
INFOPLIST_KEY_CFBundleDisplayName: MyApp
# Team ID loaded from Configs/Project.xcconfig
packages:
MyApp:
path: ../..
targets:
MyApp:
type: application
platform: iOS
sources:
- Sources
- path: Resources/Assets.xcassets
dependencies:
- package: MyApp
product: MyAppCore
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: com.example.myapp
INFOPLIST_FILE: Resources/Info.plist
SWIFT_VERSION: "6.0"
GENERATE_INFOPLIST_FILE: false
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
schemes:
MyApp:
build:
targets:
MyApp: all
run:
config: Debug
executable: MyApp
test:
config: Debug
profile:
config: Release
archive:
config: Release
CI/CD for Apple Apps (GitHub Actions)
GitHub Actions workflows for iOS/macOS apps.
Scaffold Workflow
Create .github/workflows/ios-build.yml.disabled (rename to enable):
name: iOS Build
on:
push:
branches: [main]
paths:
- 'Sources/**'
- 'Apps/**'
- 'Package.swift'
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
DERIVED_DATA: .build/DerivedData
jobs:
build:
name: Build iOS App
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode.app
- name: Install XcodeGen
run: brew install xcodegen
- name: Generate Xcode Project
run: cd Apps/MyApp && xcodegen generate
# Unsigned simulator build (no secrets required)
- name: Build for Simulator
run: |
xcodebuild -project Apps/MyApp/MyApp.xcodeproj \
-scheme MyApp \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-derivedDataPath ${{ env.DERIVED_DATA }} \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
build
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: MyApp-Simulator
path: ${{ env.DERIVED_DATA }}/Build/Products/Debug-iphonesimulator/MyApp.app
retention-days: 7
test:
name: Run Tests
runs-on: macos-14
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Run Swift Tests
run: swift testSigned Device Builds
Requires GitHub Secrets:
DEVELOPMENT_TEAM- Apple Team ID (10 chars)CERTIFICATES_P12_B64- Base64-encoded .p12 certificateCERTIFICATES_P12_PASS- Password for .p12
- name: Import Certificates
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12_B64 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASS }}
- name: Build for Device
run: |
xcodebuild -project Apps/MyApp/MyApp.xcodeproj \
-scheme MyApp \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-derivedDataPath ${{ env.DERIVED_DATA }} \
DEVELOPMENT_TEAM=${{ secrets.DEVELOPMENT_TEAM }} \
-allowProvisioningUpdates \
buildExport .p12 Certificate
# List identities
security find-identity -v -p codesigning
# Export (will prompt for password)
security export -k ~/Library/Keychains/login.keychain-db \
-t identities \
-f pkcs12 \
-o certificate.p12
# Base64 encode for GitHub Secret
base64 -i certificate.p12 | pbcopyTestFlight Deployment
- name: Archive
run: |
xcodebuild archive \
-project Apps/MyApp/MyApp.xcodeproj \
-scheme MyApp \
-archivePath .build/MyApp.xcarchive \
DEVELOPMENT_TEAM=${{ secrets.DEVELOPMENT_TEAM }}
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath .build/MyApp.xcarchive \
-exportPath .build/export \
-exportOptionsPlist ExportOptions.plist
- name: Upload to TestFlight
run: |
xcrun altool --upload-app \
--type ios \
--file .build/export/MyApp.ipa \
--apiKey ${{ secrets.APP_STORE_API_KEY_ID }} \
--apiIssuer ${{ secrets.APP_STORE_ISSUER_ID }}macOS Notarization
- name: Notarize
run: |
xcrun notarytool submit MyApp.zip \
--key ${{ secrets.APP_STORE_API_KEY_P8 }} \
--key-id ${{ secrets.APP_STORE_API_KEY_ID }} \
--issuer ${{ secrets.APP_STORE_ISSUER_ID }} \
--wait
- name: Staple
run: xcrun stapler staple MyApp.appRequired Secrets Summary
| Secret | Purpose | How to Get |
|---|---|---|
DEVELOPMENT_TEAM | Team ID | Apple Developer Portal |
CERTIFICATES_P12_B64 | Signing cert | Export from Keychain |
CERTIFICATES_P12_PASS | Cert password | Set during export |
APP_STORE_API_KEY_ID | API key ID | App Store Connect |
APP_STORE_ISSUER_ID | Issuer ID | App Store Connect |
APP_STORE_API_KEY_P8 | API key file | App Store Connect |
Caching Dependencies
- name: Cache SPM
uses: actions/cache@v4
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('Package.resolved') }}
restore-keys: ${{ runner.os }}-spm-Matrix Builds
strategy:
matrix:
destination:
- 'platform=iOS Simulator,name=iPhone 16 Pro'
- 'platform=iOS Simulator,name=iPad Pro 13-inch'
steps:
- name: Build
run: xcodebuild ... -destination '${{ matrix.destination }}'Secure Credential Management (xcconfig)
Keep DEVELOPMENT_TEAM, signing identities, and other sensitive build settings out of git using xcconfig files.
Directory Structure
Apps/MyApp/Configs/
├── Project.xcconfig # Committed (loads local + defaults)
├── Project.local.xcconfig # Gitignored (your Team ID)
└── Project.local.xcconfig.example # Committed (template)File Contents
Project.xcconfig (committed)
// Base configuration - loads credentials from optional local file
#include? "Project.local.xcconfig"
// Development Team from local file or CI environment
DEVELOPMENT_TEAM = $(DEVELOPMENT_TEAM_OVERRIDE)
// Code signing defaults
CODE_SIGN_STYLE = Automatic
CODE_SIGN_IDENTITY = Apple Development
PROVISIONING_PROFILE_SPECIFIER =Project.local.xcconfig.example (committed template)
// Copy to Project.local.xcconfig and fill in your values
// DO NOT commit Project.local.xcconfig!
//
// Find your Team ID:
// security find-certificate -c "Apple Development" -p | \
// openssl x509 -noout -subject | grep -o 'OU=[^,]*'
DEVELOPMENT_TEAM_OVERRIDE = YOUR_TEAM_ID_HERE
// Optional: Override signing identity
// CODE_SIGN_IDENTITY = iPhone Developer: Your Name (XXXXXXXXXX)Project.local.xcconfig (gitignored, your file)
DEVELOPMENT_TEAM_OVERRIDE = ABCD123456XcodeGen Integration
Add to project.yml:
configFiles:
Debug: Configs/Project.xcconfig
Release: Configs/Project.xcconfig
settings:
base:
# Team ID comes from xcconfig, not hereGitignore Patterns
# xcconfig credentials
*.local.xcconfig
!*.local.xcconfig.exampleAuto-Detection Script
Justfile recipe to auto-detect Team ID from keychain:
setup-credentials:
#!/usr/bin/env bash
set -e
LOCAL_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig"
EXAMPLE_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig.example"
if [ -f "$LOCAL_CONFIG" ]; then
echo "Local config exists: $LOCAL_CONFIG"
exit 0
fi
echo "Finding Team ID..."
TEAM_ID=$(security find-certificate -c "Apple Development" -p 2>/dev/null | \
openssl x509 -noout -subject 2>/dev/null | \
grep -o 'OU=[^,]*' | cut -d= -f2 || echo "")
if [ -z "$TEAM_ID" ]; then
echo "Could not auto-detect Team ID"
echo "Copy and edit manually:"
echo " cp $EXAMPLE_CONFIG $LOCAL_CONFIG"
exit 1
fi
echo "Found Team ID: $TEAM_ID"
cp "$EXAMPLE_CONFIG" "$LOCAL_CONFIG"
sed -i '' "s/YOUR_TEAM_ID_HERE/$TEAM_ID/" "$LOCAL_CONFIG"
echo "Created: $LOCAL_CONFIG"CI/CD Environment Variables
For GitHub Actions, pass credentials via environment:
env:
DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
- name: Build
run: |
xcodebuild ... DEVELOPMENT_TEAM=${{ env.DEVELOPMENT_TEAM }}Security Best Practices
| Pattern | Status | Purpose |
|---|---|---|
*.local.xcconfig | Gitignored | Personal Team ID |
*.p8 | Gitignored | APNs keys |
*.p12 | Gitignored | Signing certificates |
buildServer.json | Gitignored | Machine-specific paths |
Justfile Build Commands
CLI build commands for iOS/macOS development using just.
Installation
brew install justProject Structure
project-root/
├── justfile # Main entry, loads modules
├── ios.just # iOS/watchOS commands (mod ios)
├── macos.just # macOS commands (mod macos)
└── Apps/
└── MyApp/
└── project.ymlMain justfile
# Load submodules
mod ios
mod macos
# Swift Package commands
build:
swift build
test:
swift test
clean:
swift package clean
rm -rf .buildiOS Module (ios.just)
# iOS & watchOS App
# Usage: just ios::<recipe>
set positional-arguments := true
# Configuration
iphone_sim := "FBAF6E3F-71E6-4C2A-8773-5D0099B8FAA1"
bundle_id := "com.example.myapp"
project := "Apps/MyApp/MyApp.xcodeproj"
# -----------------------------------------------------------------------------
# Build
# -----------------------------------------------------------------------------
# Build for simulator
build:
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-derivedDataPath .build/DerivedData build
# Generate Xcode project
xcodegen:
cd Apps/MyApp && xcodegen generate
# -----------------------------------------------------------------------------
# Simulator
# -----------------------------------------------------------------------------
# Boot simulator
boot:
xcrun simctl boot {{iphone_sim}} 2>/dev/null || true
open -a Simulator
# Install app
install:
xcrun simctl install {{iphone_sim}} \
.build/DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app
# Launch app
launch:
xcrun simctl launch {{iphone_sim}} {{bundle_id}}
# Build + install + launch
run: build install launch
# List simulators
list:
xcrun simctl list devices available | grep -E "iPhone|iPad"
# -----------------------------------------------------------------------------
# Physical Device (iOS 17+)
# -----------------------------------------------------------------------------
# List connected devices
devices:
xcrun devicectl list devices 2>/dev/null | head -10
# Build for device (uses xcconfig for Team ID)
device-build:
@echo "Regenerating Xcode project..."
cd Apps/MyApp && xcodegen generate
@echo "Building for device..."
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-derivedDataPath .build/DerivedData \
-allowProvisioningUpdates \
build
# Install on connected device
device-install:
#!/usr/bin/env bash
set -e
APP_PATH=".build/DerivedData/Build/Products/Debug-iphoneos/MyApp.app"
if [ ! -d "$APP_PATH" ]; then
echo "Error: App not found. Run 'just ios::device-build' first"
exit 1
fi
DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -m1 'iPhone' | awk '{print $NF}')
if [ -z "$DEVICE_ID" ]; then
echo "Error: No iPhone connected"
exit 1
fi
echo "Installing to device: $DEVICE_ID"
xcrun devicectl device install app --device "$DEVICE_ID" "$APP_PATH"
# Launch on device
device-launch:
#!/usr/bin/env bash
set -e
DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep -m1 'iPhone' | awk '{print $NF}')
if [ -z "$DEVICE_ID" ]; then
echo "Error: No iPhone connected"
exit 1
fi
xcrun devicectl device process launch --device "$DEVICE_ID" {{bundle_id}}
# Full device deploy
device-deploy: device-build device-install device-launch
# Release build for device
device-release:
cd Apps/MyApp && xcodegen generate
xcodebuild -project {{project}} -scheme MyApp \
-sdk iphoneos \
-destination 'generic/platform=iOS' \
-derivedDataPath .build/DerivedData \
-configuration Release \
-allowProvisioningUpdates \
build
# -----------------------------------------------------------------------------
# Setup
# -----------------------------------------------------------------------------
# Setup credentials (auto-detect Team ID)
setup-credentials:
#!/usr/bin/env bash
set -e
LOCAL_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig"
EXAMPLE_CONFIG="Apps/MyApp/Configs/Project.local.xcconfig.example"
if [ -f "$LOCAL_CONFIG" ]; then
echo "Config exists: $LOCAL_CONFIG"
exit 0
fi
TEAM_ID=$(security find-certificate -c "Apple Development" -p 2>/dev/null | \
openssl x509 -noout -subject 2>/dev/null | \
grep -o 'OU=[^,]*' | cut -d= -f2 || echo "")
if [ -z "$TEAM_ID" ]; then
echo "Could not detect Team ID. Copy manually:"
echo " cp $EXAMPLE_CONFIG $LOCAL_CONFIG"
exit 1
fi
echo "Found Team ID: $TEAM_ID"
cp "$EXAMPLE_CONFIG" "$LOCAL_CONFIG"
sed -i '' "s/YOUR_TEAM_ID_HERE/$TEAM_ID/" "$LOCAL_CONFIG"
echo "Created: $LOCAL_CONFIG"
# Setup Zed LSP
setup-lsp:
#!/usr/bin/env bash
set -e
if ! command -v xcode-build-server &> /dev/null; then
brew install xcode-build-server
fi
xcode-build-server config -project {{project}} -scheme MyApp
echo "LSP setup complete!"devicectl Reference (iOS 17+)
xcrun devicectl replaces ios-deploy for modern device management:
# List devices
xcrun devicectl list devices
# Install app
xcrun devicectl device install app --device <UDID> /path/to/App.app
# Launch app
xcrun devicectl device process launch --device <UDID> com.example.app
# Uninstall app
xcrun devicectl device uninstall app --device <UDID> com.example.appCommon Patterns
Specific simulator by UDID:
iphone_sim := "FBAF6E3F-71E6-4C2A-8773-5D0099B8FAA1"Variable destinations:
build-iphone:
xcodebuild ... -destination 'platform=iOS Simulator,name=iPhone 16 Pro'
build-ipad:
xcodebuild ... -destination 'platform=iOS Simulator,name=iPad Pro 13-inch'Parallel builds:
build-all: (build-iphone) (build-watch)Zed Editor Setup for Apple Development
Configure Zed with full Swift LSP support for iOS/macOS projects using xcode-build-server.
Prerequisites
# Install xcode-build-server
brew install xcode-build-server
# Verify installation
xcode-build-server --versionGenerate buildServer.json
The buildServer.json file bridges SourceKit-LSP to understand your Xcode project structure.
# From project root (after building at least once)
xcode-build-server config \
-project Apps/MyApp/MyApp.xcodeproj \
-scheme MyAppThis creates buildServer.json in the project root:
{
"name": "xcode build server",
"version": "0.2",
"bspVersion": "2.0",
"languages": ["swift", "objective-c", "objective-cpp", "c", "cpp"],
"argv": [
"/opt/homebrew/bin/xcode-build-server"
],
"workspace": "/path/to/project",
"build_root": "/path/to/project",
"indexStorePath": "..."
}Justfile Recipe
setup-lsp:
#!/usr/bin/env bash
set -e
if ! command -v xcode-build-server &> /dev/null; then
echo "Installing xcode-build-server..."
brew install xcode-build-server
fi
echo "Generating buildServer.json..."
xcode-build-server config -project Apps/MyApp/MyApp.xcodeproj -scheme MyApp
echo "LSP setup complete! Restart Zed."Zed Settings (Optional)
Zed auto-detects Swift via SourceKit-LSP. To customize:
// ~/.config/zed/settings.json
{
"languages": {
"Swift": {
"language_servers": ["sourcekit-lsp"],
"format_on_save": "off"
}
}
}Workflow
1. Build project first (creates index):
just ios::build
# or: xcodebuild -project ... build2. Generate buildServer.json:
just ios::setup-lsp3. Open in Zed - code completion works immediately
4. After scheme/target changes, regenerate:
xcodegen generate && just ios::setup-lspGitignore
# Machine-specific LSP paths
buildServer.jsonTroubleshooting
No code completion:
- Build the project first to generate index
- Regenerate:
just ios::setup-lsp - Restart Zed
Wrong project configuration:
- Verify scheme:
xcodebuild -list -project Apps/MyApp/MyApp.xcodeproj - Regenerate with correct scheme
SourceKit-LSP crashes:
- Check Xcode version matches:
xcode-select -p - Update xcode-build-server:
brew upgrade xcode-build-server
Multiple Schemes
For projects with multiple schemes (iOS + Watch):
# Primary scheme for LSP
xcode-build-server config -project Apps/MyApp/MyApp.xcodeproj -scheme MyAppLSP uses one scheme at a time. Switch schemes by regenerating.
Comparison with Xcode
| Feature | Zed + xcode-build-server | Xcode |
|---|---|---|
| Code completion | Yes | Yes |
| Go to definition | Yes | Yes |
| Find references | Yes | Yes |
| Inline errors | Yes | Yes |
| Refactoring | Limited | Full |
| Build/Run | Via CLI | Integrated |
| Interface Builder | No | Yes |
Related skills
FAQ
How does apple-native-dev keep the Team ID out of git?
It uses xcconfig files: a committed Project.xcconfig, a gitignored Project.local.xcconfig with your Team ID, and a committed .example template.
How do you deploy to a physical device?
Run just ios::device-deploy to build, install, and launch on an iPhone (iOS 17+).