local_ci_plus 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52d82a3a662d876af8bd88a3ba88c27050b44a48a506a26688d719a9cd5b05a6
4
- data.tar.gz: 55c9c680244d18fe4d3f929a58478843d15ac6fd272d622c912cb6a2ce2950b7
3
+ metadata.gz: e29292c8c4b406b43f0e1729ffbfbf52bb7931fa8f62dedab7e9631baab45a5f
4
+ data.tar.gz: 685c98aadeb7b563c077a5ebf99da60880403e3a9bcbcc15e646fac6c70f841d
5
5
  SHA512:
6
- metadata.gz: ed182ba9daadbb729a5372043d0b9990698ea3b5646df35ddbb26eca7e3449aa36325997eac26306bd45c2f7e4a0e127b262de92824439df07d97e7adabe772c
7
- data.tar.gz: 44a487af7e2cf061c6287d73aab17a901ccd11f7d6cbe43f7d2b8e9064b1a6e5ebe7ada4d5f2a8e1c858e68d833c2a691d1c42bb1288b7468ce09141a6107115
6
+ metadata.gz: c40a186742fa94b71a099820f73a03feb79f8b347a00bc8d86d2835dec8070c8acd0f9c03ae30cd37d44f1b3a59bdebb3d897c4d8d0844430e75bd6f5fda1088
7
+ data.tar.gz: ac0a1b4b6adf8ca08457571a8f38fb2259e202c8bf5f4f2a47033495afeb5eb1439bebc9eab031d01083bdbbea9913d7fb05e97ce90e0a5e5d4948bc248434ee
data/README.md CHANGED
@@ -28,6 +28,23 @@ continues to work without changes. In plain/non-TTY mode, output is ASCII-only.
28
28
  bin/ci
29
29
  ```
30
30
 
31
+ Use `parallel_steps` when you want a parallel phase before sequential follow-up steps.
32
+ If any step in `parallel_steps` fails, later steps are skipped and listed in the summary.
33
+ If no `parallel_steps` block is present, all steps run in parallel (default behavior).
34
+
35
+ ```ruby
36
+ CI.run do
37
+ parallel_steps do
38
+ step "Style: Ruby", "bin/standardrb"
39
+ step "JavaScript: lint", "npm run lint"
40
+ step "Security: Gem audit", "bin/bundler-audit"
41
+ end
42
+
43
+ step "Tests: Rails", "bin/rspec"
44
+ step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
45
+ end
46
+ ```
47
+
31
48
  If your app does not already require the gem in `bin/ci`, run the installer generator:
32
49
 
33
50
  ```bash
@@ -24,7 +24,7 @@ module LocalCiPlus
24
24
  STATE_FILE = File.join("tmp", "ci_state")
25
25
  STATE_FILE_ENV = "CI_STATE_FILE"
26
26
 
27
- attr_reader :results
27
+ attr_reader :results, :skipped_steps
28
28
 
29
29
  def self.run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block)
30
30
  new.tap do |ci|
@@ -45,6 +45,11 @@ module LocalCiPlus
45
45
  @results = []
46
46
  @step_names = []
47
47
  @parallel_steps = []
48
+ @using_explicit_parallel_steps = false
49
+ @inside_parallel_steps_block = false
50
+ @skip_remaining_steps = false
51
+ @skip_remaining_reason = nil
52
+ @skipped_steps = []
48
53
  @skip_until = continue_mode? ? load_failed_step : nil
49
54
  @skipping = !!@skip_until
50
55
  end
@@ -80,31 +85,48 @@ module LocalCiPlus
80
85
  if title == @skip_until
81
86
  @skipping = false
82
87
  else
83
- heading title, "skipped (resuming from: #{@skip_until})", type: :skip
84
- results << [true, title]
88
+ mark_step_skipped(title, "resuming from: #{@skip_until}")
85
89
  return
86
90
  end
87
91
  end
88
92
 
89
- if parallel?
93
+ if @skip_remaining_steps
94
+ mark_step_skipped(title, @skip_remaining_reason || "parallel steps failed")
95
+ return
96
+ end
97
+
98
+ if queue_parallel_step?
90
99
  @parallel_steps << {title: title, command: command}
91
100
  return
92
101
  end
93
102
 
94
- heading title, command.join(" "), type: :title
95
- report(title) do
96
- success = system(*command)
97
- results << [success, title]
103
+ run_inline_step(title, command)
104
+ end
98
105
 
99
- if success
100
- clear_state if recorded_failed_step == title
101
- else
102
- record_failed_step(title)
103
- if fail_fast?
104
- abort colorize("\n#{status_marker(:error)} #{title} failed (fail-fast enabled)", :error)
105
- end
106
+ def parallel_steps(&block)
107
+ return unless block
108
+
109
+ @using_explicit_parallel_steps = true
110
+
111
+ if parallel?
112
+ if @parallel_steps.any?
113
+ completed = run_parallel_steps!(@parallel_steps)
114
+ @parallel_steps.clear
115
+ mark_remaining_steps_skipped!("parallel_steps failed") if completed.any? { |job| !job[:success] }
106
116
  end
