diff --git a/.github/workflows/reusable-quality.yml b/.github/workflows/reusable-quality.yml index 8566a01..bb832c8 100644 --- a/.github/workflows/reusable-quality.yml +++ b/.github/workflows/reusable-quality.yml @@ -8,6 +8,11 @@ on: required: false default: 0 type: number + run-hygiene-check: + description: Run generic repository hygiene checks. + required: false + default: true + type: boolean setup-node: description: Enable Node.js setup. required: false @@ -60,6 +65,11 @@ jobs: with: fetch-depth: ${{ inputs.checkout-fetch-depth }} + - name: Repository hygiene + if: ${{ inputs.run-hygiene-check }} + shell: bash + run: bash scripts/hygiene.sh + - name: Set up Node.js if: ${{ inputs.setup-node }} uses: actions/setup-node@v4 diff --git a/docs/DEVEX.md b/docs/DEVEX.md index 9e1f1a6..fea815b 100644 --- a/docs/DEVEX.md +++ b/docs/DEVEX.md @@ -44,6 +44,21 @@ Template repositories should support these task names when applicable: - `lint` for static analysis - `test` for automated tests +Generic validation is always available through: + +```bash +bash scripts/hygiene.sh +``` + +The hygiene check is stack-agnostic and currently verifies: + +- required template files are present +- shell scripts parse successfully +- unresolved merge markers are not present +- Markdown files start with a top-level heading + +`bash scripts/validate.sh` runs the generic hygiene checks first, then runs project-specific package commands when a supported project setup is present. + The reusable GitHub Actions workflow can call custom commands for each project. Local validation should stay simple and safe to run repeatedly. ## Debugging workflow diff --git a/scripts/hygiene.sh b/scripts/hygiene.sh new file mode 100644 index 0000000..21909fb --- /dev/null +++ b/scripts/hygiene.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +failures=0 + +report_failure() { + echo "hygiene: $1" >&2 + failures=$((failures + 1)) +} + +check_required_files() { + local required_files=( + README.md + LICENSE + CONTRIBUTING.md + SECURITY.md + CODE_OF_CONDUCT.md + CHANGELOG.md + .editorconfig + .gitattributes + .gitignore + docs/ARCHITECTURE.md + docs/DEVEX.md + docs/REPOSITORY_STANDARDS.md + config/.env.example + ) + + for file in "${required_files[@]}"; do + if [[ ! -f "$file" ]]; then + report_failure "missing required file: $file" + fi + done +} + +check_shell_syntax() { + local script + while IFS= read -r -d '' script; do + bash -n "$script" || report_failure "shell syntax failed: $script" + done < <(find . -type f -name '*.sh' -not -path './.git/*' -print0) +} + +check_merge_markers() { + local left middle right + left="$(printf '<%.0s' {1..7})" + middle="$(printf '=%.0s' {1..7})" + right="$(printf '>%.0s' {1..7})" + + if git grep -n -e "$left" -e "$middle" -e "$right" -- . ':!.git' >/tmp/hygiene-markers.txt; then + cat /tmp/hygiene-markers.txt >&2 + report_failure "possible unresolved merge markers found" + fi +} + +check_markdown_headings() { + local file first_line + while IFS= read -r -d '' file; do + first_line="$(head -n 1 "$file")" + if [[ "$first_line" != '#'* && "$first_line" != '---' ]]; then + report_failure "markdown file should start with a heading or front matter: $file" + fi + done < <(find . -type f -name '*.md' -not -path './.git/*' -print0) +} + +check_required_files +check_shell_syntax +check_merge_markers +check_markdown_headings + +if [[ "$failures" -gt 0 ]]; then + echo "Repository hygiene checks failed: $failures issue(s)." >&2 + exit 1 +fi + +echo "Repository hygiene checks passed." diff --git a/scripts/validate.sh b/scripts/validate.sh index ff38f62..05189b0 100644 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail +bash scripts/hygiene.sh + if [[ -f package.json ]]; then if command -v npm >/dev/null 2>&1; then npm run format:check --if-present