From 4034776e85949bef31b89bcb0899a5840f1b0ff1 Mon Sep 17 00:00:00 2001 From: Salah Al-Nawah Date: Mon, 26 Oct 2026 20:33:16 +0000 Subject: [PATCH 1/3] ci(devops): add a pre-commit hook that formats and lints staged files pnpm install points core.hooksPath at .githooks. The hook runs Prettier and ESLint --fix on staged files only and re-stages them; remaining lint errors refuse the commit. Made-with: agent Co-authored-by: mc-agents[bot] <298817976+mc-agents[bot]@users.noreply.github.com> --- .githooks/pre-commit | 19 +++++++++++++++++++ README.md | 5 +++++ package.json | 3 ++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..1909a38 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,19 @@ +#!/bin/sh +# Format and lint staged files only. Skip in an emergency: git commit --no-verify +cd "$(ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git rev-parse --show-toplevel)" || exit 1 + +staged=$(mktemp) || exit 1 +trap 'rm -f "$staged" "$staged.lint"' EXIT + +ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git diff --cached --name-only --diff-filter=ACMR -z -- \ + '*.ts' '*.tsx' '*.js' '*.json' '*.scss' '*.css' '*.md' >"$staged" +[ -s "$staged" ] || exit 0 + +xargs -0 pnpm exec prettier --write --ignore-unknown --no-error-on-unmatched-pattern <"$staged" || exit 1 + +tr '\0' '\n' <"$staged" | grep -E '\.(ts|tsx|js)$' | tr '\n' '\0' >"$staged.lint" +if [ -s "$staged.lint" ]; then + xargs -0 pnpm exec eslint --fix --no-warn-ignored <"$staged.lint" || exit 1 +fi + +xargs -0 ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git add -- <"$staged" diff --git a/README.md b/README.md index 8fbc41a..db8e6ef 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,11 @@ store's settings don't pin a release (`theme.twilight.version`). | `pnpm test` | Run the test suite (Vitest) | | `pnpm check:manifest` | Validate `twilight.json` | +## Git hook + +`pnpm install` sets `core.hooksPath` to `.githooks`; the pre-commit hook runs Prettier and ESLint (`--fix`) on staged files only, re-stages them, and refuses the commit on remaining lint errors. +In an emergency, skip it with `git commit --no-verify`. + ## Deployment Salla builds and publishes your theme on its own infrastructure — push to your diff --git a/package.json b/package.json index 8164f39..5b90137 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "lint": "eslint .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "prepare": "[ -d .git ] || [ -f .git ] && git config core.hooksPath .githooks || true" }, "dependencies": { "@salla.sa/twilight-components-react": "^3.0.2", From 8bd916c660df6ebd4ff8ac2cad1be37b4ea5e944 Mon Sep 17 00:00:00 2001 From: Salah Al-Nawah Date: Tue, 27 Oct 2026 07:02:37 +0000 Subject: [PATCH 2/3] fix(devops): use plain git in the pre-commit hook and refuse partially staged files decision(devops): reject files with unstaged changes instead of stashing, so the hook never rewrites work the contributor left out of the commit. Made-with: agent Co-authored-by: mc-agents[bot] <298817976+mc-agents[bot]@users.noreply.github.com> --- .githooks/pre-commit | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 1909a38..6589ba9 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,14 +1,27 @@ #!/bin/sh # Format and lint staged files only. Skip in an emergency: git commit --no-verify -cd "$(ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git rev-parse --show-toplevel)" || exit 1 +root=$(git rev-parse --show-toplevel) || exit 1 +cd "$root" || exit 1 staged=$(mktemp) || exit 1 trap 'rm -f "$staged" "$staged.lint"' EXIT -ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git diff --cached --name-only --diff-filter=ACMR -z -- \ - '*.ts' '*.tsx' '*.js' '*.json' '*.scss' '*.css' '*.md' >"$staged" +git diff --cached --name-only --diff-filter=ACMR -z -- \ + '*.ts' '*.tsx' '*.js' '*.json' '*.scss' '*.css' '*.md' >"$staged" || exit 1 [ -s "$staged" ] || exit 0 +# Re-staging a partially staged file would commit its unstaged changes too. +partial=0 +while IFS= read -r file; do + if ! git diff --quiet -- "$file"; then + echo "pre-commit: $file has unstaged changes; stage or stash them first." >&2 + partial=1 + fi +done <"$staged.lint" @@ -16,4 +29,4 @@ if [ -s "$staged.lint" ]; then xargs -0 pnpm exec eslint --fix --no-warn-ignored <"$staged.lint" || exit 1 fi -xargs -0 ghx "$(/opt/opencode-config/plugins/command-guard/bin/ghx-resolver "SAL-3523")" git add -- <"$staged" +xargs -0 git add -- <"$staged" From 01f86a0409fa97edcee7fea24514ef81325e1288 Mon Sep 17 00:00:00 2001 From: Salah Al-Nawah Date: Tue, 27 Oct 2026 10:24:42 +0000 Subject: [PATCH 3/3] fix(devops): install the git hook from a Node script so pnpm install works on Windows decision(devops): the shell one-liner used [ and true, which cmd.exe lacks; a small Node script behaves the same on every platform. Made-with: agent Co-authored-by: mc-agents[bot] <298817976+mc-agents[bot]@users.noreply.github.com> --- package.json | 2 +- scripts/install-hooks.mjs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 scripts/install-hooks.mjs diff --git a/package.json b/package.json index 3014363..fff8f01 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint": "eslint .", "format:check": "prettier --check .", "knip": "knip", - "prepare": "[ -d .git ] || [ -f .git ] && git config core.hooksPath .githooks || true" + "prepare": "node scripts/install-hooks.mjs" }, "dependencies": { "@salla.sa/twilight-components-react": "^3.0.2", diff --git a/scripts/install-hooks.mjs b/scripts/install-hooks.mjs new file mode 100644 index 0000000..87c3522 --- /dev/null +++ b/scripts/install-hooks.mjs @@ -0,0 +1,7 @@ +import { existsSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; + +// Skip quietly outside a git checkout (CI tarballs, published packages). +if (existsSync('.git')) { + spawnSync('git', ['config', 'core.hooksPath', '.githooks'], { stdio: 'ignore' }); +}