diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5e9a93e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..978707a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,36 @@ +# Autodetect text files +* text=auto eol=lf + +# ...Unless the name matches the following overriding patterns + +# Definitively text files +*.php text +*.css text +*.js text +*.txt text +*.md text +*.xml text +*.json text +*.bat text +*.sql text +*.yml text + +# Ensure those won't be messed up with +*.phar binary +*.png binary +*.jpg binary +*.gif binary +*.ttf binary + +# Exclude development and metadata files from distribution archive +* export-ignore +/src/ -export-ignore +/src/** -export-ignore +/composer.json -export-ignore +/README.md -export-ignore +/CHANGELOG.md -export-ignore +/LICENSE -export-ignore + +# Avoid merge conflicts in CHANGELOG +# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/ +/CHANGELOG.md merge=union diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml new file mode 100644 index 0000000..3a45b5d --- /dev/null +++ b/.github/workflows/code-style.yml @@ -0,0 +1,38 @@ +name: Code Style + +on: [ pull_request_target ] + +jobs: + php-cs-fixer: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + tools: composer:v2 + coverage: none + + - name: Install Composer dependencies + uses: "ramsey/composer-install@v3" + + - name: Run PHP CS Fixer + run: composer cs-fix + + - name: Run Rector + run: ./vendor/bin/rector --output-format=github + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Apply PHP CS Fixer and Rector changes (CI)" + file_pattern: '*.php' + disable_globbing: true diff --git a/.github/workflows/composer-dependency-analyser.yml b/.github/workflows/composer-dependency-analyser.yml new file mode 100644 index 0000000..af644cb --- /dev/null +++ b/.github/workflows/composer-dependency-analyser.yml @@ -0,0 +1,35 @@ +name: Composer Dependency Analyser + +on: + pull_request: + push: + branches: [ 'master' ] + +jobs: + composer-require-checker: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.4" + + steps: + - name: Checkout. + uses: actions/checkout@v6 + + - name: Install PHP with extensions. + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Check dependencies + run: vendor/bin/composer-dependency-analyser diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..9abc75c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,51 @@ +name: Docker Image + +on: + workflow_dispatch: ~ + workflow_run: + workflows: [ "Code Style", "Composer Dependency Analyser", "PHPStan" ] + branches: [ "master" ] + types: + - completed + +concurrency: + group: docker-image-build + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Get current timestamp + id: timestamp + run: echo "rfc3339=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + - name: Checkout + uses: actions/checkout@v6 + + - name: Login to GitHub container registry + uses: docker/login-action@v3 + with: + registry: https://ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Docker QEMU + uses: docker/setup-qemu-action@v3 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build & push image + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + tags: ghcr.io/phptg/scaffolder:latest + push: ${{ github.ref == 'refs/heads/master' && github.repository_owner == 'phptg' }} + labels: | + org.opencontainers.image.source=${{ github.event.repository.html_url }} + org.opencontainers.image.created=${{ steps.timestamp.outputs.rfc3339 }} + org.opencontainers.image.revision=${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..4f29fb0 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,37 @@ +name: PHPStan + +on: + pull_request: + push: + branches: + - master + +jobs: + phpstan: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.4" + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.php }} + tools: composer:v2, cs2pr + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Static analysis + run: vendor/bin/phpstan analyse --configuration=phpstan.neon diff --git a/.gitignore b/.gitignore index 57872d0..c93151d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +# Composer /vendor/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..7f8f32c --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +in([ + __DIR__ . '/src', +]); + +return new Config() + ->setParallelConfig(ParallelConfigFactory::detect()) + ->setCacheFile(__DIR__ . '/runtime/.php-cs-fixer.cache') + ->setRules([ + '@PER-CS3.0' => true, + 'no_unused_imports' => true, + ]) + ->setFinder($finder); diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8720aae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# PHPTG Scaffolder Change Log + +## under development + +- Initial release. diff --git a/Dockerfile b/Dockerfile index dd6da04..53acbd6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.5-cli-alpine3.22 +FROM php:8.4-cli-alpine3.22 COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3442016 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2026 Sergei Predvoditelev. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index 1d5e3df..89fcca8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,18 @@ .DEFAULT_GOAL := help +IMAGE_NAME := ghcr.io/phptg/scaffolder + +build: ## Build the docker image + docker build -t $(IMAGE_NAME) . + +run: + docker run \ + --volume .:/project \ + --user $(shell id -u):$(shell id -g) \ + --interactive --tty --rm --init \ + $(IMAGE_NAME):latest \ + $(RUN_ARGS) + # Output the help for each task, see https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: ## This help. @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) diff --git a/README.md b/README.md index 3806339..f266b3b 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# Scaffolder Template +
+ + PHPTG + +

PHPTG Scaffolder

