Skip to content
Merged
234 changes: 234 additions & 0 deletions .github/workflows/check_dependencies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# Runs `silverfin check-dependencies -h <handle>` for each reconciliation template
# changed in the PR. Triggered only when the `code-review` label is added.
# Reusable workflow: call from a consumer repo with pull_request (labeled) and/or workflow_dispatch.
#
# Does not run check_auth: that job updates CONFIG_JSON via gh secret set, which notifies
# repo/org admins by email. The CLI check-dependencies command only scans local Liquid Test
# YAML files and does not need API credentials.
name: Check dependencies
run-name: Check dependencies for changed reconciliation templates
on:
workflow_call:
inputs:
pull_request_number:
description: "Optional PR number when the caller passes it explicitly (e.g. workflow_dispatch input)."
required: false
type: string

jobs:
check-dependencies:
# Run when the added label is "code-review" (pull_request) or when triggered manually (workflow_dispatch)
if: github.event.label.name == 'code-review' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
env:
WORKFLOW_CALL_PR_NUMBER: ${{ inputs.pull_request_number }}
steps:
# Resolve PR base and head SHAs (head used for checkout so scans match the PR branch)
- name: Get PR details
id: pr-details
uses: actions/github-script@v7
with:
script: |
const fromCall = process.env.WORKFLOW_CALL_PR_NUMBER;
const prNumber =
context.payload.pull_request?.number ??
context.payload.inputs?.pull_request_number ??
(fromCall && fromCall !== "" ? parseInt(fromCall, 10) : undefined);
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
core.setOutput("base_sha", pr.base.sha);
core.setOutput("head_sha", pr.head.sha);

# Check out PR HEAD (config.json / template tree for check-dependencies must match the PR)
- name: Checkout PR head
uses: actions/checkout@v4
with:
ref: ${{ steps.pr-details.outputs.head_sha }}

# Get list of files changed in the PR via API (no PR checkout)
- name: Get PR changed files
id: pr-files
uses: actions/github-script@v7
with:
script: |
const fromCall = process.env.WORKFLOW_CALL_PR_NUMBER;
const prNumber =
context.payload.pull_request?.number ??
context.payload.inputs?.pull_request_number ??
(fromCall && fromCall !== "" ? parseInt(fromCall, 10) : undefined);
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
const paths = files.map(f => f.filename).join("\n");
core.setOutput("paths", paths);

# Derive reconciliation handles from changed paths (same pattern as run_tests "Filter templates changed")
- name: Get reconciliation handles to check
id: handles
env:
CHANGED_FILES: ${{ steps.pr-files.outputs.paths }}
run: |
changed_files="$CHANGED_FILES"
pattern="reconciliation_texts/([^/]+)/"
if [ -n "$changed_files" ]; then
filtered_names=($(printf "%s\n" "$changed_files" | grep -oE "$pattern" | sed "s|reconciliation_texts/||;s|/||" | sort -u))
else
filtered_names=()
fi
# Resolve handle from config.json if present (explicit mapping), else use directory name
handles=()
for dir in "${filtered_names[@]}"; do
config_path="reconciliation_texts/${dir}/config.json"
if [ -f "$config_path" ]; then
h=$(jq -r ".handle // .name // empty" "$config_path" 2>/dev/null || true)
[ -z "$h" ] && h="$dir"
else
h="$dir"
fi
handles+=("$h")
done
# Dedupe and output
if [ ${#handles[@]} -eq 0 ]; then
echo "handles_json=[]" >> $GITHUB_OUTPUT
echo "No reconciliation templates changed."
else
echo "handles_json=$(printf "%s\n" "${handles[@]}" | sort -u | jq -R -s -c "split(\"\n\") | map(select(length > 0))")" >> $GITHUB_OUTPUT
echo "Handles to check:"
printf "%s\n" "${handles[@]}" | sort -u
fi

- name: Post comment when no reconciliation templates changed
if: steps.handles.outputs.handles_json == '[]'
uses: actions/github-script@v7
with:
script: |
const marker = "<!-- silverfin-check-dependencies -->";
const body = [
"## Silverfin check-dependencies",
"",
"No reconciliation templates were changed in this PR. Nothing to run.",
"",
marker
].join("\n");
const { owner, repo } = context.repo;
const fromCall = process.env.WORKFLOW_CALL_PR_NUMBER;
const prNumber =
context.payload.pull_request?.number ??
context.payload.inputs?.pull_request_number ??
(fromCall && fromCall !== "" ? parseInt(fromCall, 10) : undefined);
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number: prNumber
});
const existing = comments.find(c =>
c.user.type === "Bot" && c.body && c.body.includes(marker)
);
if (existing) {
await github.rest.issues.updateComment({
owner, repo, comment_id: existing.id, body
});
} else {
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body
});
}