117
+
118
+ @inside_parallel_steps_block = true
119
+ queue_start = @parallel_steps.length
120
+ instance_eval(&block)
121
+ batch = @parallel_steps.slice!(queue_start, @parallel_steps.length - queue_start) || []
122
+ completed = run_parallel_steps!(batch)
123
+ mark_remaining_steps_skipped!("parallel_steps failed") if completed.any? { |job| !job[:success] }
124
+ else
125
+ @inside_parallel_steps_block = true
126
+ instance_eval(&block)
107
127
  end
128
+ ensure
129
+ @inside_parallel_steps_block = false
108
130
  end
109
131
 
110
132
  def success?
@@ -148,7 +170,7 @@ module LocalCiPlus
148
170
 
149
171
  elapsed = timing do
150
172
  ci.instance_eval(&block)
151
- ci.run_parallel_steps! if ci.parallel? && ci.parallel_steps.any?
173
+ ci.run_parallel_steps!(ci.queued_parallel_steps) if ci.parallel? && ci.queued_parallel_steps.any?
152
174
  end
153
175
 
154
176
  @skip_until = ci.instance_variable_get(:@skip_until)
@@ -165,6 +187,12 @@ module LocalCiPlus
165
187
  echo " #{failure_bullet} #{step_title} failed", type: :error
166
188
  end
167
189
  end
190
+
191
+ if ci.skipped_steps.any?
192
+ ci.skipped_steps.each do |step_title|
193
+ echo " #{failure_bullet} #{step_title} skipped", type: :skip
194
+ end
195
+ end
168
196
  end
169
197
 
170
198
  results.concat ci.results
@@ -208,24 +236,29 @@ module LocalCiPlus
208
236
  false
209
237
  end
210
238
 
211
- attr_reader :parallel_steps
239
+ def queued_parallel_steps
240
+ @parallel_steps
241
+ end
212
242
 
213
243
  MAX_OUTPUT_BYTES = 100 * 1024 # 100KB max per output stream
214
244
 
215
- def run_parallel_steps!
216
- total = @parallel_steps.size
245
+ def run_parallel_steps!(steps = @parallel_steps)
246
+ return [] if steps.empty?
247
+
248
+ total = steps.size
249
+ @active_parallel_size = total
217
250
  @running_jobs = []
218
251
  completed = []
219
252
  completed_by_index = {}
220
253
 
221
254
  echo "\n#{status_marker(:pending)} Running #{total} steps in parallel:", type: :subtitle
222
255
  unless plain?
223
- @parallel_steps.each do |step|
256
+ steps.each do |step|
224
257
  echo format_parallel_line(step[:title], :pending), type: :pending
225
258
  end
226
259
  end
227
260
 
228
- @parallel_steps.each_with_index do |step_info, idx|
261
+ steps.each_with_index do |step_info, idx|
229
262
  title = step_info[:title]
230
263
  command = step_info[:command]
231
264
 
@@ -277,7 +310,7 @@ module LocalCiPlus
277
310
  end
278
311
 
279
312
  if plain?
280
- @parallel_steps.each_with_index do |step, idx|
313
+ steps.each_with_index do |step, idx|
281
314
  job = completed_by_index[idx]
282
315
  type = job[:success] ? :success : :error
283
316
  echo format_parallel_line(step[:title], type, duration: job[:duration]), type: type
@@ -285,7 +318,9 @@ module LocalCiPlus
285
318
  end
286
319
 
287
320
  print_parallel_summary(completed)
321
+ completed
288
322
  ensure
323
+ @active_parallel_size = nil
289
324
  cleanup_all_jobs!
290
325
  end
291
326
 
@@ -337,6 +372,42 @@ module LocalCiPlus
337
372
 
338
373
  private
339
374
 
375
+ def run_inline_step(title, command)
376
+ heading title, command.join(" "), type: :title
377
+ report(title) do
378
+ success = system(*command)
379
+ results << [success, title]
380
+
381
+ if success
382
+ clear_state if recorded_failed_step == title
383
+ else
384
+ record_failed_step(title)
385
+ if fail_fast?
386
+ abort colorize("\n#{status_marker(:error)} #{title} failed (fail-fast enabled)", :error)
387
+ end
388
+ end
389
+ end
390
+ end
391
+
392
+ def mark_step_skipped(title, reason)
393
+ heading title, "skipped (#{reason})", type: :skip
394
+ @skipped_steps << title if reason == @skip_remaining_reason
395
+ results << [true, title]
396
+ end
397
+
398
+ def queue_parallel_step?
399
+ return false unless parallel?
400
+ return true if @inside_parallel_steps_block
401
+ return false if @using_explicit_parallel_steps
402
+
403
+ true
404
+ end
405
+
406
+ def mark_remaining_steps_skipped!(reason)
407
+ @skip_remaining_steps = true
408
+ @skip_remaining_reason = reason
409
+ end
410
+
340
411
  def print_parallel_summary(completed)
341
412
  failed_jobs = completed.reject { |j| j[:success] }
342
413
  return if failed_jobs.empty?
@@ -446,7 +517,7 @@ module LocalCiPlus
446
517
  return
447
518
  end
448
519
 
449
- lines_up = @parallel_steps.size - index
520
+ lines_up = (@active_parallel_size || @parallel_steps.size) - index
450
521
  print "\033[s"
451
522
  print "\033[#{lines_up}A" if lines_up > 0
452
523
  print "\r\033[2K"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LocalCiPlus
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: local_ci_plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Watermasysk