|
3 | 3 | require "open3" |
4 | 4 | require "json" |
5 | 5 |
|
6 | | -module Importmap |
7 | | - module Update |
8 | | - # Abstracts execution of external commands (bin/importmap) so the rest |
9 | | - # of the codebase doesn't shell out directly. This is the seam tests hook |
10 | | - # into — production code runs commands for real, tests inject a |
11 | | - # FixtureRunner that replays pre-recorded (argv → stdout, exit) tuples. |
12 | | - # |
13 | | - # The interface deliberately mirrors what Open3.capture3 returns: |
14 | | - # |
15 | | - # runner.run("bin/importmap", "outdated") |
16 | | - # # => Result(stdout: "...", stderr: "...", success: true, exit: 0) |
17 | | - # |
18 | | - # Commands are passed as an argv array, not a shell string. That's both |
19 | | - # safer (no shell-injection surprises from package names) and easier to |
20 | | - # match against fixture keys. |
21 | | - module Commands |
22 | | - Result = Data.define(:stdout, :stderr, :exit_code) do |
23 | | - def success? |
24 | | - exit_code == 0 |
25 | | - end |
| 6 | +module ImportmapUpdate |
| 7 | + # Abstracts execution of external commands (bin/importmap) so the rest |
| 8 | + # of the codebase doesn't shell out directly. This is the seam tests hook |
| 9 | + # into — production code runs commands for real, tests inject a |
| 10 | + # FixtureRunner that replays pre-recorded (argv → stdout, exit) tuples. |
| 11 | + # |
| 12 | + # The interface deliberately mirrors what Open3.capture3 returns: |
| 13 | + # |
| 14 | + # runner.run("bin/importmap", "outdated") |
| 15 | + # # => Result(stdout: "...", stderr: "...", success: true, exit: 0) |
| 16 | + # |
| 17 | + # Commands are passed as an argv array, not a shell string. That's both |
| 18 | + # safer (no shell-injection surprises from package names) and easier to |
| 19 | + # match against fixture keys. |
| 20 | + module Commands |
| 21 | + Result = Data.define(:stdout, :stderr, :exit_code) do |
| 22 | + def success? |
| 23 | + exit_code == 0 |
26 | 24 | end |
| 25 | + end |
27 | 26 |
|
28 | | - class CommandError < StandardError |
29 | | - attr_reader :argv, :result |
| 27 | + class CommandError < StandardError |
| 28 | + attr_reader :argv, :result |
30 | 29 |
|
31 | | - def initialize(argv, result) |
32 | | - @argv = argv |
33 | | - @result = result |
34 | | - super("`#{argv.join(" ")}` exited #{result.exit_code}: #{result.stderr.strip}") |
35 | | - end |
| 30 | + def initialize(argv, result) |
| 31 | + @argv = argv |
| 32 | + @result = result |
| 33 | + super("`#{argv.join(" ")}` exited #{result.exit_code}: #{result.stderr.strip}") |
36 | 34 | end |
| 35 | + end |
37 | 36 |
|
38 | | - # Production runner: actually executes the command. |
39 | | - class ShellRunner |
40 | | - # @param cwd [String, nil] working directory (defaults to current) |
41 | | - def initialize(cwd: nil) |
42 | | - @cwd = cwd |
43 | | - end |
| 37 | + # Production runner: actually executes the command. |
| 38 | + class ShellRunner |
| 39 | + # @param cwd [String, nil] working directory (defaults to current) |
| 40 | + def initialize(cwd: nil) |
| 41 | + @cwd = cwd |
| 42 | + end |
44 | 43 |
|
45 | | - def run(*argv) |
46 | | - opts = {} |
47 | | - opts[:chdir] = @cwd if @cwd |
48 | | - Bundler.with_unbundled_env do |
49 | | - stdout, stderr, status = Open3.capture3(*argv, opts) |
50 | | - Result.new(stdout:, stderr:, exit_code: status.exitstatus) |
51 | | - end |
| 44 | + def run(*argv) |
| 45 | + opts = {} |
| 46 | + opts[:chdir] = @cwd if @cwd |
| 47 | + Bundler.with_unbundled_env do |
| 48 | + stdout, stderr, status = Open3.capture3(*argv, opts) |
| 49 | + Result.new(stdout:, stderr:, exit_code: status.exitstatus) |
52 | 50 | end |
| 51 | + end |
53 | 52 |
|
54 | | - # Raises on non-zero exit. Use when you have no recovery strategy |
55 | | - # and just want to surface the error to the caller. |
56 | | - def run!(*argv) |
57 | | - result = run(*argv) |
58 | | - raise CommandError.new(argv, result) unless result.success? |
59 | | - result |
60 | | - end |
| 53 | + # Raises on non-zero exit. Use when you have no recovery strategy |
| 54 | + # and just want to surface the error to the caller. |
| 55 | + def run!(*argv) |
| 56 | + result = run(*argv) |
| 57 | + raise CommandError.new(argv, result) unless result.success? |
| 58 | + result |
61 | 59 | end |
62 | 60 | end |
63 | 61 | end |
|
0 commit comments