forked from trent_larson/crowd-funder-for-time-pwa
feat: implement Build Architecture Guard with Husky hooks
- Add pre-commit and pre-push hooks for build file protection - Create comprehensive guard script for BUILDING.md validation - Add npm scripts for guard setup and testing - Integrate with existing build system
This commit is contained in:
40
.husky/_/husky.sh
Executable file
40
.husky/_/husky.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# Husky Helper Script
|
||||
# This file is sourced by all Husky hooks
|
||||
#
|
||||
if [ -z "$husky_skip_init" ]; then
|
||||
debug () {
|
||||
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||
echo "husky (debug) - $1"
|
||||
fi
|
||||
}
|
||||
|
||||
readonly hook_name="$(basename -- "$0")"
|
||||
debug "starting $hook_name..."
|
||||
|
||||
if [ "$HUSKY" = "0" ]; then
|
||||
debug "HUSKY env variable is set to 0, skipping hook"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -f ~/.huskyrc ]; then
|
||||
debug "sourcing ~/.huskyrc"
|
||||
. ~/.huskyrc
|
||||
fi
|
||||
|
||||
readonly husky_skip_init=1
|
||||
export husky_skip_init
|
||||
sh -e "$0" "$@"
|
||||
exitCode="$?"
|
||||
|
||||
if [ $exitCode != 0 ]; then
|
||||
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||
fi
|
||||
|
||||
if [ $exitCode = 127 ]; then
|
||||
echo "husky - command not found in PATH=$PATH"
|
||||
fi
|
||||
|
||||
exit $exitCode
|
||||
fi
|
||||
10
.husky/commit-msg
Executable file
10
.husky/commit-msg
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Husky Commit Message Hook
|
||||
# Validates commit message format using commitlint
|
||||
#
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run commitlint but don't fail the commit (|| true)
|
||||
# This provides helpful feedback without blocking commits
|
||||
npx commitlint --edit "$1" || true
|
||||
15
.husky/pre-commit
Executable file
15
.husky/pre-commit
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Husky Pre-commit Hook
|
||||
# Runs Build Architecture Guard to check staged files
|
||||
#
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
echo "🔍 Running Build Architecture Guard (pre-commit)..."
|
||||
bash ./scripts/build-arch-guard.sh --staged || {
|
||||
echo
|
||||
echo "💡 To bypass this check for emergency commits, use:"
|
||||
echo " git commit --no-verify"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
27
.husky/pre-push
Executable file
27
.husky/pre-push
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Husky Pre-push Hook
|
||||
# Runs Build Architecture Guard to check commits being pushed
|
||||
#
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
echo "🔍 Running Build Architecture Guard (pre-push)..."
|
||||
|
||||
# Get the remote branch we're pushing to
|
||||
REMOTE_BRANCH="origin/$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
# Check if remote branch exists
|
||||
if git show-ref --verify --quiet "refs/remotes/$REMOTE_BRANCH"; then
|
||||
RANGE="$REMOTE_BRANCH...HEAD"
|
||||
else
|
||||
# If remote branch doesn't exist, check last commit
|
||||
RANGE="HEAD~1..HEAD"
|
||||
fi
|
||||
|
||||
bash ./scripts/build-arch-guard.sh --range "$RANGE" || {
|
||||
echo
|
||||
echo "💡 To bypass this check for emergency pushes, use:"
|
||||
echo " git push --no-verify"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
17
package.json
17
package.json
@@ -98,6 +98,13 @@
|
||||
"build:electron:dmg:dev": "./scripts/build-electron.sh --dev --dmg",
|
||||
"build:electron:dmg:test": "./scripts/build-electron.sh --test --dmg",
|
||||
"build:electron:dmg:prod": "./scripts/build-electron.sh --prod --dmg",
|
||||
"markdown:fix": "./scripts/fix-markdown.sh",
|
||||
"markdown:check": "./scripts/validate-markdown.sh",
|
||||
"markdown:setup": "./scripts/setup-markdown-hooks.sh",
|
||||
"prepare": "husky",
|
||||
"guard": "bash ./scripts/build-arch-guard.sh",
|
||||
"guard:test": "bash ./scripts/build-arch-guard.sh --staged",
|
||||
"guard:setup": "npm run prepare && echo '✅ Build Architecture Guard is now active!'",
|
||||
"clean:android": "./scripts/clean-android.sh",
|
||||
"clean:ios": "rm -rf ios/App/build ios/App/Pods ios/App/output ios/App/App/public ios/DerivedData ios/capacitor-cordova-ios-plugins ios/App/App/capacitor.config.json ios/App/App/config.xml || true",
|
||||
"clean:electron": "./scripts/build-electron.sh --clean",
|
||||
@@ -124,6 +131,12 @@
|
||||
"build:android:dev:run:custom": "./scripts/build-android.sh --dev --api-ip --auto-run",
|
||||
"build:android:test:run:custom": "./scripts/build-android.sh --test --api-ip --auto-run"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,vue,css,md,json,yml,yaml}": "eslint --fix || true"
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor-community/electron": "^5.0.1",
|
||||
"@capacitor-community/sqlite": "6.0.2",
|
||||
@@ -243,6 +256,10 @@
|
||||
"jest": "^30.0.4",
|
||||
"markdownlint": "^0.37.4",
|
||||
"markdownlint-cli": "^0.44.0",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"@commitlint/cli": "^18.6.1",
|
||||
"@commitlint/config-conventional": "^18.6.2",
|
||||
"npm-check-updates": "^17.1.13",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.38",
|
||||
|
||||
187
scripts/build-arch-guard.sh
Executable file
187
scripts/build-arch-guard.sh
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Build Architecture Guard Script
|
||||
#
|
||||
# Author: Matthew Raymer
|
||||
# Date: 2025-08-20
|
||||
# Purpose: Protects build-critical files by requiring BUILDING.md updates
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build-arch-guard.sh --staged # Check staged files (pre-commit)
|
||||
# ./scripts/build-arch-guard.sh --range # Check range (pre-push)
|
||||
# ./scripts/build-arch-guard.sh # Check working directory
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Sensitive paths that require BUILDING.md updates when modified
|
||||
SENSITIVE=(
|
||||
"vite.config.*"
|
||||
"scripts/**"
|
||||
"electron/**"
|
||||
"android/**"
|
||||
"ios/**"
|
||||
"sw_scripts/**"
|
||||
"sw_combine.js"
|
||||
"Dockerfile"
|
||||
"docker/**"
|
||||
"capacitor.config.ts"
|
||||
"package.json"
|
||||
"package-lock.json"
|
||||
"yarn.lock"
|
||||
"pnpm-lock.yaml"
|
||||
)
|
||||
|
||||
# Documentation files that must be updated alongside sensitive changes
|
||||
DOCS_REQUIRED=("BUILDING.md")
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[guard]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[guard]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[guard]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[guard]${NC} $1"
|
||||
}
|
||||
|
||||
# Collect files based on mode
|
||||
collect_files() {
|
||||
if [[ "${1:-}" == "--staged" ]]; then
|
||||
# Pre-commit: check staged files
|
||||
git diff --name-only --cached
|
||||
elif [[ "${1:-}" == "--range" ]]; then
|
||||
# Pre-push: check commits being pushed
|
||||
RANGE="${2:-HEAD~1..HEAD}"
|
||||
git diff --name-only "$RANGE"
|
||||
else
|
||||
# Default: check working directory changes
|
||||
git diff --name-only HEAD
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if a file matches any sensitive pattern
|
||||
matches_sensitive() {
|
||||
local f="$1"
|
||||
for pat in "${SENSITIVE[@]}"; do
|
||||
# Convert glob pattern to regex
|
||||
local rx="^${pat//\./\.}$"
|
||||
rx="${rx//\*\*/.*}"
|
||||
rx="${rx//\*/[^/]*}"
|
||||
|
||||
if [[ "$f" =~ $rx ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check if documentation was updated
|
||||
check_docs_updated() {
|
||||
local changed_files=("$@")
|
||||
|
||||
for changed_file in "${changed_files[@]}"; do
|
||||
for required_doc in "${DOCS_REQUIRED[@]}"; do
|
||||
if [[ "$changed_file" == "$required_doc" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Main guard logic
|
||||
main() {
|
||||
local mode="${1:-}"
|
||||
local arg="${2:-}"
|
||||
|
||||
log_info "Running Build Architecture Guard..."
|
||||
|
||||
# Collect changed files
|
||||
mapfile -t changed_files < <(collect_files "$mode" "$arg")
|
||||
|
||||
if [[ ${#changed_files[@]} -eq 0 ]]; then
|
||||
log_info "No files changed, guard check passed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info "Checking ${#changed_files[@]} changed files..."
|
||||
|
||||
# Find sensitive files that were touched
|
||||
sensitive_touched=()
|
||||
for file in "${changed_files[@]}"; do
|
||||
if matches_sensitive "$file"; then
|
||||
sensitive_touched+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
# If no sensitive files were touched, allow the change
|
||||
if [[ ${#sensitive_touched[@]} -eq 0 ]]; then
|
||||
log_success "No build-sensitive files changed, guard check passed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sensitive files were touched, log them
|
||||
log_warn "Build-sensitive paths changed:"
|
||||
for file in "${sensitive_touched[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
|
||||
# Check if required documentation was updated
|
||||
if check_docs_updated "${changed_files[@]}"; then
|
||||
log_success "BUILDING.md updated alongside build changes, guard check passed"
|
||||
exit 0
|
||||
else
|
||||
log_error "Build-sensitive files changed but BUILDING.md was not updated!"
|
||||
echo
|
||||
echo "The following build-sensitive files were modified:"
|
||||
for file in "${sensitive_touched[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
echo
|
||||
echo "When modifying build-critical files, you must also update BUILDING.md"
|
||||
echo "to document any changes to the build process."
|
||||
echo
|
||||
echo "Please:"
|
||||
echo " 1. Update BUILDING.md with relevant changes"
|
||||
echo " 2. Stage the BUILDING.md changes: git add BUILDING.md"
|
||||
echo " 3. Retry your commit/push"
|
||||
echo
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
# Handle help flag
|
||||
if [[ "${1:-}" =~ ^(-h|--help)$ ]]; then
|
||||
echo "Build Architecture Guard Script"
|
||||
echo
|
||||
echo "Usage:"
|
||||
echo " $0 [--staged|--range [RANGE]]"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --staged Check staged files (for pre-commit hook)"
|
||||
echo " --range [RANGE] Check git range (for pre-push hook)"
|
||||
echo " Default range: HEAD~1..HEAD"
|
||||
echo " (no args) Check working directory changes"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 --staged # Pre-commit check"
|
||||
echo " $0 --range origin/main..HEAD # Pre-push check"
|
||||
echo " $0 # Working directory check"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user