diff --git a/.gitignore b/.gitignore
index 187669e..4ac36bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,11 @@
.idea/
+.vscode/
.DS_Store
vendor/
+composer.lock
+.phpunit.cache/
+.phpunit.result.cache
+.phpcs-cache
+.phpstan-cache/
+.psalm-cache/
+*.log
diff --git a/README.md b/README.md
index ecbde94..5b76aa0 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,22 @@ bin/magento setup:upgrade
composer remove bradsearch/magento-extension
```
+## Development
+
+```bash
+composer install # installs captainhook pre-commit + pre-push hooks
+composer phpcbf # auto-fix Magento2 coding standard
+composer phpcs # Magento2 coding standard check
+composer phpstan # static analysis at level 6
+composer psalm # static analysis at error level 4
+composer test # phpunit unit tests
+composer check # all of the above
+```
+
+Pre-commit hooks run `phpcbf` + `phpcs` + `phpstan` on staged PHP files.
+Pre-push runs `psalm` + full test suite. CI (GitHub Actions) runs the same
+checks across PHP 7.4 / 8.1 / 8.2 / 8.3 on every PR.
+
## License
Apache License 2.0 - see [LICENSE](LICENSE).
diff --git a/captainhook.json b/captainhook.json
new file mode 100644
index 0000000..e17c320
--- /dev/null
+++ b/captainhook.json
@@ -0,0 +1,67 @@
+{
+ "config": {
+ "run-mode": "php"
+ },
+ "pre-commit": {
+ "enabled": true,
+ "actions": [
+ {
+ "action": "vendor/bin/phpcbf {$STAGED_FILES|of-type:php}",
+ "conditions": [
+ {
+ "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
+ "args": ["php"]
+ }
+ ]
+ },
+ {
+ "action": "vendor/bin/phpcs {$STAGED_FILES|of-type:php}",
+ "conditions": [
+ {
+ "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
+ "args": ["php"]
+ }
+ ]
+ },
+ {
+ "action": "vendor/bin/phpstan analyse {$STAGED_FILES|of-type:php}",
+ "conditions": [
+ {
+ "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
+ "args": ["php"]
+ }
+ ]
+ }
+ ]
+ },
+ "commit-msg": {
+ "enabled": false,
+ "actions": []
+ },
+ "pre-push": {
+ "enabled": true,
+ "actions": [
+ { "action": "composer psalm" }
+ ]
+ },
+ "prepare-commit-msg": {
+ "enabled": false,
+ "actions": []
+ },
+ "post-commit": {
+ "enabled": false,
+ "actions": []
+ },
+ "post-merge": {
+ "enabled": false,
+ "actions": []
+ },
+ "post-checkout": {
+ "enabled": false,
+ "actions": []
+ },
+ "post-rewrite": {
+ "enabled": false,
+ "actions": []
+ }
+}
diff --git a/composer.json b/composer.json
index d472873..7750f20 100644
--- a/composer.json
+++ b/composer.json
@@ -28,6 +28,22 @@
"magento/module-catalog-graph-ql": "*",
"magento/module-store-graph-ql": "*"
},
+ "require-dev": {
+ "squizlabs/php_codesniffer": "^3.7",
+ "magento/magento-coding-standard": "*",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "phpstan/phpstan": "^1.10",
+ "vimeo/psalm": "^6.0 <6.5",
+ "phpunit/phpunit": "^9.6 || ^10.0",
+ "captainhook/captainhook": "^5.22",
+ "captainhook/plugin-composer": "^5.3"
+ },
+ "repositories": {
+ "magento": {
+ "type": "composer",
+ "url": "https://repo.magento.com/"
+ }
+ },
"autoload": {
"files": [
"Analytics/registration.php",
@@ -41,5 +57,28 @@
"BradSearch\\ProductFeatures\\": "ProductFeatures/",
"BradSearch\\SearchGraphQl\\": "SearchGraphQl/"
}
+ },
+ "scripts": {
+ "post-install-cmd": "@register-phpcs-paths",
+ "post-update-cmd": "@register-phpcs-paths",
+ "register-phpcs-paths": "phpcs --config-set installed_paths vendor/magento/magento-coding-standard,vendor/phpcsstandards/phpcsutils,vendor/magento/php-compatibility-fork",
+ "phpcs": "phpcs",
+ "phpcbf": "phpcbf",
+ "phpstan": "phpstan analyse --memory-limit=1G",
+ "psalm": "psalm",
+ "test": "phpunit",
+ "check": [
+ "@phpcs",
+ "@phpstan",
+ "@psalm"
+ ]
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "captainhook/plugin-composer": true,
+ "magento/composer-dependency-version-audit-plugin": true
+ },
+ "sort-packages": true
}
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..7b71e4a
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,37 @@
+
+
+ PHP_CodeSniffer ruleset for bradsearch/magento-extension. Extends Magento2.
+
+
+
+
+
+
+ Analytics
+ Autocomplete
+ ProductFeatures
+ SearchGraphQl
+
+ */Test/*
+ */vendor/*
+
+
+
+
+
+
+
+ SearchGraphQl/Plugin/CatalogGraphQl/Model/Resolver/Aggregations\.php
+ SearchGraphQl/Plugin/CatalogGraphQl/Model/Resolver/Products\.php
+
+
+
+
+ SearchGraphQl/Model/Data/*\.php
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..a28d855
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,25 @@
+
+
+
+
+ ProductFeatures/Test/Unit
+ SearchGraphQl/Test/Unit
+
+
+
+
+ Analytics
+ Autocomplete
+ ProductFeatures
+ SearchGraphQl
+
+
+ */Test
+ */registration.php
+
+
+
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
new file mode 100644
index 0000000..6545a03
--- /dev/null
+++ b/psalm-baseline.xml
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+ queue]]>
+
+
+ collectionFactory]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '"']]>
+
+
+
+
+
+
+
+
+ groupCollection]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ placeholderFactory]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ queue]]>
+
+
+
+
+ syncStateFactory]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ collectionFactory]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ operation !== null]]>
+ operation->name !== null]]>
+ operation)]]>
+ operation) &&
+ $info->operation !== null]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null &&
+ isset($info->operation->name->value)]]>
+
+
+
+
+
+
+
+
+
+
+ operation !== null]]>
+ operation !== null]]>
+ operation->name !== null]]>
+ operation->name !== null]]>
+ operation)]]>
+ operation)]]>
+ operation) &&
+ $info->operation !== null]]>
+ operation) &&
+ $info->operation !== null]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null &&
+ isset($info->operation->name->value)]]>
+ operation) &&
+ $info->operation !== null &&
+ isset($info->operation->name) &&
+ $info->operation->name !== null &&
+ isset($info->operation->name->value)]]>
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..9bf80cb
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+