Thanks for your interest in contributing to Deployment Manager. This document covers the technical setup, architecture, and guidelines for contributors.
- Rust 1.70+ — Install via rustup
- Node.js 18+ and npm
- PHP Deployer (
dep) in PATH (for testing deployments) - Git
Ubuntu/Debian:
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-devFedora:
sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file libappindicator-gtk3-devel librsvg2-develArch:
sudo pacman -S webkit2gtk-4.1 base-devel curl wget file openssl appmenu-gtk-module libappindicator-gtk3 librsvggit clone <repo-url>
cd deployment-manager
# Install frontend dependencies
npm install
# Run in development mode (hot-reload for frontend, rebuilds Rust on change)
npm run tauri dev
# Run all Rust tests (unit + integration + property-based)
cd src-tauri && cargo test
# Type-check the frontend
npm run check
# Build production binary
npm run tauri buildThe app is built with Tauri 2.x — a Rust backend with a web-based frontend.
deployment-manager/
├── src/ # Frontend (Svelte 5 + TypeScript)
│ ├── components/ # UI components
│ │ ├── App.svelte # Root layout, event listeners, theme
│ │ ├── ProjectList.svelte # Sidebar project list
│ │ ├── ProjectPanel.svelte # Main content area
│ │ ├── EnvironmentCard.svelte
│ │ ├── DeployForm.svelte # Tag/branch selection
│ │ ├── DeployOutput.svelte # Terminal output display
│ │ ├── ReleaseHistory.svelte
│ │ └── AddProjectDialog.svelte
│ ├── lib/
│ │ ├── api.ts # Typed wrappers around Tauri invoke()
│ │ ├── events.ts # Tauri event listeners
│ │ └── stores.ts # Svelte writable stores
│ └── types.ts # TypeScript interfaces
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── main.rs # Entry point
│ │ ├── lib.rs # Tauri app setup, command registration
│ │ ├── state.rs # Shared AppState (Mutex-wrapped)
│ │ ├── error.rs # AppError enum
│ │ ├── commands/ # Tauri IPC command handlers
│ │ │ ├── projects.rs # register, remove, list, get
│ │ │ ├── environments.rs # get_environments
│ │ │ ├── deployments.rs # start_deployment, get_status
│ │ │ ├── releases.rs # get_releases + output parser
│ │ │ └── git.rs # get_tags, get_branches
│ │ ├── services/ # Business logic
│ │ │ ├── config.rs # ConfigManager (load/save JSON)
│ │ │ ├── hosts.rs # HostsParser (YAML → Environment)
│ │ │ ├── project.rs # Project validation & management
│ │ │ ├── process.rs # Subprocess execution & streaming
│ │ │ └── git.rs # Git tag/branch retrieval
│ │ └── models/ # Data structures (serde)
│ │ ├── config.rs # AppConfig, ProjectConfig
│ │ ├── project.rs # Project
│ │ ├── environment.rs # Environment
│ │ ├── deployment.rs # DeploymentStatus, DeploymentOutput
│ │ └── release.rs # Release
│ ├── tests/
│ │ └── integration_tests.rs
│ └── Cargo.toml
├── package.json
├── vite.config.ts
└── tsconfig.json
- Frontend calls backend via
invoke("command_name", { params })(Tauri IPC) - Backend processes the request (file I/O, subprocess execution, etc.)
- For deployments: backend streams output via Tauri events (
deploy_output,deploy_complete) - Frontend listens to events and updates Svelte stores reactively
- No framework on the frontend — Svelte 5 with runes, no router needed for a single-view app
- Mutex-based state — simple and sufficient for a desktop app with no concurrent users
- Subprocess execution — shells out to
depandgitCLI tools rather than reimplementing their logic - YAML parsing in Rust — keeps file system concerns in the backend
- Alphabetical environment ordering — parsed from HashMap, sorted for consistent display
cd src-tauri
# All tests
cargo test
# Only unit tests (faster, no integration)
cargo test --lib
# Specific module
cargo test --lib services::hosts
# With output
cargo test -- --nocapture- Unit tests — inline
#[cfg(test)] mod testsin each module - Property-based tests — using
proptestcrate, verify universal properties with generated inputs - Integration tests — in
tests/integration_tests.rs, test full flows (config persistence, project registration)
We use proptest to verify correctness properties. Each property test is tagged with a comment:
// Feature: deployment-manager, Property N: <description>
// **Validates: Requirements X.Y**Properties covered:
- Project validation matches file existence
- Project removal decreases list by one
- Configuration serialization round-trip
- Path availability reflects file system state
- Hosts parsing extracts all environments
- Invalid YAML produces parse error
- Deploy command construction
- Exit code determines deployment success
- Releases command construction
- Releases sorted reverse chronologically
- Tag and branch are mutually exclusive
- Keep it simple. This is a utility app, not a framework.
- No unnecessary abstractions. A function is fine; not everything needs a trait.
- Error messages should be actionable — tell the user what went wrong and what to check.
- Use
thiserrorfor error types,serdefor serialization - All commands return
Result<T, AppError>— errors serialize to the frontend - Use
log::info!/log::error!for operations that help debugging (file paths, command args, exit codes) - Run
cargo clippybefore submitting — no warnings allowed - Run
cargo fmt— consistent formatting
- Svelte 5 runes syntax (
$state,$derived,$effect,$props) - All components use
<script lang="ts"> - Types in
src/types.tsmust match Rust struct serialization - API calls go through
src/lib/api.ts— never callinvoke()directly from components - Scoped
<style>in components — no global CSS except inApp.svelte
- Use conventional commits:
feat:,fix:,refactor:,docs:,test:,chore: - Keep commits focused — one logical change per commit
- Write a clear commit message explaining why, not just what
- One feature or fix per PR
- Include tests for new functionality
- Update README.md if the change affects user-facing behavior
- Property-based tests are encouraged for any new validation logic
- All tests must pass (
cargo test+npm run check)
Typical flow:
- Add/update the Rust model in
src-tauri/src/models/ - Implement the service logic in
src-tauri/src/services/ - Create the Tauri command in
src-tauri/src/commands/ - Register the command in
src-tauri/src/lib.rs - Add the TypeScript interface in
src/types.ts - Add the API function in
src/lib/api.ts - Build/update the Svelte component
- Write tests (unit + property if applicable)
We use Semantic Versioning. The version is defined in three places that must stay in sync:
package.json— npm/frontend versionsrc-tauri/Cargo.toml— Rust crate versionsrc-tauri/tauri.conf.json— Tauri app version (used in built binaries and shown in the UI)
Use the provided script:
npm run bump 1.0.0Or directly:
./scripts/bump-version.sh 1.0.0This updates all three files. Then:
git add -A
git commit -m "chore: bump version to 1.0.0"
git tag v1.0.0
git push && git push --tagsThe version from tauri.conf.json is shown in the app sidebar (bottom). Tauri provides it at runtime via @tauri-apps/api/app.
- Patch (0.1.x): Bug fixes, minor UI tweaks
- Minor (0.x.0): New features, non-breaking changes
- Major (x.0.0): Breaking changes to config format or behavior
- No real-time output streaming — PHP Deployer buffers stdout when not connected to a TTY. Output appears after the deployment completes. This is a known limitation we'd like to solve.
- Linux only — macOS support is planned but not yet tested.
- No concurrent deployments — deploying to the same environment while one is running is not prevented at the UI level (the backend handles it gracefully).
MIT — see LICENCE