diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..8f0f3a3e --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,36 @@ +#!/bin/sh +# Pre-commit gate: Laravel Pint (PHP) + ESLint (JS/TS/Vue) must be clean on staged files. +# See CLAUDE.md "Code Quality Gate". Bypass intentionally (discouraged): git commit --no-verify + +status=0 + +# --- Pint on staged PHP --- +php_files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.php$' || true) +if [ -n "$php_files" ]; then + echo "▶ Pint (pre-commit)…" + # shellcheck disable=SC2086 + if command -v php >/dev/null 2>&1 && [ -f vendor/bin/pint ]; then + php vendor/bin/pint --test -- $php_files || status=1 + else + echo "⚠ Pint unavailable — skipping (run: composer lint before pushing)." >&2 + fi +fi + +# --- ESLint on staged JS/TS/Vue under resources/scripts (ESLint's configured scope) --- +js_files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '^resources/scripts/.*\.(js|cjs|mjs|ts|vue)$' || true) +if [ -n "$js_files" ]; then + echo "▶ eslint (pre-commit)…" + # shellcheck disable=SC2086 + if [ -x node_modules/.bin/eslint ]; then + node_modules/.bin/eslint --max-warnings 0 $js_files || status=1 + else + echo "⚠ eslint unavailable — skipping (run: npm run lint before pushing)." >&2 + fi +fi + +if [ "$status" -ne 0 ]; then + echo "✖ Lint failed — fix it (composer lint:fix / npx eslint --fix), then re-commit." >&2 + echo " To bypass intentionally: git commit --no-verify" >&2 + exit 1 +fi +exit 0 diff --git a/CLAUDE.md b/CLAUDE.md index 44406501..794c55f9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,8 +29,22 @@ Tests use SQLite in-memory DB, configured in `phpunit.xml`. Tests seed via `Data ```bash vendor/bin/pint --dirty --format agent # Fix style on modified PHP files vendor/bin/pint --test # Check style without fixing (CI uses this) +composer lint # = pint --test ; composer lint:fix = pint +npm run lint # eslint (--max-warnings 0) ; npm run lint:fix = eslint --fix ``` +### Code Quality Gate (pre-commit hook) +A committed Git hook (`.githooks/pre-commit`) runs **Pint** on staged `.php` and **ESLint** on staged +`resources/scripts/**` `.{js,cjs,mjs,ts,vue}` files, and **blocks the commit on any failure** (ESLint runs +with `--max-warnings 0`). It lints **staged files only**, and soft-skips if PHP/Pint or `node_modules` is +unavailable (CI is the backstop). The hook is enabled via `core.hooksPath`, set automatically by the +`prepare` script on `npm install`/`yarn install`; to enable it manually run: +```bash +git config core.hooksPath .githooks +``` +Bypass intentionally (discouraged): `git commit --no-verify`. Intentional `v-html` is allowed via an +inline `` with a reason. + ### Artisan Generators Always use `php artisan make:*` with `--no-interaction` to create new files (models, controllers, migrations, tests, etc.). diff --git a/composer.json b/composer.json index 3a9bdba2..c96acdf9 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,8 @@ } }, "scripts": { + "lint": "pint --test", + "lint:fix": "pint", "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" diff --git a/eslint.config.mjs b/eslint.config.mjs index 35f46bfe..6788a096 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,13 +1,39 @@ import pluginVue from 'eslint-plugin-vue' import eslintConfigPrettier from 'eslint-config-prettier' +import tsParser from '@typescript-eslint/parser' export default [ ...pluginVue.configs['flat/recommended'], + // Parse TypeScript inside + + diff --git a/resources/scripts/components/base/InvoiceInformationCard.vue b/resources/scripts/components/base/InvoiceInformationCard.vue index addfb6a6..46a9fff2 100644 --- a/resources/scripts/components/base/InvoiceInformationCard.vue +++ b/resources/scripts/components/base/InvoiceInformationCard.vue @@ -77,7 +77,7 @@ defineProps() {{ $t('invoices.notes') }}
- +
diff --git a/resources/scripts/components/editor/RichEditor.vue b/resources/scripts/components/editor/RichEditor.vue index 925a5355..44c2d6a7 100644 --- a/resources/scripts/components/editor/RichEditor.vue +++ b/resources/scripts/components/editor/RichEditor.vue @@ -23,8 +23,8 @@
diff --git a/resources/scripts/features/admin/modules/views/ModuleDetailView.vue b/resources/scripts/features/admin/modules/views/ModuleDetailView.vue index d3ea2912..88586cd8 100644 --- a/resources/scripts/features/admin/modules/views/ModuleDetailView.vue +++ b/resources/scripts/features/admin/modules/views/ModuleDetailView.vue @@ -285,7 +285,7 @@
-
+
@@ -302,7 +302,7 @@
-
+
diff --git a/resources/scripts/features/admin/views/settings/AdminUpdateAppView.vue b/resources/scripts/features/admin/views/settings/AdminUpdateAppView.vue index 87b2a340..fad95c9d 100644 --- a/resources/scripts/features/admin/views/settings/AdminUpdateAppView.vue +++ b/resources/scripts/features/admin/views/settings/AdminUpdateAppView.vue @@ -372,17 +372,9 @@ function showApiError(error: unknown): void { -
+ -
+
diff --git a/resources/scripts/features/company/estimates/components/SendEstimateModal.vue b/resources/scripts/features/company/estimates/components/SendEstimateModal.vue index 3843ccad..5e01c668 100644 --- a/resources/scripts/features/company/estimates/components/SendEstimateModal.vue +++ b/resources/scripts/features/company/estimates/components/SendEstimateModal.vue @@ -50,8 +50,8 @@ v-model="estimateMailForm.cc" type="email" :invalid="v$.cc && v$.cc.$error" - @input="v$.cc && v$.cc.$touch()" placeholder="Optional: CC recipient" + @input="v$.cc && v$.cc.$touch()" />