local_ci_plus 0.5.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: c5e469a0e3234d66985a729cb66222e4f57ea4f627804a7ff4d96f39dcafc2c0
4
- data.tar.gz: 0ae35f6c3627d2fdf6fd68b27278563d346a560d419259f115b685c41d1693c0
3
+ metadata.gz: e29292c8c4b406b43f0e1729ffbfbf52bb7931fa8f62dedab7e9631baab45a5f
4
+ data.tar.gz: 685c98aadeb7b563c077a5ebf99da60880403e3a9bcbcc15e646fac6c70f841d
5
5
  SHA512:
6
- metadata.gz: 02d9a9b3b8af2921728c5e3c7625247e72ceee8d43e2512d7f8de851a956ae1c24a2cace9691655db01600e505e834d37d0792161b4a401a4b2cad3e629b2b12
7
- data.tar.gz: '08442d855fa89ee0739d90e62145a19720ea6c44f961c1ba3af33dee3289928650e9fc32987699f0bec2a28187a81a235ef98ab0ef79ef7bb6f11d435a467d51'
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
@@ -51,16 +68,15 @@ require_relative "../config/ci"
51
68
 
52
69
  ### Options
53
70
 
71
+ Runs all CI steps in parallel by default.
72
+
54
73
  ```
55
- -f, --fail-fast Stop immediately when a step fails
56
- -c, --continue Resume from the last failed step
74
+ -f, --fail-fast Stop immediately when a step fails (runs inline)
75
+ -c, --continue Resume from the last failed step (runs inline)
57
76
  -fc, -cf Combine fail-fast and continue
58
- -p, --parallel Run all steps concurrently
77
+ -i, --inline Run steps sequentially instead of parallel
59
78
  --plain Disable ANSI cursor updates/colors (also used for non-TTY)
60
79
  -h, --help Show this help
61
-
62
- Compatibility:
63
- --parallel cannot be combined with --fail-fast or --continue
64
80
  ```
65
81
 
66
82
  ### State file
@@ -83,8 +99,8 @@ bundle exec standardrb
83
99
 
84
100
  Or run them both:
85
101
 
86
- ```base
87
- bin/ci --parallel
102
+ ```bash
103
+ bin/ci
88
104
  ```
89
105
 
90
106
  ### Publishing Updates
@@ -96,3 +112,9 @@ bin/ci --parallel
96
112
  gem build local_ci_plus.gemspec
97
113
  gem push local_ci_plus-X.Y.Z.gem
98
114
  ```
115
+
116
+ ## Prior Work
117
+
118
+ This project is based off of ActiveSupport::ContinuousIntegration.
119
+
120
+ Many thanks to the Rails team for shipping the original work.
@@ -4,7 +4,7 @@
4
4
  # - bin/ci -f (--fail-fast): Stop immediately when a step fails
5
5
  # - bin/ci -c (--continue): Resume from the last failed step
6
6
  # - bin/ci -fc: Both options combined
7
- # - bin/ci -p (--parallel): Run all steps concurrently
7
+ # - bin/ci -i (--inline): Run steps sequentially (default is parallel)
8
8
 
9
9
  require "fileutils"
10
10
  require "tempfile"
@@ -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,17 +45,17 @@ 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
51
56
 
52
57
  def validate_mode_compatibility!
53
- if parallel? && fail_fast?
54
- abort colorize("#{status_marker(:error)} Cannot combine --parallel with --fail-fast", :error)
55
- end
56
- if parallel? && continue_mode?
57
- abort colorize("#{status_marker(:error)} Cannot combine --parallel with --continue", :error)
58
- end
58
+ # No incompatible combinations now that fail-fast/continue auto-disable parallel
59
59
  end
60
60
 
61
61
  def help?
@@ -66,16 +66,15 @@ module LocalCiPlus
66
66
  $stdout.puts <<~HELP
67
67
  Usage: bin/ci [options]
68
68
 
69
+ Runs all CI steps in parallel by default.
70
+
69
71
  Options:
70
- -f, --fail-fast Stop immediately when a step fails
71
- -c, --continue Resume from the last failed step
72
+ -f, --fail-fast Stop immediately when a step fails (runs inline)
73
+ -c, --continue Resume from the last failed step (runs inline)
72
74
  -fc, -cf Combine fail-fast and continue
73
- -p, --parallel Run all steps concurrently
75
+ -i, --inline Run steps sequentially instead of parallel
74
76
  --plain Disable ANSI cursor updates/colors (also used for non-TTY)
75
77
  -h, --help Show this help
76
-
77
- Compatibility:
78
- --parallel cannot be combined with --fail-fast or --continue
79
78
  HELP
