rundoc 6.0.0 → 7.0.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: 498d5109daa7abb733fc5c27cf583bb1c84cc7b92bbccaaf9e005a51a6ec0555
4
- data.tar.gz: fc72016241033a945486b5fa893b3f2cad0c8b3c7a2c44eab3f964adb7308f9e
3
+ metadata.gz: 30dcb03fc239fe1ddd02618341f780c8158ec145646049df7b597206dd7692db
4
+ data.tar.gz: c31c17bfa5997dc0c79b218e42493d719f6f19d26c1bc758f7d17f24bbe8d996
5
5
  SHA512:
6
- metadata.gz: 5af654672de3e6051ec46377052c55b55207ce13eb29cf977d1cf793291e953d28e842fd79b0f0972f8eb83a98c6671c1834a5ac75f20da795eb686a99a02c52
7
- data.tar.gz: b2acdf08759aa0c6f73410a00a4ad387da198f1f4497228c69d27dd799799e12107cc421c8f3fd5242f9517dbfd2565e71b56e2b3a6ffd77ffd879906c902d65
6
+ metadata.gz: d1c17df9586e3c8d767f8fa79c3d8afebddf5a84c09ae6cbdee14286bb184ee7d51c0ada0827a4dd8a0d480266b63bc1713872200d841b5ae0d5a0af482c2f20
7
+ data.tar.gz: a3e244880fe064cb86360953f8b3ca4e6b3569201f9bc0c96287686dd99a17a4c59cb3122160a4024b37808e98265758dd40290643cdbb84b0f5826fce5e234c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## HEAD
2
2
 
3
+ ## 7.0.0
4
+
5
+ - Changed: Shell commands now run under `bash -eo pipefail` instead of `/bin/sh`. This surfaces failures in pipelines and compound commands that were previously swallowed. See the README for details on SIGPIPE interactions.
6
+
3
7
  ## 6.0.0
4
8
 
5
9
  - Added: `rundoc.ensure_later(dir: :cwd)` command to register cleanup blocks that run on every build (success and failure). Useful for guaranteed resource teardown (e.g., destroying Heroku apps). Multiple blocks execute in order; failures in one block do not stop the rest.
data/README.md CHANGED
@@ -259,6 +259,8 @@ However this command would fall on its face:
259
259
 
260
260
  These custom commands are kept to a minimum, and for the most part behave as you would expect them to. Write your docs as you normally would and check the output frequently.
261
261
 