+
+
+ +## Documentation + +If you have any questions or problems with this package, use [author telegram chat](https://t.me/predvoditelev_chat) for communication. + +## License + +The `phptg/scaffolder` is free software. It is released under the terms of the BSD License. +Please see [`LICENSE`](./LICENSE) for more information. diff --git a/composer-dependency-analyser.php b/composer-dependency-analyser.php new file mode 100644 index 0000000..b3108dd --- /dev/null +++ b/composer-dependency-analyser.php @@ -0,0 +1,11 @@ +disableComposerAutoloadPathScan() + ->setFileExtensions(['php']) + ->addPathToScan(__DIR__ . '/src', isDev: false); diff --git a/composer.json b/composer.json index 61c6be0..2a0bfa1 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,53 @@ { - "name": "_____/_____", + "name": "phptg/scaffolder", + "description": "PHPTG Scaffolder", + "license": "BSD-3-Clause", "type": "project", - "autoload": { - "psr-4": { - "_____\\": "src/" + "authors": [ + { + "name": "Sergei Predvoditelev", + "email": "sergei@predvoditelev.ru" } + ], + "support": { + "issues": "https://github.com/phptg/scaffolder/issues?state=open", + "chat": "https://t.me/predvoditelev_chat", + "source": "https://github.com/phptg/scaffolder" }, "require": { + "php": "~8.4.0", + "ext-filter": "*", + "symfony/console": "^8.0.3", "vjik/scaffolder": "dev-master" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.3", + "phpstan/phpstan": "^2.1.33", + "rector/rector": "^2.3" + }, + "autoload": { + "psr-4": { + "Phptg\\Scaffolder\\": "src/" + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "bump-after-update": true, + "sort-packages": true + }, + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true, + "target-directory": "tools" + } + }, + "scripts": { + "cs-fix": "php-cs-fixer fix", + "dependency-analyser": "composer-dependency-analyser", + "phpstan": "phpstan analyse -c phpstan.neon", + "rector": "rector" } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..1de14bf --- /dev/null +++ b/composer.lock @@ -0,0 +1,1182 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4e9945344ab966120fc8a21a7cdc7738", + "packages": [ + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "symfony/console", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4|^8.0" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v8.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-23T14:52:06+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", + "reference": "d937d400b980523dc9ee946bb69972b5e619058d", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-01T09:13:36+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-01T09:13:36+00:00" + }, + { + "name": "vjik/scaffolder", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/vjik/scaffolder.git", + "reference": "e46a28d0b531decd8162c83d015827f3f06bff54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vjik/scaffolder/zipball/e46a28d0b531decd8162c83d015827f3f06bff54", + "reference": "e46a28d0b531decd8162c83d015827f3f06bff54", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "php": "8.4.*", + "symfony/console": "^8", + "symfony/filesystem": "^8.0", + "yiisoft/strings": "^2.7" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.92", + "phpstan/phpstan": "^2.1" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Vjik\\Scaffolder\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sergei Predvoditelev", + "email": "sergei@predvoditelev.ru" + } + ], + "support": { + "issues": "https://github.com/vjik/scaffolder/issues", + "source": "https://github.com/vjik/scaffolder/tree/master" + }, + "time": "2026-01-04T15:31:22+00:00" + }, + { + "name": "yiisoft/strings", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/strings.git", + "reference": "9bc7fea56374619cccd4587848029fe97f98bb33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/strings/zipball/9bc7fea56374619cccd4587848029fe97f98bb33", + "reference": "9bc7fea56374619cccd4587848029fe97f98bb33", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "8.1 - 8.5" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "maglnet/composer-require-checker": "^4.7.1", + "phpbench/phpbench": "^1.4.1", + "phpunit/phpunit": "^10.5.48", + "rector/rector": "^2.1.2", + "spatie/phpunit-watcher": "^1.24" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true, + "target-directory": "tools" + } + }, + "autoload": { + "psr-4": { + "Yiisoft\\Strings\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Yii Strings Helper", + "homepage": "https://www.yiiframework.com/", + "keywords": [ + "helper", + "string", + "yii" + ], + "support": { + "chat": "https://t.me/yii3en", + "forum": "https://www.yiiframework.com/forum/", + "irc": "ircs://irc.libera.chat:6697/yii", + "issues": "https://github.com/yiisoft/strings/issues?state=open", + "source": "https://github.com/yiisoft/strings", + "wiki": "https://www.yiiframework.com/wiki/" + }, + "funding": [ + { + "url": "https://github.com/sponsors/yiisoft", + "type": "github" + }, + { + "url": "https://opencollective.com/yiisoft", + "type": "opencollective" + } + ], + "time": "2025-11-23T18:00:58+00:00" + } + ], + "packages-dev": [ + { + "name": "bamarni/composer-bin-plugin", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/bamarni/composer-bin-plugin.git", + "reference": "e7ef9e012667327516c24e5fad9903a3bc91389d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/e7ef9e012667327516c24e5fad9903a3bc91389d", + "reference": "e7ef9e012667327516c24e5fad9903a3bc91389d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "ext-json": "*", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.0", + "symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0", + "symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin" + }, + "autoload": { + "psr-4": { + "Bamarni\\Composer\\Bin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "No conflicts for your bin dependencies", + "keywords": [ + "composer", + "conflict", + "dependency", + "executable", + "isolation", + "tool" + ], + "support": { + "issues": "https://github.com/bamarni/composer-bin-plugin/issues", + "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.3" + }, + "time": "2025-11-24T19:20:55+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.33", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-12-05T10:24:31+00:00" + }, + { + "name": "rector/rector", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "f7166355dcf47482f27be59169b0825995f51c7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/f7166355dcf47482f27be59169b0825995f51c7d", + "reference": "f7166355dcf47482f27be59169b0825995f51c7d", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.33" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-12-25T22:00:18+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "vjik/scaffolder": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~8.4.0", + "ext-filter": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/files/.editorconfig b/files/.editorconfig new file mode 100644 index 0000000..5e9a93e --- /dev/null +++ b/files/.editorconfig @@ -0,0 +1,17 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 diff --git a/files/.gitattributes b/files/.gitattributes new file mode 100644 index 0000000..978707a --- /dev/null +++ b/files/.gitattributes @@ -0,0 +1,36 @@ +# Autodetect text files +* text=auto eol=lf + +# ...Unless the name matches the following overriding patterns + +# Definitively text files +*.php text +*.css text +*.js text +*.txt text +*.md text +*.xml text +*.json text +*.bat text +*.sql text +*.yml text + +# Ensure those won't be messed up with +*.phar binary +*.png binary +*.jpg binary +*.gif binary +*.ttf binary + +# Exclude development and metadata files from distribution archive +* export-ignore +/src/ -export-ignore +/src/** -export-ignore +/composer.json -export-ignore +/README.md -export-ignore +/CHANGELOG.md -export-ignore +/LICENSE -export-ignore + +# Avoid merge conflicts in CHANGELOG +# https://about.gitlab.com/2015/02/10/gitlab-reduced-merge-conflicts-by-90-percent-with-changelog-placeholders/ +/CHANGELOG.md merge=union diff --git a/files/.github/workflows/build.yml b/files/.github/workflows/build.yml new file mode 100644 index 0000000..17760cd --- /dev/null +++ b/files/.github/workflows/build.yml @@ -0,0 +1,48 @@ +on: + pull_request: + push: + branches: [ 'master' ] + +name: build + +jobs: + tests: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + env: + update: true + with: + php-version: ${{ matrix.php }} + ini-values: date.timezone='UTC' + coverage: xdebug + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Run tests with PHPUnit + run: vendor/bin/phpunit --colors=always --coverage-clover=coverage.xml + + - name: Upload coverage to Coveralls + if: matrix.os == 'ubuntu-latest' + uses: coverallsapp/github-action@v2 + with: + file: ./coverage.xml diff --git a/files/.github/workflows/code-style.yml b/files/.github/workflows/code-style.yml new file mode 100644 index 0000000..81fda80 --- /dev/null +++ b/files/.github/workflows/code-style.yml @@ -0,0 +1,38 @@ +name: Code Style + +on: [ pull_request_target ] + +jobs: + php-cs-fixer: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + tools: composer:v2 + coverage: none + + - name: Install Composer dependencies + uses: "ramsey/composer-install@v3" + + - name: Run PHP CS Fixer + run: composer cs-fix + + - name: Run Rector + run: ./vendor/bin/rector --output-format=github + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "Apply PHP CS Fixer and Rector changes (CI)" + file_pattern: '*.php' + disable_globbing: true diff --git a/files/.github/workflows/composer-dependency-analyser.yml b/files/.github/workflows/composer-dependency-analyser.yml new file mode 100644 index 0000000..e11b7c1 --- /dev/null +++ b/files/.github/workflows/composer-dependency-analyser.yml @@ -0,0 +1,38 @@ +name: Composer Dependency Analyser + +on: + pull_request: + push: + branches: [ 'master' ] + +jobs: + composer-require-checker: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout. + uses: actions/checkout@v6 + + - name: Install PHP with extensions. + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Check dependencies + run: vendor/bin/composer-dependency-analyser diff --git a/files/.github/workflows/mutation.yml b/files/.github/workflows/mutation.yml new file mode 100644 index 0000000..25ceac3 --- /dev/null +++ b/files/.github/workflows/mutation.yml @@ -0,0 +1,38 @@ +on: + pull_request: + push: + branches: [ 'master' ] + +name: mutation test + +jobs: + mutation: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.5" + + steps: + - name: Checkout. + uses: actions/checkout@v6 + + - name: Install PHP with extensions. + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + ini-values: memory_limit=-1 + coverage: xdebug + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Run infection. + run: vendor/bin/infection --threads=max --ignore-msi-with-no-mutations + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/files/.github/workflows/phpstan.yml b/files/.github/workflows/phpstan.yml new file mode 100644 index 0000000..cca02e0 --- /dev/null +++ b/files/.github/workflows/phpstan.yml @@ -0,0 +1,40 @@ +name: PHPStan + +on: + pull_request: + push: + branches: + - master + +jobs: + phpstan: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.php }} + tools: composer:v2, cs2pr + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Static analysis + run: vendor/bin/phpstan analyse --configuration=phpstan.neon diff --git a/files/.github/workflows/psalm.yml b/files/.github/workflows/psalm.yml new file mode 100644 index 0000000..d4b844b --- /dev/null +++ b/files/.github/workflows/psalm.yml @@ -0,0 +1,40 @@ +on: + pull_request: + push: + branches: + - master + +name: static analysis + +jobs: + psalm: + name: PHP ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.php }} + tools: composer:v2, cs2pr + + - name: Install Composer dependencies + uses: ramsey/composer-install@v3 + + - name: Static analysis + run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle --php-version=${{ matrix.php }} | cs2pr --graceful-warnings --colorize diff --git a/files/.gitkeep b/files/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/files/.php-cs-fixer.dist.php b/files/.php-cs-fixer.dist.php new file mode 100644 index 0000000..b6f7ea7 --- /dev/null +++ b/files/.php-cs-fixer.dist.php @@ -0,0 +1,21 @@ +in([ + __DIR__ . '/src', + __DIR__ . '/tests', +]); + +return new Config() + ->setParallelConfig(ParallelConfigFactory::detect()) + ->setCacheFile(__DIR__ . '/runtime/.php-cs-fixer.cache') + ->setRules([ + '@PER-CS3.0' => true, + 'no_unused_imports' => true, + ]) + ->setFinder($finder); diff --git a/files/composer-dependency-analyser.php b/files/composer-dependency-analyser.php new file mode 100644 index 0000000..f546a0b --- /dev/null +++ b/files/composer-dependency-analyser.php @@ -0,0 +1,12 @@ +disableComposerAutoloadPathScan() + ->setFileExtensions(['php']) + ->addPathToScan(__DIR__ . '/src', isDev: false) + ->addPathToScan(__DIR__ . '/tests', isDev: true); diff --git a/files/infection.json.dist b/files/infection.json.dist new file mode 100644 index 0000000..3776e22 --- /dev/null +++ b/files/infection.json.dist @@ -0,0 +1,16 @@ +{ + "source": { + "directories": [ + "src" + ] + }, + "logs": { + "text": "php:\/\/stderr", + "stryker": { + "report": "master" + } + }, + "mutators": { + "@default": true + } +} diff --git a/files/logo.png b/files/logo.png new file mode 100644 index 0000000..cf89438 Binary files /dev/null and b/files/logo.png differ diff --git a/files/phpstan.neon b/files/phpstan.neon new file mode 100644 index 0000000..f7f69b2 --- /dev/null +++ b/files/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: 10 + paths: + - src + tmpDir: runtime/phpstan-cache + editorUrl: "phpstorm://open?file=%%file%%&line=%%line%%" + +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/files/phpunit.xml.dist b/files/phpunit.xml.dist new file mode 100644 index 0000000..2052048 --- /dev/null +++ b/files/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + ./tests + + + + + + ./src + + + diff --git a/files/psalm.xml b/files/psalm.xml new file mode 100644 index 0000000..fe82f28 --- /dev/null +++ b/files/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/files/rector.php b/files/rector.php new file mode 100644 index 0000000..cf996d8 --- /dev/null +++ b/files/rector.php @@ -0,0 +1,29 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withPhpSets(php82: true) + ->withConfiguredRule( + AddSensitiveParameterAttributeRector::class, + [ + 'sensitive_parameters' => ['token', 'providerToken', 'secretToken'], + ] + ) + ->withSkip([ + ClosureToArrowFunctionRector::class, + NullToStrictStringFuncCallArgRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + NewInInitializerRector::class, + ]); diff --git a/files/runtime/.gitignore b/files/runtime/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/files/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/files/tools/.gitignore b/files/tools/.gitignore new file mode 100644 index 0000000..cf452dc --- /dev/null +++ b/files/tools/.gitignore @@ -0,0 +1,2 @@ +/*/vendor +/*/composer.lock diff --git a/files/tools/composer-dependency-analyser/composer.json b/files/tools/composer-dependency-analyser/composer.json new file mode 100644 index 0000000..71dd840 --- /dev/null +++ b/files/tools/composer-dependency-analyser/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "shipmonk/composer-dependency-analyser": "^1.8.4" + } +} diff --git a/files/tools/infection/composer.json b/files/tools/infection/composer.json new file mode 100644 index 0000000..d4043ea --- /dev/null +++ b/files/tools/infection/composer.json @@ -0,0 +1,10 @@ +{ + "require-dev": { + "infection/infection": "^0.32" + }, + "config": { + "allow-plugins": { + "infection/extension-installer": true + } + } +} diff --git a/files/tools/php-cs-fixer/composer.json b/files/tools/php-cs-fixer/composer.json new file mode 100644 index 0000000..9be8c6e --- /dev/null +++ b/files/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.92.4" + } +} diff --git a/files/tools/psalm/composer.json b/files/tools/psalm/composer.json new file mode 100644 index 0000000..6f68d79 --- /dev/null +++ b/files/tools/psalm/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "vimeo/psalm": "^6.14.3" + } +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..cf89438 Binary files /dev/null and b/logo.png differ diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f7f69b2 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +parameters: + level: 10 + paths: + - src + tmpDir: runtime/phpstan-cache + editorUrl: "phpstorm://open?file=%%file%%&line=%%line%%" + +includes: + - phar://phpstan.phar/conf/bleedingEdge.neon diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..9ca4c5b --- /dev/null +++ b/rector.php @@ -0,0 +1,28 @@ +withPaths([ + __DIR__ . '/src', + ]) + ->withPhpSets(php84: true) + ->withConfiguredRule( + AddSensitiveParameterAttributeRector::class, + [ + 'sensitive_parameters' => ['token', 'providerToken', 'secretToken'], + ] + ) + ->withSkip([ + ClosureToArrowFunctionRector::class, + NullToStrictStringFuncCallArgRector::class, + ClassPropertyAssignToConstructorPromotionRector::class, + NewInInitializerRector::class, + ]); diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/scaffolder.php b/scaffolder.php new file mode 100644 index 0000000..c417f20 --- /dev/null +++ b/scaffolder.php @@ -0,0 +1,16 @@ + 'PHPTG Scaffolder', + 'disable' => [ + 'docs-internals', + 'tests-directory', + ], + 'prepare-composer-autoload-dev' => false, + 'use-phpunit' => false, + 'use-psalm' => false, + 'use-phpstan' => true, + 'use-infection' => false, +]; diff --git a/src/Change/.gitkeep b/src/Change/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Change/PrepareChangelog.php b/src/Change/PrepareChangelog.php new file mode 100644 index 0000000..5a642b9 --- /dev/null +++ b/src/Change/PrepareChangelog.php @@ -0,0 +1,45 @@ +tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } + + private function createNew(Context $context): string + { + $title = $context->getFact(Title::class); + + return <<tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } + + private function createNew(Context $context): string + { + $blocks = ['# Internals']; + + if ($context->getFact(UsePhpUnit::class)) { + $blocks[] = <<getFact(UsePsalm::class)) { + $blocks[] = <<getFact(UsePhpStan::class)) { + $blocks[] = <<getFact(UseComposerDependencyAnalyser::class)) { + $blocks[] = <<tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpMatrix = implode( + "\n", + array_map( + static fn(MinorPhpVersion $version) => ' - "' . $version->value . '"', + $context->getFact(MinorPhpVersionRange::class), + ), + ); + if ($phpMatrix !== '') { + /** @var string $new */ + $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitHubWorkflowCodeStyle.php b/src/Change/PrepareGitHubWorkflowCodeStyle.php new file mode 100644 index 0000000..f881235 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowCodeStyle.php @@ -0,0 +1,47 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpVersion = $context->getFact(LowestMinorPhpVersion::class); + if ($phpVersion !== MinorPhpVersion::UNKNOWN) { + /** @var string $new */ + $new = preg_replace( + '/^(\s*php-version:\s*)(["\']?)[\d\.]+\2/m', + '${1}' . $phpVersion->value, + $new, + 1, + ); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php new file mode 100644 index 0000000..334f4aa --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowComposerDependencyAnalyser.php @@ -0,0 +1,48 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpMatrix = implode( + "\n", + array_map( + static fn(MinorPhpVersion $version) => ' - "' . $version->value . '"', + $context->getFact(MinorPhpVersionRange::class), + ), + ); + if ($phpMatrix !== '') { + /** @var string $new */ + $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitHubWorkflowMutation.php b/src/Change/PrepareGitHubWorkflowMutation.php new file mode 100644 index 0000000..c832c45 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowMutation.php @@ -0,0 +1,43 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpVersion = $context->getFact(HighestMinorPhpVersion::class); + if ($phpVersion !== MinorPhpVersion::UNKNOWN) { + $phpMatrix = ' - "' . $phpVersion->value . '"'; + /** @var string $new */ + $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitHubWorkflowPhpstan.php b/src/Change/PrepareGitHubWorkflowPhpstan.php new file mode 100644 index 0000000..265a574 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowPhpstan.php @@ -0,0 +1,48 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpMatrix = implode( + "\n", + array_map( + static fn(MinorPhpVersion $version) => ' - "' . $version->value . '"', + $context->getFact(MinorPhpVersionRange::class), + ), + ); + if ($phpMatrix !== '') { + /** @var string $new */ + $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitHubWorkflowPsalm.php b/src/Change/PrepareGitHubWorkflowPsalm.php new file mode 100644 index 0000000..4555b71 --- /dev/null +++ b/src/Change/PrepareGitHubWorkflowPsalm.php @@ -0,0 +1,48 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(__DIR__, 2) . '/files/' . self::FILE); + + $phpMatrix = implode( + "\n", + array_map( + static fn(MinorPhpVersion $version) => ' - "' . $version->value . '"', + $context->getFact(MinorPhpVersionRange::class), + ), + ); + if ($phpMatrix !== '') { + /** @var string $new */ + $new = preg_replace('/^(\s*php:\n)(?:\s*-\s*".*"\n)+/m', "$1$phpMatrix\n", $new, 1); + } + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } +} diff --git a/src/Change/PrepareGitignore.php b/src/Change/PrepareGitignore.php new file mode 100644 index 0000000..a5af9c3 --- /dev/null +++ b/src/Change/PrepareGitignore.php @@ -0,0 +1,53 @@ +tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } + + private function createNew(Context $context): string + { + $lines = [ + '# Composer', + '/vendor/', + ]; + if ($context->getFact(PackageType::class) === 'library') { + $lines[] = '/composer.lock'; + } + $lines[] = ''; + + if ($context->getFact(UsePhpUnit::class)) { + $lines[] = '# PHPUnit'; + $lines[] = '/phpunit.xml'; + $lines[] = ''; + } + + return implode("\n", $lines); + } +} diff --git a/src/Change/PrepareReadme.php b/src/Change/PrepareReadme.php new file mode 100644 index 0000000..2d2b6a7 --- /dev/null +++ b/src/Change/PrepareReadme.php @@ -0,0 +1,107 @@ +tryReadFile(self::FILE); + $new = $original ?? $this->createNew($context); + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } + + private function createNew(Context $context): string + { + $title = $context->getFact(Title::class); + $packageProject = $context->getFact(PackageProject::class); + $phpConstraint = $this->createPhpConstraint($context); + + return << + + PHPTG + +

