You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Spectre.Docs/Content/cli/explanation/command-lifecycle-and-execution-flow.md
+102-8Lines changed: 102 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,11 +5,105 @@ uid: "cli-command-lifecycle"
5
5
order: 3020
6
6
---
7
7
8
-
An explanatory deep-dive into what happens from the moment `app.Run(args)` is called to when a command finishes execution. It describes:
9
-
10
-
***Parsing Phase**: how the arguments array is parsed against the configured commands and settings, how Spectre.Console.Cli matches arguments to `CommandSettings` properties (and provides errors if something is wrong).
11
-
***Validation Phase**: how and when the library calls `Validate()` on settings or commands (e.g., does it validate CommandSettings by invoking an optional `Validate` method on settings class or just the command's override as documented).
12
-
***Execution Phase**: how the appropriate `Command` instance is constructed (using DI if available), then `Execute` or `ExecuteAsync` is called.
13
-
***Post-Execution**: handling the result (the int exit code) and any exception propagation or interception.
14
-
***Help invocation**: mention that if `--help` is detected, the above phases short-circuit to display help instead of executing a command.
15
-
This explanation might include a simple flow diagram or description: Input args -> Parser selects Command -> Settings populated -> If parse errors, show error/help -> If ok, create Command -> (Interceptor before) -> Execute -> (Interceptor after) -> return exit code. By understanding this flow, users can reason about behaviors like why their code in `Execute` might not run (e.g. if parsing failed or validation failed first).
8
+
When you call `app.Run(args)`, Spectre.Console.Cli orchestrates a multi-phase pipeline that parses your command line,
9
+
validates input, and executes your command. Understanding this flow helps you reason about when your code runs, why
10
+
certain errors appear, and how to debug unexpected behavior.
11
+
12
+
The library processes commands through distinct phases: parsing, help detection, settings binding, validation,
13
+
interception, execution, and error handling. Each phase has specific responsibilities and can short-circuit the
14
+
pipeline early if something goes wrong or if help is requested.
15
+
16
+
Execution Flow Overview
17
+
-----------------------
18
+
19
+
The following diagram shows the high-level flow from input to exit code:
20
+
21
+
```mermaid
22
+
flowchart TD
23
+
A[app.Run args] --> B[Parse args]
24
+
B --> C{Parse error?}
25
+
C -->|Yes| D[Show error, return -1]
26
+
C -->|No| E{Help requested?}
27
+
E -->|Yes| F[Show help, return 0]
28
+
E -->|No| G[Bind settings]
29
+
G --> H[settings.Validate]
30
+
H --> I{Validation failed?}
31
+
I -->|Yes| D
32
+
I -->|No| J[Interceptors before]
33
+
J --> K[command.Validate]
34
+
K --> L{Validation failed?}
35
+
L -->|Yes| D
36
+
L -->|No| M[Execute]
37
+
M --> N[Interceptors after]
38
+
N --> O[Return exit code]
39
+
```
40
+
41
+
Parsing and Command Resolution
42
+
------------------------------
43
+
44
+
The parser tokenizes the argument array and matches tokens against your configured command tree. For nested commands
45
+
like `myapp add package Newtonsoft.Json`, the parser walks through each level: first matching `add`, then `package`,
46
+
then capturing `Newtonsoft.Json` as an argument.
47
+
48
+
During parsing, the library builds a `CommandTree` representing the matched command hierarchy. Arguments that don't
49
+
match any defined parameter are collected as remaining arguments, accessible via `IRemainingArguments` in your command.
50
+
51
+
Help detection happens during parsing, not after. When the parser encounters `-h`, `-?`, or `--help`, it sets a flag
52
+
on the command node and stops processing further arguments. This means help short-circuits the entire pipeline before
53
+
any binding or validation occurs. Branch commands (those with subcommands but no direct execution logic) automatically
54
+
show help and return exit code 1.
55
+
56
+
Settings Binding and Validation
57
+
-------------------------------
58
+
59
+
After parsing, the library creates an instance of your `CommandSettings` class and populates it with parsed values.
60
+
If you have a dependency injection container configured, settings can be resolved through constructor injection.
61
+
Otherwise, the library creates the instance directly and sets properties via reflection.
62
+
63
+
Type conversion happens automatically. If an option is declared as `int` and the user provides a string, the library
64
+
converts it. Invalid conversions produce clear error messages before your code runs.
65
+
66
+
Immediately after binding, the library calls `settings.Validate()`. This is your first opportunity to check that the
67
+
combination of values makes sense. The `Validate()` method on `CommandSettings` is virtual, returning
68
+
`ValidationResult.Success()` by default. Override it to add custom validation logic that runs before command execution.
69
+
70
+
Command Validation and Execution
71
+
--------------------------------
72
+
73
+
Once settings are validated, the library resolves your command class. If you have dependency injection configured,
74
+
the command is resolved from the container, allowing constructor injection of services. Otherwise, it is instantiated
75
+
directly.
76
+
77
+
Before calling `Execute`, the library invokes `command.Validate(context, settings)`. This is a separate validation
78
+
point from settings validation. Use it when validation requires the full `CommandContext` or when you need to validate
79
+
relationships between settings and external state.
80
+
81
+
The library then calls `Execute` (or `ExecuteAsync` for async commands). Your command returns an integer exit code.
82
+
By convention, 0 indicates success and non-zero values indicate various error conditions.
83
+
84
+
Interceptors
85
+
------------
86
+
87
+
Interceptors provide hooks before and after command execution. Any class implementing `ICommandInterceptor` registered
88
+
in your DI container is automatically resolved and invoked.
89
+
90
+
The `Intercept` method runs after settings binding but before execution. Use it for cross-cutting concerns like
91
+
logging, timing, or modifying settings. The `InterceptResult` method runs after execution and receives the exit code
92
+
by reference, allowing you to modify it before it is returned to the caller.
93
+
94
+
Exit Codes and Error Handling
95
+
-----------------------------
96
+
97
+
Spectre.Console.Cli uses standard exit code conventions: 0 for success, non-zero for errors. The library returns -1
98
+
for unhandled exceptions by default.
99
+
100
+
Exception handling occurs at two levels. The `CommandExecutor` catches exceptions during execution and can invoke a
101
+
custom `ExceptionHandler` if configured. If the exception propagates, `CommandApp` catches it and either re-throws
102
+
(if `PropagateExceptions` is true) or renders the error and returns -1.
103
+
104
+
For cancellation scenarios, the library catches `OperationCanceledException` and returns a configurable cancellation
105
+
exit code.
106
+
107
+
Understanding this lifecycle helps diagnose issues: if your `Execute` method never runs, the problem likely occurred
108
+
during parsing, binding, or validation. If interceptors don't fire, check that they are registered in your DI
109
+
container. The structured pipeline ensures errors are caught early and reported clearly.
0 commit comments