backspin 0.11.0 → 0.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/.circleci/config.yml +3 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/.standard.yml +5 -0
- data/CHANGELOG.md +13 -4
- data/Gemfile.lock +27 -25
- data/README.md +80 -1
- data/Rakefile +27 -0
- data/backspin.gemspec +3 -1
- data/docs/compare-api-plan.md +193 -0
- data/fixtures/projects/dummy_cli_gem/.rspec +3 -0
- data/fixtures/projects/dummy_cli_gem/Gemfile +12 -0
- data/fixtures/projects/dummy_cli_gem/Gemfile.lock +55 -0
- data/fixtures/projects/dummy_cli_gem/LICENSE.txt +1 -0
- data/fixtures/projects/dummy_cli_gem/README.md +7 -0
- data/fixtures/projects/dummy_cli_gem/Rakefile +8 -0
- data/fixtures/projects/dummy_cli_gem/dummy_cli_gem.gemspec +22 -0
- data/fixtures/projects/dummy_cli_gem/exe/dummy_cli_gem +8 -0
- data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_echo.yml +18 -0
- data/fixtures/projects/dummy_cli_gem/fixtures/backspin/dummy_ls.yml +18 -0
- data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/cli.rb +35 -0
- data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem/version.rb +5 -0
- data/fixtures/projects/dummy_cli_gem/lib/dummy_cli_gem.rb +7 -0
- data/fixtures/projects/dummy_cli_gem/mise.toml +2 -0
- data/fixtures/projects/dummy_cli_gem/spec/dummy_cli_gem_backspin_spec.rb +46 -0
- data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_echo.yml +18 -0
- data/fixtures/projects/dummy_cli_gem/spec/fixtures/backspin/dummy_ls.yml +18 -0
- data/fixtures/projects/dummy_cli_gem/spec/fixtures/listing_target/alpha.txt +1 -0
- data/fixtures/projects/dummy_cli_gem/spec/spec_helper.rb +22 -0
- data/lib/backspin/configuration.rb +13 -0
- data/lib/backspin/record.rb +1 -1
- data/lib/backspin/recorder.rb +1 -1
- data/lib/backspin/version.rb +1 -1
- data/lib/backspin.rb +91 -25
- data/mise.toml +2 -0
- metadata +39 -5
- data/script/run_affected_tests +0 -179
data/script/run_affected_tests
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "json"
|
|
5
|
-
require "pathname"
|
|
6
|
-
require "open3"
|
|
7
|
-
|
|
8
|
-
class AffectedTestRunner
|
|
9
|
-
EXIT_SUCCESS = 0
|
|
10
|
-
EXIT_FAILURE = 2
|
|
11
|
-
|
|
12
|
-
def initialize
|
|
13
|
-
@project_dir = ENV["CLAUDE_PROJECT_DIR"]
|
|
14
|
-
validate_environment!
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def run
|
|
18
|
-
input_data = parse_input
|
|
19
|
-
file_path = extract_file_path(input_data)
|
|
20
|
-
|
|
21
|
-
return EXIT_SUCCESS unless ruby_file?(file_path)
|
|
22
|
-
|
|
23
|
-
validated_path = validate_and_normalize_path(file_path)
|
|
24
|
-
return EXIT_SUCCESS unless validated_path
|
|
25
|
-
|
|
26
|
-
test_file = determine_test_file(validated_path)
|
|
27
|
-
return EXIT_SUCCESS unless test_file
|
|
28
|
-
|
|
29
|
-
run_tests(test_file)
|
|
30
|
-
rescue => e
|
|
31
|
-
abort_to_claude("Unexpected error: #{e.message}")
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def validate_environment!
|
|
37
|
-
unless @project_dir
|
|
38
|
-
abort_to_claude("CLAUDE_PROJECT_DIR environment variable not set")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
unless Dir.exist?(@project_dir)
|
|
42
|
-
abort_to_claude("CLAUDE_PROJECT_DIR does not exist: #{@project_dir}")
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
@project_path = Pathname.new(@project_dir).realpath
|
|
46
|
-
rescue => e
|
|
47
|
-
abort_to_claude("Invalid CLAUDE_PROJECT_DIR: #{e.message}")
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def parse_input
|
|
51
|
-
input = $stdin.read
|
|
52
|
-
JSON.parse(input)
|
|
53
|
-
rescue JSON::ParserError => e
|
|
54
|
-
abort_to_claude("Invalid JSON input: #{e.message}")
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def extract_file_path(data)
|
|
58
|
-
file_path = data.dig("tool_input", "file_path")
|
|
59
|
-
|
|
60
|
-
unless file_path && !file_path.empty?
|
|
61
|
-
abort_to_claude("No file_path provided in input")
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
file_path
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def ruby_file?(file_path)
|
|
68
|
-
file_path.end_with?(".rb")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def validate_and_normalize_path(file_path)
|
|
72
|
-
# Expand the path to get absolute path
|
|
73
|
-
expanded_path = File.expand_path(file_path, @project_dir)
|
|
74
|
-
normalized_path = Pathname.new(expanded_path).cleanpath
|
|
75
|
-
|
|
76
|
-
# Check if the path is within the project directory
|
|
77
|
-
unless normalized_path.to_s.start_with?(@project_path.to_s)
|
|
78
|
-
log_info("File path outside project directory: #{file_path}")
|
|
79
|
-
return nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Convert back to relative path from project root for consistency
|
|
83
|
-
normalized_path.relative_path_from(@project_path).to_s
|
|
84
|
-
rescue => e
|
|
85
|
-
log_info("Invalid file path: #{file_path} - #{e.message}")
|
|
86
|
-
nil
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def determine_test_file(file_path)
|
|
90
|
-
log_info("Determining tests for: #{file_path}")
|
|
91
|
-
|
|
92
|
-
if spec_file?(file_path)
|
|
93
|
-
# If a spec file was modified, run just that spec
|
|
94
|
-
test_file = file_path
|
|
95
|
-
log_info("Running single spec: #{test_file}")
|
|
96
|
-
elsif lib_file?(file_path)
|
|
97
|
-
# If a lib file was modified, try to find corresponding spec
|
|
98
|
-
test_file = lib_to_spec_path(file_path)
|
|
99
|
-
|
|
100
|
-
if File.exist?(File.join(@project_dir, test_file))
|
|
101
|
-
log_info("Running corresponding spec: #{test_file}")
|
|
102
|
-
else
|
|
103
|
-
# No corresponding spec found, run all tests
|
|
104
|
-
log_info("No corresponding spec found, running all tests")
|
|
105
|
-
test_file = "spec"
|
|
106
|
-
end
|
|
107
|
-
else
|
|
108
|
-
# For other Ruby files, run all tests
|
|
109
|
-
log_info("Running all tests")
|
|
110
|
-
test_file = "spec"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Validate test file/directory exists
|
|
114
|
-
full_test_path = File.join(@project_dir, test_file)
|
|
115
|
-
unless File.exist?(full_test_path)
|
|
116
|
-
abort_to_claude("Test file/directory does not exist: #{test_file}")
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
test_file
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def spec_file?(file_path)
|
|
123
|
-
file_path.match?(%r{^.*spec/.*_spec\.rb$})
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def lib_file?(file_path)
|
|
127
|
-
file_path.match?(%r{^.*lib/.*})
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def lib_to_spec_path(lib_path)
|
|
131
|
-
# Convert lib/backspin/foo.rb to spec/backspin/foo_spec.rb
|
|
132
|
-
lib_path.sub(%r{^(.*/)?lib/}, '\1spec/')
|
|
133
|
-
.sub(/\.rb$/, "_spec.rb")
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def run_tests(test_file)
|
|
137
|
-
rspec_path = File.join(@project_dir, "bin", "rspec")
|
|
138
|
-
|
|
139
|
-
unless File.executable?(rspec_path)
|
|
140
|
-
abort_to_claude("rspec binary not found or not executable: #{rspec_path}")
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
log_info("Running: bin/rspec #{test_file}")
|
|
144
|
-
|
|
145
|
-
# Change to project directory for command execution
|
|
146
|
-
Dir.chdir(@project_dir) do
|
|
147
|
-
stdout, stderr, status = Open3.capture3("bin/rspec", test_file)
|
|
148
|
-
|
|
149
|
-
# Output both stdout and stderr
|
|
150
|
-
$stdout.print stdout
|
|
151
|
-
$stderr.print stderr
|
|
152
|
-
|
|
153
|
-
if status.success?
|
|
154
|
-
log_info("✅ Tests passed")
|
|
155
|
-
EXIT_SUCCESS
|
|
156
|
-
else
|
|
157
|
-
warn("❌ Tests failed - fix before continuing")
|
|
158
|
-
EXIT_FAILURE
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
rescue => e
|
|
162
|
-
abort_to_claude("Failed to run tests: #{e.message}")
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def log_info(message)
|
|
166
|
-
warn(message)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def abort_to_claude(message)
|
|
170
|
-
warn("❌ ERROR: #{message}")
|
|
171
|
-
exit EXIT_FAILURE
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Run the script
|
|
176
|
-
if __FILE__ == $0
|
|
177
|
-
runner = AffectedTestRunner.new
|
|
178
|
-
exit runner.run
|
|
179
|
-
end
|