From 3213bd0743081535b36394934692fa0d8d687452 Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 26 Oct 2026 23:33:13 +0000 Subject: [PATCH] Remove the script/build.sh hook; rainix is the sole staged-config installer install-staged-config no longer skips when script/build.sh exists, and rainix-copy-artifacts no longer runs script/build.sh by presence. Repos that regenerate further committed artifacts declare the command through the regenerate-command input. Closes #418 Co-Authored-By: Claude Opus 5.5 --- .../actions/install-staged-config/action.yml | 2 +- .github/workflows/rainix-copy-artifacts.yaml | 16 +++++--- README.md | 8 +++- rainix-static/src/install_staged_config.rs | 7 ---- rainix-static/src/main.rs | 7 ++-- .../action/install-staged-config.test.bats | 39 +++---------------- .../workflow/rainix-copy-artifacts.test.bats | 13 ++++++- 7 files changed, 38 insertions(+), 54 deletions(-) diff --git a/.github/actions/install-staged-config/action.yml b/.github/actions/install-staged-config/action.yml index 325279f..aeea1cf 100644 --- a/.github/actions/install-staged-config/action.yml +++ b/.github/actions/install-staged-config/action.yml @@ -1,6 +1,6 @@ name: install-staged-config description: >- - Installs what `forge script ./script/Build.sol` staged in `.staged-config/` onto the root files of the same names, then removes the dir. Foundry refuses a cheatcode write to the root `foundry.toml`, so a deploy repo's generated config reaches the root only through here. Fails when nothing was staged but `foundry.toml` carries `# rain-deploy:generated:` blocks, and when the dir is empty. A repo that stages nothing and has no such blocks is a no-op. A repo with `script/build.sh` is skipped: that hook installs the dir. + Installs what `forge script ./script/Build.sol` staged in `.staged-config/` onto the root files of the same names, then removes the dir. Foundry refuses a cheatcode write to the root `foundry.toml`, so a deploy repo's generated config reaches the root only through here. Fails when nothing was staged but `foundry.toml` carries `# rain-deploy:generated:` blocks, and when the dir is empty. A repo that stages nothing and has no such blocks is a no-op. runs: using: composite steps: diff --git a/.github/workflows/rainix-copy-artifacts.yaml b/.github/workflows/rainix-copy-artifacts.yaml index 60fb875..a915db1 100644 --- a/.github/workflows/rainix-copy-artifacts.yaml +++ b/.github/workflows/rainix-copy-artifacts.yaml @@ -1,6 +1,11 @@ name: rainix-copy-artifacts on: workflow_call: + inputs: + regenerate-command: + description: Run after forge build and CopyArtifacts.sol, outside any devshell, to regenerate committed artifacts the fixed steps do not. + type: string + default: "" env: RAINIX_SHA: 2aea60e5c4004cfaeb6168f26e71b3ed695fd349 jobs: @@ -65,14 +70,13 @@ jobs: run: | set -euo pipefail nix develop github:rainlanguage/rainix/${{ env.RAINIX_SHA }}#sol-shell -c forge script ./script/CopyArtifacts.sol --ffi | tee -a "$RUNNER_TEMP/rainix-codegen.log" - # Catch-all post-forge regen hook: consumer-supplied. Runs outside any - # nix devshell so the script picks shells per command (subgraph-shell, - # sol-shell, etc.) for whatever derived artifacts it emits. - name: Regenerate derived artifacts - if: hashFiles('script/build.sh') != '' + if: inputs.regenerate-command != '' + env: + REGENERATE_COMMAND: ${{ inputs.regenerate-command }} run: | set -euo pipefail - ./script/build.sh | tee -a "$RUNNER_TEMP/rainix-codegen.log" + bash -c "$REGENERATE_COMMAND" | tee -a "$RUNNER_TEMP/rainix-codegen.log" - name: Assert every declared generated path was written uses: rainlanguage/rainix/.github/actions/codegen-declaration@main with: @@ -82,6 +86,6 @@ jobs: - name: Assert committed artifacts match freshly built run: | if ! git diff --exit-code; then - echo "::error::Committed artifacts are stale. Regenerate (script/build-meta.sh, script/Build.sol, script/CopyArtifacts.sol, script/build.sh, forge fmt) and commit." + echo "::error::Committed artifacts are stale. Regenerate (script/build-meta.sh, script/Build.sol, script/CopyArtifacts.sol, the regenerate-command input, forge fmt) and commit." exit 1 fi diff --git a/README.md b/README.md index 6f5dd24..0e9417d 100644 --- a/README.md +++ b/README.md @@ -178,9 +178,11 @@ single job it runs whichever of these the repo has: - `.staged-config/` → the root files of the same names, then the dir is removed. `Build.sol` stages there because foundry refuses a cheatcode write to the root `foundry.toml`. Fails if the dir is empty, or missing while `foundry.toml` - carries `# rain-deploy:generated:` blocks. Skipped when the repo has - `script/build.sh`, which then owns the install. + carries `# rain-deploy:generated:` blocks. rainix is the only installer. - `forge build` + `./script/CopyArtifacts.sol --ffi` → committed ABI JSON +- the `regenerate-command` input, when set → whatever else the repo commits + (bindings ABIs, subgraph codegen). Runs after the copy, outside any devshell, + so it picks its own shells. then `forge fmt` and the `git diff` assert. @@ -212,6 +214,8 @@ jobs: copy-artifacts: uses: rainlanguage/rainix/.github/workflows/rainix-copy-artifacts.yaml@main secrets: inherit + with: + regenerate-command: ./script/regenerate-bindings.sh ``` This replaces the former `rainix-build-pointers` reusable — a pointer-only repo diff --git a/rainix-static/src/install_staged_config.rs b/rainix-static/src/install_staged_config.rs index dd13a87..3f40fca 100644 --- a/rainix-static/src/install_staged_config.rs +++ b/rainix-static/src/install_staged_config.rs @@ -2,9 +2,6 @@ use std::path::Path; pub(crate) const STAGED_DIR: &str = ".staged-config"; -/// A consumer with this hook installs the staging dir itself. -pub(crate) const BUILD_HOOK: &str = "script/build.sh"; - /// Foundry refuses a cheatcode write to the root `foundry.toml`, so a generated /// block marked there can only be refreshed through the staging dir. pub(crate) const GENERATED_MARKER: &str = "# rain-deploy:generated:"; @@ -58,10 +55,6 @@ pub(crate) fn install(root: &Path) -> Result, String> { } pub(crate) fn run(root: &Path) { - if root.join(BUILD_HOOK).is_file() { - println!("install-staged-config: {BUILD_HOOK} present, leaving {STAGED_DIR}/ to it"); - return; - } match install(root) { Err(e) => crate::fail(&format!("install-staged-config: {e}")), Ok(names) if names.is_empty() => { diff --git a/rainix-static/src/main.rs b/rainix-static/src/main.rs index 20f1f4e..209f415 100644 --- a/rainix-static/src/main.rs +++ b/rainix-static/src/main.rs @@ -69,10 +69,9 @@ // declaring nothing passes. // install-staged-config [--root ] // install what `forge script ./script/Build.sol` staged in .staged-config/ -// onto the root files of the same names, then remove the dir. Skipped -// when script/build.sh exists. Missing fails when foundry.toml carries -// rain-deploy generated markers; empty always fails. Semantics: -// install_staged_config.rs. +// onto the root files of the same names, then remove the dir. Missing +// fails when foundry.toml carries rain-deploy generated markers; empty +// always fails. Semantics: install_staged_config.rs. // snapshots-append-only [--base ] [--root ] // fail unless every existing per-tag deploy-pin snapshot under // // (default root src/generated, base origin/main) survives diff --git a/test/bats/action/install-staged-config.test.bats b/test/bats/action/install-staged-config.test.bats index d16237c..62ff20b 100644 --- a/test/bats/action/install-staged-config.test.bats +++ b/test/bats/action/install-staged-config.test.bats @@ -123,53 +123,26 @@ install() { [[ "$output" == *"does not exist"* ]] } -@test "a repo with script/build.sh leaves the staging dir to that hook" { +@test "a repo with script/build.sh has its staged config installed" { mk_marked_consumer - cp "$work/foundry.toml" "$work/before.toml" mkdir "$work/script" "$work/.staged-config" printf '#!/usr/bin/env bash\n' >"$work/script/build.sh" printf 'NEW FOUNDRY\n' >"$work/.staged-config/foundry.toml" install - [ "$status" -eq 0 ] - [[ "$output" == *"script/build.sh present"* ]] - cmp "$work/foundry.toml" "$work/before.toml" - [ "$(cat "$work/.staged-config/foundry.toml")" = "NEW FOUNDRY" ] -} - -@test "a repo with script/build.sh and nothing staged is left to that hook" { - mk_marked_consumer - mkdir "$work/script" - printf '#!/usr/bin/env bash\n' >"$work/script/build.sh" - - install - - [ "$status" -eq 0 ] - [[ "$output" == *"script/build.sh present"* ]] -} - -@test "a directory named script/build.sh is not a hook" { - mk_marked_consumer - mkdir -p "$work/script/build.sh" "$work/.staged-config" - printf 'NEW FOUNDRY\n' >"$work/.staged-config/foundry.toml" - - install - [ "$status" -eq 0 ] [ "$(cat "$work/foundry.toml")" = "NEW FOUNDRY" ] [ ! -e "$work/.staged-config" ] } -@test "a repo with other script hooks but no script/build.sh installs the staged config" { +@test "a repo with script/build.sh and nothing staged still fails" { mk_marked_consumer - mkdir "$work/script" "$work/.staged-config" - printf '#!/usr/bin/env bash\n' >"$work/script/build-meta.sh" - printf 'NEW FOUNDRY\n' >"$work/.staged-config/foundry.toml" + mkdir "$work/script" + printf '#!/usr/bin/env bash\n' >"$work/script/build.sh" install - [ "$status" -eq 0 ] - [ "$(cat "$work/foundry.toml")" = "NEW FOUNDRY" ] - [ ! -e "$work/.staged-config" ] + [ "$status" -ne 0 ] + [[ "$output" == *"does not exist"* ]] } diff --git a/test/bats/workflow/rainix-copy-artifacts.test.bats b/test/bats/workflow/rainix-copy-artifacts.test.bats index 47da8e4..7a329a9 100644 --- a/test/bats/workflow/rainix-copy-artifacts.test.bats +++ b/test/bats/workflow/rainix-copy-artifacts.test.bats @@ -49,7 +49,7 @@ setup() { @test "every codegen hook reaches the log — none is left undeclarable" { local hook - for hook in script/build-meta.sh script/Build.sol script/CopyArtifacts.sol script/build.sh; do + for hook in script/build-meta.sh script/Build.sol script/CopyArtifacts.sol REGENERATE_COMMAND; do echo "$teed" | grep -qF "$hook" done } @@ -100,3 +100,14 @@ setup() { [ "$install" -lt "$forge_build" ] [ "$install" -lt "$verify" ] } + +@test "derived artifacts regenerate from the declared input, never a file the repo happens to have" { + local step + step="$(yq -r '.jobs.copy-artifacts.steps[] | select(.name == "Regenerate derived artifacts")' "$workflow")" + [ "$(echo "$step" | yq -r '.if')" = "inputs.regenerate-command != ''" ] + # shellcheck disable=SC2016 + [ "$(echo "$step" | yq -r '.env.REGENERATE_COMMAND')" = '${{ inputs.regenerate-command }}' ] + [ "$(yq -r '.on.workflow_call.inputs.regenerate-command.default' "$workflow")" = "" ] + [ -z "$(grep -F 'script/build.sh' "$workflow" || true)" ] + [ -z "$(echo "$runs" | grep -F 'inputs.' || true)" ] +}