Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/check-shell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ jobs:
- run: nix develop --command yq --version
- run: nix develop --command gh --version
- run: nix develop --command default-shell-test
- run: nix run .#rainix-check-flake-pin
- run: nix develop .#sol-shell --command nix run .#sol-shell-test
- run: nix develop .#rust-shell --command nix run .#rust-shell-test
15 changes: 15 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,19 @@
rainix-sol-single-contract
];

# Verifies all rainix-*.yaml reusable workflows pin their RAINIX_SHA
# to the same commit SHA and contain no bare unpinned flake refs.
# Catches toolchain-bump drift across the 11 reusable workflow files.
rainix-check-flake-pin = mkTask {
name = "rainix-check-flake-pin";
body = ''
set -euo pipefail
source ${./lib/check-rainix-flake-pin.sh}
check_rainix_flake_pin_consistent ".github/workflows"
'';
additionalBuildInputs = [ pkgs.gnugrep pkgs.gnused pkgs.coreutils ];
};

Comment thread
coderabbitai[bot] marked this conversation as resolved.
rs-tasks = [
rainix-rs-static
];
Expand Down Expand Up @@ -381,6 +394,7 @@
bats test/bats/task/subgraph-build.test.bats
bats test/bats/task/subgraph-deploy-version.test.bats
bats test/bats/task/sol-single-contract.test.bats
bats test/bats/task/check-rainix-flake-pin.test.bats
'';
additionalBuildInputs = [ pkgs.bats ] ++ sol-build-inputs ++ node-build-inputs;
};
Expand Down Expand Up @@ -587,6 +601,7 @@
rainix-sol-artifacts
rainix-sol-single-contract
rainix-rs-static
rainix-check-flake-pin
prettier-bundle
sol-shell-test
rust-shell-test
Expand Down
76 changes: 76 additions & 0 deletions lib/check-rainix-flake-pin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash

# Verifies that every reusable workflow in .github/workflows/rainix-*.yaml
# pins its RAINIX_SHA to the same commit SHA, and that no bare unpinned
# github:rainlanguage/rainix# references exist. A missed SHA during a bump
# silently leaves one reusable on a stale toolchain; this script catches the
# drift before it reaches CI.
#
# Usage (from the repo root):
# source lib/check-rainix-flake-pin.sh
# check_rainix_flake_pin_consistent # exits non-zero on any mismatch

# Extract the RAINIX_SHA value from a single workflow file.
# Outputs the trimmed sha string if found; empty string otherwise.
# Usage: _extract_sha <yaml_file>
_extract_sha() {
local file="$1"
grep -E '^\s+RAINIX_SHA:' "$file" 2>/dev/null | head -1 | sed 's/.*RAINIX_SHA:\s*//' | tr -d '[:space:]'
}

# Count bare unpinned github:rainlanguage/rainix# refs (the 429-prone form)
# across all rainix-*.yaml workflow files.
# Usage: _count_unpinned_refs <workflows_dir>
_count_unpinned_refs() {
local dir="$1"
grep -rE 'github:rainlanguage/rainix#' "$dir"/rainix-*.yaml 2>/dev/null | wc -l | tr -d '[:space:]'
}

# Assert that all rainix-*.yaml reusable workflow files define the same
# RAINIX_SHA value, that it is non-empty, and that no bare unpinned
# github:rainlanguage/rainix# refs are present.
#
# Arguments:
# $1 Path to the .github/workflows directory (default: .github/workflows)
#
# Exits non-zero and prints a diagnostic on any violation.
check_rainix_flake_pin_consistent() {
local workflows_dir="${1:-.github/workflows}"
local first_sha=""
local first_file=""
local mismatches=0

if ! compgen -G "${workflows_dir}/rainix-*.yaml" > /dev/null 2>&1; then
echo "check-rainix-flake-pin: no rainix-*.yaml files found under ${workflows_dir}" >&2
return 1
fi

for f in "${workflows_dir}"/rainix-*.yaml; do
local sha
sha="$(_extract_sha "$f")"
if [ -z "$sha" ]; then
echo "check-rainix-flake-pin: RAINIX_SHA not found in ${f}" >&2
mismatches=$((mismatches + 1))
continue
fi
if [ -z "$first_sha" ]; then
first_sha="$sha"
first_file="$f"
elif [ "$sha" != "$first_sha" ]; then
echo "check-rainix-flake-pin: SHA mismatch: ${f} has '${sha}' but ${first_file} has '${first_sha}'" >&2
mismatches=$((mismatches + 1))
fi
done

local unpinned
unpinned="$(_count_unpinned_refs "$workflows_dir")"
if [ "$unpinned" -gt 0 ]; then
echo "check-rainix-flake-pin: ${unpinned} bare unpinned 'github:rainlanguage/rainix#' ref(s) found — replace with pinned SHA form" >&2
mismatches=$((mismatches + 1))
fi

if [ "$mismatches" -gt 0 ]; then
return 1
fi
return 0
}
139 changes: 139 additions & 0 deletions test/bats/task/check-rainix-flake-pin.test.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
setup() {
# shellcheck disable=SC1091
source lib/check-rainix-flake-pin.sh
TESTDIR="$(mktemp -d)"
mkdir -p "$TESTDIR"
}

