Build and distribute Dev Client APK/IPA via EAS Build with automatic Android Keystore generation. Automatically runs setup if eas.json is missing...
Dev Client APK/IPA γ EAS Build γ§ι δΏ‘γγΎγγ
IMPORTANT: Execute npx commands from the app root directory (APPNAME directory, not the .git root).
Native changes that require a rebuild:
For JS-only changes, use /ota instead (faster and free).
Claude Code automatically handles Android Keystore generation prompts based on the platform:
When this skill is invoked:
Before executing the build, check if Dev Client setup is complete.
# Check if eas.json exists
if [ ! -f "eas.json" ]; then
echo "eas.json not found. Running initial setup..."
fi
If eas.json doesn't exist, execute the following setup steps (same as /setup-dev-client):
Determine target platform - Ask user using AskUserQuestion
AskUserQuestion({
questions: [{
question: "Which platforms do you want to target?",
header: "Platforms",
multiSelect: false,
options: [
{
label: "Android only",
description: "Build APK for Android devices. Free tier available."
},
{
label: "iOS only",
description: "Build IPA for iOS devices. Requires Apple Developer account ($99/year)."
},
{
label: "Both",
description: "Build for both Android and iOS platforms."
}
]
}]
})
Install expo-dev-client (idempotent)
if ! grep -q "expo-dev-client" package.json; then
npx expo install expo-dev-client
fi
Generate native projects (idempotent)
# Based on user selection
if [Android selected] && [ ! -d "android" ]; then
npx expo prebuild --platform android
fi
if [iOS selected] && [ ! -d "ios" ]; then
npx expo prebuild --platform ios
fi
Create eas.json (idempotent)
if [ ! -f "eas.json" ]; then
# Create eas.json with appropriate platform config
cat > eas.json << 'EOF'
{
"build": {
"dev": {
"developmentClient": true,
"distribution": "internal",
"channel": "dev",
"android": {
"buildType": "apk"
}
}
}
}
EOF
fi
Continue to Step 1 (Detect Platform)
If eas.json exists: Skip setup and proceed directly to Step 1.
const platform = process.platform; // 'darwin', 'win32', 'linux'
Ensure you're in the correct directory:
cd APPNAME # Move from project root to app directory
CRITICAL: Temporary Script Cleanup
The automation scripts MUST be deleted after execution to avoid leaving temporary files in the project directory. Both implementations (expect and node-pty) include automatic cleanup mechanisms.
IMPORTANT: Run in Background
EAS Build takes significant time to complete:
ALWAYS use run_in_background: true parameter with the Bash tool to avoid blocking the conversation. This allows the user to continue with other tasks while the build runs in the cloud.
After starting the build in background:
/ota, making code changes, etc.)Generate a temporary expect script with automatic cleanup.
IMPORTANT: Use the Bash tool with run_in_background: true parameter:
Bash({
command: `
SCRIPT_NAME="eas-build-auto-$$.exp"
cat > "$SCRIPT_NAME" << 'EOF'
#!/usr/bin/expect -f
set timeout -1
spawn npx eas-cli@latest build -p android --profile dev
expect {
"Generate a new Android Keystore?" {
send "y\r"
exp_continue
}
eof
}
wait
EOF
chmod +x "$SCRIPT_NAME"
"./$SCRIPT_NAME"
EXIT_CODE=$?
# Clean up temporary file
rm -f "$SCRIPT_NAME"
exit $EXIT_CODE
`,
description: "Execute EAS build with automatic Keystore generation",
run_in_background: true,
timeout: 600000
})
Key points:
run_in_background: true to avoid blocking$$) for unique filenamerm -f "$SCRIPT_NAME"npx eas-cli@latest to ensure latest versionGenerate a temporary Node.js script with self-deletion.
IMPORTANT: Use the Bash tool with run_in_background: true parameter:
Bash({
command: `
SCRIPT_NAME="eas-build-auto-$(date +%s).js"
cat > "$SCRIPT_NAME" << 'EOF'
const pty = require('node-pty');
const os = require('os');
const fs = require('fs');
const scriptPath = __filename;
const shell = os.platform() === 'win32' ? 'cmd.exe' : 'bash';
const ptyProcess = pty.spawn('npx', ['eas-cli@latest', 'build', '-p', 'android', '--profile', 'dev'], {
name: 'xterm-color',
cwd: process.cwd(),
env: process.env
});
ptyProcess.on('data', (data) => {
process.stdout.write(data);
if (data.includes('Generate a new Android Keystore?')) {
ptyProcess.write('y\r');
}
});
ptyProcess.on('exit', (code) => {
// Clean up on exit
try {
fs.unlinkSync(scriptPath);
} catch (err) {
// Ignore deletion errors
}
process.exit(code);
});
// Clean up on interrupt
process.on('SIGINT', () => {
try {
fs.unlinkSync(scriptPath);
} catch (err) {
// Ignore deletion errors
}
process.exit(130);
});
EOF
node "$SCRIPT_NAME"
# Backup cleanup in case self-deletion failed
rm -f "$SCRIPT_NAME"
`,
description: "Execute EAS build with automatic Keystore generation",
run_in_background: true,
timeout: 600000
})
Key points:
run_in_background: true to avoid blockingnpx eas-cli@latest to ensure latest versionAfter successful build submission (running in background):
Immediate notification:
Next steps suggestion:
/ota, code changes, documentation)Example message:
Build started in background!
Build URL: https://expo.dev/accounts/...
β±οΈ Expected time:
- Paid tier: 5-10 minutes
- Free tier: 30 minutes to 3+ hours
You can continue with other tasks while the build runs. Check the URL above to monitor progress.
// Detect current platform
switch (process.platform) {
case 'darwin': // macOS - use expect
// Generate expect script
break;
case 'win32': // Windows - use node-pty
case 'linux': // Linux - use node-pty
// Generate Node.js script
break;
}
CRITICAL: Always delete temporary scripts after execution.
rm -f "$SCRIPT_NAME" after script completionfs.unlinkSync(scriptPath) on exit and SIGINTeas login firsteas init firstnpm install node-ptyEAS Build runs in the cloud and completion time varies significantly:
Paid tier:
Free tier:
Best Practice: Always run builds in background using run_in_background: true parameter. This allows users to continue working on other tasks while the build completes in the cloud.
Note: The Keystore prompt is automatically answered "yes" by the generated script.