|
| 1 | +# scdev |
| 2 | + |
| 3 | +Local development environment framework for web applications. Single command startup, shared infrastructure (Traefik, Mailpit, Adminer), project isolation via Docker networks. |
| 4 | + |
| 5 | +## Current Status |
| 6 | + |
| 7 | +**Milestone 12: Shared Redis UI** - Complete. All core milestones done. |
| 8 | + |
| 9 | +## Quick Reference |
| 10 | + |
| 11 | +```bash |
| 12 | +make build # Build binary |
| 13 | +make build-all # Cross-compile for all platforms |
| 14 | +make test # Run unit tests |
| 15 | +make test-integration # Run integration tests (requires Docker) |
| 16 | +./scdev version # Test the binary |
| 17 | +``` |
| 18 | + |
| 19 | +## Releases |
| 20 | + |
| 21 | +Releases are automated via GitHub Actions (`.github/workflows/release.yml`). |
| 22 | + |
| 23 | +**Release process:** |
| 24 | +1. Update `CHANGELOG.md` — add new `## vX.Y.Z` section at top |
| 25 | +2. Commit, tag, push: |
| 26 | + ```bash |
| 27 | + git add CHANGELOG.md |
| 28 | + git commit -m "Release vX.Y.Z" |
| 29 | + git tag vX.Y.Z |
| 30 | + git push origin main && git push origin vX.Y.Z |
| 31 | + ``` |
| 32 | +3. CI builds binaries for darwin/linux (arm64/amd64), creates GitHub Release with changelog |
| 33 | + |
| 34 | +**Version injection:** ldflags set `cmd.Version` and `cmd.BuildTime` at build time. |
| 35 | + |
| 36 | +**Self-update:** `scdev self-update` checks GitHub Releases and replaces the binary. |
| 37 | + |
| 38 | +**Install:** `curl -fsSL https://raw.githubusercontent.com/ScaleCommerce-DEV/scdev/main/install.sh | sh` |
| 39 | + |
| 40 | +## Global Flags |
| 41 | + |
| 42 | +| Flag | Description | |
| 43 | +|------|-------------| |
| 44 | +| `--config <path>` | Path to project directory containing `.scdev/` (overrides auto-discovery) | |
| 45 | + |
| 46 | +**Use case:** Bootstrap projects with tools that require empty directories (e.g., `composer create-project`): |
| 47 | + |
| 48 | +```bash |
| 49 | +# 1. Create config in separate location |
| 50 | +mkdir -p /tmp/shopware-bootstrap/.scdev |
| 51 | +cat > /tmp/shopware-bootstrap/.scdev/config.yaml << 'EOF' |
| 52 | +name: shopware |
| 53 | +services: |
| 54 | + app: |
| 55 | + image: php:8.2-cli |
| 56 | + volumes: |
| 57 | + - /path/to/empty/shopware:/app # Use absolute path |
| 58 | + working_dir: /app |
| 59 | +EOF |
| 60 | + |
| 61 | +# 2. Start containers with external config |
| 62 | +scdev --config /tmp/shopware-bootstrap start |
| 63 | + |
| 64 | +# 3. Run composer in the container |
| 65 | +scdev --config /tmp/shopware-bootstrap exec app composer create-project shopware/production . |
| 66 | + |
| 67 | +# 4. Move config into project (optional) |
| 68 | +mv /tmp/shopware-bootstrap/.scdev /path/to/empty/shopware/ |
| 69 | +``` |
| 70 | + |
| 71 | +## Project Structure |
| 72 | + |
| 73 | +``` |
| 74 | +cmd/ # Cobra CLI commands |
| 75 | +internal/ |
| 76 | + config/ # Config parsing, variable substitution |
| 77 | + defaults.go # All default values (domain, images, tool versions) |
| 78 | + templates/ # Embedded templates with ${VAR} substitution |
| 79 | + mutagen/ # Mutagen file sync wrapper |
| 80 | + runtime/ # Container runtime abstraction (Docker CLI) |
| 81 | + project/ # Project lifecycle, state management |
| 82 | + services/ # Shared services (Traefik, Mailpit, Adminer) |
| 83 | + tools/ # External tool management (just, mkcert, mutagen) |
| 84 | + ui/ # Terminal output helpers |
| 85 | +testdata/projects/ # Test fixtures |
| 86 | +planning/ # Design docs and implementation plan |
| 87 | +``` |
| 88 | + |
| 89 | +## Key Decisions |
| 90 | + |
| 91 | +- **Runtime:** Shell out to `docker` CLI (not SDK) - enables future Podman support |
| 92 | +- **Global Config:** `~/.scdev/global-config.yaml` - auto-created from template (distinct from project config.yaml) |
| 93 | +- **State:** `~/.scdev/state.yaml` - tracks registered projects |
| 94 | +- **Defaults:** All in `internal/config/defaults.go` - single source of truth for domain, images, versions |
| 95 | +- **Templates:** Embedded via `//go:embed` with `${VAR}` substitution - easy to maintain |
| 96 | +- **CLI:** Cobra + Viper |
| 97 | +- **Tests:** Unit tests from start, integration tests tagged `//go:build integration` |
| 98 | +- **Default Domain:** `scalecommerce.site` - wildcard DNS resolving to 127.0.0.1 |
| 99 | + |
| 100 | +## Milestones |
| 101 | + |
| 102 | +| # | Name | Status | |
| 103 | +|---|------|--------| |
| 104 | +| 0 | Project Skeleton | Done | |
| 105 | +| 1 | Single Container | Done | |
| 106 | +| 2 | Config-Driven | Done | |
| 107 | +| 3 | Networking | Done | |
| 108 | +| 4 | Volumes | Done | |
| 109 | +| 5 | Project State | Done | |
| 110 | +| 6 | Shared Router | Done | |
| 111 | +| 7 | SSL & First-Run | Done | |
| 112 | +| 8 | Shared Mail | Done | |
| 113 | +| 9 | Justfile | Done | |
| 114 | +| 10 | Shared DB UI | Done | |
| 115 | +| 11 | Mutagen File Sync | Done | |
| 116 | +| 12 | Shared Redis UI | Done | |
| 117 | + |
| 118 | +## Shared Services |
| 119 | + |
| 120 | +Shared services run in the `scdev_shared` network and are managed by `internal/services/manager.go`. |
| 121 | + |
| 122 | +| Service | Container | URL | Purpose | |
| 123 | +|---------|-----------|-----|---------| |
| 124 | +| Docs | (via Traefik Statiq plugin) | `docs.shared.<domain>` | Documentation page, 404 catch-all | |
| 125 | +| Router | `scdev_router` | `router.shared.<domain>` | Traefik reverse proxy | |
| 126 | +| Mail | `scdev_mail` | `mail.shared.<domain>` | Mailpit email catcher | |
| 127 | +| DB UI | `scdev_db` | `db.shared.<domain>` | Adminer database manager | |
| 128 | +| Redis UI | `scdev_redis` | `redis.shared.<domain>` | Redis Insights browser | |
| 129 | + |
| 130 | +### Adding New Shared Services |
| 131 | + |
| 132 | +When adding a new shared service: |
| 133 | + |
| 134 | +1. Add container name constant in `internal/services/<service>.go` |
| 135 | +2. Add `Start<Service>`, `Stop<Service>`, `<Service>Status` methods to `manager.go` |
| 136 | +3. **Update `cmd/services.go` `runServicesRecreate()`** - add stop/remove/start calls |
| 137 | +4. Update `cmd/services.go` start/stop/status commands |
| 138 | +5. Add image constant to `internal/config/defaults.go` |
| 139 | +6. Update `internal/config/config.go` with config struct if needed |
| 140 | + |
| 141 | +The `scdev services recreate` command force-rebuilds all containers - essential when container config changes (new volumes, args, labels). |
| 142 | + |
| 143 | +## Docs Page |
| 144 | + |
| 145 | +The docs page (`docs.shared.<domain>`) is served via Traefik's Statiq plugin and includes: |
| 146 | +- Links to all shared services |
| 147 | +- **Dynamic projects list** with running/stopped status (updated on every `scdev start/stop/down`) |
| 148 | +- Quick start guide and command reference |
| 149 | + |
| 150 | +Unmatched URLs redirect to docs (catch-all route) instead of showing ugly 404. |
| 151 | + |
| 152 | +## Project Config Options |
| 153 | + |
| 154 | +Key project config options in `.scdev/config.yaml`: |
| 155 | + |
| 156 | +| Option | Type | Description | |
| 157 | +|--------|------|-------------| |
| 158 | +| `auto_open_at_start` | bool | Open project URL in browser after `scdev start` | |
| 159 | +| `shared.router` | bool | Connect to shared Traefik router | |
| 160 | +| `shared.mail` | bool | Connect to shared Mailpit | |
| 161 | +| `shared.db` | bool | Connect to shared Adminer | |
| 162 | +| `shared.redis_insights` | bool | Connect to shared Redis Insights | |
| 163 | + |
| 164 | +### Service Config Options |
| 165 | + |
| 166 | +| Option | Type | Description | |
| 167 | +|--------|------|-------------| |
| 168 | +| `register_to_dbui` | bool | Explicitly register service in Adminer (auto-detected for services named `db`, `mysql`, `postgres`, or images containing those) | |
| 169 | + |
| 170 | +## Justfile Integration |
| 171 | + |
| 172 | +Projects can define custom commands via justfiles in `.scdev/commands/`: |
| 173 | + |
| 174 | +``` |
| 175 | +.scdev/ |
| 176 | + commands/ |
| 177 | + setup.just # scdev setup |
| 178 | + test.just # scdev test |
| 179 | + build.just # scdev build |
| 180 | +``` |
| 181 | + |
| 182 | +**Usage:** |
| 183 | +```bash |
| 184 | +scdev setup # Run default recipe in setup.just |
| 185 | +scdev setup install # Run 'install' recipe |
| 186 | +scdev setup --list # List available recipes |
| 187 | +``` |
| 188 | + |
| 189 | +**Environment variables passed to just:** |
| 190 | +- `PROJECTNAME` - Project name from config |
| 191 | +- `PROJECTPATH` - Absolute path to project |
| 192 | +- `PROJECTDIR` - Directory basename |
| 193 | +- `SCDEV_DOMAIN` - Base domain (e.g., scalecommerce.site) |
| 194 | +- `SCDEV_HOME` - ~/.scdev path |
| 195 | +- All project `environment:` vars from config |
| 196 | + |
| 197 | +**Container commands in justfiles:** |
| 198 | +Use `scdev exec` to run commands inside containers: |
| 199 | +```just |
| 200 | +install: |
| 201 | + scdev exec app npm ci |
| 202 | +
|
| 203 | +test: |
| 204 | + scdev exec app npm test |
| 205 | +``` |
| 206 | + |
| 207 | +**Command resolution:** |
| 208 | +1. Check if built-in command (start, stop, exec, etc.) |
| 209 | +2. Check if `.scdev/commands/<name>.just` exists |
| 210 | +3. Fall back to Cobra's "unknown command" error |
| 211 | + |
| 212 | +## Mutagen File Sync |
| 213 | + |
| 214 | +Mutagen provides fast bidirectional file sync between host filesystem and Docker volumes, solving VirtioFS performance issues on macOS. |
| 215 | + |
| 216 | +**Auto-detection:** |
| 217 | +- macOS: Mutagen enabled by default (bind mounts are slow) |
| 218 | +- Linux: Mutagen disabled by default (native bind mounts are fast) |
| 219 | + |
| 220 | +**Global config** (`~/.scdev/global-config.yaml`): |
| 221 | +```yaml |
| 222 | +mutagen: |
| 223 | + enabled: auto # auto, true, or false |
| 224 | + sync_mode: two-way-safe |
| 225 | +``` |
| 226 | +
|
| 227 | +**Project config** (`.scdev/config.yaml`): |
| 228 | +```yaml |
| 229 | +mutagen: |
| 230 | + ignore: |
| 231 | + - var/cache |
| 232 | + - var/log |
| 233 | + - "*.log" |
| 234 | +``` |
| 235 | + |
| 236 | +**How it works:** |
| 237 | +1. On `scdev start`: Creates Docker volumes, starts containers with volumes instead of bind mounts, creates Mutagen sync sessions |
| 238 | +2. On `scdev stop`: Pauses sync sessions |
| 239 | +3. On `scdev down`: Terminates sync sessions, optionally removes sync volumes with `-v` |
| 240 | + |
| 241 | +**CLI commands:** |
| 242 | +- `scdev mutagen status` - Show sync status for project |
| 243 | +- `scdev mutagen reset` - Recreate sync sessions (if stuck) |
| 244 | +- `scdev mutagen flush` - Wait for sync completion |
| 245 | + |
| 246 | +**Naming convention:** `scdev-<project>-<service>` (e.g., `scdev-myshop-app`) - Mutagen only allows alphanumeric and hyphens |
| 247 | + |
| 248 | +**Note:** Only directory bind mounts are synced via Mutagen. Single-file mounts remain as regular bind mounts. |
| 249 | + |
| 250 | +**Note:** Ignored paths are NOT synced in either direction. This affects IDE autocomplete if `vendor/` or `node_modules/` are ignored. |
| 251 | + |
| 252 | +## Additional Commands |
| 253 | + |
| 254 | +### Logs |
| 255 | +```bash |
| 256 | +scdev logs [service] # View logs (defaults to first service) |
| 257 | +scdev logs -f app # Follow logs in real-time |
| 258 | +scdev logs --tail 50 app # Show last 50 lines |
| 259 | +``` |
| 260 | + |
| 261 | +### Restart |
| 262 | +```bash |
| 263 | +scdev restart # Stop + start the project |
| 264 | +``` |
| 265 | + |
| 266 | +### Open Shared Service UIs |
| 267 | +```bash |
| 268 | +scdev mail # Open Mailpit in browser |
| 269 | +scdev db # Open Adminer in browser |
| 270 | +scdev redis # Open Redis Insights in browser |
| 271 | +scdev docs # Open docs page in browser |
| 272 | +``` |
| 273 | + |
| 274 | +## Detailed Docs |
| 275 | + |
| 276 | +- `planning/implementation-plan.md` - Full milestone details, acceptance criteria |
| 277 | +- `planning/scdev-project-briefing.md` - Original design doc (reference when needed) |
0 commit comments