$title

+
+ + + [![Latest Stable Version](https://poser.pugx.org/phptg/$packageProject/v)](https://packagist.org/packages/phptg/$packageProject) + [![Total Downloads](https://poser.pugx.org/phptg/$packageProject/downloads)](https://packagist.org/packages/phptg/$packageProject) + [![Build status](https://github.com/phptg/$packageProject/actions/workflows/build.yml/badge.svg)](https://github.com/phptg/$packageProject/actions/workflows/build.yml) + [![Coverage Status](https://coveralls.io/repos/github/phptg/$packageProject/badge.svg)](https://coveralls.io/github/phptg/$packageProject) + [![Mutation score](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fphptg%2F$packageProject%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/phptg/$packageProject/master) + [![Static analysis](https://github.com/phptg/$packageProject/actions/workflows/static.yml/badge.svg?branch=master)](https://github.com/phptg/$packageProject/actions/workflows/static.yml?query=branch%3Amaster) + + ## Requirements + + - $phpConstraint. + + ## Installation + + The package can be installed with [Composer](https://getcomposer.org/download/): + + ```shell + composer require phptg/$packageProject + ``` + + ## General usage + + ... + + ## Documentation + + - [Internals](docs/internals.md) + + If you have any questions or problems with this package, use [author telegram chat](https://t.me/predvoditelev_chat) for communication. + + ## License + + The `phptg/$packageProject` is free software. It is released under the terms of the BSD License. + Please see [`LICENSE`](./LICENSE) for more information. + README; + } + + private function createPhpConstraint(Context $context): string + { + $result = $context->getFact(PhpConstraintName::class) === 'php-64bit' ? 'PHP (64-bit)' : 'PHP'; + + $lowest = $context->getFact(LowestMinorPhpVersion::class); + if ($lowest === MinorPhpVersion::UNKNOWN) { + return $result; + } + + $result .= ' ' . $lowest->value; + + $highest = $context->getFact(HighestMinorPhpVersion::class); + if ($highest === MinorPhpVersion::UNKNOWN || $highest === $lowest) { + return $result; + } + + return $result . ' - ' . $highest->value; + } +} diff --git a/src/Change/PrepareRectorConfiguration.php b/src/Change/PrepareRectorConfiguration.php new file mode 100644 index 0000000..0735a27 --- /dev/null +++ b/src/Change/PrepareRectorConfiguration.php @@ -0,0 +1,61 @@ +tryReadFile(self::FILE); + + /** @var string $new */ + $new = $original ?? file_get_contents(dirname(self::FILE, 2) . '/files/' . self::FILE); + + $phpSet = $this->getPhpSet($context); + if ($phpSet === null) { + return null; + } + + /** @var string $new */ + $new = preg_replace( + '/->withPhpSets\([^()]*\)/', + "->withPhpSets($phpSet: true)", + $new, + ); + + if ($original === $new) { + return null; + } + + return static fn(Cli $cli) => $cli->step( + sprintf('Write `%s`', self::FILE), + fn() => $context->writeTextFile(self::FILE, $new), + ); + } + + private function getPhpSet(Context $context): ?string + { + $phpVersion = $context->getFact(LowestMinorPhpVersion::class); + + return match ($phpVersion) { + MinorPhpVersion::PHP82 => 'php82', + MinorPhpVersion::PHP83 => 'php83', + MinorPhpVersion::PHP84 => 'php84', + MinorPhpVersion::PHP85 => 'php85', + default => null, + }; + } +} diff --git a/src/Fact/.gitkeep b/src/Fact/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Fact/UseComposerDependencyAnalyser.php b/src/Fact/UseComposerDependencyAnalyser.php new file mode 100644 index 0000000..d2602c5 --- /dev/null +++ b/src/Fact/UseComposerDependencyAnalyser.php @@ -0,0 +1,34 @@ + + */ +final class UseComposerDependencyAnalyser extends Fact +{ + public const string VALUE_OPTION = 'use-composer-dependency-analyser'; + + public static function configureCommand(SymfonyCommand $command, Params $params): void + { + $command->addOption( + self::VALUE_OPTION, + mode: InputOption::VALUE_OPTIONAL, + default: $params->get(self::VALUE_OPTION, true), + ); + } + + public static function resolve(Cli $cli, Context $context): mixed + { + return filter_var($cli->getOption(self::VALUE_OPTION), FILTER_VALIDATE_BOOLEAN); + } +} diff --git a/src/Fact/UseInfection.php b/src/Fact/UseInfection.php new file mode 100644 index 0000000..0d8d40b --- /dev/null +++ b/src/Fact/UseInfection.php @@ -0,0 +1,34 @@ + + */ +final class UseInfection extends Fact +{ + public const string VALUE_OPTION = 'use-infection'; + + public static function configureCommand(SymfonyCommand $command, Params $params): void + { + $command->addOption( + self::VALUE_OPTION, + mode: InputOption::VALUE_OPTIONAL, + default: $params->get(self::VALUE_OPTION, true), + ); + } + + public static function resolve(Cli $cli, Context $context): mixed + { + return filter_var($cli->getOption(self::VALUE_OPTION), FILTER_VALIDATE_BOOLEAN); + } +} diff --git a/src/Fact/UsePhpStan.php b/src/Fact/UsePhpStan.php new file mode 100644 index 0000000..cdd1b51 --- /dev/null +++ b/src/Fact/UsePhpStan.php @@ -0,0 +1,34 @@ + + */ +final class UsePhpStan extends Fact +{ + public const string VALUE_OPTION = 'use-phpstan'; + + public static function configureCommand(SymfonyCommand $command, Params $params): void + { + $command->addOption( + self::VALUE_OPTION, + mode: InputOption::VALUE_OPTIONAL, + default: $params->get(self::VALUE_OPTION, false), + ); + } + + public static function resolve(Cli $cli, Context $context): mixed + { + return filter_var($cli->getOption(self::VALUE_OPTION), FILTER_VALIDATE_BOOLEAN); + } +} diff --git a/src/Fact/UsePhpUnit.php b/src/Fact/UsePhpUnit.php new file mode 100644 index 0000000..19a58cf --- /dev/null +++ b/src/Fact/UsePhpUnit.php @@ -0,0 +1,34 @@ + + */ +final class UsePhpUnit extends Fact +{ + public const string VALUE_OPTION = 'use-phpunit'; + + public static function configureCommand(SymfonyCommand $command, Params $params): void + { + $command->addOption( + self::VALUE_OPTION, + mode: InputOption::VALUE_OPTIONAL, + default: $params->get(self::VALUE_OPTION, true), + ); + } + + public static function resolve(Cli $cli, Context $context): mixed + { + return filter_var($cli->getOption(self::VALUE_OPTION), FILTER_VALIDATE_BOOLEAN); + } +} diff --git a/src/Fact/UsePsalm.php b/src/Fact/UsePsalm.php new file mode 100644 index 0000000..e2cdfd5 --- /dev/null +++ b/src/Fact/UsePsalm.php @@ -0,0 +1,34 @@ + + */ +final class UsePsalm extends Fact +{ + public const string VALUE_OPTION = 'use-psalm'; + + public static function configureCommand(SymfonyCommand $command, Params $params): void + { + $command->addOption( + self::VALUE_OPTION, + mode: InputOption::VALUE_OPTIONAL, + default: $params->get(self::VALUE_OPTION, true), + ); + } + + public static function resolve(Cli $cli, Context $context): mixed + { + return filter_var($cli->getOption(self::VALUE_OPTION), FILTER_VALIDATE_BOOLEAN); + } +} diff --git a/src/changes.php b/src/changes.php index 0dae23d..1ec3e2c 100644 --- a/src/changes.php +++ b/src/changes.php @@ -2,4 +2,147 @@ declare(strict_types=1); -return []; +use Phptg\Scaffolder\Change\PrepareChangelog; +use Phptg\Scaffolder\Change\PrepareDocsInternals; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowBuild; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowCodeStyle; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowComposerDependencyAnalyser; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowMutation; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowPhpstan; +use Phptg\Scaffolder\Change\PrepareGitHubWorkflowPsalm; +use Phptg\Scaffolder\Change\PrepareGitignore; +use Phptg\Scaffolder\Change\PrepareReadme; +use Phptg\Scaffolder\Change\PrepareRectorConfiguration; +use Phptg\Scaffolder\Fact\UseComposerDependencyAnalyser; +use Phptg\Scaffolder\Fact\UseInfection; +use Phptg\Scaffolder\Fact\UsePhpStan; +use Phptg\Scaffolder\Fact\UsePhpUnit; +use Phptg\Scaffolder\Fact\UsePsalm; +use Vjik\Scaffolder\Change\ChangeIf; +use Vjik\Scaffolder\Change\CopyFile; +use Vjik\Scaffolder\Change\CopyFileIfNotExists; +use Vjik\Scaffolder\Change\EnsureDirectoryWithGitkeep; +use Vjik\Scaffolder\Change\PrepareComposerJson; +use Vjik\Scaffolder\Change\WriteLicense\Bsd3ClauseLicense; +use Vjik\Scaffolder\Change\WriteLicense\WriteLicense; +use Vjik\Scaffolder\Context; +use Vjik\Scaffolder\Fact\PackageProject; +use Vjik\Scaffolder\Fact\SourceDirectory; +use Vjik\Scaffolder\Fact\TestsDirectory; + +$files = dirname(__DIR__) . '/files'; + +return [ + new PrepareComposerJson( + customChange: static function (array $new, Context $context): array { + $project = $context->getFact(PackageProject::class); + $new['support'] = [ + 'issues' => "https://github.com/phptg/$project/issues?state=open", + 'chat' => 'https://t.me/predvoditelev_chat', + 'source' => "https://github.com/phptg/$project", + ]; + + // Composer bin plugin + $new['require-dev']['bamarni/composer-bin-plugin'] ??= '^1.8.3'; + $new['extra']['bamarni-bin'] = [ + 'bin-links' => true, + 'forward-command' => true, + 'target-directory' => 'tools', + ]; + $new['config']['allow-plugins']['bamarni/composer-bin-plugin'] = true; + + // Rector + $new['require-dev']['rector/rector'] ??= '^2.3.0'; + $new['scripts']['rector'] = 'rector'; + + // PHP CS Fixer + $new['scripts']['cs-fix'] ??= 'php-cs-fixer fix'; + + // Psalm + if ($context->getFact(UsePsalm::class)) { + $new['scripts']['psalm'] = 'psalm'; + } + + // PHPStan + if ($context->getFact(UsePhpStan::class)) { + $new['require-dev']['phpstan/phpstan'] ??= '^2.1.33'; + $new['scripts']['phpstan'] = 'phpstan analyse -c phpstan.neon'; + } + + // PHPUnit + if ($context->getFact(UsePhpUnit::class)) { + $new['require-dev']['phpunit/phpunit'] ??= '^11.5.46'; + } + + // Composer Dependency Analyser + if ($context->getFact(UseComposerDependencyAnalyser::class)) { + $new['scripts']['dependency-analyser'] ??= 'composer-dependency-analyser'; + } + + // Infection + if ($context->getFact(UseInfection::class)) { + $new['scripts']['infection'] ??= 'infection --threads=max'; + } + + return $new; + }, + ), + new EnsureDirectoryWithGitkeep(SourceDirectory::class), + 'tests-directory' => new EnsureDirectoryWithGitkeep(TestsDirectory::class), + new PrepareGitignore(), + new CopyFile($files . '/runtime/.gitignore', 'runtime/.gitignore'), + new WriteLicense( + new Bsd3ClauseLicense('Sergei Predvoditelev'), + ), + new CopyFile($files . '/.editorconfig', '.editorconfig'), + new PrepareRectorConfiguration(), + 'readme' => [ + new PrepareReadme(), + new CopyFile($files . '/logo.png', 'logo.png'), + ], + 'docs-internals' => new PrepareDocsInternals(), + new PrepareChangelog(), + new CopyFile($files . '/tools/.gitignore', 'tools/.gitignore'), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/phpunit.xml.dist', 'phpunit.xml.dist'), + new PrepareGitHubWorkflowBuild(), + ], + UsePhpUnit::class, + ), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/tools/psalm/composer.json', 'tools/psalm/composer.json'), + new CopyFileIfNotExists($files . '/psalm.xml', 'psalm.xml'), + new PrepareGitHubWorkflowPsalm(), + ], + UsePsalm::class, + ), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/phpstan.neon', 'phpstan.neon'), + new PrepareGitHubWorkflowPhpstan(), + ], + UsePhpStan::class, + ), + new CopyFileIfNotExists($files . '/tools/php-cs-fixer/composer.json', 'tools/php-cs-fixer/composer.json'), + new CopyFileIfNotExists($files . '/.php-cs-fixer.dist.php', '.php-cs-fixer.dist.php'), + new PrepareGitHubWorkflowCodeStyle(), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/tools/composer-dependency-analyser/composer.json', 'tools/composer-dependency-analyser/composer.json'), + new CopyFileIfNotExists($files . '/composer-dependency-analyser.php', 'composer-dependency-analyser.php'), + new PrepareGitHubWorkflowComposerDependencyAnalyser(), + ], + UseComposerDependencyAnalyser::class, + ), + new ChangeIf( + [ + new CopyFileIfNotExists($files . '/tools/infection/composer.json', 'tools/infection/composer.json'), + new CopyFileIfNotExists($files . '/infection.json.dist', 'infection.json.dist'), + new PrepareGitHubWorkflowMutation(), + ], + UseInfection::class, + ), + new CopyFileIfNotExists($files . '/.gitattributes', '.gitattributes'), +]; diff --git a/src/defaults.php b/src/defaults.php deleted file mode 100644 index 0dae23d..0000000 --- a/src/defaults.php +++ /dev/null @@ -1,5 +0,0 @@ - 'phptg', + 'package-license' => 'BSD-3-Clause', + 'php-constraint-suggestion' => '8.2 - 8.5', + 'user-name' => 'Sergei Predvoditelev', + 'user-email' => 'sergei@predvoditelev.ru', + 'tests-directory' => 'tests/', + 'source-directory' => 'src/', +]; diff --git a/src/run.php b/src/run.php index 15c6664..722b374 100644 --- a/src/run.php +++ b/src/run.php @@ -2,12 +2,19 @@ declare(strict_types=1); +use Vjik\Scaffolder\Change; +use Vjik\Scaffolder\Fact; use Vjik\Scaffolder\Runner; require_once __DIR__ . '/../vendor/autoload.php'; -new Runner( - require __DIR__ . '/changes.php', - require __DIR__ . '/facts.php', - require __DIR__ . '/defaults.php', -)->run(); +/** @var list $changes */ +$changes = require __DIR__ . '/changes.php'; + +/** @var list>> $facts */ +$facts = require __DIR__ . '/facts.php'; + +/** @var array $params */ +$params = require __DIR__ . '/params.php'; + +new Runner($changes, $facts, $params)->run(); diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..cf452dc --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,2 @@ +/*/vendor +/*/composer.lock diff --git a/tools/composer-dependency-analyser/composer.json b/tools/composer-dependency-analyser/composer.json new file mode 100644 index 0000000..71dd840 --- /dev/null +++ b/tools/composer-dependency-analyser/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "shipmonk/composer-dependency-analyser": "^1.8.4" + } +} diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json new file mode 100644 index 0000000..9be8c6e --- /dev/null +++ b/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.92.4" + } +}