Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/reusable-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions docs/DEVEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions scripts/hygiene.sh
Original file line number Diff line number Diff line change
@@ -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."
2 changes: 2 additions & 0 deletions scripts/validate.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading