Skip to content

Commit a97431a

Browse files
committed
docs: [#403] add issue specification for exists command
1 parent fcd5a2f commit a97431a

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Implement `exists` CLI Command
2+
3+
**Issue**: #403
4+
**Parent Epic**: None
5+
**Related**:
6+
7+
- Feature specification: `docs/features/exists-command/specification.md`
8+
- Feature questions: `docs/features/exists-command/questions.md`
9+
- SDK `Deployer::exists()`: `packages/sdk/src/deployer.rs`
10+
- Console commands overview: `docs/console-commands.md`
11+
12+
## Overview
13+
14+
Implement a new `exists` CLI command that checks whether a deployment environment exists and returns a boolean result. The command outputs `true` or `false` to stdout and always exits 0 on success (exit 1 only for errors), following the project's standard exit code convention.
15+
16+
This fills a gap in the CLI: the SDK already has `Deployer::exists()`, but there is no CLI equivalent. Existing commands (`show`, `destroy`, etc.) check existence internally as a precondition, but their exit code 1 is ambiguous (not found vs. other error) and they produce verbose output unsuitable for scripting.
17+
18+
## Goals
19+
20+
- [ ] Provide a CLI command that checks whether a named environment exists
21+
- [ ] Output `true`/`false` to stdout (exit 0 = success, exit 1 = error)
22+
- [ ] Support human-readable and JSON output formats (both output bare `true`/`false`)
23+
- [ ] Use `EnvironmentRepository::exists()` directly (file-existence check, no deserialization)
24+
- [ ] Update SDK `Deployer::exists()` to use the new `ExistsCommandHandler`
25+
26+
## 🏗️ Architecture Requirements
27+
28+
**DDD Layers**: Application + Presentation
29+
30+
**Module Paths**:
31+
32+
- `src/application/command_handlers/exists/mod.rs` — Module definition
33+
- `src/application/command_handlers/exists/handler.rs``ExistsCommandHandler`
34+
- `src/application/command_handlers/exists/errors.rs``ExistsCommandHandlerError`
35+
- `src/presentation/cli/controllers/exists/mod.rs` — Module definition
36+
- `src/presentation/cli/controllers/exists/handler.rs``ExistsCommandController`
37+
- `src/presentation/cli/controllers/exists/errors.rs``ExistsSubcommandError`
38+
- `src/presentation/cli/input/cli/commands.rs``Commands::Exists` variant
39+
- `src/presentation/cli/dispatch/router.rs` — Router dispatch entry
40+
- `src/bootstrap/container.rs` — DI factory method
41+
42+
**Patterns**: Command Handler + CLI Controller + Router Dispatch (same as all other commands)
43+
44+
### Module Structure Requirements
45+
46+
- [ ] Follow DDD layer separation (see [docs/codebase-architecture.md](../codebase-architecture.md))
47+
- [ ] Respect dependency flow rules (dependencies flow toward domain)
48+
- [ ] Use appropriate module organization (see [docs/contributing/module-organization.md](../contributing/module-organization.md))
49+
50+
### Architectural Constraints
51+
52+
- [ ] No business logic in presentation layer
53+
- [ ] Error handling follows project conventions (see [docs/contributing/error-handling.md](../contributing/error-handling.md))
54+
- [ ] Error types implement `Traceable` trait with `help()` method
55+
- [ ] "Not found" is a valid result (`exists = false`), NOT an error — no `EnvironmentNotFound` error variant
56+
- [ ] Skip `ProgressReporter` — sub-millisecond operation, use direct `UserOutput` calls
57+
58+
### Anti-Patterns to Avoid
59+
60+
- ❌ Mixing concerns across layers
61+
- ❌ Domain layer depending on infrastructure
62+
- ❌ Monolithic modules with multiple responsibilities
63+
- ❌ Using exit codes to communicate boolean results (use stdout instead)
64+
65+
## Specifications
66+
67+
### Exit Code Semantics
68+
69+
| Scenario | Exit Code | Stdout | Stderr |
70+
| -------------------------- | --------- | ------- | --------------- |
71+
| Environment exists | **0** | `true` ||
72+
| Environment does not exist | **0** | `false` ||
73+
| Invalid environment name | **1** || Error with help |
74+
| Repository/IO error | **1** || Error with help |
75+
76+
### Output Format
77+
78+
Both human-readable and JSON formats output bare `true` or `false` — these are valid JSON values.
79+
80+
### Usage Examples
81+
82+
```bash
83+
# Basic check
84+
torrust-tracker-deployer exists my-environment
85+
86+
# Bash scripting
87+
if [ "$(torrust-tracker-deployer exists my-env)" = "true" ]; then
88+
echo "Environment already exists, skipping creation"
89+
else
90+
torrust-tracker-deployer create environment -f config.json
91+
fi
92+
93+
# JSON output
94+
torrust-tracker-deployer exists my-env --format json
95+
# true
96+
```
97+
98+
### Router Dispatch
99+
100+
```rust
101+
Commands::Exists { environment } => {
102+
context
103+
.container()
104+
.create_exists_controller()
105+
.execute(&environment, context.output_format())?;
106+
Ok(())
107+
}
108+
```
109+
110+
No special exit code handling needed — the controller prints the result and returns `Ok(())`.
111+
112+
### SDK Update
113+
114+
Update `Deployer::exists()` to use the new handler instead of wrapping `show()`:
115+
116+
```rust
117+
pub fn exists(&self, env_name: &EnvironmentName) -> Result<bool, ExistsCommandHandlerError> {
118+
let handler = ExistsCommandHandler::new(self.repository.clone());
119+
Ok(handler.execute(env_name)?.exists)
120+
}
121+
```
122+
123+
This is a breaking change to the SDK return type (acceptable — no stable release yet).
124+
125+
## Implementation Plan
126+
127+
### Phase 1: Application Layer
128+
129+
- [ ] Create `ExistsCommandHandler` with `execute()` method
130+
- [ ] Create `ExistsCommandHandlerError` with `Traceable` implementation
131+
- [ ] Create `ExistsResult` DTO (`name: String`, `exists: bool`)
132+
- [ ] Add `pub mod exists;` to `src/application/command_handlers/mod.rs`
133+
- [ ] Add unit tests (exists, not exists, repository error)
134+
135+
### Phase 2: Presentation Layer
136+
137+
- [ ] Add `Exists` variant to `Commands` enum in `commands.rs`
138+
- [ ] Create `ExistsCommandController` with output formatting
139+
- [ ] Create `ExistsSubcommandError` with error mapping
140+
- [ ] Add router dispatch entry in `router.rs`
141+
- [ ] Add `create_exists_controller()` to DI container
142+
- [ ] Add `pub mod exists;` to `src/presentation/cli/controllers/mod.rs`
143+
144+
### Phase 3: SDK Integration
145+
146+
- [ ] Update `Deployer::exists()` to use `ExistsCommandHandler`
147+
- [ ] Update return type from `ShowCommandHandlerError` to `ExistsCommandHandlerError`
148+
149+
### Phase 4: Testing and Documentation
150+
151+
- [ ] Add E2E tests (exists, not exists, invalid name, JSON output)
152+
- [ ] Update `docs/console-commands.md`
153+
- [ ] Update `docs/user-guide/commands/` with `exists` command page
154+
- [ ] Update `docs/features/active-features.md` status
155+
156+
## Acceptance Criteria
157+
158+
> **Note for Contributors**: These criteria define what the PR reviewer will check. Use this as your pre-review checklist before submitting the PR to minimize back-and-forth iterations.
159+
160+
**Quality Checks**:
161+
162+
- [ ] Pre-commit checks pass: `./scripts/pre-commit.sh`
163+
164+
**Task-Specific Criteria**:
165+
166+
- [ ] `torrust-tracker-deployer exists <env>` outputs `true` and exits 0 when environment exists
167+
- [ ] `torrust-tracker-deployer exists <env>` outputs `false` and exits 0 when environment does not exist
168+
- [ ] `--format json` produces `true` or `false` (valid JSON values)
169+
- [ ] Invalid environment names produce a clear error (exit 1) with help text
170+
- [ ] Error types implement `Traceable` trait with `help()` method
171+
- [ ] Code follows DDD layer conventions (command handler pattern)
172+
- [ ] SDK `Deployer::exists()` updated to use `ExistsCommandHandler`
173+
- [ ] Unit tests cover handler logic (exists, not exists, repository error)
174+
- [ ] E2E tests validate full command execution and exit codes
175+
- [ ] Documentation updated (`console-commands.md`, user guide)
176+
177+
## Related Documentation
178+
179+
- [Feature Specification](../features/exists-command/specification.md)
180+
- [Feature Questions](../features/exists-command/questions.md)
181+
- [Console Commands Overview](../console-commands.md)
182+
- [Codebase Architecture](../codebase-architecture.md)
183+
- [DDD Layer Placement](../contributing/ddd-layer-placement.md)
184+
- [Error Handling Conventions](../contributing/error-handling.md)
185+
186+
## Notes
187+
188+
- The `EnvironmentRepository::exists()` trait method and `FileEnvironmentRepository::exists()` implementation already exist — no domain or infrastructure changes needed
189+
- The command is deliberately thin: no environment loading, no state extraction, no network calls
190+
- Performance: sub-millisecond (`Path::exists()` system call only)
191+
- Edge cases (corrupt files, permissions, symlinks, race conditions) are documented in the feature specification

0 commit comments

Comments
 (0)