diff --git a/.gitattributes b/.gitattributes index f044953..cc4d0f6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,8 +2,6 @@ *.php text diff=php -# Keep Claude tooling scripts out of GitHub's language statistics - # Dev-only, excluded from the Packagist tarball /.github export-ignore /tests export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7a2c836..e9cc769 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,5 +12,5 @@ Closes #... - [ ] Tests added or updated. - [ ] Documentation updated when applicable. -- [ ] `composer review` passes. -- [ ] `composer tests` passes. +- [ ] `make review` passes. +- [ ] `make tests` passes. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index de1576d..e34c801 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -6,7 +6,7 @@ PHP library in the tiny-blocks ecosystem. ## Mandatory pre-task step -Before starting any task, read and strictly follow `CLAUDE.md` and every rule file in +Before starting any task, read and strictly follow `.claude/CLAUDE.md` and every rule file in `.claude/rules/`. These files are the absolute source of truth for code generation. Apply every rule strictly. Do not deviate from the patterns, folder structure, or naming conventions defined in them. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b401ca6..1b5f8ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,42 +11,39 @@ permissions: contents: read jobs: - resolve-php-version: - name: Resolve PHP version + resolve-tooling-image: + name: Resolve tooling image runs-on: ubuntu-latest timeout-minutes: 5 outputs: - php-version: ${{ steps.config.outputs.php-version }} + php-image: ${{ steps.config.outputs.php-image }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Resolve PHP version from composer.json + - name: Resolve tooling image from the Makefile id: config - run: | - version=$(jq -r '.require.php' composer.json | grep -oP '\d+\.\d+' | head -1) - echo "php-version=$version" >> "$GITHUB_OUTPUT" + run: echo "php-image=$(make show-image)" >> "$GITHUB_OUTPUT" build: name: Build - needs: resolve-php-version + needs: resolve-tooling-image runs-on: ubuntu-latest timeout-minutes: 15 + env: + image: ${{ needs.resolve-tooling-image.outputs.php-image }} + workspace: /var/www/html steps: - name: Checkout uses: actions/checkout@v7 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - tools: composer:2 - php-version: ${{ needs.resolve-php-version.outputs.php-version }} - - name: Validate composer.json - run: composer validate --no-interaction + run: docker run --rm -v "${PWD}":${{ env.workspace }} ${{ env.image }} composer validate --no-interaction - name: Install dependencies - run: composer install --no-progress --optimize-autoloader --prefer-dist --no-interaction + run: > + docker run --rm -v "${PWD}":${{ env.workspace }} ${{ env.image }} + composer install --no-progress --optimize-autoloader --prefer-dist --no-interaction - name: Upload vendor and composer.lock as artifact uses: actions/upload-artifact@v7 @@ -58,19 +55,13 @@ jobs: auto-review: name: Auto review - needs: [resolve-php-version, build] + needs: [resolve-tooling-image, build] runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v7 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - tools: composer:2 - php-version: ${{ needs.resolve-php-version.outputs.php-version }} - - name: Download vendor artifact from build uses: actions/download-artifact@v8 with: @@ -78,23 +69,17 @@ jobs: path: . - name: Run review - run: composer review + run: make review tests: name: Tests - needs: [resolve-php-version, auto-review] + needs: [resolve-tooling-image, auto-review] runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v7 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - tools: composer:2 - php-version: ${{ needs.resolve-php-version.outputs.php-version }} - - name: Download vendor artifact from build uses: actions/download-artifact@v8 with: @@ -102,4 +87,4 @@ jobs: path: . - name: Run tests - run: composer tests + run: make tests diff --git a/Makefile b/Makefile index 243682d..6915324 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,19 @@ PWD := $(CURDIR) ARCH := $(shell uname -m) PLATFORM := -TTY := $(shell [ -t 0 ] && echo -it) ifeq ($(ARCH),arm64) PLATFORM := --platform=linux/amd64 endif -PROJECT := $(notdir ${PWD}) -DOCKER_RUN = docker run ${PLATFORM} --rm ${TTY} --net=host -v ${PWD}/..:/workspace -w /workspace/${PROJECT} gustavofreze/php:8.5-alpine +TTY := $(shell [ -t 0 ] && echo -it) + +PHP_VERSION := $(shell sed -n 's/.*"php": *"^\([0-9]*\.[0-9]*\)".*/\1/p' composer.json) +IMAGE_VERSION := 1.0.0 +PHP_IMAGE := gustavofreze/php:${PHP_VERSION}-cli-${IMAGE_VERSION} +WORKSPACE := /var/www/html + +DOCKER_RUN = docker run ${PLATFORM} --rm ${TTY} --net=host -v ${PWD}:${WORKSPACE} ${PHP_IMAGE} RESET := \033[0m GREEN := \033[0;32m @@ -44,6 +49,10 @@ show-reports: ## Open coverage and mutation reports in the browser show-outdated: ## Show outdated direct dependencies @${DOCKER_RUN} composer outdated --direct +.PHONY: show-image +show-image: ## Show the pinned PHP tooling image + @echo ${PHP_IMAGE} + .PHONY: clean clean: ## Remove dependencies and generated artifacts @sudo chown -R ${USER}:${USER} ${PWD} @@ -66,7 +75,7 @@ help: ## Display this help message | awk 'BEGIN {FS = ":.*?## "}; {printf "$(YELLOW)%-25s$(RESET) %s\n", $$1, $$2}' @echo "" @echo "$$(printf '$(GREEN)')Reports$$(printf '$(RESET)')" - @grep -E '^(show-reports|show-outdated):.*?## .*$$' $(MAKEFILE_LIST) \ + @grep -E '^(show-reports|show-outdated|show-image):.*?## .*$$' $(MAKEFILE_LIST) \ | awk 'BEGIN {FS = ":.*?## "}; {printf "$(YELLOW)%-25s$(RESET) %s\n", $$1, $$2}' @echo "" @echo "$$(printf '$(GREEN)')Cleanup$$(printf '$(RESET)')" diff --git a/composer.json b/composer.json index ffb0176..492e40c 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,7 @@ "ergebnis/composer-normalize": true, "infection/extension-installer": true }, + "process-timeout": 0, "sort-packages": true }, "scripts": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 610f14b..839532d 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,14 +3,13 @@ parameters: paths: - src - tests - reportUnmatchedIgnoredErrors: true + tmpDir: reports/phpstan ignoreErrors: # Collection is an extension point designed for `new static()`. Subclasses inherit # the private constructor via factory methods. PHPStan cannot verify constructor # compatibility at the parent level, but the design enforces it by convention. - identifier: new.static path: src/Collection.php - # LazyPipeline defers element production, holding its source as `iterable|Closure` until # iteration. The element type is only realized on materialization, so level max cannot bind # the template through the constructor. `new LazyPipeline(...)` then reads as ``, which @@ -21,69 +20,54 @@ parameters: path: src/Internal/LazyPipeline.php - identifier: return.type path: src/Internal/LazyPipeline.php - # src/Internal/ operations and resolvers carry no PHPDoc per php-library-code-style.md. # Their iterable value types and mixed-origin argument types cannot be annotated in # code (the generics carve-out covers only template type parameters, which these # collaborators do not need). Suppressed by identifier per file. - - identifier: missingType.iterableValue path: src/Internal/Operations/Resolving/Each.php - identifier: callable.nonCallable path: src/Internal/Operations/Resolving/Each.php - - identifier: missingType.iterableValue path: src/Internal/Operations/Resolving/Find.php - identifier: return.type path: src/Internal/Operations/Resolving/Find.php - identifier: argument.type path: src/Internal/Operations/Resolving/Find.php - - identifier: missingType.iterableValue path: src/Internal/Operations/Resolving/Join.php - identifier: argument.type path: src/Internal/Operations/Resolving/Join.php - - identifier: missingType.iterableValue path: src/Internal/Operations/Resolving/Reduce.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Add.php - identifier: missingType.iterableValue path: src/Internal/Operations/Transforming/Add.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Filter.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/FlatMap.php - identifier: missingType.iterableValue path: src/Internal/Operations/Transforming/FlatMap.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/GroupInto.php - identifier: offsetAccess.invalidOffset path: src/Internal/Operations/Transforming/GroupInto.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Map.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Merge.php - identifier: missingType.iterableValue path: src/Internal/Operations/Transforming/Merge.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Rearrange.php - identifier: return.type path: src/Internal/Operations/Transforming/Rearrange.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Remove.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/RemoveAll.php - - identifier: missingType.generics path: src/Internal/Operations/Transforming/Segment.php - identifier: missingType.iterableValue @@ -92,35 +76,29 @@ parameters: path: src/Internal/Operations/Transforming/Segment.php - identifier: generator.keyType path: src/Internal/Operations/Transforming/Segment.php - # Test files: PHPDoc is prohibited on concrete classes. Mixed types originate # from Collection's generic element access (first, last, getBy, reduce, groupBy) # which returns mixed at the PHPStan level. - - identifier: property.nonObject path: tests/CollectionTest.php - identifier: argument.type path: tests/CollectionTest.php - - identifier: property.nonObject path: tests/EagerCollectionTest.php - identifier: argument.type path: tests/EagerCollectionTest.php - - identifier: property.nonObject path: tests/LazyCollectionTest.php - identifier: argument.type path: tests/LazyCollectionTest.php - - identifier: missingType.generics path: tests/Models/Products.php - identifier: missingType.iterableValue path: tests/Models/Products.php - - identifier: missingType.iterableValue path: tests/Models/Shipment.php - - identifier: missingType.iterableValue path: tests/Models/ShipmentRecord.php - identifier: argument.type path: tests/Models/ShipmentRecord.php + reportUnmatchedIgnoredErrors: true