From 85f7dd799802355f88a767c001265c7f25d475cf Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 10:52:58 -0700 Subject: [PATCH 1/9] fix(homebrew): render bundled path helpers as Ruby methods --- .../scripts/tests/test_release_artifacts.py | 62 +++++++++++++++++-- .../scripts/tests/test_release_artifacts.py | 62 +++++++++++++++++-- .../sc-publish/release/homebrew/formula.rb.j2 | 2 +- release/homebrew/formula.rb.j2 | 2 +- 4 files changed, 114 insertions(+), 14 deletions(-) diff --git a/.github/scripts/tests/test_release_artifacts.py b/.github/scripts/tests/test_release_artifacts.py index e88cc138..7c060f30 100644 --- a/.github/scripts/tests/test_release_artifacts.py +++ b/.github/scripts/tests/test_release_artifacts.py @@ -2580,6 +2580,60 @@ def render_release_template( return sc_compose.compose_file(request).rendered_text +def assert_homebrew_formula_install_executes(formula: str) -> None: + """Parse and execute a rendered formula against the Homebrew path helper shape.""" + ruby = subprocess.run( + ["ruby", "-c"], input=formula, text=True, capture_output=True, check=False + ) + assert ruby.returncode == 0, ruby.stderr + + harness = """ +class InstallPath + def /(component) + self + end + + def install(paths) + end +end + +class Formula + def self.test(&block) + end + + def self.method_missing(name, *args, &block) + class_eval(&block) if block + end + + def bin + InstallPath.new + end + + def pkgshare + InstallPath.new + end + + def shell_output(*args) + "" + end + + def assert_match(*args) + end +end + +eval(STDIN.read, TOPLEVEL_BINDING) +ScCompose.new.install +""" + execution = subprocess.run( + ["ruby", "-e", harness], + input=formula, + text=True, + capture_output=True, + check=False, + ) + assert execution.returncode == 0, execution.stderr + + def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) -> None: formula = render_release_template( tmp_path, @@ -2602,20 +2656,16 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) "binary_paths": ["bin/sc-compose", "bin/sc-compose-daemon"], "bundled_paths": [ { - "destination_components": ["pkgshare", "examples"], + "destination_components": ["pkgshare"], "source_glob": "share/sc-compose/examples/*", } ], }, ) - ruby = subprocess.run( - ["ruby", "-c"], input=formula, text=True, capture_output=True, check=False - ) - assert ruby.returncode == 0, ruby.stderr + assert_homebrew_formula_install_executes(formula) assert 'bin.install "bin/sc-compose"' in formula assert 'bin.install "bin/sc-compose-daemon"' in formula assert 'shell_output("#{bin}/" + "sc-compose-daemon"' in formula - assert '("pkgshare"/"examples").install Dir["share/sc-compose/examples/*"]' in formula scoop = render_release_template( tmp_path, diff --git a/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py b/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py index 5b87fd3e..24d108b9 100644 --- a/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py +++ b/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py @@ -2459,6 +2459,60 @@ def render_release_template( return sc_compose.compose_file(request).rendered_text +def assert_homebrew_formula_install_executes(formula: str) -> None: + """Parse and execute a rendered formula against the Homebrew path helper shape.""" + ruby = subprocess.run( + ["ruby", "-c"], input=formula, text=True, capture_output=True, check=False + ) + assert ruby.returncode == 0, ruby.stderr + + harness = """ +class InstallPath + def /(component) + self + end + + def install(paths) + end +end + +class Formula + def self.test(&block) + end + + def self.method_missing(name, *args, &block) + class_eval(&block) if block + end + + def bin + InstallPath.new + end + + def pkgshare + InstallPath.new + end + + def shell_output(*args) + "" + end + + def assert_match(*args) + end +end + +eval(STDIN.read, TOPLEVEL_BINDING) +ScCompose.new.install +""" + execution = subprocess.run( + ["ruby", "-e", harness], + input=formula, + text=True, + capture_output=True, + check=False, + ) + assert execution.returncode == 0, execution.stderr + + def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) -> None: formula = render_release_template( tmp_path, @@ -2481,20 +2535,16 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) "binary_paths": ["bin/sc-compose", "bin/sc-compose-daemon"], "bundled_paths": [ { - "destination_components": ["pkgshare", "examples"], + "destination_components": ["pkgshare"], "source_glob": "share/sc-compose/examples/*", } ], }, ) - ruby = subprocess.run( - ["ruby", "-c"], input=formula, text=True, capture_output=True, check=False - ) - assert ruby.returncode == 0, ruby.stderr + assert_homebrew_formula_install_executes(formula) assert 'bin.install "bin/sc-compose"' in formula assert 'bin.install "bin/sc-compose-daemon"' in formula assert 'shell_output("#{bin}/" + "sc-compose-daemon"' in formula - assert '("pkgshare"/"examples").install Dir["share/sc-compose/examples/*"]' in formula scoop = render_release_template( tmp_path, diff --git a/plugins/sc-publish/release/homebrew/formula.rb.j2 b/plugins/sc-publish/release/homebrew/formula.rb.j2 index ebbd17e0..e731fea4 100644 --- a/plugins/sc-publish/release/homebrew/formula.rb.j2 +++ b/plugins/sc-publish/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end diff --git a/release/homebrew/formula.rb.j2 b/release/homebrew/formula.rb.j2 index ebbd17e0..e731fea4 100644 --- a/release/homebrew/formula.rb.j2 +++ b/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end From d4ada7a032e2fe5a7aa437e963cb544b07d417a5 Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 10:54:53 -0700 Subject: [PATCH 2/9] test(homebrew): expect bare bundled path helper --- crates/sc-compose/tests/cli/render.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/sc-compose/tests/cli/render.rs b/crates/sc-compose/tests/cli/render.rs index 636150da..a66bf23b 100644 --- a/crates/sc-compose/tests/cli/render.rs +++ b/crates/sc-compose/tests/cli/render.rs @@ -130,7 +130,7 @@ fn render_homebrew_formula_escapes_manifest_values_as_ruby_strings() { "linux_sha256": "linux-hash", "binary_paths": ["bin/sc-compose"], "bundled_paths": [{ - "destination_components": ["share", "quoted\"component", "examples"], + "destination_components": ["pkgshare", "quoted\"component", "examples"], "source_glob": "share/quoted\"component/examples/*" }], "binary": "sc-compose", @@ -163,7 +163,7 @@ fn render_homebrew_formula_escapes_manifest_values_as_ruby_strings() { assert!(formula.contains("desc \"A \\\"quoted\\\" description\"")); assert!(formula.contains("assert_match \"A \\\"quoted\\\" result\"")); assert!(formula.contains("bin.install \"bin/sc-compose\"")); - assert!(formula.contains("(\"share\"/\"quoted\\\"component\"/\"examples\").install Dir[\"share/quoted\\\"component/examples/*\"]")); + assert!(formula.contains("(pkgshare/\"quoted\\\"component\"/\"examples\").install Dir[\"share/quoted\\\"component/examples/*\"]")); assert!(!formula.contains("{{")); } From 7011cf56790a8897f84d3c24c58355f6f9c846da Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 11:11:03 -0700 Subject: [PATCH 3/9] docs(homebrew): close formula bundled path fix --- ...x-homebrew-formula-bundled-paths-tojson.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md new file mode 100644 index 00000000..f0e8d37e --- /dev/null +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -0,0 +1,40 @@ +--- +status: complete +branch: fix/homebrew-formula-bundled-paths-tojson +pr: 603 +--- + +# Fix: render Homebrew bundled-path helpers as Ruby methods + +## Scope + +Render the first `bundled_paths.destination_components` entry as a Homebrew +path helper and quote only later path segments. This produces, for example, +`(pkgshare/"examples").install ...`, not `("pkgshare"/"examples").install ...`. + +## Closeout + +- **Load-bearing template:** `release/homebrew/formula.rb.j2`. The release + manifest selects that exact path at + `release/publish-artifacts.toml` → `channels.homebrew.formulas[0].template`; + `.github/workflows/homebrew-publish.yml` reads the manifest and passes the + selected template to the published renderer. +- **Vendored copy:** `plugins/sc-publish/release/homebrew/formula.rb.j2` is a + consumer-local mirror. It received the same stopgap correction in this PR. + The canonical package correction is in flight on + `randlee/sc-publish:fix/homebrew-formula-bundled-paths-tojson`; a subsequent + package sync should take that canonical copy rather than independently + re-deciding this behavior. +- **Prior test gap:** the earlier regression only asserted that the rendered + text contained `("pkgshare"/"examples").install ...` and ran `ruby -c`. + Quoted and bare helper forms are both valid Ruby syntax, so neither check + exercised Homebrew's runtime path-helper API. The regression now executes + the rendered `install` block against a minimal Homebrew-compatible helper + harness; the old quoted receiver fails because a Ruby `String` has no + `install` method. +- **Validation evidence for `d4ada7a`:** + [`PR #603 checks`](https://github.com/randlee/sc-compose/pull/603/checks) + include the CI record for the code commit. The required commands also passed + locally at that commit: `cargo test --workspace` and + `python3 -m pytest -q .github/scripts/tests/test_release_artifacts.py`. + From c37b3ace782bd710ebfb9be223d9cad95f6695a0 Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 11:30:14 -0700 Subject: [PATCH 4/9] fix(homebrew): guard vendored template sync --- .../scripts/tests/test_release_artifacts.py | 3 ++- .github/workflows/ci.yml | 11 ++++++++++ crates/sc-compose/tests/cli/render.rs | 3 +++ ...x-homebrew-formula-bundled-paths-tojson.md | 21 +++++++++++-------- .../sc-publish/release/homebrew/formula.rb.j2 | 2 +- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/scripts/tests/test_release_artifacts.py b/.github/scripts/tests/test_release_artifacts.py index 7c060f30..15740a7c 100644 --- a/.github/scripts/tests/test_release_artifacts.py +++ b/.github/scripts/tests/test_release_artifacts.py @@ -2656,7 +2656,7 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) "binary_paths": ["bin/sc-compose", "bin/sc-compose-daemon"], "bundled_paths": [ { - "destination_components": ["pkgshare"], + "destination_components": ["pkgshare", "examples", "nested"], "source_glob": "share/sc-compose/examples/*", } ], @@ -2666,6 +2666,7 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) assert 'bin.install "bin/sc-compose"' in formula assert 'bin.install "bin/sc-compose-daemon"' in formula assert 'shell_output("#{bin}/" + "sc-compose-daemon"' in formula + assert '(pkgshare/"examples"/"nested").install' in formula scoop = render_release_template( tmp_path, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2faa145b..e3924ab2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,17 @@ jobs: python3 -m pytest .github/scripts/tests python3 -m pytest plugins/sc-publish/.github/scripts/tests + - name: Verify vendored Homebrew template tracks sc-publish + shell: bash + run: | + set -euo pipefail + # This intentionally compares only the template whose upstream fix is + # pending. Once sc-publish merges it, this consumer must re-vendor it. + git fetch --depth=1 https://github.com/randlee/sc-publish.git \ + refs/heads/develop:refs/remotes/sc-publish-upstream/develop + git diff --exit-code sc-publish-upstream/develop -- \ + plugins/sc-publish/release/homebrew/formula.rb.j2 + - name: Validate publish-order dependency graph run: | python3 .github/scripts/release_artifacts.py validate-publish-order \ diff --git a/crates/sc-compose/tests/cli/render.rs b/crates/sc-compose/tests/cli/render.rs index a66bf23b..7a3106ee 100644 --- a/crates/sc-compose/tests/cli/render.rs +++ b/crates/sc-compose/tests/cli/render.rs @@ -163,6 +163,9 @@ fn render_homebrew_formula_escapes_manifest_values_as_ruby_strings() { assert!(formula.contains("desc \"A \\\"quoted\\\" description\"")); assert!(formula.contains("assert_match \"A \\\"quoted\\\" result\"")); assert!(formula.contains("bin.install \"bin/sc-compose\"")); + // Ruby execution belongs to the top-level Python regression because this + // cross-platform Rust CLI suite also runs on Windows, where Ruby is absent. + // That regression executes the same multi-component expression in CI. assert!(formula.contains("(pkgshare/\"quoted\\\"component\"/\"examples\").install Dir[\"share/quoted\\\"component/examples/*\"]")); assert!(!formula.contains("{{")); } diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md index f0e8d37e..0fa6c041 100644 --- a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -20,21 +20,24 @@ path helper and quote only later path segments. This produces, for example, `.github/workflows/homebrew-publish.yml` reads the manifest and passes the selected template to the published renderer. - **Vendored copy:** `plugins/sc-publish/release/homebrew/formula.rb.j2` is a - consumer-local mirror. It received the same stopgap correction in this PR. - The canonical package correction is in flight on - `randlee/sc-publish:fix/homebrew-formula-bundled-paths-tojson`; a subsequent - package sync should take that canonical copy rather than independently - re-deciding this behavior. + consumer-local mirror and must not be hand-edited. It is restored byte-for-byte + to its pre-`85f7dd7` upstream state. The canonical package correction is + `randlee/sc-publish:fix/homebrew-formula-bundled-paths-tojson` at `8f858ed`. + CI compares this specific vendored template with upstream `develop`: it passes + while that upstream change is pending and fails as soon as it merges until this + consumer synchronizes the kit. - **Prior test gap:** the earlier regression only asserted that the rendered text contained `("pkgshare"/"examples").install ...` and ran `ruby -c`. Quoted and bare helper forms are both valid Ruby syntax, so neither check exercised Homebrew's runtime path-helper API. The regression now executes - the rendered `install` block against a minimal Homebrew-compatible helper - harness; the old quoted receiver fails because a Ruby `String` has no - `install` method. + a multi-component rendered `install` block against a minimal + Homebrew-compatible helper harness; the old quoted receiver fails because a + Ruby `String` has no `install` method. The Rust CLI test keeps its + cross-platform rendering assertion only: Ruby execution is intentionally + covered by this Ubuntu Python regression because the Rust suite also runs on + Windows, where Ruby is not available. - **Validation evidence for `d4ada7a`:** [`PR #603 checks`](https://github.com/randlee/sc-compose/pull/603/checks) include the CI record for the code commit. The required commands also passed locally at that commit: `cargo test --workspace` and `python3 -m pytest -q .github/scripts/tests/test_release_artifacts.py`. - diff --git a/plugins/sc-publish/release/homebrew/formula.rb.j2 b/plugins/sc-publish/release/homebrew/formula.rb.j2 index e731fea4..ebbd17e0 100644 --- a/plugins/sc-publish/release/homebrew/formula.rb.j2 +++ b/plugins/sc-publish/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end From 2450d8c035cced355116562f1381dd7590fc917f Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 11:36:05 -0700 Subject: [PATCH 5/9] fix(homebrew): keep vendored formula template synchronized --- .github/workflows/ci.yml | 9 +++------ plugins/sc-publish/release/homebrew/formula.rb.j2 | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3924ab2..269301d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,15 +100,12 @@ jobs: python3 -m pytest .github/scripts/tests python3 -m pytest plugins/sc-publish/.github/scripts/tests - - name: Verify vendored Homebrew template tracks sc-publish + - name: Verify vendored Homebrew template matches this release pipeline shell: bash run: | set -euo pipefail - # This intentionally compares only the template whose upstream fix is - # pending. Once sc-publish merges it, this consumer must re-vendor it. - git fetch --depth=1 https://github.com/randlee/sc-publish.git \ - refs/heads/develop:refs/remotes/sc-publish-upstream/develop - git diff --exit-code sc-publish-upstream/develop -- \ + cmp --silent \ + release/homebrew/formula.rb.j2 \ plugins/sc-publish/release/homebrew/formula.rb.j2 - name: Validate publish-order dependency graph diff --git a/plugins/sc-publish/release/homebrew/formula.rb.j2 b/plugins/sc-publish/release/homebrew/formula.rb.j2 index ebbd17e0..e731fea4 100644 --- a/plugins/sc-publish/release/homebrew/formula.rb.j2 +++ b/plugins/sc-publish/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end From ed6471cd2b1515bf5b6d229b932f52b52af118cf Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 11:49:48 -0700 Subject: [PATCH 6/9] fix(homebrew): restore upstream drift guard --- .github/workflows/ci.yml | 9 ++++++--- crates/sc-compose/tests/cli/render.rs | 6 +++--- .../fix-homebrew-formula-bundled-paths-tojson.md | 16 +++++++++------- .../sc-publish/release/homebrew/formula.rb.j2 | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 269301d8..3bfc34c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,12 +100,15 @@ jobs: python3 -m pytest .github/scripts/tests python3 -m pytest plugins/sc-publish/.github/scripts/tests - - name: Verify vendored Homebrew template matches this release pipeline + - name: Verify vendored Homebrew template tracks sc-publish shell: bash run: | set -euo pipefail - cmp --silent \ - release/homebrew/formula.rb.j2 \ + # Scope this check to the pending upstream template correction. Once + # it merges, the consumer must synchronize its vendored copy. + git fetch --depth=1 https://github.com/randlee/sc-publish.git \ + refs/heads/develop:refs/remotes/sc-publish-upstream/develop + git diff --exit-code sc-publish-upstream/develop -- \ plugins/sc-publish/release/homebrew/formula.rb.j2 - name: Validate publish-order dependency graph diff --git a/crates/sc-compose/tests/cli/render.rs b/crates/sc-compose/tests/cli/render.rs index 7a3106ee..4470c3fb 100644 --- a/crates/sc-compose/tests/cli/render.rs +++ b/crates/sc-compose/tests/cli/render.rs @@ -163,9 +163,9 @@ fn render_homebrew_formula_escapes_manifest_values_as_ruby_strings() { assert!(formula.contains("desc \"A \\\"quoted\\\" description\"")); assert!(formula.contains("assert_match \"A \\\"quoted\\\" result\"")); assert!(formula.contains("bin.install \"bin/sc-compose\"")); - // Ruby execution belongs to the top-level Python regression because this - // cross-platform Rust CLI suite also runs on Windows, where Ruby is absent. - // That regression executes the same multi-component expression in CI. + // Windows-latest currently includes Ruby, but this cross-platform Rust + // suite does not declare it as a toolchain dependency. The top-level + // Python regression owns execution of this multi-component expression. assert!(formula.contains("(pkgshare/\"quoted\\\"component\"/\"examples\").install Dir[\"share/quoted\\\"component/examples/*\"]")); assert!(!formula.contains("{{")); } diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md index 0fa6c041..8dc43e3e 100644 --- a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -22,10 +22,11 @@ path helper and quote only later path segments. This produces, for example, - **Vendored copy:** `plugins/sc-publish/release/homebrew/formula.rb.j2` is a consumer-local mirror and must not be hand-edited. It is restored byte-for-byte to its pre-`85f7dd7` upstream state. The canonical package correction is - `randlee/sc-publish:fix/homebrew-formula-bundled-paths-tojson` at `8f858ed`. - CI compares this specific vendored template with upstream `develop`: it passes - while that upstream change is pending and fails as soon as it merges until this - consumer synchronizes the kit. + `randlee/sc-publish` PR #85 at `8f858ed`; that PR remains open and upstream + `develop` still has the pre-fix template. CI fetches that actual upstream + `develop` branch and diffs this specific vendored template against it. The + guard therefore passes while PR #85 remains unmerged and fails after it + merges until this consumer synchronizes the kit. - **Prior test gap:** the earlier regression only asserted that the rendered text contained `("pkgshare"/"examples").install ...` and ran `ruby -c`. Quoted and bare helper forms are both valid Ruby syntax, so neither check @@ -33,9 +34,10 @@ path helper and quote only later path segments. This produces, for example, a multi-component rendered `install` block against a minimal Homebrew-compatible helper harness; the old quoted receiver fails because a Ruby `String` has no `install` method. The Rust CLI test keeps its - cross-platform rendering assertion only: Ruby execution is intentionally - covered by this Ubuntu Python regression because the Rust suite also runs on - Windows, where Ruby is not available. + cross-platform rendering assertion only: GitHub's `windows-latest` image + currently includes Ruby, but the Rust test does not declare Ruby as a + toolchain dependency. Ruby execution is therefore intentionally owned by + this Ubuntu Python regression rather than a runner-provided executable. - **Validation evidence for `d4ada7a`:** [`PR #603 checks`](https://github.com/randlee/sc-compose/pull/603/checks) include the CI record for the code commit. The required commands also passed diff --git a/plugins/sc-publish/release/homebrew/formula.rb.j2 b/plugins/sc-publish/release/homebrew/formula.rb.j2 index e731fea4..ebbd17e0 100644 --- a/plugins/sc-publish/release/homebrew/formula.rb.j2 +++ b/plugins/sc-publish/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end From d942f7b7aa9dc4f490ff41b013b09ca891f54ad0 Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 12:04:06 -0700 Subject: [PATCH 7/9] fix(homebrew): allow tracked upstream template fix --- .github/workflows/ci.yml | 20 +++++++++++--- ...x-homebrew-formula-bundled-paths-tojson.md | 27 ++++++++++--------- .../sc-publish/release/homebrew/formula.rb.j2 | 2 +- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bfc34c7..b15bfd1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,12 +104,24 @@ jobs: shell: bash run: | set -euo pipefail - # Scope this check to the pending upstream template correction. Once - # it merges, the consumer must synchronize its vendored copy. + # PR #85 contains this already-validated template fix. Until it + # merges, accept only its exact template as the authorized ahead-of- + # upstream state; after merge, the develop comparison wins again. git fetch --depth=1 https://github.com/randlee/sc-publish.git \ refs/heads/develop:refs/remotes/sc-publish-upstream/develop - git diff --exit-code sc-publish-upstream/develop -- \ - plugins/sc-publish/release/homebrew/formula.rb.j2 + git fetch --depth=1 https://github.com/randlee/sc-publish.git \ + refs/pull/85/head:refs/remotes/sc-publish-upstream/pr-85 + template=plugins/sc-publish/release/homebrew/formula.rb.j2 + if git diff --quiet sc-publish-upstream/develop -- "${template}"; then + exit 0 + fi + if git diff --quiet sc-publish-upstream/pr-85 -- "${template}"; then + echo "vendored Homebrew template matches the authorized sc-publish PR #85 fix" + exit 0 + fi + echo "vendored Homebrew template diverges from both sc-publish develop and PR #85" >&2 + git diff sc-publish-upstream/develop -- "${template}" >&2 + exit 1 - name: Validate publish-order dependency graph run: | diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md index 8dc43e3e..273ae691 100644 --- a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -1,7 +1,8 @@ --- -status: complete +status: pending-upstream branch: fix/homebrew-formula-bundled-paths-tojson pr: 603 +upstream_pr: https://github.com/randlee/sc-publish/pull/85 --- # Fix: render Homebrew bundled-path helpers as Ruby methods @@ -20,13 +21,12 @@ path helper and quote only later path segments. This produces, for example, `.github/workflows/homebrew-publish.yml` reads the manifest and passes the selected template to the published renderer. - **Vendored copy:** `plugins/sc-publish/release/homebrew/formula.rb.j2` is a - consumer-local mirror and must not be hand-edited. It is restored byte-for-byte - to its pre-`85f7dd7` upstream state. The canonical package correction is - `randlee/sc-publish` PR #85 at `8f858ed`; that PR remains open and upstream - `develop` still has the pre-fix template. CI fetches that actual upstream - `develop` branch and diffs this specific vendored template against it. The - guard therefore passes while PR #85 remains unmerged and fails after it - merges until this consumer synchronizes the kit. + consumer-local mirror and must not be hand-edited. It temporarily matches the + validated fix in [sc-publish PR #85](https://github.com/randlee/sc-publish/pull/85) + at `8f858ed`, while upstream `develop` remains unfixed. CI fetches both + upstream `develop` and PR #85: it accepts byte parity with either source and + rejects every other divergence. Once PR #85 merges, `develop` becomes the + required byte-parity source automatically. - **Prior test gap:** the earlier regression only asserted that the rendered text contained `("pkgshare"/"examples").install ...` and ran `ruby -c`. Quoted and bare helper forms are both valid Ruby syntax, so neither check @@ -38,8 +38,9 @@ path helper and quote only later path segments. This produces, for example, currently includes Ruby, but the Rust test does not declare Ruby as a toolchain dependency. Ruby execution is therefore intentionally owned by this Ubuntu Python regression rather than a runner-provided executable. -- **Validation evidence for `d4ada7a`:** - [`PR #603 checks`](https://github.com/randlee/sc-compose/pull/603/checks) - include the CI record for the code commit. The required commands also passed - locally at that commit: `cargo test --workspace` and - `python3 -m pytest -q .github/scripts/tests/test_release_artifacts.py`. +- **Validation evidence (QA6 correction after `ed6471c`):** both the + load-bearing top-level `.github/scripts/tests` suite and the vendored + `plugins/sc-publish/.github/scripts/tests` suite pass genuinely with the + fixed template; no xfail or skip is used. The CI guard permits this single, + tracked, exact PR #85 divergence while that PR is open, while still failing + any divergence from both upstream sources. diff --git a/plugins/sc-publish/release/homebrew/formula.rb.j2 b/plugins/sc-publish/release/homebrew/formula.rb.j2 index ebbd17e0..e731fea4 100644 --- a/plugins/sc-publish/release/homebrew/formula.rb.j2 +++ b/plugins/sc-publish/release/homebrew/formula.rb.j2 @@ -26,7 +26,7 @@ class {{ formula_class }} < Formula bin.install {{ binary_path | tojson }} {% endfor %} {% for bundle in bundled_paths %} - ({% for component in bundle.destination_components %}{{ component | tojson }}{% if not loop.last %}/{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] + ({% for component in bundle.destination_components %}{% if loop.first %}{{ component }}{% else %}/{{ component | tojson }}{% endif %}{% endfor %}).install Dir[{{ bundle.source_glob | tojson }}] {% endfor %} end From c48b046cea223e1c5c220f21b7e3f6d242f49bd4 Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 13:15:52 -0700 Subject: [PATCH 8/9] test(homebrew): cover vendored bundled path chain --- ...fix-homebrew-formula-bundled-paths-tojson.md | 17 +++++++++++++++++ .../scripts/tests/test_release_artifacts.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md index 273ae691..84da3c05 100644 --- a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -44,3 +44,20 @@ path helper and quote only later path segments. This produces, for example, fixed template; no xfail or skip is used. The CI guard permits this single, tracked, exact PR #85 divergence while that PR is open, while still failing any divergence from both upstream sources. + +## QA7 coverage and follow-up + +At `d942f7b`, the fixed vendored template matches the exact template in +[sc-publish PR #85](https://github.com/randlee/sc-publish/pull/85), while +upstream `develop` remains unfixed. The CI guard fetches both sources and +accepts only byte parity with `develop` or the exact PR #85 head; every other +vendored divergence fails. + +The top-level and vendored Ruby-execution fixtures both use +`["pkgshare", "examples", "nested"]`. They therefore execute the bare helper +and the subsequently JSON-quoted path components, and fail against the +pre-fix template. QA7 re-runs both focused tests as genuine passes. + +- **Tracked follow-up:** when PR #85 merges upstream and this repository + synchronizes its vendored copy, remove the PR #85 fetch-and-diff branch from + `.github/workflows/ci.yml` and require `sc-publish/develop` byte parity only. diff --git a/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py b/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py index 24d108b9..17cd549c 100644 --- a/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py +++ b/plugins/sc-publish/.github/scripts/tests/test_release_artifacts.py @@ -2535,7 +2535,7 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) "binary_paths": ["bin/sc-compose", "bin/sc-compose-daemon"], "bundled_paths": [ { - "destination_components": ["pkgshare"], + "destination_components": ["pkgshare", "examples", "nested"], "source_glob": "share/sc-compose/examples/*", } ], @@ -2545,6 +2545,7 @@ def test_release_channel_templates_render_to_valid_ruby_and_json(tmp_path: Path) assert 'bin.install "bin/sc-compose"' in formula assert 'bin.install "bin/sc-compose-daemon"' in formula assert 'shell_output("#{bin}/" + "sc-compose-daemon"' in formula + assert '(pkgshare/"examples"/"nested").install' in formula scoop = render_release_template( tmp_path, From e81961943e82ba504aa3cb4f2f6328eb101a4071 Mon Sep 17 00:00:00 2001 From: Rand Lee Date: Mon, 31 Aug 2026 17:48:50 -0700 Subject: [PATCH 9/9] docs(homebrew): correct fixture fix attribution --- .../sprints/fix-homebrew-formula-bundled-paths-tojson.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md index 84da3c05..b7a1f8fd 100644 --- a/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md +++ b/docs/sprints/fix-homebrew-formula-bundled-paths-tojson.md @@ -47,11 +47,10 @@ path helper and quote only later path segments. This produces, for example, ## QA7 coverage and follow-up -At `d942f7b`, the fixed vendored template matches the exact template in -[sc-publish PR #85](https://github.com/randlee/sc-publish/pull/85), while -upstream `develop` remains unfixed. The CI guard fetches both sources and -accepts only byte parity with `develop` or the exact PR #85 head; every other -vendored divergence fails. +At `d942f7b`, the CI guard began accepting only byte parity with upstream +`develop` or the exact [sc-publish PR #85](https://github.com/randlee/sc-publish/pull/85) +head; every other vendored divergence fails. The vendored three-component +fixture was added later, at `c48b046`. The top-level and vendored Ruby-execution fixtures both use `["pkgshare", "examples", "nested"]`. They therefore execute the bare helper