ci: gate jobs on change detection + tag-based deploy ordering [guardrail-change]
Build + Deploy ran in parallel with CI's lint/test/loc, so a deploy could ship even when CI failed. Gate Build + Deploy on CI success via workflow_run, and add per-service change detection so only affected services rebuild and only relevant lint/test jobs run on PRs. - scripts/detect-changes.sh: shared diff helper that emits per-service + aggregate flags from a BASE_SHA diff; falls back to "rebuild all" when the base is missing or unreachable - ci.yaml: detect-changes job runs first; loc-budget, *-lint, *-build, and test-* jobs gate on the relevant outputs - build-push-deploy.yml: triggered via workflow_run on CI completion; diff base is the last-build/main git tag, force-pushed by a new mark-last-build job after each green run (handles multi-commit pushes, force pushes, and the "all skipped" case) - check-loc.sh: exclude Office/binary extensions (xlsm, docx, pptx, zip, tar, gz) so binary docs aren't counted as source - loc-exceptions.txt: grandfather two existing >500 LOC files (tender_handlers.go, DecisionTreeWizard.tsx) as Phase 5+ backlog Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# Emit per-service + aggregate change flags for the CI / build workflows.
|
||||
#
|
||||
# Reads:
|
||||
# BASE_SHA — diff base. Empty / unreachable → emit everything as true.
|
||||
# HEAD_SHA — diff target. Defaults to HEAD.
|
||||
#
|
||||
# Writes key=value lines to $GITHUB_OUTPUT (defaults to /dev/stdout for local runs).
|
||||
#
|
||||
# Keys emitted:
|
||||
# admin, backend, sdk, portal, tts, crawler, dsms_gateway, dsms_node
|
||||
# any_python, any_node, any
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_SHA="${BASE_SHA:-}"
|
||||
HEAD_SHA="${HEAD_SHA:-HEAD}"
|
||||
OUT="${GITHUB_OUTPUT:-/dev/stdout}"
|
||||
|
||||
ALL_KEYS=(admin backend sdk portal tts crawler dsms_gateway dsms_node any_python any_node any)
|
||||
|
||||
emit() {
|
||||
echo "$1=$2" >> "$OUT"
|
||||
}
|
||||
|
||||
emit_all_true() {
|
||||
reason=$1
|
||||
echo "→ rebuild all: $reason"
|
||||
for k in "${ALL_KEYS[@]}"; do
|
||||
emit "$k" true
|
||||
done
|
||||
}
|
||||
|
||||
if [ -z "$BASE_SHA" ]; then
|
||||
emit_all_true "no BASE_SHA provided"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify "${BASE_SHA}^{commit}" >/dev/null 2>&1; then
|
||||
emit_all_true "BASE_SHA ${BASE_SHA} unreachable"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
changed=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" || true)
|
||||
echo "Changed files since ${BASE_SHA}:"
|
||||
echo "${changed:-(none)}"
|
||||
echo "---"
|
||||
|
||||
check() {
|
||||
key=$1
|
||||
path=$2
|
||||
if echo "$changed" | grep -q "^${path}/"; then
|
||||
emit "$key" true
|
||||
echo " ${key} (${path}/): true"
|
||||
else
|
||||
emit "$key" false
|
||||
echo " ${key} (${path}/): false"
|
||||
fi
|
||||
}
|
||||
|
||||
check admin admin-compliance
|
||||
check backend backend-compliance
|
||||
check sdk ai-compliance-sdk
|
||||
check portal developer-portal
|
||||
check tts compliance-tts-service
|
||||
check crawler document-crawler
|
||||
check dsms_gateway dsms-gateway
|
||||
check dsms_node dsms-node
|
||||
|
||||
# Aggregate flags
|
||||
if echo "$changed" | grep -qE "^(backend-compliance|document-crawler|dsms-gateway|compliance-tts-service)/"; then
|
||||
emit any_python true
|
||||
echo " any_python: true"
|
||||
else
|
||||
emit any_python false
|
||||
echo " any_python: false"
|
||||
fi
|
||||
|
||||
if echo "$changed" | grep -qE "^(admin-compliance|developer-portal)/"; then
|
||||
emit any_node true
|
||||
echo " any_node: true"
|
||||
else
|
||||
emit any_node false
|
||||
echo " any_node: false"
|
||||
fi
|
||||
|
||||
if [ -n "$changed" ]; then
|
||||
emit any true
|
||||
echo " any: true"
|
||||
else
|
||||
emit any false
|
||||
echo " any: false"
|
||||
fi
|
||||
Reference in New Issue
Block a user