teardown() {
rm -rf "$TESTDIR"
}

# ── helper ──────────────────────────────────────────────────────────────────

_write_workflow() {
local file="$1"
local sha="$2"
cat > "$file" << EOF
on: [workflow_call]
env:
RAINIX_SHA: $sha
OTHER_VAR: foo
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: nix develop github:rainlanguage/rainix/\${{ env.RAINIX_SHA }}#sol-shell -c forge test
EOF
}

# ── extract sha ─────────────────────────────────────────────────────────────

@test "_extract_sha returns the SHA from a workflow file" {
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "abc123def456"
run _extract_sha "$TESTDIR/rainix-sol-test.yaml"
[ "$status" -eq 0 ]
[ "$output" = "abc123def456" ]
}

@test "_extract_sha returns empty string when RAINIX_SHA is absent" {
cat > "$TESTDIR/rainix-no-sha.yaml" << 'EOF'
on: [workflow_call]
jobs:
test:
runs-on: ubuntu-latest
EOF
run _extract_sha "$TESTDIR/rainix-no-sha.yaml"
[ "$output" = "" ]
}

# ── count unpinned refs ──────────────────────────────────────────────────────

@test "_count_unpinned_refs returns 0 when no bare refs present" {
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "abc123"
run _count_unpinned_refs "$TESTDIR"
[ "$status" -eq 0 ]
[ "$output" = "0" ]
}

@test "_count_unpinned_refs counts bare unpinned refs" {
cat > "$TESTDIR/rainix-bad.yaml" << 'EOF'
on: [workflow_call]
env:
RAINIX_SHA: abc123
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: nix develop github:rainlanguage/rainix#sol-shell -c forge test
EOF
run _count_unpinned_refs "$TESTDIR"
[ "$status" -eq 0 ]
[ "$output" = "1" ]
}

# ── consistency check ────────────────────────────────────────────────────────

@test "check_rainix_flake_pin_consistent passes when all files have the same SHA" {
local sha="307bf27fcc5a410994f5a6a6a96527a64625c3da"
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "$sha"
_write_workflow "$TESTDIR/rainix-rs-test.yaml" "$sha"
_write_workflow "$TESTDIR/rainix-copy-artifacts.yaml" "$sha"
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -eq 0 ]
}

@test "check_rainix_flake_pin_consistent fails when SHAs differ between files" {
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
_write_workflow "$TESTDIR/rainix-rs-test.yaml" "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -ne 0 ]
[[ "$output" == *"SHA mismatch"* ]]
}

@test "check_rainix_flake_pin_consistent fails when a file is missing RAINIX_SHA" {
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "abc123"
cat > "$TESTDIR/rainix-no-sha.yaml" << 'EOF'
on: [workflow_call]
jobs:
test:
runs-on: ubuntu-latest
EOF
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -ne 0 ]
[[ "$output" == *"RAINIX_SHA not found"* ]]
}

@test "check_rainix_flake_pin_consistent fails on bare unpinned ref" {
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "abc123"
cat > "$TESTDIR/rainix-bad.yaml" << 'EOF'
on: [workflow_call]
env:
RAINIX_SHA: abc123
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: nix develop github:rainlanguage/rainix#sol-shell -c forge test
EOF
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -ne 0 ]
[[ "$output" == *"bare unpinned"* ]]
}

@test "check_rainix_flake_pin_consistent fails when directory has no rainix-*.yaml files" {
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -ne 0 ]
[[ "$output" == *"no rainix-*.yaml files found"* ]]
}

# ── mixed-file smoke test ────────────────────────────────────────────────────

@test "check_rainix_flake_pin_consistent passes with three files sharing a full 40-char sha" {
local sha="307bf27fcc5a410994f5a6a6a96527a64625c3da"
_write_workflow "$TESTDIR/rainix-sol-test.yaml" "$sha"
_write_workflow "$TESTDIR/rainix-rs-static.yaml" "$sha"
_write_workflow "$TESTDIR/rainix-subgraph-test.yaml" "$sha"
run check_rainix_flake_pin_consistent "$TESTDIR"
[ "$status" -eq 0 ]
}
Loading