Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/release-rubygem.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ jobs:
with:
ruby-version: 4.0.6
working-directory: ruby
- name: Synchronize CiEnvironments.json
working-directory: ruby
run: bundle exec rake ci_environments
- run: ./scripts/fail-if-dirty
- uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
- uses: cucumber/action-publish-rubygem@4e79bb9aed597c835e8438f57c04d0996ab80d72 # v2.0.0
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-ruby.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest]
ruby: ['3.2', '3.3', '3.4', '3.5']
ruby: ['3.2', '3.3', '3.4', '4.0']
include:
- os: windows-latest
ruby: '3.4'
ruby: '4.0'
- os: macos-latest
ruby: '3.4'
ruby: '4.0'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Fixed
- [Ruby] Ensure `CiEnvironments.json` is published (in new dir), in gem package
- BREAKING CHANGE: [Ruby] change `VariableExpression` module namespace to be top level alongside `CiEnvironment` module

## [14.0.0] - 2026-06-11
### Changed
Expand Down
3 changes: 1 addition & 2 deletions ruby/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ coverage/
pkg/
*.gem
.compared
.deps
.tested*
lib/cucumber/CiEnvironments.json
*-go
*.iml
13 changes: 4 additions & 9 deletions ruby/.rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2025-10-07 17:03:37 UTC using RuboCop version 1.81.1.
# on 2026-07-22 07:32:12 UTC using RuboCop version 1.81.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand All @@ -9,19 +9,14 @@
# TODO: Oct '23 -> 10 files inspected, 63 offenses detected, 53 offenses autocorrectable
# TODO: Nov '24 -> 9 files inspected, 4 offenses detected, 0 offenses autocorrectable
# TODO: Oct '25 -> 9 files inspected, 5 offenses detected, 1 offenses autocorrectable
# TODO: Jul '26 -> 7 files inspected, 2 offenses detected

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
Layout/EmptyLinesAfterModuleInclusion:
Exclude:
- 'lib/cucumber/ci_environment.rb'

# Offense count: 2
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 22

# Offense count: 2
# Offense count: 1
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Metrics/MethodLength:
Max: 23
Max: 21
2 changes: 1 addition & 1 deletion ruby/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))

file :ci_environments do
FileUtils.cp('../CiEnvironments.json', 'lib/cucumber/ci_environment/CiEnvironments.json')
FileUtils.cp('../CiEnvironments.json', 'lib/cucumber/CiEnvironments.json')
end

desc 'Copy the sample environments & run all the unit tests for all environment types'
Expand Down
2 changes: 1 addition & 1 deletion ruby/cucumber-ci-environment.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ version = File.read(File.expand_path('VERSION', __dir__)).strip
Gem::Specification.new do |s|
s.name = 'cucumber-ci-environment'
s.version = version
s.authors = ['Vincent Prêtre']
s.authors = ['Vincent Prêtre', 'Luke Hill']
s.description = 'Detect CI Environment from environment variables'
s.summary = "#{s.name}-#{s.version}"
s.email = 'cukes@googlegroups.com'
Expand Down
109 changes: 54 additions & 55 deletions ruby/lib/cucumber/ci_environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,78 @@

require 'uri'
require 'json'
require 'cucumber/ci_environment/variable_expression'
require 'cucumber/variable_expression'

module Cucumber
module CiEnvironment
class CiEnvironment
extend VariableExpression
CI_ENVIRONMENTS_PATH = File.join(File.dirname(__FILE__), 'ci_environment/CiEnvironments.json')

module_function
CI_ENVIRONMENTS_PATH = File.join(File.dirname(__FILE__), './CiEnvironments.json')

def detect_ci_environment(env)
ci_environments = JSON.parse(File.read(CI_ENVIRONMENTS_PATH))
ci_environments.each do |ci_environment|
detected = detect(ci_environment, env)
return detected unless detected.nil?
end

nil
end
class << self
def detect_ci_environment(env)
ci_environments = JSON.parse(File.read(CI_ENVIRONMENTS_PATH))
ci_environments.each do |ci_environment|
detected = detect(ci_environment, env)
return detected unless detected.nil?
end

def detect(ci_environment, env)
url = evaluate(ci_environment['url'], env)
return nil if url.nil?
nil
end

result = {
name: ci_environment['name'],
url: url,
buildNumber: evaluate(ci_environment['buildNumber'], env)
}
def remove_userinfo_from_url(value)
return nil if value.nil?

