chore(scripts): add lint-infra, typecheck-all, test-all cross-repo scripts

This commit is contained in:
saravanakumardb1 2026-03-26 23:15:16 -07:00
parent f8c0da5c2a
commit 409144a2ef
5 changed files with 558 additions and 0 deletions

View File

@ -344,6 +344,17 @@ npx tsx scripts/encrypt-migrate.ts --product chronomind # Live encrypt
./scripts/prep-consumer.sh /path/to/consumer-dir # pack + rewrite
./scripts/prep-consumer.sh /path/to/consumer-dir --restore # undo
# ── Infrastructure lint (Dockerfiles + Helm charts) ─
./scripts/lint-infra.sh # Lint all 25 Dockerfiles + any Helm charts
./scripts/lint-infra.sh --docker # Dockerfiles only
./scripts/lint-infra.sh --helm # Helm charts only
./scripts/lint-infra.sh path/to/Dockerfile # Explicit path(s)
# Requires: brew install hadolint helm
# ── Cross-repo typecheck + test ──────────────────────
./scripts/typecheck-all.sh # pnpm typecheck across all 11 repos
./scripts/test-all.sh # pnpm test --run across all 11 repos
# ── Health check all services ──────────────────────
pnpm --filter @lysnrai/monitoring check
```

View File

@ -159,6 +159,27 @@ Each consumer repo has a convenience wrapper: `scripts/docker-prep.sh` (or `scri
> **Dashboards inside this repo** (`dashboards/admin-web`, `dashboards/tracker-web`) use `workspace:*` refs and do NOT need this workflow — pnpm resolves them automatically.
## Infrastructure Lint
Validates all 25 Dockerfiles across the 11 ByteLyst repos using [hadolint](https://github.com/hadolint/hadolint), and any Helm charts using `helm lint` + `helm template`.
```bash
# Prerequisites
brew install hadolint helm
# Lint everything
./scripts/lint-infra.sh
# Dockerfiles only / Helm charts only
./scripts/lint-infra.sh --docker
./scripts/lint-infra.sh --helm
# Explicit paths
./scripts/lint-infra.sh path/to/Dockerfile path/to/chart-dir
```
Suppressed rules (false positives for this codebase): `DL3045`, `DL3018`, `DL3008`, `DL3059`, `SC2155`.
## Design Tokens
Generate platform-specific token files from the canonical JSON:

348
scripts/lint-infra.sh Executable file
View File

@ -0,0 +1,348 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# lint-infra.sh — Infrastructure validation for the ByteLyst ecosystem
# ═══════════════════════════════════════════════════════════════════════
# Validates all Dockerfiles (hadolint) and Helm charts (helm lint + template)
# across the 11 ByteLyst repos.
#
# Usage:
# ./scripts/lint-infra.sh # Lint everything (Dockerfiles + Helm charts)
# ./scripts/lint-infra.sh --docker # Dockerfiles only
# ./scripts/lint-infra.sh --helm # Helm charts only
# ./scripts/lint-infra.sh --fix # Show suggested fixes alongside errors
# ./scripts/lint-infra.sh path/to/Dockerfile [path/to/Chart-dir] ...
#
# Prerequisites:
# brew install hadolint helm
#
# Exit codes:
# 0 All checks passed
# 1 One or more checks failed
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail
# ── Colors ─────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
# ── Globals ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
WORKSPACE_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
LINT_DOCKER=true
LINT_HELM=true
SHOW_FIX=false
EXPLICIT_PATHS=()
PASS=0
FAIL=0
SKIP=0
FAILURES=()
# ── Known Hadolint false-positive suppressions ─────────────────────────
# DL3045: COPY to relative path — all our Dockerfiles set WORKDIR first
# DL3018: Pin versions in apk add — we use node:22-alpine base images
# DL3008: Pin versions in apt-get — impractical for Python sidecar base images
# DL3059: Multiple consecutive RUN — intentional for layer caching
# SC2155: Declare and assign separately — false positive for BuildKit secret mount pattern
# (export VAR="$(cat /run/secrets/...)" is the canonical Docker secret idiom)
HADOLINT_IGNORE="DL3045,DL3018,DL3008,DL3059,SC2155"
# ── ByteLyst ecosystem repos (relative to workspace root) ─────────────
ECOSYSTEM_REPOS=(
learning_ai_common_plat
learning_voice_ai_agent
learning_multimodal_memory_agents
learning_ai_clock
learning_ai_jarvis_jr
learning_ai_fastgap
learning_ai_peakpulse
learning_ai_flowmonk
learning_ai_notes
learning_ai_trails
learning_ai_local_memory_gpt
)
# ── Helpers ────────────────────────────────────────────────────────────
log() { echo -e "${CYAN}[lint-infra]${NC} $*"; }
ok() { echo -e " ${GREEN}${NC} $*"; PASS=$((PASS + 1)); }
fail() { echo -e " ${RED}${NC} $*"; FAIL=$((FAIL + 1)); FAILURES+=("$1"); }
skip() { echo -e " ${DIM}$*${NC}"; SKIP=$((SKIP + 1)); }
warn() { echo -e " ${YELLOW}${NC} $*"; }
usage() {
echo "Usage: $(basename "$0") [OPTIONS] [PATH ...]"
echo ""
echo "Options:"
echo " --docker Lint Dockerfiles only"
echo " --helm Lint Helm charts only"
echo " --fix Show suggested fixes alongside errors"
echo " -h, --help Show this help"
echo ""
echo "If PATHs are given, only those files/dirs are checked."
echo "Otherwise, auto-discovers across all 11 ByteLyst repos."
echo ""
echo "Prerequisites: brew install hadolint helm"
}
# Check if a tool is available, print install instructions if not
require_tool() {
local tool="$1" install_cmd="$2"
if ! command -v "$tool" &>/dev/null; then
echo -e "${RED}Error:${NC} '${tool}' is not installed."
echo -e " Install: ${BOLD}${install_cmd}${NC}"
echo ""
return 1
fi
return 0
}
# Return a short relative path for display
short_path() {
local full="$1"
# Try to make path relative to workspace root
if [[ "$full" == "$WORKSPACE_ROOT"/* ]]; then
echo "${full#$WORKSPACE_ROOT/}"
else
echo "$full"
fi
}
# ── Auto-discovery ─────────────────────────────────────────────────────
discover_dockerfiles() {
local files=()
for repo in "${ECOSYSTEM_REPOS[@]}"; do
local repo_dir="${WORKSPACE_ROOT}/${repo}"
[ -d "$repo_dir" ] || continue
while IFS= read -r -d '' f; do
files+=("$f")
done < <(find "$repo_dir" -maxdepth 4 -name 'Dockerfile*' \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-not -path '*/_deferred*' \
-print0 2>/dev/null | sort -z)
done
echo "${files[@]}"
}
discover_helm_charts() {
local dirs=()
for repo in "${ECOSYSTEM_REPOS[@]}"; do
local repo_dir="${WORKSPACE_ROOT}/${repo}"
[ -d "$repo_dir" ] || continue
while IFS= read -r -d '' f; do
dirs+=("$(dirname "$f")")
done < <(find "$repo_dir" -maxdepth 4 -name 'Chart.yaml' \
-not -path '*/node_modules/*' \
-not -path '*/.git/*' \
-print0 2>/dev/null | sort -z)
done
echo "${dirs[@]+"${dirs[@]}"}"
}
# ── Lint functions ─────────────────────────────────────────────────────
lint_dockerfile() {
local file="$1"
local display
display=$(short_path "$file")
if [ ! -f "$file" ]; then
skip "${display} (not found)"
return
fi
local output exit_code=0
# Build hadolint args
local hadolint_args=(--no-color)
IFS=',' read -ra ignore_rules <<< "$HADOLINT_IGNORE"
for rule in "${ignore_rules[@]}"; do
hadolint_args+=(--ignore "$rule")
done
output=$(hadolint "${hadolint_args[@]}" "$file" 2>&1) || exit_code=$?
if [ $exit_code -eq 0 ]; then
ok "${display}"
else
fail "${display}"
# Print each warning/error indented
while IFS= read -r line; do
if [[ "$line" == *"error"* ]]; then
echo -e " ${RED}${line}${NC}"
elif [[ "$line" == *"warning"* ]]; then
echo -e " ${YELLOW}${line}${NC}"
else
echo -e " ${DIM}${line}${NC}"
fi
done <<< "$output"
if $SHOW_FIX; then
echo -e " ${CYAN}Fix: review hadolint wiki — https://github.com/hadolint/hadolint/wiki${NC}"
fi
fi
}
lint_helm_chart() {
local chart_dir="$1"
local display
display=$(short_path "$chart_dir")
if [ ! -f "${chart_dir}/Chart.yaml" ]; then
skip "${display} (no Chart.yaml)"
return
fi
local output exit_code
# 1. helm lint (syntax + structure)
output=$(helm lint "$chart_dir" 2>&1) || exit_code=$?
if [ "${exit_code:-0}" -ne 0 ]; then
fail "${display} (helm lint)"
echo "$output" | while IFS= read -r line; do
echo -e " ${DIM}${line}${NC}"
done
return
fi
# 2. helm template (renders without errors)
output=$(helm template test-release "$chart_dir" 2>&1) || exit_code=$?
if [ "${exit_code:-0}" -ne 0 ]; then
fail "${display} (helm template)"
echo "$output" | tail -10 | while IFS= read -r line; do
echo -e " ${DIM}${line}${NC}"
done
return
fi
ok "${display}"
}
# ── Parse CLI ──────────────────────────────────────────────────────────
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
--docker)
LINT_DOCKER=true; LINT_HELM=false ;;
--helm)
LINT_DOCKER=false; LINT_HELM=true ;;
--fix)
SHOW_FIX=true ;;
-h|--help)
usage; exit 0 ;;
-*)
echo "Unknown option: $1"; usage; exit 1 ;;
*)
EXPLICIT_PATHS+=("$1") ;;
esac
shift
done
}
# ── Main ───────────────────────────────────────────────────────────────
main() {
parse_args "$@"
echo ""
echo -e "${BOLD}╔═══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ ByteLyst Infrastructure Lint ║${NC}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════════════╝${NC}"
echo ""
# ── Tool checks ────────────────────────────────────────────────────
local missing=false
if $LINT_DOCKER; then
require_tool hadolint "brew install hadolint" || missing=true
fi
if $LINT_HELM; then
require_tool helm "brew install helm" || missing=true
fi
if $missing; then
echo -e "${RED}Missing required tools. Install them and re-run.${NC}"
exit 1
fi
# ── Explicit paths mode ────────────────────────────────────────────
if [ ${#EXPLICIT_PATHS[@]} -gt 0 ]; then
log "Linting ${#EXPLICIT_PATHS[@]} explicit path(s)..."
echo ""
for p in "${EXPLICIT_PATHS[@]}"; do
if [[ "$(basename "$p")" == Dockerfile* ]]; then
lint_dockerfile "$p"
elif [ -f "${p}/Chart.yaml" ]; then
lint_helm_chart "$p"
else
warn "Unknown target: $p (expected Dockerfile* or dir with Chart.yaml)"
fi
done
else
# ── Auto-discover mode ─────────────────────────────────────────
if $LINT_DOCKER; then
log "Discovering Dockerfiles across ${#ECOSYSTEM_REPOS[@]} repos..."
echo ""
local dockerfiles
IFS=' ' read -ra dockerfiles <<< "$(discover_dockerfiles)"
if [ ${#dockerfiles[@]} -eq 0 ]; then
warn "No Dockerfiles found."
else
log "Found ${#dockerfiles[@]} Dockerfile(s)"
echo ""
for df in "${dockerfiles[@]}"; do
lint_dockerfile "$df"
done
fi
echo ""
fi
if $LINT_HELM; then
log "Discovering Helm charts across ${#ECOSYSTEM_REPOS[@]} repos..."
echo ""
local charts
IFS=' ' read -ra charts <<< "$(discover_helm_charts)"
if [ ${#charts[@]} -eq 0 ] || [ -z "${charts[0]:-}" ]; then
log "No Helm charts found (none in this workspace)."
else
log "Found ${#charts[@]} Helm chart(s)"
echo ""
for chart in "${charts[@]}"; do
lint_helm_chart "$chart"
done
fi
echo ""
fi
fi
# ── Summary ──────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}═══ Summary ═══${NC}"
echo -e " ${GREEN}Passed:${NC} ${PASS}"
echo -e " ${RED}Failed:${NC} ${FAIL}"
if [ $SKIP -gt 0 ]; then
echo -e " ${DIM}Skipped: ${SKIP}${NC}"
fi
echo ""
if [ $FAIL -gt 0 ]; then
echo -e "${RED}${BOLD}FAILED${NC}${FAIL} issue(s) found:"
for f in "${FAILURES[@]}"; do
echo -e " ${RED}${NC} ${f}"
done
echo ""
echo -e "${DIM}Suppressed rules: ${HADOLINT_IGNORE}${NC}"
echo -e "${DIM}To see fix suggestions, re-run with --fix${NC}"
exit 1
else
echo -e "${GREEN}${BOLD}ALL PASSED${NC}${PASS} check(s) clean."
if [ $SKIP -gt 0 ]; then
echo -e "${DIM}(${SKIP} skipped)${NC}"
fi
exit 0
fi
}
main "$@"

95
scripts/test-all.sh Executable file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# test-all.sh — Run Vitest across all ByteLyst repos
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'
BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PASS=0
FAIL=0
SKIP=0
FAILURES=()
REPOS=(
learning_ai_common_plat
learning_voice_ai_agent
learning_multimodal_memory_agents
learning_ai_clock
learning_ai_jarvis_jr
learning_ai_fastgap
learning_ai_peakpulse
learning_ai_flowmonk
learning_ai_notes
learning_ai_trails
learning_ai_local_memory_gpt
)
SUBS=(backend web user-dashboard-web mindlyst-native/web)
echo ""
echo -e "${BOLD}╔═══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ ByteLyst Cross-Repo Test Runner ║${NC}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════════════╝${NC}"
echo ""
for repo in "${REPOS[@]}"; do
repo_dir="${WORKSPACE_ROOT}/${repo}"
[ -d "$repo_dir" ] || continue
for sub in "${SUBS[@]}"; do
pkg="${repo_dir}/${sub}/package.json"
[ -f "$pkg" ] || continue
has_test=$(jq -r '.scripts.test // empty' "$pkg" 2>/dev/null)
[ -n "$has_test" ] || continue
# Check if there are actually test files
test_count=$(find "${repo_dir}/${sub}/src" -name '*.test.ts' -o -name '*.test.tsx' 2>/dev/null | head -1)
if [ -z "$test_count" ]; then
SKIP=$((SKIP + 1))
continue
fi
label="${repo}/${sub}"
echo -ne " ${CYAN}${NC} ${label}..."
if (cd "${repo_dir}/${sub}" && pnpm run test --run 2>&1) > /tmp/test-output-$$.txt 2>&1; then
# Extract test count from vitest output
summary=$(grep -E 'Tests\s+[0-9]' /tmp/test-output-$$.txt 2>/dev/null | tail -1 || echo "")
echo -e "\r ${GREEN}${NC} ${label} ${DIM}${summary}${NC}"
PASS=$((PASS + 1))
else
echo -e "\r ${RED}${NC} ${label}"
grep -E 'FAIL|Error|failed' /tmp/test-output-$$.txt 2>/dev/null | tail -5 | while IFS= read -r line; do echo " ${line}"; done
FAIL=$((FAIL + 1))
FAILURES+=("$label")
fi
rm -f /tmp/test-output-$$.txt
done
done
echo ""
echo -e "${BOLD}═══ Summary ═══${NC}"
echo -e " ${GREEN}Passed:${NC} ${PASS}"
echo -e " ${RED}Failed:${NC} ${FAIL}"
if [ $SKIP -gt 0 ]; then
echo -e " ${DIM}Skipped: ${SKIP} (no test files)${NC}"
fi
if [ $FAIL -gt 0 ]; then
echo ""
echo -e "${RED}${BOLD}FAILED${NC}${FAIL} test suite(s) failed:"
for f in "${FAILURES[@]}"; do
echo -e " ${RED}${NC} ${f}"
done
exit 1
else
echo ""
echo -e "${GREEN}${BOLD}ALL PASSED${NC}${PASS} suite(s) green."
exit 0
fi

83
scripts/typecheck-all.sh Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════
# typecheck-all.sh — Run TypeScript typecheck across all ByteLyst repos
# ═══════════════════════════════════════════════════════════════════════
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'
BOLD='\033[1m'; DIM='\033[2m'; NC='\033[0m'
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PASS=0
FAIL=0
FAILURES=()
REPOS=(
learning_ai_common_plat
learning_voice_ai_agent
learning_multimodal_memory_agents
learning_ai_clock
learning_ai_jarvis_jr
learning_ai_fastgap
learning_ai_peakpulse
learning_ai_flowmonk
learning_ai_notes
learning_ai_trails
learning_ai_local_memory_gpt
)
# Subdirectories that may have their own typecheck script
SUBS=(backend web user-dashboard-web mindlyst-native/web)
echo ""
echo -e "${BOLD}╔═══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ ByteLyst Cross-Repo Typecheck ║${NC}"
echo -e "${BOLD}╚═══════════════════════════════════════════════════════════╝${NC}"
echo ""
for repo in "${REPOS[@]}"; do
repo_dir="${WORKSPACE_ROOT}/${repo}"
[ -d "$repo_dir" ] || continue
for sub in "${SUBS[@]}"; do
pkg="${repo_dir}/${sub}/package.json"
[ -f "$pkg" ] || continue
has_tc=$(jq -r '.scripts.typecheck // empty' "$pkg" 2>/dev/null)
[ -n "$has_tc" ] || continue
label="${repo}/${sub}"
echo -ne " ${CYAN}${NC} ${label}..."
if (cd "${repo_dir}/${sub}" && pnpm run typecheck 2>&1) > /tmp/tc-output-$$.txt 2>&1; then
echo -e "\r ${GREEN}${NC} ${label}"
PASS=$((PASS + 1))
else
echo -e "\r ${RED}${NC} ${label}"
tail -5 /tmp/tc-output-$$.txt | while IFS= read -r line; do echo " ${line}"; done
FAIL=$((FAIL + 1))
FAILURES+=("$label")
fi
rm -f /tmp/tc-output-$$.txt
done
done
echo ""
echo -e "${BOLD}═══ Summary ═══${NC}"
echo -e " ${GREEN}Passed:${NC} ${PASS}"
echo -e " ${RED}Failed:${NC} ${FAIL}"
if [ $FAIL -gt 0 ]; then
echo ""
echo -e "${RED}${BOLD}FAILED${NC}${FAIL} typecheck(s) failed:"
for f in "${FAILURES[@]}"; do
echo -e " ${RED}${NC} ${f}"
done
exit 1
else
echo ""
echo -e "${GREEN}${BOLD}ALL PASSED${NC}${PASS} typecheck(s) clean."
exit 0
fi