- name: Setup Node and Silverfin CLI
if: steps.handles.outputs.handles_json != '[]'
run: |
npm install https://github.com/silverfin/silverfin-cli.git
node ./node_modules/silverfin-cli/bin/cli.js -V

# Run check-dependencies for each handle and collect results
- name: Run check-dependencies per handle
id: run-check
if: steps.handles.outputs.handles_json != '[]'
env:
HANDLES_JSON: ${{ steps.handles.outputs.handles_json }}
run: |
job_failed=0
: > check_results.txt
while IFS= read -r handle; do
echo "## Handle: \`${handle}\`" >> check_results.txt
echo "" >> check_results.txt
echo "Command: \`silverfin check-dependencies -h ${handle}\`" >> check_results.txt
echo "" >> check_results.txt
set +e
output=$(node ./node_modules/silverfin-cli/bin/cli.js check-dependencies -h "$handle" 2>&1)
exit_code=$?
set -e
echo '```' >> check_results.txt
echo "$output" >> check_results.txt
echo '```' >> check_results.txt
echo "" >> check_results.txt
if [[ $exit_code -ne 0 ]]; then
echo "**Status: Failed (exit code ${exit_code})**" >> check_results.txt
job_failed=1
else
echo "**Status: OK**" >> check_results.txt
fi
echo "" >> check_results.txt
done < <(jq -r '.[]' <<< "$HANDLES_JSON")
echo "results<<CHECK_EOF" >> $GITHUB_OUTPUT
cat check_results.txt >> $GITHUB_OUTPUT
echo "CHECK_EOF" >> $GITHUB_OUTPUT
echo "job_failed=$job_failed" >> $GITHUB_OUTPUT

- name: Post PR comment with results
if: steps.handles.outputs.handles_json != '[]' && always() && steps.run-check.outcome != 'skipped'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = "<!-- silverfin-check-dependencies -->";
const resultsContent = fs.existsSync('check_results.txt')
? fs.readFileSync('check_results.txt', 'utf8')
: '';
const body = [
"## Silverfin check-dependencies",
"",
"Ran for reconciliation templates changed in this PR (triggered by `code-review` label).",
"",
resultsContent,
marker
].join("\n");

const fromCall = process.env.WORKFLOW_CALL_PR_NUMBER;
const prNumber =
context.payload.pull_request?.number ??
context.payload.inputs?.pull_request_number ??
(fromCall && fromCall !== "" ? parseInt(fromCall, 10) : undefined);
const owner = context.repo.owner;
const repo = context.repo.repo;

const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number: prNumber
});
const existing = comments.find(c =>
c.user.type === "Bot" && c.body && c.body.includes(marker)
);

if (existing) {
await github.rest.issues.updateComment({
owner, repo, comment_id: existing.id, body
});
console.log("Updated existing check-dependencies comment");
} else {
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body
});
console.log("Created new check-dependencies comment");
}

- name: Fail job if any check-dependencies failed
if: steps.handles.outputs.handles_json != '[]' && steps.run-check.outputs.job_failed == '1'
run: |
echo "One or more silverfin check-dependencies runs failed. See PR comment for details."
exit 1
Loading