Provides a DSL for declaring a continuous integration workflow that can be run either locally or in the cloud. Each step is timed, reports success/error, and is aggregated into a collective report that reports total runtime, as well as whether the entire run was successful or not.
Example:
ActiveSupport::ContinuousIntegration.run do step "Setup", "bin/setup --skip-server" step "Style: Ruby", "bin/rubocop" step "Security: Gem audit", "bin/bundler-audit" step "Tests: Rails", "bin/rails test test:system" if success? step "Signoff: Ready for merge and deploy", "gh signoff" else failure "Skipping signoff; CI failed.", "Fix the issues and try again." end end
Starting with Rails 8.1, a default ‘bin/ci` and `config/ci.rb` file are created to provide out-of-the-box CI.
Constants
| COLORS | = | { banner: "\033[1;32m", # Green title: "\033[1;35m", # Purple subtitle: "\033[1;90m", # Medium Gray error: "\033[1;31m", # Red success: "\033[1;32m", # Green progress: "\033[1;36m" # Cyan } |
Attributes
| [R] | results |
Class Public methods
new() Link
run(title = "Continuous Integration", subtitle = "Running tests, style checks, and security audits", &block) Link
Perform a CI run. Execute each step, show their results and runtime, and exit with a non-zero status if there are any failures.
Pass an optional title, subtitle, and a block that declares the steps to be executed.
Sets the CI environment variable to “true” to allow for conditional behavior in the app, like enabling eager loading and disabling logging.
A ‘fail fast’ option can be passed as a CLI argument (-f or –fail-fast). This exits with a non-zero status directly after a step fails.
Example:
ActiveSupport::ContinuousIntegration.run do step "Setup", "bin/setup --skip-server" step "Style: Ruby", "bin/rubocop" step "Security: Gem audit", "bin/bundler-audit" step "Tests: Rails", "bin/rails test test:system" if success? step "Signoff: Ready for merge and deploy", "gh signoff" else failure "Skipping signoff; CI failed.", "Fix the issues and try again." end end
Instance Public methods
echo(text, type:) Link
Echo text to the terminal in the color corresponding to the type of the text.
Examples:
echo "This is going to be green!", type: :success echo "This is going to be red!", type: :error
See ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.
failure(title, subtitle = nil) Link
Display an error heading with the title and optional subtitle to reflect that the run failed.
group(name, parallel: 1, &block) Link
Declare a group of steps that can be run in parallel. Steps within the group are collected first, then executed either concurrently (when parallel > 1) or sequentially (when parallel is 1).
When running in parallel, each step’s output is captured to avoid interleaving, and a progress display shows which steps are currently running.
Sub-groups within a parallel group occupy a single parallel slot and run their steps sequentially.
Examples:
group "Checks", parallel: 3 do step "Style: Ruby", "bin/rubocop" step "Security: Brakeman", "bin/brakeman --quiet" step "Security: Gem audit", "bin/bundler-audit" end group "Tests" do step "Unit tests", "bin/rails test" step "System tests", "bin/rails test:system" end
heading(heading, subtitle = nil, type: :banner, padding: true) Link
Display a colorized heading followed by an optional subtitle.
Examples:
heading "Smoke Testing", "End-to-end tests verifying key functionality", padding: false heading "Skipping video encoding tests", "Install FFmpeg to run these tests", type: :error
See ActiveSupport::ContinuousIntegration::COLORS for a complete list of options.
run(title, subtitle, &block) Link
step(title, *command) Link
Declare a step with a title and a command. The command can either be given as a single string or as multiple strings that will be passed to ‘system` as individual arguments (and therefore correctly escaped for paths etc).
Examples:
step "Setup", "bin/setup" step "Single test", "bin/rails", "test", "--name", "test_that_is_one"
# File activesupport/lib/active_support/continuous_integration.rb, line 81 def step(title, *command) previous_trap = Signal.trap("INT") { abort colorize("\n❌ #{title} interrupted", :error) } report_step(title, command) do started = Time.now.to_f [system(*command), Time.now.to_f - started] end abort if failing_fast? ensure Signal.trap("INT", previous_trap || "-") end