This guide helps you contribute to the devbox-plugins mobile development templates repository. The repository provides Devbox plugins for Android, iOS, and React Native with example projects demonstrating reproducible, project-local mobile development environments.
Install Devbox following the instructions at jetify.com/devbox.
Clone the repository and verify your environment.
# Clone the repository
git clone https://github.com/segment-integrations/devbox-plugins.git
cd devbox-plugins
# Install root dependencies
devbox shell
# Verify plugin installation in an example project
cd examples/android
devbox shell
devbox run android.sh devices list
# Return to root
cd ../...
├── plugins/
│ ├── android/ # Android plugin
│ ├── ios/ # iOS plugin
│ ├── react-native/ # React Native plugin
│ ├── tests/ # Plugin unit tests
│ └── CONVENTIONS.md # Plugin development patterns
├── examples/
│ ├── android/ # Minimal Android app
│ ├── ios/ # Swift package example
│ └── react-native/ # React Native app
├── tests/ # E2E test scripts
├── .github/workflows/ # CI/CD workflows
└── devbox.json # Root devbox config
Reference plugins from GitHub using the Devbox plugin syntax.
{
"include": [
"github:segment-integrations/devbox-plugins?dir=plugins/android",
"github:segment-integrations/devbox-plugins?dir=plugins/ios",
"github:segment-integrations/devbox-plugins?dir=plugins/react-native"
]
}Never modify .devbox/virtenv/ directly. These are temporary runtime directories regenerated from plugin sources. Changes to these files are lost when the virtenv regenerates.
Correct workflow:
- Edit source files in
plugins/{platform}/scripts/ - Run
devbox run syncto copy changes to example projects - The
.devbox/virtenv/directories regenerate automatically ondevbox shellordevbox run
Development sync commands:
devbox run sync- Full sync, reinstalls all example projects (slow, complete)scripts/dev/sync-examples.sh- Quick sync, copies plugin scripts only (fast, development-only)
Plugin configuration lives in plugin.json. This file defines initialization hooks, environment variables, and script locations. Runtime scripts go in the scripts/ directory.
Plugin directory layout:
plugins/{platform}/
├── config/ # Template files copied to user projects
│ ├── devices/ # Default device definitions
│ └── *.yaml # Process-compose test suites
├── scripts/ # Runtime scripts
│ ├── {platform}.sh # Main CLI
│ ├── lib.sh # Shared utilities
│ ├── env.sh # Environment setup
│ └── {feature}.sh # Feature scripts
├── plugin.json # Plugin manifest
└── REFERENCE.md # Complete API reference
Plugin scripts follow strict layers to prevent circular dependencies. Scripts can only source/depend on scripts from earlier layers.
scripts/
├── lib/ # Layer 1: Pure utilities
├── platform/ # Layer 2: SDK/platform setup
├── domain/ # Layer 3: Domain operations (AVD, emulator, run)
├── user/ # Layer 4: User-facing CLI
└── init/ # Layer 5: Environment initialization
Key principles:
lib/: Pure utility functions, no platform-specific logicplatform/: SDK resolution, PATH setup, device configurationdomain/: Internal domain operations - atomic, independent, orchestrated by layer 4user/: User-facing CLI commands - orchestrates domain operationsinit/: Environment initialization run by devbox hooks
Domain layer scripts cannot call each other. If two domain scripts need the same functionality, that functionality must be moved to the platform or lib layer.
Device definitions are JSON files in devbox.d/{platform}/devices/. Modify devices using CLI commands, not manual editing.
# Android - specify API level and device profile
devbox run --pure android.sh devices create pixel_api30 \
--api 30 \
--device pixel \
--tag google_apis \
--preferred_abi x86_64
# iOS - specify simulator runtime version
devbox run --pure ios.sh devices create iphone14 --runtime 16.4
# Regenerate lock file after changes
devbox run --pure {platform}.sh devices evalAfter creating, updating, or deleting devices, regenerate the lock file. Lock files optimize CI by limiting which SDK versions are evaluated. Commit lock files to the repository.
Enable debug logging with environment variables.
# Platform-specific
ANDROID_DEBUG=1 devbox shell
IOS_DEBUG=1 devbox shell
# Global
DEBUG=1 devbox shellValidate lock files after device changes.
devbox run --pure android.sh devices eval
devbox run --pure ios.sh devices evalSimplicity and readability first. Code should be easy to understand at a glance. Prefer straightforward solutions over clever ones. If you need to explain what code does, the code is probably too complex.
DRY and single responsibility. Extract repeated logic into functions with clear names. Each function should do one thing well. Each file should have a focused purpose.
Keep files focused and manageable. Split large files by concern. When a file exceeds ~500 lines or contains unrelated functions, split it.
Example file organization:
lib.sh- Generic utilities (path manipulation, JSON parsing, logging)devices.sh- Device management operationsavd.sh- AVD-specific operationsenv.sh- Environment variable setup
Minimal comments in code. Write self-documenting code with clear function and variable names. Use comments only for:
- Why decisions were made (not what the code does)
- Complex algorithms that can't be simplified
- Workarounds for external tool bugs
Document public APIs exhaustively in REFERENCE.md files, not in code comments.
Fail loudly, avoid fallbacks. When something is wrong, the code should exit with a clear error message and non-zero status. Avoid silent fallbacks that hide problems.
Reduce edge cases and unexpected behavior. Design for the common path. When edge cases arise, validate assumptions early and fail fast rather than adding complex branching logic.
Scripts fail on error. All shell scripts use set -euo pipefail (or set -eu for POSIX sh). Functions return 0 on success, non-zero on failure. Avoid || true except in validation functions where warnings shouldn't block execution.
Validation warns but doesn't block. User-facing validation commands (like lock file checksum mismatches) should warn with actionable fix commands but never prevent the user from continuing. The validation philosophy is "inform, don't obstruct."
All logs must go to ${TEST_LOGS_DIR} (defaults to reports/logs/), never to /tmp/.
Use standardized logging functions from lib.sh:
# Auto-detects script name from $0
android_log_info "Creating AVD: pixel_api30"
android_log_warn "Emulator already running"
android_log_error "APK not found"
android_log_debug "SDK path: /nix/store/..."
# Or explicitly provide script name
android_log_info "avd.sh" "Creating AVD: pixel_api30"
ios_log_warn "simulator.sh" "Simulator boot timeout"Output format:
[INFO] [avd.sh] Creating AVD: pixel_api30
[WARN] [emulator.sh] Emulator already running
[ERROR] [deploy.sh] APK not found
[DEBUG] [core.sh] SDK path: /nix/store/...
Log levels:
android_log_debug/ios_log_debug- Only shown whenDEBUG=1orANDROID_DEBUG=1/IOS_DEBUG=1android_log_info/ios_log_info- Always shownandroid_log_warn/ios_log_warn- Always shownandroid_log_error/ios_log_error- Always shown
File logging paths:
# Use environment variables (preferred)
echo "$data" > "${TEST_LOGS_DIR}/test-output.txt"
mkdir -p "${TEST_LOGS_DIR}"
log_file="${TEST_LOGS_DIR}/$(date +%Y%m%d-%H%M%S)-test.log"
# Or use hardcoded path if variables unavailable
echo "$data" > reports/logs/test-output.txt
mkdir -p reports/logsIncorrect:
echo "$data" > /tmp/test-output.txt # WRONG - /tmp not project-local
log_file="/tmp/test.log" # WRONG - may be cleaned up by systemCritical rule: Only terminate processes that we explicitly started. Never interfere with external processes that may have been spawned by other projects.
Implementation requirements:
- Track Process IDs: When starting a process, record its PID in a project-local file.
- Verify Before Killing: Before terminating a process, verify the PID file exists, the process is still running, and the process is what we expect.
- Let Process Managers Handle Lifecycle: When using process-compose, let it manage process termination. Cleanup scripts should only remove state files.
This ensures multiple projects can run simultaneously without conflicts and enables reproducible, conflict-free execution.
Environment Variables:
{PLATFORM}_{CATEGORY}_{DESCRIPTOR}
Examples:
- ANDROID_DEFAULT_DEVICE
- ANDROID_SDK_ROOT
- IOS_DEVELOPER_DIR
- ANDROID_BUILD_TOOLS_VERSION
Shell Scripts:
{platform}.sh - Main CLI entry point (android.sh, ios.sh)
{feature}.sh - Feature-specific scripts (devices.sh, avd.sh, env.sh)
lib.sh - Shared utility functions
test-{feature}.sh - Test scripts
Shell Functions:
{platform}_{category}_{action}
Examples:
- android_devices_list
- android_devices_create
- ios_get_developer_dir
- android_validate_lock_file
Device Files:
{descriptor}.json - Device definition files
Examples:
- min.json - Minimum supported version
- max.json - Maximum/latest version
- pixel_api30.json - Descriptive device name
Lock Files:
devices.lock - Generated lock file (plain text, device:checksum format)
Cache Files:
.{feature}.cache - Hidden cache files with descriptive names
Examples:
- .xcode_dev_dir.cache
- .shellenv.cache
File naming:
process-compose-{suite}.yaml
Examples:
- lint.yaml
- unit-tests.yaml
- e2e.yaml
Process naming:
{category}-{feature}
Examples:
- lint-android
- test-android-lib
- e2e-android
- summary
Log locations:
test-results/{suite-name}-logs
Examples:
- test-results/devbox-lint-logs
- test-results/android-repo-e2e-logs
Always include a summary process:
- Depends on all other processes with
process_completed(notprocess_completed_successfully) - Displays test results in clean, scannable format
- Lists log file locations for debugging
Documentation is split into two types with different purposes.
Help users accomplish tasks.
- Use prose style with short, digestible paragraphs (2-4 sentences)
- Only use bullet points and numbered lists for step-by-step instructions
- Focus on practical workflows and common use cases
- Include runnable code examples
- No marketing language or superlatives
- Examples: CLAUDE.md, CONVENTIONS.md, workflow README files
Exhaustive explanations of all options.
- Document every user-facing option, variable, method, and command
- Organized by component (environment variables, CLI commands, config options)
- Concise descriptions without fluff
- Include valid values, defaults, and constraints
- Examples: REFERENCE.md files for each plugin
- Write concisely. Remove unnecessary words.
- No marketing language. Avoid terms like "powerful," "seamless," "robust," "flexible."
- Use active voice. "The script validates" not "Validation is performed."
- One concept per paragraph.
- Code examples should be runnable and realistic.
Fast tests (lint + unit + integration):
devbox run test:fastPlugin-specific tests:
# Android plugin tests
cd plugins/tests/android
./test-lib.sh
./test-devices.sh
# iOS plugin tests
cd plugins/tests/ios
./test-lib.sh
./test-devices.shEnd-to-end tests:
# Android E2E
cd examples/android
devbox run test:e2e
# iOS E2E
cd examples/ios
devbox run test:e2e
# React Native E2E
cd examples/react-native
devbox run test:e2e:android
devbox run test:e2e:ios
devbox run test:e2e:webFast tests:
- Linting and formatting checks
- Plugin unit tests (device management, validation, utilities)
- Quick integration tests
E2E tests:
- Full build and deployment workflows
- Emulator/simulator boot and app launch
- Tests min and max platform versions:
- Android: API 21 (min) to API 36 (max)
- iOS: iOS 15.4 (min) to iOS 26.2 (max)
Use act to run GitHub Actions workflows locally (requires Docker).
# Install act
devbox add act
# Run specific jobs
act -j fast-tests
act -j android-e2e
act -j ios-e2e
# Run full workflow
act -W .github/workflows/pr-checks.yml- New features should include unit tests
- Bug fixes should include regression tests
- CLI commands should have integration tests
- Changes to device management should update E2E tests
- Run fast tests locally:
devbox run test:fast - Test affected platform E2E tests
- Update REFERENCE.md if adding/changing public APIs
- Update device lock files if changing device definitions
- Ensure commit messages follow conventions
Use conventional commit format:
{type}({scope}): {description}
Examples:
- feat(android): add device sync command
- fix(ios): resolve Xcode path caching issue
- docs(contributing): add naming standards
- test(android): add device management tests
- refactor(react-native): simplify plugin composition
Types: feat, fix, docs, test, refactor, perf, chore
Scopes: android, ios, react-native, ci, docs, tests
Before submitting your pull request:
- Fast tests pass locally
- E2E tests pass for affected platforms
- Code follows naming conventions
- Functions follow DRY principles
- Scripts use standardized logging
- Process isolation rules followed
- Public APIs documented in REFERENCE.md
- Device lock files updated if needed
- Commit messages follow conventions
Pull requests require approval from maintainers. The CI runs fast tests automatically on every PR. Full E2E tests run on a weekly schedule or can be triggered manually.
Reviewers check for:
- Code simplicity and readability
- Adherence to naming conventions
- Proper error handling and validation
- Test coverage
- Documentation completeness
Runs automatically on every PR.
- Plugin validation and quick smoke tests
- Fast tests (lint + unit + integration)
- E2E tests for min and max devices on each platform
Manual trigger or weekly schedule.
- Tests min/max platform versions comprehensively
- Android: API 21 (min) to API 36 (max)
- iOS: iOS 15.4 (min) to iOS 26.2 (max)
- React Native: Full cross-platform testing
- Matrix execution for parallel testing
Both workflows use matrix execution to test multiple platforms and devices in parallel. They cache build artifacts (Gradle, CocoaPods, Xcode) to speed up execution. All workflows upload reports and logs as artifacts when tests complete.
For complete command and configuration references, see:
../reference/android.md- Android plugin API reference../reference/ios.md- iOS plugin API reference../reference/react-native.md- React Native plugin API reference../../CONVENTIONS.md- Plugin development patterns and best practices../../.github/workflows/README.md- CI/CD workflow documentation
For questions about contributing:
- Open an issue on GitHub
- Review existing issues and pull requests
- Check the REFERENCE.md files for API documentation
- Review CONVENTIONS.md for development patterns