cmdx 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/docs/basics/execution.md +56 -0
- data/docs/basics/setup.md +26 -3
- data/docs/deprecation.md +0 -2
- data/docs/getting_started.md +11 -0
- data/docs/logging.md +6 -10
- data/docs/outcomes/result.md +4 -1
- data/lib/cmdx/chain.rb +18 -4
- data/lib/cmdx/context.rb +2 -0
- data/lib/cmdx/executor.rb +29 -24
- data/lib/cmdx/result.rb +24 -1
- data/lib/cmdx/task.rb +3 -1
- data/lib/cmdx/version.rb +1 -1
- data/mkdocs.yml +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0b1cd7841d40aeac40b6eb36a7ff87a5ace03deae3496e1aef5cb10e34f8f42d
|
|
4
|
+
data.tar.gz: 303333d4635690575ceda40a2564be417b47f5ec4c2992af542c509710289ae3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18cde5cf4b6c7fa974ffb7a3827e09d0a5c2ac5f3c2c01427abbc94de71ef1bc03aee78e25032900761f09c8d39e89e52a4ea1d5fa33e07c86f060b483fe839f
|
|
7
|
+
data.tar.gz: 5ed05ecdb682ae160be47cf6399c76081486011b6143f95f9884a06b588474ba64e3e000efd72ba2dcd6abb343e3e12985d2691ca15ee7575873f90daff6e70f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [UNRELEASED]
|
|
8
8
|
|
|
9
|
+
## [1.13.0] - 2025-12-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added task execution rollback tracking and logging
|
|
14
|
+
- Added `dry_run` option to task execution with inheritance support for nested tasks
|
|
15
|
+
- Added context `delete` alias for `delete!`
|
|
16
|
+
- Added context `merge` alias for `merge!`
|
|
17
|
+
|
|
9
18
|
## [1.12.0] - 2025-12-18
|
|
10
19
|
|
|
11
20
|
### Added
|
data/docs/basics/execution.md
CHANGED
|
@@ -11,6 +11,33 @@ Both methods return results, but handle failures differently:
|
|
|
11
11
|
| `execute` | Always returns `CMDx::Result` | Never raises | Predictable result handling |
|
|
12
12
|
| `execute!` | Returns `CMDx::Result` on success | Raises `CMDx::Fault` when skipped or failed | Exception-based control flow |
|
|
13
13
|
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart LR
|
|
16
|
+
subgraph Methods
|
|
17
|
+
E[execute]
|
|
18
|
+
EB[execute!]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph Returns [Returns CMDx::Result]
|
|
22
|
+
Success
|
|
23
|
+
Failed
|
|
24
|
+
Skipped
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
subgraph Raises [Raises CMDx::Fault]
|
|
28
|
+
FailFault
|
|
29
|
+
SkipFault
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
E --> Success
|
|
33
|
+
E --> Failed
|
|
34
|
+
E --> Skipped
|
|
35
|
+
|
|
36
|
+
EB --> Success
|
|
37
|
+
EB --> FailFault
|
|
38
|
+
EB --> SkipFault
|
|
39
|
+
```
|
|
40
|
+
|
|
14
41
|
## Non-bang Execution
|
|
15
42
|
|
|
16
43
|
Always returns a `CMDx::Result`, never raises exceptions. Perfect for most use cases.
|
|
@@ -94,3 +121,32 @@ result.chain #=> Task execution chain
|
|
|
94
121
|
result.context #=> Context with all task data
|
|
95
122
|
result.metadata #=> Hash with execution metadata
|
|
96
123
|
```
|
|
124
|
+
|
|
125
|
+
## Dry Run
|
|
126
|
+
|
|
127
|
+
Execute tasks in dry-run mode to simulate execution without performing side effects. Pass `dry_run: true` in the context when initializing or executing the task.
|
|
128
|
+
|
|
129
|
+
Inside your task, use the `dry_run?` method to conditionally skip side effects.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
class CloseStripeCard < CMDx::Task
|
|
133
|
+
def work
|
|
134
|
+
context.stripe_result =
|
|
135
|
+
if dry_run?
|
|
136
|
+
FactoryBot.build(:stripe_closed_card)
|
|
137
|
+
else
|
|
138
|
+
StripeApi.close_card(context.card_id)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Execute in dry-run mode
|
|
144
|
+
result = CloseStripeCard.execute(card_id: "card_abc123", dry_run: true)
|
|
145
|
+
result.success? # => true
|
|
146
|
+
|
|
147
|
+
# FactoryBot object
|
|
148
|
+
result.context.stripe_result = {
|
|
149
|
+
card_id: "card_abc123",
|
|
150
|
+
status: "closed"
|
|
151
|
+
}
|
|
152
|
+
```
|
data/docs/basics/setup.md
CHANGED
|
@@ -29,13 +29,14 @@ IncompleteTask.execute #=> raises CMDx::UndefinedMethodError
|
|
|
29
29
|
Undo any operations linked to the given status, helping to restore a pristine state.
|
|
30
30
|
|
|
31
31
|
```ruby
|
|
32
|
-
class
|
|
32
|
+
class ChargeCard < CMDx::Task
|
|
33
33
|
def work
|
|
34
|
-
# Your logic here
|
|
34
|
+
# Your logic here, ex: charge $100
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Called automatically if a later step in the workflow fails
|
|
37
38
|
def rollback
|
|
38
|
-
# Your undo logic
|
|
39
|
+
# Your undo logic, ex: void $100 charge
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
```
|
|
@@ -70,6 +71,28 @@ end
|
|
|
70
71
|
|
|
71
72
|
Tasks follow a predictable execution pattern:
|
|
72
73
|
|
|
74
|
+
```mermaid
|
|
75
|
+
stateDiagram-v2
|
|
76
|
+
Initialized: Instantiation
|
|
77
|
+
Initialized --> Validating: execute
|
|
78
|
+
Validating --> Executing: Valid?
|
|
79
|
+
Validating --> Failed: Invalid
|
|
80
|
+
Executing --> Success: Work done
|
|
81
|
+
Executing --> Skipped: skip!
|
|
82
|
+
Executing --> Failed: fail! / Exception
|
|
83
|
+
Executed
|
|
84
|
+
|
|
85
|
+
state Executed {
|
|
86
|
+
Success
|
|
87
|
+
Skipped
|
|
88
|
+
Failed
|
|
89
|
+
Rollback
|
|
90
|
+
|
|
91
|
+
Skipped --> Rollback
|
|
92
|
+
Failed --> Rollback
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
73
96
|
!!! danger "Caution"
|
|
74
97
|
|
|
75
98
|
Tasks are single-use objects. Once executed, they're frozen and immutable.
|
data/docs/deprecation.md
CHANGED
data/docs/getting_started.md
CHANGED
|
@@ -43,6 +43,13 @@ If not using Rails, manually copy the [configuration file](https://github.com/dr
|
|
|
43
43
|
|
|
44
44
|
CMDx embraces the Compose, Execute, React, Observe (CERO, pronounced "zero") pattern—a simple yet powerful approach to building reliable business logic.
|
|
45
45
|
|
|
46
|
+
```mermaid
|
|
47
|
+
flowchart LR
|
|
48
|
+
Compose --> Execute
|
|
49
|
+
Execute --> React
|
|
50
|
+
Execute -.-> Observe
|
|
51
|
+
```
|
|
52
|
+
|
|
46
53
|
### Compose
|
|
47
54
|
|
|
48
55
|
Build reusable, single-responsibility tasks with typed attributes, validation, and callbacks. Tasks can be chained together in workflows to create complex business processes from simple building blocks.
|
|
@@ -93,6 +100,10 @@ I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx:
|
|
|
93
100
|
index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="AnalyzeMetrics" state="complete" status="success" metadata={runtime: 187}
|
|
94
101
|
```
|
|
95
102
|
|
|
103
|
+
!!! note
|
|
104
|
+
|
|
105
|
+
This represents a log-only event-sourcing approach, enabling full traceability and a complete, time-ordered view of system behavior.
|
|
106
|
+
|
|
96
107
|
## Task Generator
|
|
97
108
|
|
|
98
109
|
Generate new CMDx tasks quickly using the built-in generator:
|
data/docs/logging.md
CHANGED
|
@@ -18,20 +18,16 @@ Sample output:
|
|
|
18
18
|
|
|
19
19
|
```log
|
|
20
20
|
<!-- Success (INFO level) -->
|
|
21
|
-
I, [
|
|
22
|
-
index=0 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="GenerateInvoice" state="complete" status="success" metadata={runtime: 187}
|
|
21
|
+
I, [2025-12-23T17:04:07.292614Z #20108] INFO -- cmdx: {index: 1, chain_id: "019b4c2b-087b-79be-8ef2-96c11b659df5", type: "Task", tags: [], class: "GenerateInvoice", dry_run: false, id: "019b4c2b-0878-704d-ba0b-daa5410123ec", state: "complete", status: "success", outcome: "success", metadata: {runtime: 187}}
|
|
23
22
|
|
|
24
|
-
<!-- Skipped (
|
|
25
|
-
|
|
26
|
-
index=1 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="ValidateCustomer" state="interrupted" status="skipped" reason="Customer already validated"
|
|
23
|
+
<!-- Skipped (INFO level) -->
|
|
24
|
+
I, [2025-12-23T17:04:11.496881Z #20139] INFO -- cmdx: {index: 2, chain_id: "019b4c2b-18e8-7af6-a38b-63b042c4fbed", type: "Task", tags: [], class: "ValidateCustomer", dry_run: false, id: "019b4c2b-18e5-7230-af7e-5b4a4bd7cda2", state: "interrupted", status: "skipped", outcome: "skipped", metadata: {}, reason: "Customer already validated", cause: #<CMDx::SkipFault: Customer already validated>, rolled_back: false}
|
|
27
25
|
|
|
28
|
-
<!-- Failed (
|
|
29
|
-
|
|
30
|
-
index=2 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="CalculateTax" state="interrupted" status="failed" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"}
|
|
26
|
+
<!-- Failed (INFO level) -->
|
|
27
|
+
I, [2025-12-23T17:04:15.875306Z #20173] INFO -- cmdx: {index: 3, chain_id: "019b4c2b-2a02-7dbc-b713-b20a7379704f", type: "Task", tags: [], class: "CalculateTax", dry_run: false, id: "019b4c2b-2a00-70b7-9fab-2f14db9139ef", state: "interrupted", status: "failed", outcome: "failed", metadata: {error_code: "TAX_SERVICE_UNAVAILABLE"}, reason: "Validation failed", cause: #<CMDx::FailFault: Validation failed>, rolled_back: false}
|
|
31
28
|
|
|
32
29
|
<!-- Failed Chain -->
|
|
33
|
-
|
|
34
|
-
index=3 chain_id="018c2b95-b764-7615-a924-cc5b910ed1e5" type="Task" class="BillingWorkflow" state="interrupted" status="failed" caused_failure={index: 2, class: "CalculateTax", status: "failed"} threw_failure={index: 1, class: "ValidateCustomer", status: "failed"}
|
|
30
|
+
I, [2025-12-23T17:04:20.972539Z #20209] INFO -- cmdx: {index: 0, chain_id: "019b4c2b-3de9-71f7-bcc3-2a98836bcfd7", type: "Workflow", tags: [], class: "BillingWorkflow", dry_run: false, id: "019b4c2b-3de6-70b9-9c16-5be13b1a463c", state: "interrupted", status: "failed", outcome: "interrupted", metadata: {}, reason: "Validation failed", cause: #<CMDx::FailFault: Validation failed>, rolled_back: false, threw_failure: {index: 3, chain_id: "019b4c2b-3de9-71f7-bcc3-2a98836bcfd7", type: "Task", tags: [], class: "CalculateTax", id: "019b4c2b-3dec-70b3-969b-c5b7896e3b27", state: "interrupted", status: "failed", outcome: "failed", metadata: {error_code: "TAX_SERVICE_UNAVAILABLE"}, reason: "Validation failed", cause: #<CMDx::FailFault: Validation failed>, rolled_back: false}, caused_failure: {index: 3, chain_id: "019b4c2b-3de9-71f7-bcc3-2a98836bcfd7", type: "Task", tags: [], class: "CalculateTax", id: "019b4c2b-3dec-70b3-969b-c5b7896e3b27", state: "interrupted", status: "failed", outcome: "failed", metadata: {error_code: "TAX_SERVICE_UNAVAILABLE"}, reason: "Validation failed", cause: #<CMDx::FailFault: Validation failed>, rolled_back: false}}
|
|
35
31
|
```
|
|
36
32
|
|
|
37
33
|
!!! tip
|
data/docs/outcomes/result.md
CHANGED
|
@@ -30,7 +30,7 @@ result.metadata #=> { error_code: "BUILD_TOOL.NOT_FOUND" }
|
|
|
30
30
|
|
|
31
31
|
## Lifecycle Information
|
|
32
32
|
|
|
33
|
-
Check execution state and
|
|
33
|
+
Check execution state, status, and rollback with predicate methods:
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
36
|
result = BuildApplication.execute(version: "1.2.3")
|
|
@@ -48,6 +48,9 @@ result.skipped? #=> false (not skipped)
|
|
|
48
48
|
# Outcome categorization
|
|
49
49
|
result.good? #=> true (success or skipped)
|
|
50
50
|
result.bad? #=> false (skipped or failed)
|
|
51
|
+
|
|
52
|
+
# Rollback Status
|
|
53
|
+
result.rolled_back? #=> true (execution was rolled back)
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
## Outcome Analysis
|
data/lib/cmdx/chain.rb
CHANGED
|
@@ -39,9 +39,10 @@ module CMDx
|
|
|
39
39
|
# @return [Chain] A new chain instance
|
|
40
40
|
#
|
|
41
41
|
# @rbs () -> void
|
|
42
|
-
def initialize
|
|
42
|
+
def initialize(dry_run: false)
|
|
43
43
|
@id = Identifier.generate
|
|
44
44
|
@results = []
|
|
45
|
+
@dry_run = !!dry_run
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
class << self
|
|
@@ -102,16 +103,28 @@ module CMDx
|
|
|
102
103
|
# puts "Chain size: #{chain.size}"
|
|
103
104
|
#
|
|
104
105
|
# @rbs (Result result) -> Chain
|
|
105
|
-
def build(result)
|
|
106
|
+
def build(result, dry_run: false)
|
|
106
107
|
raise TypeError, "must be a CMDx::Result" unless result.is_a?(Result)
|
|
107
108
|
|
|
108
|
-
self.current ||= new
|
|
109
|
+
self.current ||= new(dry_run:)
|
|
109
110
|
current.results << result
|
|
110
111
|
current
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
end
|
|
114
115
|
|
|
116
|
+
# Returns whether the chain is running in dry-run mode.
|
|
117
|
+
#
|
|
118
|
+
# @return [Boolean] Whether the chain is running in dry-run mode
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# chain.dry_run? # => true
|
|
122
|
+
#
|
|
123
|
+
# @rbs () -> bool
|
|
124
|
+
def dry_run?
|
|
125
|
+
!!@dry_run
|
|
126
|
+
end
|
|
127
|
+
|
|
115
128
|
# Converts the chain to a hash representation.
|
|
116
129
|
#
|
|
117
130
|
# @option return [String] :id The chain identifier
|
|
@@ -127,7 +140,8 @@ module CMDx
|
|
|
127
140
|
# @rbs () -> Hash[Symbol, untyped]
|
|
128
141
|
def to_h
|
|
129
142
|
{
|
|
130
|
-
id
|
|
143
|
+
id:,
|
|
144
|
+
dry_run: dry_run?,
|
|
131
145
|
results: results.map(&:to_h)
|
|
132
146
|
}
|
|
133
147
|
end
|
data/lib/cmdx/context.rb
CHANGED
|
@@ -164,6 +164,7 @@ module CMDx
|
|
|
164
164
|
args.to_h.each { |key, value| self[key.to_sym] = value }
|
|
165
165
|
self
|
|
166
166
|
end
|
|
167
|
+
alias merge merge!
|
|
167
168
|
|
|
168
169
|
# Deletes a key-value pair from the context.
|
|
169
170
|
#
|
|
@@ -182,6 +183,7 @@ module CMDx
|
|
|
182
183
|
def delete!(key, &)
|
|
183
184
|
table.delete(key.to_sym, &)
|
|
184
185
|
end
|
|
186
|
+
alias delete delete!
|
|
185
187
|
|
|
186
188
|
# Compares this context with another object for equality.
|
|
187
189
|
#
|
data/lib/cmdx/executor.rb
CHANGED
|
@@ -8,6 +8,8 @@ module CMDx
|
|
|
8
8
|
# and proper error handling for different types of failures.
|
|
9
9
|
class Executor
|
|
10
10
|
|
|
11
|
+
extend Forwardable
|
|
12
|
+
|
|
11
13
|
# Returns the task being executed.
|
|
12
14
|
#
|
|
13
15
|
# @return [Task] The task instance
|
|
@@ -18,6 +20,8 @@ module CMDx
|
|
|
18
20
|
# @rbs @task: Task
|
|
19
21
|
attr_reader :task
|
|
20
22
|
|
|
23
|
+
def_delegators :task, :result
|
|
24
|
+
|
|
21
25
|
# @param task [CMDx::Task] The task to execute
|
|
22
26
|
#
|
|
23
27
|
# @return [CMDx::Executor] A new executor instance
|
|
@@ -65,13 +69,13 @@ module CMDx
|
|
|
65
69
|
rescue UndefinedMethodError => e
|
|
66
70
|
raise(e) # No need to clear the Chain since exception is not being re-raised
|
|
67
71
|
rescue Fault => e
|
|
68
|
-
|
|
72
|
+
result.throw!(e.result, halt: false, cause: e)
|
|
69
73
|
rescue StandardError => e
|
|
70
74
|
retry if retry_execution?(e)
|
|
71
|
-
|
|
75
|
+
result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
|
|
72
76
|
task.class.settings[:exception_handler]&.call(task, e)
|
|
73
77
|
ensure
|
|
74
|
-
|
|
78
|
+
result.executed!
|
|
75
79
|
post_execution!
|
|
76
80
|
end
|
|
77
81
|
|
|
@@ -96,14 +100,14 @@ module CMDx
|
|
|
96
100
|
rescue UndefinedMethodError => e
|
|
97
101
|
raise_exception(e)
|
|
98
102
|
rescue Fault => e
|
|
99
|
-
|
|
103
|
+
result.throw!(e.result, halt: false, cause: e)
|
|
100
104
|
halt_execution?(e) ? raise_exception(e) : post_execution!
|
|
101
105
|
rescue StandardError => e
|
|
102
106
|
retry if retry_execution?(e)
|
|
103
|
-
|
|
107
|
+
result.fail!("[#{e.class}] #{e.message}", halt: false, cause: e)
|
|
104
108
|
raise_exception(e)
|
|
105
109
|
else
|
|
106
|
-
|
|
110
|
+
result.executed!
|
|
107
111
|
post_execution!
|
|
108
112
|
end
|
|
109
113
|
|
|
@@ -137,14 +141,14 @@ module CMDx
|
|
|
137
141
|
available_retries = (task.class.settings[:retries] || 0).to_i
|
|
138
142
|
return false unless available_retries.positive?
|
|
139
143
|
|
|
140
|
-
current_retries = (
|
|
144
|
+
current_retries = (result.metadata[:retries] ||= 0).to_i
|
|
141
145
|
remaining_retries = available_retries - current_retries
|
|
142
146
|
return false unless remaining_retries.positive?
|
|
143
147
|
|
|
144
148
|
exceptions = Array(task.class.settings[:retry_on] || StandardError)
|
|
145
149
|
return false unless exceptions.any? { |e| exception.class <= e }
|
|
146
150
|
|
|
147
|
-
|
|
151
|
+
result.metadata[:retries] += 1
|
|
148
152
|
|
|
149
153
|
task.logger.warn do
|
|
150
154
|
reason = "[#{exception.class}] #{exception.message}"
|
|
@@ -208,7 +212,7 @@ module CMDx
|
|
|
208
212
|
task.class.settings[:attributes].define_and_verify(task)
|
|
209
213
|
return if task.errors.empty?
|
|
210
214
|
|
|
211
|
-
|
|
215
|
+
result.fail!(
|
|
212
216
|
Locale.t("cmdx.faults.invalid"),
|
|
213
217
|
errors: {
|
|
214
218
|
full_message: task.errors.to_s,
|
|
@@ -223,7 +227,7 @@ module CMDx
|
|
|
223
227
|
def execution!
|
|
224
228
|
invoke_callbacks(:before_execution)
|
|
225
229
|
|
|
226
|
-
|
|
230
|
+
result.executing!
|
|
227
231
|
task.work
|
|
228
232
|
end
|
|
229
233
|
|
|
@@ -231,12 +235,12 @@ module CMDx
|
|
|
231
235
|
#
|
|
232
236
|
# @rbs () -> void
|
|
233
237
|
def post_execution!
|
|
234
|
-
invoke_callbacks(:"on_#{
|
|
235
|
-
invoke_callbacks(:on_executed) if
|
|
238
|
+
invoke_callbacks(:"on_#{result.state}")
|
|
239
|
+
invoke_callbacks(:on_executed) if result.executed?
|
|
236
240
|
|
|
237
|
-
invoke_callbacks(:"on_#{
|
|
238
|
-
invoke_callbacks(:on_good) if
|
|
239
|
-
invoke_callbacks(:on_bad) if
|
|
241
|
+
invoke_callbacks(:"on_#{result.status}")
|
|
242
|
+
invoke_callbacks(:on_good) if result.good?
|
|
243
|
+
invoke_callbacks(:on_bad) if result.bad?
|
|
240
244
|
end
|
|
241
245
|
|
|
242
246
|
# Finalizes execution by freezing the task, logging results, and rolling back work.
|
|
@@ -246,26 +250,25 @@ module CMDx
|
|
|
246
250
|
log_execution!
|
|
247
251
|
log_backtrace! if task.class.settings[:backtrace]
|
|
248
252
|
|
|
253
|
+
rollback_execution!
|
|
249
254
|
freeze_execution!
|
|
250
255
|
clear_chain!
|
|
251
|
-
|
|
252
|
-
rollback_execution!
|
|
253
256
|
end
|
|
254
257
|
|
|
255
258
|
# Logs the execution result at the configured log level.
|
|
256
259
|
#
|
|
257
260
|
# @rbs () -> void
|
|
258
261
|
def log_execution!
|
|
259
|
-
task.logger.info {
|
|
262
|
+
task.logger.info { result.to_h }
|
|
260
263
|
end
|
|
261
264
|
|
|
262
265
|
# Logs the backtrace of the exception if the task failed.
|
|
263
266
|
#
|
|
264
267
|
# @rbs () -> void
|
|
265
268
|
def log_backtrace!
|
|
266
|
-
return unless
|
|
269
|
+
return unless result.failed?
|
|
267
270
|
|
|
268
|
-
exception =
|
|
271
|
+
exception = result.caused_failure.cause
|
|
269
272
|
return if exception.is_a?(Fault)
|
|
270
273
|
|
|
271
274
|
task.logger.error do
|
|
@@ -287,11 +290,11 @@ module CMDx
|
|
|
287
290
|
return if Coercions::Boolean.call(skip_freezing)
|
|
288
291
|
|
|
289
292
|
task.freeze
|
|
290
|
-
|
|
293
|
+
result.freeze
|
|
291
294
|
|
|
292
295
|
# Freezing the context and chain can only be done
|
|
293
296
|
# once the outer-most task has completed.
|
|
294
|
-
return unless
|
|
297
|
+
return unless result.index.zero?
|
|
295
298
|
|
|
296
299
|
task.context.freeze
|
|
297
300
|
task.chain.freeze
|
|
@@ -301,7 +304,7 @@ module CMDx
|
|
|
301
304
|
#
|
|
302
305
|
# @rbs () -> void
|
|
303
306
|
def clear_chain!
|
|
304
|
-
return unless
|
|
307
|
+
return unless result.index.zero?
|
|
305
308
|
|
|
306
309
|
Chain.clear
|
|
307
310
|
end
|
|
@@ -310,12 +313,14 @@ module CMDx
|
|
|
310
313
|
#
|
|
311
314
|
# @rbs () -> void
|
|
312
315
|
def rollback_execution!
|
|
316
|
+
return if result.rolled_back?
|
|
313
317
|
return unless task.respond_to?(:rollback)
|
|
314
318
|
|
|
315
319
|
statuses = task.class.settings[:rollback_on]
|
|
316
320
|
statuses = Array(statuses).map(&:to_s).uniq
|
|
317
|
-
return unless statuses.include?(
|
|
321
|
+
return unless statuses.include?(result.status)
|
|
318
322
|
|
|
323
|
+
result.rolled_back!
|
|
319
324
|
task.rollback
|
|
320
325
|
end
|
|
321
326
|
|
data/lib/cmdx/result.rb
CHANGED
|
@@ -95,7 +95,7 @@ module CMDx
|
|
|
95
95
|
# @rbs @cause: (Exception | nil)
|
|
96
96
|
attr_reader :cause
|
|
97
97
|
|
|
98
|
-
def_delegators :task, :context, :chain, :errors
|
|
98
|
+
def_delegators :task, :context, :chain, :errors, :dry_run?
|
|
99
99
|
alias ctx context
|
|
100
100
|
|
|
101
101
|
# @param task [CMDx::Task] The task instance this result represents
|
|
@@ -118,6 +118,7 @@ module CMDx
|
|
|
118
118
|
@metadata = {}
|
|
119
119
|
@reason = nil
|
|
120
120
|
@cause = nil
|
|
121
|
+
@rolled_back = false
|
|
121
122
|
end
|
|
122
123
|
|
|
123
124
|
STATES.each do |s|
|
|
@@ -419,6 +420,27 @@ module CMDx
|
|
|
419
420
|
failed? && !caused_failure?
|
|
420
421
|
end
|
|
421
422
|
|
|
423
|
+
# @return [void]
|
|
424
|
+
#
|
|
425
|
+
# @example
|
|
426
|
+
# result.rolled_back!
|
|
427
|
+
# result.rolled_back? # => true
|
|
428
|
+
#
|
|
429
|
+
# @rbs () -> void
|
|
430
|
+
def rolled_back!
|
|
431
|
+
@rolled_back = true
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# @return [Boolean] Whether the result has been rolled back
|
|
435
|
+
#
|
|
436
|
+
# @example
|
|
437
|
+
# result.rolled_back? # => true
|
|
438
|
+
#
|
|
439
|
+
# @rbs () -> bool
|
|
440
|
+
def rolled_back?
|
|
441
|
+
!!@rolled_back
|
|
442
|
+
end
|
|
443
|
+
|
|
422
444
|
# @return [Integer] Index of this result in the chain
|
|
423
445
|
#
|
|
424
446
|
# @example
|
|
@@ -457,6 +479,7 @@ module CMDx
|
|
|
457
479
|
if interrupted?
|
|
458
480
|
hash[:reason] = reason
|
|
459
481
|
hash[:cause] = cause
|
|
482
|
+
hash[:rolled_back] = rolled_back?
|
|
460
483
|
end
|
|
461
484
|
|
|
462
485
|
if failed?
|
data/lib/cmdx/task.rb
CHANGED
|
@@ -71,6 +71,7 @@ module CMDx
|
|
|
71
71
|
attr_reader :chain
|
|
72
72
|
|
|
73
73
|
def_delegators :result, :skip!, :fail!, :throw!
|
|
74
|
+
def_delegators :chain, :dry_run?
|
|
74
75
|
|
|
75
76
|
# @param context [Hash, Context] The initial context for the task
|
|
76
77
|
#
|
|
@@ -94,7 +95,7 @@ module CMDx
|
|
|
94
95
|
@id = Identifier.generate
|
|
95
96
|
@context = Context.build(context)
|
|
96
97
|
@result = Result.new(self)
|
|
97
|
-
@chain = Chain.build(@result)
|
|
98
|
+
@chain = Chain.build(@result, dry_run: @context.delete(:dry_run))
|
|
98
99
|
end
|
|
99
100
|
|
|
100
101
|
class << self
|
|
@@ -341,6 +342,7 @@ module CMDx
|
|
|
341
342
|
type: self.class.include?(Workflow) ? "Workflow" : "Task",
|
|
342
343
|
tags: self.class.settings[:tags],
|
|
343
344
|
class: self.class.name,
|
|
345
|
+
dry_run: dry_run?,
|
|
344
346
|
id:
|
|
345
347
|
}
|
|
346
348
|
end
|
data/lib/cmdx/version.rb
CHANGED
data/mkdocs.yml
CHANGED
|
@@ -65,7 +65,11 @@ markdown_extensions:
|
|
|
65
65
|
- attr_list
|
|
66
66
|
- md_in_html
|
|
67
67
|
- pymdownx.details
|
|
68
|
-
- pymdownx.superfences
|
|
68
|
+
- pymdownx.superfences:
|
|
69
|
+
custom_fences:
|
|
70
|
+
- name: mermaid
|
|
71
|
+
class: mermaid
|
|
72
|
+
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
69
73
|
- pymdownx.highlight:
|
|
70
74
|
anchor_linenums: true
|
|
71
75
|
line_spans: __span
|