262
+ Shell commands run under `bash -eo pipefail` to catch silent failures in pipelines and compound commands. See [Bash Error Handling](#bash-error-handling) for details and SIGPIPE caveats.
263
+
262
264
  Running shell commands like this can be very powerful, you'll likely want more control of how you manipulate files in your project. To do this you can use the `file.` namespace:
263
265
 
264
266
  ## Dynamic command templating
@@ -719,6 +721,93 @@ The output of this will not be included in the document. Multiple ensure blocks
719
721
 
720
722
  If a build is successful, but an `ensure_later` block fails, the build will be considered a failure. A failure in one block will not stop the rest from executing.
721
723
 
724
+ ## Bash Error Handling
725
+
726
+ ### Bash `set -eo pipefail` explained
727
+
728
+ Bash error behavior is configurable. Rundoc uses `set -eo pipefail` to catch common problems not caught by the default error mode.
729
+
730
+ If a failing command is piped to a valid command, by default bash will "hide" the failure by returning a zero (success) exit code:
731
+
732
+ ```term
733
+ $ bash -c "cat does-not-exist | head -n1"
734
+ cat: does-not-exist: No such file or directory
735
+ $ echo $?
736
+ 0
737
+ ```
738
+
739
+ With `-o pipefail`, the pipeline reports the exit status of the last command that failed rather than the last command in the pipe. With `-e`, bash exits immediately when a command fails. Together, `-eo pipefail` ensures a failing command's exit status is not hidden. Here, the exit code `1` comes from `cat` failing to open a file that doesn't exist:
740
+
741
+ ```term
742
+ $ bash -eo pipefail -c "cat does-not-exist | head -n1"
743
+ cat: does-not-exist: No such file or directory
744
+ $ echo $?
745
+ 1
746
+ ```
747
+
748
+ A similar scenario is when multiple commands are given on one line separated by a semicolon. Without `-e`, bash continues past the failure:
749
+
750
+ ```term
751
+ $ bash -c "cat does-not-exist; echo 'done'"
752
+ cat: does-not-exist: No such file or directory
753
+ done
754
+ $ echo $?
755
+ 0
756
+ ```
757
+
758
+ With `-e`, bash exits immediately after the failing command:
759
+
760
+ ```term
761
+ $ bash -eo pipefail -c "cat does-not-exist; echo 'done'"
762
+ cat: does-not-exist: No such file or directory
763
+ $ echo $?
764
+ 1
765
+ ```
766
+
767
+ These settings help prevent silent failures from slipping through your Rundoc scripts.
768
+
769
+ ### Bash SIGPIPE
770
+
771
+ Tools like `head -n1` and `grep -m1 "value"` read only part of their input and then exit. When the upstream process next tries to write to the now-closed pipe, the kernel delivers a `SIGPIPE` signal to terminate it. This behavior is useful when the input is a never-ending stream but you only need a subset:
772
+
773
+ ```term
774
+ $ bash -c 'yes "output" | head -n1'
775
+ output
776
+ $ echo $?
777
+ 0
778
+ ```
779
+
780
+ However, this behavior negatively interacts with `set -eo pipefail`. Even though `head -n1` successfully produced its output and the command appears to run cleanly, `pipefail` reports the upstream process's SIGPIPE termination as a non-zero exit:
781
+
782
+ ```term
783
+ $ bash -eo pipefail -c 'yes "output" | head -n1'
784
+ output
785
+ $ echo $?
786
+ 141
787
+ ```
788
+
789
+ If the input is fully buffered (such as reading from a small file), `head` can finish before the writer needs to write again, so SIGPIPE is never triggered. That means `cat small-file.txt | head -n1` is reasonably safe, but this behavior can surface with larger files:
790
+
791
+ ```term
792
+ $ tmpfile=$(mktemp)
793
+ $ seq 1 200000 > "$tmpfile"
794
+ $ bash -eo pipefail -c "cat $tmpfile | head -n1"
795
+ 1
796
+ $ echo $?
797
+ 141
798
+ ```
799
+
800
+ This SIGPIPE behavior can cause your rundoc script to exit early when you aren't expecting it.
801
+
802
+ You can rewrite some commands to take a file directly. For example, `cat Gemfile | head -n 5` can be rewritten as `head -n 5 Gemfile` without SIGPIPE risk.
803
+
804
+ Here are common commands that can trigger a SIGPIPE:
805
+
806
+ - `head` with `-n <N>` or `-c <N>`
807
+ - `grep` with `-m <N>` (max count)
808
+ - `sed <N>q` (quit after N lines)
809
+ - `awk` with `{exit}` — e.g., rewrite `awk '/^Start/ {flag=1} /^End/ {exit} flag'` without exit as `awk '/^Start/ {flag=1} /^End/ {flag=0} flag'`
810
+
722
811
  ## Writing a new command
723
812
 
724
813
  > Note: This is an advanced topic and this interface is unstable.
@@ -45,13 +45,18 @@ class Rundoc::CodeCommand::BashRunner
45
45
  end
46
46
 
47
47
  def shell(cmd, stdin = nil)
48
- cmd = "(#{cmd}) 2>&1"
49
48
  msg = "Running: $ '#{cmd}'"
50
49
  msg << " with stdin: '#{stdin.inspect}'" if stdin && !stdin.empty?
51
50
  io.puts msg
52
51
 
53
52
  result = +""
54
- IO.popen(cmd, "w+") do |pipe|
53
+ # -e: Abort on first failure in compound commands (e.g. `bundle install; echo done`)
54
+ # so the exit status reflects the real failure, not a trailing success.
55
+ # -o pipefail: Report the first failure in a pipeline (e.g. `git push | tee log`)
56
+ # instead of only the last command's exit status.
57
+ # -u (nounset) is intentionally omitted: tutorial commands routinely
58
+ # reference environment variables that may not be set.
59
+ IO.popen(["bash", "-eo", "pipefail", "-c", cmd, err: [:child, :out]], "w+") do |pipe|
55
60
  pipe << stdin if stdin
56
61
  pipe.close_write
57
62
 
@@ -22,7 +22,7 @@ module ::Rundoc::CodeCommand
22
22
  end
23
23
 
24
24
  def to_s
25
- @dir
25
+ @dir.to_s
26
26
  end
27
27
  end
28
28
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rundoc
4
- VERSION = "6.0.0"
4
+ VERSION = "7.0.0"
5
5
  end
@@ -332,4 +332,34 @@ class IntegrationEnsureLaterTest < Minitest::Test
332
332
  end
333
333
  end
334
334
  end
335
+
336
+ def test_log_output_shows_dir_mode_name
337
+ Dir.mktmpdir do |dir|
338
+ Dir.chdir(dir) do
339
+ dir = Pathname(dir)
340
+
341
+ source_path = dir.join("RUNDOC.md")
342
+ source_path.write <<~EOF
343
+ ```
344
+ :::-- rundoc.ensure_later(dir: :cwd)
345
+ puts "hello"
346
+ ```
347
+
348
+ ```
349
+ :::>> $ echo "hello"
350
+ ```
351
+ EOF
352
+
353
+ io = StringIO.new
354
+ Rundoc::CLI.new(
355
+ io: io,
356
+ source_path: source_path,
357
+ on_success_dir: dir.join(SUCCESS_DIRNAME)
358
+ ).call
359
+
360
+ output = io.string
361
+ assert_includes output, "Registering ensure_later block (dir: cwd => /"
362
+ end
363
+ end
364
+ end
335
365
  end
@@ -25,6 +25,30 @@ class BashTest < Minitest::Test
25
25
  end
26
26
  end
27
27
 
28
+ def test_pipefail_catches_early_failure
29
+ command = "false | true"
30
+ bash = Rundoc::CodeCommand::BashRunner.new(
31
+ render_command: false,
32
+ render_result: false,
33
+ io: StringIO.new,
34
+ user_args: Rundoc::CodeCommand::BashArgs.new(command)
35
+ )
36
+ error = assert_raises(RuntimeError) { bash.call }
37
+ assert_match(/exited with non zero status/, error.message)
38
+ end
39
+
40
+ def test_errexit_catches_compound_command_failure
41
+ command = "false; echo done"
42
+ bash = Rundoc::CodeCommand::BashRunner.new(
43
+ render_command: false,
44
+ render_result: false,
45
+ io: StringIO.new,
46
+ user_args: Rundoc::CodeCommand::BashArgs.new(command)
47
+ )
48
+ error = assert_raises(RuntimeError) { bash.call }
49
+ assert_match(/exited with non zero status/, error.message)
50
+ end
51
+
28
52
  def test_stdin
29
53
  command = "tail -n 2"
30
54
  bash = Rundoc::CodeCommand::BashRunner.new(
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rundoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 7.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Schneeman
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: thor
@@ -304,6 +305,7 @@ homepage: https://github.com/schneems/rundoc
304
305
  licenses:
305
306
  - MIT
306
307
  metadata: {}
308
+ post_install_message:
307
309
  rdoc_options: []
308
310
  require_paths:
309
311
  - lib
@@ -318,7 +320,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
318
320
  - !ruby/object:Gem::Version
319
321
  version: '0'
320
322
  requirements: []
321
- rubygems_version: 4.0.10
323
+ rubygems_version: 3.4.19
324
+ signing_key:
322
325
  specification_version: 4
323
326
  summary: RunDOC generates runable code from docs
324
327
  test_files: []