detected_git = detect_git(ci_environment, env)
result[:git] = detected_git if detected_git
result
end
begin
uri = URI(value)
uri.userinfo = ''
uri.to_s
rescue StandardError
value
end
end

def detect_git(ci_environment, env)
revision = detect_revision(ci_environment, env)
return nil if revision.nil?
private

remote = evaluate(ci_environment['git']['remote'], env)
return nil if remote.nil?
def detect(ci_environment, env)
url = evaluate(ci_environment['url'], env)
return nil if url.nil?

git_info = {
remote: remove_userinfo_from_url(remote),
revision: revision
}
result = {
name: ci_environment['name'],
url: url,
buildNumber: evaluate(ci_environment['buildNumber'], env)
}

tag = evaluate(ci_environment['git']['tag'], env)
branch = evaluate(ci_environment['git']['branch'], env)
git_info[:tag] = tag if tag
git_info[:branch] = branch if branch
git_info
end
detected_git = detect_git(ci_environment, env)
result[:git] = detected_git if detected_git
result
end

def detect_revision(ci_environment, env)
return evaluate(ci_environment['git']['revision'], env) unless env['GITHUB_EVENT_NAME'] == 'pull_request'
def detect_git(ci_environment, env)
revision = detect_revision(ci_environment, env)
return nil if revision.nil?

raise StandardError('GITHUB_EVENT_PATH not set') unless env['GITHUB_EVENT_PATH']
remote = evaluate(ci_environment['git']['remote'], env)
return nil if remote.nil?

event = JSON.parse(File.read(env['GITHUB_EVENT_PATH']))
event.dig('pull_request', 'head', 'sha').tap do |revision|
raise StandardError("Could not find .pull_request.head.sha in GITHUB_EVENT_PATH:\n#{JSON.pretty_generate(event)}") if revision.nil?
{
remote: remove_userinfo_from_url(remote),
revision: revision,
tag: evaluate(ci_environment['git']['tag'], env),
branch: evaluate(ci_environment['git']['branch'], env)
}.compact
end
end

def remove_userinfo_from_url(value)
return nil if value.nil?
def detect_revision(ci_environment, env)
return evaluate(ci_environment['git']['revision'], env) unless env['GITHUB_EVENT_NAME'] == 'pull_request'

raise StandardError('GITHUB_EVENT_PATH not set') unless env['GITHUB_EVENT_PATH']

begin
uri = URI(value)
uri.userinfo = ''
uri.to_s
rescue StandardError
value
event = JSON.parse(File.read(env['GITHUB_EVENT_PATH']))
event.dig('pull_request', 'head', 'sha').tap do |revision|
raise StandardError("Could not find .pull_request.head.sha in GITHUB_EVENT_PATH:\n#{JSON.pretty_generate(event)}") if revision.nil?
end
end
end
end
Expand Down
1 change: 0 additions & 1 deletion ruby/lib/cucumber/ci_environment/.gitignore

This file was deleted.

49 changes: 0 additions & 49 deletions ruby/lib/cucumber/ci_environment/variable_expression.rb

This file was deleted.

45 changes: 45 additions & 0 deletions ruby/lib/cucumber/variable_expression.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Cucumber
module VariableExpression
def evaluate(expression, env)
return nil if expression.nil?

expression.gsub(/\${(.*?)(?:(?<!\\)\/(.*)\/(.*))?}/) do
variable = Regexp.last_match(1)
pattern = Regexp.last_match(2)
replacement = Regexp.last_match(3)

value = get_value(variable, env)
raise "Undefined variable #{variable}" if value.nil?

if pattern.nil?
value
else
regexp = Regexp.new(pattern.gsub('\/', '/'))
match = value.match(regexp)
raise "No match for variable #{variable}" if match.nil?

match[1..].each_with_index do |group, i|
replacement = replacement.gsub("\\#{i + 1}", group)
end
replacement
end
end
rescue StandardError
nil
end

def get_value(variable, env)
if variable.index('*')
regexp = Regexp.new(variable.gsub('*', '.*'))
# GoCD env var with dynamic "material" name
# https://github.com/ashwanthkumar/gocd-build-github-pull-requests#github
env.each do |name, value|
return value if regexp.match?(name)
end
end
env[variable]
end
end
end
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

require 'cucumber/ci_environment'
require 'cucumber/variable_expression'

describe Cucumber::CiEnvironment::VariableExpression do
describe Cucumber::VariableExpression do
describe '.evaluate' do
subject(:ci_environment) { Cucumber::CiEnvironment }

Expand Down
Loading