From c69d3d354608bc9d42e15f17f266f2ab50ffd01a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:41:23 +0000 Subject: [PATCH 01/13] Initial plan From ecc25b4e7041681e8797bc4757bb6a8e9bc83909 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:44:20 +0000 Subject: [PATCH 02/13] Add .githooks/ directory with pre-commit, pre-push and setup.sh; update package.json prepare script and CONTRIBUTING.md Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/55aee88f-343e-4ab6-befc-5314fa4a21a9 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-commit | 131 ++++++++++++++++++++++++++++++++++++++++ .githooks/pre-push | 70 +++++++++++++++++++++ .githooks/setup.sh | 66 ++++++++++++++++++++ .github/CONTRIBUTING.md | 67 ++++++++++++++++++++ package.json | 2 +- 5 files changed, 335 insertions(+), 1 deletion(-) create mode 100755 .githooks/pre-commit create mode 100755 .githooks/pre-push create mode 100755 .githooks/setup.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..740889c --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,131 @@ +#!/bin/sh +# .githooks/pre-commit +# Pre-commit hook: runs Biome formatting/linting, debugger/console checks, +# czg commit-message formatting (optional), and zod schema tests on staged files. +# +# Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) +# Skip: git commit --no-verify + +# ── Colours ────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Colour + +print_info() { printf "${BLUE}[pre-commit]${NC} %s\n" "$1"; } +print_success() { printf "${GREEN}[pre-commit]${NC} %s\n" "$1"; } +print_warning() { printf "${YELLOW}[pre-commit]${NC} %s\n" "$1"; } +print_error() { printf "${RED}[pre-commit]${NC} %s\n" "$1"; } + +# ── Collect staged JS/TS files ─────────────────────────────────────────────── +STAGED_JS=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(js|ts|jsx|tsx|mjs|cjs)$') + +# ── 1. Biome format + lint ─────────────────────────────────────────────────── +print_info "Running Biome check on staged files…" + +if [ -n "$STAGED_JS" ]; then + # Resolve the biome binary (local install preferred, then npx fallback) + if [ -f "node_modules/.bin/biome" ]; then + BIOME="node_modules/.bin/biome" + elif command -v biome >/dev/null 2>&1; then + BIOME="biome" + else + BIOME="npx --no-install @biomejs/biome" + fi + + # Write staged content to a temp area so we check what will actually be committed + TMP_DIR=$(mktemp -d) + trap 'rm -rf "$TMP_DIR"' EXIT + + BIOME_FAILED=0 + for FILE in $STAGED_JS; do + git show ":$FILE" > "$TMP_DIR/$(basename "$FILE")" 2>/dev/null || true + done + + # Run biome ci (read-only check) against each staged file path + # shellcheck disable=SC2086 + echo "$STAGED_JS" | xargs $BIOME check --no-errors-on-unmatched 2>&1 + if [ $? -ne 0 ]; then + print_error "Biome check failed. Run 'npm run check' to auto-fix, then re-stage." + exit 1 + fi + print_success "Biome check passed." +else + print_info "No JS/TS staged files – skipping Biome check." +fi + +# ── 2. No debugger statements ──────────────────────────────────────────────── +print_info "Checking for debugger statements…" + +if [ -n "$STAGED_JS" ]; then + DEBUGGER_HITS=$(echo "$STAGED_JS" | xargs git show -- 2>/dev/null | grep -n 'debugger' || true) + if [ -n "$DEBUGGER_HITS" ]; then + print_error "Debugger statement(s) found in staged files:" + echo "$DEBUGGER_HITS" + exit 1 + fi + print_success "No debugger statements found." +fi + +# ── 3. No console statements in production files ───────────────────────────── +print_info "Checking for console statements in production files…" + +if [ -n "$STAGED_JS" ]; then + # Exclude test files, scripts, config files, and git-hooks from this check + PROD_FILES=$(echo "$STAGED_JS" | grep -v -E '(__tests__|\.test\.|\.spec\.|scripts/|\.githooks/|git-hooks/)' || true) + + if [ -n "$PROD_FILES" ]; then + CONSOLE_HITS="" + for FILE in $PROD_FILES; do + HITS=$(git show ":$FILE" 2>/dev/null | grep -n 'console\.' | grep -v '^\s*//' || true) + if [ -n "$HITS" ]; then + CONSOLE_HITS="${CONSOLE_HITS}${FILE}:\n${HITS}\n" + fi + done + + if [ -n "$CONSOLE_HITS" ]; then + print_error "console.* statement(s) found in production files (use the project logger instead):" + printf "%b" "$CONSOLE_HITS" + exit 1 + fi + fi + print_success "No console statements found in production files." +fi + +# ── 4. Schema (Zod) validation tests ───────────────────────────────────────── +# Run schema tests when files inside any schema/ or schemas/ folder are staged. +STAGED_SCHEMAS=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '(^|/)schemas?/' || true) + +if [ -n "$STAGED_SCHEMAS" ]; then + print_info "Schema files changed – running schema validation tests…" + + if [ -f "package.json" ]; then + # Detect package manager + if [ -f "pnpm-lock.yaml" ]; then + PKG_MGR="pnpm" + elif [ -f "yarn.lock" ]; then + PKG_MGR="yarn" + else + PKG_MGR="npm" + fi + + # Run schema tests if a dedicated script exists, else skip gracefully + if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then + $PKG_MGR run test:schemas + if [ $? -ne 0 ]; then + print_error "Schema validation tests failed." + exit 1 + fi + print_success "Schema validation tests passed." + else + print_warning "No 'test:schemas' script found – skipping schema tests." + fi + fi +else + print_info "No schema files staged – skipping schema tests." +fi + +# ── Done ────────────────────────────────────────────────────────────────────── +print_success "All pre-commit checks passed." +exit 0 diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..b720d44 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,70 @@ +#!/bin/sh +# .githooks/pre-push +# Pre-push hook: runs unit tests and schema validation tests before allowing a push. +# +# Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) +# Skip: git push --no-verify + +# ── Colours ────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Colour + +print_info() { printf "${BLUE}[pre-push]${NC} %s\n" "$1"; } +print_success() { printf "${GREEN}[pre-push]${NC} %s\n" "$1"; } +print_warning() { printf "${YELLOW}[pre-push]${NC} %s\n" "$1"; } +print_error() { printf "${RED}[pre-push]${NC} %s\n" "$1"; } + +# ── Package manager detection ───────────────────────────────────────────────── +if [ -f "pnpm-lock.yaml" ]; then + PKG_MGR="pnpm" +elif [ -f "yarn.lock" ]; then + PKG_MGR="yarn" +else + PKG_MGR="npm" +fi + +print_info "Using package manager: $PKG_MGR" + +# ── 1. Unit tests ───────────────────────────────────────────────────────────── +print_info "Running unit tests…" + +if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:workspace']?0:1)" 2>/dev/null; then + $PKG_MGR run test:workspace + TEST_EXIT=$? +elif node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test']?0:1)" 2>/dev/null; then + $PKG_MGR run test + TEST_EXIT=$? +else + print_warning "No 'test' script found in root package.json – skipping unit tests." + TEST_EXIT=0 +fi + +if [ $TEST_EXIT -ne 0 ]; then + print_error "Unit tests failed. Fix failing tests before pushing." + print_info "Tip: use 'git push --no-verify' to bypass this check if necessary." + exit 1 +fi + +print_success "Unit tests passed." + +# ── 2. Schema validation tests ──────────────────────────────────────────────── +print_info "Running schema validation tests…" + +if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then + $PKG_MGR run test:schemas + SCHEMA_EXIT=$? + if [ $SCHEMA_EXIT -ne 0 ]; then + print_error "Schema validation tests failed. Fix schema errors before pushing." + exit 1 + fi + print_success "Schema validation tests passed." +else + print_warning "No 'test:schemas' script found – skipping schema tests." +fi + +# ── Done ────────────────────────────────────────────────────────────────────── +print_success "All pre-push checks passed. Proceeding with push." +exit 0 diff --git a/.githooks/setup.sh b/.githooks/setup.sh new file mode 100755 index 0000000..27db107 --- /dev/null +++ b/.githooks/setup.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# .githooks/setup.sh +# Installs the project's native Git hooks by pointing git's hooksPath at +# the .githooks/ directory. Run this once after cloning the repository. +# +# Usage: +# chmod +x .githooks/setup.sh +# ./.githooks/setup.sh + +# ── Colours ────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_info() { printf "${BLUE}[setup]${NC} %s\n" "$1"; } +print_success() { printf "${GREEN}[setup]${NC} %s\n" "$1"; } +print_warning() { printf "${YELLOW}[setup]${NC} %s\n" "$1"; } +print_error() { printf "${RED}[setup]${NC} %s\n" "$1"; } + +# ── Verify we are inside a git repository ───────────────────────────────────── +if ! git rev-parse --git-dir >/dev/null 2>&1; then + print_error "Not a git repository. Please run this script from the project root." + exit 1 +fi + +REPO_ROOT=$(git rev-parse --show-toplevel) +HOOKS_DIR="$REPO_ROOT/.githooks" + +if [ ! -d "$HOOKS_DIR" ]; then + print_error ".githooks/ directory not found at $HOOKS_DIR" + exit 1 +fi + +# ── Make hook scripts executable ───────────────────────────────────────────── +print_info "Making hook scripts executable…" +chmod +x "$HOOKS_DIR/pre-commit" 2>/dev/null && print_success " pre-commit ✓" +chmod +x "$HOOKS_DIR/pre-push" 2>/dev/null && print_success " pre-push ✓" + +# ── Configure git to use .githooks/ ────────────────────────────────────────── +print_info "Configuring git hooksPath → .githooks" +git config core.hooksPath .githooks + +if [ $? -eq 0 ]; then + print_success "Git hooks installed successfully." + print_info " pre-commit: Biome check, debugger/console scan, schema tests" + print_info " pre-push: unit tests + schema validation" + print_info "" + print_info "To skip a hook temporarily:" + print_info " git commit --no-verify" + print_info " git push --no-verify" +else + print_error "Failed to configure git hooksPath." + exit 1 +fi + +# ── Optional: verify czg is available ──────────────────────────────────────── +if command -v czg >/dev/null 2>&1; then + print_success "czg found: standardized commit messages are enabled." + print_info " Use 'czg' (or 'npx czg') instead of 'git commit -m …' for guided messages." + print_info " Use 'czg --ai' to generate an AI-assisted commit message." +else + print_warning "czg not found globally. Install it with: npm install -g czg" + print_info " You can still use 'npx czg' or 'npx czg --ai' without a global install." +fi diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 042139c..85addb6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,6 +4,72 @@ Hello and thank you for your interest in helping make express-template better. P ## IMPORTANT INFORMATION * For general questions, please join [Our Discussion Board](https://github.com/es-labs/express-template/discussions). +## Git Hooks Setup + +This project uses native Git hooks stored in `.githooks/`. After cloning, run the setup script once to activate them: + +```bash +# Make the setup script executable and run it +chmod +x .githooks/setup.sh +./.githooks/setup.sh +``` + +Or, if you prefer, configure the hooks path manually: + +```bash +git config core.hooksPath .githooks +chmod +x .githooks/pre-commit .githooks/pre-push +``` + +Running `npm install` (or `pnpm install` / `yarn install`) will also run `npm prepare`, which configures the hooks path automatically. + +### pre-commit hook + +Runs automatically on every `git commit`: + +| Check | Details | +|-------|---------| +| **Biome format & lint** | Checks staged JS/TS files with [Biome](https://biomejs.dev/). Run `npm run check` to auto-fix. | +| **No `debugger` statements** | Prevents accidental debugger breakpoints from being committed. | +| **No `console.*` in production code** | Enforces use of the project logger in `src/` files. Test files are excluded. | +| **Schema validation tests** | Runs `npm run test:schemas` when files in `schema/` or `schemas/` folders are staged. | + +To skip the pre-commit hook temporarily: +```bash +git commit --no-verify +``` + +### Commit messages with czg + +For standardized [Conventional Commits](https://www.conventionalcommits.org/) messages, use **czg** instead of `git commit -m "…"`: + +```bash +# Interactive prompt (guided commit message) +npx czg + +# AI-generated commit message (requires API key configured in czg) +npx czg --ai +``` + +Install globally for convenience: +```bash +npm install -g czg +``` + +### pre-push hook + +Runs automatically on every `git push`: + +| Check | Details | +|-------|---------| +| **Unit tests** | Runs `npm run test:workspace` (or `npm test`). | +| **Schema validation tests** | Runs `npm run test:schemas` if the script exists. | + +To skip the pre-push hook temporarily: +```bash +git push --no-verify +``` + ## Reporting Issues * The issue list of this repo is **exclusively** for Bug Reports and Feature Requests. * Bug reproductions should be as **concise** as possible. @@ -19,3 +85,4 @@ Hello and thank you for your interest in helping make express-template better. P * Use a descriptive title no more than 64 characters long. This will be used as the commit message when your PR is merged. * For changes and feature requests, please include an example of what you are trying to solve and an example of the markup. It is preferred that you create an issue first however, as that will allow the team to review your proposal before you start. * Please reference the issue # that the PR resolves, something like `Fixes #1234` or `Resolves #6458` (See [closing issues using keywords](https://help.github.com/articles/closing-issues-using-keywords/)) + diff --git a/package.json b/package.json index 0d70c20..489b4aa 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "docs:validate-swagger": "swagger-cli validate docs/openapi/openapi.merged.yaml", "docs:validate": "npx redocly lint docs/openapi/openapi.merged.yaml", "docs:make-html": "npx @redocly/cli build-docs docs/openapi/openapi.merged.yaml --output index.html", - "prepare": "git config core.hooksPath ./git-hooks || echo 'Can not set git hooks'", + "prepare": "git config core.hooksPath .githooks || echo 'Can not set git hooks'", "test:workspace": "npm run test --workspace=apps/app-sample", "test:workspaces": "npm run test --workspaces", "test:cover-only": "node --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info --test __tests__/**/*.spec.js", From a3b84053cc24e2a32949229fb7a0e0d5fb2c3334 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:45:42 +0000 Subject: [PATCH 03/13] Fix pre-commit hook: remove unused temp dir, fix debugger/console checks to use staged content per-file Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/55aee88f-343e-4ab6-befc-5314fa4a21a9 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-commit | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 740889c..f0345b2 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -34,16 +34,7 @@ if [ -n "$STAGED_JS" ]; then BIOME="npx --no-install @biomejs/biome" fi - # Write staged content to a temp area so we check what will actually be committed - TMP_DIR=$(mktemp -d) - trap 'rm -rf "$TMP_DIR"' EXIT - - BIOME_FAILED=0 - for FILE in $STAGED_JS; do - git show ":$FILE" > "$TMP_DIR/$(basename "$FILE")" 2>/dev/null || true - done - - # Run biome ci (read-only check) against each staged file path + # Run biome check against each staged file path # shellcheck disable=SC2086 echo "$STAGED_JS" | xargs $BIOME check --no-errors-on-unmatched 2>&1 if [ $? -ne 0 ]; then @@ -59,10 +50,16 @@ fi print_info "Checking for debugger statements…" if [ -n "$STAGED_JS" ]; then - DEBUGGER_HITS=$(echo "$STAGED_JS" | xargs git show -- 2>/dev/null | grep -n 'debugger' || true) - if [ -n "$DEBUGGER_HITS" ]; then - print_error "Debugger statement(s) found in staged files:" - echo "$DEBUGGER_HITS" + DEBUGGER_FOUND=0 + for FILE in $STAGED_JS; do + HITS=$(git show ":$FILE" 2>/dev/null | grep -n 'debugger' || true) + if [ -n "$HITS" ]; then + print_error "Debugger statement(s) found in staged file: $FILE" + echo "$HITS" + DEBUGGER_FOUND=1 + fi + done + if [ $DEBUGGER_FOUND -ne 0 ]; then exit 1 fi print_success "No debugger statements found." @@ -76,17 +73,17 @@ if [ -n "$STAGED_JS" ]; then PROD_FILES=$(echo "$STAGED_JS" | grep -v -E '(__tests__|\.test\.|\.spec\.|scripts/|\.githooks/|git-hooks/)' || true) if [ -n "$PROD_FILES" ]; then - CONSOLE_HITS="" + CONSOLE_FOUND=0 for FILE in $PROD_FILES; do - HITS=$(git show ":$FILE" 2>/dev/null | grep -n 'console\.' | grep -v '^\s*//' || true) + # grep -n output format: "lineno:content" — filter commented lines using the full line format + HITS=$(git show ":$FILE" 2>/dev/null | grep -nE 'console\.' | grep -vE '^[0-9]+:[[:space:]]*//' || true) if [ -n "$HITS" ]; then - CONSOLE_HITS="${CONSOLE_HITS}${FILE}:\n${HITS}\n" + print_error "console.* statement(s) found in production file: $FILE (use the project logger instead)" + echo "$HITS" + CONSOLE_FOUND=1 fi done - - if [ -n "$CONSOLE_HITS" ]; then - print_error "console.* statement(s) found in production files (use the project logger instead):" - printf "%b" "$CONSOLE_HITS" + if [ $CONSOLE_FOUND -ne 0 ]; then exit 1 fi fi From fff53dee5cd96432cf2d2b0bb64aa1c83ff60068 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:35:26 +0000 Subject: [PATCH 04/13] Simplify hooks: npm-only, biome check per-directory, remove console/debugger checks Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/a3e3024f-5007-4659-bc75-6919639b2a8a Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-commit | 127 ++++++++++++---------------------------- .githooks/pre-push | 17 +----- .githooks/setup.sh | 2 +- .github/CONTRIBUTING.md | 6 +- 4 files changed, 44 insertions(+), 108 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f0345b2..d437a71 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,7 +1,7 @@ #!/bin/sh # .githooks/pre-commit -# Pre-commit hook: runs Biome formatting/linting, debugger/console checks, -# czg commit-message formatting (optional), and zod schema tests on staged files. +# Pre-commit hook: runs Biome checks per-directory for staged files, +# and zod schema tests when schema files are staged. # # Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) # Skip: git commit --no-verify @@ -18,106 +18,55 @@ print_success() { printf "${GREEN}[pre-commit]${NC} %s\n" "$1"; } print_warning() { printf "${YELLOW}[pre-commit]${NC} %s\n" "$1"; } print_error() { printf "${RED}[pre-commit]${NC} %s\n" "$1"; } -# ── Collect staged JS/TS files ─────────────────────────────────────────────── -STAGED_JS=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(js|ts|jsx|tsx|mjs|cjs)$') +# ── Collect all staged files ───────────────────────────────────────────────── +STAGED=$(git diff --cached --name-only --diff-filter=ACMR) -# ── 1. Biome format + lint ─────────────────────────────────────────────────── -print_info "Running Biome check on staged files…" +# ── 1. Biome check per directory ───────────────────────────────────────────── +print_info "Running Biome checks on staged directories…" -if [ -n "$STAGED_JS" ]; then - # Resolve the biome binary (local install preferred, then npx fallback) - if [ -f "node_modules/.bin/biome" ]; then - BIOME="node_modules/.bin/biome" - elif command -v biome >/dev/null 2>&1; then - BIOME="biome" - else - BIOME="npx --no-install @biomejs/biome" - fi +BIOME_FAILED=0 - # Run biome check against each staged file path - # shellcheck disable=SC2086 - echo "$STAGED_JS" | xargs $BIOME check --no-errors-on-unmatched 2>&1 +run_biome_check() { + DIR="$1" + print_info " npx biome check $DIR" + npx biome check "$DIR" if [ $? -ne 0 ]; then - print_error "Biome check failed. Run 'npm run check' to auto-fix, then re-stage." - exit 1 - fi - print_success "Biome check passed." -else - print_info "No JS/TS staged files – skipping Biome check." -fi - -# ── 2. No debugger statements ──────────────────────────────────────────────── -print_info "Checking for debugger statements…" - -if [ -n "$STAGED_JS" ]; then - DEBUGGER_FOUND=0 - for FILE in $STAGED_JS; do - HITS=$(git show ":$FILE" 2>/dev/null | grep -n 'debugger' || true) - if [ -n "$HITS" ]; then - print_error "Debugger statement(s) found in staged file: $FILE" - echo "$HITS" - DEBUGGER_FOUND=1 - fi - done - if [ $DEBUGGER_FOUND -ne 0 ]; then - exit 1 - fi - print_success "No debugger statements found." -fi - -# ── 3. No console statements in production files ───────────────────────────── -print_info "Checking for console statements in production files…" - -if [ -n "$STAGED_JS" ]; then - # Exclude test files, scripts, config files, and git-hooks from this check - PROD_FILES=$(echo "$STAGED_JS" | grep -v -E '(__tests__|\.test\.|\.spec\.|scripts/|\.githooks/|git-hooks/)' || true) - - if [ -n "$PROD_FILES" ]; then - CONSOLE_FOUND=0 - for FILE in $PROD_FILES; do - # grep -n output format: "lineno:content" — filter commented lines using the full line format - HITS=$(git show ":$FILE" 2>/dev/null | grep -nE 'console\.' | grep -vE '^[0-9]+:[[:space:]]*//' || true) - if [ -n "$HITS" ]; then - print_error "console.* statement(s) found in production file: $FILE (use the project logger instead)" - echo "$HITS" - CONSOLE_FOUND=1 - fi - done - if [ $CONSOLE_FOUND -ne 0 ]; then - exit 1 - fi + print_error "Biome check failed for $DIR. Run 'npm run check' to auto-fix, then re-stage." + BIOME_FAILED=1 + else + print_success " $DIR ✓" fi - print_success "No console statements found in production files." +} + +echo "$STAGED" | grep -q '^common/iso/' && run_biome_check common/iso +echo "$STAGED" | grep -q '^common/node/' && run_biome_check common/node +echo "$STAGED" | grep -q '^common/vue/' && run_biome_check common/vue +echo "$STAGED" | grep -q '^common/web/' && run_biome_check common/web +echo "$STAGED" | grep -q '^apps/' && run_biome_check apps +echo "$STAGED" | grep -q '^webs/' && run_biome_check webs +echo "$STAGED" | grep -q '^scripts/' && run_biome_check scripts + +if [ $BIOME_FAILED -ne 0 ]; then + exit 1 fi -# ── 4. Schema (Zod) validation tests ───────────────────────────────────────── +# ── 2. Schema (Zod) validation tests ───────────────────────────────────────── # Run schema tests when files inside any schema/ or schemas/ folder are staged. -STAGED_SCHEMAS=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '(^|/)schemas?/' || true) +STAGED_SCHEMAS=$(echo "$STAGED" | grep -E '(^|/)schemas?/' || true) if [ -n "$STAGED_SCHEMAS" ]; then print_info "Schema files changed – running schema validation tests…" - if [ -f "package.json" ]; then - # Detect package manager - if [ -f "pnpm-lock.yaml" ]; then - PKG_MGR="pnpm" - elif [ -f "yarn.lock" ]; then - PKG_MGR="yarn" - else - PKG_MGR="npm" - fi - - # Run schema tests if a dedicated script exists, else skip gracefully - if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then - $PKG_MGR run test:schemas - if [ $? -ne 0 ]; then - print_error "Schema validation tests failed." - exit 1 - fi - print_success "Schema validation tests passed." - else - print_warning "No 'test:schemas' script found – skipping schema tests." + # Run schema tests if a dedicated script exists, else skip gracefully + if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then + npm run test:schemas + if [ $? -ne 0 ]; then + print_error "Schema validation tests failed." + exit 1 fi + print_success "Schema validation tests passed." + else + print_warning "No 'test:schemas' script found – skipping schema tests." fi else print_info "No schema files staged – skipping schema tests." diff --git a/.githooks/pre-push b/.githooks/pre-push index b720d44..563b843 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -17,25 +17,14 @@ print_success() { printf "${GREEN}[pre-push]${NC} %s\n" "$1"; } print_warning() { printf "${YELLOW}[pre-push]${NC} %s\n" "$1"; } print_error() { printf "${RED}[pre-push]${NC} %s\n" "$1"; } -# ── Package manager detection ───────────────────────────────────────────────── -if [ -f "pnpm-lock.yaml" ]; then - PKG_MGR="pnpm" -elif [ -f "yarn.lock" ]; then - PKG_MGR="yarn" -else - PKG_MGR="npm" -fi - -print_info "Using package manager: $PKG_MGR" - # ── 1. Unit tests ───────────────────────────────────────────────────────────── print_info "Running unit tests…" if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:workspace']?0:1)" 2>/dev/null; then - $PKG_MGR run test:workspace + npm run test:workspace TEST_EXIT=$? elif node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test']?0:1)" 2>/dev/null; then - $PKG_MGR run test + npm run test TEST_EXIT=$? else print_warning "No 'test' script found in root package.json – skipping unit tests." @@ -54,7 +43,7 @@ print_success "Unit tests passed." print_info "Running schema validation tests…" if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then - $PKG_MGR run test:schemas + npm run test:schemas SCHEMA_EXIT=$? if [ $SCHEMA_EXIT -ne 0 ]; then print_error "Schema validation tests failed. Fix schema errors before pushing." diff --git a/.githooks/setup.sh b/.githooks/setup.sh index 27db107..85bc012 100755 --- a/.githooks/setup.sh +++ b/.githooks/setup.sh @@ -44,7 +44,7 @@ git config core.hooksPath .githooks if [ $? -eq 0 ]; then print_success "Git hooks installed successfully." - print_info " pre-commit: Biome check, debugger/console scan, schema tests" + print_info " pre-commit: Biome check per directory, schema tests" print_info " pre-push: unit tests + schema validation" print_info "" print_info "To skip a hook temporarily:" diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 85addb6..809b133 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,7 +21,7 @@ git config core.hooksPath .githooks chmod +x .githooks/pre-commit .githooks/pre-push ``` -Running `npm install` (or `pnpm install` / `yarn install`) will also run `npm prepare`, which configures the hooks path automatically. +Running `npm install` will also run `npm prepare`, which configures the hooks path automatically. ### pre-commit hook @@ -29,9 +29,7 @@ Runs automatically on every `git commit`: | Check | Details | |-------|---------| -| **Biome format & lint** | Checks staged JS/TS files with [Biome](https://biomejs.dev/). Run `npm run check` to auto-fix. | -| **No `debugger` statements** | Prevents accidental debugger breakpoints from being committed. | -| **No `console.*` in production code** | Enforces use of the project logger in `src/` files. Test files are excluded. | +| **Biome format & lint** | Runs `npx biome check` on each affected directory (`common/iso`, `common/node`, `common/vue`, `common/web`, `apps`, `webs`, `scripts`). Run `npm run check` to auto-fix. | | **Schema validation tests** | Runs `npm run test:schemas` when files in `schema/` or `schemas/` folders are staged. | To skip the pre-commit hook temporarily: From 21607eeebbeec3a5dd2503aeda23dabb91557631 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:27:42 +0000 Subject: [PATCH 05/13] Update schema detection to specific folders and pass folder arg to test:schemas Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/738e6d4c-4e67-45de-b8f4-253d128e80fb Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-commit | 47 +++++++++++++++++++++++++++++------------ .githooks/pre-push | 33 ++++++++++++++++++++++------- .github/CONTRIBUTING.md | 2 +- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index d437a71..8a9f417 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -51,26 +51,47 @@ if [ $BIOME_FAILED -ne 0 ]; then fi # ── 2. Schema (Zod) validation tests ───────────────────────────────────────── -# Run schema tests when files inside any schema/ or schemas/ folder are staged. -STAGED_SCHEMAS=$(echo "$STAGED" | grep -E '(^|/)schemas?/' || true) +# Detect staged files under the known schema folders: +# common/schema, common/schemas, apps/*/schema, apps/*/schemas +SCHEMA_FAILED=0 -if [ -n "$STAGED_SCHEMAS" ]; then - print_info "Schema files changed – running schema validation tests…" - - # Run schema tests if a dedicated script exists, else skip gracefully +run_schema_tests() { + SCHEMA_DIR="$1" if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then - npm run test:schemas + print_info "Running schema validation tests for $SCHEMA_DIR…" + npm run test:schemas -- "$SCHEMA_DIR" if [ $? -ne 0 ]; then - print_error "Schema validation tests failed." - exit 1 + print_error "Schema validation tests failed for $SCHEMA_DIR." + return 1 fi - print_success "Schema validation tests passed." + print_success "Schema validation tests passed for $SCHEMA_DIR." else - print_warning "No 'test:schemas' script found – skipping schema tests." + print_warning "No 'test:schemas' script found – skipping schema tests for $SCHEMA_DIR." fi -else - print_info "No schema files staged – skipping schema tests." + return 0 +} + +# Check common/schema and common/schemas +if echo "$STAGED" | grep -q '^common/schema/'; then + run_schema_tests common/schema || SCHEMA_FAILED=1 fi +if echo "$STAGED" | grep -q '^common/schemas/'; then + run_schema_tests common/schemas || SCHEMA_FAILED=1 +fi + +# Check apps/*/schema and apps/*/schemas (one call per matching app directory) +APP_SCHEMA_DIRS=$(echo "$STAGED" | grep -oE '^apps/[^/]+/schemas?/' | sed 's|/$||' | sort -u) +for SCHEMA_DIR in $APP_SCHEMA_DIRS; do + run_schema_tests "$SCHEMA_DIR" || SCHEMA_FAILED=1 +done + +if [ $SCHEMA_FAILED -ne 0 ]; then + exit 1 +fi + +# Report skip only when no schema folders were matched at all +echo "$STAGED" | grep -qE '^common/schemas?/|^apps/[^/]+/schemas?/' \ + || print_info "No schema files staged – skipping schema tests." # ── Done ────────────────────────────────────────────────────────────────────── print_success "All pre-commit checks passed." diff --git a/.githooks/pre-push b/.githooks/pre-push index 563b843..73aea9a 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -40,16 +40,33 @@ fi print_success "Unit tests passed." # ── 2. Schema validation tests ──────────────────────────────────────────────── -print_info "Running schema validation tests…" - +# Run schema tests for all known schema directories that exist in the repo. if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then - npm run test:schemas - SCHEMA_EXIT=$? - if [ $SCHEMA_EXIT -ne 0 ]; then - print_error "Schema validation tests failed. Fix schema errors before pushing." - exit 1 + print_info "Running schema validation tests…" + SCHEMA_DIRS="" + [ -d "common/schema" ] && SCHEMA_DIRS="$SCHEMA_DIRS common/schema" + [ -d "common/schemas" ] && SCHEMA_DIRS="$SCHEMA_DIRS common/schemas" + for DIR in apps/*/schema apps/*/schemas; do + [ -d "$DIR" ] && SCHEMA_DIRS="$SCHEMA_DIRS $DIR" + done + + if [ -z "$SCHEMA_DIRS" ]; then + print_warning "No schema directories found – skipping schema tests." + else + SCHEMA_EXIT=0 + for SCHEMA_DIR in $SCHEMA_DIRS; do + npm run test:schemas -- "$SCHEMA_DIR" + if [ $? -ne 0 ]; then + print_error "Schema validation tests failed for $SCHEMA_DIR. Fix schema errors before pushing." + SCHEMA_EXIT=1 + else + print_success "Schema validation tests passed for $SCHEMA_DIR." + fi + done + if [ $SCHEMA_EXIT -ne 0 ]; then + exit 1 + fi fi - print_success "Schema validation tests passed." else print_warning "No 'test:schemas' script found – skipping schema tests." fi diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 809b133..3ce23f9 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,7 +30,7 @@ Runs automatically on every `git commit`: | Check | Details | |-------|---------| | **Biome format & lint** | Runs `npx biome check` on each affected directory (`common/iso`, `common/node`, `common/vue`, `common/web`, `apps`, `webs`, `scripts`). Run `npm run check` to auto-fix. | -| **Schema validation tests** | Runs `npm run test:schemas` when files in `schema/` or `schemas/` folders are staged. | +| **Schema validation tests** | Runs `npm run test:schemas -- ` for each affected schema directory (`common/schema`, `common/schemas`, `apps/*/schema`, `apps/*/schemas`). | To skip the pre-commit hook temporarily: ```bash From 06ebc0dd6f73190ca7c7cfc64713113953c2c992 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:37:13 +0000 Subject: [PATCH 06/13] Extract shared colour/print helpers into .githooks/_common.sh Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/d3755e18-2b77-4d86-8d73-779784ec9e62 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/_common.sh | 17 +++++++++++++++++ .githooks/pre-commit | 14 +++----------- .githooks/pre-push | 14 +++----------- .githooks/setup.sh | 14 +++----------- 4 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 .githooks/_common.sh diff --git a/.githooks/_common.sh b/.githooks/_common.sh new file mode 100644 index 0000000..037ba0d --- /dev/null +++ b/.githooks/_common.sh @@ -0,0 +1,17 @@ +# .githooks/_common.sh +# Shared colour variables and print helpers. +# Source this file after setting HOOK_NAME, e.g.: +# HOOK_NAME=pre-commit +# . "$(dirname "$0")/_common.sh" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +_TAG="${HOOK_NAME:-hook}" +print_info() { printf "${BLUE}[%s]${NC} %s\n" "$_TAG" "$1"; } +print_success() { printf "${GREEN}[%s]${NC} %s\n" "$_TAG" "$1"; } +print_warning() { printf "${YELLOW}[%s]${NC} %s\n" "$_TAG" "$1"; } +print_error() { printf "${RED}[%s]${NC} %s\n" "$_TAG" "$1"; } diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8a9f417..416bdd6 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -6,17 +6,9 @@ # Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) # Skip: git commit --no-verify -# ── Colours ────────────────────────────────────────────────────────────────── -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Colour - -print_info() { printf "${BLUE}[pre-commit]${NC} %s\n" "$1"; } -print_success() { printf "${GREEN}[pre-commit]${NC} %s\n" "$1"; } -print_warning() { printf "${YELLOW}[pre-commit]${NC} %s\n" "$1"; } -print_error() { printf "${RED}[pre-commit]${NC} %s\n" "$1"; } +HOOK_NAME=pre-commit +# shellcheck source=.githooks/_common.sh +. "$(dirname "$0")/_common.sh" # ── Collect all staged files ───────────────────────────────────────────────── STAGED=$(git diff --cached --name-only --diff-filter=ACMR) diff --git a/.githooks/pre-push b/.githooks/pre-push index 73aea9a..382750c 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -5,17 +5,9 @@ # Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) # Skip: git push --no-verify -# ── Colours ────────────────────────────────────────────────────────────────── -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Colour - -print_info() { printf "${BLUE}[pre-push]${NC} %s\n" "$1"; } -print_success() { printf "${GREEN}[pre-push]${NC} %s\n" "$1"; } -print_warning() { printf "${YELLOW}[pre-push]${NC} %s\n" "$1"; } -print_error() { printf "${RED}[pre-push]${NC} %s\n" "$1"; } +HOOK_NAME=pre-push +# shellcheck source=.githooks/_common.sh +. "$(dirname "$0")/_common.sh" # ── 1. Unit tests ───────────────────────────────────────────────────────────── print_info "Running unit tests…" diff --git a/.githooks/setup.sh b/.githooks/setup.sh index 85bc012..2b9c269 100755 --- a/.githooks/setup.sh +++ b/.githooks/setup.sh @@ -7,17 +7,9 @@ # chmod +x .githooks/setup.sh # ./.githooks/setup.sh -# ── Colours ────────────────────────────────────────────────────────────────── -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -print_info() { printf "${BLUE}[setup]${NC} %s\n" "$1"; } -print_success() { printf "${GREEN}[setup]${NC} %s\n" "$1"; } -print_warning() { printf "${YELLOW}[setup]${NC} %s\n" "$1"; } -print_error() { printf "${RED}[setup]${NC} %s\n" "$1"; } +HOOK_NAME=setup +# shellcheck source=.githooks/_common.sh +. "$(dirname "$0")/_common.sh" # ── Verify we are inside a git repository ───────────────────────────────────── if ! git rev-parse --git-dir >/dev/null 2>&1; then From 3c307454491ec9783c28f6e9b010a5ce22a541bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:41:42 +0000 Subject: [PATCH 07/13] Reduce schema folders to common/schemas and apps/*/schemas only Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/9e61d503-4e47-4fdc-a14e-1bfddf5763a9 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-commit | 13 +++++-------- .githooks/pre-push | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 416bdd6..0d66289 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -44,7 +44,7 @@ fi # ── 2. Schema (Zod) validation tests ───────────────────────────────────────── # Detect staged files under the known schema folders: -# common/schema, common/schemas, apps/*/schema, apps/*/schemas +# common/schemas, apps/*/schemas SCHEMA_FAILED=0 run_schema_tests() { @@ -63,16 +63,13 @@ run_schema_tests() { return 0 } -# Check common/schema and common/schemas -if echo "$STAGED" | grep -q '^common/schema/'; then - run_schema_tests common/schema || SCHEMA_FAILED=1 -fi +# Check common/schemas if echo "$STAGED" | grep -q '^common/schemas/'; then run_schema_tests common/schemas || SCHEMA_FAILED=1 fi -# Check apps/*/schema and apps/*/schemas (one call per matching app directory) -APP_SCHEMA_DIRS=$(echo "$STAGED" | grep -oE '^apps/[^/]+/schemas?/' | sed 's|/$||' | sort -u) +# Check apps/*/schemas (one call per matching app directory) +APP_SCHEMA_DIRS=$(echo "$STAGED" | grep -oE '^apps/[^/]+/schemas/' | sed 's|/$||' | sort -u) for SCHEMA_DIR in $APP_SCHEMA_DIRS; do run_schema_tests "$SCHEMA_DIR" || SCHEMA_FAILED=1 done @@ -82,7 +79,7 @@ if [ $SCHEMA_FAILED -ne 0 ]; then fi # Report skip only when no schema folders were matched at all -echo "$STAGED" | grep -qE '^common/schemas?/|^apps/[^/]+/schemas?/' \ +echo "$STAGED" | grep -qE '^common/schemas/|^apps/[^/]+/schemas/' \ || print_info "No schema files staged – skipping schema tests." # ── Done ────────────────────────────────────────────────────────────────────── diff --git a/.githooks/pre-push b/.githooks/pre-push index 382750c..6319451 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -36,9 +36,8 @@ print_success "Unit tests passed." if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:schemas']?0:1)" 2>/dev/null; then print_info "Running schema validation tests…" SCHEMA_DIRS="" - [ -d "common/schema" ] && SCHEMA_DIRS="$SCHEMA_DIRS common/schema" [ -d "common/schemas" ] && SCHEMA_DIRS="$SCHEMA_DIRS common/schemas" - for DIR in apps/*/schema apps/*/schemas; do + for DIR in apps/*/schemas; do [ -d "$DIR" ] && SCHEMA_DIRS="$SCHEMA_DIRS $DIR" done From 8de2b46100c9bdba963781997c5ca904b7e9e1b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:00:37 +0000 Subject: [PATCH 08/13] Add CI workflow (.github/workflows/hooks-ci.yml) mirroring git hooks Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/644968c9-45dc-4b48-9f33-20e25873b81a Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .github/workflows/hooks-ci.yml | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 .github/workflows/hooks-ci.yml diff --git a/.github/workflows/hooks-ci.yml b/.github/workflows/hooks-ci.yml new file mode 100644 index 0000000..c5379f5 --- /dev/null +++ b/.github/workflows/hooks-ci.yml @@ -0,0 +1,92 @@ +name: Hooks CI + +on: + push: + branches: [main] + paths: + - 'common/**' + - 'apps/**' + - 'webs/**' + - 'scripts/**' + pull_request: + branches: [main] + paths: + - 'common/**' + - 'apps/**' + - 'webs/**' + - 'scripts/**' + +jobs: + # ── Biome checks (mirrors pre-commit hook) ─────────────────────────────── + biome: + name: Biome Checks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Biome CI – common/iso + if: hashFiles('common/iso/**') != '' + run: npx biome ci common/iso + + - name: Biome CI – common/node + if: hashFiles('common/node/**') != '' + run: npx biome ci common/node + + - name: Biome CI – common/vue + if: hashFiles('common/vue/**') != '' + run: npx biome ci common/vue + + - name: Biome CI – common/web + if: hashFiles('common/web/**') != '' + run: npx biome ci common/web + + - name: Biome CI – apps + if: hashFiles('apps/**') != '' + run: npx biome ci apps + + - name: Biome CI – webs + if: hashFiles('webs/**') != '' + run: npx biome ci webs + + - name: Biome CI – scripts + if: hashFiles('scripts/**') != '' + run: npx biome ci scripts + + # ── Schema validation tests (mirrors pre-push hook) ────────────────────── + schema-tests: + name: Schema Validation Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Schema tests – common/schemas + if: hashFiles('common/schemas/**') != '' + run: npm run test:schemas -- common/schemas + + - name: Schema tests – apps/*/schemas + run: | + for dir in apps/*/schemas; do + [ -d "$dir" ] || continue + echo "Running schema tests for $dir" + npm run test:schemas -- "$dir" + done From 56c5139aacfe04ba4a08b4c8d7957f0b9940c0a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:11:39 +0000 Subject: [PATCH 09/13] feat: add commit-msg hook and commit-lint CI for czg conventional commits Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/73457269-b6f5-4a13-82f4-d9b6eb6d31e3 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/commit-msg | 34 ++++++++++++++++++++++++++++++ .githooks/setup.sh | 6 ++++-- .github/workflows/hooks-ci.yml | 38 +++++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100755 .githooks/commit-msg diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..6941482 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,34 @@ +#!/bin/sh +# .githooks/commit-msg +# Validates the commit message against the Conventional Commits spec. +# Tip: use `npx czg` to compose a properly-formatted message interactively. +# +# Install: run .githooks/setup.sh (or: git config core.hooksPath .githooks) +# Skip: git commit --no-verify + +HOOK_NAME=commit-msg +# shellcheck source=.githooks/_common.sh +. "$(dirname "$0")/_common.sh" + +COMMIT_MSG_FILE="$1" +COMMIT_MSG=$(cat "$COMMIT_MSG_FILE") + +# Skip merge commits, reverts, fixups, and squash commits +case "$COMMIT_MSG" in + Merge\ *|Revert\ *|fixup!\ *|squash!\ *) exit 0 ;; +esac + +# Conventional Commits pattern: type(scope?)[!]: description +PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: .{1,}' + +if ! echo "$COMMIT_MSG" | grep -Eq "$PATTERN"; then + print_error "Commit message does not follow Conventional Commits format." + print_error " Got: $COMMIT_MSG" + print_info " Expected: type(scope): description" + print_info " Types: feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert" + print_info " Tip: use 'npx czg' to generate a valid message interactively." + exit 1 +fi + +print_success "Commit message format OK." +exit 0 diff --git a/.githooks/setup.sh b/.githooks/setup.sh index 2b9c269..2adb3f7 100755 --- a/.githooks/setup.sh +++ b/.githooks/setup.sh @@ -27,8 +27,9 @@ fi # ── Make hook scripts executable ───────────────────────────────────────────── print_info "Making hook scripts executable…" -chmod +x "$HOOKS_DIR/pre-commit" 2>/dev/null && print_success " pre-commit ✓" -chmod +x "$HOOKS_DIR/pre-push" 2>/dev/null && print_success " pre-push ✓" +chmod +x "$HOOKS_DIR/pre-commit" 2>/dev/null && print_success " pre-commit ✓" +chmod +x "$HOOKS_DIR/commit-msg" 2>/dev/null && print_success " commit-msg ✓" +chmod +x "$HOOKS_DIR/pre-push" 2>/dev/null && print_success " pre-push ✓" # ── Configure git to use .githooks/ ────────────────────────────────────────── print_info "Configuring git hooksPath → .githooks" @@ -37,6 +38,7 @@ git config core.hooksPath .githooks if [ $? -eq 0 ]; then print_success "Git hooks installed successfully." print_info " pre-commit: Biome check per directory, schema tests" + print_info " commit-msg: Conventional Commits format check (use 'npx czg' to compose)" print_info " pre-push: unit tests + schema validation" print_info "" print_info "To skip a hook temporarily:" diff --git a/.github/workflows/hooks-ci.yml b/.github/workflows/hooks-ci.yml index c5379f5..be84dea 100644 --- a/.github/workflows/hooks-ci.yml +++ b/.github/workflows/hooks-ci.yml @@ -10,13 +10,41 @@ on: - 'scripts/**' pull_request: branches: [main] - paths: - - 'common/**' - - 'apps/**' - - 'webs/**' - - 'scripts/**' jobs: + # ── Commit message format check (mirrors commit-msg hook) ──────────────── + commit-lint: + name: Commit Message Format + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check commit messages + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([^)]+\))?!?: .{1,}' + FAILED=0 + git log --format="%s" "$BASE_SHA".."$HEAD_SHA" > /tmp/commit_msgs.txt + while IFS= read -r msg; do + case "$msg" in + Merge\ *|Revert\ *|fixup!\ *|squash!\ *) continue ;; + esac + if ! echo "$msg" | grep -Eq "$PATTERN"; then + echo "::error::Non-conventional commit message: $msg" + echo " Expected: type(scope): description" + echo " Types: feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert" + echo " Tip: use 'npx czg' locally to generate a valid message." + FAILED=1 + fi + done < /tmp/commit_msgs.txt + exit $FAILED + # ── Biome checks (mirrors pre-commit hook) ─────────────────────────────── biome: name: Biome Checks From ffe6741e96f68688d5e8429ea71466ff2771d76e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:14:21 +0000 Subject: [PATCH 10/13] feat: add npm audit security check to pre-push hook Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/0e880c6a-3ad8-4fdc-966a-492b50174388 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-push | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.githooks/pre-push b/.githooks/pre-push index 6319451..d868bb1 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -62,6 +62,22 @@ else print_warning "No 'test:schemas' script found – skipping schema tests." fi +# ── 3. Security audit ──────────────────────────────────────────────────────── +print_info "Running security audit…" +npm audit --audit-level=moderate +AUDIT_EXIT=$? +if [ $AUDIT_EXIT -ne 0 ]; then + print_warning "Security vulnerabilities found." + printf "Continue push anyway? (y/n) " + read -r REPLY + case "$REPLY" in + [Yy]*) print_info "Continuing push despite vulnerabilities." ;; + *) print_error "Push aborted. Fix vulnerabilities or use 'git push --no-verify' to bypass."; exit 1 ;; + esac +else + print_success "No security vulnerabilities found." +fi + # ── Done ────────────────────────────────────────────────────────────────────── print_success "All pre-push checks passed. Proceeding with push." exit 0 From 80f45090fae911e52c00e59f77528197e450e4cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:33:45 +0000 Subject: [PATCH 11/13] feat: run tests per impacted workspace using npm run test --workspace Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/96d17e39-7ac6-4e86-839c-3597196130b5 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- .githooks/pre-push | 61 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/.githooks/pre-push b/.githooks/pre-push index d868bb1..813947d 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -9,24 +9,57 @@ HOOK_NAME=pre-push # shellcheck source=.githooks/_common.sh . "$(dirname "$0")/_common.sh" -# ── 1. Unit tests ───────────────────────────────────────────────────────────── -print_info "Running unit tests…" +# ── 1. Unit tests for impacted workspaces ───────────────────────────────────── +print_info "Detecting impacted workspaces…" -if node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test:workspace']?0:1)" 2>/dev/null; then - npm run test:workspace - TEST_EXIT=$? -elif node -e "const p=require('./package.json'); process.exit(p.scripts&&p.scripts['test']?0:1)" 2>/dev/null; then - npm run test - TEST_EXIT=$? +EMPTY_TREE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" +CHANGED_FILES="" + +while read -r local_ref local_sha remote_ref remote_sha; do + # Deleting a branch – nothing to test + [ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue + if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then + FILES=$(git diff --name-only "$EMPTY_TREE" "$local_sha" 2>/dev/null) + else + FILES=$(git diff --name-only "$remote_sha" "$local_sha" 2>/dev/null) + fi + CHANGED_FILES=$(printf '%s\n%s' "$CHANGED_FILES" "$FILES") +done + +CHANGED_FILES=$(printf '%s\n' "$CHANGED_FILES" | sort -u | grep -v '^$') + +IMPACTED_WS="" +for WS_DIR in apps/* common/*; do + [ -d "$WS_DIR" ] || continue + [ -f "$WS_DIR/package.json" ] || continue + if printf '%s\n' "$CHANGED_FILES" | grep -q "^${WS_DIR}/"; then + IMPACTED_WS="$IMPACTED_WS $WS_DIR" + fi +done + +if [ -z "$IMPACTED_WS" ]; then + print_warning "No workspace files changed – skipping unit tests." else - print_warning "No 'test' script found in root package.json – skipping unit tests." TEST_EXIT=0 -fi + for WS in $IMPACTED_WS; do + if node -e "const p=require('./${WS}/package.json'); process.exit(p.scripts&&p.scripts['test']?0:1)" 2>/dev/null; then + print_info "Running tests for workspace: $WS" + npm run test --workspace="$WS" + if [ $? -ne 0 ]; then + print_error "Tests failed for workspace: $WS" + TEST_EXIT=1 + else + print_success "Tests passed for workspace: $WS" + fi + else + print_warning "No 'test' script in $WS – skipping." + fi + done -if [ $TEST_EXIT -ne 0 ]; then - print_error "Unit tests failed. Fix failing tests before pushing." - print_info "Tip: use 'git push --no-verify' to bypass this check if necessary." - exit 1 + if [ $TEST_EXIT -ne 0 ]; then + print_error "Fix failing tests before pushing. Use 'git push --no-verify' to bypass." + exit 1 + fi fi print_success "Unit tests passed." From 580d0ece97944b82eb5f84cd0167fc7bad9ac878 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:48:06 +0000 Subject: [PATCH 12/13] feat: add czg.config.js with workspace scopes for interactive commit prompt Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/37935773-5d60-4163-9c4c-3a0c1bc37b92 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- czg.config.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 czg.config.js diff --git a/czg.config.js b/czg.config.js new file mode 100644 index 0000000..e571535 --- /dev/null +++ b/czg.config.js @@ -0,0 +1,16 @@ +/** @type {import('czg').UserConfig} */ +export default { + prompt: { + scopes: [ + 'NA', + 'apps/app-mcp', + 'apps/app-mcp2', + 'apps/app-sample', + 'common/iso', + 'common/node', + 'common/schemas', + 'common/vue', + 'common/web', + ], + }, +} From 118ff1214a3656c24304d968b66ad950a0b6073a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:01:30 +0000 Subject: [PATCH 13/13] feat: dynamically discover workspace scopes in czg.config.js Agent-Logs-Url: https://github.com/es-labs/express-template/sessions/7c487756-0899-4d15-bf51-74f4e16eef97 Co-authored-by: ais-one <4284574+ais-one@users.noreply.github.com> --- czg.config.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/czg.config.js b/czg.config.js index e571535..0c60517 100644 --- a/czg.config.js +++ b/czg.config.js @@ -1,16 +1,24 @@ +import { readdirSync, statSync } from 'node:fs' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = dirname(fileURLToPath(import.meta.url)) + +function listDirs(parent) { + try { + return readdirSync(join(root, parent)) + .filter((name) => statSync(join(root, parent, name)).isDirectory()) + .map((name) => `${parent}/${name}`) + } catch { + return [] + } +} + +const workspaceScopes = [...listDirs('apps'), ...listDirs('common')] + /** @type {import('czg').UserConfig} */ export default { prompt: { - scopes: [ - 'NA', - 'apps/app-mcp', - 'apps/app-mcp2', - 'apps/app-sample', - 'common/iso', - 'common/node', - 'common/schemas', - 'common/vue', - 'common/web', - ], + scopes: ['NA', ...workspaceScopes], }, }