80
79
  end
81
80
 
@@ -86,31 +85,48 @@ module LocalCiPlus
86
85
  if title == @skip_until
87
86
  @skipping = false
88
87
  else
89
- heading title, "skipped (resuming from: #{@skip_until})", type: :skip
90
- results << [true, title]
88
+ mark_step_skipped(title, "resuming from: #{@skip_until}")
91
89
  return
92
90
  end
93
91
  end
94
92
 
95
- 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?
96
99
  @parallel_steps << {title: title, command: command}
97
100
  return
98
101
  end
99
102
 
100
- heading title, command.join(" "), type: :title
101
- report(title) do
102
- success = system(*command)
103
- results << [success, title]
103
+ run_inline_step(title, command)
104
+ end
104
105
 
105
- if success
106
- clear_state if recorded_failed_step == title
107
- else
108
- record_failed_step(title)
109
- if fail_fast?
110
- abort colorize("\n#{status_marker(:error)} #{title} failed (fail-fast enabled)", :error)
111
- 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] }
112
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)
113
127
  end
128
+ ensure
129
+ @inside_parallel_steps_block = false
114
130
  end
115
131
 
116
132
  def success?
@@ -154,7 +170,7 @@ module LocalCiPlus
154
170
 
155
171
  elapsed = timing do
156
172
  ci.instance_eval(&block)
157
- 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?
158
174
  end
159
175
 
160
176
  @skip_until = ci.instance_variable_get(:@skip_until)
@@ -171,6 +187,12 @@ module LocalCiPlus
171
187
  echo " #{failure_bullet} #{step_title} failed", type: :error
172
188
  end
173
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
174
196
  end
175
197
 
176
198
  results.concat ci.results
@@ -198,7 +220,10 @@ module LocalCiPlus
198
220
  end
199
221
 
200
222
  def parallel?
201
- ARGV.include?("-p") || ARGV.include?("--parallel")
223
+ return false if ARGV.include?("-i") || ARGV.include?("--inline")
224
+ return false if fail_fast? || continue_mode?
225
+
226
+ true
202
227
  end
203
228
 
204
229
  def plain?
@@ -211,24 +236,29 @@ module LocalCiPlus
211
236
  false
212
237
  end
213
238
 
214
- attr_reader :parallel_steps
239
+ def queued_parallel_steps
240
+ @parallel_steps
241
+ end
215
242
 
216
243
  MAX_OUTPUT_BYTES = 100 * 1024 # 100KB max per output stream
217
244
 
218
- def run_parallel_steps!
219
- 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
220
250
  @running_jobs = []
221
251
  completed = []
222
252
  completed_by_index = {}
223
253
 
224
254
  echo "\n#{status_marker(:pending)} Running #{total} steps in parallel:", type: :subtitle
225
255
  unless plain?
226
- @parallel_steps.each do |step|
256
+ steps.each do |step|
227
257
  echo format_parallel_line(step[:title], :pending), type: :pending
228
258
  end
229
259
  end
230
260
 
231
- @parallel_steps.each_with_index do |step_info, idx|
261
+ steps.each_with_index do |step_info, idx|
232
262
  title = step_info[:title]
233
263
  command = step_info[:command]
234
264
 
@@ -280,7 +310,7 @@ module LocalCiPlus
280
310
  end
281
311
 
282
312
  if plain?
283
- @parallel_steps.each_with_index do |step, idx|
313
+ steps.each_with_index do |step, idx|
284
314
  job = completed_by_index[idx]
285
315
  type = job[:success] ? :success : :error
286
316
  echo format_parallel_line(step[:title], type, duration: job[:duration]), type: type
@@ -288,7 +318,9 @@ module LocalCiPlus
288
318
  end
289
319
 
290
320
  print_parallel_summary(completed)
321
+ completed
291
322
  ensure
323
+ @active_parallel_size = nil
292
324
  cleanup_all_jobs!
293
325
  end
294
326
 
@@ -340,6 +372,42 @@ module LocalCiPlus
340
372
 
341
373
  private
342
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
+
343
411
  def print_parallel_summary(completed)
344
412
  failed_jobs = completed.reject { |j| j[:success] }
345
413
  return if failed_jobs.empty?
@@ -449,7 +517,7 @@ module LocalCiPlus
449
517
  return
450
518
  end
451
519
 
452
- lines_up = @parallel_steps.size - index
520
+ lines_up = (@active_parallel_size || @parallel_steps.size) - index
453
521
  print "\033[s"
454
522
  print "\033[#{lines_up}A" if lines_up > 0
455
523
  print "\r\033[2K"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LocalCiPlus
4
- VERSION = "0.5.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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Watermasysk